Files
CelloEffects/AGENTS.md
2026-10-24 00:14:53 +02:00

282 lines
19 KiB
Markdown

# AGENTS.md
## Hardware
- **Board**: STM32H7S78-DK evaluation board
- **MCU**: STM32H7S78 (Cortex-M7 @ 480MHz)
- **Flash**: 64 KB internal flash at 0x08000000 (Boot) + 128 MB external OctoSPI NOR flash at 0x70000000 (Appli code + TouchGFX assets)
- **RAM**: 1 MB internal + external PSRAM (for Audio/Framebuffers)
## Project Architecture
### Dual-Boot Structure
This project uses a **dual-project architecture**:
| Project | Directory | Purpose |
|---------|-----------|---------|
| **Boot** | `/Boot/` | Bootloader - initializes hardware, loads application from external flash |
| **Appli** | `/Appli/` | Main application - TouchGFX UI + audio processing |
**Flashing order**: Boot must be flashed first (without loader), then Appli (with external loader). After flashing Appli, press NRST button to start.
**Note**: The Appli uses external memory (0x70000000) for TouchGFX assets and requires the external FlashLoader `MX66UW1G45G_STM32H7S78-DK.stldr` to program the external flash.
## Project Setup
- **IDE**: STM32CubeIDE with STM32CubeMX plugin
- **RTOS**: FreeRTOS V10.5.1 (CMSIS-RTOS V2)
- **GUI**: TouchGFX 4.26.1 with NemaGFX GPU acceleration
- **Language**: C/C++
- **Build System**: GCC Makefile (not CMake)
## Key Constraints
1. **Dual-boot flashing sequence**: Flash Boot first, then Appli (`gcc/flash.sh all` does both and hard-resets the board).
2. **External FlashLoader required**: Use `gcc/MX66UW1G45G_STM32H7S78-DK.stldr` for programming external flash (0x70000000)
3. **Never edit `Core/Src/` and `Core/Inc/` directly** - regenerate via STM32CubeMX instead
4. **High-speed audio acquisition (48kHz)** - uses DMA for ADF1 (microphone) and audio codec
5. **External memory usage**: Audio buffers (>900KB) stored in external PSRAM (section `AudioLooperBuffer`)
6. **TouchGFX architecture**: Two-stage debug - debug Boot first, then Appli
7. **FreeRTOS heap size**: Configured to 50KB in `FreeRTOSConfig.h` (heap_4)
8. **Interrupt priorities**: Audio interrupts at priority 2 (high priority), touch/SD at 14-15
9. **Debug probe connection**: always `ap=1 mode=UR` (STM32_Programmer_CLI) / `-m 1 -k` (ST-LINK_gdbserver). Without them the connection fails (`DEV_TARGET_NOT_HALTED`, `Cannot connect to access port 1`)
10. **No OpenOCD**: the installed OpenOCD 0.12 has no STM32H7RS target; debug with ST-LINK_gdbserver (Cortex-Debug in VS Code)
## Build & Flash
### Toolchain (macOS, verified)
| Tool | Path |
|------|------|
| `arm-none-eabi-gcc` / `-gdb` 10.3 | `/usr/local/bin` (in PATH) |
| STM32_Programmer_CLI | `/Applications/STMicroelectronics/programmer/STM32CubeProgrammer.app/Contents/MacOs/bin/STM32_Programmer_CLI` (not in PATH; `gcc/flash.sh` finds it, override with `$STM32_PROGRAMMER_CLI`) |
| ST-LINK_gdbserver | `/Applications/STMicroelectronics/STM32CubeIDE.app/Contents/Eclipse/plugins/com.st.stm32cube.ide.mcu.externaltools.stlink-gdb-server.macos64_*/tools/bin/ST-LINK_gdbserver` |
| SVD | `/Applications/STMicroelectronics/programmer/STM32CubeProgrammer.app/Contents/MacOs/SVD/STM32H7S.svd` |
| Probe | on-board STLINK-V3 (USB) |
TouchGFX 4.26.1 + STM32CubeMX are only needed to regenerate code.
On Linux, `arm-none-eabi-gcc` comes from STM32CubeIDE (`~/st/stm32cubeide_*/plugins/com.st.stm32cube.ide.mcu.externaltools.gnu-tools-for-stm32.*/tools/bin`, GCC 14.3); `~/.bashrc` adds it to `PATH`. The build also needs `ruby` (TouchGFX text/video conversion) and the executable bit on `Appli/Middlewares/ST/touchgfx/framework/tools/{imageconvert,fontconvert}/build/linux/*.out`. Build outputs (`*.o`, `*.d`, `*.su` in `*/TouchGFX/build` and `STM32CubeIDE/*/Debug`) are git-ignored and untracked.
### Build Commands
Run from the repository root (the makefiles `cd` to the root themselves):
```bash
make -C gcc -j8 # Build Boot + Appli
make -C gcc -j8 -f makefile_boot all # Boot only
make -C gcc -j8 -f makefile_appli all # Appli only
make -C gcc clean # Clean
python3 gcc/gen_compile_commands.py # Regenerate compile_commands.json (IntelliSense/clangd) after adding sources
```
Outputs:
- Boot: `Boot/TouchGFX/build/bin/target.elf|.hex` (internal flash 0x08000000)
- Appli: `Appli/TouchGFX/build/bin/target.elf|.hex` (external flash 0x70000000)
The "old Ruby version (2.6.10)" TouchGFX warning during the build is harmless.
### Flashing Commands
Use `gcc/flash.sh` (wraps STM32_Programmer_CLI with `ap=1 mode=UR` and the external loader):
```bash
gcc/flash.sh boot # Boot -> internal flash (with verify)
gcc/flash.sh appli # Appli -> external flash via gcc/MX66UW1G45G_STM32H7S78-DK.stldr (~20 s)
gcc/flash.sh all # Boot then Appli (required order), then hard reset
gcc/flash.sh probe # Check the ST-Link/board connection (no write)
gcc/flash.sh reset # Hardware reset
gcc/flash.sh verify # Dump 4 KB of 0x08000000 / 0x70000000 to /tmp/read_int.bin, /tmp/read_ext.bin
make -C gcc flash # Build + flash both (calls gcc/flash.sh on macOS/Linux)
```
Equivalent raw commands:
```bash
STM32_Programmer_CLI -c port=SWD ap=1 mode=UR -d Boot/TouchGFX/build/bin/target.hex -v -hardRst
STM32_Programmer_CLI -c port=SWD ap=1 mode=UR -el gcc/MX66UW1G45G_STM32H7S78-DK.stldr \
-d Appli/TouchGFX/build/bin/target.hex -hardRst
```
Only one tool can hold the ST-Link at a time: stop any debug session before flashing.
### VS Code (`.vscode/`, git-ignored)
Linux setup (uses the `ms-vscode.cpptools` extension, no Cortex-Debug needed; tools come from STM32CubeIDE in `~/st`):
- `gcc/toolchain-env.sh`: sourced by the helpers below; adds `arm-none-eabi-gcc/gdb` to `PATH` and exports `ST_GDBSERVER` / `ST_PROGRAMMER_BIN` (globs, so CubeIDE updates do not break it).
- `gcc/gdbserver.sh <boot|appli>`: starts `ST-LINK_gdbserver -p 61234 --swd --halt -m 1 -k -cp <programmer>` (+ `-el` external loader for `appli`). Kills a stale server first.
- `gcc/arm-gdb.sh`: wrapper around `arm-none-eabi-gdb` (VS Code `miDebuggerPath` cannot glob).
- `tasks.json`: Build All (default, Ctrl+Shift+B) / Boot / Appli, Clean, Flash Boot/Appli/All, Build + Flash All, Board: Probe/Reset/Verify, `gdbserver: Boot|Appli` (background), `Debug prep: ...` (build + gdbserver, used as `preLaunchTask`), Kill gdbserver, Generate compile_commands.json.
- `launch.json` (`cppdbg`, `miDebuggerServerAddress: localhost:61234`): **Debug Boot** (build, `load`, `monitor reset`, hw break on `main`), **Debug Appli** (build, `load` via the external loader, reset, stops at Appli `main()`), **Debug Appli (no flash)** (reset only).
- `launch.json` also has three `stlinkgdbtarget` configs (they need `deviceName: STM32H7S7L8H6H` = CMSIS pack variant / `Mcu.CPN`, and `deviceCore: Cortex-M7`; the ST extension cannot infer them for this Makefile project) (**ST Debug Boot / Appli / Appli (no flash)**) for the STMicroelectronics VS Code extensions (`stm32cube-ide-debug-stlink-gdbserver`, `-registers`, `-rtos`): they start their own ST-LINK server from `~/.local/share/stm32cube/bundles` (no `gdbserver:` task needed), and give the peripheral **Registers** view (SVD `~/STMicroelectronics/STM32Cube/STM32CubeProgrammer/SVD/STM32H7S.svd`), the **FreeRTOS** threads view (`serverRtos`, `ARM_CM7`) and Live Watch. Validated headless (DAP driven by script: server, RTOS proxy, GDB client and Live Watch all start; flash `load` not exercised). **Required, git-ignored file** `.settings/ide.store.json` at the repo root: `{"device": "STM32H7S7L8H6H", "core": "Cortex-M7", "tz": "TZ-disabled"}`. The extension looks for it by walking up from the ELF directory; without it the ST-LINK server step is silently skipped and launch fails with "No GDB Server running" ("project settings not found" in the log). The ST configs need `"preRunCommands": ["monitor reset"]`: after `load` the extension starts at the ELF `Reset_Handler`, but external flash is only memory-mapped once Boot has run, so without the reset Appli faults on its first instruction (SIGTRAP in Boot's handler, black screen). Other notes: the extension's CMSIS pack root `~/.local/share/stm32cube/packs` should contain `STMicroelectronics/stm32h7xx_dfp/1.3.0`; the `ST Debug Appli (no flash)` variant only has a symbol file, so it sets `program` (the RTOS proxy needs the ELF; without it the proxy dies and GDB times out).
- `settings.json` / `extensions.json`: IntelliSense from `compile_commands.json`.
macOS keeps the Cortex-Debug (`servertype: stlink`) setup described under "Debugging". `pkill -x ST-LINK_gdbserver` does NOT work (process name truncated to 15 chars): use `pkill -f '[S]T-LINK_gdbserver'`.
Keep `AGENTS.md`, `.vscode/tasks.json`, `.vscode/launch.json` and `gcc/flash.sh` consistent when changing any build/flash/debug command.
### Debugging
Boot must already be in internal flash (`gcc/flash.sh boot`): at reset the core runs Boot, which maps the XSPI memories and jumps to Appli `main()` at 0x70000000.
VS Code: run "Debug Appli" (F5). No NRST press needed.
Command line:
```bash
GDBSRV=$(ls -d /Applications/STMicroelectronics/STM32CubeIDE.app/Contents/Eclipse/plugins/com.st.stm32cube.ide.mcu.externaltools.stlink-gdb-server.macos64_*/tools/bin | tail -1)/ST-LINK_gdbserver
"$GDBSRV" -p 61234 --swd --halt -m 1 -k \
-cp /Applications/STMicroelectronics/programmer/STM32CubeProgrammer.app/Contents/MacOs/bin \
-el gcc/MX66UW1G45G_STM32H7S78-DK.stldr
# In another terminal
arm-none-eabi-gdb Appli/TouchGFX/build/bin/target.elf
(gdb) target extended-remote :61234
(gdb) load # optional: programs 0x70000000 through the external loader
(gdb) monitor reset
(gdb) break main # hardware breakpoint chosen automatically (0x70000000 is flash in the memory map)
(gdb) continue
```
STM32CubeIDE alternative: debug Boot, terminate (Ctrl+F2), debug Appli, Resume (F8), press NRST.
## Sensors/Peripherals
### Audio (Primary Function)
- **ADF1** - Digital microphone (PDM interface, 48kHz sample rate)
- **I2C1** - Audio codec (STM32 Audio BSP)
- **DMA2D/GPDMA1/HPDMA1** - Audio DMA channels
### Display
- **LTDC** - LCD-TFT controller
- **GPU2D (NemaGFX)** - 2D acceleration
- **Display**: 800x480 RGB565 (LTDC)
### Storage & Communication
- **XSPI1** - External PSRAM (for audio buffers)
- **XSPI2** - External Flash (for TouchGFX assets)
- **I2C1** - Touch controller (FT5336), sensors
### Touch Control
- **FT5336** - Capacitive touch controller
- **SPI** - Touch interface
- **GPIO** - Touch interrupt (TP_IRQ)
## Remote Control (BLE, X-NUCLEO-67W61M1)
The effects can be driven from a phone/PC over BLE through the ST67W611M1 shield (SPI4, Arduino header). Wi-Fi (MQTT/HTTP) is the next step; **audio streaming to Bluetooth speakers is not possible** with this module (BLE GATT only, no A2DP/Classic, no LE Audio) - use an external A2DP transmitter on the headphone jack if needed (adds 100-250 ms of latency).
Layers (all hand-written, regeneration-safe):
- `Appli/Core/Src/remote_params.c` + `Inc/remote_params.h`: transport-independent table of 34 parameters (id byte -> field of `AudioEffects_GetParams()`, scale, range). Unit-testable on PC.
- `Appli/Core/Src/remote_ble.c` + `Inc/remote_ble.h`: FreeRTOS task `remoteBle` (BelowNormal, 3 KB stack) started by `RemoteBle_Start()` from the `RTOS_THREADS` user section of `main.c`. GATT service `c3110000-7e8a-4b1c-9d52-f0a1e4b7c600`, `Control` char (`...0001`, write) and `State` char (`...0002`, notify). Records are `[id u8][value i16 LE]`; id `0xFF` requests a snapshot (also sent when the client subscribes). Full protocol in the header comment.
- `Appli/Core/Src/st67_glue.c`: `vLoggingPrintf` (the driver's Logging component is not selected, there is no UART) writes INFO/WARN/ERROR into the RAM ring `st67_log_ring` (4 KB, read it in the debugger; `st67_log_head` = next write position), and `ST67_ExtiCallback()` forwards the `SPI_RDY` edges (hooked in `STM32TouchController.cpp`, the only `HAL_GPIO_EXTI_Callback`).
- `Appli/ST67W6X_Network_Driver/Target/spi_port.c` (generated by the pack): only `USER CODE` sections were edited (pin-name aliases `CHIP_EN_*`/`SPI_CS_*`, and **CS driven low at init**: the driver treats CS as active-high, ST's example idles it low; CubeMX leaves it high).
- Build: `gcc/makefile_appli` compiles the driver (`Middlewares/ST/ST67W6X_Network_Driver`, Core + W61_at + W61_bus) with `-DST67_ARCH=W6X_ARCH_T01`.
**NCP firmware**: the ST67W611M1 must run the "mission profile T01" firmware of the pack (UM3449 step 4), otherwise the SPI header is rejected (symptom: `Invalid magic 0x5501`, logged in `st67_log_ring`; the module answers `01 55 00 00 00 00 01 00` to `AA 55 00 ...`). ST's scripts only support a few NUCLEO hosts, so the DK acts as the UART bridge: `gcc/ncp_loader/main.c` (port of ST's NCP_Loader: bit-level copy VCP UART4 PD0/PD1 <-> module UART PE7/PE8, BOOT PD15 / CHIP_EN PD13 to enter the ROM download mode) built by `gcc/makefile_ncp_loader`, driven by `gcc/ncp_update.sh [version] [--yes]` (compiles the bridge, loads it in place of Boot, flashes the module with the pack's `QConn_Flash_Cmd-ubuntu` at 2 Mbaud, then restores Boot with `gcc/flash.sh boot`; Appli in external flash is untouched). Bridge validated on the board with the read-only `QConn_Eflash -r` (handshake + eFuse read OK). The module is already locked (eFuse public key filled, JTAG disabled). Needs a free ST-Link (no debug session) and the X-NUCLEO on the Arduino header.
Not validated on hardware yet. Things to check first: the FreeRTOS heap (50 KB in `DTCM_RTOS`, region is 52 KB, ~94 % used at link time: the driver allocates its tasks/queues there - `st67_log_ring` records the free heap after start-up), solder bridges SB3/SB6/SB12/SB20 closed on the shield (UM3449 lists them OFF by default, which would leave the Arduino SPI pins unconnected), JP1/JP2 fitted. Pairing is "Just Works": anyone in range can control the effects.
## Audio Processing
### Effects Chain (in order)
1. **Octaver** - Dual-grain pitch shifting (-2, -1, +1, +2 octaves)
2. **Equalizer** - 10-band graphic EQ (31 Hz - 16 kHz, -24..+24 dB)
3. **Overdrive** - Distortion effect
4. **Saturation** - Audio saturation
5. **Echo** - Delay-based echo
6. **Reverb** - Room reverb
### Analysis
- **FFT**: 2048-point real FFT (CMSIS-DSP)
- **Sample rate**: 48kHz
- **Bins**: 1024 (0-24kHz spectrum)
- **Peak detection**: Frequency with maximum magnitude
### Looping
- **Duration**: Up to 5 seconds at 48kHz
- **Playback**: 2x speed (raises pitch by octave)
- **Storage**: External PSRAM (937.5KB buffer)
## TouchGFX Architecture
### Project Structure
```
Appli/
├── TouchGFX/
│ ├── App/ # Application code (user created)
│ ├── assets/ # Images, fonts, videos
│ ├── build/ # Generated build files
│ ├── config/ # Build configuration
│ ├── generated/ # Auto-generated code (DO NOT EDIT)
│ ├── gui/ # Custom GUI code (views, presenters)
│ ├── simulator/ # Simulator files
│ └── target/ # Target-specific code
```
### GUI Screens
- **InitScreen** - Splash screen (title + central button) -> Overview
- **Overview** - Home screen: power toggle + master volume, 3x2 grid of effect cards (tap = open the effect screen, switch = enable/bypass), looper (hold to record, plays back x2), Spectrum button
- **Octaver, Equalizer, Overdrive, Saturation, Echo, Reverb** - Effect tuning screens (common header: back, title, on/off; sliders with live value)
- **Oscillo** - Spectrum analyzer (title "SPECTRE")
### UI Conventions (keep when editing the GUI)
- **Never write files in `Appli/TouchGFX/generated/`**: it is regenerated by TouchGFX Designer. Screens, widgets and interactions come from `CelloEffects.touchgfx`.
- **Custom look is done in user code only** (`Appli/TouchGFX/gui/`): each `XxxView::setupScreen()` re-lays out the generated widgets and adds extra ones.
- `gui/include/gui/common/UiTheme.hpp`: palette, per-effect accent color (`ui::accent()`), `ui::Header`, `ui::ValueText` (formatted value: `show("%d %%", v)`, `showDb(v)`), `ui::layoutEffectHeader()`, `ui::layoutParamRow()`
- `gui/include/gui/common/EffectCard.hpp`: home-screen effect card
- The home grid lives in `OverviewView`; the generated `buttonWithLabel1..6` of Overview are hidden on purpose.
- **A new screen must be created in TouchGFX Designer** (so `FrontendHeapBase` and `FrontendApplicationBase` include it). A screen hand-written outside Designer is not in the screen heap and corrupts memory.
- **Texts**: add them to `Appli/TouchGFX/assets/texts/texts.xml` (group `UiRework`); the build regenerates texts and fonts (`T_<ID>`). Typographies: `Default` 20 px, `Medium` 28 px, `Large` 40 px. Wildcard texts: `VALUE_M_LEFT` (Medium), `VALUE_S_CENTER` (Default); a character shown in a wildcard must be listed in the typography's `WidgetWildcardCharacters`.
- **Canvas widgets** (Circle, Graph...) need a per-screen `CanvasWidgetRenderer::setupBuffer()` (see `OverviewView`, `OscilloViewBase`).
## Memory Map
### Key Sections
- **AudioLooperBuffer** - External PSRAM (audio loop recording)
- **TouchGFX framebuffers** - External PSRAM (LCD display)
- **Heap** - Internal RAM (50KB FreeRTOS heap)
- **Stack** - Internal RAM (task stacks)
### External Memory
- **PSRAM**: 8MB (XSPI1) - Audio buffers + framebuffers
- **Flash**: 128MB (XSPI2) - TouchGFX assets (compressed images/fonts)
- **External FlashLoader**: `gcc/MX66UW1G45G_STM32H7S78-DK.stldr` (required for programming external flash via STM32CubeProgrammer)
## Data Logging
- **Format**: CSV (to be implemented)
- **Storage**: SD card via SPI (not currently implemented in main code)
- **FATFS**: File system (available via Middlewares/ST)
---
*Last updated: October 2026*
*Project: STM32H7S78-DK Audio Effects Processor with TouchGFX*
**Last fixed**: October 18, 2026 (VS Code/CLI build-flash-debug verified on board: `gcc/flash.sh`, Cortex-Debug + ST-LINK_gdbserver)
## Flashing Troubleshooting
### Common Issues
1. **`DEV_TARGET_NOT_HALTED` / `Cannot connect to access port 1` / gdbserver `Target unknown error 32`**
- Cause: missing access port or connect-under-reset option
- Solution: `ap=1 mode=UR` (CLI) or `-m 1 -k` (ST-LINK_gdbserver), as used by `gcc/flash.sh` and `.vscode/launch.json`
2. **"Upload size is bigger than flash memory size"**
- Cause: reading beyond the 64 KB internal flash (0x08000000-0x08010000)
- Solution: use a smaller read size
3. **External memory (0x70000000) not accessible**
- Cause: External FlashLoader not loaded
- Solution: add `-el gcc/MX66UW1G45G_STM32H7S78-DK.stldr` (CLI and gdbserver)
4. **Application runs but display is blank**
- Cause: Appli not flashed in external memory correctly, or Boot missing
- Solution: `gcc/flash.sh all`, then `gcc/flash.sh verify`
5. **ST-Link busy**
- Cause: a debug session or STM32CubeProgrammer GUI holds the probe
- Solution: stop it, then retry
6. **`nul` files appear in the repo**
- Cause: Windows `2> nul` redirections in `gcc/include/cube_programmer.mk` (now guarded with `ifeq ($(OS),Windows_NT)`)