james-e-morris / hx711-multi

Sample multiple HX711 24-bit ADCs with Python 3 on a Raspberry Pi Rasperry Pi Zero, 2 or 3
MIT License
13 stars 5 forks source link

Four HX711 with expander MCP23017 #8

Closed Ankitjaiswal1201 closed 2 years ago

Ankitjaiswal1201 commented 2 years ago

Hello @Morrious ,

Firstly thank you for making this git repository available. I connected on HX711 with raspberry pi 3B and everything worked fine.

I have issue. I wanted to connect four HX711 with expander mcp23017 which is connected to Raspberry Pi. I wanted to ask can I use this package to connect with expander mcp23017? I see you have used BMC numbering which is where I am getting errors. For expander MCP23017, I have used CircuitPython code which can be found here .

The combined code which I have written is:

#!/usr/bin/env python3
import time
import board
import busio
import digitalio
from adafruit_mcp230xx.mcp23017 import MCP23017
from hx711_multi import HX711
from time import perf_counter
import RPi.GPIO as GPIO  # import GPIO

# Initialize the I2C bus:
i2c = busio.I2C(board.SCL, board.SDA)
# Create an instance of MCP23017 class
mcp = MCP23017(i2c)  # MCP23017

# Optionally change the address of the device if you set any of the A0, A1, A2
# pins.  Specify the new address with a keyword parameter:
mcp = MCP23017(i2c, address=0x20)  # MCP23017 w/ A0, A1, A2 not set

# Now call the get_pin function to get an instance of a pin on the chip.
# This instance will act just like a digitalio.DigitalInOut class instance
# and has all the same properties and methods (except you can't set pull-down
# resistors, only pull-up!).  For the MCP23008 you specify a pin number from 0
# to 7 for the GP0...GP7 pins.  For the MCP23017 you specify a pin number from
# 0 to 15 for the GPIOA0...GPIOA7, GPIOB0...GPIOB7 pins (i.e. pin 12 is GPIOB4).
pin9 = mcp.get_pin(9)
pin11 = mcp.get_pin(11)

# Setup pin1 as an input with a pull-up resistor enabled.  Notice you can also
# use properties to change this state.
pin9.direction = digitalio.Direction.INPUT
pin11.direction = digitalio.Direction.INPUT

# init GPIO (should be done outside HX711 module in case you are using other GPIO functionality)
GPIO.setmode(GPIO.BCM)  # set GPIO pin mode to BCM numbering

readings_to_average = 10
sck_pin = board.SCL
dout_pins = [pin9, pin11]
weight_multiples = [389.8, 390.0]

# create hx711 instance
hx711 = HX711(dout_pins=dout_pins,
              sck_pin=sck_pin,
              channel_A_gain=128,
              channel_select='A',
              all_or_nothing=False,
              log_level='CRITICAL')
# reset ADC, zero it
hx711.reset()
try:
    hx711.zero(readings_to_average=readings_to_average*3)
except Exception as e:
    print(e)
# uncomment below loop to see raw 2's complement and read integers
# for adc in hx711._adcs:
#     print(adc.raw_reads)  # these are the 2's complemented values read bitwise from the hx711
#     print(adc.reads)  # these are the raw values after being converted to signed integers
hx711.set_weight_multiples(weight_multiples=weight_multiples)

# read until keyboard interrupt
try:
    while True:
        start = perf_counter()

        # perform read operation, returns signed integer values as delta from zero()
        # readings aare filtered for bad data and then averaged
        raw_vals = hx711.read_raw(readings_to_average=readings_to_average)

        # request weights using multiples set previously with set_weight_multiples()
        # This function call will not perform a new measurement, it will just use what was acquired during read_raw()
        weights = hx711.get_weight()

        read_duration = perf_counter() - start
        sample_rate = readings_to_average/read_duration
        print('\nread duration: {:.3f} seconds, rate: {:.1f} Hz'.format(read_duration, sample_rate))
        print(
            'raw',
            ['{:.3f}'.format(x) if x is not None else None for x in raw_vals])
        print(' wt',
              ['{:.3f}'.format(x) if x is not None else None for x in weights])
        # uncomment below loop to see raw 2's complement and read integers
        # for adc in hx711._adcs:
        #     print(adc.raw_reads)  # these are the 2's complemented values read bitwise from the hx711
        #     print(adc.reads)  # these are the raw values after being converted to signed integers
