Skip to content

Latest commit

 

History

7 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 

Repository files navigation

Delibot Controller Firmware

C CMake Ninja STM32 Cortex-M4F STM32CubeMX HAL/LL CRSF I2C UART

Firmware for an STM32F401CCU6-based differential-drive (2-wheel) robot controller. It accepts speed commands from either a TBS CRSF receiver or a Raspberry Pi, and drives an L298N motor driver through an encoder-based (optionally BNO080 IMU-assisted) speed control loop.

  • CRSF always takes priority when the link is alive. The Raspberry Pi command is only used when CRSF is disconnected.
  • If neither source is present, the motors stop (failsafe).

Hardware

Pin Function Mode
PA0 L298N ENA (Motor A PWM) TIM5_CH1
PA1 L298N ENB (Motor B PWM) TIM5_CH2
PA2 Raspberry Pi link TX USART2_TX
PA3 Raspberry Pi link RX USART2_RX
PA4 L298N IN1 GPIO_Output
PA5 L298N IN2 GPIO_Output
PA6 L298N IN3 GPIO_Output
PA7 L298N IN4 GPIO_Output
PA9 CRSF TX (telemetry) USART1_TX
PA10 CRSF RX USART1_RX
PA13 SWDIO SYS
PA14 SWCLK SYS
PA15 Encoder1 channel A TIM2_CH1
PB3 Encoder1 channel B TIM2_CH2
PB4 Encoder2 channel A TIM3_CH1
PB5 Encoder2 channel B TIM3_CH2
PB6 BNO080 SCL I2C1_SCL
PB7 BNO080 SDA I2C1_SDA

Clocks: HSE 25 MHz → SYSCLK 84 MHz, APB1 42 MHz (timers 84 MHz), APB2 84 MHz

Driver assignment

Peripheral Driver
RCC, GPIO, TIM2, TIM3, TIM5 LL
I2C1 (BNO080) HAL
USART1 (CRSF) LL + DMA2 Stream2 circular RX + IDLE-line interrupt
USART2 (Raspberry Pi) HAL + byte-wise interrupt

The encoders sit on the gearbox output shaft, so their counts are treated directly as wheel (output-shaft) rotation — no extra gear ratio is applied.

Source layout

Core/           CubeMX-generated code (main.c, etc.). Only the USER CODE sections call into App/
App/Inc, Src/
  app.c              app_init()/app_run() top-level orchestration, 200 Hz control loop
  app_config.h        Fixed constants + robot_config default-value macros
  robot_config.c      Runtime parameter table (PARAM_*), persisted to flash sector 5
  crsf.c               USART1 LL+DMA+IDLE CRSF parser, RC channel decode, ATTITUDE telemetry TX
  rpi_link.c           USART2 framing transport layer (pure send/receive, no protocol semantics)
  host_commands.c      Interprets/answers Raspberry Pi & configurator commands (built on rpi_link)
  command_source.c     Picks CRSF vs. Raspberry Pi as the active command source (CRSF wins)
  encoder.c            TIM2/TIM3 LL quadrature encoder wrapper, sign inversion, raw tick accumulator
  motor.c              TIM5 LL PWM + L298N direction GPIO wrapper, sign inversion
  pid.c                Generic PID controller
  drive_controller.c   Left/right wheel speed PID + yaw control (encoder differential or IMU gyro trim)
  bno080.c             BNO080 SHTP/SH2 I2C driver (Rotation Vector + Gyro, DCD calibration save)
  crc8.c               CRC8-DVB-S2 (poly 0xD5) used by CRSF

Build

cmake -S . -B build -G Ninja -DCMAKE_TOOLCHAIN_FILE=cmake/gcc-arm-none-eabi.cmake -DCMAKE_BUILD_TYPE=Debug
cmake --build build

