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

19 KiB

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):

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):

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:

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:

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))