Close
0%
0%

A Cheap 24-bit Differential ADC for Raspberry Pi

Making the HX711 into a more general purpose differential data acquisition module with an interface to Raspberry Pi.

Similar projects worth following
The HX711 is a cheap 24-bit differential ADC used primarily for measuring load cells in weight scales. It can be purchased as a module for less than $1 on the web, but the module has a few problems and needs to be modified to interface with more general sensors and also for interfacing to the Raspberry Pi. This alternative design takes advantage of more of the features of the HX711 and allows flexibility in both the sensor interfaces and the interface to the Raspberry Pi.

The primary application of this design (for me) is to interface a Raspberry Pi to a Lehman-style seismometer, which uses an inductive velocity sensor. The sensor is free-floating so the differential nature of the HX711 is ideal for this purpose.

This is a completed project.

The HX711 has all of the necessary components to process the output of a velocity sensor: 24-bit resolution with nominal sample rates of either 80Hz or 10Hz, a programmable gain pre-amplifier with gain values of 32, 64 and 128, and a fully differential bridge-type interface. I ordered a pre-built module from eBay for about $1. After a few hours of “tinkering” with it I realized it’s potential as a seismometer interface, but I had to toss out the eBay module to unleash it.

The HX711 datasheet shows an application using its internal/external regulator to provide a quiet analog supply for the ADC. It also says that if the on-chip regulator option is not used, the two supply pins can be connected to a separate regulated supply. Using a 3.3V LDO regulator is a better option for use with the Pi because the Pi GPIO interfaces operate at 3.3V. The HX711 supply range is specified over 2.6V - 5.5V.

I also found that the HX711 internal clock generator was more than 20% low on the eBay board. This may be why it only costs a buck. This is not really acceptable for a seismometer — the seedlink server software is a bit sensitive to variable sampling rates. But the HX711 provides for a crystal controlled clock (not an option on the eBay board, which is hard-wired at the slow sample rate.)

The HX711 ADC requires its inputs to both be within a certain common mode range but the eBay board did not provide any circuitry to accomplish that.

Lastly, the HX711 has a weird serial communication protocol that requires a bit-bang on two GPIO pins. The HX711 is designed to go into a low-power sleep mode if the PD_SCK input goes high for more than 50µs. This is a difficult requirement for the Raspberry Pi because of the Linux housekeeping. There is a HX711 interface for Python, using PIGPIO, but it kept quitting after a few seconds or minutes of operation. I suspect that eventually the Raspian kernel would clobber the code and it stops for some reason. I just implemented a 1µs one-shot to control the PD_SCK input, which prevents the HX711 from sleeping and solved a lot of the interface issues -- sometimes software can’t fix everything. Since the one-shot frees the code from having to keep the PD_SCK high time less than 50us I can limp along with the generic GPIO interface. Every once in a while the data is clobbered by the kernel, but I have a pulse swallower that detects this condition and just sticks the previous “good” data into the current sample and proceeds merrily along. I can get away with this because the system is oversampled by 10x. Since a seismometer requires 24/7 operation the sleep function is not required or desired.

So here’s the final schematic for a seismometer velocity sensor:

U3 is a cheap ($0.06) LDO regulator that puts out 3.3V +/- 2% and is stable with small ceramic capacitors. All of the circuitry operates from the 3.3V VDD and makes the digital interface to the Raspberry Pi much easier.

U2 is a 1µs one-shot. It prevents the PD_SCK pin from ever setting the HX711 into sleep mode. The one shot can be removed from the board and R2 (a short) can be used to directly connect the Pi to the HX711, if desired. This is a much cheaper solution, since the LTC6993-1 is the most expensive component by far at $3.35 in low quantities. But it is a cute part and provides a low component count solution to the sleeping HX711 problem.

The Pi can set the sample rate to Fcrystal/138240 or Fcrystal/1105920, which yields 80Hz or 10Hz when using a 11.0592MHz crystal. The HX711 is specified to function with crystal frequencies from 1MHz to 20MHz. You can also configure the board to use the HX711 internal oscillator if desired. I found the 80Hz rate a bit too fast for a seismometer application: it generates a large amount of data storage per day and the RPi has problems keeping up. So I am using a 4MHz crystal to get a sample rate of about 29Hz with the RATE pin set high.

The rest of circuitry is...

Read more »

HX711_BOM.xls

Bill of materials. I added a few sources, but it may be cheaper to pay a bit more to a single electronic parts supplier like Digikey than ordering in bulk from eBay as I do.

ms-excel - 8.50 kB - 07/11/2017 at 13:42

Download

HX711-RPI3.zip

These are the zipped gerber files that can be uploaded to OSH Park to make a set of PCBs.

Zip Archive - 50.74 kB - 07/10/2017 at 20:52

Download

hx711_english.pdf

The data sheet for the HX711.

Adobe Portable Document Format - 160.43 kB - 07/10/2017 at 14:56

