From bd449ef0dba93dadd20ed1e622cfcb8bb9744a02 Mon Sep 17 00:00:00 2001 From: Andy Shinn Date: Wed, 9 Sep 2026 23:38:33 -0500 Subject: [PATCH 1/2] Publish the core clock SystemInit selects, and scale micros() by it SystemCoreClock is a .data variable initialised to __SYSTEM_CLOCK_DEFAULT (64 MHz) and nothing corrected it, while SystemInit had already programmed OSCILLATORS.PLL.FREQ to 128 MHz. Measured on an nRF54LM20A with DWT CYCCNT over a millis()-timed second: 128.15 MHz. The startup file now calls SystemCoreClockUpdate(), which reads PLL.CURRENTFREQ, between SystemInit and __libc_init_array, so the value is right before a global constructor can ask, and .data has already been copied so it survives. micros() divided CYCCNT by a literal 64 and so returned twice the elapsed time while the cycle counter runs. It divides by SystemCoreClock / 1000000 now. With the clock corrected, nrfx_coredep_delay_us() runs long until the RRAM cache is on: its loop is costed at 3 cycles per iteration and takes about 8 from RRAM. Long is the safe direction for a delay. [cvaldess: reduced at review to the clock and micros() parts of 6c76f98. The DWT delay layer, the weak SystemCoreClockUpdate() stub and the comment commits that followed it are dropped: with the cache on (next commit) the stock nrfx loop is exact.] --- cores/nRF5/delay.h | 3 ++- cores/nRF5/startup_nrf54l15.S | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/cores/nRF5/delay.h b/cores/nRF5/delay.h index 404334b..24421a7 100644 --- a/cores/nRF5/delay.h +++ b/cores/nRF5/delay.h @@ -57,7 +57,8 @@ static inline uint32_t micros( void ) __attribute__((always_inline)); static inline uint32_t micros( void ) { // Use DWT cycle count if it is enabled, otherwise use rtos tick - return dwt_enabled() ? (DWT->CYCCNT / 64) : tick2us(xTaskGetTickCount()); + return dwt_enabled() ? (DWT->CYCCNT / (SystemCoreClock / 1000000UL)) + : tick2us(xTaskGetTickCount()); } /** diff --git a/cores/nRF5/startup_nrf54l15.S b/cores/nRF5/startup_nrf54l15.S index f00b11a..b4356ea 100644 --- a/cores/nRF5/startup_nrf54l15.S +++ b/cores/nRF5/startup_nrf54l15.S @@ -347,6 +347,11 @@ LoopClearBss: RunMain: bl SystemInit + + /* SystemCoreClock starts at its 64 MHz default, but SystemInit has set the + * PLL to 128 MHz. Correct it before global constructors can time anything. */ + bl SystemCoreClockUpdate + bl __libc_init_array cpsie i bl main From c8ba60ba95763db113871afbc1744ae89079b07a Mon Sep 17 00:00:00 2001 From: CValdesS Date: Thu, 15 Oct 2026 17:27:27 +0200 Subject: [PATCH 2/2] Turn the RRAM instruction cache on in Reset_Handler nRF54L leaves CACHE off after reset and the core never enabled it, so every instruction and constant came straight from RRAM. nRF Connect SDK enables it in its SoC init. With it on, the crypto Meshtastic runs is 3.4-3.9x faster on an nRF54L15 at 128 MHz, as on an nRF54LM20: X25519 113 -> 28.6 ms, Ed25519 verify 330 -> 84 ms, AES-128-CTR on 240 bytes 2.2 -> 0.64 ms. It goes on right after SystemCoreClockUpdate() and before __libc_init_array, so global constructors run cached too. From the cache, the nrfx_coredep_delay_us() loop costs the 3 cycles per iteration it is costed at, so with SystemCoreClock corrected the stock delay is exact. Measured on an nRF54L15-DK against the GRTC, interrupts masked: delayMicroseconds(10/100/1000) took 10/100/995 us, against 14/134/1327 us on master. The 0.5% left is the CPU clock itself (the bench sketch runs from HFINT): micros() on CYCCNT reads the same 0.5% fast against the GRTC. CPU writes to RRAM invalidate the cached line, and RRAMC commits its write buffer before a read of a pending unit, so stored data stays coherent. --- cores/nRF5/startup_nrf54l15.S | 5 +++++ cores/nRF5/wiring.c | 7 +++++++ 2 files changed, 12 insertions(+) diff --git a/cores/nRF5/startup_nrf54l15.S b/cores/nRF5/startup_nrf54l15.S index b4356ea..6cb6860 100644 --- a/cores/nRF5/startup_nrf54l15.S +++ b/cores/nRF5/startup_nrf54l15.S @@ -352,6 +352,11 @@ RunMain: * PLL to 128 MHz. Correct it before global constructors can time anything. */ bl SystemCoreClockUpdate + /* The RRAM cache is off after reset. On before the global constructors, so all + * code runs cached, which is also what nrfx_coredep_delay_us() is costed for + * (3 cycles per loop iteration; about 8 from RRAM). */ + bl nrf54_cache_enable + bl __libc_init_array cpsie i bl main diff --git a/cores/nRF5/wiring.c b/cores/nRF5/wiring.c index c7fb808..e8f2114 100644 --- a/cores/nRF5/wiring.c +++ b/cores/nRF5/wiring.c @@ -19,6 +19,7 @@ #include "Arduino.h" #include "nrf.h" +#include "nrf_cache.h" #include "nrf_nvic.h" #include "sd_isr.h" @@ -32,6 +33,12 @@ nrf_nvic_state_t nrf_nvic_state; uint32_t bootloaderVersion = 0; static uint32_t _reset_reason = 0; +// Called from Reset_Handler (startup_nrf54l15.S), before the global constructors +void nrf54_cache_enable( void ) +{ + nrf_cache_enable(NRF_ICACHE); +} + void init( void ) { _reset_reason = NRF_RESET->RESETREAS;