A high-performance Zwift steering device using the M5StickCPlus2 with advanced IMU/AHRS implementation, power management, and BLE connectivity.
Want your own M5StickC Plus2? Get it here: M5StickC Plus2 on AliExpress (affiliate link - supports further development)
This project transforms your M5StickCPlus2 into a professional-grade steering controller for Zwift cycling simulation. Based on the excellent work from @stefaandesmet2003/ESP32Sterzo, this implementation adds significant enhancements including:
- Mahony AHRS algorithm for stable yaw calculation
- Advanced power management with adaptive CPU/IMU frequency scaling
- Motion-based sleep system with 2-minute low power and 5-minute BLE wait timeouts
- Comprehensive calibration system with visual feedback
- 1-degree steering precision (vs 10-degree binning)
- Drift compensation and automatic centering
- Professional button handling with short/long press actions
- ✅ Zwift BLE Integration - Full compatibility with Zwift steering protocol
- ✅ Mahony AHRS Filter - Superior stability compared to basic IMU readings
- ✅ 1° Steering Precision - Fine-grained control for competitive cycling
- ✅ Automatic Calibration - Smart startup calibration with manual override
- ✅ Drift Compensation - Gradual centering force prevents long-term drift
- ⚡ Adaptive IMU Frequency - 25Hz for optimal performance and power balance
- ⚡ CPU Frequency Scaling - 80MHz for BLE compatibility and power efficiency
- ⚡ Motion-Based Sleep - 5 minutes BLE wait, 2 minutes low power before ULP sleep
- ⚡ ULP Coprocessor Sleep - Ultra-low power sleep with button wake capability
- ⚡ Smart Screen Control - Auto off after 60s, motion detection keeps active
- 📱 Real-time Display - Yaw angle, steering bin, BLE status, power mode
- 📱 Visual Limit Indicators - Flashing arrows at ±40° steering limits
- 📱 Battery Monitoring - Voltage display with 0.01V precision
- 📱 Calibration Progress - Visual progress bars and audio feedback
- 📱 Power Status - Shows current power mode and sleep countdowns
- 🔘 Button A (Front) Short (<1s) - Wake screen / reset screen timer
- 🔘 Button A (Front) Long (≥1s) - Quick yaw recenter with audio feedback
- 🔘 Button B (Top) Short (<2s) - Quick yaw recenter with audio feedback
- 🔘 Button B (Top) Long (≥2s) - Full calibration sequence
- 🔘 Button C (Power) Short (<1s) - Wake screen / reset screen timer
- 🔘 Button C (Power) Long (≥2s) - Power off / enter deep sleep mode
- M5StickCPlus2 - Main device with built-in IMU (MPU6886)
- USB-C Cable - For programming and charging
- Battery - Built-in 200mAh LiPo battery
The M5StickC Plus2 is a compact development board perfect for IoT projects. It features a built-in MPU6886 6-axis IMU, 1.14" color LCD display, and 200mAh battery - everything needed for this Zwift steering controller.
- PlatformIO installed
- Git for cloning
# Clone the repository
git clone https://github.com/Felixrising/STERZStick.git
cd STERZStick
# Build and upload
pio run --target upload
# Monitor serial output (optional)
pio device monitorAll dependencies are automatically managed by PlatformIO:
M5Unified- M5StickCPlus2 hardware abstractionESP32 BLE Arduino- Bluetooth Low Energy supportPreferences- Non-volatile storageMadgwick- AHRS algorithm library (unused, kept for compatibility)
- Power On - Device performs automatic calibration on first boot
- Zwift Pairing - Open Zwift → Settings → Connections → Search for "STERZO"
- Calibration - Hold Button A or B for 2-3 seconds for manual calibration if needed
- Mount Device - Attach to handlebars or hold in hand
- Connect to Zwift - Device advertises as "STERZO"
- Steering Range - ±40° physical rotation = full Zwift steering
- Auto-Centering - Device gradually returns to center when not actively steering
- BLE Active Mode - Full performance during Zwift sessions (80MHz CPU, 25Hz IMU)
- BLE Waiting Mode - Waiting for connection (80MHz CPU, reduced activity, 5min timeout)
- Low Power Mode - No BLE active (80MHz CPU, IMU/button monitoring, 2min timeout)
- ULP Sleep Mode - Ultra-low power with button wake capability
- Wake Up - Press any button or device will auto-wake when needed
- Sensor - MPU6886 6-axis IMU (gyroscope + accelerometer)
- Algorithm - Mahony AHRS for quaternion-based orientation
- Sampling Rate - 25Hz for optimal performance and power balance
- Calibration - Advanced gyro bias compensation and accelerometer calibration
- Service UUID -
347b0001-7635-408b-8918-8ff3949ce592 - Steering Data - Float32 angle in degrees (-40° to +40°)
- Update Rate - On-change notification (1° resolution)
- Zwift Handshake - Full protocol compatibility
- BLE Active Current - ~80-120mA (80MHz CPU, 25Hz IMU, BLE active)
- BLE Waiting Current - ~40-60mA (80MHz CPU, 25Hz IMU, BLE advertising)
- Low Power Current - ~20-30mA (80MHz CPU, 25Hz IMU, no BLE)
- ULP Sleep Current - ~5-10μA (ULP coprocessor monitoring buttons)
- Battery Life - 3-5 hours active use, weeks/months standby
- Gyro Bias - 3000 samples over 3 seconds (device stationary)
- Accelerometer - 6-point calibration with audio/visual guidance
- Yaw Centering - Sets current orientation as center position
- Quick Recenter - Long press Button A (≥1s) or short press Button B (<2s)
- Full Calibration - Long press Button B (≥2s)
- Progress Feedback - Visual progress bars and audio beeps
Device not connecting to Zwift:
- Ensure Bluetooth is enabled on your device
- Restart Zwift and search for "STERZO" in connections
- Try power cycling the M5StickCPlus2
Steering feels unstable:
- Perform full calibration (long press Button B ≥2s)
- Perform quick recenter (long press Button A ≥1s or short press Button B <2s)
- Ensure device is mounted securely
- Check battery level (low battery affects performance)
Device goes to sleep too quickly:
- Motion detection requires actual movement/rotation
- BLE connection prevents automatic sleep
- Screen stays on for 60s after button press, indefinitely when connected to Zwift
- Device enters ULP sleep after 5min BLE wait + 2min low power (when disconnected)
Yaw drift over time:
- Built-in drift compensation should handle minor drift
- Perform quick recenter (long press Button A ≥1s or short press Button B <2s)
- Full recalibration may be needed for major drift (long press Button B ≥2s)
Enable serial monitoring to see detailed debug output:
pio device monitorDebug output includes:
- IMU readings and frequencies
- Power management status
- BLE connection events
- Calibration progress
- Button press detection
- Use during Zwift sessions only - Device sleeps when not connected
- Regular charging - Don't let battery fully discharge
- Stable mounting - Reduces unnecessary motion detection
- Firmware updates - Keep firmware updated for latest power optimizations
Contributions are welcome! Please feel free to submit issues, feature requests, or pull requests.
# Clone with development dependencies
git clone https://github.com/Felixrising/STERZStick.git
cd STERZStick
# Build in debug mode
pio run -e debug
# Run tests (if available)
pio testThis project is licensed under the MIT License - see the LICENSE file for details.
- @stefaandesmet2003 - Original ESP32Sterzo implementation
- M5Stack - M5StickCPlus2 hardware and libraries
- Zwift - Steering protocol documentation and support
- Mahony & Madgwick - AHRS algorithm implementations
- ✅ Core Functionality - Complete and tested
- ✅ Power Management - Optimized for battery life
- ✅ Zwift Integration - Fully compatible
- ✅ Calibration System - Comprehensive and user-friendly
- 🔄 Documentation - Ongoing improvements
- 🔄 Testing - Continuous validation with Zwift updates
Critical Bug Fixes & IMU Stability Release
CRITICAL FIXES:
-
🔴 FIXED: Power Management Bug - BLE no longer incorrectly stops after 5 minutes during active use
- Device now properly tracks user activity (motion, button presses)
- BLE timeout resets when user is active
- Safety checks prevent LOW_POWER mode during recent activity
- Resolves heading display freeze, unresponsive steering, ESP_ERR_INVALID_STATE errors
-
🔴 FIXED: IMU Timing & Drift - Stable heading tracking restored
- Hybrid delay approach: vTaskDelay + delayMicroseconds for timing precision
- Mahony filter now uses actual measured frequency (not target constant)
- Drift reduced from 3-5°/min (RC2) to <0.5°/min
- Smooth centering force behavior, no more wild swings
-
🎯 IMPROVED: Smart Display Updates
- 10 FPS when Zwift connected OR yaw changes OR motion detected
- 5 FPS when BLE connected but idle
- 1 FPS only when truly idle (no connection, no changes)
- Responsive steering feel during active use
Impact:
- ✅ Heading display remains responsive after screen wake
- ✅ BLE stays active indefinitely during use
- ✅ Accurate, stable yaw tracking (minimal drift)
- ✅ Power efficiency from RC2 maintained
⚠️ UPGRADE STRONGLY RECOMMENDED for all RC2 users
See: RELEASE_NOTES_v1.0-RC3.md for complete technical details
Major Power Optimization & Performance Release
Power Optimizations:
- 🔋 Reduced LOW_POWER_CPU_FREQ from 80MHz to 10MHz (significant power savings)
- 🔋 LED breathing rate-limited to 20Hz (50ms intervals) for power savings
- 🔋 Adaptive display refresh: 10 FPS active, 5 FPS idle BLE, 1 FPS disconnected
- 🔋 Battery ADC reads now cached for 1 second instead of every frame
- 🔋 Debug logging conditionally compiled with
#ifdef DEBUG_MODE - 🔋 Replaced
delayMicroseconds()busy-wait withvTaskDelay()for power-efficient sleep
Performance Improvements:
- ⚡ BLE controller state checking prevents redundant init/enable calls
- ⚡ Splash overlay uses char arrays instead of String objects (stack vs heap allocation)
- ⚡ Smart display refresh only when content actually changes
- ⚡ Button press duration constants now defined for maintainability
Bug Fixes:
- 🐛 Fixed BLE controller conflicts when switching between power modes
- 🐛 Corrected splash screen string handling for better memory efficiency
- 🐛 Improved motion detection threshold accuracy
Expected Impact:
- 30-50% longer battery life in low power modes
- Smoother display updates during active use
- More reliable BLE connection management
- Reduced memory fragmentation
See: RELEASE_NOTES_v1.0-RC2.md
- Added LittleFS support for splash screen images
- Enhanced splash screen system with logo display
- Improved startup sequence
- Repository renamed from ZTEERStick to STERZStick
Built with ❤️ for the Zwift cycling community
