You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Restructure project: extract PID controller, migrate to git submodules
- Replace vendored driver copies with git submodules (AS5600, BNO085, DShot)
- Extract PID controller from main.py into pid.py (reusable class with anti-windup)
- Add README, CLAUDE.md updates, and architecture decision records (ADR-001..003)
- Remove old examples and resources (now live in respective submodule repos)
- Update display driver with state-based drawing API
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: CLAUDE.md
+42-25Lines changed: 42 additions & 25 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -14,14 +14,16 @@ Test bench for learning flight control systems, built around a Raspberry Pi Pico
14
14
15
15
## Development Approach
16
16
17
-
**Current milestone:**Characterize BNO085 sensor performance by measuring angle error and time lag against the AS5600 reference. This data is critical because a flight controller must compensate for sensor lag—controlling based on "where you are now" with a delayed sensor leads to instability and crashes. The control system needs to predict where it *will be* and adjust thrust accordingly.
17
+
**Completed:**M1 — Single-axis PI(D) controller with AS5600 encoder, validated on hardware. Lever holds at 0° within ±3°. See `decision/ADR-001-pid-lever-stabilization.md`.
18
18
19
-
**Future milestones:**
20
-
- Telemetry logging from both sensors (encoder + IMU)
21
-
- Log lag and angle difference over time for troubleshooting and debugging
22
-
- Implement control loops with lag compensation
23
-
- Implement bidirectional DShot for ESC telemetry (RPM, voltage, temperature)
24
-
- Analyze motor response lag (time between throttle command and RPM reaching target)
19
+
**Current focus:** M2 — Switch PID input from AS5600 to BNO085 IMU. The IMU will be the primary and only control input (as on a real drone). AS5600 becomes telemetry-only ground truth for measuring IMU lag and angle error.
20
+
21
+
**Roadmap (see README.md for full details):**
22
+
- M2: BNO085 as primary control input (depends on driver work)
23
+
- M2a: Telemetry logging via Adalogger PiCowbell — RTC timestamps + SD card black box (see `decision/ADR-002-telemetry-logging.md`)
24
+
- M3: Mixer abstraction (pure refactor)
25
+
- M4: Cascaded PID — angle loop + rate loop using raw gyro (depends on M2, M2a, M3)
26
+
- M5: Multi-axis control (depends on hardware evolution)
25
27
26
28
## Hardware Components
27
29
@@ -30,43 +32,56 @@ Test bench for learning flight control systems, built around a Raspberry Pi Pico
30
32
-**AS5600 Magnetic Encoder** - 12-bit absolute position encoder (reference sensor)
31
33
-**Pimoroni Pico Display Pack** - 240x135 LCD for real-time data visualization
32
34
-**2x Drone Motors + ESCs** - DShot protocol control via PIO
35
+
-**Adafruit PiCowbell Adalogger** - PCF8523 RTC + MicroSD for telemetry logging (planned, see ADR-002)
33
36
-**Power Distribution Board** - Motor power supply
34
37
35
38
## Project Structure
36
39
37
40
```
38
41
├── main.py # Entry point - upload to Pico, runs on boot
**Running examples:** Temporarily copy an example script to Pico as `main.py`, or run directly via Thonny/mpremote.
64
+
All driver files are uploaded flat to Pico root so MicroPython resolves imports without `sys.path` manipulation.
49
65
50
66
## Architecture
51
67
52
-
### Sensor Drivers (`adafruit/`)
68
+
### AS5600 Encoder (`AS5600/driver/as5600.py`)
53
69
54
-
-`adafruit/encoder/as5600.py` - AS5600 driver. Key function: `to_degrees(raw_angle, axis_center)` converts raw 12-bit readings to degrees relative to a calibrated center position.
55
-
-`adafruit/imu/bradcar/bno08x.py` - BNO08x driver adapted from Adafruit. Uses interrupt-driven sensor updates with precise timestamp tracking.
56
-
-`adafruit/imu/bradcar/i2c.py` - I2C transport layer for BNO08x.
70
+
Magnetic rotary encoder driver. Key function: `to_degrees(raw_angle, axis_center)` converts raw 12-bit readings to degrees relative to a calibrated center position. Includes low-latency filter configuration and diagnostic telemetry.
57
71
58
-
### Display (`pimoroni/display/`)
72
+
### BNO085 IMU (`BNO085/driver/`)
59
73
60
-
-`display_pack.py` - Display abstraction using PicoGraphics library.
74
+
-`bno08x.py` - BNO08x driver with SHTP protocol, interrupt-driven sensor updates, quaternion/euler output, and precise timestamp tracking.
75
+
-`i2c.py` - I2C transport layer for BNO08x. Handles non-standard clock stretching and fragment reassembly.
61
76
62
-
### Motor Control (`dshot/`)
77
+
### Display (`pimoroni/display/`)
63
78
64
-
-`dshot/jrddupont/dshot_pio.py` - DShot protocol via RP2040 PIO. Supports DSHOT150/300/600/1200.
65
-
- See `resources/dshot_protocol.md` for full protocol specification (packet structure, timing, special commands, bidirectional DShot, telemetry).
79
+
-`display_pack.py` - Display abstraction using PicoGraphics library.
66
80
67
-
### Examples (`examples/`)
81
+
### Motor Control (`DShot/driver/`)
68
82
69
-
Small standalone scripts for testing individual sensors, calibration routines, and experiments. Not part of the main application.
83
+
-`dshot_pio.py` - Low-level DShot protocol via RP2040/RP2350 PIO. Supports DSHOT150/300/600/1200.
84
+
-`motor_throttle_group.py` - Dual-core facade for managing multiple motors. Core 1 runs a dedicated 1kHz command loop for reliable ESC communication. Provides arming, throttle control, emergency stop, and health monitoring.
70
85
71
86
## Key Constants
72
87
@@ -82,6 +97,8 @@ Small standalone scripts for testing individual sensors, calibration routines, a
82
97
83
98
- BNO085 reset: Pin 2
84
99
- BNO085 interrupt: Pin 3
85
-
- Motor 1 (DShot): Pin 4
86
-
- Motor 2 (DShot): Pin 5
100
+
- Motor 1 (DShot): Pin 4 (will move to Pin 6 when Adalogger is integrated — see ADR-002)
101
+
- Motor 2 (DShot): Pin 5 (will move to Pin 7 when Adalogger is integrated — see ADR-002)
Test bench for learning flight control systems, built around a Raspberry Pi Pico 2. Experiment with sensor fusion, control loops, and motor control in a controlled single-axis environment before applying concepts to real drones.
4
+
5
+
## Hardware
6
+
7
+
-**Raspberry Pi Pico 2** — Main microcontroller (RP2350, dual-core)
8
+
-**AS5600 Magnetic Encoder** — 12-bit absolute position at pivot (ground truth reference, ~0.088° resolution)
9
+
-**BNO085 IMU** — 9-axis IMU with onboard sensor fusion (future primary control input)
10
+
-**2x Drone Motors + ESCs** — DShot600 protocol via PIO, mounted on opposite ends of lever
11
+
-**Pimoroni Pico Display Pack** — 240x135 LCD for real-time status
12
+
-**Adafruit PiCowbell Adalogger** — PCF8523 RTC + MicroSD for black box telemetry (planned)
13
+
-**Power Distribution Board** — Motor power supply
14
+
15
+
## Mechanical Setup
16
+
17
+
Metal frame with 3D-printed elements forming a swinging lever pivoting around a central axis. Two drone motors on opposite ends produce **downward** thrust (inverted for safety — bench can't fly off the desk). Differential thrust creates torque to control lever angle. Mechanical range is approximately ±50°.
18
+
19
+
## Roadmap
20
+
21
+
### M1: Single-axis PID with encoder — DONE
22
+
23
+
Single PI(D) loop at 50 Hz using AS5600 encoder feedback. Differential thrust mixer for 2 motors. Lever holds at 0° within ±3°. See [ADR-001](decision/ADR-001-pid-lever-stabilization.md).
24
+
25
+
### M2: Switch to BNO085 IMU as primary control input
26
+
27
+
Replace AS5600 with BNO085 quaternion/euler output as the sole PID input. AS5600 becomes telemetry-only for measuring IMU-vs-encoder error and lag. This is the critical step — a real drone has no encoder.
Log timestamped data from both sensors + PID internals to SD card via Adalogger PiCowbell. Enables post-experiment analysis with Python/pandas. See [ADR-002](decision/ADR-002-telemetry-logging.md).
Extract `base ± output` motor mapping into a configurable mixer module. Makes code drone-topology-agnostic — swap a mix table to support different frame types (2-motor lever, quadcopter X-frame, etc.).
40
+
41
+
**Depends on:** M1 (pure code refactor, no hardware dependency).
42
+
43
+
### M4: Cascaded PID (angle loop + rate loop)
44
+
45
+
Replace single angle PID with two nested loops: an outer angle loop (~50-100 Hz) feeding desired rotation rate to an inner rate loop (~500+ Hz) using raw gyro data. This is how real flight controllers (Betaflight, ArduPilot) work — the inner loop uses near-zero-lag gyro data for crisp response.
46
+
47
+
**Depends on:** M2 (IMU as input), M2a (telemetry to validate improvement), M3 (clean mixer).
48
+
49
+
### M5: Multi-axis control
50
+
51
+
Add roll and/or yaw axes. Requires either mechanical modifications to the bench or moving to an actual drone frame.
See [ADR-001, "Test Bench vs Real Drone" section](decision/ADR-001-pid-lever-stabilization.md) for a detailed comparison covering: single axis vs three axes, single PID vs cascaded PIDs, encoder vs IMU, and fixed pivot vs free flight.
58
+
59
+
## Project Structure
60
+
61
+
```
62
+
├── main.py # Entry point — upload to Pico, runs on boot
0 commit comments