olkal / HX711_ADC

Arduino library for the HX711 24-bit ADC for weight scales
MIT License
235 stars 124 forks source link

Missing Timing #81

Closed dsafak closed 2 years ago

dsafak commented 3 years ago

Hi, This is more of a question then an issue but the issue page should be fine for this. Case: Using 3xhx711s at 80hz with 32u4 and some custom port manipulation code to make it quick enough to make it work on 32u4. Result: All works fine with the magic micros(1); delay on conversion24bits method with SCK_DELAY enabled, which takes about 12 cycles so 12*62.4ns. Problem: I moved my code to use another USB stack now I encounter reading errors on 1 out of 3 hx711s here and there. HX711 sets DOUT to "LOW" when data is ready, but it is in free-running mode, so it doesn't hold data till it is read. so when the read window is missed, the pin goes High for the rest of the conversion. Cause: I am unable to hit the magic read timing on 3xhx711s now and one misses time to time. Even if I try inserting delays from 3 Nops to 12 Nops (1 nop = 62.5ns).

I know it is timing related after spending hours on both hx711 and cs1237. It is timing, interrupts something with the new USB Stack / Project braking into calculations and missing the hx711 data read window.

Solution: Using hardware interrupts to enter ISR the moment one of the 3 says data is ready to read. Problem with this solution: Only one interrupt can be served at a time so other 2 might miss, might not miss. (probably won't miss needs testing but I need a new PCB for this so I can use hardware interrupt pins).

Question: Is there any better or other way to make sure that, Code is not going to miss the hx711 read window?

olkal commented 3 years ago

Hi there! Hmm, not sure what you mean by "free-running mode"? The HX711 should hold the data after DOUT goes low, and it should not start a new conversion before the current conversion has been clocked out with 25 SCK pulses. For example, if you have some time consuming code in your sketch the conversions should still read out correctly, but the sample rate (SPS) will of course go down. So there isn't any limited "read window" that I know of. This is also indicated in the data sheet, as T1 (the first "DOUT falling edge to PD_SCK rising edge") don't seem to have an upper time limit.

If I remember correctly you don't have an oscilloscope available to check the actual communication? Would have been interresting to see though...

Regardless, if you want to read out the conversion asap after it's ready I don't think that there is any other way then using interrupt on DOUT.

dsafak commented 3 years ago

As far as I know it is not in data-sheet but HX711 is not holding the data till its read at least at at rate = 1. I remember reading it somewhere too but I'll check again.

By free-running I mean that, it continuously convert and doesn't wait for it to be read. check this out: https://github.com/bogde/HX711/issues/35#issuecomment-246322798

dsafak commented 3 years ago

Further thinking upon this, I realized in this library (HX711_ADC), we only start reading when DOUT goes low, and it is synchronus reads right after that, so this couldn't be free running related as long as I do not miss timing between SCK HIGH and LOW clocks to read data.

There is a long explaination if anyone stumbles upon this, I'll try my best:

It turns out there is an interrupt in my code used by a library (unrelated so I am not going deep into detail on the library) which is triggering an ISR, while the HX711 Data Reading is in progress (conversion24bits method).

ISRs timing is breaking the (delaying conversion24bits method in middle of execution) so the data was getting corrupted.

Probaby because hx711 is in free running mode at 80hz. So this happens: DOUT = LOW Conversion24bits Runs 10 times (10-11-12 this is hyptotetical state of conversion24bits loop state) Interrupt Triggered from another source, ISR enters HX711 starts converting new data, SCK HIGH ISR ends, code is back to Conversion24bits and continues from 11th loop SCK HIGH, LOW doesn't matter next reading bits are all wrong.

@olkal you already knew this so there is an interrupt define in config. I already knew this as well but, I made a typo at some point, so interrupts were not disabled.

Leaving this issue aside, it is solved, this time it wasn't related to timing issue caused by hx711 but ISR:

I still come up with a way to make conversion exactly after DOUT goes low when we are observing and expecting it. Because of free-running mode (assuming it exists) there is still potential for a timing issue.

I believe, I bumped into it couple of times and had to fiddle with delays to get it right in past. But maybe Free-running mode is not a thing, and my past problems were also related to another problem.

so I thought, This issue (The one I just solved) was related to missing timings because of Free running mode, but it wasn't, I just expected it to be related to free-running mode.

Anyway, Here is what I come up with:

inside update() method, at the top, we put this:

     #if defined WaitForNextReading
     while(!digitalRead(doutPin));
     while(digitalRead(doutPin));
     #endif

This is a blocking method but it should transfer the idea behind this. Case: We have 3 cells, they are running in free-running mode, WaitForNextReading is UN-defined Our main loop is running { We enter 1stCell's update() method. If DOUT == LOW else olddata returns conversion24bit(); getData(); We enter 3rdCell's update() method, If DOUT == LOW conversion24bit(); getData(); We enter 3rdCell's update() method, If DOUT == LOW conversion24bit(); getData(); } When did DOUT for all 3 loadcell's become LOW? We do not know. When can DOUT for all 3 loadcell's potentially go HIGH again? we do not know. (Free-running mode)

WaitForNextReading is DEFINED Our main loop is running { We enter 1stCell's update() method While DOUT == LOW, wait. While DOUT == HIGH Wait. If DOUT == LOW conversion24bit(); getData(); ... } This way we make sure We are reading exactly after a conversion is complete and ready to be read. I see this as a somewhat fail-safe guard.

If we could confirm the free-running mode @ rate = 1, Everything I wrote above would start making more sense. So the code above is either useless, or it is not. Depends on HX711's behaviour.

Here: https://github.com/bogde/HX711/issues/35 There is couple of mentions about this, and seems pretty confirmed.

I know this is a long post, but I tried to explain what is going on, and What I think is going on, the best way I could.

olkal commented 3 years ago

Hi So you're saying that @80hz the HX711 starts the next conversions by itself even if you're not done clocking out the data? If this is true, I didn't know that! (I have mostly used it @10hz myself). That would be a rather serious flaw in the HX711 chip I think.

I don't like while() loops much, and you would of course miss the DOUT going low on your other two HX711 while waiting for the first to go low. But I understand that you have it working without this while() loops now that you fixed the disable interrupt code in conversion24bit(), correct?

olkal commented 3 years ago

Hi did some testing with HX711 at 80hz now: Using the library example file Read_1x_load_cell.ino . This example sketch does only read out the data every 250ms there is a delay from the DOUT goes low to the data is clocked out as it is. Nevertheless the readout value is consistent, and no corrupted data during my 5min test. Based on this, I don't think there is such a thing as a non-documented "free-running mode". Your problem was probably caused alone by the un-related ISR triggering, don't you think?

Edit: My test doesn't proove anything as the LoadCell.update() is still called without any delay.

dsafak commented 3 years ago

Hi, Yeah I fixed my problem by fixing typo with interrupts disable flag, I already knew about it, and I thought I already disabled interrupts but the there was a typo. So the issue I had is gone. But that led me into this free-running loop-hole and that ugly while loops (which I don't like as much as you do) and I am aware of its flaws.

But I knew this free-running mode thing way back, I never paid huge-attention because whenever I had a timing issue, I just made the code run faster, and it was gone.

I think matouchat790te from other hx711 library's thread where he/she reporting they noticed undocumented free-running mode is onto something, or maybe different undocumented revisions of hx711s?. Clones?

I'll try this: I'll try to apply dynamic load (static load will return same bits, so can't notice a jump in values) Calculate how long single conversion24bit() takes exactly. Time the start myself so I know my T0. Stop conversion at T1 midway. Wait till known conversion24bit() time is over. Wait conversion24bit() / 2 time more. Continue reading from hx711.

if I see an unexpected jump / value / error, we can be sure it is free running, if not, it is no such thing as free-running @ 80hz. I'll try to get my hands on an oscilloscope, at this point I should buy one already...

My issue is solved but I would like to contribute

JM-DfT commented 3 years ago

Hi, the HX711 device specs (Avia Semiconductor) are very vague on the exact communication protocol: what triggers a next ADC conversion, does it wait until read before starting a conversion, does it continuously convert at a 80sps rate? The only meaningless clue I could read in the datasheet: "The 25th pulse at PD_SCK input will pull DOUT pin back to high" - on which one might assume it will start a new conversion. You could try to contact Avia for exact details: sales@aviaic.com

olkal commented 3 years ago

I have looked at the data sheet for ADS1231, a chip that is not identical but in many ways similar to the HX711 (this library also work with ADS1231). From the ADS1231 datasheet it seems to be indeed "free-running", meaning that it does conversions continuously, and the register data is updated in a "no-read" window of 90us just before the DOUT goes low. The "no-read" period is labeled tupdate in the ADS1231 datasheet. I think we should be able to test if this is the same for HX711 as well.

olkal commented 3 years ago

@JM-DfT: yes, the HX711 datasheet is not good, but looking at Avia's website I don't have much hope of getting more detailed information out of them.

I have done more testing and found some interresting things indicating I was a bit wrong about how the HX711 actually works... The conversions seems indeed to be going on continiously both at 10SPS and 80SPS. At the end of each conversion the DOUT pin goes high for about 75µs before it is pulled low. Assuming that there is some similarity between HX711 and ADS1231 this should be the "tupdate" or "no read" period used for updating the data register before starting next conversion.

But it's not entirely correct to call it a "free-running mode" as it's not a mode at all, the behaviour is the same regardless of 10SPS/80SPS setting. It's just that at 80SPS it's 8 times more likely to accidentally read out data during the "no-read" period.

See results below, just measuring the DOUT pin, no data reading:

HX711_DOUT_@80SPS_no_reading

<html xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel" xmlns="http://www.w3.org/TR/REC-html40">

Numbers measured with Arduino Due | HX711 LOW internal osc. | HX711 HIGH internal osc. | HX711 LOW 20mhz crystal | HX711 HIGH 20mhz crystal -- | -- | -- | -- | -- DOUT low time | 85560µs ±20µs | 10627µs ±20µs | 55243µs ±1µs | 6863µs ±1µs DOUT high time ("no-read" period) | 75µs ±1µs | 75µs ±1µs | 48µs | 48µs Conversion time | 85635µs ±20µs | 10702µs ±20µs | 55291µs ±1µs | 6911µs ±1µs

dsafak commented 3 years ago

@olkal can you draw a diagram like one in datasheet for timings?

olkal commented 3 years ago

Here goes: Upper diagram is based on results from oscilloscope and Arduino Due measurements of DOUT pin without data read-out. Lower diagram is how I think it will look during a data read-out and will take some more work to verify. But I'm not sure I have time for that right now.

HX711_timing <html xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel" xmlns="http://www.w3.org/TR/REC-html40">

Numbers measured with Arduino Due | HX711 LOW internal osc. | HX711 HIGH internal osc. | HX711 LOW 20mhz crystal | HX711 HIGH 20mhz crystal -- | -- | -- | -- | -- t_Update ("no-read" period) | 75µs ±1µs | 75µs ±1µs | 48µs | 48µs t_Conversion | 85635µs ±20µs | 10702µs ±20µs | 55291µs ±1µs | 6911µs ±1µs

dsafak commented 2 years ago

It is funny because this time I hit this issue for real (I hope lol), using my blocking "while code" gets job done but it slows down my 32u4 real bad and I need it to run fast. I made couple of attempts with interrupts but failed so far, I got it to work previously with CS1237 using interrupts at 1280SPS so I need to think smarter I guess but I want to avoid the interrupt route because it messes up with my other stuff.

If anyone figured out something smarter than I have, please let me know, If I find something smarter, I'll let you share under this issue.

Update: I ended up using interrupts it took me a whole day (I was looking for the issue in wrong place -_-) but I would still prefer a solution without interrupts. For future information in case this issue doesn't get a clever solution and someone is stuck, using external interrupts on "Falling Edge" connected to hx711 dout and triggering update() and getData() inside interrupt works just fine, so far my test results show no errors in readings.

dsafak commented 2 years ago

I spend more time thinking about this and drawing cycles etc. I realized 2 things, I might be wrong so here it goes. With a fast enough looping speed through all hx711s, only the first ever reading has a chance of being wrongly timed, when loop is slow, that is not true.

1- We start reading data from hx711, we catch it at a wrong low state, we accidentally enter conversion24bits method because of it, we start pulsing and reading, we get wrong conversion and HX711 returns DATA pin to HIGH within newly found time period. (does it immediately go high after reading? I don't know but regardless it'll go HIGH on itself now that we know it). 2-Loop is fast enough, keeps polling for DOUT LOW, catches the exact moment DOUT goes LOW, reading is correct.

Now, for the next reading and further readings there can be 2 scenarios and I don't have info to determine which will happen. The info required: Does hx711 pull DOUT HIGH, immediately after 24 SCK pulses, or does it wait untill the time period we newly discovered?

Case A: If it goes HIGH immediatly after 24 SCK pulses: 3- Loop again starts polling for DOUT low, Loop is fast enough so it catches it at high and can catch exact moment when it goes LOW, all is fine. Case B: If DOUT doesn't go HIGH Immediatly after reading but waits for fixed time period: 3- Loop again starts polling for DOUT LOW, but if loop is too quick, it'll enter again and there is a big-change while reading same data, the DOUT will go HIGH. So loop neither should be too fast or too slow.

Case A is preffered one, also HX711 has sleep features etc. on extra pulses, so considering all works fine, I believe that is the case. But I need a second thought on what I come up with.

Also there is one more thing, all of this creates a real challenge for reading multiple hx711s. Loop can be fast enough but we still have to pulse each 711 at least 25 times and do some processing to get data which we are losing time while doing it. So there is a limit on how fast someone can pulse SCK high and Low and gather data, for myself I am at limits of 32u4 and I am suprised hx711 actually does work fine with such a short HIGH and LOW pulse period.

In my case, I got them working with interrupts and disabling interrupt of one that has been read, and re-enabling all when all hx711 is read (using EIMSK inside ISR to disable the ISR that run, and enabling them back when in loop IF(EIMSK==0) is true), this solution seems to be good but I didn't want to interrupt on DOUT LOW (for reasons special to my case outside scope of this issue). so I come up with another way around.

I created a timer interrupt and started doing my time-heavy code 500 times a second, that meant I now got a fast enough loop, so I placed 3 hx711 update code into the loop and got them to work. Loop reads 1st 711, 2nd 711 and 3rd 711 in sequence, by the time it reaches 3rd loadcell it is still not too late so it can retrieve data before 3rd loadcell pulls DOUT back to HIGH, I am not sure how many can be read in a sequence like this.

So there is still a possibility that if say someone is trying to read 6x HX711s with high SPS, by the time Loop reaches 6th loadcell no matter how fast it loops, it has to slow down for each HX711, so that 6th or nTH (whatever) hx711 could switch back to HIGH while it is being read.

@olkal sorry for posting paragraph long messages. Maybe someone someday will find this helpful. I don't think there is any escaping this but one can only design around it.

olkal commented 2 years ago

Hi! I can confirm that DOUT is pulled high immediately after the last CLK pulse like it's shown in the diagram "24-bit data retrieval" in my post above. If you need to read a lot of HX711's I think it's better to read out data from the one that at any time first had DOUT pulled low rather than reading them in a fixed sequence. I guess you could create a timestamp for the DOUT low interrupt, and then avoid clocking out the value during the no-read period.

dsafak commented 2 years ago

That is also a good idea. I got my preferred optimal way working for now, but well anyone facing this have 4 workaround options now.

olkal commented 2 years ago

This issue was closed because it has been inactive for some time