Commit e05c49ee authored by Gilles MARCKMANN's avatar Gilles MARCKMANN

First implementation of a standalone raspi GUI for Load Cell HX711:

- default values set on raspberry Pi4
- a calibration procedure allows to reset parameters
- a tare button allows to reset value to zero
- figure_raspi_HX711.png gives the way to connect the load cell to the raspberry
parents
CREDITS:
This code is base on based on https://github.com/tatobari/hx711py, based on https://gist.github.com/underdoeg/98a38b54f889fce2b237
RASPBERRY PI GENERALITIES:
- update your rapsberry before all:
> sudo aptitude update -y
> sudo aptitude upgrade -y
> sudo reboot
- install Pixel (X11 Desktop), if any:
> sudo dpkg –configure –a
> sudo apt-get install lightdm
> sudo apt –fix-broken install
> sudo apt install lxde lxde-core lxterminal lxappearance
> sudo apt install xinit
> sudo apt install raspberrypi-ui-mods
> sudo apt-get install xutils
or:
> sudo apt-get update
> sudo apt-get dist-upgrade
> sudo reboot
> sudo apt-get install --no-install-recommends xserver-xorg
> sudo apt-get install raspberrypi-ui-mods
> sudo apt-get install lightdm
> sudo startx
HX711 PREREQUIREMENT:
- install GPIO:
> sudo pip install RPi.GPIO
> sudo pip install statistics
- install tkinter python module (GUI library for python)
> sudo apt-get install python-tk
- install matplotlib python module:
> sudo pip install matplotlib
> sudo pip2 uninstall backports.functools-lru-cache
> sudo apt install python-backports.functools-lru-cache
#! /usr/bin/python2
import time
import sys
import RPi.GPIO as GPIO
import csv
from tkinter import *
import tkinter.font as tkFont
import numpy
from matplotlib.backends.backend_tkagg import (
FigureCanvasTkAgg, NavigationToolbar2Tk)
# Implement the default Matplotlib key bindings.
from matplotlib.backend_bases import key_press_handler
from matplotlib.figure import Figure
import matplotlib.pyplot as plt
# import class definition
from tatobari.hx711 import HX711
def callback_cleanAndExit():
global _main_window_
global GPIO
GPIO.cleanup()
sys.exit()
_main_window_.quit()
def callback_tare():
print("Please wait...")
hx.reset()
hx.tare()
hx.tare_A()
hx.tare_B()
print("Tare done! Add weight now...")
def callback_CalSet(_parent_window_, text_value, text_unity):
global hx
global referenceUnit
global _WEIGHT_
global _UNITY_
val=0.
try:
val=float(text_value)
except:
print('Bad balue, no calibration done !')
val=0.
if(val!=0.):
print("reference="+_WEIGHT_.get())
referenceUnit = float(_WEIGHT_.get())/val
hx.set_reference_unit(referenceUnit)
_UNITY_.set(text_unity)
_parent_window_.destroy()
def callback_reset():
global hx
global time
global _UNITY_
print("No weight on scale!")
hx.set_reference_unit(1.)
hx.tare()
_UNITY_.set("")
print("Go to step 2.")
return
def callback_calibrate():
global hx
global _space_
global _calibration_window_
global _UNITY_
_calibration_window_ = Tk()
_calibration_window_['bg']='white'
_cal_frame_=Frame(_calibration_window_, padx=_space_, pady=_space_ ,relief=GROOVE, background="white")
_cal_frame_.pack()
_indication1_=Label(_cal_frame_, text="1. Remove all weight and click on 'Reset' button", background="white")
_indication1_.grid(row=0,column=0,columnspan=2)
_reset_button_=Button(_cal_frame_, text="Reset", command=lambda: callback_reset())
_reset_button_.grid(row=1, column=0 ,columnspan=2, padx=_space_, pady=_space_)
_indication2_=Label(_cal_frame_, text="2. Place a known weight on the scale, indicate the value and click on 'Calibrate' button", background="white")
_indication2_.grid(row=2,column=0,columnspan=2)
_val_frame_=Frame(_cal_frame_, padx=_space_,pady=_space_,borderwidth=0, background="white")
_val_frame_.grid(row=3,column=0)
_val_label_=Label(_val_frame_,text="Refrence weight:", background="white")
_val_label_.pack(side=LEFT)
_val_entry_=Entry(_val_frame_,width=10,relief=SUNKEN,takefocus=1,justify=RIGHT)
_val_entry_.pack(side=LEFT)
_unity_frame_=Frame(_cal_frame_, padx=_space_,pady=_space_,borderwidth=0, background="white")
_unity_frame_.grid(row=3,column=1)
_unity_label_=Label(_unity_frame_,text=" Unity symbol:", background="white")
_unity_label_.pack(side=LEFT)
_unity_entry_=Entry(_unity_frame_,width=10,relief=SUNKEN,takefocus=1,justify=RIGHT)
_unity_entry_.delete(0, END)
_unity_entry_.insert(0,_UNITY_.get())
_unity_entry_.pack(side=LEFT)
_cancel_button_=Button(_cal_frame_, text="Cancel", command=_calibration_window_.destroy)
_cancel_button_.grid(row=4, column=0, padx=_space_, pady=_space_)
_cal_button_=Button(_cal_frame_, text="Calibrate", command=lambda: callback_CalSet(_calibration_window_,_val_entry_.get(),_unity_entry_.get()))
_cal_button_.grid(row=4, column=1, padx=_space_, pady=_space_)
_calibration_window_.mainloop()
def callback_inbetween_mainloop():
global _WEIGHT_
global hx
global _main_window_
try:
val=hx.get_weight(1)
#_WEIGHT_.set( val )
_WEIGHT_.set( 1.*int(val*1000)/1000. )
#print(_WEIGHT_.get())
# To get weight from both channels (if you have load cells hooked up
# to both channel A and B), do something like this
#val_A = hx.get_weight_A(1)
#val_B = hx.get_weight_B(1)
#print "A: %s B: %s" % ( val_A, val_B )
hx.power_down()
hx.power_up()
time.sleep(.1)
except (KeyboardInterrupt, SystemExit):
cleanAndExit()
_main_window_.after(1, callback_inbetween_mainloop)
#-------------------------------------------------
# MAIN
#-------------------------------------------------
_main_window_ = Tk()
_main_window_['bg']='white'
_space_=3
#-----------------
# TITLE FRAME
#-----------------
_FrameTitle_=Frame(_main_window_, borderwidth=0)
_FrameTitle_.grid(row=0, column=0, columnspan=3, padx=_space_, pady=_space_)
_font_style_title_ = tkFont.Font(family="Lucida Grande", size=14)
_label_title_ = Label(_FrameTitle_, text="Scale",bg="white",font=_font_style_title_).pack(side=LEFT,fill=BOTH)
_font_style_title_ = tkFont.Font(family="Lucida Grande", size=14)
_label_title_red_ = Label(_FrameTitle_, text="HX711 load cell",bg="white",font=_font_style_title_,foreground="red").pack(side=LEFT,fill=BOTH)
hx = HX711(5, 6)
# If you're experiencing super random values, change these values to MSB or LSB until to get more stable values.
# According to the HX711 Datasheet, the second parameter is MSB so you shouldn't need to modify it.
hx.set_reading_format("MSB", "MSB")
# HOW TO CALCULATE THE REFFERENCE UNIT
# To set the reference unit to 1.
# Put 1kg on your sensor or anything you have and know exactly how much it weights.
# set this number divided by the real weight.
referenceUnit = int(1464009/2015)
hx.set_reference_unit(referenceUnit)
hx.reset()
hx.tare()
print("Tare done! Add weight now...")
_space_=3
#-----------------
# Main Label
#-----------------
_UNITY_=StringVar()
_UNITY_.set("g")
_WEIGHT_=StringVar()
_WEIGHT_.set(str(0.0))
_font_style_weight_ = tkFont.Font(family="Lucida Grande", size=20)
_weight_frame_=Frame(_main_window_,padx=_space_,pady=_space_, relief=GROOVE)
_weight_frame_.grid(row=1,column=1)
_weight_label_ = Label(_weight_frame_,padx=_space_,pady=_space_, width=10,textvariable=_WEIGHT_, borderwidth=2,
font=_font_style_weight_,relief=SUNKEN, background="white", anchor=E)
_weight_label_.pack(side=LEFT)
_unity_label_=Label(_main_window_,padx=_space_,pady=_space_, width=5,textvariable=_UNITY_, borderwidth=2,
font=_font_style_weight_, background="white", anchor=W)
_unity_label_.grid(row=1,column=2)
#-----------------
# button tare
#-----------------
_buttonTare_=Button(_main_window_, text="Tare", command=callback_tare)
_buttonTare_.grid(row=1, column=3, padx=_space_, pady=_space_)
#-----------------
# button calibrate
#-----------------
_buttonTare_=Button(_main_window_, text="Calibration", command=callback_calibrate)
_buttonTare_.grid(row=1, column=0, padx=_space_, pady=_space_)
#-----------------
# button quit
#-----------------
_buttonQuit_=Button(_main_window_, text="Quit", command=callback_cleanAndExit)
_buttonQuit_.grid(row=3, column=0, padx=_space_, pady=_space_)
#-----------------
# button prendre une mesure
#-----------------
#_buttonMeasure_=Button(_main_window_, text="Record Data", command=callback_add_measure)
#_buttonMeasure_=Button(_main_window_, text="Record Data")
#_buttonMeasure_.grid(row=3, column=1, padx=_space_, pady=_space_)
#-----------------
# button export
#-----------------
#_buttonExport_=Button(_main_window_, text="Export CSV", command=callback_export)
#_buttonExport_=Button(_main_window_, text="Export CSV")
#_buttonExport_.grid(row=3, column=2, padx=_space_, pady=_space_)
_main_window_.after(10, callback_inbetween_mainloop)
_main_window_.mainloop()
This diff is collapsed.
import time
import random
import math
import threading
class HX711:
def __init__(self, dout, pd_sck, gain=128):
self.PD_SCK = pd_sck
self.DOUT = dout
# Last time we've been read.
self.lastReadTime = time.time()
self.sampleRateHz = 80.0
self.resetTimeStamp = time.time()
self.sampleCount = 0
self.simulateTare = False
# Mutex for reading from the HX711, in case multiple threads in client
# software try to access get values from the class at the same time.
self.readLock = threading.Lock()
self.GAIN = 0
self.REFERENCE_UNIT = 1 # The value returned by the hx711 that corresponds to your reference unit AFTER dividing by the SCALE.
self.OFFSET = 1
self.lastVal = long(0)
self.DEBUG_PRINTING = False
self.byte_format = 'MSB'
self.bit_format = 'MSB'
self.set_gain(gain)
# Think about whether this is necessary.
time.sleep(1)
def convertToTwosComplement24bit(self, inputValue):
# HX711 has saturating logic.
if inputValue >= 0x7fffff:
return 0x7fffff
# If it's a positive value, just return it, masked with our max value.
if inputValue >= 0:
return inputValue & 0x7fffff
if inputValue < 0:
# HX711 has saturating logic.
if inputValue < -0x800000:
inputValue = -0x800000
diff = inputValue + 0x800000
return 0x800000 + diff
def convertFromTwosComplement24bit(self, inputValue):
return -(inputValue & 0x800000) + (inputValue & 0x7fffff)
def is_ready(self):
# Calculate how long we should be waiting between samples, given the
# sample rate.
sampleDelaySeconds = 1.0 / self.sampleRateHz
return time.time() >= self.lastReadTime + sampleDelaySeconds
def set_gain(self, gain):
if gain is 128:
self.GAIN = 1
elif gain is 64:
self.GAIN = 3
elif gain is 32:
self.GAIN = 2
# Read out a set of raw bytes and throw it away.
self.readRawBytes()
def get_gain(self):
if self.GAIN == 1:
return 128
if self.GAIN == 3:
return 64
if self.GAIN == 2:
return 32
# Shouldn't get here.
return 0
def readRawBytes(self):
# Wait for and get the Read Lock, incase another thread is already
# driving the virtual HX711 serial interface.
self.readLock.acquire()
# Wait until HX711 is ready for us to read a sample.
while not self.is_ready():
pass
self.lastReadTime = time.time()
# Generate a 24bit 2s complement sample for the virtual HX711.
rawSample = self.convertToTwosComplement24bit(self.generateFakeSample())
# Read three bytes of data from the HX711.
firstByte = (rawSample >> 16) & 0xFF
secondByte = (rawSample >> 8) & 0xFF
thirdByte = rawSample & 0xFF
# Release the Read Lock, now that we've finished driving the virtual HX711
# serial interface.
self.readLock.release()
# Depending on how we're configured, return an orderd list of raw byte
# values.
if self.byte_format == 'LSB':
return [thirdByte, secondByte, firstByte]
else:
return [firstByte, secondByte, thirdByte]
def read_long(self):
# Get a sample from the HX711 in the form of raw bytes.
dataBytes = self.readRawBytes()
if self.DEBUG_PRINTING:
print(dataBytes,)
# Join the raw bytes into a single 24bit 2s complement value.
twosComplementValue = ((dataBytes[0] << 16) |
(dataBytes[1] << 8) |
dataBytes[2])
if self.DEBUG_PRINTING:
print("Twos: 0x%06x" % twosComplementValue)
# Convert from 24bit twos-complement to a signed value.
signedIntValue = self.convertFromTwosComplement24bit(twosComplementValue)
# Record the latest sample value we've read.
self.lastVal = signedIntValue
# Return the sample value we've read from the HX711.
return int(signedIntValue)
def read_average(self, times=3):
# Make sure we've been asked to take a rational amount of samples.
if times <= 0:
print("HX711().read_average(): times must >= 1!! Assuming value of 1.")
times = 1
# If we're only average across one value, just read it and return it.
if times == 1:
return self.read_long()
# If we're averaging across a low amount of values, just take an
# arithmetic mean.
if times < 5:
values = int(0)
for i in range(times):
values += self.read_long()
return values / times
# If we're taking a lot of samples, we'll collect them in a list, remove
# the outliers, then take the mean of the remaining set.
valueList = []
for x in range(times):
valueList += [self.read_long()]
valueList.sort()
# We'll be trimming 20% of outlier samples from top and bottom of collected set.
trimAmount = int(len(valueList) * 0.2)
# Trim the edge case values.
valueList = valueList[trimAmount:-trimAmount]
# Return the mean of remaining samples.
return sum(valueList) / len(valueList)
def get_value(self, times=3):
return self.read_average(times) - self.OFFSET
def get_weight(self, times=3):
value = self.get_value(times)
value = value / self.REFERENCE_UNIT
return value
def tare(self, times=15):
# If we aren't simulating Taring because it takes too long, just skip it.
if not self.simulateTare:
return 0
# Backup REFERENCE_UNIT value
reference_unit = self.REFERENCE_UNIT
self.set_reference_unit(1)
value = self.read_average(times)
if self.DEBUG_PRINTING:
print("Tare value:", value)
self.set_offset(value)
# Restore the reference unit, now that we've got our offset.
self.set_reference_unit(reference_unit)
return value;
def set_reading_format(self, byte_format="LSB", bit_format="MSB"):
if byte_format == "LSB":
self.byte_format = byte_format
elif byte_format == "MSB":
self.byte_format = byte_format
else:
print("Unrecognised byte_format: \"%s\"" % byte_format)
if bit_format == "LSB":
self.bit_format = bit_format
elif bit_format == "MSB":
self.bit_format = bit_format
else:
print("Unrecognised bit_format: \"%s\"" % bit_format)
def set_offset(self, offset):
self.OFFSET = offset
def get_offset(self):
return self.OFFSET
def set_reference_unit(self, reference_unit):
# Make sure we aren't asked to use an invalid reference unit.
if reference_unit == 0:
print("HX711().set_reference_unit(): Can't use 0 as a reference unit!!")
return
self.REFERENCE_UNIT = reference_unit
def power_down(self):
# Wait for and get the Read Lock, incase another thread is already
# driving the HX711 serial interface.
self.readLock.acquire()
# Wait 100us for the virtual HX711 to power down.
time.sleep(0.0001)
# Release the Read Lock, now that we've finished driving the HX711
# serial interface.
self.readLock.release()
def power_up(self):
# Wait for and get the Read Lock, incase another thread is already
# driving the HX711 serial interface.
self.readLock.acquire()
# Wait 100 us for the virtual HX711 to power back up.
time.sleep(0.0001)
# Release the Read Lock, now that we've finished driving the HX711
# serial interface.
self.readLock.release()
# HX711 will now be defaulted to Channel A with gain of 128. If this
# isn't what client software has requested from us, take a sample and
# throw it away, so that next sample from the HX711 will be from the
# correct channel/gain.
if self.get_gain() != 128:
self.readRawBytes()
def reset(self):
# self.power_down()
# self.power_up()
# Mark time when we were reset. We'll use this for sample generation.
self.resetTimeStamp = time.time()
def generateFakeSample(self):
sampleTimeStamp = time.time() - self.resetTimeStamp
noiseScale = 1.0
noiseValue = random.randrange(-(noiseScale * 1000),(noiseScale * 1000)) / 1000.0
sample = math.sin(math.radians(sampleTimeStamp * 20)) * 72.0
self.sampleCount += 1
if sample < 0.0:
sample = -sample
sample += noiseValue
BIG_ERROR_SAMPLE_FREQUENCY = 142
###BIG_ERROR_SAMPLE_FREQUENCY = 15
BIG_ERROR_SAMPLES = [0.0, 40.0, 70.0, 150.0, 280.0, 580.0]
if random.randrange(0, BIG_ERROR_SAMPLE_FREQUENCY) == 0:
sample = random.sample(BIG_ERROR_SAMPLES, 1)[0]
print("Sample %d: Injecting %f as a random bad sample." % (self.sampleCount, sample))
sample *= 1000
sample *= self.REFERENCE_UNIT
return int(sample)
# EOF - emulated_hx711.py
#! /usr/bin/python2
import time
import sys
referenceUnit = int(1464009/2015)
import RPi.GPIO as GPIO
from hx711 import HX711
def cleanAndExit():
print("Cleaning...")
if not EMULATE_HX711:
GPIO.cleanup()
print("Bye!")
sys.exit()
hx = HX711(5, 6)
# If you're experiencing super random values, change these values to MSB or LSB until to get more stable values.
# According to the HX711 Datasheet, the second parameter is MSB so you shouldn't need to modify it.
hx.set_reading_format("MSB", "MSB")
# HOW TO CALCULATE THE REFFERENCE UNIT
# To set the reference unit to 1.
# Put 1kg on your sensor or anything you have and know exactly how much it weights.
# set this number divided by the real weight.
hx.set_reference_unit(referenceUnit)
hx.reset()
hx.tare()
print("Tare done! Add weight now...")
# to use both channels, you'll need to tare them both
#hx.tare_A()
#hx.tare_B()
while True:
try:
val = hx.get_weight(5)
print(val)
# To get weight from both channels (if you have load cells hooked up
# to both channel A and B), do something like this
val_A = hx.get_weight_A(5)
val_B = hx.get_weight_B(5)
print "A: %s B: %s" % ( val_A, val_B )
hx.power_down()
hx.power_up()
time.sleep(0.1)
except (KeyboardInterrupt, SystemExit):
cleanAndExit()
This diff is collapsed.
from setuptools import setup
setup(
name='hx711',
version='0.1',
description='HX711 Python Library for Raspberry Pi',
py_modules=['hx711'],
install_requires=['Rpi.GPIO', 'numpy'],
)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment