Extend nRF52 radio profile timing across RTC wraps

This commit is contained in:
mikecarper committed 2026-10-19 18:53:33 -07:00
1 parent ca67494f8c
commit 8038715ffe
5 files changed
+65 -27

No files matched your search

+28 -16
View File
@@ -56,12 +56,13 @@ static RadioInterruptEvents radio_interrupt_events[RADIO_INTERRUPT_SLOT_COUNT];
static RadioLibWrapper* radio_interrupt_owners[RADIO_INTERRUPT_SLOT_COUNT] = {};
// The Adafruit nRF52 core's micros() falls back to 1024 Hz RTOS ticks when
// DWT is disabled. A sub-millisecond retune then appears to take exactly
// 977 us (or 1953 us across two ticks), making the learned switch allowance
// and scan dwell inaccurate. On nRF52, RTC2 provides a 32768 Hz profile clock
// without changing micros() globally or holding the high-frequency clock on.
// Keep it stopped in the ordinary single-profile mode.
// Extend the 24-bit RTC counter on each loop poll. A valid explicit preamble
// can require a visit longer than its 512-second hardware wrap period.
#if defined(NRF52_PLATFORM)
static uint32_t profile_clock_raw = 0;
static uint32_t profile_clock_ticks = 0;
#endif
static void syncProfileClock(bool dual_profile) {
#if defined(NRF52_PLATFORM)
static bool active = false;
@@ -69,7 +70,10 @@ static void syncProfileClock(bool dual_profile) {
if (dual_profile) {
NRF_RTC2->TASKS_STOP = 1;
NRF_RTC2->PRESCALER = 0;
NRF_RTC2->TASKS_CLEAR = 1;
// Keep the retained hardware count: an asynchronous clear could otherwise
// look like a wrap before its first poll.
profile_clock_raw = NRF_RTC2->COUNTER & 0x00ffffffUL;
profile_clock_ticks = 0;
NRF_RTC2->TASKS_START = 1;
} else {
NRF_RTC2->TASKS_STOP = 1;
@@ -83,7 +87,12 @@ static void syncProfileClock(bool dual_profile) {
static uint32_t profileTimestamp(bool dual_profile) {
syncProfileClock(dual_profile);
#if defined(NRF52_PLATFORM)
if (dual_profile) return NRF_RTC2->COUNTER;
if (dual_profile) {
const uint32_t raw = NRF_RTC2->COUNTER & 0x00ffffffUL;
profile_clock_ticks += (raw - profile_clock_raw) & 0x00ffffffUL;
profile_clock_raw = raw;
return profile_clock_ticks;
}
#endif
return micros();
}
@@ -91,10 +100,9 @@ static uint32_t profileTimestamp(bool dual_profile) {
static uint32_t profileElapsedUs(uint32_t since, uint32_t now, bool dual_profile) {
#if defined(NRF52_PLATFORM)
if (!dual_profile) return now - since;
// RTC2 is 24-bit and wraps every 512 seconds. Active scanning samples it
// many times per second; a freshly configured profile gets a new visit stamp.
const uint32_t ticks = (now - since) & 0x00ffffffUL;
return static_cast<uint32_t>((uint64_t(ticks) * 15625 + 511) >> 9);
const uint32_t ticks = now - since;
const uint64_t elapsed = (uint64_t(ticks) * 15625 + 511) >> 9;
return elapsed > UINT32_MAX ? UINT32_MAX : static_cast<uint32_t>(elapsed);
#else
(void) dual_profile;
return now - since;
@@ -177,7 +185,7 @@ void RadioLibWrapper::begin() {
#if MESH_ENABLE_TX_BEACONS
if (_beacon_owned) return;
#endif
syncProfileClock(_profiles.enabled());
(void)profileTimestamp(_profiles.enabled());
#ifdef USE_CC310_HW_CRYPTO
// Initialize CryptoCell once from normal task context. The session helper
// still initializes lazily if an earlier crypto operation runs first.
@@ -800,7 +808,7 @@ void RadioLibWrapper::serviceProfileScan() {
#if defined(NRF52_PLATFORM)
_profile_idle_visit_ticks = 0; // Every deferred/restarted scan fails closed.
#endif
syncProfileClock(_profiles.enabled());
(void)profileTimestamp(_profiles.enabled());
if (_cw_active) return;
if (!_params_valid || (_profile_retry_at && (int32_t)(millis() - _profile_retry_at) < 0)) return;
_profile_retry_at = 0;
@@ -942,7 +950,11 @@ bool RadioLibWrapper::getProfileIdleDelay(uint32_t& delay_ms) const {
#if MESH_ENABLE_MORSE_RX
if (_morse_rx_owned) return false;
#endif
const uint32_t elapsed = (NRF_RTC2->COUNTER - _profile_idle_stamp) & 0x00ffffffUL;
// Use the extended domain without mutating the clock in this final sleep
// check. The scanner already sampled it before publishing this idle proof.
const uint32_t stamp = profile_clock_ticks
+ ((NRF_RTC2->COUNTER - profile_clock_raw) & 0x00ffffffUL);
const uint32_t elapsed = stamp - _profile_idle_stamp;
// RTC1 wakes the ready sketch task at 1024 Hz (32 RTC2 ticks). Retain
// another 16 ticks (488 us) for return-to-loop work, validated on RAK3401.
// Short visits poll; neither a new timer nor RF duty cycling is needed.
@@ -1868,7 +1880,7 @@ void RadioLibWrapper::loop() {
#if MESH_ENABLE_TX_BEACONS
if (_beacon_owned) return;
#endif
syncProfileClock(_profiles.enabled());
(void)profileTimestamp(_profiles.enabled());
#if MESH_ENABLE_CARRIER_TOOLS || MESH_ENABLE_MORSE_RX
if (serviceCarrierWave()) return;
#endif