Text Editor

Adafruit recommends using the Mu editor for editing your CircuitPython code. You can get more info in this guide.

Alternatively, you can use any text editor that saves files.

Installing Project Code

To use with CircuitPython, you need to first install a few libraries, into the lib folder on your CIRCUITPY drive. Then you need to update code.py with the example script.

Thankfully, we can do this in one go. In the example below, click the Download Project Bundle button below to download the necessary libraries and the code.py file in a zip file. Extract the contents of the zip file, open the directory CLUE_Egg_Drop/ and then click on the directory that matches the version of CircuitPython you're using and copy the contents of that directory to your CIRCUITPY drive.

Your CIRCUITPY drive should now look similar to the following image:

CIRCUITPY
# SPDX-FileCopyrightText: 2020 John Park for Adafruit Industries
#
# SPDX-License-Identifier: MIT

import time
import math
import board
from digitalio import DigitalInOut, Direction, Pull
import pwmio
from adafruit_lsm6ds.lsm6ds33 import LSM6DS33
from adafruit_lsm6ds import AccelRange, AccelHPF, Rate
from adafruit_display_text import label
import displayio
import terminalio

button_a = DigitalInOut(board.BUTTON_A)
button_a.direction = Direction.INPUT
button_a.pull = Pull.UP

splash = displayio.Group()

# bad egg
BAD_EGG_FILENAME = "broken_egg.bmp"

# CircuitPython 6 & 7 compatible
begg_file = open(BAD_EGG_FILENAME, "rb")
begg_bmp = displayio.OnDiskBitmap(begg_file)
begg_sprite = displayio.TileGrid(
    begg_bmp,
    pixel_shader=getattr(begg_bmp, 'pixel_shader', displayio.ColorConverter())
)

# # CircuitPython 7+ compatible
# begg_bmp = displayio.OnDiskBitmap(BAD_EGG_FILENAME)
# begg_sprite = displayio.TileGrid(begg_bmp, pixel_shader=begg_bmp.pixel_shader)

# good egg
GOOD_EGG_FILENAME = "good_egg.bmp"

# CircuitPython 6 & 7 compatible
gegg_file = open(GOOD_EGG_FILENAME, "rb")
gegg_bmp = displayio.OnDiskBitmap(gegg_file)
gegg_sprite = displayio.TileGrid(
    gegg_bmp,
    pixel_shader=getattr(gegg_bmp, 'pixel_shader', displayio.ColorConverter())
)

# # CircuitPython 7+ compatible
# gegg_bmp = displayio.OnDiskBitmap(GOOD_EGG_FILENAME)
# gegg_sprite = displayio.TileGrid(gegg_bmp, pixel_shader=gegg_bmp.pixel_shader)

# draw the bad egg!
splash.append(begg_sprite)
# draw the good egg on top
splash.append(gegg_sprite)

# Draw a label
text_group = displayio.Group(scale=2, x=10, y=220)
text = "Current & Max Acceleration"
text_area = label.Label(terminalio.FONT, text=text, color=0x0000FF)
text_group.append(text_area) # Subgroup for text scaling
splash.append(text_group)

# display everything so far
board.DISPLAY.root_group = splash

# connect to the accelerometer
i2c = board.I2C()  # uses board.SCL and board.SDA
# i2c = board.STEMMA_I2C()  # For using the built-in STEMMA QT connector on a microcontroller
sensor = LSM6DS33(i2c)
# highest range for impacts!
sensor.accelerometer_range = AccelRange.RANGE_16G
# we'll read at about 1KHz
sensor.accelerometer_rate = Rate.RATE_1_66K_HZ
# Instead of raw accelerometer data, we'll read the *change* in acceleration (shock)
sensor.high_pass_filter = AccelHPF.SLOPE
sensor.high_pass_filter_enabled = True

# and make a lil buzzer
buzzer = pwmio.PWMOut(board.SPEAKER, variable_frequency=True)
buzzer.frequency = 1000

max_slope = 0
egg_ok = True
while True:
    # This isn't the acceleration but the SLOPE (change!) in acceleration
    x, y, z = sensor.acceleration
    # take the vector length by squaring, adding, taking root
    slope_g = math.sqrt(x*x + y*y + z*z) / 9.8  # take vector, convert to g
    # we'll track the max delta g

    if max_slope < slope_g:
        max_slope = slope_g
        print(slope_g)
        text_area.text = "Max Slope %0.1fg" % max_slope
        if max_slope >= 9 and egg_ok:
            gegg_sprite.x = 300
            time.sleep(0.1)
            egg_ok = False
            buzzer.duty_cycle = 2**15
            time.sleep(2)
            buzzer.duty_cycle = 0
            continue

    if button_a.value is False and egg_ok is False:
        print("Reset")
        time.sleep(0.1)  # debounce
        max_slope = 0
        gegg_sprite.x = 0
        egg_ok = True

How It Works

Library Import

First, the code imports libraries: time, math, board, digitalio (in this case for using the A button), pulseio for sounding the buzzer, the accelerometer adafruit_lsm6 library, and, so we can display text and graphics on screen, adafruit_display_text, displayio and terminalio.

import time
import math
import board
from digitalio import DigitalInOut, Direction, Pull
import pulseio
from adafruit_lsm6ds import LSM6DS33, AccelRange, AccelHPF, Rate
from adafruit_display_text import label
import displayio
import terminalio

Button Setup

Next we'll set up the button as an input with pull up resistor.

