Repository navigation
perf(tft): clock clk_peri from clk_sys; the panel's SPI at 66.5 MHz - #278
Merged
Merged
Conversation
The panel's SPI has run at 24 MHz in every build, not at the 62.5 MHz that
SIMUT_TFT_SPI_HZ asked for, nor at the 31.25 before it. The framework's
main( ) calls set_sys_clock_khz( 133000, true ) (F_CPU of rpipico and
rpipicow), and the SDK's set_sys_clock_pll( ) answers by moving clk_peri to
the 48 MHz USB PLL (PICO_CLOCK_ADJUST_PERI_CLOCK_WITH_SYS_CLOCK is 0, and the
SDK is prebuilt in libpico.a, out of reach of our flags). The PL022 divides
clk_peri by an even prescale times a post-divider, so its ceiling there is
24 MHz. Read on the bench on 2026-10-15, on a study build: clk_sys 133 MHz,
clk_peri 48 MHz, SPI0 24 MHz after a push; a 320x40 strip 9.5 ms, a
full-screen DMA clear 56.7 ms. v2.1.6's "31.25 -> 62.5 MHz" never reached the
wire: its GRAM readback proved the pixels, not the speed. The read-clock
sweep of 2026-09-19 had already drawn the 48 MHz ladder (10 = 8 and
16 = 20 = 12), and MODELO_DE_RECURSOS R-H18 inferred it on 2026-09-25.
What changes, on the six images that drive the panel (pico_w_release,
pico_w_test, pico_w_test_https, pico_w_asserts, pico2_w_release,
pico_wiretft; the macro is SIMUT_DISPLAY_TFT):
- src/main.cpp: setup( ) clocks clk_peri from clk_sys first, with
clock_configure( clk_peri, 0, CLOCKS_CLK_PERI_CTRL_AUXSRC_VALUE_CLK_SYS,
f, f ), f = clock_get_hz( clk_sys ), and skips it when clk_peri already is
clk_sys (the Pico 2 W: its F_CPU is the SDK's own, so main( ) never calls
set_sys_clock_khz( )). It is `if ( SIMUT_DISPLAY_TFT )`, not an #if, so the
entanglement gate gets no new site there.
- src/simut_config.h: SIMUT_TFT_SPI_HZ 62.5 -> 66.5 MHz, the ceiling of
clk_peri = 133 MHz. On the RP2350 (clk_sys 150 MHz) the same request gives
37.5 MHz (prescale 2, post-divider 2), as 62.5 did; its ceiling would be
75 MHz, not tried. The next rung down on the Pico W is 33.25 MHz; the
manual's 31.25 is no rung of this clock (22.17). The comments that said
"clk_peri at 125 MHz" (simut_config.h, DisplayManager.cpp) are rewritten,
and so are the wire times in DisplayManager_Dashboard.cpp.
- src/display/Pl022Baud.h: the SDK's spi_set_baudrate( ) as a pure function
(prescale, post-divider, baud for a clk_peri and a request).
- GET /api/status: sys.clk_peri and sys.tft_hz, on the panel images only. The
second is read from the PL022 registers in dmaTftOpen( ) after the panel's
transaction begins (spi_get_baudrate( spi0 )); 0 until the first push. Read
on the bench before this change: 48000000 and 24000000. They are macros like
STATUS_SLOT_*: an `if ( SIMUT_DISPLAY_TFT )` there left alpha, Air and
pico_wire two bytes different in handleApiStatus( ) (a cmp operand order),
and those three must stay main's bytes. That is one new #if site:
tools/feature_sprawl.json, SIMUT_DISPLAY_TFT in WebManager_Api.cpp, 0 -> 1.
Nothing before the call configures a clk_peri block. The direct call graph
read from the disassembly of main's six panel images, from every static
initializer (13 to 16 per image), from the SDK's runtime init and from main( )
up to setup( ), reaches no spi_init, uart_init, SerialUART::begin or
SPIClassRP2040::begin; the clock code it reaches is the SDK's own
(runtime_init_clocks and, on the RP2040 images, set_sys_clock_pll from main( )).
The first user is AppManager::setup( )'s uart_init( uart1, 115200), a few
statements after the call; Core 1 starts later still. Indirect calls are
invisible to that walk; the constructors' source was read as well.
Every clk_peri user, before -> after (clk_peri 48 -> 133 MHz; computed with
Pl022Baud.h and the SDK's uart_set_baudrate( ) formula, not measured):
panel write, SPI0 (library begin( ), DMA push) asks 66.5 (was 62.5):
24.000 -> 66.500 MHz
panel GRAM read, SPI0 (screenshot, mirror) asks 12: 12.000 -> 11.083 MHz
XPT2046 touch, SPI0 asks 2: 2.000 -> 1.956 MHz
UART1 boot log, uart_init( uart1, 115200 ) 115176 -> 115201 baud
ENC28J60 (pico_wiretft): not a PL022 user. PicoENC28J60 1.3.1 runs its SPI
in PIO from clk_sys (spi_clkdiv256( spi_hz, clock_get_hz( clk_sys ) )),
asking its 10 MHz default; unchanged.
hardware I2C: clk_sys in the SDK (i2c.c). SIMUT's sensors are PIO. Unchanged.
CYW43 (PIO), PWM, ADC, USB: clk_sys / clk_adc / clk_usb. Unchanged.
Pico 2 W (clk_peri = clk_sys = 150 MHz before and after): 37.5, 10.714,
1.974 MHz, 115207 baud. Unchanged.
Every one is inside its part's range. The read goes about 8 % slower; it was
validated at exactly 12 MHz, so a slower read is on the safe side.
Tests, written first (the plan was written before the code; the first run had
the constant's case red). test_validators 288 -> 300 cases, the suite total
689 -> 701, the three READMEs. On a copy of the header, with the real suite
compiled by g++ and Unity: prescale += 1 fails
test_pl022_prescale_is_even_and_in_range; the post-divider off by one fails 9
cases (the touch's, "never exceeds the request", the 125/133/150 MHz rungs);
`>` as `>=` fails 8 (the constant's ceiling, "a request that is a rung", the
touch); -DSIMUT_TFT_SPI_HZ=62500000u fails exactly the constant's case; the
unmutated copy passes 300 of 300. The model against the SDK's own function,
cut out of the installed framework's spi.c and run over fake registers:
2,607,539 (clk_peri, request) pairs for 12, 48, 100, 125, 133, 150 and 200 MHz,
0 differences.
Sizes, flash used / .bin (main 80c3c88 -> here):
pico_w_release 1,022,516 -> 1,022,732 (+216) / 1,034,548 -> 1,034,764
pico_w_test 1,026,948 -> 1,027,156 (+208) / 1,038,980 -> 1,039,188
pico_w_test_https 1,032,004 -> 1,032,212 (+208) / 1,044,036 -> 1,044,244
pico_w_asserts 1,024,820 -> 1,025,028 (+208) / 1,036,852 -> 1,037,060
pico2_w_release 998,624 -> 998,848 (+224) / 998,648 -> 998,872
pico_wiretft 774,380 -> 774,588 (+208) / 786,416 -> 786,624
pico_wire, pico_w_alpha and pico_w_air are byte for byte main's, by sha256
(3fe223cb, 22ba2fae, 0d6adc66). pico_w_test is left 955 B under the OTA
ceiling (it was 1,163). Budgets, "bin" records and the OTA headroom table
(PLANO_STABLE.md 3.1) moved to match; check_flash_budget.py passes on all
nine and --table agrees.
Not verified on hardware: nothing here ran on a board. The bench checks are
in the pull request: sys.clk_peri 133000000 and sys.tft_hz 66500000 (Pico 2 W:
150000000 and 37500000), a 320x40 strip and a full-screen clear against the
9.5 and 56.7 ms above, a GRAM readback of a still frame at 66.5 MHz (fall back
to -DSIMUT_TFT_SPI_HZ=33250000u if a pixel is wrong), touch, and the boot log
on UART1.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LyLm7Q1o7kCHngE63BnoTx
…list and the resource model state it The documents that quote the clock follow the code. docs/_manual (chapters 2, 3, 26 and 30) and docs/FEATURES in three languages say 66.5 MHz where they said 62.5; chapter 3's compile-time example for a panel that shows stray pixels now passes 33250000u, the next rung (31250000u is no rung of clk_peri = 133 MHz and runs at 22.17 MHz); chapter 2 says the web mirror's read-back runs at about 11 MHz (it asks 12 and gets 11.08); chapter 26 and docs/MANUAL.md list the two new /api/status fields. docs/MANUAL.pt-BR.html is regenerated by tools/build_manual.py from the chapters, and its diff is those lines. MODELO_DE_RECURSOS.md closes R-H18: it inferred on 2026-09-25 that clk_peri was 48 MHz, and the bench read it on 2026-10-15. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LyLm7Q1o7kCHngE63BnoTx
…sured; test: two divider edges pinned Bench, 2026-10-15, plain Pico + ENC28J60 + ILI9341 (the pico_wiretft wiring), one panel module: - this branch's pico_wiretft reports sys.clk_peri 133000000 and sys.tft_hz 66500000 (48000000 and 24000000 read before the change on a study build); - a full frame of six 320x40 strips is pushed in 21.2 ms (57.1 before), a full-screen DMA clear takes 20.6 ms (56.4), timed by a study build of the Angulo UI driver that carries the same two changes; - pixel integrity at 66.5 MHz: five round trips of the calendar's month, 60 strips rewritten, every GRAM readback identical to the first frame, 0 pixels different; - an A/B walk of the original UI by touch, main 80c3c88 against this branch: 19 captures equal, 1 different only in a live humidity reading (a step where both runs had left for the dashboard), 18 skipped in both; the touch and the network (HTTPS over the ENC28J60) worked throughout. Not run on hardware: a Pico W with the panel, the Pico 2 W, the boot log on UART1. Review of a433044..94cac27: - CHANGELOG (both languages) said the bus had run at 24 MHz "in every build since the TFT was added"; measured is one build (arduino-pico 5.6.1) plus the read-clock sweep of 2026-09-19. It now says so, and that the 5.4.x builds, v2.1.6 among them, carry the same clock code unmeasured. - docs/analysis/ESPELHO_DELTA.md is Living (docs/README.md) and still named 62.5 MHz twice; it names 66.5 MHz and the 24 MHz history now. - Two mutants of Pl022Baud.h survived the 300 cases: the product prescale x 256 x request in 32 bits (it passes 2^32 at 2 x 256 x 8,391,750) and a `>= 254` refusal. Two asserts in test_pl022_a_request_below_what_the_divider_makes_is_refused pin both: clean 300 of 300; the 32-bit product fails it ("Expected 2 Was 16"), the `>= 254` fails it ("Expected 254 Was 0"). The case count stays 300. - docs/analysis/MODELO_DE_RECURSOS.md's anchors into simut_config.h moved with the constants (115-147, 147, 190). - simut_config.h's comment says the 66.5 MHz figure was validated on this module and wiring, and what to do on another. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LyLm7Q1o7kCHngE63BnoTx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
perf(tft): clock clk_peri from clk_sys; the panel's SPI at 66.5 MHz
The panel's SPI ran at 24 MHz on the build measured (arduino-pico 5.6.1), whatever
SIMUT_TFT_SPI_HZasked for(62.5 MHz since v2.1.6, 31.25 before); the 5.4.x builds before it carry the same clock code and were not measured.
The framework's
main( )callsset_sys_clock_khz( 133000, true ), and the SDK'sset_sys_clock_pll( )answers bymoving
clk_perito the 48 MHz USB PLL (PICO_CLOCK_ADJUST_PERI_CLOCK_WITH_SYS_CLOCKis 0, and the SDK is prebuilt inlibpico.a). The PL022 dividesclk_periby an even prescale times a post-divider, so the ceiling there is 24 MHz.Measured on the bench, 2026-10-15, study build:
clk_sys133 MHz,clk_peri48 MHz, SPI0 24 MHz after a push(
spi_get_baudrate( spi0 )); a 320x40 strip 9.5 ms, a full-screen DMA clear 56.7 ms. v2.1.6's "31.25 -> 62.5 MHz"did not reach the wire on any build measured: its GRAM readback proved the pixels, not the speed. The read-clock sweep of 2026-09-19 had
already drawn the 48 MHz ladder (10 = 8, 16 = 20 = 12) and
MODELO_DE_RECURSOSR-H18 inferred it on 2026-09-25.Change (six images:
pico_w_release,pico_w_test,pico_w_test_https,pico_w_asserts,pico2_w_release,pico_wiretft)src/main.cpp:setup( )clocksclk_perifromclk_sysas its first statement, skipped when it already is(the Pico 2 W never calls
set_sys_clock_khz( )).if ( SIMUT_DISPLAY_TFT ), a constant condition: alpha, Air andthe plain Wire compile none of it.
SIMUT_TFT_SPI_HZ= 66.5 MHz, the ceiling of 133 MHz. On the RP2350 (150 MHz) the same request gives 37.5 MHz(prescale 2, post-divider 2), as 62.5 did; noted at the constant. One constant, as before. Next rung down: 33.25 MHz.
src/display/Pl022Baud.h: the SDK'sspi_set_baudrate( )as a pure function, with 12 native cases.GET /api/statussys.clk_periandsys.tft_hz(panel images only).tft_hzis read from the PL022 registers indmaTftOpen( )after the panel's transaction begins, 0 until the first push. Added, nothing renamed.DisplayManager_Dashboard.cpp;CHANGELOG(Unreleased, both languages) corrects v2.1.6's claim; the manual,FEATURESandMODELO_DE_RECURSOSfollow(second commit;
MANUAL.pt-BR.htmlis regenerated). A third commit answers a review: the bench results, the claimnarrowed to the builds measured (one build of arduino-pico 5.6.1; the 5.4.x builds, v2.1.6 among them, have the same
clock code and were not measured),
docs/analysis/ESPELHO_DELTA.md(Living) at 66.5 MHz, and two edges of thedivider pinned by asserts (each was a surviving mutant).
Nothing before the call configures a
clk_periblockDirect call graph from the disassembly of main's six panel images, from every static initializer (13 to 16 per image),
the SDK's runtime init and
main( )up tosetup( ): no path tospi_init,uart_init,SerialUART::beginorSPIClassRP2040::begin. The clock code reached is the SDK's own. (The call graph, the mutant runs and the differential run against the SDKare kept outside the repository; this description is their record.) The first user is
AppManager::setup( )'suart_init( uart1, 115200 ), a few statements after; Core 1 starts later. Indirect calls areinvisible to that walk; the constructors' source was read too.
Every
clk_periuser, before -> after (computed, not measured)pico_wiretft)clk_sys: unchangedclk_sys/clk_adc/clk_usbPico 2 W (
clk_peri=clk_sys= 150 MHz before and after): 37.5, 10.714 and 1.974 MHz, 115207 baud. Unchanged.The ENC28J60 is not a PL022 user: PicoENC28J60 1.3.1 runs its SPI in a PIO state machine clocked from
clk_sys(spi_clkdiv256( spi_hz, clock_get_hz( clk_sys ) )), andenc28j60Config( )sets nospi_hz. It is not aPL022 user, so it has no PL022 case and the network does not need a retest for this change.
Tests (written first)
test_validators288 -> 300 cases, suite total 689 -> 701, the READMEs follow (check_readme_numbers.pyagrees).Red first: with the old constant the constant's case failed.
prescale += 1failstest_pl022_prescale_is_even_and_in_range; post-divider off by one fails 9 cases (the touch's,"never exceeds the request" among them);
>as>=fails 8 (the constant's ceiling, "a request that is a rung" amongthem);
-DSIMUT_TFT_SPI_HZ=62500000ufails exactly the constant's case; unmutated 300 of 300.spi_set_baudrate( )cut out of the installedspi.c, run over fake registers,2,607,539 (clock, request) pairs for 12, 48, 100, 125, 133, 150 and 200 MHz: 0 differences.
Sizes and bytes
.binpico_w_releasepico_w_testpico_w_test_httpspico_w_assertspico2_w_releasepico_wiretftpico_wire,pico_w_alphaandpico_w_airare byte for byte main's (sha2563fe223cb,22ba2fae,0d6adc66).The first cut had the status fields behind
if ( SIMUT_DISPLAY_TFT ); that left those three two bytes different inhandleApiStatus( )(acmpoperand order), so they are macros likeSTATUS_SLOT_*. The price is one#ifsite:tools/feature_sprawl.json,SIMUT_DISPLAY_TFTinWebManager_Api.cpp, 0 -> 1.CI gates run locally (
ci_gates_local.py): 47 of 47 steps pass (the eleven native suites, the sanitizer pass, cppcheck, 60 s of fuzzing, the README numbers, the sprawl mark, the OTA table).check_flash_budget.pypasses on all nine images and--tableagrees.On the bench (2026-10-15, plain Pico + ENC28J60 + ILI9341, the
pico_wiretftwiring, one panel module)sys.clk_peripico_wiretftsys.tft_hzpico_wiretftThe two timing rows come from a study build of the Ângulo UI driver (branch
feat/ui-drivers, not on main) that timeseach strip and carries the same two changes (
clk_perifromclk_sys, 66.5 MHz); the original UI has no per-striptimer. Pixel integrity at 66.5 MHz, read back from the panel's GRAM (the 11 MHz read path, its own transaction):
five round trips of the calendar's month (60 strips rewritten), every readback identical to the first frame, 0
pixels different. An A/B walk of the original UI by touch, main 80c3c88 against this branch, both on
pico_wiretft(
ab_original.py): 19 captures equal, 1 different only in a live humidity reading (a step where both runs had left forthe dashboard), 18 skipped in both, as in the earlier A/B of 2026-10-15. The touch and the network (HTTPS over the
ENC28J60, every capture of both walks) worked throughout.
Not run on hardware: a Pico W with the panel (
pico_w_release,pico_w_test) and the Pico 2 W (pico2_w_release),and the boot log on UART1. They share the clock code; the Pico 2 W's
clk_periis alreadyclk_sys, so its requestgives 37.5 MHz as before.
66.5 MHz stays one constant; the next rung down is
-DSIMUT_TFT_SPI_HZ=33250000u.🤖 Generated with Claude Code
https://claude.ai/code/session_01LyLm7Q1o7kCHngE63BnoTx