Let's take a walk through the code and look in more detail how each section works within code.py.
The main module, code.py, prepares and operates the Coffee Scale. It consists of the following major sections:
- Import and Set Defaults
- Calibration Ratio
- Instantiate the Sensor and Display
- Display the Background Bitmap Image
- Define and Display the Text Labels and Graphic Elements
- Helpers
- Activate the Sensor and Prepare to Loop
- The Primary Code Loop
Import and Set Defaults
This section imports all the needed modules and libraries, including the NAU7802 sensor driver. After importing, the first visible task of this section is to turn the Clue NeoPixel to the color yellow to indicate that the Coffee Scale is initializing. During operation, the indicator LED will glow green when operating normally or red when zeroing the scale.
Next, the scale defaults are specified. These are constants with names that are capitalized to help identify them as constants. These are values that you can change to alter the operation of the scale.
MAX_GR
is the full-scale value of the scale in grams. The tick mark values adjacent to the graduated scale graphic are automatically derived from MAX_GR
which can by any positive integer value. Since the graduated scale graphic is divided into tenths of full-scale with five tick marks, it's best to choose an integer MAX_GR
value that will display nicely such as 1000, 500, 250, 100, 50, 25, 10, or 5.
DEFAULT_GAIN
is the gain setting of the NAU7802's internal ADC pre-amplifier. Normally, this is set to the highest value of 128.
SAMPLE_AVG
specifies the number of measurements that are averaged when the load cell is measured. The measurement value will be more stable with a high SAMPLE_AVG
value. The display will update more slowly with a high value; it's a trade-off between stability and speed.
SCALE_NAME_1
is the text string that appears on the display to the left of the graduated scale. This should be no longer than 6 characters. Upper case letters will look the best.
SCALE_NAME_2
is the text string that appears on the display to the right of the graduated scale. This should be no longer than 6 characters. Upper case letters will look the best on the display.
# import clue_scale_calibrator # Uncomment to run calibrator method import time import board from simpleio import map_range from adafruit_clue import clue from adafruit_display_shapes.circle import Circle from adafruit_display_text.label import Label from adafruit_bitmap_font import bitmap_font import displayio from cedargrove_nau7802 import NAU7802 clue.pixel.brightness = 0.2 # Set NeoPixel brightness clue.pixel[0] = clue.YELLOW # Set status indicator to yellow (initializing) # Set Scale Defaults MAX_GR = 100 # Maximum (full-scale) display range in grams DEFAULT_GAIN = 128 # Default gain for internal PGA SAMPLE_AVG = 5 # Number of sample values to average SCALE_NAME_1 = "COFFEE" # 6 characters maximum SCALE_NAME_2 = "SCALE" # 6 characters maximum
Calibration Ratio
CALIB_RATIO
is the factor that is used to convert the NAU7802's raw measurement value into grams. This ratio is updated after running the calibrator method. See the guide section, Calibrate the Load Cell for instructions. You should only have to measure and record the load cell calibration ratio once.
"""Enter the calibration ratio for the individual load cell in-use. The ratio is composed of the reference weight in grams divided by the raw reading. For example, a raw reading of 215300 for a 100 gram weight results in a calibration ratio of 100 / 215300. Use the clue_scale_single_calibrate method to obtain the raw value. FYI: A US dime coin weighs 2.268 grams or 0.079 ounces.""" CALIB_RATIO = 100 / 215300 # load cell serial#4540-02
Instantiate the Sensor and Display
The NAU7802 Stemma board is connected to the Clue's I2C bus and lives at address 42 (hexadecimal 2A). The NAU7802 24-bit ADC (analog to digital converter) chip is a dual-channel device, but only the first channel is available on the Stemma board; active_channels
is set to 1
.
Next, the Clue's integrated display is instantiated along with the primary displayio graphics group layer, scale_group
which will contain the background bitmap image and other group layers.
The three font objects for display titles, labels, and measurements are listed as constants that will be used when display labels are defined.
# Instantiate the Sensor and Display nau7802 = NAU7802(board.I2C(), address=0x2A, active_channels=1) display = board.DISPLAY scale_group = displayio.Group() FONT_0 = bitmap_font.load_font("/fonts/Helvetica-Bold-24.bdf") FONT_1 = bitmap_font.load_font("/fonts/OpenSans-16.bdf") FONT_2 = bitmap_font.load_font("/fonts/OpenSans-9.bdf")
The display background containing the graduated scale is loaded from the Clue root directory and is appended as the first item in the primary displayio group layer. All other graphics objects such as labels and the indicator bubble will be put on layers above the background.
# Display the Background Bitmap Image bkg = displayio.OnDiskBitmap("/clue_scale_bkg.bmp") _background = displayio.TileGrid(bkg, pixel_shader=bkg.pixel_shader, x=0, y=0) scale_group.append(_background)
This code section defines the graphic elements that will be placed on the display in front of the background graphic. First, the scale name text and zeroing button graphics are appended to the primary displayio group.
The subsection that starts with for i in range..
. is the code that steps through the graduated scale's tick marks, creating a value label calculated using the MAX_GR
constant and appending them to the displayio group. Measurement values and units labels added next.
Lastly, the displayio indicator_group
and it's floating indicator bubble is created and added to the primary displayio group. The indicator_group
becomes the front-most graphics layer of the display.
The indicator bubble is a yellow circle that travels up and down the graduated scale, pointing to the measured value. The center of the circle will normally be transparent, but will appear yellow or red depending on the scale's current status; yellow when initializing, red when zeroing.
# Define and Display the Text Labels and Graphic Elements # Place the project name on either side of the graduated scale scale_name_1 = Label(FONT_1, text=SCALE_NAME_1, color=clue.CYAN) scale_name_1.anchor_point = (0.5, 0.5) scale_name_1.anchored_position = (40, 96) scale_group.append(scale_name_1) scale_name_2 = Label(FONT_1, text=SCALE_NAME_2, color=clue.CYAN) scale_name_2.anchor_point = (0.5, 0.5) scale_name_2.anchored_position = (199, 96) scale_group.append(scale_name_2) # Define the zeroing button graphic zero_button_circle = Circle(14, 152, 14, fill=None, outline=clue.RED, stroke=2) scale_group.append(zero_button_circle) zero_button_label = Label(FONT_1, text="Z", color=clue.RED) zero_button_label.x = 8 zero_button_label.y = 150 scale_group.append(zero_button_label) # Place tickmark labels next to the graduated scale for i in range(-1, 6): tick_value = Label(FONT_2, text=str((MAX_GR) // 5 * i), color=clue.CYAN) if i == -1: tick_value.anchor_point = (1.0, 1.1) elif i == 5: tick_value.anchor_point = (1.0, 0.0) else: tick_value.anchor_point = (1.0, 0.5) tick_value.anchored_position = (99, 201 - (i * 40)) scale_group.append(tick_value) # Place the grams and ounces labels and values near the bottom of the display grams_label = Label(FONT_0, text="grams", color=clue.BLUE) grams_label.anchor_point = (1.0, 0) grams_label.anchored_position = (80, 216) scale_group.append(grams_label) ounces_label = Label(FONT_0, text="ounces", color=clue.BLUE) ounces_label.anchor_point = (1.0, 0) ounces_label.anchored_position = (230, 216) scale_group.append(ounces_label) grams_value = Label(FONT_0, text="0.0", color=clue.WHITE) grams_value.anchor_point = (1.0, 0.5) grams_value.anchored_position = (80, 200) scale_group.append(grams_value) ounces_value = Label(FONT_0, text="0.00", color=clue.WHITE) ounces_value.anchor_point = (1.0, 0.5) ounces_value.anchored_position = (230, 200) scale_group.append(ounces_value) # Define the moveable indicator bubble indicator_group = displayio.Group() bubble = Circle(120, 200, 10, fill=clue.YELLOW, outline=clue.YELLOW, stroke=3) indicator_group.append(bubble) scale_group.append(indicator_group) display.root_group = scale_group
Helpers
These two helper functions work with the NAU7802 driver class to zero the scale and to read the load cell's current raw value.
The zero_channel( )
helper sets up and zeros the NAU7802's internal amplifiers and ADC (analog to digital converter) to prepare it to receive signals. This function is used when the scale is first powered-up as well as when manually zeroed by pressing the Clue's button A.
The load cell's raw measurement value is obtained by the read( )
helper. This helper accepts an integer parameter that specifies the number of samples to be averaged each time the helper is called, defaulting to 1
sample (no averaging). The helper returns the averaged raw measurement value. Since the NAU7802's internal ADC has 24-bit accuracy, the raw value can range from about -8.3M to + 8.3M.
# Helpers def zero_channel(): """Prepare internal amplifier settings and zero the current channel. Use after power-up, a new channel is selected, or to adjust for measurement drift. Can be used to zero the scale with a tare weight. The nau7802.calibrate function used here does not calibrate the load cell, but sets the NAU7802 internals to prepare for measuring input signals.""" nau7802.calibrate("INTERNAL") nau7802.calibrate("OFFSET") def read(samples=1): """Read and average consecutive raw samples; return averaged 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)
Activate the Sensor and Prepare to Loop
This is where the NAU7802 ADC is enabled and calibrated for use. Before calibrating and zeroing, the internal sensor amplifier's gain is set to the default value. Once completed, the Clue will chirp some welcoming notes.
# Activate the Sensor # Enable the internal analog circuitry, set gain, and calibrate/zero nau7802.enable(True) nau7802.gain = DEFAULT_GAIN zero_channel() # Play "welcome" tones clue.play_tone(1660, 0.15) clue.play_tone(1440, 0.15)
The Primary Code Loop
The primary loop is the operational process of the scale. The loop indicates the scale's status on the Clue NeoPixel; green when the scale is operating, red when it's busy zeroing.
After setting the NeoPixel to green, the load cell raw value is measured and converted to grams and ounces. The converted values are formatted and placed into the corresponding display labels and is printed in the REPL.
The grams measurement value is used to position the on-screen indicator bubble along the graduated scale graphic using the map_range( )
function. Also, if the grams measurement value falls outside of the minimum or maximum range, the bubble is "parked" at the extreme position and its interior color is changed from transparent to red.
Finally, the Clue A button is watched to see if it has been pressed. When pressed, the Clue will play a sound and will begin zeroing the scale. During the zeroing process, the NeoPixel and the center of the bubble are set to a red color. When zeroing is completed, the code will wait until the button is released before playing a completion sound, setting the bubble interior to transparent, and returning to normal operation.
# The Primary Code Loop # Read sensor, move bubble, and display values while True: clue.pixel[0] = clue.GREEN # Set status indicator to green (ready) # Read the raw scale value and scale for grams and ounces value = read(SAMPLE_AVG) mass_grams = round(value * CALIB_RATIO, 1) mass_ounces = round(mass_grams * 0.03527, 2) grams_value.text = f"{mass_grams:5.1f}" ounces_value.text = f"{mass_ounces:5.2f}" print(f" {mass_grams:5.1f} grams {mass_ounces:5.2f} ounces") # Reposition the indicator bubble based on grams value min_gr = (MAX_GR // 5) * -1 # Minimum display value bubble.y = int(map_range(mass_grams, min_gr, MAX_GR, 240, 0)) - 10 if mass_grams > MAX_GR or mass_grams < min_gr: bubble.fill = clue.RED else: bubble.fill = None # Check to see if the zeroing button is pressed if clue.button_a: # Zero the sensor clue.pixel[0] = clue.RED # Set status indicator to red (stopped) bubble.fill = clue.RED # Set bubble center to red (stopped) clue.play_tone(1660, 0.3) # Play "button pressed" tone zero_channel() while clue.button_a: # Wait until the button is released time.sleep(0.1) clue.play_tone(1440, 0.5) # Play "reset completed" tone bubble.fill = None # Set bubble center to transparent (ready)
Page last edited March 08, 2024
Text editor powered by tinymce.