/ Clue_Scale / code.py
code.py
1 # SPDX-FileCopyrightText: 2022 Jan Goolsbey for Adafruit Industries 2 # SPDX-License-Identifier: MIT 3 # 4 # clue_scale_code.py 5 # 2022-07-29 v1.2.0 6 # 7 # Clue Scale - Single Channel Version 8 # Adafruit NAU7802 Stemma breakout example 9 10 # import clue_scale_calibrator # Uncomment to run calibrator method 11 12 import time 13 import board 14 from simpleio import map_range 15 from adafruit_clue import clue 16 from adafruit_display_shapes.circle import Circle 17 from adafruit_display_text.label import Label 18 from adafruit_bitmap_font import bitmap_font 19 import displayio 20 from cedargrove_nau7802 import NAU7802 21 22 clue.pixel.brightness = 0.2 # Set NeoPixel brightness 23 clue.pixel[0] = clue.YELLOW # Set status indicator to yellow (initializing) 24 25 # Set Scale Defaults 26 MAX_GR = 100 # Maximum (full-scale) display range in grams 27 DEFAULT_GAIN = 128 # Default gain for internal PGA 28 SAMPLE_AVG = 100 # Number of sample values to average 29 SCALE_NAME_1 = "COFFEE" # 6 characters maximum 30 SCALE_NAME_2 = "SCALE" # 6 characters maximum 31 32 """Enter the calibration ratio for the individual load cell in-use. The ratio is 33 composed of the reference weight in grams divided by the raw reading. For 34 example, a raw reading of 215300 for a 100 gram weight results in a calibration 35 ratio of 100 / 215300. Use the clue_scale_single_calibrate method to obtain the 36 raw value. 37 FYI: A US dime coin weighs 2.268 grams or 0.079 ounces.""" 38 CALIB_RATIO = 100 / 215300 # load cell serial#4540-02 39 40 # Instantiate the Sensor and Display 41 nau7802 = NAU7802(board.I2C(), address=0x2A, active_channels=1) 42 43 display = board.DISPLAY 44 scale_group = displayio.Group() 45 46 FONT_0 = bitmap_font.load_font("/fonts/Helvetica-Bold-24.bdf") 47 FONT_1 = bitmap_font.load_font("/fonts/OpenSans-16.bdf") 48 FONT_2 = bitmap_font.load_font("/fonts/OpenSans-9.bdf") 49 50 # Display the Background Bitmap Image 51 bkg = displayio.OnDiskBitmap("/clue_scale_bkg.bmp") 52 _background = displayio.TileGrid(bkg, pixel_shader=bkg.pixel_shader, x=0, y=0) 53 scale_group.append(_background) 54 55 # Define and Display the Text Labels and Graphic Elements 56 # Place the project name on either side of the graduated scale 57 scale_name_1 = Label(FONT_1, text=SCALE_NAME_1, color=clue.CYAN) 58 scale_name_1.anchor_point = (0.5, 0.5) 59 scale_name_1.anchored_position = (40, 96) 60 scale_group.append(scale_name_1) 61 62 scale_name_2 = Label(FONT_1, text=SCALE_NAME_2, color=clue.CYAN) 63 scale_name_2.anchor_point = (0.5, 0.5) 64 scale_name_2.anchored_position = (199, 96) 65 scale_group.append(scale_name_2) 66 67 # Define the zeroing button graphic 68 zero_button_circle = Circle(14, 152, 14, fill=None, outline=clue.RED, stroke=2) 69 scale_group.append(zero_button_circle) 70 71 zero_button_label = Label(FONT_1, text="Z", color=clue.RED) 72 zero_button_label.x = 8 73 zero_button_label.y = 150 74 scale_group.append(zero_button_label) 75 76 # Place tickmark labels next to the graduated scale 77 for i in range(-1, 6): 78 tick_value = Label(FONT_2, text=str((MAX_GR) // 5 * i), color=clue.CYAN) 79 if i == -1: 80 tick_value.anchor_point = (1.0, 1.1) 81 elif i == 5: 82 tick_value.anchor_point = (1.0, 0.0) 83 else: 84 tick_value.anchor_point = (1.0, 0.5) 85 tick_value.anchored_position = (99, 201 - (i * 40)) 86 scale_group.append(tick_value) 87 88 # Place the grams and ounces labels and values near the bottom of the display 89 grams_label = Label(FONT_0, text="grams", color=clue.BLUE) 90 grams_label.anchor_point = (1.0, 0) 91 grams_label.anchored_position = (80, 216) 92 scale_group.append(grams_label) 93 94 ounces_label = Label(FONT_0, text="ounces", color=clue.BLUE) 95 ounces_label.anchor_point = (1.0, 0) 96 ounces_label.anchored_position = (230, 216) 97 scale_group.append(ounces_label) 98 99 grams_value = Label(FONT_0, text="0.0", color=clue.WHITE) 100 grams_value.anchor_point = (1.0, 0.5) 101 grams_value.anchored_position = (80, 200) 102 scale_group.append(grams_value) 103 104 ounces_value = Label(FONT_0, text="0.00", color=clue.WHITE) 105 ounces_value.anchor_point = (1.0, 0.5) 106 ounces_value.anchored_position = (230, 200) 107 scale_group.append(ounces_value) 108 109 # Define the moveable indicator bubble 110 indicator_group = displayio.Group() 111 bubble = Circle(120, 200, 10, fill=clue.YELLOW, outline=clue.YELLOW, stroke=3) 112 indicator_group.append(bubble) 113 114 scale_group.append(indicator_group) 115 display.show(scale_group) 116 117 118 # Helpers 119 def zero_channel(): 120 """Prepare internal amplifier settings and zero the current channel. Use 121 after power-up, a new channel is selected, or to adjust for measurement 122 drift. Can be used to zero the scale with a tare weight. 123 The nau7802.calibrate function used here does not calibrate the load cell, 124 but sets the NAU7802 internals to prepare for measuring input signals.""" 125 nau7802.calibrate("INTERNAL") 126 nau7802.calibrate("OFFSET") 127 128 129 def read(samples=100): 130 """Read and average consecutive raw samples; return averaged value.""" 131 sample_sum = 0 132 sample_count = samples 133 while sample_count > 0: 134 if nau7802.available: 135 sample_sum = sample_sum + nau7802.read() 136 sample_count -= 1 137 return int(sample_sum / samples) 138 139 140 # Activate the Sensor 141 # Enable the internal analog circuitry, set gain, and zero 142 nau7802.enable(True) 143 nau7802.gain = DEFAULT_GAIN 144 zero_channel() 145 146 # Play "welcome" tones 147 clue.play_tone(1660, 0.15) 148 clue.play_tone(1440, 0.15) 149 150 # The Primary Code Loop 151 # Read sensor, move bubble, and display values 152 while True: 153 clue.pixel[0] = clue.GREEN # Set status indicator to green (ready) 154 155 # Read the raw scale value and scale for grams and ounces 156 value = read(SAMPLE_AVG) 157 mass_grams = round(value * CALIB_RATIO, 1) 158 mass_ounces = round(mass_grams * 0.03527, 2) 159 grams_value.text = f"{mass_grams:5.1f}" 160 ounces_value.text = f"{mass_ounces:5.2f}" 161 print(f" {mass_grams:5.1f} grams {mass_ounces:5.2f} ounces") 162 163 # Reposition the indicator bubble based on grams value 164 min_gr = (MAX_GR // 5) * -1 # Minimum display value 165 bubble.y = int(map_range(mass_grams, min_gr, MAX_GR, 240, 0)) - 10 166 if mass_grams > MAX_GR or mass_grams < min_gr: 167 bubble.fill = clue.RED 168 else: 169 bubble.fill = None 170 171 # Check to see if the zeroing button is pressed 172 if clue.button_a: 173 # Zero the sensor 174 clue.pixel[0] = clue.RED # Set status indicator to red (stopped) 175 bubble.fill = clue.RED # Set bubble center to red (stopped) 176 clue.play_tone(1660, 0.3) # Play "button pressed" tone 177 178 zero_channel() 179 180 while clue.button_a: 181 # Wait until the button is released 182 time.sleep(0.1) 183 184 clue.play_tone(1440, 0.5) # Play "reset completed" tone 185 bubble.fill = None # Set bubble center to transparent (ready)