A variation on the code makes it respond to footsteps…
In the video above, so we can hear it outside, the HalloWing’s speaker output has been wired into a portable amplified speaker worn on a belt…but for smaller indoor gatherings, the little oval speaker from Adabox 009 may suffice.
HalloWing can be worn on a lanyard, tucked in a pocket, or even ride along inside one’s trick-or-treat candy bucket…it still senses steps and jumps!
It’s not a commercial-grade step counter…it might miss some steps, or register false positives…quite fine though for casual Halloween shenanigans, for something that can be made and customized very quickly. Try it out!
As with the prior example, click “Download Project Bundle” to download a ZIP file with this code and the project’s graphics and sound files and required libraries. After uncompressing, navigate to the folder matching your CircuitPython version, and copy all of the files (and the “lib” folder) to the root directory of the CIRCUITPY drive (i.e. not inside a folder…just straight to the drive).
# SPDX-FileCopyrightText: 2018 Phillip Burgess for Adafruit Industries # # SPDX-License-Identifier: MIT """ Stomp & roar sound example for Adafruit Hallowing. Functions as a crude pedometer, plays different sounds in response to steps & jumps. Step detection based on "Full-Featured Pedometer Design Realized with 3-Axis Digital Accelerometer" by Neil Zhao, Analog Dialogue Technical Journal, June 2010. """ import time import math import digitalio import displayio import board import audioio import audiocore import neopixel def load_wav(name): """ Load a WAV audio file into RAM. @param name: partial file name string, complete name will be built on this, e.g. passing 'foo' will load file 'foo.wav'. @return WAV buffer that can be passed to play_wav() below. """ return audiocore.WaveFile(open(name + '.wav', 'rb')) STOMP_WAV = load_wav('stomp') # WAV file to play with each step ROAR_WAV = load_wav('roar') # WAV when jumping IMAGEFILE = 'reptar.bmp' # BMP image to display IS_HALLOWING_M4 = False # Perform a couple extra steps for the HalloWing M4 try: if getattr(board, "CAP_PIN"): IS_HALLOWING_M4 = True if getattr(board, "SPEAKER_ENABLE"): # Enable the Speaker speaker_enable = digitalio.DigitalInOut(board.SPEAKER_ENABLE) speaker_enable.direction = digitalio.Direction.OUTPUT speaker_enable.value = True except AttributeError: pass AUDIO = audioio.AudioOut(board.SPEAKER) # Speaker try: board.DISPLAY.auto_brightness = False except AttributeError: pass # Set up accelerometer on I2C bus, 4G range: i2c = board.I2C() # uses board.SCL and board.SDA # i2c = board.STEMMA_I2C() # For using the built-in STEMMA QT connector on a microcontroller if IS_HALLOWING_M4: import adafruit_msa301 ACCEL = adafruit_msa301.MSA301(i2c) else: import adafruit_lis3dh try: ACCEL = adafruit_lis3dh.LIS3DH_I2C(i2c, address=0x18) # Production board except ValueError: ACCEL = adafruit_lis3dh.LIS3DH_I2C(i2c, address=0x19) # Beta hardware ACCEL.range = adafruit_lis3dh.RANGE_4_G STEP_INTERVAL_MIN = 0.3 # Shortest interval to walk one step (seconds) STEP_INTERVAL_MAX = 2.0 # Longest interval to walk one step (seconds) SAMPLE_RATE_HZ = 50 # Accelerometer polling frequency (per second) WINDOW_INTERVAL = 1.0 # How often to reset window min/max range (seconds) PRECISION = 2.0 # Lower numbers = more sensitive to steps SAMPLE_INTERVAL = 1.0 / SAMPLE_RATE_HZ FILTER_SIZE = 4 # Number of accelerometer readings to average FILTER_BUF = [0] * FILTER_SIZE FILTER_SUM = 0 # Initial average value FILTER_INDEX = 0 # Current position in sample-averaging buffer # Display BMP image. try: board.DISPLAY.brightness = 0 SCREEN = displayio.Group() board.DISPLAY.root_group = SCREEN # CircuitPython 7+ compatible BITMAP = displayio.OnDiskBitmap(IMAGEFILE) TILEGRID = displayio.TileGrid(BITMAP, pixel_shader=BITMAP.pixel_shader) SCREEN.append(TILEGRID) board.DISPLAY.brightness = 1.0 # Turn on display backlight except (OSError, ValueError): pass # If everything has initialized correctly, turn off the onboard NeoPixel: PIXEL = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0) PIXEL.show() # Read initial accelerometer state and assign to various things to start X, Y, Z = ACCEL.acceleration MAG = math.sqrt(X * X + Y * Y + Z * Z) # 3space magnitude WINDOW_MIN = MAG # Minimum reading from accel in last WINDOW_INTERVAL seconds WINDOW_MAX = MAG # Maximum reading from accel in last WINDOW_INTERVAL seconds THRESHOLD = MAG # Midpoint of WINDOW_MIN, WINDOW_MAX SAMPLE_OLD = MAG SAMPLE_NEW = MAG LAST_STEP_TIME = time.monotonic() # Time of last step detect LAST_WINDOW_TIME = LAST_STEP_TIME # Time of last min/max window reset while True: TIME = time.monotonic() # Time at start of loop X, Y, Z = ACCEL.acceleration # Read accelerometer MAG = math.sqrt(X * X + Y * Y + Z * Z) # Calc 3space magnitude # Low-pass filter: average the last FILTER_SIZE magnitude readings FILTER_SUM -= FILTER_BUF[FILTER_INDEX] # Subtract old value from sum FILTER_BUF[FILTER_INDEX] = MAG # Store new value in buffer FILTER_SUM += MAG # Add new value to sum FILTER_INDEX += 1 # Increment position in buffer if FILTER_INDEX >= FILTER_SIZE: # and wrap around to start FILTER_INDEX = 0 SAMPLE_RESULT = FILTER_SUM / FILTER_SIZE # Average buffer value if SAMPLE_RESULT < 2: # Jump detected (freefall, or close to it) while MAG < 10 and time.monotonic() - TIME < 1: # Wait for landing X, Y, Z = ACCEL.acceleration MAG = math.sqrt(X * X + Y * Y + Z * Z) AUDIO.play(ROAR_WAV) while AUDIO.playing: pass continue # Back to top of loop # Every WINDOW_INTERVAL seconds, calc new THRESHOLD, reset min and max if TIME - LAST_WINDOW_TIME >= WINDOW_INTERVAL: # Time for new window? THRESHOLD = (WINDOW_MIN + WINDOW_MAX) / 2 # Average of min & max WINDOW_MIN = SAMPLE_RESULT # Reset min and max to WINDOW_MAX = SAMPLE_RESULT # the last value read LAST_WINDOW_TIME = TIME # Note time of reset else: # Not WINDOW_INTERVAL yet, if SAMPLE_RESULT < WINDOW_MIN: # keep adjusting min and WINDOW_MIN = SAMPLE_RESULT # max to accel data. if SAMPLE_RESULT > WINDOW_MAX: WINDOW_MAX = SAMPLE_RESULT # Watch for sufficiently large changes in accelerometer readings... SAMPLE_OLD = SAMPLE_NEW if abs(SAMPLE_RESULT - SAMPLE_OLD) > PRECISION: SAMPLE_NEW = SAMPLE_RESULT # If crossing the threshold in the + direction... if SAMPLE_OLD <= THRESHOLD <= SAMPLE_NEW: # And if within reasonable time window for another step... TIME_SINCE_LAST_STEP = TIME - LAST_STEP_TIME if STEP_INTERVAL_MIN <= TIME_SINCE_LAST_STEP <= STEP_INTERVAL_MAX: # It's a step! AUDIO.play(STOMP_WAV) LAST_STEP_TIME = TIME # Dillydally so the accelerometer isn't polled faster than desired rate ELAPSED = time.monotonic() - TIME if ELAPSED < SAMPLE_INTERVAL: time.sleep(SAMPLE_INTERVAL - ELAPSED)
Sound and graphics are easily customized, starting around line 29. 16-bit mono PCM WAV files (22,050 sample rate or less) are ideal. Graphics should be a 128x128 pixel 24-bit color BMP:
STOMP_WAV = load_wav('stomp') # WAV file to play with each step ROAR_WAV = load_wav('roar') # WAV when jumping IMAGEFILE = 'reptar.bmp' # BMP image to display
The WAV files don’t require a complete filename; the “.wav” is implied. (e.g. 'stomp' will load the file stomp.wav). The BMP needs a whole filename.
If you find it too sensitive to steps, or not sensitive enough, change the value of PRECISION around line 48:
PRECISION = 2.0 # Lower numbers = more sensitive to steps
Other ways to improve results include more secure mounting of the HalloWing board. Worn on a lanyard, HalloWing bobs around a bit and may register false steps. Buttoned in a pocket or attached to a hat with an elastic band is usually more reliable. The nice thing here is that the board doesn’t need to be in any particular orientation, it will still detect movements, so use whatever is easiest or most comfortable!
Advanced users could get into fine-tuning the step-detection algorithm, or using only one or two accelerometer axes (requiring the board in a particular orientation for use).
Text editor powered by tinymce.