Motion Alarm
Works with
Any CircuitPython board with I2C
Attach this to a box, a door, a backpack, or anything you want to protect. The LIS3DH accelerometer detects taps and shakes at the hardware level — no polling, no missed events. When something moves, NeoPixels flash and a piezo buzzer fires. A small project with a lot of practical uses.
What you'll build
A motion-triggered alarm unit. The LIS3DH is configured to detect single or double taps using its built-in interrupt hardware. When the tap interrupt fires, CircuitPython triggers a NeoPixel flash sequence and a buzzer tone. The alarm resets automatically, ready for the next event.
What you'll need
| Part | Notes |
|---|---|
| CircuitPython board with I2C | Feather, ItsyBitsy, Pico, Metro, Trinket M0 |
| Adafruit LIS3DH accelerometer breakout | Product page |
| NeoPixel strip or ring | 8 pixels is plenty |
| Piezo buzzer | Passive (not active) — you need PWM control over frequency |
| Hookup wire | |
| USB cable or battery | Battery makes it portable |
Wiring
The LIS3DH connects over I2C. The buzzer connects to any PWM-capable digital pin. NeoPixels connect to a separate digital output.
graph LR
subgraph Board
B_3V3["3.3V"]
B_GND["GND"]
B_SDA["SDA"]
B_SCL["SCL"]
B_D5["D5 (NeoPixel data)"]
B_D9["D9 (PWM — buzzer)"]
end
subgraph LIS3DH["LIS3DH Breakout"]
L_VIN["VIN"]
L_GND["GND"]
L_SDA["SDA"]
L_SCL["SCL"]
L_INT["INT (optional)"]
end
subgraph NeoPixels["NeoPixel Strip"]
N_PWR["3.3V"]
N_GND["GND"]
N_DIN["DIN"]
end
subgraph Buzzer["Piezo Buzzer"]
BZ_POS["+"]
BZ_NEG["-"]
end
B_3V3 --> L_VIN
B_GND --> L_GND
B_SDA --- L_SDA
B_SCL --- L_SCL
B_3V3 --> N_PWR
B_GND --> N_GND
B_D5 --> N_DIN
B_D9 --> BZ_POS
B_GND --> BZ_NEG
Note
The INT pin on the LIS3DH is optional here. This project polls the tap flag in software. If you want a true hardware interrupt that wakes a sleeping microcontroller, connect INT to any interrupt-capable pin and use countio or alarm.
The code
import time
import board
import busio
import neopixel
import pwmio
import adafruit_lis3dh
# I2C and accelerometer
i2c = busio.I2C(board.SCL, board.SDA)
lis3dh = adafruit_lis3dh.LIS3DH_I2C(i2c)
# Configure tap detection
# Arguments: tap type (1=single, 2=double), threshold (0-127)
lis3dh.set_tap(1, 80)
# NeoPixels
pixels = neopixel.NeoPixel(board.D5, 8, brightness=0.5, auto_write=False)
# PWM buzzer
buzzer = pwmio.PWMOut(board.D9, variable_frequency=True)
buzzer.duty_cycle = 0
def sound_alarm():
"""Flash NeoPixels and sound buzzer for alarm effect."""
for _ in range(4):
# Flash red
pixels.fill((255, 0, 0))
pixels.show()
# Buzzer on at 880 Hz
buzzer.frequency = 880
buzzer.duty_cycle = 32768 # 50% duty cycle
time.sleep(0.1)
# Flash off
pixels.fill((0, 0, 0))
pixels.show()
# Buzzer off
buzzer.duty_cycle = 0
time.sleep(0.1)
def standby():
"""Dim green pulse to show system is armed and waiting."""
pixels.fill((0, 20, 0))
pixels.show()
standby()
print("Alarm armed. Waiting for tap...")
while True:
if lis3dh.tapped:
print("Tap detected!")
sound_alarm()
standby()
time.sleep(0.01)
How it works
Accelerometers measure acceleration by tracking the movement of a tiny suspended mass inside the chip. In normal operation sitting on a table, the sensor reads approximately 1g on whichever axis points down — that is gravity. When the device is tapped or shaken, additional acceleration spikes appear on top of that baseline. The LIS3DH is a 3-axis sensor, so it can detect motion in any direction simultaneously and reports values in units of g-force.
Tap detection uses a hardware threshold built into the LIS3DH itself, which is what makes it efficient. You configure a threshold value and a time window, and the chip's internal logic watches the accelerometer data continuously. When a spike crosses the threshold within the time window, the chip sets an internal tap flag. Your code only needs to read that flag — it does not need to sample and analyze raw acceleration data on every loop iteration. This is important for battery-powered projects where you might want to sleep the microcontroller and only wake it on a tap event.
Combining two outputs — lights and sound — makes the alarm more effective and also demonstrates a common pattern in physical computing: the same event drives multiple independent subsystems. The NeoPixels and the buzzer are updated in sequence inside sound_alarm(). Because the flashes and tones are short (100 ms each), the whole alarm cycle finishes quickly and the system returns to monitoring. If you wanted the alarm to persist until a button press, you would wrap the loop in a flag variable and only clear it on user input.
Installing the libraries
Download the CircuitPython Library Bundle that matches your CircuitPython version. Copy these to the lib/ folder on your CIRCUITPY drive:
adafruit_lis3dh.mpyneopixel.mpyadafruit_bus_device/(entire folder)
Remix it
Remix idea
Add a BLE-capable board and send a notification to your phone when the tap fires. Walk away from the protected object and get an alert if it moves. See BLE Keyboard for the BLE output pattern — the same approach works for sending notification strings.
Remix idea
Log every tap event with a timestamp to Adafruit IO. Leave it running for a day and chart when and how often something was disturbed. See Adafruit IO Basics for the WiFi data-logging setup.
Remix idea
Sew the LIS3DH and NeoPixels onto a jacket. The alarm becomes a reactive wearable that flashes when you move. Swap the buzzer for a small vibration motor to keep it silent. See Reactive Wearable for the wearable build pattern.
Go deeper
- LIS3DH sensor reference
- Adafruit LIS3DH breakout guide — Credit: Adafruit Learning System