/ 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)