except KeyboardInterrupt:
    print('Keyboard interrupt..')
except Exception as e:
    print(e)

# cleanup GPIO
GPIO.cleanup()

Initial error I am getting for dout pins.

TypeError: dout_pins must be type int or array of int.
Received dout_pins: [<adafruit_mcp230xx.digital_inout.DigitalInOut object at 0x75af32f8>, <adafruit_mcp230xx.digital_inout.DigitalInOut object at 0x75abd868>]

What can I do in this case?

james-e-morris commented 2 years ago

@Ankitjaiswal1201 Per my error message, it appears you're passing an MCP object into the pin field, which is just supposed to be an integer input that aligns with the BCM pinout. If the BCM pinout is correct for your uses, it would just be the value [9, 11], and my code does the rest of the work to allocate them as input pins.

The BCM pinout can be found here. I'm not familiar with this breakout board and if you're even able to access the GPIO pins the way this codeset does. HX711-Multi is just sampling directly with the GPIO pins using the RPi GPIO module.

I include a raw GPIO script, so you can see exactly how the GPIO is used to sample an HX711. This is here, also below:

import RPi.GPIO as GPIO

GPIO.setmode(GPIO.BCM)  # set GPIO pin mode to BCM numbering

sck_pin = 1
dout_pin = 2
channel_select = 'A'
channel_A_gain = 128

# init GPIO
GPIO.setup(sck_pin, GPIO.OUT)  # sck_pin is output only
GPIO.setup(dout_pin, GPIO.IN)  # dout_pin is input only

# prepare for read
GPIO.output(sck_pin, False)  # start by setting the pd_sck to 0
# check if dout pin is ready by confirming zero
for _ in range(20):
    ready = (GPIO.input(dout_pin) == 0)
    if ready:
        break

if not ready:
    print('GPIO pin not ready, quitting..')
    quit()

# for each bit in 24 bits, perform ADC read
raw_read = 0
for _ in range(24):
    # pulse sck high to request each bit
    GPIO.output(sck_pin, True)
    GPIO.output(sck_pin, False)
    # left shift by one bit then bitwise OR with the new bit
    raw_read = (raw_read << 1) | GPIO.input(dout_pin)

# set channel after read
# get number of pulses based on channel configuration
num_pulses = 2  # default 2 for channel B
if channel_select == 'A' and channel_A_gain == 128:
    num_pulses = 1
elif channel_select == 'A' and channel_A_gain == 64:
    num_pulses = 3
# pulse num_pulses
for _ in range(num_pulses):
    GPIO.output(sck_pin, True)
    GPIO.output(sck_pin, False)

print(f'Raw read (2s complement): {raw_read}')
if raw_read in [0x800000, 0x7FFFFF, 0xFFFFFF]:
    print(f'Invalid raw value detected')
# calculate int from 2's complement
# check if the sign bit is 1, indicating a negative number
if (raw_read & 0x800000):
    # convert from 2's complement to negative int
    signed_value = -((raw_read ^ 0xffffff) + 1)
else:  # else do not do anything the value is positive number
    signed_value = raw_read
print(f'Raw read (signed integer): {signed_value}')

GPIO.cleanup()
james-e-morris commented 2 years ago

@Ankitjaiswal1201 I just read up on it a little more. If you're trying to access the measurements through a secondary SPI or I2C interface, the RPi GPIO module won't be able to access it the same way. Can you successfully read high and low values from pins 9 and 11 through the MCP23017? Are you able to do this with the GPIO module from RPi, or do you have to sample through that I2C class (I expect you do)?

