read an high precision adc like hx711

Posted on
Page
of 2
/ 2
Next
  • Hi,
    I've tried to connect the hx711 to espruino to measure a loadcell
    unfortunately, the hx711 pd_sck pin asks for a pulse of 1 and 0 (24 to get the bits of reading) then 1-3 bits for the gain setting.
    and the max time for the 1 period is 50microseconds (pcd_sck high time)
    cdn.sparkfun.com/datasheets/Sensors/Forc­eFlex/hx711_english.pdf

    and the espruino min delay for a digitalwrite is 160 microseconds. I've measured with my oscilloscope.

    so the hx711 is "too fast" for the espruino.

    Do you have an idea how I can get the adc values ?

  • Hi - there's actually another post just today on interfacing the HX711 - it might be some help: http://forum.espruino.com/conversations/­287032/#comment12991616

    Have you tried it? I'd be a little surprised if the timeout was such that it didn't work.

    If it does need pulsing that quick, you have a few options:

    • Use SPI SCK + MISO (but then you'll only be able to send 24 bits)
    • Use SPI MOSI + MISO - send 0b10101010 as the SPI data - then you'll just have to pick out every other bit from the returned result. Even software SPI should be fast enough for this. You can just make sure the final byte is only 0b10100000 or whatever is needed to set the gain
    • Use minified, unrolled JS - it should be just about fast enough. Something like:

    function get() {
      var s = CLK.set.bind(CLK);  
      var r = CLK.reset.bind(CLK);
      var b = DATA.read.bind(DATA);
      var v = 0;
    s();r();v=(v<<1)|b();s();r();v=(v<<1)|b(­);s();r();v=(v<<1)|b();s();r();v=(v<<1)|­b();
    s();r();v=(v<<1)|b();s();r();v=(v<<1)|b(­);s();r();v=(v<<1)|b();s();r();v=(v<<1)|­b();
    s();r();v=(v<<1)|b();s();r();v=(v<<1)|b(­);s();r();v=(v<<1)|b();s();r();v=(v<<1)|­b();
    s();r();v=(v<<1)|b();s();r();v=(v<<1)|b(­);s();r();v=(v<<1)|b();s();r();v=(v<<1)|­b();
    s();r();v=(v<<1)|b();s();r();v=(v<<1)|b(­);s();r();v=(v<<1)|b();s();r();v=(v<<1)|­b();
    s();r();v=(v<<1)|b();s();r();v=(v<<1)|b(­);s();r();v=(v<<1)|b();s();r();v=(v<<1)|­b();
    s();r(); // gain
    return v;
    }
    
    • Use 'compiled' JS - http://www.espruino.com/Compilation - this would definitely be fast enough, but maybe isn't as portable as it needs the online web service to compile the JS into native code each time you upload.

    Hope that helps!

  • Hi,
    I stumbled upon THIS webpage for the electric imp.
    Github link to the code
    It has implemented the read function using :

    function read_weight(){
        //This is bit banged so we 
        //bind calls to local variables
        //to speed things up.
        //Takes about 2.2mS to do the read
        local sw  = spi.write.bindenv(spi);
        local dr  = data.read.bindenv(data);
        local val = 0;
    
        for(local i = 24; i >= 0; i--){
            sw(PULSE);
            val += dr() ? math.pow(2, i) : 0;
        }
    
        return (val-ZERO_OFFSET)/GAIN;  //Value in grams
    }
    

    It does point out that I should have typed >0 in the for statement not 1...
    I will test my code again with the refinements suggested by Gordon later tonight hopefully and will report back.

    Many thanks,

    Aaron

  • sorry, I've mistaked.
    I managed to have a 22microsecond pulse with digitalPulse(..,.., 1000/65000) for a 65khz freq.
    I've checked with an oscillo.
    I'll finish my hx711 javascript port and post it there, and on the other thread.

  • Hi. I've tried with the Gordon get()'s fonction.
    it works, but the value read is not "stable" comparing to arduino.
    the code here :
    https://learn.sparkfun.com/tutorials/loa­d-cell-amplifier-hx711-breakout-hookup-g­uide
    calibration code normally continuously reads a 0 'lbs' value, on arduino it works, on espruino, the value goes from 0.004 lbs up to -1000 lbs sometimes, meaning that the adc is not read correctly, surely because of too long high time for the CLK pulse (value > 60 us puts to power down mode)
    I've used "compiled" directive.

  • That's a shame - I'd have thought that especially the code posted above, when compiled, would be more than fast enough.

    The other option is something like:

    var s = new SPI();
    s.setup({ miso : data_in_pin, sck : clk_out_pin });
    var d = s.send([0b10101010,0b10101010,0b10101010­,0b10101010,0b10101010,0b10101010,0b1000­0000]);
    var v = 0;
    for (var i=0;i<5;i++) {
      v = (v<<1) | ((d[i]>>6)&1);
      v = (v<<1) | ((d[i]>>4)&1);
      v = (v<<1) | ((d[i]>>2)&1);
      v = (v<<1) | (d[i]&1);
    }
    
  • I just tried. values read with espruino are more stable, but still much less stable than the arduino values.
    the signal sent on sck is very fast. sck high time is 1,5 microsecond.
    but those burst are composed by much faster high/low very small bursts, and seems to confuse the hx711.
    So this is not precise enough for me, until I have a solution like arduino, that will sent stable precise bursts.
    anyway, thanks for your help.

  • Ok, use hardware SPI - it should provide a steady train of pulses. Instead of:

    var s = new SPI();
    s.setup({ miso : data_in_pin, sck : clk_out_pin });
    

    do:

    var s = SPI1;
    s.setup({ miso : data_in_pin, sck : clk_out_pin, baud: 1000000 });
    

    you'll just have to choose pins like B3 and B4 that work on SPI1: http://www.espruino.com/Pico#pinout

  • the value are still not stable.
    if you want I can send you my code, if you have a hx711 to test it.
    values returned are 25000 ... 31000
    arduino's are much more stable.

  • I don't have an HX711 to test with - you could post your code up here though in case I can see anything wrong with it. Have you tried different baud rates?

    It could also be a power supply issue - as on Arduino you may be running it from 5V, but you're using 3.3V on Espruino?

    However as I said in the other thread, you're reading a 24 bit value, so it could give anything up to 16 million back. The number might be changing by 6000, but that's only 0.0375% - which is a pretty small variation.

    How about trying it with some load on it, and seeing if it actually works?

  • Hello,
    Could you pls post your code for testing?
    I have some HX711, so I would test it using both 3.3V and 5V.
    As I experienced that using HX711 with 3.3V is not so stable.
    Thanks in advance.

  • you have here all the versions, tests I've made.
    the main one is hx711.js, the other ones are derivated, with the attempts based on this thread's advices.
    actually, I have a working project with 2 load sensors, working with arduino, I'm still unsuccessfull with espruino, not stable and precise enough.


    1 Attachment

  • Thanks, I'll give it a try.

  • After reading this thread I almost didn't even bother to try to use an HX711 with espruino, but I somehow decided to give it a go anyway, and I'm glad I did. It is possible to get nice stable and accurate (and precise!) readings from an HX711 chip and a load cell using espruino.
    I'm using the MDBT42Q breakout board powered by a 9V battery. When powered by a CR2032, my board would flash red, which I'm assuming means it couldn't supply enough current.

    I'm using a shielded HX711 breakout board (from Amazon: http://a.co/ge0HhQR) and a balanced 4-wire load cell.

    I'm connecting the standard Red,Black,Green,White wires from the load cell to the board pins like this:

    Red --> "Out+" (that's how it's labeled on this particular board)
    White --> A-
    Green --> A+
    Black --> GND

    From the board to the espruino, I have the following connections:
    VCC --> 3.3
    DO/RX --> D15
    CLK/TX --> D14
    GND --> GND

    Here is the code I'm using:

    var gain_grams = 0.00103123388;
    var zero = 162050;
    
    var s = SPI1;
    s.setup({ miso : D15, sck : D14, baud: 1000000 });
    
    var lastReadTime = 0;
    
    function readRaw(){
        var d = s.send([0b11111111,0b11111111,0b11111111­]);
        var val = d[0]*65536 + d[1]*256 + d[0];
        lastReadTime = Date.now();
        return val;
    }
    
    function tare(){
        zero = readRaw(); 
    }
    
    function readGrams(){
        return (readRaw()-zero)*gain_grams; 
    }
    
    

    I get the same accuracy and better precision than my cheap store-bought digital kitchen scale. I'm getting about 0.1g precision reliably.

    Note that as long as I wait a while between reads, I get very consistent values back. If I do multiple reads one after the other without waiting in between, then I get high variability. I don't know why that is. Earlier in this forum thread there was talk about needing to follow the 24 pulses with 1-3 pulses for selecting the gain level, but that was problematic for me, as it caused erroneous readings when I tried that. Maybe I didn't do it right. But for my needs, with a simple wait between reads, I'm getting great results. I currently force it to wait 1 second between reads, which is more than enough, and not too slow of a sample rate for my application.

  • That's great! Thanks for letting us know - is it ok if I turn that code into a module for Espruino?

  • ...once more: flakyness comes from power supply... as experienced a while back when the ESP8266 became available and communication was flaky... either with Espruino combined or Espruino on ESP8266...

    Espruino once again has proven to be rock solid!

    PS: The waits... could they be needed because reading is triggering another convert from the sensor and storing the value in the register from where it is read? Also, switching between idle and operate may play an issue - fluctuation in current... I did not read up HX711's internals...

  • Hi Gordon, a module would be fantastic! Your modules are so nicely designed and their plug-and-play nature really saves a developer like me lots of time and headache, making the espruino such a great platform to build on.

  • Thanks! Please can you try:

    h=require("https://github.com/espruino/E­spruinoDocs/blob/master/devices/HX711.js­").connect({
      sck : B15,
      miso : LED2,
      lsbGrams : 0.00103123388,
      mode : "A128"
    });
    h.tare();
    setInterval(function() {
      print(h.readGrams());
    }, 1000);
    

    and see if it works for you?

    I don't have one to test with here so it's guesswork really.

    The module itself is here if you want to take a look at what's available: https://github.com/espruino/EspruinoDocs­/blob/master/devices/HX711.js

    I tried to make it so that it will output the correct number of pulses - I think you probably hit issues because if the delay is too great between pulses it can really throw it off.

    I also added isReady - since it looks like you can tell if a reading is ready by checking the state of the DATA line - it might help with allowing you to get a good reading at a higher data rate.

  • For some reason, your module has the variability issue that I first ran into and was trying to fix. Although, to be fair, your module's variability is not that bad, and probably acceptable, but my implementation gets better results and I'm not sure why.

    I did a simple test. I ran the code and let it sit for several seconds, then placed a 28g weight on the load cell. I captured a screenshot comparing both your module and my original code:

    Note the high variability on the left and the low variability on the right. Also, the multiplier is the same 0.00103123388 for both, so it's interesting that the module says the 28g weight is twice as much (56g). That might be a clue as to what is going on?

    I also noticed that I'm sending a string of 1's ([0b11111111,0b11111111,0b11111111]), but in your module, you added zeros between the ones ([0b10101010,0b10101010,0b10101010,0b101­01010,0b10101010,,0b10101010]), and added the finalClk at the end as well. The other difference is software vs hardware SPI.

    Gordon, let me know what I can do to help get this to the end zone of a nice working module.

  • Thanks - that is interesting... The 2x difference in readings may be because the gain is set differently - the number of clocks sent specifies the gain and channel to be used for the next reading. I have no idea what 24 clocks does as it's not in the datasheet, but 25 clocks would be 128x gain and 27 clocks is 64x gain. Of course it could be to do with the serial data as well...

    What I'm doing with 0b10101010 is I'm using the data line as a clock, since if I used the actual clock line it'd only be able to output a multiple of 8 clocks.

    Sadly the JS execution speed isn't fast enough to simply clock the data in using JS code at the moment (as if it's too slow the ADC will just reset itself).

    Could you try copying the module code out of https://github.com/espruino/EspruinoDocs­/blob/master/devices/HX711.js into the IDE and changing:

    function ex(x) { return ((x&128)?8:0)|((x&32)?4:0)|((x&8)?2:0)|(­(x&2)?1:0); }
    

    to

    function ex(x) { return ((x&64)?8:0)|((x&16)?4:0)|((x&4)?2:0)|((­x&1)?1:0); }
    

    That'll change the point at which the data line is sampled (as it might have been read too quickly).

    Maybe you could also print readRaw().toString(2) for your and my code as seeing what binary data is received might flag up what's wrong with my code.

  • I made your function change, Gordon, but I'm getting pretty much the same results.

    I see what you are trying to do with using mosi to send data as clock ticks. However, when I dig into it, it doesn't look to be very "clock like". I hooked it up to a scope, and here's what I'm seeing:

    The yellow on the bottom is the actual spi clock (sck), and the cyan on the top is your mosi 0b10101010 data. If you were to scroll rightward along time, you'd see 6 of those 0b10101010 waveforms followed by the finalClk. This image only captures the first two bytes. A few things to note:
    1) There appears to be a 30 microsecond spacing between each byte of data transmitted.
    2) mosi appears to be normally high between bytes, which appears to result in a very long clock tick between each 0b10101010.
    3) Since the mosi signal is normally high, that very first "1" tick probably doesn't count since it was already at 1, and thus didn't go from 0 to 1, which might be why all the bits are shifted over by one resulting in the data reporting back as 2x the actual value. Yup, I just verified this by changing your 0b10101010 to 0b01010101, and the 2x multiplier went away.

    You probably have some additional tricks up your sleeve which I'm more than happy to try. In the mean time, If you are okay with it, I'd like to modify your module to use hardware SPI1, and to use the actual SPI clock to send the 24 ticks. It seems to work great even though, as you say, the data sheet doesn't specify what 24 clocks does. It also doesn't allow one to change the gain, etc., but it's very stable and seems to work really well with whatever gain 24 clocks is resulting in...

  • I've been testing more after changing line 38 in your module from:

    var c = 0b10101010;
    

    to:

    var c = 0b01010101;
    

    The 2x scale factor is removed and the variability has gone down and it's actually quite good now. If I round to the nearest 0.25 grams, its quite stable, and that's all the resolution I need for my application. I'm thinking it would be fine to release this as a beta version of the module with just line 38 changed. Don't change line 41 to what you had me test. The original ex() function works better.

    Cool!

  • That's really interesting. Which pin are you using? And you're on the MDBT42Q with 1v99 firmware?

    I actually hooked it up to a scope before posting to check it was doing the right thing, and I see the opposite of you (0v normally on mosi with 4 pulses) so I'm very surprised it's got inverted somehow.

    There is the ~30uS gap between pulses as you note, but looking at the datasheet that should be ok.

    SPI should normally transmit most significant bit first, so if the lowest bit is 0, that should be the state the MOSI line is left in...

  • Yes, I'm using MDBT42Q with 1v99 firmware. I'm using pins D14 and D15. Using D14 for the clock. I just restarted the board by disconnecting the battery and reconnecting. I'm now no longer getting the inverted mosi! I've tried for the last hour to reproduce getting that inversion back, but I haven't been successful yet. Any ideas on how it got into that state? Some internal pullup or something that didn't get reset? It probably would go unnoticed for most SPI situations since during the clock ticks, the voltage is correct. It's just in between the bytes that it goes high. But, after resetting, I'm not able to reproduce. Argh!! If you think that might be a bug somehow, I'm happy to try things that will help track this down if you want me to.

    With more testing, I did catch something that I didn't catch before which might be causing the variability in the readings. There appears to be an intermittent variability in the gaps between the mosi bytes. Normally, it's 30uS, but sometimes it's much higher. Here are a couple of examples:

    Notice that there can sometimes be over 100uS gaps. Have you run into this with software SPI? These longer gaps seem relatively infrequent. I'm wondering if this is causing the variability in the weight readings due to the ADC timing out or something. I haven't yet been able to correlate these two things though.

    Sorry. I feel like this is muddying the water instead of adding clarity.

  • For what it's worth, here is the version of the module that I'm using. It's the most stable and precise of all the versions I've tested so far. It uses the SPI clock to send 24 ticks, and it uses hardware SPI1, which appears to be twice as fast as software SPI. It doesn't allow changing of the mode, as that requires a nonmultiple of 8 ticks. Here it is:

    /**
    Options should contain:
    {
      sck   : PD_SCK pin
      miso  : DOUT pin
      lsbGrams : grams per LSB
    */
    function HX711(options) {
      options = options||{};
      this.sck = options.sck;
      if (!this.sck) throw "Expecting sck";
      this.miso = options.miso;
      if (!this.miso) throw "Expecting miso";
      //this.mode = options.mode||"A128"; //changing the mode isn't supported yet
      this.lsbGrams = options.lsbGrams||1;
      /* The spec sheet specifies sending 24 ticks followed by 1-3 ticks for the gain.
         However, we are using the SPI clock which can only send multiples of 8 ticks.
         The HX711 seems to be fine with only getting 24 ticks.
      */
      this.spi = SPI1;//new SPI(); // use hardware SPI instead of software SPI
      this.spi.setup({miso:this.miso, sck:this.sck, baud: 1000000});
      //this.sck.write(0); // idle, but on
      this.zero = 0;
    }
    
    HX711.prototype.readRaw = function() {
        var d = this.spi.send([0,0,0]);
        var val = d[0]*65536 + d[1]*256 + d[2];
        return val;
    };
    /// Set the current reading to be the zero
    HX711.prototype.tare = function() {
      this.zero = this.readRaw();
    };
    /// Calibrate the lsbGrams value by putting a known weight on the scale
    /// then call this function passing in the grams of the known weight.
    HX711.prototype.calibrateLSBGrams = function( knownGrams ) {
      if(!this.zero){
        throw "Must call tare() with zero weight first before calibrating.";
      }
      if(!knownGrams){
        throw "Must supply a non-zero value for the known weight in grams.";
      }
      this.lsbGrams = knownGrams/this.readRaw();
    };
    /// Read the ADC and return the result in grams (based on options.lsbGrams)
    HX711.prototype.readGrams = function() {
      return (this.readRaw() - this.zero) * this.lsbGrams;
    };
    /// Is data ready for retrieval?
    HX711.prototype.isReady = function() {
      return !this.miso.read();
    };
    /// Set whether the ADC is in standby mode or not
    HX711.prototype.setStandby = function(isStandby) {
      this.sck.write(isStandby);
    };
    
    exports.connect = function(options) {
      return new HX711(options);
    };
    

    Below is an example of using it where I call tare() after 1 second and then spit out the weight measurement every second after that. Note that if you tare() too quickly, it'll not be accurate. Sometimes I need to tare again because the zero was off. This appears to be normal behavior since my kitchen scale is the same way (requiring multiple tares to get a stable zero). You can't do reads too close together in time or it'll introduce variability. This might be because the ADC needs to time out since I'm only sending 24 ticks or something... It appears that if you wait at least 30ms between reads, you'll be fine. Here I'm waiting 1000ms:

    //var HX711 = require(....)
    
    var h=HX711.connect({
      sck : D14,
      miso : D15,
      lsbGrams : 0.00103123388
    });
    
    setInterval(function() {
      if(!h.zero){
        print("tare");
        h.tare();
      }
      else{
        print(h.readGrams());
      }
    }, 1000);
    

    Here are some readings with nothing on the scale:

    -0.04949922623
    0.08559241203
    -0.04743675847
    0.01237480655
    0.00618740327
    -0.01753097595
    -0.04124935519
    -0.00206246775
    -0.03918688743
    0.025780847
    0.04743675847
    

    And here are some sample readings with a 33 gram weight:

    33.00773403103
    33.00464032939
    32.96854714359
    32.94998493375
    33.00876526491
    32.93039149003
    33.07167053159
    32.96339097419
    33.03248364415
    33.05620202339
    32.95926603867
    32.94895369987
    32.97060961135
    

    If you round to the nearest 0.1 gram, it's quite stable. Rounding to the nearest quarter gram is extremely stable. Since you can sample every 30ms, one could easily increase sampling rate and return an average over multiple reads to reduce noise even more. That's probably what I'll do for my current application.

    Hope this helps others who want to use an HX711.

    Gordon, I'm available to investigate things or do tests, or otherwise help in any way.

  • Post a reply
    • Bold
    • Italics
    • Link
    • Image
    • List
    • Quote
    • code
    • Preview
About

read an high precision adc like hx711

Posted by Avatar for user64817 @user64817

Actions