TouchGFX Integration
The UI is built with TouchGFX Designer's Model/View/Presenter (MVP) pattern. This page covers how that pattern is wired to the rest of the firmware, which files are Designer-generated versus hand-owned, and how a rendered frame actually reaches the panel.
Model, ModelListener, Presenter, View
Model (TouchGFX/gui/src/model/Model.cpp) is the single source of truth for everything TouchGFX-side. FrontendApplication::handleTickEvent() calls model.tick() once per frame, driven by the framework's own vsync-paced tick, roughly 60 Hz. Each tick, Model::tick():
- Reads
can_input_data(populated bycanRxTask, see CAN) into a local snapshot. - Advances lap timing state if a lap is active (see Lap Tracking).
memcmps the new CAN snapshot against the previous one, and only callsmodelListener->updateCANData(currentData)if something actually changed.- Always calls
modelListener->updateUIData(ui_data), since lap time and other UI-only fields change every frame regardless of CAN traffic.
Each active screen's Presenter implements ModelListener and receives those calls, then pushes the relevant values down into its View through plain setter methods (view.setKmh(...), view.setSoc(...), and so on). Screens never talk to can_input_data or ui_data directly; they only see what their presenter forwards.
The reverse direction (driver touches a button) goes through the same Presenter, but starting from the View's touch callback, which calls a Model setter (setAppsRecalibrateFlag, setSlipControlMode, and so on). Those setters write ui_data and call notifyButtonUpdate(), which wakes canTxTask. See CAN for what happens from there.
Generated vs. hand-owned files
TouchGFX Designer owns everything under TouchGFX/generated/ and regenerates it whenever you hit "Generate Code" in the Designer. Never hand-edit those files; changes will be silently lost on the next regen. Board-specific behavior lives in a parallel set of hand-owned files that subclass or wrap the generated ones:
| Generated (Designer-owned) | Hand-owned (ours) |
|---|---|
TouchGFX/generated/gui_generated/** | TouchGFX/gui/** (screen View/Presenter subclasses, Model) |
TouchGFX/target/generated/TouchGFXGeneratedHAL.cpp/.hpp | TouchGFX/target/TouchGFXHAL.cpp/.hpp (the actual FMC/GPDMA panel driver) |
TouchGFXGeneratedHAL picks the framebuffer strategy and wires the low-level callbacks the framework expects (touchgfxDisplayDriverTransmitBlock, touchgfxDisplayDriverShouldTransferBlock, and so on); TouchGFXHAL implements what those callbacks actually do on this board's hardware. See FMC Hardware Bring-Up for that implementation.
Screens
Seven screens are registered with the framework. See Screens for what each one shows and how the driver interacts with it.
Rendering strategy: partial framebuffer, TE-paced
This board uses TouchGFX's partial framebuffer strategy (REFRESH_STRATEGY_PARTIAL_FRAMEBUFFER), not a full 320x480 framebuffer in RAM. A small block allocator (ManyBlockAllocator<12800, 4, 2>, 4 blocks of 12800 bytes each) holds only the dirty regions currently being rendered and transferred; the rest of the frame stays on the panel's own GRAM until touched again.
Each block is handed to the panel over GPDMA (LCD_IO_SendDataDMA), and the whole pipeline is paced by the panel's tearing-effect (TE) line rather than a fixed timer:
- TE rising edge (
HAL_GPIO_EXTI_Rising_Callback, PF13): signals vsync to the TouchGFX engine and resets a hardware timer (TIM7) used to estimate how far the panel has scanned. - TE falling edge: starts TIM7 counting, so its counter value approximates "how many lines the panel has drawn since the last TE pulse."
touchgfxDisplayDriverShouldTransferBlock()uses that TIM7 counter to only start transmitting a block once the panel has scanned past it, avoiding tearing.waitUntilCanTransferBlock()sleeps in small increments if the panel hasn't caught up yet.
This is the same strategy used by the known-good reference project this driver was built against; the only board-specific differences are the TE pin (PF13 here, PE13 on the reference) and the FMC bus width (see FMC Hardware Bring-Up).
Do not bypass TE pacing "to fix corruption"
It's tempting, when the display looks wrong, to rip out the TE-paced partial-framebuffer strategy in favor of a single full framebuffer with a synthetic vsync. That was tried on this board and made things worse: TouchGFX rendered, but the image was heavily corrupted, because the actual root cause was unrelated (see FMC Hardware Bring-Up). Confirm TE is actually toggling on the scope before concluding it's dead.
