Skip to content

Commit 7cbfbea

Browse files
committed
Add MPU6050 motion wake module
1 parent e060237 commit 7cbfbea

File tree

1 file changed

+154
-0
lines changed

1 file changed

+154
-0
lines changed

modules/mpu6050_wake.py

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
# MPU6050 Motion Wake Driver
2+
# Configures MPU6050 to trigger INT pin HIGH on motion for ESP32 wake
3+
4+
from machine import I2C, Pin
5+
import time
6+
7+
# MPU6050 I2C address
8+
MPU6050_ADDR = 0x68
9+
10+
# Registers
11+
REG_PWR_MGMT_1 = 0x6B
12+
REG_PWR_MGMT_2 = 0x6C
13+
REG_INT_PIN_CFG = 0x37
14+
REG_INT_ENABLE = 0x38
15+
REG_MOT_THR = 0x1F # Motion detection threshold
16+
REG_MOT_DUR = 0x20 # Motion detection duration
17+
REG_ACCEL_CONFIG = 0x1C
18+
REG_MOT_DETECT_CTRL = 0x69
19+
REG_WHO_AM_I = 0x75
20+
21+
22+
class MPU6050Wake:
23+
"""MPU6050 configured for motion-triggered wake."""
24+
25+
def __init__(self, sda_pin=33, scl_pin=22, int_pin=36):
26+
# Note: GPIO33 used for SDA (sacrificing Shortcut4 button)
27+
# GPIO21 is not exposed on the Everything Remote ESP32 board
28+
self.int_pin = int_pin
29+
self._i2c = None
30+
self._initialized = False
31+
self.sda_pin = sda_pin
32+
self.scl_pin = scl_pin
33+
34+
def init(self):
35+
"""Initialize MPU6050 for motion detection, then release I2C pins."""
36+
try:
37+
# Initialize I2C
38+
self._i2c = I2C(0, sda=Pin(self.sda_pin), scl=Pin(self.scl_pin), freq=400000)
39+
40+
# Check if MPU6050 is present
41+
devices = self._i2c.scan()
42+
if MPU6050_ADDR not in devices:
43+
print(f"MPU6050 not found at 0x{MPU6050_ADDR:02X}, found: {[hex(d) for d in devices]}")
44+
self._release_i2c_pins()
45+
return False
46+
47+
# Verify WHO_AM_I register (should be 0x68)
48+
who = self._read_byte(REG_WHO_AM_I)
49+
if who != 0x68:
50+
print(f"MPU6050 WHO_AM_I mismatch: got 0x{who:02X}, expected 0x68")
51+
# Some clones return different values, continue anyway
52+
53+
# Wake up MPU6050 (clear sleep bit)
54+
self._write_byte(REG_PWR_MGMT_1, 0x00)
55+
time.sleep_ms(100)
56+
57+
# Configure for low power motion detection
58+
self._configure_motion_detect()
59+
60+
self._initialized = True
61+
print("MPU6050 initialized for motion wake")
62+
63+
# Release I2C pins so they can be used for buttons
64+
self._release_i2c_pins()
65+
print("I2C pins released - Shortcut4 button available")
66+
67+
return True
68+
69+
except Exception as e:
70+
print(f"MPU6050 init error: {e}")
71+
self._release_i2c_pins()
72+
return False
73+
74+
def _release_i2c_pins(self):
75+
"""Release I2C pins so SDA pin can be used as button."""
76+
try:
77+
if self._i2c:
78+
self._i2c.deinit()
79+
self._i2c = None
80+
# Reconfigure SDA pin as input with pull-up (for button use)
81+
Pin(self.sda_pin, Pin.IN, Pin.PULL_UP)
82+
except:
83+
pass
84+
85+
def _write_byte(self, reg, value):
86+
"""Write a byte to a register."""
87+
self._i2c.writeto_mem(MPU6050_ADDR, reg, bytes([value]))
88+
89+
def _read_byte(self, reg):
90+
"""Read a byte from a register."""
91+
return self._i2c.readfrom_mem(MPU6050_ADDR, reg, 1)[0]
92+
93+
def _configure_motion_detect(self):
94+
"""Configure motion detection with INT pin HIGH on motion."""
95+
# Configure accelerometer with High-Pass Filter enabled
96+
# HPF is CRITICAL for motion detection to work
97+
# Bits [2:0] = ACCEL_HPF: 001 = 5Hz cutoff
98+
self._write_byte(REG_ACCEL_CONFIG, 0x01)
99+
100+
# Set motion detection threshold (1-255, lower = more sensitive)
101+
# Each LSB = 2mg at ±2g scale
102+
# 20 = ~40mg threshold - sensitive enough for pickup, ignores small vibrations
103+
self._write_byte(REG_MOT_THR, 20)
104+
105+
# Set motion detection duration (1-255 ms)
106+
self._write_byte(REG_MOT_DUR, 1)
107+
108+
# Configure motion detect control
109+
# 0x15 = proper decrement and delay settings for motion detection
110+
self._write_byte(REG_MOT_DETECT_CTRL, 0x15)
111+
112+
# Configure INT pin:
113+
# Bit 7: INT_LEVEL = 0 (active HIGH)
114+
# Bit 6: INT_OPEN = 0 (push-pull)
115+
# Bit 5: LATCH_INT_EN = 1 (latched - stays HIGH until status read)
116+
# Bit 4: INT_RD_CLEAR = 1 (clear INT on any read)
117+
# Latched mode is required for deep sleep wake
118+
self._write_byte(REG_INT_PIN_CFG, 0x30)
119+
120+
# Enable motion detection interrupt
121+
self._write_byte(REG_INT_ENABLE, 0x40) # MOT_EN bit
122+
123+
# Put into cycle mode for low power (~10µA vs ~3.5mA)
124+
# Note: cycle mode works for deep sleep wake but not for active polling
125+
# PWR_MGMT_1: CYCLE=1, SLEEP=0, TEMP_DIS=1
126+
self._write_byte(REG_PWR_MGMT_1, 0x28)
127+
128+
# PWR_MGMT_2: LP_WAKE_CTRL = 01 (5Hz sample rate), disable gyro
129+
self._write_byte(REG_PWR_MGMT_2, 0x47)
130+
131+
print("MPU6050 motion detection configured (threshold=20, HPF=5Hz, cycle mode)")
132+
133+
def get_int_pin(self):
134+
"""Get the interrupt pin object for wake configuration."""
135+
return Pin(self.int_pin, Pin.IN)
136+
137+
@property
138+
def is_initialized(self):
139+
return self._initialized
140+
141+
def check_motion(self):
142+
"""Check if motion was detected (reads INT status)."""
143+
if not self._initialized:
144+
return False
145+
try:
146+
# Reading INT_STATUS clears the interrupt
147+
status = self._read_byte(0x3A)
148+
return (status & 0x40) != 0 # MOT_INT bit
149+
except:
150+
return False
151+
152+
153+
# Singleton instance
154+
mpu6050 = MPU6050Wake()

0 commit comments

Comments
 (0)