Once you've finished setting up your QT Py ESP32-S2 with CircuitPython, you can access the code and necessary libraries by downloading the Project Bundle.
To do this, click on the Download Project Bundle button in the window below. It will download as a zipped folder.
# SPDX-FileCopyrightText: 2022 Liz Clark for Adafruit Industries
# SPDX-License-Identifier: MIT
import time
import board
from digitalio import DigitalInOut, Direction, Pull
from adafruit_ht16k33.segments import Seg14x4
from cedargrove_nau7802 import NAU7802
from calibration import calibration
# I2C setup with STEMMA port
i2c = board.STEMMA_I2C()
# alphanumeric segment displpay setup
# using two displays together
display = Seg14x4(i2c, address=(0x70, 0x71))
# start-up text
display.print("*HELLO* ")
# button LEDs
blue = DigitalInOut(board.A1)
blue.direction = Direction.OUTPUT
green = DigitalInOut(board.A3)
green.direction = Direction.OUTPUT
# buttons setup
blue_btn = DigitalInOut(board.A0)
blue_btn.direction = Direction.INPUT
blue_btn.pull = Pull.UP
green_btn = DigitalInOut(board.A2)
green_btn.direction = Direction.INPUT
green_btn.pull = Pull.UP
# nau7802 setup
nau7802 = NAU7802(board.STEMMA_I2C(), address=0x2A, active_channels=2)
nau7802.gain = 128
enabled = nau7802.enable(True)
# zeroing function
def zero_channel():
"""Initiate internal calibration for current channel; return raw zero
offset value. Use when scale is started, a new channel is green_btned, or to
adjust for measurement drift. Remove weight and tare from load cell before
executing."""
blue.value = True
print(
"channel %1d calibrate.INTERNAL: %5s"
% (nau7802.channel, nau7802.calibrate("INTERNAL"))
)
blue.value = False
print(
"channel %1d calibrate.OFFSET: %5s"
% (nau7802.channel, nau7802.calibrate("OFFSET"))
)
blue.value = True
zero_offset = read_raw_value(100) # Read 100 samples to establish zero offset
print("...channel %1d zeroed" % nau7802.channel)
blue.value = False
return zero_offset
# read raw value function
def read_raw_value(samples=100):
"""Read and average consecutive raw sample values. Return average raw value."""
sample_sum = 0
sample_count = samples
while sample_count > 0:
if nau7802.available:
sample_sum = sample_sum + nau7802.read()
sample_count -= 1
return int(sample_sum / samples)
# function for finding the average of an array
def find_average(num):
count = 0
for n in num:
count = count + n
average = count / len(num)
return average
# calibration function
def calculateCalibration(array):
for _ in range(10):
blue.value = True
green.value = False
nau7802.channel = 1
#value = read_raw_value()
print("channel %1.0f raw value: %7.0f" % (nau7802.channel, abs(read_raw_value())))
array.append(abs(read_raw_value()))
blue.value = False
green.value = True
time.sleep(1)
green.value = False
avg = find_average(array)
return avg
# blink LED function
def blink(led, amount, count):
for _ in range(count):
led.value = True
time.sleep(amount)
led.value = False
time.sleep(amount)
# zeroing on startup
display.fill(0)
display.marquee("CLEAR SCALE CLEAR", 0.3, False)
time.sleep(2)
display.fill(0)
display.print("ZEROING")
time.sleep(3)
# zeroing each channel
nau7802.channel = 1
zero_channel() # Calibrate and zero channel
display.fill(0)
display.print("STARTING")
# variables and states
clock = time.monotonic() # time.monotonic() device
reset_clock = time.monotonic()
long_clock = time.monotonic()
mode = "run"
mode_names = ["SHOW OZ?", " GRAMS?", " ZERO?", "CALIBRTE", " OFFSET?"]
stage = 0
zero_stage = 0
weight_avg = 0
zero_avg = 0
show_oz = True
show_grams = False
zero_out = False
calibrate_mode = False
blue_btn_pressed = False
green_btn_pressed = False
run_mode = True
avg_read = []
values = []
val_offset = 0
avg_values = []
for w in range(5):
nau7802.channel = 1
value = read_raw_value()
# takes value reading and divides with by the offset value
# to get the weight in grams
grams = value / calibration['offset_val']
avg_read.append(grams)
if len(avg_read) > 4:
the_avg = find_average(avg_read)
oz = the_avg / 28.35
display.print(" %0.1f oz" % oz)
avg_read.clear()
time.sleep(1)
while True:
# button debouncing
if blue_btn.value and blue_btn_pressed:
blue_btn_pressed = False
if green_btn.value and green_btn_pressed:
green_btn_pressed = False
green.value = False
# default run mode
# checks NAU7802 every 2 seconds
if run_mode is True and (time.monotonic() - clock) > 2:
nau7802.channel = 1
value = read_raw_value()
print(value)
value = abs(value) - val_offset
print(value)
#value = abs(value)
values.append(value)
# takes value reading and divides with by the offset value
# to get the weight in grams
grams = value / calibration['offset_val']
oz = grams / 28.35
if show_oz is True:
# append reading
avg_read.append(oz)
label = "oz"
if show_grams is True:
avg_read.append(grams)
label = "g"
print(avg_read)
if len(avg_read) > 10:
the_avg = find_average(avg_read)
display.print(" %0.1f %s" % (the_avg, label))
avg_read.clear()
val_offset += 10
clock = time.monotonic()
if (time.monotonic() - reset_clock) > 43200:
run_mode = False
show_oz = False
show_grams = False
zero_out = True
reset_clock = time.monotonic()
# if you press the change mode button
if (not green_btn.value and not green_btn_pressed) and run_mode:
green.value = True
# disables run mode (stops weighing)
run_mode = False
show_oz = False
show_grams = False
# mode is set to 0
mode = 0
# display shows the mode option
display.print(mode_names[mode])
blue.value = True
green_btn_pressed = True
# advances through the modes menu
if (not green_btn.value and not green_btn_pressed) and mode != "run":
green.value = True
# counts up to 4 and loops back to 0
mode = (mode+1) % 5
# updates display
display.print(mode_names[mode])
green_btn_pressed = True
# if you select show_oz
if (not blue_btn.value and not blue_btn_pressed) and mode == 0:
# show_oz is set as the state
show_oz = True
label = "oz"
blue.value = False
# goes back to weighing mode
mode = "run"
blue_btn_pressed = True
display.print(" %0.1f %s" % (the_avg, label))
run_mode = True
# if you select show_grams
if (not blue_btn.value and not blue_btn_pressed) and mode == 1:
# show_grams is set as the state
show_grams = True
label = "g"
blue.value = False
# goes back to weighing mode
mode = "run"
blue_btn_pressed = True
display.print(" %0.1f %s" % (the_avg, label))
run_mode = True
# if you select zero_out
if (not blue_btn.value and not blue_btn_pressed) and mode == 2:
# zero_out is set as the state
# can zero out the scale without full recalibration
zero_out = True
blue.value = False
mode = "run"
blue_btn_pressed = True
# if you select calibrate_mode
if (not blue_btn.value and not blue_btn_pressed) and mode == 3:
# calibrate_mode is set as the state
# starts up the calibration process
calibrate_mode = True
blue.value = False
mode = "run"
blue_btn_pressed = True
# if you select the offset
if (not blue_btn.value and not blue_btn_pressed) and mode == 4:
# displays the curren offset value stored in the code
blue.value = False
display.fill(0)
display.print("%0.4f" % calibration['offset_val'])
time.sleep(5)
mode = "run"
# goes back to weighing mode
show_oz = True
label = "oz"
display.print(" %0.1f %s" % (the_avg, label))
run_mode = True
blue_btn_pressed = True
# if the zero_out state is true
if zero_out and zero_stage == 0:
blue_btn_pressed = True
# clear the scale for zeroing
display.fill(0)
display.print("REMOVE ")
zero_stage = 1
blue.value = True
green.value = True
if (not blue_btn.value and not blue_btn_pressed) and zero_stage == 1:
green.value = False
# updates display
display.fill(0)
display.print("ZEROING")
blue.value = False
# runs zero_channel() function on both channels
nau7802.channel = 1
zero_channel()
display.fill(0)
display.print("ZEROED ")
zero_out = False
zero_stage = 0
# goes into weighing mode
val_offset = 0
run_mode = True
show_oz = True
label = "oz"
display.print(" %0.1f %s" % (the_avg, label))
# the calibration process
# each step is counted in stage
# blue button is pressed to advance to the next stage
if calibrate_mode is True and stage == 0:
blue_btn_pressed = True
# clear the scale for zeroing
display.fill(0)
display.print("REMOVE ")
stage = 1
blue.value = True
# stage 2
if (not blue_btn.value and not blue_btn_pressed) and stage == 1:
blue_btn_pressed = True
# runs the zero out function
display.fill(0)
display.print("ZEROING")
blue.value = False
nau7802.channel = 1
zero_channel()
display.fill(0)
display.print("ZEROED ")
stage = 2
blue.value = True
# stage 3
if (not blue_btn.value and not blue_btn_pressed) and stage == 2:
blue_btn_pressed = True
blue.value = False
display.print("STARTING")
blink(blue, 0.5, 3)
zero_readings = []
display.print("AVG ZERO")
# runs the calculateCallibration function
# takes 10 raw readings, stores them into an array and gets an average
zero_avg = calculateCalibration(zero_readings)
stage = 3
display.fill(0)
display.print("DONE")
blue.value = True
# stage 4
if (not blue_btn.value and not blue_btn_pressed) and stage == 3:
# place the known weight item
# item's weight matches calibration['weight'] in grams
blue_btn_pressed = True
blue.value = False
display.fill(0)
display.print("PUT ITEM")
stage = 4
blue.value = True
# stage 5
if (not blue_btn.value and not blue_btn_pressed) and stage == 4:
blue_btn_pressed = True
blue.value = False
display.fill(0)
display.print("WEIGHING")
weight_readings = []
# weighs the item 10 times, stores the readings in an array & averages them
weight_avg = calculateCalibration(weight_readings)
# calculates the new offset value
calibration['offset_val'] = (weight_avg-zero_avg) / calibration['weight']
display.marquee("%0.2f - CALIBRATED " % calibration['offset_val'], 0.3, False)
stage = 5
display.fill(0)
display.print("DONE")
blue.value = True
# final stage
if (not blue_btn.value and not blue_btn_pressed) and stage == 5:
blue_btn_pressed = True
zero_readings.clear()
weight_readings.clear()
calibrate_mode = False
blue.value = False
# goes back into weighing mode
show_oz = True
label = "oz"
display.print(" %0.1f %s" % (the_avg, label))
val_offset = 0
run_mode = True
# resets stage
stage = 0
Upload the Code and Libraries to the QT Py ESP32-S2
After downloading the Project Bundle, plug your QT Py ESP32-S2 into the computer's USB port with a known good USB data+power cable. You should see a new flash drive appear in the computer's File Explorer or Finder (depending on your operating system) called CIRCUITPY. Unzip the folder and copy the following items to the QT Py ESP32-S2's CIRCUITPY drive.Â
- lib folder
- calibration.py
- code.py
Your QT Py ESP32-S2 CIRCUITPY drive should look like this after copying the lib folder, calibration.py file and the code.py file.
Install the cedargrove_nau7802 CircuitPython Library
Follow along with the steps outlined in this guide to download the cedargrove_nau7802 CircuitPython library and upload it to your QT Py ESP32-S2 CIRCUITPY drive lib folder. The library is a part of the CircuitPython Community Bundle.
The calibration.py file holds two important values for the main code.py file: offset_val and weight. weight is the known weight in grams that you use to calibrate the NAU7802. The offset_val is the calibration number used to divide against the raw value from the NAU7802 to find the actual weight on top of the scale.Â
Each strain gauge is slightly different, so you'll want to edit the calibration.py file with your known weight item and the offset_val you generate after running the calibration mode in the code.py file.
First, the LED segment displays, buttons, button LEDs and NAU7802 are setup.
I2C setup with STEMMA port
i2c = board.STEMMA_I2C()
# alphanumeric segment displpay setup
# using two displays together
display = Seg14x4(i2c, address=(0x70, 0x71))
# start-up text
display.print("*HELLO* ")
# button LEDs
blue = DigitalInOut(board.A1)
blue.direction = Direction.OUTPUT
green = DigitalInOut(board.A3)
green.direction = Direction.OUTPUT
# buttons setup
blue_btn = DigitalInOut(board.A0)
blue_btn.direction = Direction.INPUT
blue_btn.pull = Pull.UP
green_btn = DigitalInOut(board.A2)
green_btn.direction = Direction.INPUT
green_btn.pull = Pull.UP
# nau7802 setup
nau7802 = NAU7802(board.STEMMA_I2C(), address=0x2A, active_channels=2)
nau7802.gain = 128
enabled = nau7802.enable(True)
Functions
There are a few functions for the NAU7802. zero_channel() is from the cedargrove_nau7802 library and resets the NAU7802 to be zeroed out. The blue button's LED blinks as the zeroing process takes place.
# zeroing function
def zero_channel():
"""Initiate internal calibration for current channel; return raw zero
offset value. Use when scale is started, a new channel is green_btned, or to
adjust for measurement drift. Remove weight and tare from load cell before
executing."""
blue.value = True
print(
"channel %1d calibrate.INTERNAL: %5s"
% (nau7802.channel, nau7802.calibrate("INTERNAL"))
)
blue.value = False
print(
"channel %1d calibrate.OFFSET: %5s"
% (nau7802.channel, nau7802.calibrate("OFFSET"))
)
blue.value = True
zero_offset = read_raw_value(100) # Read 100 samples to establish zero offset
print("...channel %1d zeroed" % nau7802.channel)
blue.value = False
return zero_offset
read_raw_value() reads the raw value from the NAU7802 and is also from the cedargrove_nau7802 library.
# read raw value function
def read_raw_value(samples=100):
"""Read and average consecutive raw sample values. Return average raw value."""
sample_sum = 0
sample_count = samples
while sample_count > 0:
if nau7802.available:
sample_sum = sample_sum + nau7802.read()
sample_count -= 1
return int(sample_sum / samples)
find_average(num) is a quick function for finding the average of an array of values.
# function for finding the average of an array
def find_average(num):
count = 0
for n in num:
count = count + n
average = count / len(num)
return average
calculateCalibration(array) is the function used for calibrating the NAU7802 to find the offset_val. It runs read_raw_value() ten times and appends those values to an array. That array is averaged and the average is returned.
def calculateCalibration(array):
for _ in range(10):
blue.value = True
green.value = False
nau7802.channel = 1
#value = read_raw_value()
print("channel %1.0f raw value: %7.0f" % (nau7802.channel, abs(read_raw_value())))
array.append(abs(read_raw_value()))
blue.value = False
green.value = True
time.sleep(1)
green.value = False
avg = find_average(array)
return avg
blink(led, amount, count) is a quick function for blinking an LED. You pass the LED's pin for led, length of time for each blink for amount and number of blinks for count.
# blink LED function
def blink(led, amount, count):
for _ in range(count):
led.value = True
time.sleep(amount)
led.value = False
time.sleep(amount)
Startup Reading
On startup, the NAU7802 is zeroed and then an initial reading is taken and displayed in ounces. The displays and button LEDs let you know what is happening in the code.
# zeroing on startup
display.fill(0)
display.marquee("CLEAR SCALE CLEAR", 0.3, False)
time.sleep(2)
display.fill(0)
display.print("ZEROING")
time.sleep(3)
# zeroing each channel
nau7802.channel = 1
zero_channel() # Calibrate and zero channel
display.fill(0)
display.print("STARTING")
for w in range(5):
nau7802.channel = 1
value = read_raw_value()
# takes value reading and divides with by the offset value
# to get the weight in grams
grams = value / calibration['offset_val']
avg_read.append(grams)
if len(avg_read) > 4:
the_avg = find_average(avg_read)
oz = the_avg / 28.35
display.print(" %0.1f oz" % oz)
avg_read.clear()
averaging = 0
time.sleep(1)
The Loop
The loop has multiple modes that can be selected with states. The default mode is run_mode. In run_mode, the NAU7802 takes a reading every two seconds. This value is converted to grams and ounces and is appended to the avg_read array.Â
Once the length of avg_read is greater than 10, avg_read is averaged. The return is shown on the displays and then the process begins again. After this process runs 100 times, the scale prompts you to zero it out to retain accuracy.
The scale can show the weight in either ounces or grams, depending on whether show_oz or show_grams is True.Â
# default run mode
# checks NAU7802 every 2 seconds
if run_mode is True and (time.monotonic() - clock) > 2:
nau7802.channel = 1
value = read_raw_value()
value = abs(value) - val_offset
# takes value reading and divides with by the offset value
# to get the weight in grams
grams = value / calibration['offset_val']
oz = grams / 28.35
if show_oz is True:
# append reading
avg_read.append(oz)
label = "oz"
if show_grams is True:
avg_read.append(grams)
label = "g"
if len(avg_read) > 10:
the_avg = find_average(avg_read)
display.print(" %0.1f %s" % (the_avg, label))
avg_read.clear()
val_offset += 12
count += 1
if count > 100:
count = 0
run_mode = False
show_oz = False
show_grams = False
zero_out = True
clock = time.monotonic()
Mode Select
If you press the green button, you get into mode select mode. This stops run_mode and displays the mode options on the displays. You can press the green button to cycle through the options and press the blue button to select a mode.
# if you press the change mode button
if (not green_btn.value and not green_btn_pressed) and run_mode:
green.value = True
# disables run mode (stops weighing)
run_mode = False
show_oz = False
show_grams = False
# mode is set to 0
mode = 0
# display shows the mode option
display.print(mode_names[mode])
blue.value = True
green_btn_pressed = True
Zeroing Mode
show_oz and show_grams change whether you're showing the weight in ounces or grams and goes back into run_mode once they're selected.
zero_out mode allows you to re-zero the NAU7802 at anytime. You'll be prompted to remove the weight from the scale, press the blue button and then zero_channel() runs.
# if you select zero_out
if (not blue_btn.value and not blue_btn_pressed) and mode == 2:
# zero_out is set as the state
# can zero out the scale without full recalibration
zero_out = True
blue.value = False
mode = "run"
blue_btn_pressed = True
# if the zero_out state is true
if zero_out and zero_stage == 0:
blue_btn_pressed = True
# clear the scale for zeroing
display.fill(0)
display.print("REMOVE ")
zero_stage = 1
blue.value = True
green.value = True
if (not blue_btn.value and not blue_btn_pressed) and zero_stage == 1:
green.value = False
# updates display
display.fill(0)
display.print("ZEROING")
blue.value = False
# runs zero_channel() function on both channels
nau7802.channel = 1
zero_channel()
display.fill(0)
display.print("ZEROED ")
zero_out = False
stage = 0
# goes into weighing mode
val_offset = 0
run_mode = True
show_oz = True
label = "oz"
display.print(" %0.1f %s" % (the_avg, label))
Show Offset Mode
By selecting show offset, the current offset_val is displayed for 5 seconds. This is helpful for updating your calibration.py file and other troubleshooting.
# if you select the offset
if (not blue_btn.value and not blue_btn_pressed) and mode == 4:
# displays the curren offset value stored in the code
blue.value = False
display.fill(0)
display.print("%0.4f" % calibration['offset_val'])
time.sleep(5)
mode = "run"
# goes back to weighing mode
show_oz = True
label = "oz"
display.print(" %0.1f %s" % (the_avg, label))
run_mode = True
blue_btn_pressed = True
Calibration Mode
calibration_mode lets you recalibrate the NAU7802 by running through the zero_channel() function, calculateCalibration() function with the scale zeroed, calculateCalibration() function with the defined weight object on the scale and then finding the new offset_val.
The steps of the process are advanced by button presses that trigger and increase the value of stage. This ensures that the scale is prepped for each stage of the calibration.
calculateCalibration() returns an average of the NAU7802's readings. The average of the scale with nothing on it (zero_avg) and the average of the scale with the defined weight object (weight_avg) are subtracted from each other and then divided by weight. The result of that sets the new offset_val.
# stage 5
if (not blue_btn.value and not blue_btn_pressed) and stage == 4:
blue_btn_pressed = True
blue.value = False
display.fill(0)
display.print("WEIGHING")
weight_readings = []
# weighs the item 10 times, stores the readings in an array & averages them
weight_avg = calculateCalibration(weight_readings)
# calculates the new offset value
calibration['offset_val'] = (weight_avg-zero_avg) / calibration['weight']
display.marquee("%0.2f - CALIBRATED " % calibration['offset_val'], 0.3, False)
stage = 5
display.fill(0)
display.print("DONE")
blue.value = True
Page last edited January 22, 2025
Text editor powered by tinymce.