PID Motor Control
Closed-loop PID motor controller on STM32 Nucleo-F411RE with real-time IMU feedback. Live tuning via UART CLI, step response capture to CSV for plotting.
Hardware
| Component | Part | Interface |
|---|---|---|
| MCU | STM32 Nucleo-F411RE | - |
| Motor Driver | TB6612FNG | GPIO + PWM |
| IMU | MPU-6050 (6-axis) | I2C1 @ 400 kHz |
| Display | SSD1306 0.96” OLED | I2C1 @ 0x3C |
| Motor | 130-type DC | via TB6612FNG |
| Debug | ST-Link VCP (USART2) | 115200 baud |
Architecture
TIM3_CH1 (20 kHz PWM) --> TB6612FNG --> DC Motor
|
MPU-6050 (I2C, 14-byte burst read) <-----+
| (physical coupling)
v
Complementary Filter (alpha=0.98)
|
v
PID Controller (1 kHz, TIM2 ISR)
| - Derivative on measurement
| - Low-pass filter on D term (N=10)
| - Integral clamping anti-windup
v
Motor effort (-1.0 to +1.0) --> sign-magnitude drive
Control Design
The PID loop runs at 1 kHz in TIM2’s update ISR. Three techniques keep the output clean:
Derivative on measurement – the D term differentiates the process variable, not the error signal. This eliminates the derivative kick that occurs when the setpoint changes abruptly.
Low-pass filter on D term – a first-order filter with N=10 smooths high-frequency noise from the IMU without adding significant phase lag at the control bandwidth.
Integral anti-windup – the integrator is clamped to the output range. When the motor saturates, the integrator stops accumulating, preventing overshoot on recovery.
UART CLI
The main loop runs a serial command interface for live tuning:
| Command | Description |
|---|---|
kp <value> |
Set proportional gain |
ki <value> |
Set integral gain |
kd <value> |
Set derivative gain |
sp <angle> |
Set target angle |
step |
Trigger step response, stream CSV |
status |
Print current gains, setpoint, error |
Step response data streams as CSV over UART for offline plotting with scripts/plot_step_response.py.
Build
mkdir build && cd build
cmake -DCMAKE_BUILD_TYPE=Debug ..
make
Flash via ST-Link SWD.