button_a = DigitalInOut(board.BUTTON_A)
button_a.direction = Direction.INPUT
button_a.pull = Pull.UP

Display

To prepare the screen for bitmap display, we'll create a displayio.Group() called splash, and then create tiles for the two egg .bmp image files saved on the CLUE.

splash = displayio.Group()

# bad egg
BAD_EGG_FILENAME = "broken_egg.bmp"

# CircuitPython 6 & 7 compatible
begg_file = open(BAD_EGG_FILENAME, "rb")
begg_bmp = displayio.OnDiskBitmap(begg_file)
begg_sprite = displayio.TileGrid(
    begg_bmp,
    pixel_shader=getattr(begg_bmp, 'pixel_shader', displayio.ColorConverter())
)

# # CircuitPython 7+ compatible
# begg_bmp = displayio.OnDiskBitmap(BAD_EGG_FILENAME)
# begg_sprite = displayio.TileGrid(begg_bmp, pixel_shader=begg_bmp.pixel_shader)

# good egg
GOOD_EGG_FILENAME = "good_egg.bmp"

# CircuitPython 6 & 7 compatible
gegg_file = open(GOOD_EGG_FILENAME, "rb")
gegg_bmp = displayio.OnDiskBitmap(gegg_file)
gegg_sprite = displayio.TileGrid(
    gegg_bmp,
    pixel_shader=getattr(gegg_bmp, 'pixel_shader', displayio.ColorConverter())
)

# # CircuitPython 7+ compatible
# gegg_bmp = displayio.OnDiskBitmap(GOOD_EGG_FILENAME)
# gegg_sprite = displayio.TileGrid(gegg_bmp, pixel_shader=gegg_bmp.pixel_shader)

# draw the bad egg!
splash.append(begg_sprite)
# draw the good egg on top
splash.append(gegg_sprite)

Label

We'll also prepare the text display by creating a text group with the text "Current & Max Acceleration".

All of these elements will be drawn to the screen with the command board.DISPLAY.root_group = splash

# Draw a label
text_group = displayio.Group(scale=2, x=10, y=220)
text = "Current & Max Acceleration"
text_area = label.Label(terminalio.FONT, text=text, color=0x0000FF)
text_group.append(text_area) # Subgroup for text scaling
splash.append(text_group)

# display everything so far
board.DISPLAY.root_group = splash

Accelerometer & Buzzer

The accelerometer object will be instantiated as sensor connected over the I2C bus to the LSM6DS33 package. The range is set to 16G so it can read large impacts (poor egg!) read at a rate of 1.66KHz.

To filter out noise, the accelerometer's high pass filter is enabled.

We'll also set up the buzzer using pulseio with a frequency of 1000Hz.

# connect to the accelerometer
sensor = LSM6DS33(board.I2C())
# highest range for impacts!
sensor.accelerometer_range = AccelRange.RANGE_16G
# we'll read at about 1KHz
sensor.accelerometer_rate = Rate.RATE_1_66K_HZ
# Instead of raw accelerometer data, we'll read the *change* in acceleration (shock)
sensor.high_pass_filter = AccelHPF.SLOPE
sensor.high_pass_filter_enabled = True

# and make a lil buzzer
buzzer = pulseio.PWMOut(board.SPEAKER, variable_frequency=True)
buzzer.frequency = 1000

The max_slope variable is created with an initial value of 0 -- this is used to measure the slope change in acceleration.

The egg_ok variable is set to True and will be used to reset the board after a large impact beyond the maximum slope value of 9.

max_slope = 0
egg_ok = True

Main Loop

In the main loop of the program the accelerometer is read, and a slope_g is determined by squaring, adding and taking the root of the accelerometer value.

When the egg drop CLUE is in freefall, we'll begin checking the max_slope value acceleration -- if it is greater than 9, the simulated egg has probably cracked!

In this case, we move the good egg sprite off screen by 300 pixels, revealing the bad egg image. The buzzer will also sound for two seconds.

# This isn't the acceleration but the SLOPE (change!) in acceleration
    x, y, z = sensor.acceleration
    # take the vector length by squaring, adding, taking root
    slope_g = math.sqrt(x*x + y*y + z*z) / 9.8  # take vector, convert to g
    # we'll track the max delta g

    if max_slope < slope_g:
        max_slope = slope_g
        print(slope_g)
        text_area.text = "Max Slope %0.1fg" % max_slope
        if max_slope >= 9 and egg_ok:
            gegg_sprite.x = 300
            time.sleep(0.1)
            egg_ok = False
            buzzer.duty_cycle = 2**15
            time.sleep(2)
            buzzer.duty_cycle = 0
            continue

Button Reset

Now, we can check the button_a.value to see if it changes after the egg_ok variable has been set to False. Since the button is on a pull up resistor, it is normally True until pressed. When pressed, the button value will go to False and we can then reset the max_slope value, and return the good egg image, and reset the egg_ok to True!

if button_a.value is False and egg_ok is False:
        print("Reset")
        time.sleep(0.1)  # debounce
        max_slope = 0
        gegg_sprite.x = 0
        egg_ok = True

At this point, we're ready for another try!

Next, we'll build a landing vehicle and take it for a spin (drop?)!

This guide was first published on Mar 11, 2020. It was last updated on Mar 28, 2024.

This page (Code the Egg Drop CLUE with CircuitPython) was last updated on Mar 27, 2024.

Text editor powered by tinymce.