RobTillaart / HX711

Arduino library for HX711 24 bit ADC used for load cells and scales.
MIT License
86 stars 29 forks source link

Access to the B-channel of HX711 #27

Closed scboulogne closed 1 year ago

scboulogne commented 1 year ago

HX711 has a 1:2 mux permitting to switch between two input channels, channel A used in this library and B, which is not used. It would be nice to gain access to channel B of HX711. True, it has fixed gain and is not equivalent to A, but it might come handy to measure additional data, such as thermistor voltage (to control temperature as mentioned in the readme), or differential pressure meter.

RobTillaart commented 1 year ago

thanks for the issue, I will look into it later today. I need a good read of the datasheet again...

RobTillaart commented 1 year ago

Are you able to test? - I have no HX711 setup at the moment

RobTillaart commented 1 year ago

@scboulogne Created a develop branch for this issue, no functional changes yet (have to read the datasheet. This branch adds the support of the RP2040 in the build-CI which is on my list, and already increased version to 0.3.4. The version might also be bumped to 0.4.0 depending on how breaking the support for the B channel is.

scboulogne commented 1 year ago

Hello Rob, thank you for your prompt response. Yes I have an HX711 sitting on my table (even 2 of them) and am planing to use it in the near future. I have already built a sensor prototype from an old bathroom scale, and I just need to solder a few wires to the module to get it hooked to Arduino Uno. I haven't used the HX711 or any other such diff amplifier before, am unfamiliar with your code, and so I might need some time to get all to work just with channel A.   The project is to weigh bee hives remotely (in the mountains) and to log the results via gsm (sms) once or twice per day. It aims at having 4 x HX711 modules, and it would be nice to add a temperature sensor to at least one of them. So, I am running out of digital pins, but the analog ones (A0-3) are available, and I can, of course, just add a thermistor or a Pt-resistor. However, it seems more elegant to have a Wheatstone bridge with one or two thermistors in it, and to sample temperature variations on channel B with the same card, along with weight variations on channel A. For bees, this level of accuracy might be an overkill (I teach physics labs and am no expert in bees..), but I have another application in mind, in the student lab, where it is quite important.   best to you, dmitrii

-----Original Message-----

From: Rob @.> To: RobTillaart @.> Cc: scboulogne @.>; Author @.> Date: Wednesday, 2 November 2022 3:10 PM CET Subject: Re: [RobTillaart/HX711] Access to the B-channel of HX711 (Issue #27)

  Are you able to test? - I have no HX711 setup at the moment — Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you authored the thread.Message ID: @.***>

RobTillaart commented 1 year ago

Thanks Dmitrii for the explanation.

one of the things you need to do is to make multiple samples and average them. I recall from similar projects that wind can influence the scales quite a bit. Maybe humidity needs to be monitored too as water can cause extra weight. That said, it is good for students to discover such "anomalies" themselves and explain them.

So, I am running out of digital pins, but the analog ones (A0-3) are available

The analog pins of the UNO can be used as digital ones. See - https://www.arduino.cc/en/Tutorial/Foundations/AnalogInputPins

Going to dive into the datasheet now, Might take a few days, in the meantime if there are questions just ask, R.

RobTillaart commented 1 year ago

Datasheet was only 9 pages and the library already supported all that is needed to read channel B.

To select channel B

HX.set_gain(32);
HX.read();  // dummy read to send the new gain to the sensor

To select channel A

HX.set_gain(128);
HX.read();  // dummy read to send the new gain to the sensor

or

HX.set_gain(64);
HX.read();  // dummy read to send the new gain to the sensor

The version of the library can be downloaded from - https://github.com/RobTillaart/HX711/tree/develop

As the new version of set_gain() returns a bool to indicate valid value one can do

if (HX.set_gain(64) == false) Serial.println("error");
else HX.read();  // dummy read tonly if a new valid value is set.
scboulogne commented 1 year ago

Hello Rob, thank you for looking into this so rapidly. It looks perfect. I have also checked the hx711 datasheet and what you suggest makes sense to me. I will be able to test this on the weekend and will let you know asap.   as for various errors I can face in using strain gauges, temperature is an obvious factor affecting the deformation of the sensor, though I haven't given much thought to it. Thank you for warning about humidity.  I am currently worried most about residual deformation. These sensors are designed for short time stress, like in a bathroom scale. I plan to use them permanently charged. I ignore what residual unrecoverable deformation this would produce in a day or month. For this project to work, the sensor should remain reliable for several days, as the tendency of daily increases/decreases in weight is sought for. On a longer timescale it can drift off a bit, I suppose. So, after I get a functional prototype, I'll leave it working under charge for a month to see what happens.

-----Original Message-----

From: Rob @.> To: RobTillaart @.> Cc: scboulogne @.>; Mention @.> Date: Wednesday, 2 November 2022 5:48 PM CET Subject: Re: [RobTillaart/HX711] Access to the B-channel of HX711 (Issue #27)

  Datasheet was only 9 pages and the library already supported all that is needed to read channel B. To select channel B HX.set_gain(32); HX.read(); // dummy read to send the new gain to the sensor To select channel A HX.set_gain(128); HX.read(); // dummy read to send the new gain to the sensor or HX.set_gain(64); HX.read(); // dummy read to send the new gain to the sensor The version of the library can be downloaded from - https://github.com/RobTillaart/HX711/tree/develop As the new version of set_gain() returns a bool to indicate valid value one can do if (HX.set_gain(64) == false) Serial.println("error"); else HX.read(); // dummy read tonly if a new valid value is set. — Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you were mentioned.Message ID: @.***>

RobTillaart commented 1 year ago

If you use a sensor that can handle e.g. 5x weight of a hive i expect little deformation. If you use the sensor at the max of its scale, to get highest resolution, chances for deformation are bigger.

Read a blog once about how to connect multiple loadcells to one HX to maximize resolution. Have no link sorry.

Alternatively you might use a hinge on one side an a loadcell on the other to scale the weight on the sensor.

RobTillaart commented 1 year ago

https://www.hardysolutions.com/tenants/hardy/documents/5factorsa.pdf

interesting read, It states creep / deformation is a problem, so one might need a construct in which the bee hive is lifted most of the time and put down on the load cells for measurement.

RobTillaart commented 1 year ago

@scboulogne think of adding wrapper functions for set_gain() that are more descriptive

bool set_chanA_gain128()
{
  return set_gain(128);
}

bool set_chanA_gain64()
{
  return set_gain(64);
}

bool set_chanB_gain32()
{
  return set_gain(32);
}

furthermore I will add the dummy read() into set_gain() for convenience.


done

RobTillaart commented 1 year ago

Another option is to define constants and use them in set_gain()

const uint8_t HX711_CHANNEL_A_GAIN_128 = 128;
const uint8_t HX711_CHANNEL_A_GAIN_64 = 64;
const uint8_t HX711_CHANNEL_B_GAIN_32 = 32;

mmm, think I like this one better as it is less an interface break.

scboulogne commented 1 year ago

Hello Rob, I have the thing wired and got it running the "classic" way (borrowing on your performance.ino from examples, but using just raw reads and their averages). Judging from the raw data, it seems to do reasonably within the body of my code (so it doesn't interfere with RTC interrupts, sleep mode, button interrupts, etc, and it shows reasonable raw data which change with weight). Too late for anything more now... I will calibrate it carefully tomorrow to make sure it really does work. And then I will test different channels.   As for the code, I'd also prefer just defining constants. Makes smaller code. However, I would go even further.

define HX711_CHANNEL_A_GAIN_128 128

define HX711_CHANNEL_A_GAIN_64 64

define HX711_CHANNEL_B_GAIN_32 32

I do not see the reason to have them stored as constants in the RAM, especially in the small fast Arduino data RAM. If you really need such things in RAM, though I do not see why, then you can at least push them to PROGMEM (on AVR). The only reason I can come with is if one calls set_gain with the same parameter in many different places across the code. Then having the parameter stored in RAM would save a bit of storage. But this seems to me an unlikely situation, and if it occurs, the programmer will take care of it, eg.

const PROGMEM uint8_t hx711_gain = HX711_CHANNEL_A_GAIN_128;

-----Original Message-----

From: Rob @.> To: RobTillaart @.> Cc: scboulogne @.>; Mention @.> Date: Friday, 4 November 2022 10:28 AM CET Subject: Re: [RobTillaart/HX711] Access to the B-channel of HX711 (Issue #27)

  Another option is to define constants and use them in set_gain() const uint8_t HX711_CHANNEL_A_GAIN_128 = 128; const uint8_t HX711_CHANNEL_A_GAIN_64 = 64; const uint8_t HX711_CHANNEL_B_GAIN_32 = 32; mmm, think I like this one better as it is less an interface break. — Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you were mentioned.Message ID: @.***>

RobTillaart commented 1 year ago

@scboulogne

Thanks for testing (working on another lib right now)

The main reason I go for const uint8_t is that there is compile time type checking, the compiler will optimize it anyway. So it should not differ in size (not tried yet).

to be continued tomorrow.

RobTillaart commented 1 year ago

I do not see the reason to have them stored as constants in the RAM, especially in the small fast Arduino data RAM.

Tested with one of the example sketches and both the #define and the const uint8_t version gave the same size and RAM usage. So I keep the extra type-check as the compiler solves it.

scboulogne commented 1 year ago

you are right about const vs #define, I have checked that too. makes no difference. unlike c, the cpp compiler is smart enough. I think that const does it.   I have gotten the whole thing running today, see the calibration curve attached. It works similarly on all channels with the same slope coefficient (you call it scale) if, of course, the raw data are divided (normalized) on the gain used. So good news. Here are my current problems/observations:  

  1. Unlike the slope, the bias (you call it offset) is a different issue. It seems to shift when you change the gain. However it does float substantially anyway, like by 300-500 gramms in 30min even with gain and weight charge left unchanged and the thing just idling there! So I should investigate first where this drift comes from.  
  2. I'm surprised of how slow this is. Does one read() normally take 100ms on an Uno? And the actual millis() count should be even larger because millis() uses timer0 and you seem to disable interrupts for a while in your read(). I expect a 24-bit ADC take 200 usec, ok 500 max. And a transmission of 24bits at, for example, 9600 baud (like old serial lines of 1980's...) takes 2500 to 3000 usec. So we should expect a rate of at most 5 msec/point, not 100 msec. Btw, this delay doesn't depend on the gain/channel selected and on whether I run the thing in the setup or in the loop (where I put the MCU to deep sleep interrupted by RTC every sec and min, which are later to become hour and day).  
  3. The first point after the gain switch should be rather thrown away. You seem to imply this when you propose to make one dummy read() after set_gain. Ok it is good to know,  but there's nothing in the hardware datasheet that hints to it.   I have to focus on the first issue because it is crucial to the project. It looks like a hardware or an electronic problem to me. The second issue is a bit of a bother too in principle, but not at my target rates of once/hour. Anyone can live with the third point. it is just weird

-----Original Message-----

From: Rob @.> To: RobTillaart @.> Cc: scboulogne @.>; Mention @.> Date: Saturday, 5 November 2022 11:59 AM CET Subject: Re: [RobTillaart/HX711] Access to the B-channel of HX711 (Issue #27)

  I do not see the reason to have them stored as constants in the RAM, especially in the small fast Arduino data RAM. Tested with one of the example sketches and both the #define and the const uint8_t version gave the same size and RAM usage. So I keep the extra type-check as the compiler solves it. — Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you were mentioned.Message ID: @.***>

RobTillaart commented 1 year ago

see the calibration curve attached.

Sorry I see no attachment

bias - offset

This sounds like a new issue to me, please open a separate issue for this as current one was primary to get channel B working. I am thinking in small steps instead of putting too much in one PR.

performance

Yes it is slow due to a blocking interface ... it is mentioned in the code

float HX711::read()
{
  //  this BLOCKING wait takes most time...
  while (digitalRead(_dataPin) == HIGH) yield();

also due to line 330 +336 to handle the T2 time robustly. You can try to remove them and see if that is better

uint8_t HX711::_shiftIn()
{
  // local variables are faster.
  uint8_t clk   = _clockPin;
  uint8_t data  = _dataPin;
  uint8_t value = 0;
  uint8_t mask  = 0x80;
  while (mask > 0)
  {
    digitalWrite(clk, HIGH);
    delayMicroseconds(1);   //  T2  >= 0.2 us   <<<<<<<<<<<<<
    if (digitalRead(data) == HIGH)
    {
      value |= mask;
    }
    digitalWrite(clk, LOW);
    delayMicroseconds(1);   //  keep duty cycle ~50%   <<<<<<<<<<<<<
    mask >>= 1;
  }
  return value;
}

An alternative could be using a boolean flag to check if the delay is needed.

uint8_t HX711::_shiftIn()
{
  // local variables are faster.
  uint8_t clk   = _clockPin;
  uint8_t data  = _dataPin;
  uint8_t value = 0;
  uint8_t mask  = 0x80;
  while (mask > 0)
  {
    digitalWrite(clk, HIGH);
    if (_T2_flag) delayMicroseconds(1);   //  T2  >= 0.2 us   <<<<<<<<<<<<<
    if (digitalRead(data) == HIGH)
    {
      value |= mask;
    }
    digitalWrite(clk, LOW);
    if (_T2_flag) delayMicroseconds(1);   //  keep duty cycle ~50%   <<<<<<<<<<<<<
    mask >>= 1;
  }
  return value;
}

Better option would be to use the "an asynchronous call" by testing bool is_ready() yourself. If not ready, do something else. if ready, read() will be fast too.

RobTillaart commented 1 year ago
  1. The first point after the gain switch should be rather thrown away. You seem to imply this when you propose to make one dummy read() after set_gain. Ok it is good to know, but there's nothing in the hardware datasheet that hints to it.

It is in the protocol if I read correctly between the lines of the datasheet. This is my line of thinking: To set the gain, extra clock pulses (25-27) are needed to inform the HX711. Then the following read() will have the right gain and channel. However the only way to send those extra pulses is to use a read command. (OK a stripped version could do the job but would increase footprint)

scboulogne commented 1 year ago

you are right as usual. the datasheet (fig.2) sort of implies that the gain changing bits should follow the data exchange sequence and will apply for the next data output (they say Next). In my tests, I see that this works as expected. However, there are troubling variations:  

  1. In most cases, one dummy read does the job of switching gain/channel. This can be implemented as  follows (currently I keep it in HX711.h, this can be moved to .cpp)  

  //  GAIN values: 128, 64 32  [20221105 all tested]   //  are defined above as CORE "CONSTANTS" -> read the datasheet   void     set_gain(uint8_t gain = 128) {    if(_gain != gain){      if(gain) _gain = gain;       // gain=0 "enforces" current _gain      read();                               // dummy read is required to set the new gain/channel      // the gain/channel change requires 400ms (table on page 3)    }   };  

  1. However, in one place in my code, repeatedly, this fails... and requires one more read like this

       while(!is_ready());          // read does that anyway?      read();                              // one more dummy read, but why?!  
This occurs after I read four times on channel A (128) and then switch to channel B. Looks like some strange timing issue.  

  1. One more weird problem remains. On power cycle, and according to the datasheet, HX711 comes up in channel-gain A-128. However, when MCU gets simply rebooted, such as when uploading the new sketch, or via the reset button, or serial line reconnection (on linux), HX711 conserves whatever channel-gain it had. This seems logical. Unfortunately, there is no way for the MCU to know that channel-gain and set the _gain variable accordingly. The solution would be to reset HX711. The only way I can see from the datasheet to do that is by cycling power_down(); for 60us and then power_up();. It says that this resets HX711 to A-128. Well, it doesn't. Maybe it doesn't power down, for all I can know now (I think of hooking the scope to E+ and see if HX711 powers the measurement circuit or not). In this case, adding an extra read(); on begin(); may even hang infinitely as read() keeps waiting for is_ready() !!   at the moment, I am out of ideas of how to debug 2. and 3.   PS. I will comment separately about sample rate and bias issues. I agree, they do not belong to this thread (and, maybe, to any thread because these are not really software issues). I was just trying to make sense of how it worked.

-----Original Message-----

From: Rob @.> To: RobTillaart @.> Cc: scboulogne @.>; Mention @.> Date: Saturday, 5 November 2022 9:26 PM CET Subject: Re: [RobTillaart/HX711] Access to the B-channel of HX711 (Issue #27)

 

  1. The first point after the gain switch should be rather thrown away. You seem to imply this when you propose to make one dummy read() after set_gain. Ok it is good to know, but there's nothing in the hardware datasheet that hints to it. It is in the protocol if I read correctly between the lines of the datasheet. This is my line of thinking: To set the gain, extra clock pulses (25-27) are needed to inform the HX711. Then the following read() will have the right gain and channel. However the only way to send those extra pulses is to use a read command. (OK a stripped version could do the job but would increase footprint) — Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you were mentioned.Message ID: @.***>
RobTillaart commented 1 year ago
  //  GAIN values: 128, 64 32  [20221105 all tested] 
  //  are defined above as CORE "CONSTANTS" -> read the datasheet 
  void     set_gain(uint8_t gain = 128) { 
   if(_gain != gain){ 
     if(gain) _gain = gain;       // gain=0 "enforces" current _gain 
     read();                               // dummy read is required to set the new gain/channel 
     // the gain/channel change requires 400ms (table on page 3) 
   } 
  }; 

I notice three differences with current code:

I will remove the test for the _gain == gain so you can enforce a refresh by calling set_gain() with a valid parameter again. No need then to introduce a zero parameter. makes it also clear what value we want to refresh.

The remark about the 400 ms is important, I will add that in the readme.md

RobTillaart commented 1 year ago
  1. However, in one place in my code, repeatedly, this fails... and requires one more read like this
 while(!is_ready());          // read does that anyway?
 read();                              // one more dummy read, but why?!

This occurs after I read four times on channel A (128) and then switch to channel B. Looks like some strange timing issue.

No clue yet. How fast do you change the channel?

RobTillaart commented 1 year ago
  1. HX711 conserves whatever channel-gain it had if it is reset / mcu reboots (summarized) In this case, adding an extra read(); on begin(); may even hang infinitely as read() keeps waiting for is_ready() !!

If you know your sketch does call set_gain() somewhere, you as programmer must initialize the sensor properly in setup() as there is a chance that a reboot or watchdog reset or ... can happen leaving the hardware in an undefined state.

This is not different when I am using a DS18B20 temperature sensor and I put it to a different resolution to do quick measurements sometime and at other moments I want high resolution measurements. In such case I need to do my setup() correctly. Same is true for robotics / servo motors etc, all those devices should be set to a known position. Or the software should be able to read the status from the hardware.

So there will not be a forced read() in begin() as this is the responsibility of the programmer as stated above. This prevents a blocking loop scenario in the library code.

I will add a note in the readme.md in the set_gain() section.

RobTillaart commented 1 year ago

Changes pushed to develop branch.

scboulogne commented 1 year ago

I am using an older lib 0.3.3 which I have downloaded from the link in your letter. there set_gain was still just void.   For now this is for tests an I assume that the programmer knows what he is doing, so no checks for valid gain values. It is up to you to make changes to the lib   I believe that set_gain should accomplish what its name promises, i.e. set the gain both as _gain AND in the hardware. Therefore the (at least one) dummy read belongs in there. On the other hand, if _gain has already the value asked, no dummy read() is needed. This saves 100ms..   However, I ran into the situation when all this doesn't work. Such as after a "hot" reset of the MCU,  when _gain=128 does not necessarily correspond to the actual gain of HX117 an has to be enforced. I agree that asking gain=0 is awkward. So is an extra dummy read(); As I wrote, I have found no solution for the whole issue, because my (?) HX711 doesn't want to be reset.   Same goes for the second dummy read(); I do not like it. But it works for me only that way  

-----Original Message-----

From: Rob @.> To: RobTillaart @.> Cc: scboulogne @.>; Mention @.> Date: Sunday, 6 November 2022 3:46 PM CET Subject: Re: [RobTillaart/HX711] Access to the B-channel of HX711 (Issue #27)

    //  GAIN values: 128, 64 32  [20221105 all tested]   //  are defined above as CORE "CONSTANTS" -> read the datasheet   void     set_gain(uint8_t gain = 128) {    if(_gain != gain){      if(gain) _gain = gain;       // gain=0 "enforces" current _gain      read();                               // dummy read is required to set the new gain/channel      // the gain/channel change requires 400ms (table on page 3)    }   }; I notice three differences with current code:

scboulogne commented 1 year ago

No clue yet. How fast do you change the channel?
  I haven't printed that time. just the time to print a few things after previous read(). here is what I do  

include       // by Rob Tillaart for diff. amplifier module HX711

define HX711_WEIGHT_GAIN HX711_CHAN_A_GAIN_MAX

define HX711_THERMI_GAIN HX711_CHAN_B_GAIN_FIX

// 2022-11-05 on old Terraillon x4 load cells with HX711 A channel (128)

define HX711_SCALE -9.44647  // +/- 0.02527 (0.2675%)

define HX711_SHIFT  11396.5  // +/- 22.3    (0.1957%)

define HX711_TIMES  4        // number of reads/sample

// linear calibration function Vraw/gain -> weght [g] int cell2weight(float Vraw, uint8_t gain) {  return (int) round(HX711_SCALEVraw/gain + HX711_SHIFT); } /    NB: when 5V VADD is used, the full-scale differential input voltage +/- is     20mV (64) or 40mV (128) on channel A, and 80mV (32) on channel B.    After a reset or power-down, input selection defaults to channel A (128).    NB: the raw ADC data is 24bit bipolar, so 32bit int will do */ HX711 myHX711;           // differential amplifier (for load cells etc)

define HX711_DEBUG 0

float sample_HX711(unsigned char cnt) {  float w,t;

if defined(DEBUG) && DEBUG > HX711_DEBUG

 time_t elapse_timer = millis();  Serial.print(F("samples="));  Serial.print(cnt);  Serial.print(F(" "));

endif

 myHX711.set_gain(HX711_WEIGHT_GAIN); // invoques a dummy read if required  while(!myHX711.is_ready());          // read does that anyway?

if defined(DEBUG) && DEBUG > HX711_DEBUG

 Serial.print(F("0x"));  Serial.print(myHX711.get_gain(),HEX);  Serial.print(F(":"));  Serial.print(millis() - elapse_timer);  Serial.print(F("ms "));  elapse_timer = millis();

endif

 w = myHX711.read();

if defined(DEBUG) && DEBUG > 2

 Serial.print(F("HX711 lib "));  Serial.print(HX711_LIB_VERSION);  Serial.print(F(" "));

endif

 Serial.print(F("r="));

if defined(DEBUG) && DEBUG > HX711_DEBUG

 Serial.print((long int) floor(w));  Serial.print(F(" "));

endif

 Serial.print(cell2weight(w, HX711_WEIGHT_GAIN));  w = myHX711.read_average(cnt);  Serial.print(F("g, avg "));  Serial.print((long int) round(w));

if defined(DEBUG) && DEBUG > HX711_DEBUG

 Serial.print(F(":"));  Serial.print(millis() - elapse_timer);  Serial.print(F("ms"));  elapse_timer = millis();

endif

 Serial.print(F(" "));  Serial.print(cell2weight(w, HX711_WEIGHT_GAIN));  Serial.print(F("g"));  myHX711.set_gain(HX711_THERMI_GAIN);   // temperature reading (on channel B)     // this works only if the following TWO lines are inserted here or in set_gain directly

//     while(!is_ready());          // read does that anyway?  //    read();                      // one more dummy read, but why?! // after this reads become regular and take about 100ms each ...  
   

if defined(DEBUG) && DEBUG > HX711_DEBUG

 Serial.print(F(" 0x"));  Serial.print(myHX711.get_gain(),HEX);  Serial.print(F(":"));  Serial.print(millis() - elapse_timer);  Serial.print(F("ms"));  elapse_timer = millis();

endif

 Serial.print(F(" t_avg "));             // (returns previous channel read)  t = myHX711.read_average(cnt);  Serial.print((long int) round(t));

if defined(DEBUG) && DEBUG > HX711_DEBUG

 Serial.print(F(":"));  Serial.print(millis() - elapse_timer);  Serial.print(F("ms"));  elapse_timer = millis();

endif

 Serial.println();  return w; }   I call this 4 times in setup() after begin(), and I can call it any time with a push button interrupt INT0 in the loop(). The two calls show different timings, it is kind of slow read() in the loop(), but I trust the setup sequence more, because in the loop() the MCU stays in POWER_DOWN sleep mode with all timers dead. RTC kicks it every second. And all this might not be very helpful for timing measurements via millis();    
 

-----Original Message-----

From: Rob @.> To: RobTillaart @.> Cc: scboulogne @.>; Mention @.> Date: Sunday, 6 November 2022 3:54 PM CET Subject: Re: [RobTillaart/HX711] Access to the B-channel of HX711 (Issue #27)

 

  1. However, in one place in my code, repeatedly, this fails... and requires one more read like this while(!is_ready()); // read does that anyway? read(); // one more dummy read, but why?!
    This occurs after I read four times on channel A (128) and then switch to channel B. Looks like some strange timing issue. No clue yet. How fast do you change the channel?
    • how much time between calls?
    • and between the last read() call and set_gain()? -- recall your 400 ms comment above. — Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you were mentioned.Message ID: @.***>
RobTillaart commented 1 year ago

Sorry This is the latest version (not yet merged into master) - https://github.com/RobTillaart/HX711/tree/develop

On the other hand, if _gain has already the value asked, no dummy read() is needed. This saves 100ms..

but you stated that sometimes set_gain() needed an extra read() so I assumed you meant that one read was not enough.

when _gain=128 does not necessarily correspond to the actual gain of HX117 an has to be enforced

That is why set_gain() should not test if the value is already set, it should just do a forced set.

So I implemented the following

bool HX711::set_gain(uint8_t gain, bool forced)  //  forced = default false
{
  if ( (not forced) && (_gain == gain)) return true;
  switch(gain)
  {
    case HX711_CHANNEL_B_GAIN_32:
    case HX711_CHANNEL_A_GAIN_64:
    case HX711_CHANNEL_A_GAIN_128:
      _gain = gain;
      read();     //  next user read() is from right channel / gain
      return true;
  }
  return false;   //  unchanged, but incorrect value.
}
RobTillaart commented 1 year ago

... MCU stays in POWER_DOWN sleep mode with all timers dead. RTC kicks it every second. And all this might not be very helpful for timing measurements via millis();

Agree it is not helpfull

Another problem with time measurement is that you must add a delay(25); before starting a measurement. This allows the serial buffer to flush itself, so you won't get interrupts to print the next character anymore. (more output might need a bigger delay)

uint32_t start, stop;
...
delay(25);
start = millis();
do_the_action();
stop = millis();
Serial println(stop - start);
scboulogne commented 1 year ago

If you know your sketch does call set_gain() somewhere, you as programmer must initialize the sensor properly in setup() as there is a chance that a reboot or watchdog reset or ... can happen leaving the hardware in an undefined state.   true. I do precisely that: after my begin(), I tried calling read(); to enforce the default gain (128) which I have on reboot as _gain; and this hangs for reasons which I cannot understand. No matter how long the delay is after begin() ... However, just a bit later, when I do read(), all works fine, albeit the first read is faulty, it has the wrong channel-gain. The thing is, you never know how many dummy reads are needed to make sure that your next read will be valid.   as for the programming style, as I said earlier, I believe that set_gain and get_gain should be as close a s possible to what they announce. Otherwise, they are misleading: the programmer should have a look at your code and should realize that they simply deal with variable _gain which doesn't always reflect to the actual gain of HX711.  

-----Original Message-----

From: Rob @.> To: RobTillaart @.> Cc: scboulogne @.>; Mention @.> Date: Sunday, 6 November 2022 4:06 PM CET Subject: Re: [RobTillaart/HX711] Access to the B-channel of HX711 (Issue #27)

 

  1. HX711 conserves whatever channel-gain it had if it is reset / mcu reboots (summarized) In this case, adding an extra read(); on begin(); may even hang infinitely as read() keeps waiting for is_ready() !! If you know your sketch does call set_gain() somewhere, you as programmer must initialize the sensor properly in setup() as there is a chance that a reboot or watchdog reset or ... can happen leaving the hardware in an undefined state. This is not different when I am using a DS18B20 temperature sensor and I put it to a different resolution to do quick measurements sometime and at other moments I want high resolution measurements. In such case I need to do my setup() correctly. Same is true for robotics / servo motors etc, all those devices should be set to a known position. Or the software should be able to read the status from the hardware. So there will not be a forced read() in begin() as this is the responsibility of the programmer as stated above. This prevents a blocking loop scenario in the library code. I will add a note in the readme.md in the set_gain() section. — Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you were mentioned.Message ID: @.***>
RobTillaart commented 1 year ago

Otherwise, they are misleading: the programmer should have a look at your code and should realize that they simply deal with variable _gain which doesn't always reflect to the actual gain of HX711.

What should those functions be called then?

try_to_set_the_gain_and_channel_but_not_sure_it_works_always() 😂

RobTillaart commented 1 year ago

First thing a programmer has to look at (carefully) is the data sheet of the HX711. Then she or he knows that the device cannot report its gain. Using a library without reading (a bit of ) the datasheet is opportunistic (yes I do it all the time too)

RobTillaart commented 1 year ago

No matter how long the delay is after begin() ... However, just a bit later, when I do read(), all works fine, albeit the first read is faulty, it has the wrong channel-gain. The thing is, you never know how many dummy reads are needed to make sure that your next read will be valid.

I will reread the datasheet (again) to see if there is some information between the lines..

RobTillaart commented 1 year ago

image

bug in datasheet

RobTillaart commented 1 year ago

image

The only way I can see from the datasheet to do that is by cycling power_down(); for 60us and then power_up();. It says that this resets HX711 to A-128. Well, it doesn't.

OK, you tried that.

scboulogne commented 1 year ago

well, right. I do not know how to call set_gain and get_gain. you get the point. leave them like they  are and tell their sad story in the readme.md. set_gain has now some sense in it. but doesn't always work... get_gain is just a cpp gadget to get to the value of the private variable.   at the end, it seems that we will have to say what we know that works and admit in the readme.md that we have no complete solution to this two-channel gain thing.    

-----Original Message-----

From: Rob @.> To: RobTillaart @.> Cc: scboulogne @.>; Mention @.> Date: Sunday, 6 November 2022 5:19 PM CET Subject: Re: [RobTillaart/HX711] Access to the B-channel of HX711 (Issue #27)

  First thing a programmer has to look at (carefully) is the data sheet of the HX711. Then she or he knows that the device cannot report its gain. Using a library without reading (a bit of ) the datasheet is opportunistic (yes I do it all the time too) — Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you were mentioned.Message ID: @.***>

RobTillaart commented 1 year ago

mmm, found a possible problem


void HX711::power_down()
{
  digitalWrite(_clockPin, LOW);
  digitalWrite(_clockPin, HIGH);
}

it does not guarantee the 60 us...

scboulogne commented 1 year ago

mmm, found a possible problem void HX711::power_down() { digitalWrite(_clockPin, LOW); digitalWrite(_clockPin, HIGH); } it does not guarantee the 60 us...

I have tried that already but it made no effect and I didn't want bothering you As I said, I have no other indicator of whether it powers down or not but for the gain that is meant to come back to 128 and it doesn't this is what I tried to use (to no awail)

// from datasheet p.5: When PD_SCK pin changes from low to high and stays at // high for longer than 60us (fig.3), HX711 enters power down mode // (and it remains in power down mode while PD_SCK is high) void HX711::power_down() {  if(!digitalRead(_clockPin)) {    delayMicroseconds(1);           // T1  >= 0.1 us, just in case    digitalWrite(_clockPin, HIGH);  };  delayMicroseconds(64);            // wait until power down >= 60 us  // After a reset or power-down event,  // input selection defaults to Channel A with a gain of 128  // test 20221106: THIS DOESN'T SEEM TO WORK, AS GAIN IS NOT RESTORED TO 128  _gain = 128; } // When PD_SCK returns to low, chip will reset and enter normal operation mode. void HX711::power_up() {  if(digitalRead(_clockPin)) digitalWrite(_clockPin, LOW);  delayMicroseconds(1);             // T1  >= 0.1 us, just in case }  
 

-----Original Message-----

From: Rob @.> To: RobTillaart @.> Cc: scboulogne @.>; Mention @.> Date: Sunday, 6 November 2022 5:49 PM CET Subject: Re: [RobTillaart/HX711] Access to the B-channel of HX711 (Issue #27)

  mmm, found a possible problem void HX711::power_down() { digitalWrite(_clockPin, LOW); digitalWrite(_clockPin, HIGH); } it does not guarantee the 60 us... — Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you were mentioned.Message ID: @.***>

scboulogne commented 1 year ago

since set_gain() with one dummy read() was working "not always",  I have also added small delays into your gain setting loop in read(), meaning that it might not get this sequence all the times (the data exchange is different, there is some work done on shifting registers and some explicit usec delays added in_shiftIn , here there was no time at all)  

  delayMicroseconds(1);     //  typical T1 >= 0.1 us   while (m-- > 0)   {     digitalWrite(_clockPin, HIGH);     delayMicroseconds(1);   //  typical T3 >= 0.2 us     digitalWrite(_clockPin, LOW);     delayMicroseconds(1);   //  typical T4 >= 0.2 us   }   so it kind of reproduces better their timing diagram. helas, to no awail either
 

-----Original Message-----

From: Rob @.> To: RobTillaart @.> Cc: scboulogne @.>; Mention @.> Date: Sunday, 6 November 2022 5:49 PM CET Subject: Re: [RobTillaart/HX711] Access to the B-channel of HX711 (Issue #27)

  mmm, found a possible problem void HX711::power_down() { digitalWrite(_clockPin, LOW); digitalWrite(_clockPin, HIGH); } it does not guarantee the 60 us... — Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you were mentioned.Message ID: @.***>

RobTillaart commented 1 year ago
RobTillaart commented 1 year ago

delayMicroseconds(1); // typical T1 >= 0.1 us ...

Think these should be conditional for very fast digitalWrite() processors e.g. ESP32 @240 MHZ For now I will not change it as the selecting does work.

RobTillaart commented 1 year ago

// After a reset or power-down event, // input selection defaults to Channel A with a gain of 128 // test 20221106: THIS DOESN'T SEEM TO WORK, AS GAIN IS NOT RESTORED TO 128 _gain = 128; }

as it does not work, I will not add the _gain = 128 line.

It is a pity I have no hardware setup at the moment (to busy with other libraries too) so I could test with another board. does your board looks like

RobTillaart commented 1 year ago

@scboulogne I am going to do a last build today, and if tomorrow there are no new insights I will merge the develop branch.

scboulogne commented 1 year ago

mine is very much like the 3rd card on your list, and is even called very similarly XFW-HX711   I am frustrated with the bias issue (today it drifted from +50g to -450g during the afternoon) and am tempted to try the sparkfun one (like the 1st on your list). Maybe they do a better breakout job at sparkfun. Their card has a jumper for 80Hz rate and also dual supply Vcc and Vdd which can be used with 3.3V MCU. However, they have no 2nd channel.   I think that we have covered the original question, which I have asked in this thread: can one use other gains? The answer is yes, but at a cost of 400ms waiting and a bit of pain with dummy read()'s, reboot etc.   Too bad, we couldn't get to the bottom of it all. I will probably have to put a stop here, because I have a lot of teaching and work coming and won't be able to concentrate on this. Besides, I have to think calmly of it all wrt my project, for which using or not channel B is a minor issue.   btw. the reason why they use 24-bit adc in a bathroom scale (!) is the requirement to weigh 80kg to  smth like 1g resolution.

-----Original Message-----

From: Rob @.> To: RobTillaart @.> Cc: scboulogne @.>; Mention @.> Date: Sunday, 6 November 2022 7:44 PM CET Subject: Re: [RobTillaart/HX711] Access to the B-channel of HX711 (Issue #27)

  // After a reset or power-down event, // input selection defaults to Channel A with a gain of 128 // test 20221106: THIS DOESN'T SEEM TO WORK, AS GAIN IS NOT RESTORED TO 128 _gain = 128; } as it does not work, I will not add the _gain = 128 line. It is a pity I have no hardware setup at the moment (to busy with other libraries too) so I could test with another board. does your board looks like

RobTillaart commented 1 year ago

The drift is a loadcell issue, not a library one. Have seen profi loadcells selling for above €100. Where the hobby market they are 10x less. Alibaba might even be cheaper. So I expect differences in quality too.

I have looked at the library Sparkfun is offering, but it gives no clue either. (It is a copy of Bigdes HX lib).

An option might be to mail the manufacturer?

scboulogne commented 1 year ago

well, just to let you know. As I suspected, it appears to have nothing to do with cells but a lot with the card I'm using. Possibly, but very unlikely, also with the power supply (it is 5V from usb and it should have enough for the step down voltage stabilizer of the card to take care of the noise).   The test is simple: replace those cells with four 1K resistors in a Wheatstone bridge. You cannot blame those for whatever instability... The thing should basically measure 0, or a few Ohms due to resistors' 1% error tolerance. Take a few samples (may change polarity if you like positive numbers), compute the average and the r.m.s. error sigma, estimate peak-to-peak as 6*sigma, and obtain effective noise-free bit resolution of the ADC. Guess what I have on eight pre-averaged 4-samples? 10-12 bits noise free! Out of a 24bit setup? The card is total garbage.  

-----Original Message-----

From: Rob @.> To: RobTillaart @.> Cc: scboulogne @.>; Mention @.> Date: Sunday, 6 November 2022 9:55 PM CET Subject: Re: [RobTillaart/HX711] Access to the B-channel of HX711 (Issue #27)

  The drift is a loadcell issue, not a library one. Have seen profi loadcells selling for above €100. Where the hobby market they are 10x less. Alibaba might even be cheaper. So I expect differences in quality too. I have looked at the library Sparkfun is offering, but it gives no clue either. (It is a copy of Bigdes HX lib). An option might be to mail the manufacturer? — Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you were mentioned.Message ID: @.***>

RobTillaart commented 1 year ago

@scboulogne That is indeed a very good test to see if the cells or the card is to blame. Could you wrap this in an analytic sketch?

RobTillaart commented 1 year ago

@scboulogne I am going to merge the develop branch as in essence the issue is solved (datasheet wise in theory) IF there are new insight or other problems please open a new issue.

Thanks for your testing!