Record SPI9 hardware checks and fix idle sleep test fixture
This commit is contained in:
1 parent
d6853f4521
commit
837847f891
5 files changed
+319
-2
No files matched your search
@@ -11,6 +11,7 @@ Completed validation reports, experiments, and test guides are collected here. O
|
||||
|
||||
## Radio and RF
|
||||
|
||||
- [Saved SPI option 9: native, build and V3/V4 persistence checks, 2026-10-23](saved_spi9_2026-10-23.md)
|
||||
- [Heltec V3/V4 8 MHz versus 8.889 MHz radio switching measurements, 2026-10-23](esp32_spi_clock_retune_2026-10-23.md)
|
||||
- [Five-board eight-peer WiFi UDP bridge hardware qualification, 2026-10-22](udp_bridge_hardware_2026-10-22.md)
|
||||
- [Eight-peer WiFi UDP bridge: V3/V4/R8 Full build and host validation, 2026-10-22](udp_bridge_2026-10-22.md)
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
<!-- meshcore-hosted-doc-link:start -->
|
||||
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/test-results/saved_spi9_2026-10-23/">View this page on MeshCore Docs</a>.</p>
|
||||
<!-- meshcore-hosted-doc-link:end -->
|
||||
|
||||
# Saved SPI option 9, 23 October 2026
|
||||
|
||||
Status: **Passed native, build and V3/V4 setting checks.** Firmware source:
|
||||
`d6853f4521b6676298260c1c49095369a106000e`.
|
||||
|
||||
```text
|
||||
get radio.fast.caps
|
||||
set radio.fast.spi 9
|
||||
get radio.fast.spi
|
||||
```
|
||||
|
||||
The integer label `9` selects 8,888,888 Hz, approximately 8.889 MHz. It does
|
||||
not select 9,000,000 Hz. The setting is saved without floating-point storage
|
||||
or a file-format change. `0` restores the board's original default.
|
||||
|
||||
## Availability and defaults
|
||||
|
||||
The option is advertised by tunable radio HALs on the supported ESP32,
|
||||
ESP32-S3 and ESP32-C3 boards when the permitted clock is at least 8,888,888 Hz.
|
||||
The pinned ESP32-C6 core cannot select this divider and does not advertise it.
|
||||
Current nRF52, RP2040 and STM32 radio buses do not offer it either. Unsupported
|
||||
selections are rejected before changing or saving the policy.
|
||||
|
||||
Seven older ESP32 targets now use a tunable bytewise HAL: Heltec V2, GEPRC
|
||||
Linkflow 900, T3S3 SX1276, T-Beam SX1276, T-LoRa V2.1, Meshnology W12 and
|
||||
Waveshare ESP32-S3 LR1121. They retain their 2 MHz default, existing pins,
|
||||
SPI controller, bus ownership and byte transfers. Existing buffered targets
|
||||
retain their defaults too; enabling this option does not enable 16 MHz on
|
||||
targets that previously lacked it.
|
||||
|
||||
The existing capability record remains two bytes and the saved policy remains
|
||||
24 bytes at format version 1. Native tests cover strict integer parsing,
|
||||
actual SPISettings mapping, unavailable choices, clock limits, saved and early
|
||||
boot restore, transaction failure handling and output bounds. The constructor
|
||||
test executes all eleven paths in the seven older targets. A further CLI case
|
||||
covers clock-only radios with no other acceleration features.
|
||||
|
||||
## Builds and physical checks
|
||||
|
||||
Canonical builds passed for V3 Full observer MQTT Repeater, V4 Full observer
|
||||
MQTT Repeater, Heltec V2 BLE Companion and XIAO ESP32-C3 USB Companion. Their
|
||||
normal capacity and capability checks passed without feature reductions.
|
||||
The first C3 attempt was stopped during a slow dependency download; the final
|
||||
build used the identical cached, pinned RadioLib commit.
|
||||
|
||||
Both physical repeaters were updated through application-only USB writes.
|
||||
Complete application readback matched the candidate images, partition tables
|
||||
and OTA selection metadata stayed unchanged, and readable saved settings were
|
||||
preserved.
|
||||
|
||||
| Physical board | Verified body | Saved 9 after one normal reboot | RX profile switch count | Switch errors |
|
||||
| --- | --- | --- | ---: | ---: |
|
||||
| Heltec V3 | `B05EC4F8E75A2A24` | Passed, 8888888 Hz | 8 to 332 | 0 |
|
||||
| Heltec V4.3 OLED | `C4E28F80E5FBDF2E` | Passed, 8888888 Hz | 15 to 329 | 0 |
|
||||
|
||||
The short runtime check used primary 917 MHz and secondary 918 MHz, both
|
||||
BW 62.5 kHz, SF7, CR5, preamble 32, with the secondary receive-only. No test
|
||||
packets were deliberately transmitted. Core flags stayed zero and watchdog
|
||||
recovery counters did not increase within either measured startup epoch.
|
||||
|
||||
Both boards retain the normal new firmware with the option available. Cleanup
|
||||
restored their original selection `0` at 8 MHz, all seven fast features on,
|
||||
radio2 off, temporary profiles cleared and their saved preferences unchanged.
|
||||
The deliberate reboot resets volatile counters; it is recorded separately
|
||||
from unexpected recoveries.
|
||||
|
||||
Production getters report the requested/resolved SPISettings clock. They are
|
||||
not hardware-divider measurements. The earlier
|
||||
[physical V3/V4 comparison](esp32_spi_clock_retune_2026-10-23.md) verified the
|
||||
80 MHz / 9 divider; this change adds a saved selector for the same requested
|
||||
clock. The timing campaign was not repeated. These checks qualify saving,
|
||||
reboot persistence and retune liveness, not RF packet delivery, BLE, CPU sleep
|
||||
or all firmware roles on physical hardware.
|
||||
|
||||
The [sanitized evidence](saved_spi9_2026-10-23.safe.json) includes build and
|
||||
hardware results and hashes of the retained private receipts. Raw device
|
||||
identities and CLI logs are excluded.
|
||||
@@ -0,0 +1,192 @@
|
||||
{
|
||||
"schema": 1,
|
||||
"passed": true,
|
||||
"source_commit": "d6853f4521b6676298260c1c49095369a106000e",
|
||||
"selection": 9,
|
||||
"requested_spi_hz": 8888888,
|
||||
"defaults_changed": false,
|
||||
"capability_storage_bytes": 2,
|
||||
"saved_record_bytes": 24,
|
||||
"saved_record_version": 1,
|
||||
"availability": {
|
||||
"supported_current_mcus": [
|
||||
"esp32",
|
||||
"esp32s3",
|
||||
"esp32c3"
|
||||
],
|
||||
"unsupported_current_mcus": [
|
||||
"esp32c6",
|
||||
"nrf52",
|
||||
"rp2040",
|
||||
"stm32"
|
||||
],
|
||||
"note": "Advertised only on supporting tunable HALs; current C6 core cannot select this divider."
|
||||
},
|
||||
"scope": "Saving SPI9, normal reboot persistence and RX retune liveness; no RF delivery, BLE or CPU sleep qualification.",
|
||||
"clock_evidence": "Production getters report requested/resolved SPISettings. Physical divider was measured in the earlier V3/V4 HIL campaign, not repeated here.",
|
||||
"native_checks": {
|
||||
"focused_repository_methods_passed": 30,
|
||||
"legacy_constructor_paths_passed": 11,
|
||||
"additional_clock_only_cli_case_passed": true,
|
||||
"private_installer_methods_passed": 25,
|
||||
"private_controller_methods_passed": 12
|
||||
},
|
||||
"builds": {
|
||||
"Heltec_v3_repeater_observer_mqtt": {
|
||||
"passed": true,
|
||||
"platform": "ESP32_PLATFORM",
|
||||
"mcu": "esp32s3",
|
||||
"build_profile": "full",
|
||||
"application_bytes": 2302216,
|
||||
"application_sha256": "9c7ad5ae3704fded2ab75ef42da3b1101c09cd6a2db2ca66fb35b2b5c163c755",
|
||||
"capacity_passed": true,
|
||||
"available_internal_bytes": 208768,
|
||||
"required_heap_bytes": 207024,
|
||||
"largest_internal_region_bytes": 176000,
|
||||
"required_contiguous_bytes": 19168,
|
||||
"reductions": [],
|
||||
"elf_sha256": "fe75b5759e308738e6912ca9a6b0c62cfa4c156b3b271b3c37da5622066570f8",
|
||||
"memory_receipt_sha256": "9988e39bd1b5053c448bd6ee88a78e7461f0b2a78ac1eabb56bfb1c3a64838c2",
|
||||
"capabilities_receipt_sha256": "52c025efe6fc67e5086fa4becd39fe64500d2d35e99c184e2ab86b9a6ecca78f",
|
||||
"build_summary_sha256": "4c26c4e0e504081da322b5fcb8d6d1cd57082b3401639c37888015575989c8fc"
|
||||
},
|
||||
"heltec_v4_repeater_observer_mqtt": {
|
||||
"passed": true,
|
||||
"platform": "ESP32_PLATFORM",
|
||||
"mcu": "esp32s3",
|
||||
"build_profile": "full",
|
||||
"application_bytes": 2343800,
|
||||
"application_sha256": "daf6fd466aa39c32d4a155e9bbdfdee3bb6c5e316e7bcc84f2dfab2a580a9beb",
|
||||
"capacity_passed": true,
|
||||
"available_internal_bytes": 201880,
|
||||
"required_heap_bytes": 195760,
|
||||
"largest_internal_region_bytes": 169112,
|
||||
"required_contiguous_bytes": 19168,
|
||||
"reductions": [],
|
||||
"elf_sha256": "e1943b021faf410302582ebd665fb1354d279589f56df70696ba2735f3422f98",
|
||||
"memory_receipt_sha256": "9754cf3f6eebca5724f71be6e0924ef253d498ee8992baf048a8a7a3de2897fe",
|
||||
"capabilities_receipt_sha256": "da714d8f23735f5b395f527cfc83561fd45a6d15b6b5ac7a595b4bce84d0cb65",
|
||||
"build_summary_sha256": "f3fee6f98707378063c2f36703066f7657ee171acd63493e7e4d6b6930899574"
|
||||
},
|
||||
"Heltec_v2_companion_radio_ble": {
|
||||
"passed": true,
|
||||
"platform": "ESP32_PLATFORM",
|
||||
"mcu": "esp32",
|
||||
"build_profile": "auto",
|
||||
"application_bytes": 1821016,
|
||||
"application_sha256": "378df81ff7b0e60b46a5078927c737aa80166d5ede7a7776866cb5f1c7939575",
|
||||
"capacity_passed": true,
|
||||
"available_internal_bytes": 181272,
|
||||
"required_heap_bytes": 141104,
|
||||
"largest_internal_region_bytes": 113840,
|
||||
"required_contiguous_bytes": 18464,
|
||||
"reductions": [],
|
||||
"elf_sha256": "281229fe997ce4b854af5ccd5a2d4fbb7bcc2eb28d2f26afbcf7345ca54711ab",
|
||||
"memory_receipt_sha256": "e59953d945260ed23deea74effd1ef3c9c9d186784a41f7bf4a79d3d96ec67f6",
|
||||
"capabilities_receipt_sha256": "a2cb45d0e410261bc414d6cf1fb0dc83eb6a33d8e3427a5f9d4f37840616ea59",
|
||||
"build_summary_sha256": "03edad5a409fd997478dfca6684a0ec60b68518e913bb316ee00e30fa23bac92"
|
||||
},
|
||||
"Xiao_C3_companion_radio_usb": {
|
||||
"passed": true,
|
||||
"platform": "ESP32_PLATFORM",
|
||||
"mcu": "esp32c3",
|
||||
"build_profile": "auto",
|
||||
"application_bytes": 1494728,
|
||||
"application_sha256": "15f29a99e9915d9217fc71991f67a56c9693c7f6710f316ccdd23bdc37cb0901",
|
||||
"capacity_passed": true,
|
||||
"available_internal_bytes": 119792,
|
||||
"required_heap_bytes": 72736,
|
||||
"largest_internal_region_bytes": 119792,
|
||||
"required_contiguous_bytes": 18464,
|
||||
"reductions": [],
|
||||
"elf_sha256": "9d4598abd647292c4c4d84f85917f35396cd038932e3c525c8f3fbfe9ac9cebe",
|
||||
"memory_receipt_sha256": "72b704a15c5cb778bcdde1b4406565b9a111e372b71e3e2ab50d0b3007115fa9",
|
||||
"capabilities_receipt_sha256": "a77d314543cbd72052eac4a337c101ec42d17c541137de13f6b05811ce62066d",
|
||||
"build_summary_sha256": "70320556ee234398363cced43fd939f3afd28e34db507f3f77da87fa37e208ca"
|
||||
}
|
||||
},
|
||||
"hardware": {
|
||||
"v3": {
|
||||
"passed": true,
|
||||
"source_commit": "d6853f4521b6676298260c1c49095369a106000e",
|
||||
"application_body": "B05EC4F8E75A2A24",
|
||||
"application_sha256": "9c7ad5ae3704fded2ab75ef42da3b1101c09cd6a2db2ca66fb35b2b5c163c755",
|
||||
"app_only_install_and_full_readback_passed": true,
|
||||
"partition_table_and_ota_selection_unchanged": true,
|
||||
"readable_settings_preserved": true,
|
||||
"spi9_save_and_getters_passed": true,
|
||||
"capability_clocks": [
|
||||
0,
|
||||
2,
|
||||
4,
|
||||
8,
|
||||
9
|
||||
],
|
||||
"requested_spi_hz": 8888888,
|
||||
"fast_features": {
|
||||
"warm": "on",
|
||||
"fs": "on",
|
||||
"packet": "on",
|
||||
"iq": "on",
|
||||
"modem": "on",
|
||||
"batch": "on",
|
||||
"buffer": "on"
|
||||
},
|
||||
"single_application_reboot_persistence_passed": true,
|
||||
"pre_reboot_uptime_secs": 38,
|
||||
"post_reboot_uptime_secs": 5,
|
||||
"runtime_switches_before": 8,
|
||||
"runtime_switches_after": 332,
|
||||
"runtime_switch_errors": 0,
|
||||
"new_core_flags": 0,
|
||||
"new_watchdog_recoveries": 0,
|
||||
"cleanup_passed": true,
|
||||
"restored_spi_selection": 0,
|
||||
"restored_resolved_spi_hz": 8000000,
|
||||
"temporary_profiles_cleared": true,
|
||||
"install_receipt_sha256": "4f92d878d603496515a79f69632cf7356a16fe8b9dc254a8f57b7b53cc7142d2",
|
||||
"focus_receipt_sha256": "fdb2fd99ea266eec62ea12571a382520318b1a3a4618d666bc94eac73fbbd80c"
|
||||
},
|
||||
"v4": {
|
||||
"passed": true,
|
||||
"source_commit": "d6853f4521b6676298260c1c49095369a106000e",
|
||||
"application_body": "C4E28F80E5FBDF2E",
|
||||
"application_sha256": "daf6fd466aa39c32d4a155e9bbdfdee3bb6c5e316e7bcc84f2dfab2a580a9beb",
|
||||
"app_only_install_and_full_readback_passed": true,
|
||||
"partition_table_and_ota_selection_unchanged": true,
|
||||
"readable_settings_preserved": true,
|
||||
"spi9_save_and_getters_passed": true,
|
||||
"capability_clocks": [
|
||||
0,
|
||||
2,
|
||||
4,
|
||||
8,
|
||||
9
|
||||
],
|
||||
"requested_spi_hz": 8888888,
|
||||
"fast_features": {
|
||||
"warm": "on",
|
||||
"fs": "on",
|
||||
"packet": "on",
|
||||
"iq": "on",
|
||||
"modem": "on",
|
||||
"batch": "on",
|
||||
"buffer": "on"
|
||||
},
|
||||
"single_application_reboot_persistence_passed": true,
|
||||
"pre_reboot_uptime_secs": 48,
|
||||
"post_reboot_uptime_secs": 7,
|
||||
"runtime_switches_before": 15,
|
||||
"runtime_switches_after": 329,
|
||||
"runtime_switch_errors": 0,
|
||||
"new_core_flags": 0,
|
||||
"new_watchdog_recoveries": 0,
|
||||
"cleanup_passed": true,
|
||||
"restored_spi_selection": 0,
|
||||
"restored_resolved_spi_hz": 8000000,
|
||||
"temporary_profiles_cleared": true,
|
||||
"install_receipt_sha256": "718c11c61ec972c2988a2e1e28d16bc83e6184c21da23028a62ee159a45732aa",
|
||||
"focus_receipt_sha256": "1baad7dd0242a95c2a42ed958a871355ea3e647ee22cf83359fa6dc5d35896ce"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -232,6 +232,48 @@ static void roundedClockRequiresCapabilityAndBootAdmission() {
|
||||
puts("Fast tuning: rounded SPI 9 uses exact divider Hz, persists unchanged and rejects unsupported clocks");
|
||||
}
|
||||
|
||||
static void clockOnlyLegacyHalKeepsFeaturesUnavailable() {
|
||||
TuningFixture f;
|
||||
f.radio.caps.flags = f.radio.tuning.flags = 0;
|
||||
f.radio.caps.spi_clocks = 2 | 4 | 8 | mesh::RadioSwitchCapabilities::Spi9;
|
||||
f.radio.default_spi_hz = 2000000;
|
||||
assert(!strcmp(f.cmd("get radio.fast.caps"), "> features:; spi=0,2,4,8,9"));
|
||||
assert(!strcmp(f.cmd("get radio.fast"),
|
||||
"> spi=0 (2000000Hz) warm=na fs=na packet=na iq=na modem=na batch=na buffer=na"));
|
||||
f.cmd("set radio.fast.spi 9");
|
||||
assert(!f.radio.tuning.flags && f.radio.tuning.spi_mhz == 9);
|
||||
assert(f.radio.switchSpiHz() == 8888888);
|
||||
const auto saved = f.fs.files;
|
||||
TuningRadio reboot;
|
||||
reboot.caps = f.radio.caps;
|
||||
reboot.tuning.flags = 0;
|
||||
reboot.default_spi_hz = 2000000;
|
||||
mesh::RadioProfileCLI::configureBootTuning(&f.fs, reboot);
|
||||
assert(!reboot.tuning.flags && reboot.tuning.spi_mhz == 9);
|
||||
assert(reboot.switchSpiHz() == 8888888 && reboot.boot_configurations == 1);
|
||||
mesh::RadioProfileCLI restored;
|
||||
restored.begin(&f.fs, &reboot, &f.clock);
|
||||
assert(reboot.tuning == f.radio.tuning && f.fs.files == saved);
|
||||
|
||||
const auto original = f.radio.tuning;
|
||||
const auto attempts = f.radio.attempts;
|
||||
const auto persists = f.radio.persists;
|
||||
for (const char* feature : {"warm", "fs", "packet", "iq", "modem", "batch", "buffer"}) {
|
||||
char command[48];
|
||||
snprintf(command, sizeof(command), "set radio.fast.%s on", feature);
|
||||
f.cmd(command, false);
|
||||
assert(f.radio.tuning == original && f.fs.files == saved);
|
||||
assert(f.radio.attempts == attempts && f.radio.persists == persists);
|
||||
}
|
||||
f.cmd("set radio.fast off");
|
||||
assert(!f.radio.tuning.flags && f.radio.tuning.spi_mhz == 2);
|
||||
assert(f.radio.switchSpiHz() == 2000000);
|
||||
f.cmd("set radio.fast on");
|
||||
assert(!f.radio.tuning.flags && !f.radio.tuning.spi_mhz);
|
||||
assert(f.radio.switchSpiHz() == 2000000);
|
||||
puts("Fast tuning: clock-only legacy HAL saves SPI 9 without exposing cached feature controls");
|
||||
}
|
||||
|
||||
static void localOnlyAndBusyTransactions() {
|
||||
TuningFixture f;
|
||||
f.cmd("set radio.fast.fs off");
|
||||
@@ -447,6 +489,7 @@ int main() {
|
||||
controlsAndPersistence();
|
||||
strictInputsAndUnsupportedFeatures();
|
||||
roundedClockRequiresCapabilityAndBootAdmission();
|
||||
clockOnlyLegacyHalKeepsFeaturesUnavailable();
|
||||
localOnlyAndBusyTransactions();
|
||||
storageFailureKeepsCommittedPolicy();
|
||||
bootRecoveryAndDeferredAdmission();
|
||||
|
||||
@@ -84,9 +84,9 @@ struct Mesh {
|
||||
RadioLibWrapper& radio;
|
||||
bool getDualRadioIdleDelay(uint32_t& out){return radio.getProfileIdleDelay(out);}
|
||||
};
|
||||
struct Board {unsigned calls=0;uint32_t deadline=0;void sleepUntilMicroseconds(uint32_t at){++calls;deadline=at;}};
|
||||
struct ProfileIdleBoard {unsigned calls=0;uint32_t deadline=0;void sleepUntilMicroseconds(uint32_t at){++calls;deadline=at;}};
|
||||
int main(){
|
||||
RadioLibWrapper w;ready(w);Mesh m{w};Board b;
|
||||
RadioLibWrapper w;ready(w);Mesh m{w};ProfileIdleBoard b;
|
||||
auto start=w._esp32_profile_visit_started_us;auto visit=w._esp32_profile_idle_visit_us;
|
||||
elapsed_us=start+visit-14000;assert(idle(w,10));
|
||||
assert(change("set powersaving.test guard_ms 9"));assert(idle(w,5));
|
||||
|
||||
Reference in new issue
Block a user