Add saved controls for radio switching acceleration
This commit is contained in:
1 parent
5a4e81c4d5
commit
61d024bc72
78 files changed
+1696
-160
No files matched your search
@@ -5,6 +5,7 @@
|
||||
#include <Packet.h>
|
||||
#include <Utils.h>
|
||||
#include <RadioProfiles.h>
|
||||
#include <helpers/RadioSwitchTuning.h>
|
||||
#include <helpers/MeshtextConfig.h>
|
||||
#include <helpers/MorseTx.h>
|
||||
#include <helpers/MorseRx.h>
|
||||
@@ -98,6 +99,16 @@ public:
|
||||
virtual void begin() { }
|
||||
virtual RadioProfiles* profiles() { return nullptr; }
|
||||
virtual const RadioProfiles* profiles() const { return nullptr; }
|
||||
virtual RadioSwitchCapabilities switchTuningCapabilities() const { return {}; }
|
||||
virtual RadioSwitchTuning switchTuning() const { return {}; }
|
||||
virtual uint32_t switchSpiHz() const { return 0; }
|
||||
virtual bool configureBootSwitchTuning(const RadioSwitchTuning&) { return false; }
|
||||
// Commit only after admission and verified RX restoration. The callback
|
||||
// must not access the radio; a failed save restores the previous policy.
|
||||
virtual RadioParamApplyResult trySetSwitchTuning(const RadioSwitchTuning&,
|
||||
bool (*)(void*, const RadioSwitchTuning&) = nullptr, void* = nullptr) {
|
||||
return RadioParamApplyResult::FAILED;
|
||||
}
|
||||
virtual bool validateProfile(const RadioProfileParams&) const { return false; }
|
||||
virtual bool supportsMeshtext() const { return false; }
|
||||
virtual bool supportsEthernetTunnel() const { return false; }
|
||||
|
||||
@@ -36,6 +36,9 @@ const char* const TempPath = "/radio_profiles.tmp";
|
||||
// Never considered a boot-time committed image. Losing power before the
|
||||
// acknowledgement leaves the previous saved radio untouched.
|
||||
const char* const ReplyPath = "/radio_profiles.reply";
|
||||
const char* const FastPath = "/radio_fast";
|
||||
const char* const FastBackup = "/radio_fast.bak";
|
||||
const char* const FastTemp = "/radio_fast.tmp";
|
||||
uint32_t checksum(const uint8_t* p, size_t n) {
|
||||
return storage::updateCRC32(0xffffffffU, p, n);
|
||||
}
|
||||
@@ -198,21 +201,176 @@ bool RadioProfileCLI::prepareSavedImage(const char* path, const RadioProfileConf
|
||||
}
|
||||
|
||||
bool RadioProfileCLI::commitSavedImage(const char* path) {
|
||||
if (!fs_ || hold_) return false;
|
||||
if (fs_->exists(ImagePath)) {
|
||||
if (fs_->exists(BackupPath) && !fs_->remove(BackupPath)) return false;
|
||||
if (!fs_->rename(ImagePath, BackupPath)) return false;
|
||||
return commitImage(path, ImagePath, BackupPath, hold_);
|
||||
}
|
||||
|
||||
bool RadioProfileCLI::commitImage(const char* path, const char* active,
|
||||
const char* backup, bool& hold, bool preserve_backup) {
|
||||
if (!fs_ || hold) return false;
|
||||
if (fs_->exists(active)) {
|
||||
if (preserve_backup) {
|
||||
if (!fs_->remove(active)) return false;
|
||||
} else {
|
||||
if (fs_->exists(backup) && !fs_->remove(backup)) return false;
|
||||
if (!fs_->rename(active, backup)) return false;
|
||||
}
|
||||
}
|
||||
if (!fs_->rename(path, ImagePath)) {
|
||||
if (fs_->exists(BackupPath) && !fs_->rename(BackupPath, ImagePath)) hold_ = true;
|
||||
if (!fs_->rename(path, active)) {
|
||||
if (fs_->exists(backup) && !fs_->rename(backup, active)) hold = true;
|
||||
return false;
|
||||
}
|
||||
if (fs_->exists(BackupPath)) fs_->remove(BackupPath);
|
||||
if (fs_->exists(backup)) fs_->remove(backup);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RadioProfileCLI::validFastImage(const uint8_t* bytes, RadioSwitchTuning& value) const {
|
||||
if (bytes[2] != 1) return false;
|
||||
for (size_t n = 5; n < ImageSize - 4; ++n) if (bytes[n]) return false;
|
||||
value.flags = bytes[3]; value.spi_mhz = bytes[4];
|
||||
return radio_ && validRadioSwitchTuning(value, radio_->switchTuningCapabilities());
|
||||
}
|
||||
|
||||
void RadioProfileCLI::configureBootTuning(FILESYSTEM* fs, Radio& radio) {
|
||||
RadioProfileCLI loader;
|
||||
loader.fs_ = fs; loader.radio_ = &radio;
|
||||
loader.loadFastSettings(true);
|
||||
}
|
||||
|
||||
__attribute__((noinline)) void RadioProfileCLI::loadFastSettings(bool boot) {
|
||||
fast_pending_ = fast_hold_ = false;
|
||||
if (!fs_ || !radio_) return;
|
||||
uint8_t bytes[ImageSize];
|
||||
const auto primary = readImage(FastPath, bytes, sizeof(bytes));
|
||||
bool loaded = primary == ImageReadResult::Valid && validFastImage(bytes, fast_saved_);
|
||||
if (!loaded) {
|
||||
const auto backup = readImage(FastBackup, bytes, sizeof(bytes));
|
||||
loaded = backup == ImageReadResult::Valid && validFastImage(bytes, fast_saved_);
|
||||
fast_hold_ = primary == ImageReadResult::Unreadable || backup == ImageReadResult::Unreadable;
|
||||
if (loaded && !fast_hold_) {
|
||||
// Publish the known-good backup before accepting further writes. Never
|
||||
// move a corrupt/unsupported primary over this only valid record.
|
||||
fast_hold_ = (fs_->exists(FastPath) && !fs_->remove(FastPath))
|
||||
|| !fs_->rename(FastBackup, FastPath);
|
||||
}
|
||||
}
|
||||
if (loaded) {
|
||||
fast_pending_ = boot ? !radio_->configureBootSwitchTuning(fast_saved_)
|
||||
: radio_->trySetSwitchTuning(fast_saved_) != RadioParamApplyResult::APPLIED;
|
||||
fast_retry_ms_ = millis() + 1000;
|
||||
}
|
||||
}
|
||||
|
||||
bool RadioProfileCLI::saveFastSettings(const RadioSwitchTuning& value) {
|
||||
if (!fs_ || fast_hold_) return false;
|
||||
// A read error must not be interpreted as a missing/corrupt saved policy.
|
||||
uint8_t previous[ImageSize];
|
||||
RadioSwitchTuning decoded;
|
||||
const auto read = readImage(FastPath, previous, sizeof(previous));
|
||||
if (read == ImageReadResult::Unreadable) return false;
|
||||
const bool preserve = read != ImageReadResult::Valid || !validFastImage(previous, decoded);
|
||||
uint8_t image[ImageSize] = {'R', '2', 1, value.flags, value.spi_mhz};
|
||||
const uint32_t crc = checksum(image, ImageSize - 4);
|
||||
memcpy(image + ImageSize - 4, &crc, 4);
|
||||
if (!writeImage(FastTemp, image, sizeof(image))
|
||||
|| !commitImage(FastTemp, FastPath, FastBackup, fast_hold_, preserve)) return false;
|
||||
fast_saved_ = value;
|
||||
fast_pending_ = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RadioProfileCLI::commitFastSettings(void* context, const RadioSwitchTuning& value) {
|
||||
return static_cast<RadioProfileCLI*>(context)->saveFastSettings(value);
|
||||
}
|
||||
|
||||
__attribute__((noinline)) bool RadioProfileCLI::handleFastSettings(const char* key, const char* args, int verb,
|
||||
bool remote, char* reply, size_t capacity) {
|
||||
if (strcmp(key, "radio.fast") && strncmp(key, "radio.fast.", 11)) return false;
|
||||
if (!radio_) { cli::formatError(reply, capacity, "radio tuning unsupported"); return true; }
|
||||
const auto caps = radio_->switchTuningCapabilities();
|
||||
const auto current = radio_->switchTuning();
|
||||
const char* suffix = key + (key[10] ? 11 : 10);
|
||||
static const char names[] = "warm\0fs\0packet\0iq\0modem\0batch\0buffer";
|
||||
const char* name = names;
|
||||
unsigned feature = 0;
|
||||
while (feature < 7 && strcmp(suffix, name)) { name += strlen(name) + 1; ++feature; }
|
||||
const bool master = !*suffix, spi = !strcmp(suffix, "spi");
|
||||
const bool capabilities = !strcmp(suffix, "caps");
|
||||
if (!master && !spi && !capabilities && feature == 7) {
|
||||
cli::formatError(reply, capacity, "unknown radio.fast control"); return true;
|
||||
}
|
||||
const uint8_t bit = feature < 7 ? 1U << feature : 0;
|
||||
if ((spi && !caps.spi_clocks) || (bit && !(caps.flags & bit))
|
||||
|| (master && !caps.flags && !caps.spi_clocks)) {
|
||||
cli::formatError(reply, capacity, "radio tuning unsupported"); return true;
|
||||
}
|
||||
if (verb == 0 && !*args) {
|
||||
if (capabilities) {
|
||||
snprintf(reply, capacity, "> features:");
|
||||
name = names;
|
||||
for (unsigned n = 0; n < 7; ++n) {
|
||||
if (caps.flags & (1U << n)) appendReply(reply, capacity, " %s", name);
|
||||
name += strlen(name) + 1;
|
||||
}
|
||||
appendReply(reply, capacity, "; spi=%s", caps.spi_clocks ? "0" : "na");
|
||||
for (unsigned n = 1; n <= 4; ++n)
|
||||
if (caps.spi_clocks & (1U << n)) appendReply(reply, capacity, ",%u", 1U << n);
|
||||
} else if (master) {
|
||||
if (caps.spi_clocks) {
|
||||
snprintf(reply, capacity, "> spi=%u (%luHz)", current.spi_mhz,
|
||||
(unsigned long)radio_->switchSpiHz());
|
||||
} else snprintf(reply, capacity, "> spi=na");
|
||||
if (fast_pending_) appendReply(reply, capacity, " pending saved policy");
|
||||
name = names;
|
||||
for (unsigned n = 0; n < 7; ++n) {
|
||||
appendReply(reply, capacity, " %s=%s", name,
|
||||
!(caps.flags & (1U << n)) ? "na" : current.flags & (1U << n) ? "on" : "off");
|
||||
name += strlen(name) + 1;
|
||||
}
|
||||
} else if (spi) snprintf(reply, capacity, "> %u (%luHz; 0=board default)",
|
||||
current.spi_mhz, (unsigned long)radio_->switchSpiHz());
|
||||
else snprintf(reply, capacity, "> %s", current.flags & bit ? "on" : "off");
|
||||
return true;
|
||||
}
|
||||
if (capabilities) {
|
||||
cli::formatError(reply, capacity, "radio.fast.caps is read-only"); return true;
|
||||
}
|
||||
if (verb != 1 || !*args) {
|
||||
cli::formatError(reply, capacity, "use get/set radio.fast[.control] value"); return true;
|
||||
}
|
||||
if (remote || remote_command_) {
|
||||
cli::formatError(reply, capacity, "radio.fast changes require local USB or BLE"); return true;
|
||||
}
|
||||
if (hasReplyMutation() || fast_hold_) {
|
||||
cli::formatError(reply, capacity, "radio settings held; retry after recovery"); return true;
|
||||
}
|
||||
RadioSwitchTuning next = current;
|
||||
if (spi) {
|
||||
// Keep parsing strict without five separate whole-string comparisons.
|
||||
if (!args[1] && strchr("0248", args[0])) next.spi_mhz = args[0] - '0';
|
||||
else if (!strcmp(args, "16")) next.spi_mhz = 16;
|
||||
else { cli::formatError(reply, capacity, "SPI MHz: 0,2,4,8,16 where supported"); return true; }
|
||||
} else {
|
||||
const bool on = !strcmp(args, "on");
|
||||
if (!on && strcmp(args, "off")) { cli::formatError(reply, capacity, "use on or off"); return true; }
|
||||
if (master) {
|
||||
next.flags = on ? caps.flags : 0;
|
||||
next.spi_mhz = !on && (caps.spi_clocks & 2) ? 2 : 0;
|
||||
} else if (on) next.flags |= bit;
|
||||
else next.flags &= ~bit;
|
||||
}
|
||||
if (!validRadioSwitchTuning(next, caps)) {
|
||||
cli::formatError(reply, capacity, "radio tuning unsupported"); return true;
|
||||
}
|
||||
const auto result = radio_->trySetSwitchTuning(next, commitFastSettings, this);
|
||||
if (result == RadioParamApplyResult::APPLIED) snprintf(reply, capacity, "OK - %s=%s (saved)", key, args);
|
||||
else cli::formatError(reply, capacity, result == RadioParamApplyResult::BUSY
|
||||
? "radio busy; retry" : "radio tuning could not be applied/saved");
|
||||
return true;
|
||||
}
|
||||
|
||||
void RadioProfileCLI::begin(FILESYSTEM* fs, Radio* radio, RTCClock* rtc, bool infrastructure_replies) {
|
||||
fs_ = fs; radio_ = radio; rtc_ = rtc; last_ms_ = millis();
|
||||
loadFastSettings();
|
||||
hold_ = false;
|
||||
cross_ = RadioCrossMode::Off;
|
||||
recoverable_corrupt_store_ = false;
|
||||
@@ -575,8 +733,15 @@ bool RadioProfileCLI::applyReplyMutation() {
|
||||
}
|
||||
|
||||
void RadioProfileCLI::loop() {
|
||||
const uint32_t now = millis();
|
||||
if (fast_pending_ && radio_ && (int32_t)(now - fast_retry_ms_) >= 0) {
|
||||
// A saved setting may fail RX verification as well as be temporarily busy.
|
||||
// Avoid repeatedly applying and rolling it back at the main-loop rate.
|
||||
fast_pending_ = radio_->trySetSwitchTuning(fast_saved_) != RadioParamApplyResult::APPLIED;
|
||||
fast_retry_ms_ = now + 1000;
|
||||
}
|
||||
if (!radio_ || !radio_->profiles()) return;
|
||||
const uint32_t now = millis(), elapsed = now - last_ms_;
|
||||
const uint32_t elapsed = now - last_ms_;
|
||||
last_ms_ = now;
|
||||
remote_commit_retry_ms_ = elapsed >= remote_commit_retry_ms_ ? 0 : remote_commit_retry_ms_ - elapsed;
|
||||
if (remote_delivered_ && !remote_commit_retry_ms_ && !applyReplyMutation()) {
|
||||
@@ -784,6 +949,7 @@ bool RadioProfileCLI::handle(const char* command, char* reply, size_t capacity,
|
||||
memcpy(key, text, n); key[n] = 0;
|
||||
const char* args = text + n;
|
||||
while (*args == ' ' || *args == '\t') ++args;
|
||||
if (handleFastSettings(key, args, verb, remote_origin, reply, capacity)) return true;
|
||||
enum { Base, Temporary, Scheduled, ScheduledTemporary, Crossing, Replies,
|
||||
Status, Scan, Timing, TimingAlias, KeyCount };
|
||||
static const char keys[] = "radio2\0tempradio2\0radioat2\0tempradioat2\0radio2.cross\0"
|
||||
|
||||
@@ -56,6 +56,9 @@ class RadioProfileCLI {
|
||||
// readable backup. Read/I/O failures and an intact backup remain read-only.
|
||||
bool recoverable_corrupt_store_ = false;
|
||||
bool publish_pending_ = false;
|
||||
RadioSwitchTuning fast_saved_;
|
||||
uint32_t fast_retry_ms_ = 0;
|
||||
bool fast_pending_ = false, fast_hold_ = false;
|
||||
enum class ImageReadResult : uint8_t { Missing, Valid, Invalid, Unreadable };
|
||||
enum class RemoteMutation : uint8_t {
|
||||
None, Saved, Temporary, Off, TempOff, DeleteTemp
|
||||
@@ -83,6 +86,14 @@ class RadioProfileCLI {
|
||||
bool prepareSavedImage(const char* path, const RadioProfileConfig& config,
|
||||
uint16_t preamble, RadioCrossMode cross, int8_t manual_secondary = -1);
|
||||
bool commitSavedImage(const char* path);
|
||||
bool commitImage(const char* staged, const char* active, const char* backup,
|
||||
bool& hold, bool preserve_backup = false);
|
||||
bool validFastImage(const uint8_t* bytes, RadioSwitchTuning& value) const;
|
||||
void loadFastSettings(bool boot = false);
|
||||
bool saveFastSettings(const RadioSwitchTuning& value);
|
||||
static bool commitFastSettings(void* context, const RadioSwitchTuning& value);
|
||||
bool handleFastSettings(const char* key, const char* args, int verb,
|
||||
bool remote, char* reply, size_t capacity);
|
||||
bool discardCorruptSavedImagesForWrite();
|
||||
bool applyReplyMutation();
|
||||
ImageReadResult readImage(const char* path, uint8_t* bytes, size_t size);
|
||||
@@ -96,6 +107,8 @@ class RadioProfileCLI {
|
||||
const Schedule* schedule = nullptr) const;
|
||||
void appendSecondaryWarnings(char* reply, size_t capacity, const RadioProfiles& preview) const;
|
||||
public:
|
||||
// Call after the settings filesystem mounts and before radio_init().
|
||||
static void configureBootTuning(FILESYSTEM* fs, Radio& radio);
|
||||
void begin(FILESYSTEM* fs, Radio* radio, RTCClock* rtc, bool infrastructure_replies = false);
|
||||
void loop();
|
||||
bool handle(const char* command, char* reply, size_t capacity = 160, bool remote_origin = false);
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
|
||||
namespace mesh {
|
||||
|
||||
// Saved policy for one physical radio, shared by both RF profiles. SPI MHz 0
|
||||
// retains the board's default clock (which need not be an integer MHz).
|
||||
struct RadioSwitchTuning {
|
||||
enum Feature : uint8_t {
|
||||
Warm = 1, Fs = 2, Packet = 4, Iq = 8, Modem = 16,
|
||||
Batch = 32, Buffer = 64
|
||||
};
|
||||
uint8_t flags = 0;
|
||||
uint8_t spi_mhz = 0;
|
||||
bool operator==(const RadioSwitchTuning& other) const {
|
||||
return flags == other.flags && spi_mhz == other.spi_mhz;
|
||||
}
|
||||
bool operator!=(const RadioSwitchTuning& other) const { return !(*this == other); }
|
||||
};
|
||||
|
||||
struct RadioSwitchCapabilities {
|
||||
uint8_t flags = 0;
|
||||
// Bit N permits 2^N MHz; bit 0 (1 MHz) is deliberately unused.
|
||||
uint8_t spi_clocks = 0;
|
||||
};
|
||||
|
||||
inline bool validRadioSwitchTuning(const RadioSwitchTuning& value,
|
||||
const RadioSwitchCapabilities& caps) {
|
||||
if (value.flags & ~caps.flags) return false;
|
||||
if (!value.spi_mhz) return true;
|
||||
for (uint8_t n = 1; n <= 4; ++n)
|
||||
if (value.spi_mhz == (1U << n)) return caps.spi_clocks & (1U << n);
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace mesh
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "RXPowerSaving.h"
|
||||
#include "LR1110RxRecovery.h"
|
||||
#include "LR1110ProfileSwitchState.h"
|
||||
#include <helpers/RadioSwitchTuning.h>
|
||||
#include "LR11x0MorseRfSwitch.h"
|
||||
#include "../MorseRx.h"
|
||||
|
||||
@@ -29,6 +30,7 @@ class CustomLR1110 : public LR1110 {
|
||||
bool _headerSeen = false;
|
||||
bool _rx_boosted = false;
|
||||
LR1110ProfileSwitchState _profileSwitch{MC_LR1110_FAST_PROFILE_SWITCH != 0};
|
||||
uint8_t _profileSwitchFeatures = profileSwitchCapabilities();
|
||||
bool _profileStandbyWarm = false;
|
||||
bool _profilePacketDirty = false;
|
||||
uint32_t _profileFastResumes = 0;
|
||||
@@ -74,11 +76,23 @@ class CustomLR1110 : public LR1110 {
|
||||
return _regulator.finishInitialization(status);
|
||||
}
|
||||
|
||||
void setProfileSwitchOptimization(bool enabled) {
|
||||
_profileSwitch.enabled = enabled;
|
||||
uint8_t profileSwitchCapabilities() const {
|
||||
return MC_LR1110_FAST_PROFILE_SWITCH
|
||||
? mesh::RadioSwitchTuning::Warm | mesh::RadioSwitchTuning::Fs
|
||||
| mesh::RadioSwitchTuning::Packet | mesh::RadioSwitchTuning::Modem
|
||||
| mesh::RadioSwitchTuning::Batch
|
||||
: 0;
|
||||
}
|
||||
uint8_t getProfileSwitchFeatures() const { return _profileSwitchFeatures; }
|
||||
void setProfileSwitchFeatures(uint8_t features) {
|
||||
_profileSwitchFeatures = features & profileSwitchCapabilities();
|
||||
_profileSwitch.enabled = (_profileSwitchFeatures & mesh::RadioSwitchTuning::Warm) != 0;
|
||||
_profileSwitch.invalidate();
|
||||
_profilePacketDirty = false;
|
||||
}
|
||||
void setProfileSwitchOptimization(bool enabled) {
|
||||
setProfileSwitchFeatures(enabled ? profileSwitchCapabilities() : 0);
|
||||
}
|
||||
void setProfileStandbyWarm(bool enabled) {
|
||||
_profileStandbyWarm = enabled;
|
||||
if (!enabled) _profileSwitch.invalidate();
|
||||
@@ -119,8 +133,8 @@ class CustomLR1110 : public LR1110 {
|
||||
const bool warm = _profileSwitch.active && _profileSwitch.rxValid;
|
||||
if (!warm) _profileSwitch.invalidate();
|
||||
// RadioLib 7.7.1 defines STANDBY_XOSC as 0, the same as STANDBY_RC.
|
||||
// The LR1110 command's XOSC selector is 1 (Semtech UM §2.1.1).
|
||||
bool useFs = warm;
|
||||
// The LR1110 command's XOSC selector is 1 (Semtech UM section 2.1.1).
|
||||
bool useFs = warm && (_profileSwitchFeatures & mesh::RadioSwitchTuning::Fs);
|
||||
#ifdef MC_LR1110_HIL
|
||||
useFs = useFs && hilUseFsOnRetune;
|
||||
#endif
|
||||
@@ -227,7 +241,8 @@ class CustomLR1110 : public LR1110 {
|
||||
else if (fabsf(bw - 250.0f) < 0.01f) bwCode = RADIOLIB_LR11X0_LORA_BW_250_0;
|
||||
else if (fabsf(bw - 500.0f) < 0.01f) bwCode = RADIOLIB_LR11X0_LORA_BW_500_0;
|
||||
else return RADIOLIB_ERR_INVALID_BANDWIDTH;
|
||||
if (!_profileSwitch.canResumeFast()) {
|
||||
if (!(_profileSwitchFeatures & mesh::RadioSwitchTuning::Modem)
|
||||
|| !_profileSwitch.canResumeFast()) {
|
||||
uint8_t type = RADIOLIB_LR11X0_PACKET_TYPE_NONE;
|
||||
const int16_t rc = getPacketType(&type);
|
||||
if (rc != RADIOLIB_ERR_NONE) return rc;
|
||||
@@ -237,7 +252,8 @@ class CustomLR1110 : public LR1110 {
|
||||
? (float(uint32_t(1) << sf) / bw >= 16.0f
|
||||
? RADIOLIB_LR11X0_LORA_LDRO_ENABLED : RADIOLIB_LR11X0_LORA_LDRO_DISABLED)
|
||||
: ldrOptimize;
|
||||
const bool unchanged = _profileSwitch.matchesModulation(sf, bwCode, cr - 4, effectiveLdro);
|
||||
const bool unchanged = (_profileSwitchFeatures & mesh::RadioSwitchTuning::Batch)
|
||||
&& _profileSwitch.matchesModulation(sf, bwCode, cr - 4, effectiveLdro);
|
||||
const auto oldSf = spreadingFactor, oldBw = bandwidth, oldCr = codingRate, oldLdro = ldrOptimize;
|
||||
const auto oldBwKhz = bandwidthKhz;
|
||||
spreadingFactor = sf;
|
||||
@@ -467,7 +483,8 @@ class CustomLR1110 : public LR1110 {
|
||||
fastClearCycles += DWT->CYCCNT - segment;
|
||||
segment = DWT->CYCCNT;
|
||||
#endif
|
||||
if (rc == RADIOLIB_ERR_NONE && _profilePacketDirty) {
|
||||
if (rc == RADIOLIB_ERR_NONE && (_profilePacketDirty
|
||||
|| !(_profileSwitchFeatures & mesh::RadioSwitchTuning::Packet))) {
|
||||
rc = setPacketParamsLoRa(preambleLengthLoRa, headerType,
|
||||
RADIOLIB_LR11X0_MAX_PACKET_LENGTH, crcTypeLoRa, invertIQEnabled);
|
||||
}
|
||||
|
||||
@@ -35,6 +35,15 @@ public:
|
||||
void setDeepInitCallback(DeepInitCallback callback) { _deep_init = callback; }
|
||||
|
||||
protected:
|
||||
uint8_t getDriverSwitchFeatures() const override {
|
||||
return static_cast<const CustomLR1110*>(_radio)->getProfileSwitchFeatures();
|
||||
}
|
||||
uint8_t driverSwitchCapabilities() const override {
|
||||
return static_cast<const CustomLR1110*>(_radio)->profileSwitchCapabilities();
|
||||
}
|
||||
void setDriverSwitchFeatures(uint8_t features) override {
|
||||
static_cast<CustomLR1110*>(_radio)->setProfileSwitchFeatures(features);
|
||||
}
|
||||
#if MESH_ENABLE_MESHTEXT_WIRE
|
||||
bool applyProtocol(mesh::RadioProfileProtocol protocol) override {
|
||||
if (protocol != mesh::RadioProfileProtocol::MeshCore
|
||||
@@ -97,19 +106,20 @@ public:
|
||||
}
|
||||
|
||||
protected:
|
||||
bool applyProfileModulation(float bw, uint8_t sf, uint8_t cr) {
|
||||
auto* radio = static_cast<CustomLR1110*>(_radio);
|
||||
if (getDriverSwitchFeatures() & mesh::RadioSwitchTuning::Batch)
|
||||
return radio->setLoRaModulationParams(bw, sf, cr) == RADIOLIB_ERR_NONE;
|
||||
return radio->setSpreadingFactor(sf) == RADIOLIB_ERR_NONE
|
||||
&& radio->setBandwidth(bw) == RADIOLIB_ERR_NONE
|
||||
&& radio->setCodingRate(cr) == RADIOLIB_ERR_NONE;
|
||||
}
|
||||
|
||||
bool applyParams(float freq, float bw, uint8_t sf, uint8_t cr) override {
|
||||
if (static_cast<CustomLR1110*>(_radio)->profileSwitchFailed()) return false;
|
||||
#if MC_LR1110_FAST_PROFILE_SWITCH
|
||||
bool success = ((CustomLR1110 *)_radio)->setFrequency(freq) == RADIOLIB_ERR_NONE
|
||||
&& ((CustomLR1110 *)_radio)->setLoRaModulationParams(bw, sf, cr) == RADIOLIB_ERR_NONE
|
||||
&& applyProfileModulation(bw, sf, cr)
|
||||
&& updatePreamble(sf, bw);
|
||||
#else
|
||||
bool success = ((CustomLR1110 *)_radio)->setFrequency(freq) == RADIOLIB_ERR_NONE
|
||||
&& ((CustomLR1110 *)_radio)->setSpreadingFactor(sf) == RADIOLIB_ERR_NONE
|
||||
&& ((CustomLR1110 *)_radio)->setBandwidth(bw) == RADIOLIB_ERR_NONE
|
||||
&& ((CustomLR1110 *)_radio)->setCodingRate(cr) == RADIOLIB_ERR_NONE
|
||||
&& updatePreamble(sf, bw);
|
||||
#endif
|
||||
if (!success) return false;
|
||||
|
||||
PacketMillis pm = calcMaxPacketMillis(sf, bw, cr, preambleLengthForParams(sf, bw));
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "MeshCore.h"
|
||||
#include "RXPowerSaving.h"
|
||||
#include "SX1262ProfileSwitchState.h"
|
||||
#include <helpers/RadioSwitchTuning.h>
|
||||
#include "SX126xMorseRssi.h"
|
||||
#include "ESP32RadioTrace.h"
|
||||
#if defined(STATION_G3_ESP32)
|
||||
@@ -67,6 +68,7 @@ class CustomSX1262 : public SX1262 {
|
||||
bool _rx_ps_rf_rx_disabled = false;
|
||||
uint32_t _rxDutyCycleTransitionUs = 1000;
|
||||
SX1262ProfileSwitchState _profileSwitch{MC_SX1262_FAST_PROFILE_SWITCH != 0};
|
||||
uint8_t _profileSwitchFeatures = defaultProfileSwitchFeatures();
|
||||
uint32_t _profileFastResumes = 0;
|
||||
bool _coldStandby = true;
|
||||
bool _tcxoWakePending = false;
|
||||
@@ -113,10 +115,53 @@ class CustomSX1262 : public SX1262 {
|
||||
return status == RADIOLIB_ERR_NONE ? raw * -0.5f : NAN;
|
||||
}
|
||||
|
||||
void setProfileSwitchOptimization(bool enabled) {
|
||||
_profileSwitch.enabled = enabled;
|
||||
uint8_t defaultProfileSwitchFeatures() const {
|
||||
return mesh::RadioSwitchTuning::Batch
|
||||
| (MC_SX1262_FAST_PROFILE_SWITCH ? mesh::RadioSwitchTuning::Warm : 0)
|
||||
| (MC_SX1262_FAST_FS ? mesh::RadioSwitchTuning::Fs : 0)
|
||||
| (MC_SX1262_FAST_PACKET_CACHE ? mesh::RadioSwitchTuning::Packet : 0)
|
||||
| (MC_SX1262_FAST_IQ_CACHE ? mesh::RadioSwitchTuning::Iq : 0)
|
||||
| (MC_SX1262_FAST_MODEM_CACHE ? mesh::RadioSwitchTuning::Modem : 0);
|
||||
}
|
||||
uint8_t profileSwitchCapabilities() const {
|
||||
uint8_t features = defaultProfileSwitchFeatures();
|
||||
#ifdef MC_SX1262_HIL
|
||||
// Lab images retain their explicit per-shortcut experiments even when a
|
||||
// production macro defaults off. Ordinary builds never gain these caps.
|
||||
if (MC_SX1262_FAST_PROFILE_SWITCH)
|
||||
features |= mesh::RadioSwitchTuning::Fs | mesh::RadioSwitchTuning::Packet
|
||||
| mesh::RadioSwitchTuning::Iq | mesh::RadioSwitchTuning::Modem;
|
||||
#endif
|
||||
return features;
|
||||
}
|
||||
uint8_t getProfileSwitchFeatures() const {
|
||||
uint8_t features = _profileSwitchFeatures;
|
||||
#ifdef MC_SX1262_HIL
|
||||
// Legacy lab commands directly select these booleans. Reflect them in
|
||||
// the same getter used by the production feature gates and new CLI.
|
||||
features &= ~(mesh::RadioSwitchTuning::Fs | mesh::RadioSwitchTuning::Packet
|
||||
| mesh::RadioSwitchTuning::Iq | mesh::RadioSwitchTuning::Modem);
|
||||
if (hilUseFsOnRetune) features |= mesh::RadioSwitchTuning::Fs;
|
||||
if (hilSkipUnchangedPacket) features |= mesh::RadioSwitchTuning::Packet;
|
||||
if (hilSkipUnchangedIq) features |= mesh::RadioSwitchTuning::Iq;
|
||||
if (hilSkipConfirmedModemQuery) features |= mesh::RadioSwitchTuning::Modem;
|
||||
#endif
|
||||
return features & profileSwitchCapabilities();
|
||||
}
|
||||
void setProfileSwitchFeatures(uint8_t features) {
|
||||
_profileSwitchFeatures = features & profileSwitchCapabilities();
|
||||
#ifdef MC_SX1262_HIL
|
||||
hilUseFsOnRetune = (_profileSwitchFeatures & mesh::RadioSwitchTuning::Fs) != 0;
|
||||
hilSkipUnchangedPacket = (_profileSwitchFeatures & mesh::RadioSwitchTuning::Packet) != 0;
|
||||
hilSkipUnchangedIq = (_profileSwitchFeatures & mesh::RadioSwitchTuning::Iq) != 0;
|
||||
hilSkipConfirmedModemQuery = (_profileSwitchFeatures & mesh::RadioSwitchTuning::Modem) != 0;
|
||||
#endif
|
||||
_profileSwitch.enabled = (_profileSwitchFeatures & mesh::RadioSwitchTuning::Warm) != 0;
|
||||
_profileSwitch.invalidate();
|
||||
}
|
||||
void setProfileSwitchOptimization(bool enabled) {
|
||||
setProfileSwitchFeatures(enabled ? profileSwitchCapabilities() : 0);
|
||||
}
|
||||
void beginProfileSwitch(bool continuousRx) { _profileSwitch.begin(continuousRx, standbyXOSC); }
|
||||
void endProfileSwitch(bool success) { _profileSwitch.end(success); }
|
||||
bool profileSwitchFailed() const { return _profileSwitch.failed(); }
|
||||
@@ -160,9 +205,9 @@ class CustomSX1262 : public SX1262 {
|
||||
const bool alreadyAwake = !_coldStandby && _profileSwitch.active && _profileSwitch.rxValid;
|
||||
if (!alreadyAwake) _profileSwitch.invalidate();
|
||||
int16_t rc;
|
||||
bool useFs = MC_SX1262_FAST_FS != 0;
|
||||
bool useFs = (getProfileSwitchFeatures() & mesh::RadioSwitchTuning::Fs) != 0;
|
||||
#ifdef MC_SX1262_HIL
|
||||
useFs = hilUseFsOnRetune;
|
||||
useFs = useFs && hilUseFsOnRetune;
|
||||
#endif
|
||||
if (alreadyAwake && useFs) {
|
||||
getMod()->setRfSwitchState(Module::MODE_IDLE);
|
||||
@@ -327,9 +372,9 @@ class CustomSX1262 : public SX1262 {
|
||||
}
|
||||
bool queryModem = true;
|
||||
#if MC_SX1262_FAST_MODEM_CACHE || defined(MC_SX1262_HIL)
|
||||
bool reuseModem = MC_SX1262_FAST_MODEM_CACHE != 0;
|
||||
bool reuseModem = (getProfileSwitchFeatures() & mesh::RadioSwitchTuning::Modem) != 0;
|
||||
#ifdef MC_SX1262_HIL
|
||||
reuseModem = hilSkipConfirmedModemQuery;
|
||||
reuseModem = reuseModem && hilSkipConfirmedModemQuery;
|
||||
#endif
|
||||
// rxValid is established only after full RX setup confirms LoRa, or by
|
||||
// resuming that same owned context. A successful owned standby, warm
|
||||
@@ -353,7 +398,8 @@ class CustomSX1262 : public SX1262 {
|
||||
const uint8_t effectiveLdro = ldroAuto
|
||||
? ((float(uint32_t(1) << sf) / bw) >= 16.0f ? 1 : 0)
|
||||
: ldrOptimize;
|
||||
const bool unchanged = _profileSwitch.matchesModulation(
|
||||
const bool unchanged = (_profileSwitchFeatures & mesh::RadioSwitchTuning::Batch)
|
||||
&& _profileSwitch.matchesModulation(
|
||||
sf, codes[index], cr - 4, effectiveLdro);
|
||||
const auto oldSf = spreadingFactor;
|
||||
const auto oldBw = bandwidth;
|
||||
@@ -744,17 +790,17 @@ class CustomSX1262 : public SX1262 {
|
||||
// Existing continuous RX already established buffer bases and the
|
||||
// MeshCore IRQ map (including preamble detection). No TX/CAD/sleep/
|
||||
// reset/staging may intervene without revoking that context.
|
||||
bool skipPacket = MC_SX1262_FAST_PACKET_CACHE != 0;
|
||||
bool skipPacket = (getProfileSwitchFeatures() & mesh::RadioSwitchTuning::Packet) != 0;
|
||||
#ifdef MC_SX1262_HIL
|
||||
skipPacket = hilSkipUnchangedPacket;
|
||||
skipPacket = skipPacket && hilSkipUnchangedPacket;
|
||||
#endif
|
||||
skipPacket = skipPacket && _profileSwitch.matchesPacket(
|
||||
preambleLengthLoRa, crcTypeLoRa, implicitLen,
|
||||
headerType, invertIQEnabled);
|
||||
#if MC_SX1262_FAST_IQ_CACHE || defined(MC_SX1262_HIL)
|
||||
bool reuseIq = MC_SX1262_FAST_IQ_CACHE != 0;
|
||||
bool reuseIq = (getProfileSwitchFeatures() & mesh::RadioSwitchTuning::Iq) != 0;
|
||||
#ifdef MC_SX1262_HIL
|
||||
reuseIq = hilSkipUnchangedIq;
|
||||
reuseIq = reuseIq && hilSkipUnchangedIq;
|
||||
#endif
|
||||
// Capture this capability BEFORE revoking rxValid for the command
|
||||
// sequence. Packet/IQ setters, protocol changes, sleep, reset, TX/CAD,
|
||||
|
||||
@@ -17,6 +17,15 @@ class CustomSX1262Wrapper : public RadioLibWrapper {
|
||||
bool _saved_standby_xosc = false;
|
||||
|
||||
protected:
|
||||
uint8_t getDriverSwitchFeatures() const override {
|
||||
return static_cast<const CustomSX1262*>(_radio)->getProfileSwitchFeatures();
|
||||
}
|
||||
uint8_t driverSwitchCapabilities() const override {
|
||||
return static_cast<const CustomSX1262*>(_radio)->profileSwitchCapabilities();
|
||||
}
|
||||
void setDriverSwitchFeatures(uint8_t features) override {
|
||||
static_cast<CustomSX1262*>(_radio)->setProfileSwitchFeatures(features);
|
||||
}
|
||||
int8_t readReceiveMode() override { return sx126xReceiveMode((SX126x*)_radio); }
|
||||
void beginProfileRetune(bool continuousRx) override {
|
||||
static_cast<CustomSX1262*>(_radio)->beginProfileSwitch(continuousRx);
|
||||
@@ -125,13 +134,22 @@ protected:
|
||||
#endif
|
||||
}
|
||||
|
||||
bool applyProfileModulation(float bw, uint8_t sf, uint8_t cr) {
|
||||
auto* radio = static_cast<CustomSX1262*>(_radio);
|
||||
if (getDriverSwitchFeatures() & mesh::RadioSwitchTuning::Batch)
|
||||
return radio->setLoRaModulationParams(bw, sf, cr) == RADIOLIB_ERR_NONE;
|
||||
return radio->setSpreadingFactor(sf) == RADIOLIB_ERR_NONE
|
||||
&& radio->setBandwidth(bw) == RADIOLIB_ERR_NONE
|
||||
&& radio->setCodingRate(cr) == RADIOLIB_ERR_NONE;
|
||||
}
|
||||
|
||||
bool applyParams(float freq, float bw, uint8_t sf, uint8_t cr) override {
|
||||
if (static_cast<CustomSX1262*>(_radio)->profileSwitchFailed()) return false;
|
||||
#if MESH_HIL_PROFILE_TIMING
|
||||
bool success = ((CustomSX1262 *)_radio)->setFrequency(freq) == RADIOLIB_ERR_NONE;
|
||||
mesh::profiletiming::mark(mesh::profiletiming::Frequency, mesh::profiletiming::readClock());
|
||||
if (success) {
|
||||
success = ((CustomSX1262 *)_radio)->setLoRaModulationParams(bw, sf, cr) == RADIOLIB_ERR_NONE;
|
||||
success = applyProfileModulation(bw, sf, cr);
|
||||
mesh::profiletiming::mark(mesh::profiletiming::Modulation, mesh::profiletiming::readClock());
|
||||
}
|
||||
if (success) {
|
||||
@@ -140,7 +158,7 @@ protected:
|
||||
}
|
||||
#else
|
||||
bool success = ((CustomSX1262 *)_radio)->setFrequency(freq) == RADIOLIB_ERR_NONE
|
||||
&& ((CustomSX1262 *)_radio)->setLoRaModulationParams(bw, sf, cr) == RADIOLIB_ERR_NONE
|
||||
&& applyProfileModulation(bw, sf, cr)
|
||||
&& updatePreamble(sf, bw);
|
||||
#endif
|
||||
if (!success) return false;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#pragma once
|
||||
#include <hal/Arduino/ArduinoHal.h>
|
||||
#include "TunableRadioHal.h"
|
||||
|
||||
#ifndef MC_SX1262_SPI_HZ
|
||||
#define MC_SX1262_SPI_HZ 8000000
|
||||
@@ -8,11 +8,13 @@
|
||||
// Opted-in ESP32 boards only. Keep RadioLib's transaction/CS, BUSY polling and
|
||||
// status parsing unchanged; send each full-duplex buffer in one SPI operation.
|
||||
// SX1262 boards retain their configured default; LR1110 boards pass 16 MHz.
|
||||
class ESP32BufferedRadioHal : public ArduinoHal {
|
||||
class ESP32BufferedRadioHal : public TunableRadioHal {
|
||||
public:
|
||||
explicit ESP32BufferedRadioHal(SPIClass& bus, uint32_t hz = MC_SX1262_SPI_HZ)
|
||||
: ArduinoHal(bus, SPISettings(hz, MSBFIRST, SPI_MODE0)) {}
|
||||
: TunableRadioHal(bus, hz, false, true,
|
||||
hz >= 16000000 ? 16000000 : 8000000) {}
|
||||
void spiTransfer(uint8_t* out, size_t len, uint8_t* in) override {
|
||||
if (len) spi->transferBytes(out, in, len);
|
||||
if (!bufferedTransfersEnabled()) ArduinoHal::spiTransfer(out, len, in);
|
||||
else if (len) spi->transferBytes(out, in, len);
|
||||
}
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <hal/Arduino/ArduinoHal.h>
|
||||
#include "TunableRadioHal.h"
|
||||
#include "NrfRadioProfileExperimentConfig.h"
|
||||
|
||||
#ifndef MC_NRF_RADIO_PROFILE_PRIORITY
|
||||
@@ -26,13 +26,14 @@
|
||||
// nRF52840 SPIClass::transfer(uint8_t) starts one SPIM transaction per byte.
|
||||
// RadioLib sends whole command buffers; keep its CS, BUSY, and status handling
|
||||
// but transfer each buffer in one SPIM transaction. LR1110 supports 16 MHz SPI.
|
||||
class Nrf52BufferedRadioHal : public ArduinoHal {
|
||||
class Nrf52BufferedRadioHal : public TunableRadioHal {
|
||||
#if MC_NRF_RADIO_BUSY_SPIN_POLLS > 0
|
||||
uint8_t _busy_spin_polls_remaining = 0;
|
||||
#endif
|
||||
public:
|
||||
explicit Nrf52BufferedRadioHal(SPIClass& bus, uint32_t hz = 16000000)
|
||||
: ArduinoHal(bus, SPISettings(hz, MSBFIRST, SPI_MODE0)) {}
|
||||
: TunableRadioHal(bus, hz, false, true,
|
||||
hz >= 16000000 ? 16000000 : 8000000) {}
|
||||
|
||||
#if MC_NRF_RADIO_PROFILE_PRIORITY || MC_NRF_RADIO_PROFILE_USB_IRQ_GUARD
|
||||
void spiBeginTransaction() override {
|
||||
@@ -50,7 +51,8 @@ public:
|
||||
|
||||
void spiTransfer(uint8_t* out, size_t len, uint8_t* in) override {
|
||||
if (!len) return;
|
||||
spi->transfer(out, in, len);
|
||||
if (!bufferedTransfersEnabled()) ArduinoHal::spiTransfer(out, len, in);
|
||||
else spi->transfer(out, in, len);
|
||||
#if MC_NRF_RADIO_BUSY_SPIN_POLLS > 0
|
||||
_busy_spin_polls_remaining = MC_NRF_RADIO_BUSY_SPIN_POLLS;
|
||||
#endif
|
||||
|
||||
@@ -6,6 +6,10 @@
|
||||
#include "ProfileSwitchTiming.h"
|
||||
#include "NrfRadioPriorityLease.h"
|
||||
#include "NrfRadioUsbIrqGuard.h"
|
||||
#if defined(ESP32_PLATFORM) || defined(NRF52_PLATFORM) || defined(RP2040_PLATFORM)
|
||||
#include "TunableRadioHal.h"
|
||||
#define MESH_TUNABLE_RADIO_HAL 1
|
||||
#endif
|
||||
#if MESH_HIL_PROFILE_TIMING
|
||||
namespace mesh { namespace profiletiming {
|
||||
extern "C" {
|
||||
@@ -443,6 +447,69 @@ void RadioLibWrapper::endReconfigure(bool resume_rx) {
|
||||
if (resume_rx) startRecv();
|
||||
}
|
||||
|
||||
mesh::RadioSwitchCapabilities RadioLibWrapper::switchTuningCapabilities() const {
|
||||
mesh::RadioSwitchCapabilities caps;
|
||||
#if MESH_TUNABLE_RADIO_HAL
|
||||
auto* hal = TunableRadioHal::find(_radio->getMod()->hal);
|
||||
if (hal) caps = hal->capabilities();
|
||||
#endif
|
||||
caps.flags |= driverSwitchCapabilities();
|
||||
return caps;
|
||||
}
|
||||
|
||||
mesh::RadioSwitchTuning RadioLibWrapper::switchTuning() const {
|
||||
mesh::RadioSwitchTuning value;
|
||||
#if MESH_TUNABLE_RADIO_HAL
|
||||
auto* hal = TunableRadioHal::find(_radio->getMod()->hal);
|
||||
if (hal) value = hal->getTuning();
|
||||
#endif
|
||||
value.flags |= getDriverSwitchFeatures();
|
||||
return value;
|
||||
}
|
||||
|
||||
uint32_t RadioLibWrapper::switchSpiHz() const {
|
||||
#if MESH_TUNABLE_RADIO_HAL
|
||||
auto* hal = TunableRadioHal::find(_radio->getMod()->hal);
|
||||
if (hal) return hal->effectiveHz();
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
mesh::RadioParamApplyResult RadioLibWrapper::trySetSwitchTuning(
|
||||
const mesh::RadioSwitchTuning& value,
|
||||
bool (*persist)(void*, const mesh::RadioSwitchTuning&), void* context) {
|
||||
using Result = mesh::RadioParamApplyResult;
|
||||
if (!mesh::validRadioSwitchTuning(value, switchTuningCapabilities())) return Result::FAILED;
|
||||
const auto previous = switchTuning();
|
||||
if (value == previous) return !persist || persist(context, value) ? Result::APPLIED : Result::FAILED;
|
||||
const uint8_t resume_rx = beginReconfigure();
|
||||
if (resume_rx > 1) return resume_rx == 2 ? Result::BUSY : Result::FAILED;
|
||||
|
||||
// These setters change policy/SPISettings only, never an SPI peripheral or
|
||||
// pin mapping. Clearing owned caches forces the next retune to prove RX.
|
||||
configureBootSwitchTuning(value);
|
||||
_profiles.resetSwitchTest();
|
||||
endReconfigure(resume_rx);
|
||||
const bool resumed = !resume_rx || isInRecvMode();
|
||||
if (resumed && (!persist || persist(context, value))) return Result::APPLIED;
|
||||
|
||||
// There is no SPI in these setters, including after an RX completion was
|
||||
// queued during a filesystem save. Never rearm or clear that unread FIFO.
|
||||
configureBootSwitchTuning(previous);
|
||||
if (!resumed) endReconfigure(resume_rx);
|
||||
return Result::FAILED;
|
||||
}
|
||||
|
||||
bool RadioLibWrapper::configureBootSwitchTuning(const mesh::RadioSwitchTuning& value) {
|
||||
if (!mesh::validRadioSwitchTuning(value, switchTuningCapabilities())) return false;
|
||||
#if MESH_TUNABLE_RADIO_HAL
|
||||
auto* hal = TunableRadioHal::find(_radio->getMod()->hal);
|
||||
if (hal && !hal->setTuning(value)) return false;
|
||||
#endif
|
||||
setDriverSwitchFeatures(value.flags);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RadioLibWrapper::restoreAfterDeepInit() {
|
||||
#if MESH_ENABLE_TX_BEACONS
|
||||
if (_beacon_owned) cacheBeaconRestoreParams();
|
||||
|
||||
@@ -328,6 +328,9 @@ protected:
|
||||
virtual bool radioDeepInit() { return false; }
|
||||
virtual bool supportsRadioDeepInit() const { return false; }
|
||||
virtual bool applyParams(float freq, float bw, uint8_t sf, uint8_t cr) = 0;
|
||||
virtual uint8_t getDriverSwitchFeatures() const { return 0; }
|
||||
virtual uint8_t driverSwitchCapabilities() const { return 0; }
|
||||
virtual void setDriverSwitchFeatures(uint8_t) {}
|
||||
virtual int16_t applyCachedTxPower(int8_t dbm) {
|
||||
return _radio->setOutputPower(dbm);
|
||||
}
|
||||
@@ -483,6 +486,13 @@ public:
|
||||
#endif
|
||||
mesh::RadioProfiles* profiles() override { return &_profiles; }
|
||||
const mesh::RadioProfiles* profiles() const override { return &_profiles; }
|
||||
mesh::RadioSwitchCapabilities switchTuningCapabilities() const override;
|
||||
mesh::RadioSwitchTuning switchTuning() const override;
|
||||
uint32_t switchSpiHz() const override;
|
||||
bool configureBootSwitchTuning(const mesh::RadioSwitchTuning& tuning) override;
|
||||
mesh::RadioParamApplyResult trySetSwitchTuning(const mesh::RadioSwitchTuning& tuning,
|
||||
bool (*persist)(void*, const mesh::RadioSwitchTuning&) = nullptr,
|
||||
void* context = nullptr) override;
|
||||
bool validateProfile(const mesh::RadioProfileParams& params) const override;
|
||||
uint8_t receiveProfile() const override { return _active_profile; }
|
||||
uint32_t receiveProfileGeneration() const override { return _profile_generation; }
|
||||
|
||||
@@ -11,15 +11,16 @@
|
||||
#include "ESP32BufferedRadioHal.h"
|
||||
using SX126xBufferedRadioHalBase = ESP32BufferedRadioHal;
|
||||
#elif defined(RP2040_PLATFORM)
|
||||
#include <hal/Arduino/ArduinoHal.h>
|
||||
#include "TunableRadioHal.h"
|
||||
// Earle Philhower's HardwareSPI bulk API retains full-duplex blocking
|
||||
// completion while filling/draining the controller FIFO in one call.
|
||||
class SX126xBufferedRadioHalBase : public ArduinoHal {
|
||||
class SX126xBufferedRadioHalBase : public TunableRadioHal {
|
||||
public:
|
||||
explicit SX126xBufferedRadioHalBase(SPIClass& bus, uint32_t hz)
|
||||
: ArduinoHal(bus, SPISettings(hz, MSBFIRST, SPI_MODE0)) {}
|
||||
: TunableRadioHal(bus, hz, false, true) {}
|
||||
void spiTransfer(uint8_t* out, size_t len, uint8_t* in) override {
|
||||
if (len) spi->transfer(out, in, len);
|
||||
if (!bufferedTransfersEnabled()) ArduinoHal::spiTransfer(out, len, in);
|
||||
else if (len) spi->transfer(out, in, len);
|
||||
}
|
||||
};
|
||||
#else
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
#pragma once
|
||||
|
||||
#include <hal/Arduino/ArduinoHal.h>
|
||||
#include "../RadioSwitchTuning.h"
|
||||
|
||||
// A radio keeps the board-selected SPI controller and its original lifetime
|
||||
// owner. Runtime tuning only changes transaction settings and transfer style;
|
||||
// it must never remap an nRF controller or restart a shared storage/display bus.
|
||||
class TunableRadioHal : public ArduinoHal {
|
||||
TunableRadioHal* _next;
|
||||
const uint32_t _defaultHz;
|
||||
const uint8_t _spiClocks;
|
||||
const bool _canBuffer;
|
||||
uint8_t _spiMHz = 0;
|
||||
bool _bufferEnabled;
|
||||
|
||||
static TunableRadioHal*& registry() {
|
||||
static TunableRadioHal* head = nullptr;
|
||||
return head;
|
||||
}
|
||||
|
||||
protected:
|
||||
bool bufferedTransfersEnabled() const { return _bufferEnabled; }
|
||||
|
||||
public:
|
||||
explicit TunableRadioHal(SPIClass& bus, uint32_t hz = 8000000,
|
||||
bool initializeBus = false, bool canBuffer = false,
|
||||
uint32_t maxHz = 8000000)
|
||||
: ArduinoHal(bus, SPISettings(hz, MSBFIRST, SPI_MODE0)),
|
||||
_next(registry()), _defaultHz(hz),
|
||||
_spiClocks(2 | 4 | 8 | (maxHz >= 16000000 ? 16 : 0)),
|
||||
_canBuffer(canBuffer), _bufferEnabled(canBuffer) {
|
||||
initInterface = initializeBus;
|
||||
registry() = this;
|
||||
}
|
||||
|
||||
~TunableRadioHal() override {
|
||||
TunableRadioHal** entry = ®istry();
|
||||
while (*entry && *entry != this) entry = &(*entry)->_next;
|
||||
if (*entry) *entry = _next;
|
||||
}
|
||||
|
||||
TunableRadioHal(const TunableRadioHal&) = delete;
|
||||
TunableRadioHal& operator=(const TunableRadioHal&) = delete;
|
||||
|
||||
// Pointer identity avoids RTTI and safely rejects unrelated board HALs.
|
||||
static TunableRadioHal* find(RadioLibHal* hal) {
|
||||
for (TunableRadioHal* entry = registry(); entry; entry = entry->_next)
|
||||
if (static_cast<RadioLibHal*>(entry) == hal) return entry;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
mesh::RadioSwitchTuning getTuning() const {
|
||||
mesh::RadioSwitchTuning result;
|
||||
result.flags = _bufferEnabled ? mesh::RadioSwitchTuning::Buffer : 0;
|
||||
result.spi_mhz = _spiMHz;
|
||||
return result;
|
||||
}
|
||||
|
||||
mesh::RadioSwitchCapabilities capabilities() const {
|
||||
mesh::RadioSwitchCapabilities result;
|
||||
result.flags = _canBuffer ? mesh::RadioSwitchTuning::Buffer : 0;
|
||||
result.spi_clocks = _spiClocks;
|
||||
return result;
|
||||
}
|
||||
|
||||
bool setTuning(const mesh::RadioSwitchTuning& tuning) {
|
||||
mesh::RadioSwitchTuning value = tuning;
|
||||
value.flags &= mesh::RadioSwitchTuning::Buffer;
|
||||
if (!mesh::validRadioSwitchTuning(value, capabilities())) return false;
|
||||
_spiMHz = value.spi_mhz;
|
||||
_bufferEnabled = value.flags & mesh::RadioSwitchTuning::Buffer;
|
||||
spiSettings = SPISettings(effectiveHz(), MSBFIRST, SPI_MODE0);
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t defaultHz() const { return _defaultHz; }
|
||||
uint32_t effectiveHz() const {
|
||||
return _spiMHz ? static_cast<uint32_t>(_spiMHz) * 1000000UL : _defaultHz;
|
||||
}
|
||||
};
|
||||
Reference in new issue
Block a user