Using a SAMD51 based board gives plenty of memory and speed for writing in CircuitPython. Because of that it was decided from the start to write the code in CircuitPython.
Getting Familiar
CircuitPython is a programming language based on Python, one of the fastest growing programming languages in the world. It is specifically designed to simplify experimenting and learning to code on low-cost microcontroller boards. Here are some guides which cover the basics:
Be sure you have the latest CircuitPython loaded onto your board per the second guide.
CircuitPython is easiest to use within the Mu Editor. If you haven't previously used Mu, this guide will get you started.
Download Library Files
Plug your Feather M4 Express board into your computer via a USB cable. Please be sure the cable is a good power+data cable so the computer can talk to the Feather board.
A new disk should appear in your computer's file explorer/finder called CIRCUITPY. This is the place we'll copy the code and code library. If you can only get a drive named CPLAYBOOT, load CircuitPython per the guide above.
Create a new directory on the CIRCUITPY drive named lib.
Download the latest CircuitPython driver package to your computer using the green button below. Match the library you get to the version of CircuitPython you are using. Save to your computer's hard drive where you can find it.
With your file explorer/finder, browse to the bundle and open it up. Copy the following folder from the library bundle to your CIRCUITPY lib directory you made earlier:
- adafruit_bus_device
- adafruit_epd
- adafruit_si7021.mpy
All of the other necessary code is baked into CircuitPython!
Your CIRCUITPY drive should look like the snapshot below.
Download Code
Below is the code for this project. Select download project zip below and save it to your computer's hard drive where you can find it.
# SPDX-FileCopyrightText: 2019 Dave Astels for Adafruit Industries # # SPDX-License-Identifier: MIT import digitalio import board done = digitalio.DigitalInOut(board.A4) done.direction = digitalio.Direction.OUTPUT done.value = False #pylint: disable=wrong-import-position,wrong-import-order import time import pwmio import busio from adafruit_epd.epd import Adafruit_EPD from adafruit_epd.il0373 import Adafruit_IL0373 import adafruit_si7021 import font #-------------------------------------------------- # Setup spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO) ecs = digitalio.DigitalInOut(board.D11) dc = digitalio.DigitalInOut(board.D10) srcs = digitalio.DigitalInOut(board.D9) rst = digitalio.DigitalInOut(board.D6) busy = digitalio.DigitalInOut(board.D12) display = Adafruit_IL0373(152, 152, rst, dc, busy, srcs, ecs, spi) i2c = busio.I2C(board.SCL, board.SDA) sensor = adafruit_si7021.SI7021(i2c) ON = 2**15 OFF = 0 buzzer = pwmio.PWMOut(board.D5, variable_frequency=True) buzzer.duty_cycle = OFF silence_button = digitalio.DigitalInOut(board.A5) silence_button.direction = digitalio.Direction.INPUT silence_button.pull = digitalio.Pull.UP #-------------------------------------------------- # Default parameter values settings = {} settings['temperature_range'] = (15, 30) settings['humidity_range'] = (60, 70) settings['title'] = 'Weed Minder' settings['alarm_frequency'] = 4000 settings['alarm_number_of_beeps'] = 3 settings['alarm_seconds_beep_on'] = 0.5 settings['alarm_seconds_between_beeps'] = 0.5 settings['alarm_seconds_between_alarms'] = 5.0 settings['alarm_timeout'] = 60.0 #-------------------------------------------------- # Support functions def render_character(x, y, ch, color=Adafruit_EPD.BLACK): """Render a character. :param int x: horizontal position of the left edge of the character :param int y: vertical position of the top edge of the character :param str ch: a single character string to be displayed :param Adafruit_EPD.* color: BLACK or RED, background is always white """ if x < 144 and y < 144: bitmap = font.bitmaps[ord(ch)] for row_num in range(8): row = bitmap[row_num] for column_num in range(8): if (row & 1) == 0: display.draw_pixel(x + column_num, y + row_num, Adafruit_EPD.WHITE) else: display.draw_pixel(x + column_num, y + row_num, color) row >>= 1 def render_string(x, y, s, color=Adafruit_EPD.BLACK): """Render a string. :param int x: horizontal position of the left edge of the string :param int y: vertical position of the top edge of the string :param str ch: a string to be displayed :param Adafruit_EPD.* color: BLACK or RED, background is always white """ x_pos = x for ch in s: render_character(x_pos, y, ch, color) x_pos += 8 def centered(s): """Computer the X position to center a string. :param str s: the string to center """ return 75 - (4 * len(s)) def to_int_tuple(a): """Convert an array of strings to a tuple of ints. :param [int] a: array of strings to convert """ return tuple([int(x.strip()) for x in a]) def check_for_push(button, duration): """Wait for a time, regularly checking for a button push. :param DigitalInOut button: the button input to check :param float duration: seconds to wait Return True if the button is pushed, False if the time passes """ stop_at = time.monotonic() + duration while time.monotonic() < stop_at: if not button.value: return True time.sleep(0.1) return False def sound_alarm(): """Sound the alarm based on the settings.""" buzzer.frequency = settings['alarm_frequency'] for _ in range(settings['alarm_number_of_beeps']): buzzer.duty_cycle = ON if check_for_push(silence_button, settings['alarm_seconds_beep_on']): buzzer.duty_cycle = OFF return True buzzer.duty_cycle = OFF if check_for_push(silence_button, settings['alarm_seconds_between_beeps']): return True return False def out_of_range(t, h): """Check if either temperature and humidity is out of range. :param float t: temperature reading :param float h: humidity reading """ if t < settings['temperature_range'][0]: return True if t > settings['temperature_range'][1]: return True if h < settings['humidity_range'][0]: return True if h > settings['humidity_range'][1]: return True return False #-------------------------------------------------- # Handle edit mode: allow the user to edit description and settings # This is done by waking the device while holding the silence button pressed # A low beep indicated entry and the display will indicate it as well if not silence_button.value: buzzer.frequency = 440 buzzer.duty_cycle = ON time.sleep(0.5) buzzer.duty_cycle = OFF display.clear_buffer() render_string(39, 64, 'EDIT MODE') display.display() while not silence_button.value: # wait for button to be released pass while silence_button.value: # wait for button to be pressed pass buzzer.duty_cycle = ON time.sleep(0.5) buzzer.duty_cycle = OFF # Pressing the silence button again reverts to monitor mode # A low beep indicates this #-------------------------------------------------- # Main script # Read settings file into setting dictionary with open('settings.txt', 'r') as f: for line in f: key, value = [x.strip() for x in line.strip().split(':')] values = value.split('-') if key == 'temperature_range': setting = to_int_tuple(values) elif key == 'humidity_range': setting = to_int_tuple(values) elif key == 'title': setting = value elif key == 'alarm_frequency': setting = int(value) elif key == 'alarm_number_of_beeps': setting = int(value) elif key == 'alarm_seconds_beep_on': setting = float(value) elif key == 'alarm_seconds_between_beeps': setting = float(value) elif key == 'alarm_timeout': setting = float(value) settings[key] = setting # Get text with open('description.txt', 'r') as f: text = [line.strip() for line in f] display.clear_buffer() render_string(centered(settings['title']), 12, settings['title']) # Display text row_index = 64 for line in text: if row_index > 112: break render_string(centered(line), row_index, line) row_index += 10 temperature = int(sensor.temperature) humidity = int(sensor.relative_humidity) render_string(8, 32, '{0:2d} C'.format(temperature)) render_string(112, 32, '{0:2d} %'.format(humidity)) if temperature < settings['temperature_range'][0]: temperature_message = 'LOW TEMPERATURE' elif temperature > settings['temperature_range'][1]: temperature_message = 'HIGH TEMPERATURE' else: temperature_message = '' if humidity < settings['humidity_range'][0]: humidity_message = 'LOW HUMIDITY' elif humidity > settings['humidity_range'][1]: humidity_message = 'HIGH HUMIDITY' else: humidity_message = '' if temperature_message: render_string(centered(temperature_message), 122, temperature_message, Adafruit_EPD.RED) if humidity_message: render_string(centered(humidity_message), 132, humidity_message, Adafruit_EPD.RED) if temperature_message or humidity_message: display.fill_rect(0, 0, 152, 10, Adafruit_EPD.RED) display.fill_rect(0, 142, 152, 10, Adafruit_EPD.RED) display.display() timeout = time.monotonic() + settings['alarm_timeout'] while out_of_range(temperature, humidity) and time.monotonic() < timeout: if sound_alarm(): break if check_for_push(silence_button, settings['alarm_seconds_between_alarms']): break done.value = True
Page last edited January 20, 2025
Text editor powered by tinymce.