Use precise T1000-E dual-profile timing
This commit is contained in:
1 parent
960936183c
commit
d66b9336a9
11 files changed
+142
-74
No files matched your search
@@ -2,12 +2,15 @@
|
||||
|
||||
Historical validation record: the nominal 600 us policy described below has
|
||||
been replaced by the activation self-test and 10% guarded measured budget.
|
||||
The later automatic policy also removed the pair-specific 88-symbol SF8/500
|
||||
floor and the fixed 300 us loop allowance; the older calculations below remain
|
||||
as test history.
|
||||
See [radio profiles](radio_profiles.md#preamble) for
|
||||
current behavior.
|
||||
|
||||
Implemented in the `keymindCascade` working tree, 2026-09-14. The initial
|
||||
4.1/4000-us implementation record below is retained as history; the
|
||||
[4.6/300-us policy update](#automatic-46-chirp--300-us-policy-update) is now current.
|
||||
[4.6/300-us policy update](#automatic-46-chirp--300-us-policy-update) was a prior stage.
|
||||
No radios were flashed or RF experiments started for either function-only change.
|
||||
|
||||
## Initial implementation scope
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
|
||||
Historical validation record: the nominal 600 us switch allowance below has
|
||||
been replaced by the activation self-test and a 10% guarded measured budget.
|
||||
The current automatic policy also removed the fixed 300 us loop allowance and
|
||||
the pair-specific SF8/500 88-symbol floor. Values below are retained as bench
|
||||
history, not current automatic defaults.
|
||||
See [radio profiles](radio_profiles.md#preamble) for current behavior.
|
||||
|
||||
The shared `RadioProfiles` function uses a 4.6-symbol minimum for both logical
|
||||
|
||||
+34
-20
@@ -300,30 +300,31 @@ profile**, then spends the remaining budgeted time on the faster profile,
|
||||
also with a **4.6-symbol floor**. Slower
|
||||
means a longer LoRa symbol (`2^SF / bandwidth`), regardless of profile number.
|
||||
The slower preamble sets the return deadline. The calculation allows two slow
|
||||
visits within that preamble, reserving the self-tested switch time per hop and 0.3 ms of main-loop
|
||||
margin. Fast-channel transmit preambles include sixteen acquisition symbols.
|
||||
For a primary SF7 / 62.5 kHz profile with a *measured* 600 us guarded switch allowance:
|
||||
visits within that preamble, reserving the self-tested, 10%-guarded switch time
|
||||
per hop. There is no extra fixed main-loop allowance. Fast-channel transmit
|
||||
preambles include sixteen acquisition symbols.
|
||||
For a primary SF7 / 62.5 kHz profile with a 763 us measured maximum switch
|
||||
(840 us after the 10% guard, from the BLE-on T1000-E trial):
|
||||
|
||||
| Secondary profile | Automatic secondary preamble | Previously tested explicit preamble | Primary preamble |
|
||||
| --- | ---: | ---: | ---: |
|
||||
| SF9 / 500 kHz | 32 | 32 | 32 |
|
||||
| SF8 / 500 kHz | 88 | 64 | 32 |
|
||||
| SF8 / 500 kHz | 40 | 64 | 32 |
|
||||
| SF7 / 500 kHz | 64 | 80 | 32 |
|
||||
|
||||
With preamble 32 on that slower profile, its visit is 9.421 ms and the faster
|
||||
visit is 21.847 ms, plus the actual switching time. Increasing the slow preamble
|
||||
visit is 21.667 ms, plus the actual switching time. Increasing the slow preamble
|
||||
also increases the time available on the fast channel. If the symbols are equal
|
||||
in length, the primary profile goes first. Detected packets hold the current
|
||||
channel until reception finishes.
|
||||
|
||||
The SF8 / 500 kHz value also retains the measured 88-symbol floor from the
|
||||
production V4/XIAO tests, where 72 and 80 each missed a packet.
|
||||
The explicit values come from the
|
||||
[production validation](radio_profiles_validation.md): the final fast-channel
|
||||
attempts each received 50/50 packets at 4.8 slow symbols. Automatic values use
|
||||
the switching and loop-jitter allowances below.
|
||||
The SF8 / 500 kHz value is calculated from the same formula as every other
|
||||
profile; the older pair-specific 88-symbol floor has been removed. The explicit
|
||||
values come from [historical production validation](radio_profiles_validation.md):
|
||||
the final fast-channel attempts each received 50/50 packets at 4.8 slow symbols.
|
||||
Automatic values use the measured switch allowance below.
|
||||
Those explicit-preamble tests used the older 4.8-symbol policy; they do not
|
||||
validate the new 4.6-symbol slow dwell / 0.3 ms reserve. The
|
||||
validate the newer 4.6-symbol slow dwell. The
|
||||
[4.6/0.3 ms two-profile bench](radio_dwell_policy_validation.md#hardware-context) received 197/200
|
||||
at SF7/62.5 + SF8/500 with explicit 32-symbol preambles. The newer four-channel
|
||||
5.1/6.1/7.7 tests also had misses, so this is a timing policy, not a claim of zero packet loss.
|
||||
@@ -338,13 +339,27 @@ receiver's setting does not lengthen packets sent by other nodes.
|
||||
`get radio2.timing` (alias `get radio.timing`) reports the active pair in
|
||||
`radio,radio2` order: dwell in chirps, recommended preambles in symbols, and
|
||||
the switching/loop allowances. Before calibration it reports `timing self-test
|
||||
pending` and the provisional preambles. For SF7/62.5 on `radio` and SF7/500 on
|
||||
`radio2` after a 600 us guarded measurement:
|
||||
pending` and the provisional preambles. On the T1000-E, the Full Companion now
|
||||
uses RTC2 for both retune timing and scan visits (about 31 us resolution).
|
||||
Previously, the nRF52 `micros()` fallback counted 1,024 Hz RTOS ticks: a real
|
||||
sub-millisecond retune could be reported as exactly 977 us, and two ticks as
|
||||
1,953 us. Those old numbers are timer artifacts, not measured RF costs.
|
||||
For SF7/62.5 on `radio` and SF8/500 on `radio2`, one live BLE-on read returned:
|
||||
|
||||
```text
|
||||
> chirps=4.60,85.34; need=32,64; switch=600us; loop=300us (estimate); test=4/4,4/4 ready; WARN recommended preamble: radio2=64
|
||||
> chirps=4.60,42.84; need=32,40; switch=706us; loop=0us; test=4/4,4/4 ready; WARN recommended preamble: radio2=40
|
||||
```
|
||||
|
||||
Later good hops in another BLE-on trial raised the measured maximum to 763 us
|
||||
(840 us with the 10% guard); the recommendation stayed at 40 symbols.
|
||||
|
||||
The measured switch duration is wall time, so an interrupt during the hop is
|
||||
included. Bluetooth's SoftDevice may still preempt application code; this
|
||||
clock correction does not stop it. Ordinary Companion/USB work in the main
|
||||
loop does not run inside the synchronous retune, but it can delay when the
|
||||
next scan visit begins. The zero fixed loop allowance is not a guarantee of
|
||||
lossless reception under arbitrary application load.
|
||||
|
||||
`WARN recommended preamble` identifies **each** profile whose recommendation
|
||||
exceeds the standard 32 symbols or its explicitly selected value. For example,
|
||||
with SF7/62.5 on the primary profile, `set radio2 910.5,500,7,5,rx,32`
|
||||
@@ -383,14 +398,13 @@ T[i] = 2^SF[i] * 1000 / BW_kHz[i]
|
||||
slow = profile with the longer T (radio wins ties)
|
||||
L[slow] = max(requested_visit_us[slow], ceil(4.6 * T[slow]))
|
||||
L[fast] = max(requested_visit_us[fast], ceil(4.6 * T[fast]))
|
||||
O = 2 * switch_us + loop_margin_us
|
||||
O = 2 * switch_us
|
||||
C = L[radio] + L[radio2] + O
|
||||
|
||||
P[slow] = round_up_to_8(max(32, 2 * C / T[slow]))
|
||||
P[fast] = round_up_to_8(max(32, (L[slow] + O) / T[fast] + 16))
|
||||
```
|
||||
|
||||
The previous SF8/500 versus SF7/62.5 measured 88-symbol floor remains in force.
|
||||
Automatic selection uses the minimum visits to choose a preamble, then allocates
|
||||
the fast visit from half the slow preamble's duration. Diagnostics feed the
|
||||
actual allocated visits back into the same function, including longer visits
|
||||
@@ -406,11 +420,11 @@ fast_dwell = max(ceil(4.6 * T[fast]),
|
||||
```
|
||||
|
||||
For the tested SF7/62.5 + SF8/500 pair with slow preamble 32, this yields
|
||||
9,421 us slow and 21,847 us fast (42.67 fast chirps). Swapping `radio` and
|
||||
`radio2` swaps the allocations automatically. The 0.3 ms reserve is not a sleep.
|
||||
9,421 us slow and 21,197 us fast (41.40 fast chirps) with the 1,075 us guarded
|
||||
switch allowance above. Swapping `radio` and `radio2` swaps the allocations automatically.
|
||||
|
||||
Production scheduling and automatic preambles use the **self-tested maximum
|
||||
successful switch plus 10%**, together with 300 us per cycle. Subsequent
|
||||
successful switch plus 10%**, without an additional fixed loop allowance. Subsequent
|
||||
successful hops can only raise that allowance until the next profile change.
|
||||
The measured recommendation is rounded up to eight symbols and capped at 128
|
||||
for automatic settings. The calculator and warnings retain the *uncapped*
|
||||
|
||||
+10
-13
@@ -42,8 +42,9 @@ class RadioProfiles {
|
||||
static constexpr uint16_t MaxPreamble = 65528;
|
||||
static constexpr uint16_t MaxAutomaticPreamble = 128;
|
||||
// Successful RX-to-RX hops self-test the board-specific switch allowance.
|
||||
// Packet receptions can extend a visit; these budgets describe an idle scan.
|
||||
static constexpr uint32_t LoopBudgetUs = 300;
|
||||
// Packet receptions can extend a visit. No fixed loop allowance is added to
|
||||
// the self-tested, guarded switch time in the idle-scan timing model.
|
||||
static constexpr uint32_t LoopBudgetUs = 0;
|
||||
static constexpr uint8_t SwitchTestSamplesPerDirection = 4;
|
||||
|
||||
struct ChirpTiming {
|
||||
@@ -148,21 +149,17 @@ class RadioProfiles {
|
||||
return isfinite(symbol) && symbol > 0 && symbol <= UINT32_MAX / symbols
|
||||
? (uint32_t)ceil(symbols * symbol) : 0;
|
||||
}
|
||||
static double preambleForVisits(const RadioProfileParams& p, const RadioProfileParams& other,
|
||||
bool slow, double own_us, double other_us, double overhead_us) {
|
||||
static double preambleForVisits(const RadioProfileParams& p, bool slow,
|
||||
double own_us, double other_us, double overhead_us) {
|
||||
const double symbol = symbolUs(p);
|
||||
if (symbol <= 0) return 32;
|
||||
double result = slow ? roundPreamble(2.0 * (own_us + other_us + overhead_us) / symbol)
|
||||
return slow ? roundPreamble(2.0 * (own_us + other_us + overhead_us) / symbol)
|
||||
: roundPreamble((other_us + overhead_us) / symbol + 2 * AcquisitionSymbols);
|
||||
// Production V4/XIAO empirical floor; retain prior measured margin.
|
||||
if (!slow && p.sf == 8 && p.bw == 500 && other.sf == 7 && other.bw == 62.5f && result < 88)
|
||||
result = 88;
|
||||
return result;
|
||||
}
|
||||
// A two-profile idle-scan timing policy, NOT a fitted zero-loss model.
|
||||
// Use a 4.6-symbol minimum on both profiles. Reserve
|
||||
// two return opportunities on the slow profile, and blind time + 16
|
||||
// acquisition symbols on the fast one. The 4.6/300-us pair bench received
|
||||
// acquisition symbols on the fast one. The 4.6-symbol pair bench received
|
||||
// 197/200: a sweep fitting inside 32 is not a guarantee of acquisition.
|
||||
// Explicit visit times let diagnostics account for the actual scheduler,
|
||||
// including a longer fast visit selected by an explicit slow preamble.
|
||||
@@ -180,8 +177,8 @@ class RadioProfiles {
|
||||
t.switch_us = switch_us; t.loop_us = loop_us;
|
||||
const double overhead = 2.0 * switch_us + loop_us;
|
||||
t.cycle_us = double(t.listen_us[0]) + t.listen_us[1] + overhead;
|
||||
t.preamble[0] = preambleForVisits(a, b, t.slow == 0, t.listen_us[0], t.listen_us[1], overhead);
|
||||
t.preamble[1] = preambleForVisits(b, a, t.slow == 1, t.listen_us[1], t.listen_us[0], overhead);
|
||||
t.preamble[0] = preambleForVisits(a, t.slow == 0, t.listen_us[0], t.listen_us[1], overhead);
|
||||
t.preamble[1] = preambleForVisits(b, t.slow == 1, t.listen_us[1], t.listen_us[0], overhead);
|
||||
t.valid = true;
|
||||
return t;
|
||||
}
|
||||
@@ -190,7 +187,7 @@ class RadioProfiles {
|
||||
// Keep the hot retune path to one profile's calculation; the full
|
||||
// validated diagnostic structure is only needed by CLI/reporting.
|
||||
const uint8_t slow = slowerProfile();
|
||||
return preambleForVisits(params(profile), params(profile ^ 1), profile == slow,
|
||||
return preambleForVisits(params(profile), profile == slow,
|
||||
minimumListenUs(params(profile), profile == slow), minimumListenUs(params(profile ^ 1), (profile ^ 1) == slow),
|
||||
2.0 * switchBudgetUs() + LoopBudgetUs);
|
||||
}
|
||||
|
||||
@@ -528,9 +528,10 @@ bool RadioProfileCLI::handle(const char* command, char* reply, size_t capacity,
|
||||
radio_->profilePreamble(0), radio_->profilePreamble(1));
|
||||
else if (!t.valid) snprintf(reply, capacity, "Error: timing unavailable");
|
||||
else {
|
||||
snprintf(reply, capacity, "> chirps=%.2f,%.2f; need=%.0f,%.0f; switch=%luus; loop=%luus (estimate)",
|
||||
snprintf(reply, capacity, "> chirps=%.2f,%.2f; need=%.0f,%.0f; switch=%luus; loop=%luus%s",
|
||||
t.listen_us[0] / t.symbol_us[0], t.listen_us[1] / t.symbol_us[1],
|
||||
t.preamble[0], t.preamble[1], (unsigned long)t.switch_us, (unsigned long)t.loop_us);
|
||||
t.preamble[0], t.preamble[1], (unsigned long)t.switch_us,
|
||||
(unsigned long)t.loop_us, t.loop_us ? " (estimate)" : "");
|
||||
size_t used = strlen(reply);
|
||||
if (used < capacity) snprintf(reply + used, capacity - used, "; test=%u/%u,%u/%u%s",
|
||||
p.switch_test_samples[0], RadioProfiles::SwitchTestSamplesPerDirection,
|
||||
|
||||
@@ -21,6 +21,39 @@
|
||||
|
||||
static volatile uint8_t state = STATE_IDLE;
|
||||
|
||||
// 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. RTC2 is otherwise unused on the T1000-E and
|
||||
// provides a continuous 32768 Hz clock without changing micros() globally or
|
||||
// holding the high-frequency clock on for this measurement.
|
||||
static uint32_t profileTimestamp() {
|
||||
#if defined(T1000_E) && defined(NRF52_PLATFORM)
|
||||
static bool started = false;
|
||||
if (!started) {
|
||||
NRF_RTC2->TASKS_STOP = 1;
|
||||
NRF_RTC2->PRESCALER = 0;
|
||||
NRF_RTC2->TASKS_CLEAR = 1;
|
||||
NRF_RTC2->TASKS_START = 1;
|
||||
started = true;
|
||||
}
|
||||
return NRF_RTC2->COUNTER;
|
||||
#else
|
||||
return micros();
|
||||
#endif
|
||||
}
|
||||
|
||||
static uint32_t profileElapsedUs(uint32_t since, uint32_t now) {
|
||||
#if defined(T1000_E) && defined(NRF52_PLATFORM)
|
||||
// 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);
|
||||
#else
|
||||
return now - since;
|
||||
#endif
|
||||
}
|
||||
|
||||
// this function is called when a complete packet
|
||||
// is transmitted by the module
|
||||
static
|
||||
@@ -33,6 +66,7 @@ void setFlag(void) {
|
||||
}
|
||||
|
||||
void RadioLibWrapper::begin() {
|
||||
(void) profileTimestamp();
|
||||
#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.
|
||||
@@ -254,7 +288,7 @@ mesh::RadioParamApplyResult RadioLibWrapper::trySetParams(float freq, float bw,
|
||||
}
|
||||
}
|
||||
|
||||
_profile_visit_us = micros();
|
||||
_profile_visit_stamp = profileTimestamp();
|
||||
return success ? mesh::RadioParamApplyResult::APPLIED : mesh::RadioParamApplyResult::FAILED;
|
||||
}
|
||||
|
||||
@@ -287,7 +321,7 @@ mesh::RadioParamApplyResult RadioLibWrapper::tuneProfile(uint8_t profile) {
|
||||
if (!_profile_refresh_required && _active_profile == profile && _profile_generation == _profiles.generation[profile]
|
||||
&& _physical_preamble == preamble) return mesh::RadioParamApplyResult::APPLIED;
|
||||
if (!validateProfile(p)) return mesh::RadioParamApplyResult::FAILED;
|
||||
const uint32_t started = micros();
|
||||
const uint32_t started = profileTimestamp();
|
||||
beginProfileRetune(isInRecvMode() && !_rx_ps_armed && !_rx_ps_enabled);
|
||||
const uint8_t resume = beginReconfigure();
|
||||
if (resume > 1) {
|
||||
@@ -330,16 +364,17 @@ mesh::RadioParamApplyResult RadioLibWrapper::tuneProfile(uint8_t profile) {
|
||||
if (restored) endReconfigure(resume);
|
||||
if (!restored || (resume && !isInRecvMode())) _profile_refresh_required = true;
|
||||
}
|
||||
_profile_visit_us = micros();
|
||||
_profile_visit_stamp = profileTimestamp();
|
||||
// A profile retune re-enters RX on a different tuple. Do not read its
|
||||
// instantaneous RSSI until the chip family's settle interval has elapsed.
|
||||
if (applied && _nf_refresh_requested) {
|
||||
_nf_sample_from = millis() + NF_CALIB_SETTLE_MS;
|
||||
}
|
||||
const uint32_t elapsed = _profile_visit_us - started;
|
||||
const uint32_t elapsed = profileElapsedUs(started, _profile_visit_stamp);
|
||||
if (elapsed > _profiles.longest_switch_us) _profiles.longest_switch_us = elapsed;
|
||||
// Learn the actual board's RX-to-RX retune cost on the first four good hops
|
||||
// in each direction. Deferred and rolled-back operations are not samples.
|
||||
// Require four good RX-to-RX hops in each direction before using the budget,
|
||||
// then continue raising the observed maximum as later good hops run.
|
||||
// Deferred and rolled-back operations are not samples.
|
||||
if (applied && resume && old_profile != profile) {
|
||||
_profiles.sampleSwitch(old_profile, profile, elapsed);
|
||||
}
|
||||
@@ -442,13 +477,13 @@ void RadioLibWrapper::serviceProfileScan() {
|
||||
&& visit_us < NF_CALIB_SETTLE_MS * 1000UL) {
|
||||
visit_us = NF_CALIB_SETTLE_MS * 1000UL;
|
||||
}
|
||||
if ((uint32_t)(micros() - _profile_visit_us) >= visit_us) target ^= 1;
|
||||
if (profileElapsedUs(_profile_visit_stamp, profileTimestamp()) >= visit_us) target ^= 1;
|
||||
}
|
||||
const auto result = tuneProfile(target);
|
||||
if (restart_scan && result == mesh::RadioParamApplyResult::APPLIED) {
|
||||
_profile_scan_generation[0] = _profiles.generation[0];
|
||||
_profile_scan_generation[1] = _profiles.generation[1];
|
||||
_profile_visit_us = micros();
|
||||
_profile_visit_stamp = profileTimestamp();
|
||||
}
|
||||
if (result == mesh::RadioParamApplyResult::FAILED) {
|
||||
// Retry a rejected or failing profile at a bounded rate; retain primary RX.
|
||||
@@ -1046,7 +1081,7 @@ void RadioLibWrapper::startRecv() {
|
||||
int err = startReceiveMode();
|
||||
if (err == RADIOLIB_ERR_NONE) {
|
||||
// A very short frame may complete while startReceiveMode() returns.
|
||||
_profile_visit_us = micros();
|
||||
_profile_visit_stamp = profileTimestamp();
|
||||
// Retain that RX interrupt instead of overwriting it with software state.
|
||||
noInterrupts();
|
||||
state = (state & STATE_INT_READY) | STATE_RX;
|
||||
|
||||
@@ -81,7 +81,7 @@ protected:
|
||||
uint16_t _physical_preamble = 0;
|
||||
uint32_t _profile_generation = 0;
|
||||
uint32_t _profile_scan_generation[2] = {};
|
||||
uint32_t _profile_visit_us = 0;
|
||||
uint32_t _profile_visit_stamp = 0;
|
||||
uint32_t _profile_retry_at = 0;
|
||||
bool _profile_rxps_suspended = false;
|
||||
bool _profile_saved_rxps = false;
|
||||
|
||||
+25
-12
@@ -11,7 +11,7 @@ int main() {
|
||||
assert(Profiles::MinListenSymbols==4.6);
|
||||
assert(Profiles::SlowListenSymbols==4.6);
|
||||
assert(Profiles::MinFastListenSymbols==4.6);
|
||||
assert(Profiles::LoopBudgetUs==300);
|
||||
assert(Profiles::LoopBudgetUs==0);
|
||||
auto a=params(10,125),b=params(10,125);
|
||||
auto t=Profiles::calculateChirpTiming(a,b,1,0,1000,4000);
|
||||
assert(t.valid && t.slow==0 && t.symbol_us[0]==8192);
|
||||
@@ -31,9 +31,9 @@ int main() {
|
||||
b=params(10,1e-30f);assert(Profiles::minimumListenUs(b)==0);
|
||||
a=params(7,62.5);b=params(8,500);
|
||||
t=Profiles::calculateChirpTiming(a,b);
|
||||
assert(t.preamble[1]==88); // Existing measured acquisition floor retained.
|
||||
assert(t.preamble[1]==40); // Derived from dwell and acquisition, not a pair-specific floor.
|
||||
auto swapped=Profiles::calculateChirpTiming(b,a);
|
||||
assert(swapped.slow==1 && swapped.preamble[0]==88);
|
||||
assert(swapped.slow==1 && swapped.preamble[0]==40);
|
||||
assert(swapped.preamble[1]==t.preamble[0]);
|
||||
for (float bw0: {7.8f,62.5f,125.0f,250.0f,500.0f})
|
||||
for (float bw1: {7.8f,62.5f,125.0f,250.0f,500.0f})
|
||||
@@ -76,8 +76,8 @@ int main() {
|
||||
for (unsigned n=0;n<Profiles::SwitchTestSamplesPerDirection;++n) {
|
||||
p.sampleSwitch(0,1,545);p.sampleSwitch(1,0,545);
|
||||
}
|
||||
assert(p.listenUs(0)==9421 && p.listenUs(1)==21847);
|
||||
assert(p.chirpTiming().switch_us==600 && p.chirpTiming().loop_us==300 && p.chirpTiming().preamble[1]==64);
|
||||
assert(p.listenUs(0)==9421 && p.listenUs(1)==22147);
|
||||
assert(p.chirpTiming().switch_us==600 && p.chirpTiming().loop_us==0 && p.chirpTiming().preamble[1]==64);
|
||||
auto before=p.chirpTiming();const auto wire0=p.preamble(0,32),wire1=p.preamble(1,32);
|
||||
p.sampleSwitch(0,1,8428);auto overrun=p.chirpTiming();
|
||||
assert(overrun.switch_us==9271 && overrun.preamble[1]>before.preamble[1]);
|
||||
@@ -97,23 +97,23 @@ int main() {
|
||||
p.primary=params(7,62.5);p.primary.preamble=32;
|
||||
p.secondary.params=params(8,500);p.secondary.params.preamble=32;
|
||||
p.secondary.mode=mesh::RadioProfileMode::Rx;
|
||||
assert(p.automaticPreambleFits() && p.listenUs(0)==9421 && p.listenUs(1)==21847);
|
||||
assert(p.automaticPreambleFits() && p.listenUs(0)==9421 && p.listenUs(1)==22147);
|
||||
assert(p.preamble(0,32)==32 && p.preamble(1,32)==32);
|
||||
assert(p.chirpTiming().preamble[1]==88);
|
||||
assert(p.chirpTiming().preamble[1]==40);
|
||||
std::swap(p.primary,p.secondary.params);
|
||||
assert(p.slowerProfile()==1 && p.listenUs(1)==9421 && p.listenUs(0)==21847);
|
||||
assert(p.slowerProfile()==1 && p.listenUs(1)==9421 && p.listenUs(0)==22147);
|
||||
p.secondary.params.preamble=8;
|
||||
assert(!p.automaticPreambleFits());
|
||||
// This formerly fit the 4.1 fast floor (1050 us), but not 4.6 (1178 us).
|
||||
// With no fixed loop reserve, 27 symbols cannot cover both 4.6-symbol visits.
|
||||
p.primary=params(7,500);p.secondary.params=params(7,500);
|
||||
p.primary.preamble=30;
|
||||
p.primary.preamble=27;
|
||||
assert(p.listenUs(0)==1178 && p.listenUs(1)>=1178);
|
||||
assert(!p.automaticPreambleFits());
|
||||
p.primary.preamble=32;
|
||||
assert(p.automaticPreambleFits() && p.listenUs(1)==1418);
|
||||
assert(p.automaticPreambleFits() && p.listenUs(1)==1718);
|
||||
// Long requested visits remain visible, not replaced by policy minima.
|
||||
t=Profiles::calculateChirpTiming(params(7,62.5),params(8,500),20000,30000);
|
||||
assert(t.listen_us[0]==20000 && t.listen_us[1]==30000 && t.cycle_us==50300);
|
||||
assert(t.listen_us[0]==20000 && t.listen_us[1]==30000 && t.cycle_us==50000);
|
||||
Profiles measured;
|
||||
measured.primary=params(7,500);
|
||||
measured.secondary.params=params(7,500);
|
||||
@@ -138,4 +138,17 @@ int main() {
|
||||
measured.setSecondary(replacement,true);
|
||||
assert(!measured.switchTestReady() && measured.switchBudgetUs()==0);
|
||||
assert(measured.preamble(0,32)==128 && measured.preamble(1,32)==88);
|
||||
|
||||
// A T1000-E-sized 977 us hop calibrates to 1075 us, yet SF8/500 no longer
|
||||
// inherits the historical 88-symbol floor or a fixed 300 us loop pad.
|
||||
Profiles t1000;
|
||||
t1000.primary=params(7,62.5);
|
||||
t1000.secondary.params=params(8,500);
|
||||
t1000.secondary.mode=mesh::RadioProfileMode::RxTx;
|
||||
for (unsigned n=0;n<Profiles::SwitchTestSamplesPerDirection;++n) {
|
||||
t1000.sampleSwitch(0,1,977);t1000.sampleSwitch(1,0,977);
|
||||
}
|
||||
assert(t1000.switchBudgetUs()==1075);
|
||||
assert(t1000.preamble(0,32)==32 && t1000.preamble(1,32)==40);
|
||||
assert(t1000.chirpTiming().loop_us==0 && t1000.chirpTiming().preamble[1]==40);
|
||||
}
|
||||
+7
-7
@@ -367,11 +367,11 @@ int main(int argc, char** argv) {
|
||||
assert(strstr(f.reply,"switch=600us"));
|
||||
assert(strstr(f.cmd("set radio2 910.5,500,7,5,rxtx"),"preamble=64;"));
|
||||
assert(strstr(f.reply,"switch=600us"));
|
||||
assert(strstr(f.cmd("get radio2.scan"),"slow=radio; listen_us=9421,21847"));
|
||||
assert(strstr(f.cmd("get radio2.timing"),"chirps=4.60,85.34; need=32,64"));
|
||||
assert(strstr(f.cmd("get radio2.scan"),"slow=radio; listen_us=9421,22147"));
|
||||
assert(strstr(f.cmd("get radio2.timing"),"chirps=4.60,86.51; need=32,64"));
|
||||
assert(strstr(f.reply,"switch=600us"));
|
||||
assert(strstr(f.reply,"loop=300us"));
|
||||
assert(strstr(f.reply,"estimate"));
|
||||
assert(strstr(f.reply,"loop=0us"));
|
||||
assert(!strstr(f.reply,"estimate"));
|
||||
assert(strstr(f.reply,"WARN recommended preamble: radio2=64"));
|
||||
char timing_reply[160]; strcpy(timing_reply, f.reply);
|
||||
assert(!strcmp(f.cmd("get radio.timing"),timing_reply));
|
||||
@@ -476,9 +476,9 @@ int main(int argc, char** argv) {
|
||||
f.advance(2000);
|
||||
f.finishTimingTest();
|
||||
assert(f.radio.p.secondary.params.preamble==32);
|
||||
assert(strstr(f.cmd("get radio2.scan"),"slow=radio; listen_us=9421,21847"));
|
||||
assert(strstr(f.cmd("get radio2.timing"),"chirps=4.60,42.67; need=32,88"));
|
||||
assert(strstr(f.reply,"loop=300us"));
|
||||
assert(strstr(f.cmd("get radio2.scan"),"slow=radio; listen_us=9421,22147"));
|
||||
assert(strstr(f.cmd("get radio2.timing"),"chirps=4.60,43.26; need=32,40"));
|
||||
assert(strstr(f.reply,"loop=0us"));
|
||||
}
|
||||
{
|
||||
Fixture f;
|
||||
|
||||
+5
-5
@@ -699,15 +699,15 @@ TEST(RadioProfiles, AutomaticPreamblesUseSelfTestedSwitchingMargin) {
|
||||
p.sampleSwitch(0, 1, 545); p.sampleSwitch(1, 0, 545);
|
||||
}
|
||||
EXPECT_EQ(600, p.switchBudgetUs());
|
||||
EXPECT_EQ(300, p.LoopBudgetUs);
|
||||
const uint16_t expected[] = {64, 88, 32};
|
||||
EXPECT_EQ(0, p.LoopBudgetUs);
|
||||
const uint16_t expected[] = {64, 40, 32};
|
||||
for (int sf = 7; sf <= 9; ++sf) {
|
||||
p.secondary.params.sf = sf;
|
||||
EXPECT_EQ(expected[sf-7], p.preamble(1, 32));
|
||||
EXPECT_EQ(32, p.preamble(0, 32));
|
||||
EXPECT_EQ(0, p.preamble(1, 32) % 8);
|
||||
EXPECT_EQ(9421, p.listenUs(0));
|
||||
EXPECT_EQ(21847, p.listenUs(1));
|
||||
EXPECT_EQ(22147, p.listenUs(1));
|
||||
EXPECT_LE(2 * (p.listenUs(0) + p.listenUs(1) + 2 * p.switchBudgetUs() + p.LoopBudgetUs),
|
||||
p.preamble(0, 32) * p.symbolUs(p.primary));
|
||||
EXPECT_LE(p.listenUs(1) + 2 * p.switchBudgetUs() + p.LoopBudgetUs
|
||||
@@ -729,9 +729,9 @@ TEST(RadioProfiles, SlowerProfileDeterminesOrderAndFasterReceiveWindow) {
|
||||
std::swap(p.primary, p.secondary.params);
|
||||
EXPECT_EQ(1, p.slowerProfile());
|
||||
EXPECT_EQ(9421, p.listenUs(1));
|
||||
EXPECT_EQ(21847, p.listenUs(0));
|
||||
EXPECT_EQ(22147, p.listenUs(0));
|
||||
p.secondary.params.preamble = 40;
|
||||
EXPECT_EQ(30039, p.listenUs(0));
|
||||
EXPECT_EQ(30339, p.listenUs(0));
|
||||
p.secondary.params.preamble = 8;
|
||||
EXPECT_FALSE(p.automaticPreambleFits()); // no safe time for the faster channel
|
||||
}
|
||||
|
||||
@@ -24,6 +24,8 @@ namespace mesh { enum class RadioParamApplyResult { APPLIED, BUSY, FAILED }; }
|
||||
static uint64_t elapsed_us;
|
||||
static uint8_t state;
|
||||
uint32_t micros() { return (uint32_t)elapsed_us; }
|
||||
uint32_t profileTimestamp() { return micros(); }
|
||||
uint32_t profileElapsedUs(uint32_t since, uint32_t now) { return now - since; }
|
||||
uint32_t millis() { return (uint32_t)(elapsed_us / 1000); }
|
||||
struct Chip { bool standbyXOSC=false; int standby() { return 0; } };
|
||||
using CustomSX1262 = Chip;
|
||||
@@ -45,7 +47,7 @@ struct RadioLibWrapper {
|
||||
}
|
||||
uint32_t _rx_ps_rx_us=50000, _rx_ps_sleep_us=50000;
|
||||
uint8_t _active_profile=0, _cur_sf=7, _cur_cr=5;
|
||||
uint32_t _profile_generation=1, _profile_visit_us=0, _profile_retry_at=0;
|
||||
uint32_t _profile_generation=1, _profile_visit_stamp=0, _profile_retry_at=0;
|
||||
uint32_t _profile_scan_generation[2]={};
|
||||
uint16_t _physical_preamble=32;
|
||||
float _cur_freq=909.5, _cur_bw=62.5;
|
||||
@@ -57,7 +59,7 @@ struct RadioLibWrapper {
|
||||
state=STATE_RX; elapsed_us=100000;
|
||||
auto& p=_profiles.primary;
|
||||
p.freq=_cur_freq; p.bw=_cur_bw; p.sf=_cur_sf; p.cr=_cur_cr;
|
||||
_profile_visit_us=micros();
|
||||
_profile_visit_stamp=profileTimestamp();
|
||||
}
|
||||
bool isChipBusy() { return busy; }
|
||||
bool isInRecvMode() const { return (state & ~STATE_INT_READY) == STATE_RX; }
|
||||
@@ -69,7 +71,7 @@ struct RadioLibWrapper {
|
||||
void startRecv() {
|
||||
if (failRxStarts) { --failRxStarts; state=STATE_IDLE; return; }
|
||||
if (_profile_rxps_suspended) assert(chip.standbyXOSC && !_rx_ps_enabled);
|
||||
state=STATE_RX; _profile_visit_us=micros(); _rx_ps_armed=_rx_ps_enabled;
|
||||
state=STATE_RX; _profile_visit_stamp=profileTimestamp(); _rx_ps_armed=_rx_ps_enabled;
|
||||
}
|
||||
void stopReceiveDutyCycle() { _rx_ps_armed=false; }
|
||||
bool applyParams(float f,float,uint8_t,uint8_t) { ++applies; elapsed_us+=switch_apply_us; return !fail || f==909.5f; }
|
||||
@@ -273,7 +275,7 @@ int main() {
|
||||
w.enable();
|
||||
w._floor_estimator.add(-100, millis());
|
||||
w._active_profile=0;
|
||||
w._profile_visit_us=micros();
|
||||
w._profile_visit_stamp=profileTimestamp();
|
||||
const auto primary_visit=w._profiles.listenUs(0);
|
||||
elapsed_us += primary_visit - 1; w.serviceProfileScan(); assert(w._active_profile==0);
|
||||
elapsed_us++; w.serviceProfileScan(); assert(w._active_profile==1);
|
||||
|
||||
Reference in new issue
Block a user