arm-none-eabi-gcc must be on PATH. App/Src/*.c is picked up automatically via glob in the top-level CMakeLists.txt.

Flash layout: the last sector (Sector 5, 0x08020000, 128 KB) is reserved for configuration storage, so firmware code must stay under 128 KB (currently ~26 KB).

Control loop

app_run() runs the following every 5 ms (CONTROL_LOOP_PERIOD_MS, 200 Hz):

  1. bno080_poll() — non-blocking IMU report poll
  2. command_source_get() — checks CRSF link (300 ms timeout) first; if connected, uses CRSF channels, otherwise falls back to the last Raspberry Pi velocity command (500 ms timeout), or zero if neither is present
  3. drive_controller_update() — converts the target linear/angular velocity into left/right wheel speed targets, runs encoder-feedback PID to produce PWM duty, and optionally adds an IMU yaw-rate trim
  4. Sends telemetry to the Raspberry Pi every 20 ms (TELEMETRY_PERIOD_MS) and attitude telemetry over CRSF every 100 ms (CRSF_TELEMETRY_PERIOD_MS)

Raspberry Pi UART protocol

USART2, 115200 8N1. Framing matches uart.py / stm32_uart.py from Delibot-RPi-Controller.

Frame format

[0xFF][LEN][CMD][DATA...][CRC]
  • LEN = number of bytes in CMD + DATA (i.e. len(DATA) + 1)
  • CRC = 1-byte XOR checksum over CMD and every DATA byte (crc = CMD; for b in DATA: crc ^= b)
  • Minimum frame length is 4 bytes (commands with no DATA have LEN = 1)

On a CRC mismatch the frame is discarded and the parser resyncs by searching for the next 0xFF.

Host (Pi) → STM32

CMD Name Payload Description
0x01 VELOCITY int16 linear_mm_s, int16 angular_mrad_s (LE) Target velocity. Also doubles as a heartbeat — if not received within 500 ms, the link is considered lost
0x02 PARAM_GET uint8 param_id Request a parameter value → replies with PARAM_VALUE
0x03 PARAM_SET uint8 param_id, float32 value (LE) Applies the value to RAM immediately (not persisted to flash) → replies with PARAM_VALUE
0x04 PARAM_SAVE none Commits the entire in-RAM parameter set to flash
0x05 PARAM_RESET_DEFAULTS none Resets all parameters to defaults (RAM only — send PARAM_SAVE separately to persist)
0x06 PARAM_LIST_REQUEST none Streams a PARAM_VALUE reply for every parameter, in order
0x07 CRSF_CHANNELS_REQUEST none Request raw RC channel values → replies with CRSF_CHANNELS
0x08 ENCODER_RAW_REQUEST none Request accumulated encoder ticks → replies with ENCODER_RAW
0x09 ENCODER_ZERO none Resets the accumulated encoder tick counters to 0
0x0A IMU_SAVE_CALIBRATION none Sends the SH2 "Save DCD" command to the BNO080 (persists its own calibration)
0x10 DEBUG_PRINT none (ignored on receive) Reserved; STM32 → Pi direction only
0xFF HEALTH_CHECK uint8 = 0x01 Ping. Echoed back with the same payload immediately

STM32 → Host (Pi)

CMD Name Payload Description
0x81 TELEMETRY int16 yaw_centideg, int16 yaw_rate_centideg_s, int16 left_wheel_mm_s, int16 right_wheel_mm_s, uint8 status_flags (all LE) Periodic status broadcast, every 20 ms
0x82 PARAM_VALUE uint8 param_id, float32 value (LE) Reply to PARAM_GET / PARAM_SET / PARAM_LIST_REQUEST
0x83 CRSF_CHANNELS uint16 channel[16] (LE) Raw values of all 16 RC channels (CRSF standard range: 172–1811)
0x84 ENCODER_RAW int32 left_ticks, int32 right_ticks (LE) Accumulated ticks, before the ENCODER_*_SIGN inversion is applied
0x10 DEBUG_PRINT ASCII text Debug log line (e.g. sent on BNO080 init failure)
0xFF HEALTH_CHECK uint8 = 0x01 Ping reply

status_flags bits: bit0 CRSF connected, bit1 Raspberry Pi link active, bit2 IMU OK, bit3 motors driving.

Note: the stm32_uart.py reference we received doesn't yet define command constants or helper methods for VELOCITY / PARAM_* / TELEMETRY etc. The Raspberry Pi's STM32_UART class needs matching command IDs added from the table above before the two sides can actually talk. Also, its UART('/dev/ttyACM0', ...) port is hardcoded — this board uses USART2 (PA2/PA3) GPIO UART rather than USB, so on the Raspberry Pi it will typically enumerate as /dev/ttyAMA0 or /dev/serial0 instead. That needs to be fixed on the Raspberry Pi side.

Parameters (robot_config)

PARAM_SET applies a value immediately; you must also send PARAM_SAVE for it to survive a reboot (without it, the value only lives in RAM and the next boot reloads whatever was last saved to flash, or the defaults).

ID Name Default Description
0 CRSF_STEERING_CHANNEL 0 CRSF channel index (0-based) used for steering
1 CRSF_THROTTLE_CHANNEL 1 CRSF channel index used for forward/back throttle
2 CRSF_STEERING_SIGN 1.0 Steering channel sign (±1)
3 CRSF_THROTTLE_SIGN 1.0 Throttle channel sign (±1)
4 CRSF_DEADBAND 10 Channel center deadband, in raw units
5 MOTOR_LEFT_SIGN 1.0 Left motor rotation direction sign
6 MOTOR_RIGHT_SIGN 1.0 Right motor rotation direction sign
7 ENCODER_LEFT_SIGN 1.0 Left encoder count direction sign
8 ENCODER_RIGHT_SIGN 1.0 Right encoder count direction sign
9 WHEEL_TRACK_WIDTH_MM 200.0 Distance between the wheel centers (mm)
10 WHEEL_DIAMETER_MM 65.0 Wheel diameter (mm)
11 ENCODER_COUNTS_PER_REV 2800.0 Encoder counts per wheel (output-shaft) revolution, X4 mode
12 MAX_LINEAR_SPEED_MM_S 1500.0 Target linear speed at full throttle stick (mm/s)
13 MAX_ANGULAR_SPEED_MRAD_S 3000.0 Target angular speed at full steering stick (mrad/s)
14 WHEEL_PID_KP 0.0015 Wheel speed PID proportional gain
15 WHEEL_PID_KI 0.02 Wheel speed PID integral gain
16 WHEEL_PID_KD 0.0 Wheel speed PID derivative gain
17 YAW_CONTROL_SOURCE 0 0 = encoder differential only, 1 = add IMU gyro yaw-rate trim
18 YAW_RATE_PID_KP 0.3 (IMU mode) Yaw-rate PID proportional gain
19 YAW_RATE_PID_KI 0.1 (IMU mode) Yaw-rate PID integral gain
20 YAW_RATE_PID_KD 0.0 (IMU mode) Yaw-rate PID derivative gain

All values are transferred uniformly as float32 (even parameters that are semantically integers, like CRSF_STEERING_CHANNEL, are sent/received as float and cast to int in firmware).

Calibration guide (for the configurator app to implement)

  1. Confirm the link with HEALTH_CHECK (0xFF)
  2. Poll CRSF_CHANNELS_REQUEST (0x07) to monitor channels — wiggle the sticks to identify the steering/throttle channel and direction, then set CRSF_STEERING_CHANNEL / CRSF_THROTTLE_CHANNEL / *_SIGN
  3. Send ENCODER_ZERO (0x09), manually rotate a wheel exactly N turns, then read raw ticks with ENCODER_RAW_REQUEST (0x08) and compute ENCODER_COUNTS_PER_REV = raw_ticks / N; check wheel direction from TELEMETRY (left/right wheel mm/s) and flip MOTOR_*_SIGN / ENCODER_*_SIGN if needed
  4. Enter the measured WHEEL_TRACK_WIDTH_MM and WHEEL_DIAMETER_MM
  5. To use IMU-assisted yaw control, wave the robot through a figure-8 by hand, send IMU_SAVE_CALIBRATION (0x0A), then set YAW_CONTROL_SOURCE = 1
  6. Finish with PARAM_SAVE (0x04) — without it, everything resets on the next reboot

Known limitations / TODO

  • The physical constants (track width, wheel diameter, encoder CPR) and PID gains in robot_config are all placeholders — they need to be calibrated against the real robot
  • Verify that MOTOR_*_SIGN and ENCODER_*_SIGN match the actual wiring after first boot
  • The "Delibot Configurator" PC app itself is out of scope for this repository — implementing the protocol documented here is sufficient
  • The BNO080 INT/RST pins are not wired, so it only runs in polling mode (no low-latency interrupt-driven updates)

About

Delibot Controller is an STM32 code to control delibot using Uart & CRSF.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages