# 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//_if.h — ops struct + register function + API declarations interfaces//_if.c — dispatching through registered ops hal/stm32f4//hal_.c — implements ops, registers them at init bsp/stm32f4//bsp_.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.