- Add callback_task with ISR-based UART command parser (@command\n protocol) - Add log_printf with mutex protection to prevent printf interleaving - Add per-motor enable/disable (motor enable yaw|pitch) - Add PID tuning via UART (pid roll kp 15) - Add cmd_parser module (registration + tokenize + dispatch) - Add UART layer architecture (interface → HAL → BSP) - Add filter modules (lowpass, moving_average, notch, rate_limiter) - Rewrite I2C bus and UART bus modules - Rewrite PID controller and MF4010V2 motor driver - Fix soft I2C driver Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
115 lines
4.9 KiB
Markdown
115 lines
4.9 KiB
Markdown
# CLAUDE.md
|
|
|
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
|
|
## Project Overview
|
|
|
|
CloudPlant — a 2-axis gimbal stabilization controller for STM32F407xx (Cortex-M4F). Uses FreeRTOS (CMSIS V2 API) with a 100Hz PID control loop reading IMU angles over soft I2C and driving MF4010V2 brushless motors over CAN bus.
|
|
|
|
## Build Commands
|
|
|
|
```bash
|
|
# Configure (one-time; Debug is the default preset)
|
|
cmake --preset Debug
|
|
|
|
# Build
|
|
cmake --build build/Debug
|
|
|
|
# Or build with ninja directly
|
|
ninja -C build/Debug
|
|
|
|
# Build a specific object file only
|
|
ninja -C build/Debug CMakeFiles/CloudPlant.dir/path/to/file.c.obj
|
|
```
|
|
|
|
The project uses CMake with Ninja as the generator and the `arm-none-eabi-gcc` toolchain (`cmake/gcc-arm-none-eabi.cmake`). A `starm-clang` toolchain file also exists as an alternative. Compile commands are exported to `build/Debug/compile_commands.json` for clangd.
|
|
|
|
## Flash
|
|
|
|
```bash
|
|
# Flash the Debug ELF (requires STM32CubeProgrammer CLI)
|
|
python flash.py build/Debug/CloudPlant.elf
|
|
|
|
# Erase chip only
|
|
python flash.py --erase-only
|
|
|
|
# Flash a release build
|
|
python flash.py build/Release/CloudPlant.elf
|
|
```
|
|
|
|
The script converts ELF to BIN with `arm-none-eabi-objcopy`, then flashes via `STM32_Programmer_CLI` at `0x08000000` over SWD.
|
|
|
|
## Architecture: Strict 6-Layer Stack
|
|
|
|
Every hardware access must follow this chain. Cross-layer calls are forbidden.
|
|
|
|
```
|
|
APP → Business logic, task orchestration
|
|
↓
|
|
Module → Device/algorithm encapsulation (motor, PID, LED, soft_i2c)
|
|
↓
|
|
Interface → Abstract ops-based API (_if.h with function pointer structs)
|
|
↓
|
|
HAL → Hardware operations using STM32 HAL, registers ops to Interface
|
|
↓
|
|
BSP → Hardware description only — pin maps, CAN handles, no logic
|
|
↓
|
|
Driver → STM32 HAL / CMSIS (generated by CubeMX, in Core/, Drivers/, Middlewares/)
|
|
```
|
|
|
|
**Key rules:**
|
|
- Module must NOT call HAL/BSP directly — it goes through Interface APIs.
|
|
- Interface defines a `xxx_ops_t` struct with function pointers; HAL implements and registers ops via `xxx_register_ops()`.
|
|
- BSP files contain only static data arrays (pin mappings, handle arrays) — no `if`/`for`/`while` or business logic.
|
|
- APP never calls HAL or BSP directly; it calls Module functions, which call Interface APIs.
|
|
|
|
## Key Directories (User Code)
|
|
|
|
| Directory | Purpose |
|
|
|-----------|---------|
|
|
| `app/` | RTOS tasks: `app_init`, `control_task`, `sensor_task`, `monitor_task` |
|
|
| `modules/device/motor/` | MF4010V2 brushless motor driver (CAN-based, speed/position/torque modes) |
|
|
| `modules/device/led/` | LED device abstraction |
|
|
| `modules/control/pid/` | Generic PID controller (float-based, anti-windup via output clamping) |
|
|
| `modules/bus/soft_i2c/` | Bit-banged I2C master (frame-level API) |
|
|
| `interfaces/` | Abstract APIs: `can_if`, `led_if`, `gpio_if`, `i2c_if`, `soft_i2c_if` |
|
|
| `hal/stm32f4/` | HAL implementations per peripheral — registers ops with interfaces |
|
|
| `bsp/stm32f4/` | Board-specific pin/instance mappings |
|
|
| `3rdparty/jy901p/` | JY901P IMU SDK (I2C protocol) |
|
|
| `3rdparty/cjson/` | cJSON library |
|
|
|
|
## CubeMX Integration
|
|
|
|
CubeMX-generated code lives in `Core/`, `Drivers/`, and `Middlewares/`. It is compiled as three static libraries defined in `cmake/stm32cubemx/CMakeLists.txt`:
|
|
- `stm32cubemx` (INTERFACE — headers and defines)
|
|
- `STM32_Drivers` (OBJECT — HAL driver sources)
|
|
- `FreeRTOS` (OBJECT — FreeRTOS kernel sources)
|
|
|
|
User code is added to the main `CMakeLists.txt` via `target_sources()` and `target_include_directories()`. Global defines `USE_HAL_DRIVER`, `STM32F407xx`, and `STM32_THREAD_SAFE_STRATEGY=2` come from the CubeMX CMake.
|
|
|
|
FreeRTOS task creation happens in `Core/Src/freertos.c` (CubeMX-managed). The tasks delegate to `app/` entry points: `StartSensorTask` → `sensor_task()`, `StartControlTask` → `control_task()`, `StartMonitorTask` → `monitor_task()`.
|
|
|
|
## Data Flow (Gimbal Stabilization)
|
|
|
|
```
|
|
sensor_task (IMU poll via soft I2C)
|
|
→ osMessageQueue (imu_data_t: acc, gyro, angle)
|
|
→ control_task (100Hz PID loop)
|
|
→ CAN bus (motor speed commands to MF4010V2)
|
|
```
|
|
|
|
The IMU queue (`g_imu_queue`) is created in `app_init()` with depth 4. `sensor_task` reads JY901P IMU registers via soft I2C and pushes data into the queue. `control_task` reads the queue with a 20ms timeout, runs two PID controllers (Roll → Yaw motor, Pitch → Pitch motor), and sends speed commands over CAN.
|
|
|
|
## Interface Pattern
|
|
|
|
Every hardware peripheral follows this pattern:
|
|
|
|
```
|
|
interfaces/<periph>/<periph>_if.h — ops struct + register function + API declarations
|
|
interfaces/<periph>/<periph>_if.c — dispatching through registered ops
|
|
hal/stm32f4/<periph>/hal_<periph>.c — implements ops, registers them at init
|
|
bsp/stm32f4/<periph>/bsp_<periph>.c — static hardware mapping
|
|
```
|
|
|
|
Module code (e.g., `modules/device/led/led.c`) calls `led_if_init()`, `led_if_write()`, etc. — never `hal_led_*` or `bsp_led_*` directly.
|