mirror of
https://github.com/mikecarper/MeshCore.git
synced 2026-10-23 19:12:23 +00:00
Extend nRF52 radio profile timing across RTC wraps
This commit is contained in:
1 parent
ca67494f8c
commit
8038715ffe
5 files changed
+65
-27
No files matched your search
@@ -589,6 +589,7 @@ jobs:
|
||||
python3 -B test/test_radio_profile_spiffs_missing.py -v
|
||||
python3 -B test/test_radio_reply_mutation.py
|
||||
python3 -B test/test_radio_profile_scan.py
|
||||
python3 -B test/test_radio_profile_clock.py -v
|
||||
python3 -B test/test_radio_profile_runtime_recommendation.py -v
|
||||
python3 -B test/test_radio_profile_idle_dispatcher.py -v
|
||||
python3 -B test/test_radio_rxps_rejection.py -v
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -18,6 +18,7 @@ HARNESS = r'''
|
||||
#define MESH_ENABLE_MORSE_RX 1
|
||||
struct Rtc { uint32_t COUNTER = 200; } rtc;
|
||||
#define NRF_RTC2 (&rtc)
|
||||
uint32_t profile_clock_raw=0, profile_clock_ticks=0;
|
||||
struct NRF52Board {
|
||||
static bool ready;
|
||||
static bool canIdleRadioProfile() { return ready; }
|
||||
@@ -54,6 +55,13 @@ int main() {
|
||||
assert(!w.getProfileIdleDelay(delay) && delay==0);
|
||||
#else
|
||||
assert(w.getProfileIdleDelay(delay) && delay==26);
|
||||
// Identical raw RTC values after a full hardware wrap must not resurrect
|
||||
// an old idle proof. Both stamps use the software-extended tick domain.
|
||||
profile_clock_ticks=0x01000000UL;
|
||||
assert(!w.getProfileIdleDelay(delay));
|
||||
w._profile_visit_stamp=w._profile_idle_stamp=0x01000064UL;
|
||||
assert(w.getProfileIdleDelay(delay) && delay==26);
|
||||
profile_clock_ticks=0;w=RadioLibWrapper();
|
||||
// Exact early-wake boundary, rounding, already expired, and RTC wrap.
|
||||
for (uint32_t remaining=0; remaining<=48; ++remaining) {
|
||||
rtc.COUNTER=100+1000-remaining;
|
||||
@@ -63,7 +71,7 @@ int main() {
|
||||
assert(w.getProfileIdleDelay(delay) && delay==1);
|
||||
rtc.COUNTER=1101;
|
||||
assert(!w.getProfileIdleDelay(delay));
|
||||
w._profile_visit_stamp=w._profile_idle_stamp=0x00fffff0;
|
||||
w._profile_visit_stamp=w._profile_idle_stamp=0xfffffff0;
|
||||
rtc.COUNTER=0x10;
|
||||
assert(w.getProfileIdleDelay(delay) && delay==29);
|
||||
w=RadioLibWrapper(); rtc.COUNTER=200;
|
||||
|
||||
@@ -8,9 +8,8 @@ import unittest
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
SOURCE = (ROOT / "src/helpers/radiolib/RadioLibWrappers.cpp").read_text()
|
||||
CLOCK = "static void syncProfileClock(bool dual_profile) {" + SOURCE.split(
|
||||
"static void syncProfileClock(bool dual_profile) {", 1
|
||||
)[1].split("\nstatic\n#if defined(ESP8266)", 1)[0]
|
||||
CLOCK = SOURCE[SOURCE.index("// Extend the 24-bit RTC counter"):].split(
|
||||
"\nstatic\n#if defined(ESP8266)", 1)[0]
|
||||
|
||||
HARNESS = r'''
|
||||
#include <cassert>
|
||||
@@ -28,22 +27,40 @@ int main() {
|
||||
assert(profileElapsedUs(12, 42, false) == 30);
|
||||
#if defined(NRF52_PLATFORM)
|
||||
rtc.COUNTER = 100;
|
||||
assert(profileTimestamp(true) == 100);
|
||||
assert(profileTimestamp(true) == 0);
|
||||
assert(rtc.TASKS_STOP == 1 && rtc.PRESCALER == 0);
|
||||
assert(rtc.TASKS_CLEAR == 1 && rtc.TASKS_START == 1);
|
||||
assert(rtc.TASKS_CLEAR == 0 && rtc.TASKS_START == 1);
|
||||
rtc.TASKS_CLEAR = rtc.TASKS_START = rtc.TASKS_STOP = 0;
|
||||
assert(profileTimestamp(true) == 100);
|
||||
assert(profileTimestamp(true) == 0);
|
||||
assert(rtc.TASKS_CLEAR == 0 && rtc.TASKS_START == 0 && rtc.TASKS_STOP == 0);
|
||||
assert(profileElapsedUs(0, 32768, true) == 1000000);
|
||||
assert(profileElapsedUs(0x00fffff0, 0x10, true) == 977);
|
||||
assert(profileElapsedUs(0xfffffff0, 0x10, true) == 977);
|
||||
uint32_t stamp=0;
|
||||
for(unsigned second=1;second<=532;++second) {
|
||||
rtc.COUNTER=(rtc.COUNTER+32768)&0x00ffffffUL;
|
||||
stamp=profileTimestamp(true);
|
||||
assert(stamp==second*32768UL);
|
||||
assert(profileElapsedUs(0,stamp,true)==second*1000000UL);
|
||||
}
|
||||
assert(stamp>0x00ffffffUL); // A valid visit can exceed the hardware period.
|
||||
assert(profileElapsedUs(0,UINT32_MAX,true)==UINT32_MAX);
|
||||
syncProfileClock(false);
|
||||
assert(rtc.TASKS_STOP == 1 && profileTimestamp(false) == 1234);
|
||||
rtc.TASKS_CLEAR = rtc.TASKS_START = rtc.TASKS_STOP = 0;
|
||||
syncProfileClock(false);
|
||||
assert(rtc.TASKS_CLEAR == 0 && rtc.TASKS_START == 0 && rtc.TASKS_STOP == 0);
|
||||
rtc.COUNTER = 200;
|
||||
assert(profileTimestamp(true) == 200);
|
||||
assert(rtc.TASKS_CLEAR == 1 && rtc.TASKS_START == 1);
|
||||
assert(profileTimestamp(true) == 0);
|
||||
assert(rtc.TASKS_CLEAR == 0 && rtc.TASKS_START == 1);
|
||||
for(unsigned chunk=0;chunk<511;++chunk) {
|
||||
rtc.COUNTER=(rtc.COUNTER+256UL*32768)&0x00ffffffUL;
|
||||
profileTimestamp(true);
|
||||
}
|
||||
rtc.COUNTER=(rtc.COUNTER+255UL*32768)&0x00ffffffUL;
|
||||
const auto before=profileTimestamp(true);assert(before==0xffff8000UL);
|
||||
rtc.COUNTER=(rtc.COUNTER+32768)&0x00ffffffUL;
|
||||
const auto wrapped=profileTimestamp(true);assert(wrapped==0);
|
||||
assert(profileElapsedUs(before,wrapped,true)==1000000UL);
|
||||
#else
|
||||
assert(profileTimestamp(true) == 1234);
|
||||
assert(profileElapsedUs(12, 42, true) == 30);
|
||||
|
||||
@@ -26,7 +26,7 @@ static uint64_t elapsed_us;
|
||||
uint32_t micros() { return (uint32_t)elapsed_us; }
|
||||
static bool profile_clock_active=false;
|
||||
void syncProfileClock(bool dual_profile) { profile_clock_active=dual_profile; }
|
||||
uint32_t profileTimestamp(bool) { return micros(); }
|
||||
uint32_t profileTimestamp(bool dual) { syncProfileClock(dual); return micros(); }
|
||||
uint32_t profileElapsedUs(uint32_t since, uint32_t now, bool) { return now - since; }
|
||||
uint32_t millis() { return (uint32_t)(elapsed_us / 1000); }
|
||||
struct Chip { bool standbyXOSC=false; int standby() { return 0; } };
|
||||
|
||||
Reference in new issue
Block a user