Preview

  • Python Code

    Bud Bennett07/12/2017 at 17:30 0 comments

    I tried to get the the HX711 routine from the pigpio library working, but it kept stopping after a couple of minutes. This is what I ended up with. Occasionally, the data gets corrupted when the linux kernel interferes with the data transfer so it has to be processed to avoid big spikes in the data stream.

    This code works at the physical layer -- bit banging the data from the HX711. It must be called after the DOUT line transitions to a low state, indicating that the HX711 has finished a conversion.

    myHX711.py

    #!/usr/bin/env python3
    #-*- coding:utf-8 -*-
    
    import RPi.GPIO as GPIO
    import time
    import math
    
    GPIO.setmode(GPIO.BCM)
    
    class HX711:
        """ Configured as just a 24-bit differential ADC """
    
        def __init__(self, dout, pd_sck, gain=32):
            self.PD_SCK = pd_sck
            self.DOUT = dout
            GPIO.setup(self.PD_SCK, GPIO.OUT)
            GPIO.setup(self.DOUT, GPIO.IN)
    
            if gain is 128:
                self.GAIN = 1
            elif gain is 64:
                self.GAIN = 3
            elif gain is 32:
                self.GAIN = 2
    
    		#initialize
            GPIO.output(self.PD_SCK, False)
            time.sleep(0.4)  # settle time
            GPIO.wait_for_edge(self.DOUT, GPIO.FALLING)
            self.read()
    
        def read(self):
            """ Clock in the data.
                PD_SCK must be low to keep the HX711 in an active state.
                The data from DOUT is clocked in on the falling edge of PD_SCK.
                Min PD_SCK high time = 0.2us, Max PD_SCK high time = 50us.
                Min PD_SCK low time = 0.2us. """
            
            databits = []
            for j in range(2, -1, -1):
                xbyte = 0x00  # intialize data byte
                for i in range(7, -1, -1):
                    GPIO.output(self.PD_SCK, True)
                    xbyte = xbyte | GPIO.input(self.DOUT) << i
                    GPIO.output(self.PD_SCK, False)
                    
                databits.append(xbyte)
    
            #set channel and gain factor for next reading
            for i in range(self.GAIN):
                GPIO.output(self.PD_SCK, True)
                GPIO.output(self.PD_SCK, False)
            
            # convert to integer from 2's complement bytes
            result = (databits[0] << 16) + (databits[1] << 8) + databits[2]
            if result > 8388607: result -= 16777216
            
            return result
    
        def meanstdv(self, x):
            """
            Calculate mean and standard deviation of data x[]:
            mean = {\sum_i x_i \over n}
            std = math.sqrt(\sum_i (x_i - mean)^2 \over n-1)
            """
            n = len(x)
            mean, std = 0, 0
            for a in x:
                mean +=  a
            mean = mean / float(n)
            for a in x:
                std += (a - mean)**2
            if(n > 1):
                std = math.sqrt(std / float(n-1))
            else:
                std = 0.0
            return mean, std
    
    
    # test code
    
    if __name__ == '__main__':
        DOUT = 25
        PD_SCK = 24
        gain = 128
        print("Gain = {}".format(gain))
        hx = HX711(DOUT, PD_SCK, gain)
    
        while True:
            GPIO.wait_for_edge(DOUT, GPIO.FALLING)
            try:
                val = hx.read()
                print("value = {0} ".format(val))
                
            except KeyboardInterrupt:
                GPIO.cleanup()
    
        GPIO.cleanup()
    

    An this is an example of how to use it, along with the pulse swallowing code.

    #!/usr/bin/env python3
    #-*- coding:utf-8 -*-
    
    '''
    An example of getting data from the HX711 and swallowing artifacts. No guarantees that
    this particular code block will work, since I butchered it pretty heavily. There are
    additional processes (not shown) that processes the data into a seedlink stream and
    web server -- pretty esoteric -- I didn't think that there would be much interest. 
    But the general method should be pretty clear.
    '''
    
    from myHX711 import HX711
    import time
    from datetime import datetime, timedelta, date
    from multiprocessing import Process, Manager
    import RPi.GPIO as GPIO
    
    GPIO.setmode(GPIO.BCM)
    
    # instantiate hx711
    gain = 128  # allowed values: 128, 64, 32
    print("gain = {}".format(gain))
    DOUT = 25
    PD_SCK = 24
    RATE = 23
    GPIO.setup(DOUT, GPIO.IN)
    GPIO.setup(RATE, GPIO.OUT)
    # set rate high (sample_rate = 28.94 Hz with 4.00 MHz xtal)
    GPIO.output(RATE, 1)
    hx = HX711(dout=DOUT, pd_sck=PD_SCK, gain=gain)
    # the Manager provides communication between processes. In this case between getData and the others.
    mgr = Manager()
    # makes a global list variable that is compatible with multiprocessing module.
    resultArray = mgr.list()
    
    def getData():
        global resultArray, DOUT
        while True:
            GPIO.wait_for_edge(DOUT, GPIO.FALLING,bouncetime=1)
            tn = time.time() * 1000  # creates a unix timestamp with millisecond resolution
    ...
    Read more »

  • Shielding

    Bud Bennett07/11/2017 at 16:30 0 comments

    The inputs to the ADC are very sensitive to any kind of electrical interference. I recommend that you shield *everything*!

    I wrapped the board in electrical tape and then covered it and the sensor leads with copper foil connected to GND.

    This made a huge reduction in artifacts in the signal.

  • Results

    Bud Bennett07/10/2017 at 21:10 0 comments

    I found the circuit to be quite sensitive to external disturbances and noise, so shielding everything is a very good idea. Here's a plot of a 4µVp-p 10-minute-period square wave injected directly into the HX711 channel #1 inputs with gain=128:

    The measurement is in ADC counts. The input resistance was 10kΩ, so the 1µF input capacitor formed a 16Hz corner frequency. (At this point you should be asking yourself, "How did he inject a 4uV signal with a common mode offset of 1.65V?") The software can easily measure mean and standard deviation, so I estimate that the input referred noise is about 500nV p-p. That is on par with other amateur ADC systems designed for velocity sensors -- and compares favorably with ADC systems costing 100x more.

View all 3 project logs

Enjoy this project?

Share

Discussions

Similar Projects

Does this project spark your interest?

Become a member to follow this project and never miss any updates