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
- Dual-boot flashing sequence: Flash Boot first, then Appli (
gcc/flash.sh alldoes both and hard-resets the board). - External FlashLoader required: Use
gcc/MX66UW1G45G_STM32H7S78-DK.stldrfor programming external flash (0x70000000) - Never edit
Core/Src/andCore/Inc/directly - regenerate via STM32CubeMX instead - High-speed audio acquisition (48kHz) - uses DMA for ADF1 (microphone) and audio codec
- External memory usage: Audio buffers (>900KB) stored in external PSRAM (section
AudioLooperBuffer) - TouchGFX architecture: Two-stage debug - debug Boot first, then Appli
- FreeRTOS heap size: Configured to 50KB in
FreeRTOSConfig.h(heap_4) - Interrupt priorities: Audio interrupts at priority 2 (high priority), touch/SD at 14-15
- 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) - 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; addsarm-none-eabi-gcc/gdbtoPATHand exportsST_GDBSERVER/ST_PROGRAMMER_BIN(globs, so CubeIDE updates do not break it).gcc/gdbserver.sh <boot|appli>: startsST-LINK_gdbserver -p 61234 --swd --halt -m 1 -k -cp <programmer>(+-elexternal loader forappli). Kills a stale server first.gcc/arm-gdb.sh: wrapper aroundarm-none-eabi-gdb(VS CodemiDebuggerPathcannot 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 aspreLaunchTask), Kill gdbserver, Generate compile_commands.json.launch.json(cppdbg,miDebuggerServerAddress: localhost:61234): Debug Boot (build,load,monitor reset, hw break onmain), Debug Appli (build,loadvia the external loader, reset, stops at Applimain()), Debug Appli (no flash) (reset only).launch.jsonalso has threestlinkgdbtargetconfigs (they needdeviceName: STM32H7S7L8H6H= CMSIS pack variant /Mcu.CPN, anddeviceCore: 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(nogdbserver: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; flashloadnot exercised). Required, git-ignored file.settings/ide.store.jsonat 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"]: afterloadthe extension starts at the ELFReset_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/packsshould containSTMicroelectronics/stm32h7xx_dfp/1.3.0; theST Debug Appli (no flash)variant only has a symbol file, so it setsprogram(the RTOS proxy needs the ELF; without it the proxy dies and GDB times out).settings.json/extensions.json: IntelliSense fromcompile_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 ofAudioEffects_GetParams(), scale, range). Unit-testable on PC.Appli/Core/Src/remote_ble.c+Inc/remote_ble.h: FreeRTOS taskremoteBle(BelowNormal, 3 KB stack) started byRemoteBle_Start()from theRTOS_THREADSuser section ofmain.c. GATT servicec3110000-7e8a-4b1c-9d52-f0a1e4b7c600,Controlchar (...0001, write) andStatechar (...0002, notify). Records are[id u8][value i16 LE]; id0xFFrequests 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 ringst67_log_ring(4 KB, read it in the debugger;st67_log_head= next write position), andST67_ExtiCallback()forwards theSPI_RDYedges (hooked inSTM32TouchController.cpp, the onlyHAL_GPIO_EXTI_Callback).Appli/ST67W6X_Network_Driver/Target/spi_port.c(generated by the pack): onlyUSER CODEsections were edited (pin-name aliasesCHIP_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_applicompiles 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)
- Octaver - Dual-grain pitch shifting (-2, -1, +1, +2 octaves)
- Equalizer - 10-band graphic EQ (31 Hz - 16 kHz, -24..+24 dB)
- Overdrive - Distortion effect
- Saturation - Audio saturation
- Echo - Delay-based echo
- 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 fromCelloEffects.touchgfx. - Custom look is done in user code only (
Appli/TouchGFX/gui/): eachXxxView::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 generatedbuttonWithLabel1..6of Overview are hidden on purpose.
- A new screen must be created in TouchGFX Designer (so
FrontendHeapBaseandFrontendApplicationBaseinclude 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(groupUiRework); the build regenerates texts and fonts (T_<ID>). Typographies:Default20 px,Medium28 px,Large40 px. Wildcard texts:VALUE_M_LEFT(Medium),VALUE_S_CENTER(Default); a character shown in a wildcard must be listed in the typography'sWidgetWildcardCharacters. - Canvas widgets (Circle, Graph...) need a per-screen
CanvasWidgetRenderer::setupBuffer()(seeOverviewView,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
-
DEV_TARGET_NOT_HALTED/Cannot connect to access port 1/ gdbserverTarget 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 bygcc/flash.shand.vscode/launch.json
-
"Upload size is bigger than flash memory size"
- Cause: reading beyond the 64 KB internal flash (0x08000000-0x08010000)
- Solution: use a smaller read size
-
External memory (0x70000000) not accessible
- Cause: External FlashLoader not loaded
- Solution: add
-el gcc/MX66UW1G45G_STM32H7S78-DK.stldr(CLI and gdbserver)
-
Application runs but display is blank
- Cause: Appli not flashed in external memory correctly, or Boot missing
- Solution:
gcc/flash.sh all, thengcc/flash.sh verify
-
ST-Link busy
- Cause: a debug session or STM32CubeProgrammer GUI holds the probe
- Solution: stop it, then retry
-
nulfiles appear in the repo- Cause: Windows
2> nulredirections ingcc/include/cube_programmer.mk(now guarded withifeq ($(OS),Windows_NT))
- Cause: Windows