If you can adapt my raw code above to work with your MCP (replace any GPIO portions that won't work for you), I might be able to add some functionality to adapt to that need.

Ankitjaiswal1201 commented 2 years ago

@Ankitjaiswal1201 Per my error message, it appears you're passing an MCP object into the pin field, which is just supposed to be an integer input that aligns with the BCM pinout. If the BCM pinout is correct for your uses, it would just be the value [9, 11], and my code does the rest of the work to allocate them as input pins.

The BCM pinout can be found here. I'm not familiar with this breakout board and if you're even able to access the GPIO pins the way this codeset does. HX711-Multi is just sampling directly with the GPIO pins using the RPi GPIO module.

I include a raw GPIO script, so you can see exactly how the GPIO is used to sample an HX711. This is here, also below:

import RPi.GPIO as GPIO

GPIO.setmode(GPIO.BCM)  # set GPIO pin mode to BCM numbering

sck_pin = 1
dout_pin = 2
channel_select = 'A'
channel_A_gain = 128

# init GPIO
GPIO.setup(sck_pin, GPIO.OUT)  # sck_pin is output only
GPIO.setup(dout_pin, GPIO.IN)  # dout_pin is input only

# prepare for read
GPIO.output(sck_pin, False)  # start by setting the pd_sck to 0
# check if dout pin is ready by confirming zero
for _ in range(20):
    ready = (GPIO.input(dout_pin) == 0)
    if ready:
        break

if not ready:
    print('GPIO pin not ready, quitting..')
    quit()

# for each bit in 24 bits, perform ADC read
raw_read = 0
for _ in range(24):
    # pulse sck high to request each bit
    GPIO.output(sck_pin, True)
    GPIO.output(sck_pin, False)
    # left shift by one bit then bitwise OR with the new bit
    raw_read = (raw_read << 1) | GPIO.input(dout_pin)

# set channel after read
# get number of pulses based on channel configuration
num_pulses = 2  # default 2 for channel B
if channel_select == 'A' and channel_A_gain == 128:
    num_pulses = 1
elif channel_select == 'A' and channel_A_gain == 64:
    num_pulses = 3
# pulse num_pulses
for _ in range(num_pulses):
    GPIO.output(sck_pin, True)
    GPIO.output(sck_pin, False)

print(f'Raw read (2s complement): {raw_read}')
if raw_read in [0x800000, 0x7FFFFF, 0xFFFFFF]:
    print(f'Invalid raw value detected')
# calculate int from 2's complement
# check if the sign bit is 1, indicating a negative number
if (raw_read & 0x800000):
    # convert from 2's complement to negative int
    signed_value = -((raw_read ^ 0xffffff) + 1)
else:  # else do not do anything the value is positive number
    signed_value = raw_read
print(f'Raw read (signed integer): {signed_value}')

GPIO.cleanup()

@Morrious Thank you for your reply. The expander MCP23017 that I am using is from CircuitPython. That code is written in Board Numbering. I am not sure if making pin9 as 9 will work. Will pi not be confused about using its own GPIO pin 9 instead of using expander? Yes, I am referring to https://pinout.xyz/ for BMC numbering for Pi but could not finding anything for the expander.

I am using your Raw GPIO code too. I saw that you mentioned about Raw GPIO file in other issues. When I connected one hx711 directly with Pi, it worked properly. But when I am trying to connect it via breadboard, it is just giving me 0,0 value. I think this can be because of a loose connection as it should work via Breadboard too. This is a different problem so don't want to discuss in this thread. I will try to look for more details about it.

Ankitjaiswal1201 commented 2 years ago

@Ankitjaiswal1201 I just read up on it a little more. If you're trying to access the measurements through a secondary SPI or I2C interface, the RPi GPIO module won't be able to access it the same way. Can you successfully read high and low values from pins 9 and 11 through the MCP23017? Are you able to do this with the GPIO module from RPi, or do you have to sample through that I2C class (I expect you do)?

If you can adapt my raw code above to work with your MCP (replace any GPIO portions that won't work for you), I might be able to add some functionality to adapt to that need.

@Morrious I tested a simple Led example on the expander MCP23017. And it worked well. Just Led and resistor is important with expander. Switch is not used.

MCP23017-led

With the HX711 connected to the expander, I am not able to read anything. But for LED, any pin I connect is working. I was using the following code

# SPDX-FileCopyrightText: 2017 Tony DiCola for Adafruit Industries
#
# SPDX-License-Identifier: MIT

# Simple demo of reading and writing the digital I/O of the MCP2300xx as if
# they were native CircuitPython digital inputs/outputs.
# Author: Tony DiCola
import time

import board
import busio
import digitalio

from adafruit_mcp230xx.mcp23017 import MCP23017

# Initialize the I2C bus:
i2c = busio.I2C(board.SCL, board.SDA)

# Create an instance of either the MCP23008 or MCP23017 class depending on
# which chip you're using:
mcp = MCP23017(i2c)  # MCP23017

# Optionally change the address of the device if you set any of the A0, A1, A2
# pins.  Specify the new address with a keyword parameter:
mcp = MCP23017(i2c, address=0x20)  # MCP23017 A0,A1,A2 not set

# Now call the get_pin function to get an instance of a pin on the chip.
# This instance will act just like a digitalio.DigitalInOut class instance
# and has all the same properties and methods (except you can't set pull-down
# resistors, only pull-up!).  For the MCP23008 you specify a pin number from 0
# to 7 for the GP0...GP7 pins.  For the MCP23017 you specify a pin number from
# 0 to 15 for the GPIOA0...GPIOA7, GPIOB0...GPIOB7 pins (i.e. pin 12 is GPIOB4).
pin8 = mcp.get_pin(8)
pin9 = mcp.get_pin(9)

# Setup pin0 as an output that's at a high logic level.
pin8.switch_to_output(value=True)

print("Line 45")
# Setup pin1 as an input with a pull-up resistor enabled.  Notice you can also
# use properties to change this state.
pin9.direction = digitalio.Direction.INPUT
pin9.pull = digitalio.Pull.UP

# Now loop blinking the pin 0 output and reading the state of pin 1 input.
while True:
    # Blink pin 0 on and then off.

    pin8.value = True
    time.sleep(0.5)
    pin8.value = False
    time.sleep(0.5)

I also tried changing the GPIO portion in your Raw file. Did some changes but later got confused about the type of GPIO pin. When I check the type of GPIO pin9, it says Adafruit Object.

james-e-morris commented 2 years ago

@Ankitjaiswal1201 sorry for the delay in replying. I have been very busy lately and this fell off my radar.

I created a dev branch to try out passing custom external functions to the HX711 for the GPIO functionality. I created a test script as well and copied in what I expect might work for your MCP setup. Please clone and check out the dev branch and try out the test script.

This update basically just allows you to pass your own init/read/write functions to the HX711 which passes the pin numbering already configured with the HX711 to pass pin numbers to your custom functions. I think that this should work with your mcp objects initialized before the functions, but I'm not certain. Give the test a try and see if those functions get run properly and the inputs/outputs work.

Branch: dev-expander-support Test Script: tests/simple_read_test_external_mcp.py

#!/usr/bin/env python3

# try to import hx711, first from src dir, second from src dir after adding parent to path, last from pip
try:
    from src.hx711_multi import HX711
except:
    try:
        # try after inserting parent folder in path
        import sys
        import pathlib
        from os.path import abspath
        sys.path.insert(0, str(pathlib.Path(abspath(__file__)).parents[1]))
        from src.hx711_multi import HX711
    except:
        from hx711_multi import HX711

from time import perf_counter

import board
import busio
import digitalio
from adafruit_mcp230xx.mcp23017 import MCP23017

readings_to_average = 10
sck_pin = 1
dout_pins = [2]
weight_multiples = [1]

# initialize MCP
i2c = busio.I2C(board.SCL, board.SDA)
mcp = MCP23017(i2c)  # MCP23017
mcp = MCP23017(i2c, address=0x20)  # MCP23017 A0,A1,A2 not set

# create custom functions for init, write, read
def mcp_init_gpio_func(sck_pin, dout_pins):
    # init clock/sck pin
    mcp_sck_pin = mcp.get_pin(sck_pin)
    mcp_sck_pin.switch_to_output(value=True)
    # init dout pins
    for dout in dout_pins:
        mcp_dout_pin = mcp.get_pin(dout)
        # mcp_dout_pin.direction = digitalio.Direction.INPUT
        # mcp_dout_pin.pull = digitalio.Pull.UP
        mcp_dout_pin.switch_to_input(pull=digitalio.Pull.UP)

def mcp_gpio_output_func(pin_number, value=False):
    mcp_output_pin = mcp.get_pin(pin_number)
    mcp_output_pin.value = value

def mcp_gpio_input_func(pin_number):
    mcp_output_pin = mcp.get_pin(pin_number)
    return mcp_output_pin.value

# create hx711 instance
hx711 = HX711(
    dout_pins=dout_pins,
    sck_pin=sck_pin,
    channel_A_gain=128,
    channel_select='A',
    all_or_nothing=False,
    log_level='CRITICAL',
    init_gpio_func=mcp_init_gpio_func,
    gpio_output_func=mcp_gpio_output_func,
    gpio_input_func=mcp_gpio_input_func,
)
# reset ADC, zero it
hx711.reset()
try:
    hx711.zero(readings_to_average=readings_to_average * 3)
except Exception as e:
    print(e)
# uncomment below loop to see raw 2's complement and read integers
# for adc in hx711._adcs:
#     print(adc.raw_reads)  # these are the 2's complemented values read bitwise from the hx711
#     print(adc.reads)  # these are the raw values after being converted to signed integers
hx711.set_weight_multiples(weight_multiples=weight_multiples)

# read until keyboard interrupt
try:
    while True:
        start = perf_counter()

        # perform read operation, returns signed integer values as delta from zero()
        # readings aare filtered for bad data and then averaged
        raw_vals = hx711.read_raw(readings_to_average=readings_to_average)

        # request weights using multiples set previously with set_weight_multiples()
        # This function call will not perform a new measurement, it will just use what was acquired during read_raw()
        weights = hx711.get_weight()

        read_duration = perf_counter() - start
        sample_rate = readings_to_average / read_duration
        print('\nread duration: {:.3f} seconds, rate: {:.1f} Hz'.format(read_duration, sample_rate))
        print('raw', ['{:.3f}'.format(x) if x is not None else None for x in raw_vals])
        print(' wt', ['{:.3f}'.format(x) if x is not None else None for x in weights])
        # uncomment below loop to see raw 2's complement and read integers
        # for adc in hx711._adcs:
        #     print(adc.raw_reads)  # these are the 2's complemented values read bitwise from the hx711
        #     print(adc.reads)  # these are the raw values after being converted to signed integers
except KeyboardInterrupt:
    print('Keyboard interrupt..')
except Exception as e:
    print(e)
Ankitjaiswal1201 commented 2 years ago

Hello @Morrious , Thank you for your reply. I tried to do the run the above code, but I am getting an error. The error is as follows:

image

I tried to go in the file and see if I can resolve it but could not find solution for this function error. I got rid of that module not found error but could not get rid of that function name error. This I was trying after cloning the dev branch in a folder.

I tried to install the repo again with development branch after uninstalling everything related to hx711 with pip install @dev branch but then got the following error.

>>> %Run test_mcp23017.py
Traceback (most recent call last):
  File "/home/pi/Documents/electronicSensor/src/test_mcp23017.py", line 59, in <module>
    hx711 = HX711(
TypeError: __init__() got an unexpected keyword argument 'init_gpio_func'

Not sure what I am doing wrong.

james-e-morris commented 2 years ago

It looks like I misunderstood typing an input argument as a "function" type. The input shouldn't be "init_gpio_func: function", it should be "init_gpio_func: Callable" and I need to import Callable.

I am away from my computer for a day or two, but you can try editing that code yourself to try to fix the error. You'll need to add an import line near the top to import Callable, and then replace the "function" typing with "Callable" in multiple places where I define something like "func: function". Here is the solution from StackOverflow:

https://stackoverflow.com/questions/37835179/how-can-i-specify-the-function-type-in-my-type-hints

Ankitjaiswal1201 commented 2 years ago

Ya, I will try it and get back to you. I have an other question which I will ask in another issue.

Ankitjaiswal1201 commented 2 years ago

Hello @Morrious , I tried the above solution, works partly. There was some error which i was getting but for now i have put those lines in comments and then all the errors are resolved. The error lines were: image

I have put the comments here: image

But I have some questions in the code:

image

the sck_pin mentioned is 1. This 1 corresponds to bmc numbering or board?

Also as i understood from the ada_fruit docs for MCP_23017, the total number of pins defined in code is 0 to 15 where we can specify a pin number from 0 to 15 for the GPIOA0...GPIOA7, GPIOB0...GPIOB7 pins (i.e. pin 12 is GPIOB4). AdaFruit Link

So in the following code, when we give pin 1 to sck_pin, aren't we giving GPIOA1? image

In my connection, I have connected pin 2,3 (BMC) to MCP23017 and pin 1 (BMC) to the 3 HX711 that I am using.

james-e-morris commented 2 years ago

Looks like my function typing checks still don't work, I'll have to debug that later. Commenting out the error check is fine for now.

In my use-case the PIN number is the BCM GPIO number. With the MCP, the integer should be the 0-15 value I would guess.

With the code I added, it will always use the custom functions if passed, so you're using only the MCP pins, right? Or are you using a mixture of MCP and normal GPIO?

github-actions[bot] commented 2 years ago

Stale issue message