diff --git a/docs/cli_commands.md b/docs/cli_commands.md index c316bd6c..02e14ea2 100644 --- a/docs/cli_commands.md +++ b/docs/cli_commands.md @@ -51,7 +51,7 @@ - `time ` **Parameters:** -- `epoc_seconds`: Unix epoc time +- `epoch_seconds`: Unix epoch time --- @@ -134,7 +134,7 @@ --- -### End capture of rx log to node sotrage +### End capture of rx log to node storage **Usage:** `log stop` --- @@ -198,7 +198,7 @@ **Default:** Varies by board -**Notes:** This setting only controls the power level of the LoRa chip. Some nodes have an additional power amplifier stage which increases the total output. Referr to the node's manual for the correct setting to use. **Setting a value too high may violate the laws in your country.** +**Notes:** This setting only controls the power level of the LoRa chip. Some nodes have an additional power amplifier stage which increases the total output. Refer to the node's manual for the correct setting to use. **Setting a value too high may violate the laws in your country.** --- @@ -228,6 +228,7 @@ **Default:** `869.525` **Note:** Requires reboot to apply +**Serial Only:** `set freq ` ### System @@ -293,17 +294,16 @@ #### View or change this node's admin password **Usage:** -- `get password` -- `set password ` +- `password ` **Parameters:** -- `password`: Admin password +- `new_password`: New admin password **Set by build flag:** `ADMIN_PASSWORD` **Default:** `password` -**Note:** Echoed back for confirmation +**Note:** Command reply echoes the updated password for confirmation. **Note:** Any node using this password will be added to the admin ACL list. @@ -768,7 +768,7 @@ region save - `gps advert ` **Parameters:** -- `policy`: `none`|`shared`|`prefs` +- `policy`: `none`|`share`|`prefs` - `none`: don't include location in adverts - `share`: share gps location (from SensorManager) - `prefs`: location stored in node's lat and lon settings diff --git a/examples/companion_radio/DataStore.cpp b/examples/companion_radio/DataStore.cpp index a4e8a619..66268183 100644 --- a/examples/companion_radio/DataStore.cpp +++ b/examples/companion_radio/DataStore.cpp @@ -229,7 +229,8 @@ void DataStore::loadPrefsInt(const char *filename, NodePrefs& _prefs, double& no file.read((uint8_t *)&_prefs.gps_enabled, sizeof(_prefs.gps_enabled)); // 85 file.read((uint8_t *)&_prefs.gps_interval, sizeof(_prefs.gps_interval)); // 86 file.read((uint8_t *)&_prefs.autoadd_config, sizeof(_prefs.autoadd_config)); // 87 - file.read((uint8_t *)&_prefs.sx126x_rx_boosted_gain, sizeof(_prefs.sx126x_rx_boosted_gain)); // 88 + file.read((uint8_t *)&_prefs.autoadd_max_hops, sizeof(_prefs.autoadd_max_hops)); // 88 + file.read((uint8_t *)&_prefs.sx126x_rx_boosted_gain, sizeof(_prefs.sx126x_rx_boosted_gain)); // 89 file.close(); } @@ -266,7 +267,8 @@ void DataStore::savePrefs(const NodePrefs& _prefs, double node_lat, double node_ file.write((uint8_t *)&_prefs.gps_enabled, sizeof(_prefs.gps_enabled)); // 85 file.write((uint8_t *)&_prefs.gps_interval, sizeof(_prefs.gps_interval)); // 86 file.write((uint8_t *)&_prefs.autoadd_config, sizeof(_prefs.autoadd_config)); // 87 - file.write((uint8_t *)&_prefs.sx126x_rx_boosted_gain, sizeof(_prefs.sx126x_rx_boosted_gain)); // 88 + file.write((uint8_t *)&_prefs.autoadd_max_hops, sizeof(_prefs.autoadd_max_hops)); // 88 + file.write((uint8_t *)&_prefs.sx126x_rx_boosted_gain, sizeof(_prefs.sx126x_rx_boosted_gain)); // 89 file.close(); } diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index e2f877c2..5b2866bb 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -318,6 +318,10 @@ bool MyMesh::shouldOverwriteWhenFull() const { return (_prefs.autoadd_config & AUTO_ADD_OVERWRITE_OLDEST) != 0; } +uint8_t MyMesh::getAutoAddMaxHops() const { + return _prefs.autoadd_max_hops; +} + void MyMesh::onContactOverwrite(const uint8_t* pub_key) { _store->deleteBlobByKey(pub_key, PUB_KEY_SIZE); // delete from storage if (_serial->isConnected()) { @@ -1797,12 +1801,16 @@ void MyMesh::handleCmdFrame(size_t len) { } } else if (cmd_frame[0] == CMD_SET_AUTOADD_CONFIG) { _prefs.autoadd_config = cmd_frame[1]; + if (len >= 3) { + _prefs.autoadd_max_hops = min(cmd_frame[2], (uint8_t)64); + } savePrefs(); - writeOKFrame(); + writeOKFrame(); } else if (cmd_frame[0] == CMD_GET_AUTOADD_CONFIG) { int i = 0; out_frame[i++] = RESP_CODE_AUTOADD_CONFIG; out_frame[i++] = _prefs.autoadd_config; + out_frame[i++] = _prefs.autoadd_max_hops; _serial->writeFrame(out_frame, i); } else if (cmd_frame[0] == CMD_GET_ALLOWED_REPEAT_FREQ) { int i = 0; diff --git a/examples/companion_radio/MyMesh.h b/examples/companion_radio/MyMesh.h index 87e6cf33..fe2c19bf 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -119,6 +119,7 @@ protected: bool isAutoAddEnabled() const override; bool shouldAutoAddContactType(uint8_t type) const override; bool shouldOverwriteWhenFull() const override; + uint8_t getAutoAddMaxHops() const override; void onContactsFull() override; void onContactOverwrite(const uint8_t* pub_key) override; bool onContactPathRecv(ContactInfo& from, uint8_t* in_path, uint8_t in_path_len, uint8_t* out_path, uint8_t out_path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) override; diff --git a/examples/companion_radio/NodePrefs.h b/examples/companion_radio/NodePrefs.h index 49e758a5..45a6bbea 100644 --- a/examples/companion_radio/NodePrefs.h +++ b/examples/companion_radio/NodePrefs.h @@ -31,4 +31,5 @@ struct NodePrefs { // persisted to file uint8_t sx126x_rx_boosted_gain; // SX126x RX boosted gain mode (0=power saving, 1=boosted) uint8_t client_repeat; uint8_t path_hash_mode; // which path mode to use when sending + uint8_t autoadd_max_hops; // 0 = no limit, 1 = direct (0 hops), N = up to N-1 hops (max 64) }; \ No newline at end of file diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 9787f3ae..549a57ce 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -396,6 +396,23 @@ File MyMesh::openAppend(const char *fname) { #endif } +static uint8_t max_loop_minimal[] = { 0, /* 1-byte */ 4, /* 2-byte */ 2, /* 3-byte */ 1 }; +static uint8_t max_loop_moderate[] = { 0, /* 1-byte */ 2, /* 2-byte */ 1, /* 3-byte */ 1 }; +static uint8_t max_loop_strict[] = { 0, /* 1-byte */ 1, /* 2-byte */ 1, /* 3-byte */ 1 }; + +bool MyMesh::isLooped(const mesh::Packet* packet, const uint8_t max_counters[]) { + uint8_t hash_size = packet->getPathHashSize(); + uint8_t hash_count = packet->getPathHashCount(); + uint8_t n = 0; + const uint8_t* path = packet->path; + while (hash_count > 0) { // count how many times this node is already in the path + if (self_id.isHashMatch(path, hash_size)) n++; + hash_count--; + path += hash_size; + } + return n >= max_counters[hash_size]; +} + bool MyMesh::allowPacketForward(const mesh::Packet *packet) { if (_prefs.disable_fwd) return false; if (packet->isRouteFlood() && packet->getPathHashCount() >= _prefs.flood_max) return false; @@ -403,6 +420,20 @@ bool MyMesh::allowPacketForward(const mesh::Packet *packet) { MESH_DEBUG_PRINTLN("allowPacketForward: unknown transport code, or wildcard not allowed for FLOOD packet"); return false; } + if (packet->isRouteFlood() && _prefs.loop_detect != LOOP_DETECT_OFF) { + const uint8_t* maximums; + if (_prefs.loop_detect == LOOP_DETECT_MINIMAL) { + maximums = max_loop_minimal; + } else if (_prefs.loop_detect == LOOP_DETECT_MODERATE) { + maximums = max_loop_moderate; + } else { + maximums = max_loop_strict; + } + if (isLooped(packet, maximums)) { + MESH_DEBUG_PRINTLN("allowPacketForward: FLOOD packet loop detected!"); + return false; + } + } return true; } diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index 27b1e190..50297383 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -128,6 +128,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { mesh::Packet* createSelfAdvert(); File openAppend(const char* fname); + bool isLooped(const mesh::Packet* packet, const uint8_t max_counters[]); protected: float getAirtimeBudgetFactor() const override { diff --git a/src/helpers/BaseChatMesh.cpp b/src/helpers/BaseChatMesh.cpp index 5ec678c7..33d7edbe 100644 --- a/src/helpers/BaseChatMesh.cpp +++ b/src/helpers/BaseChatMesh.cpp @@ -141,6 +141,15 @@ void BaseChatMesh::onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id, return; } + // check hop limit for new contacts (0 = no limit, 1 = direct (0 hops), N = up to N-1 hops) + uint8_t max_hops = getAutoAddMaxHops(); + if (max_hops > 0 && packet->getPathHashCount() >= max_hops) { + ContactInfo ci; + populateContactFromAdvert(ci, id, parser, timestamp); + onDiscoveredContact(ci, true, packet->path_len, packet->path); // let UI know + return; + } + from = allocateContactSlot(); if (from == NULL) { ContactInfo ci; diff --git a/src/helpers/BaseChatMesh.h b/src/helpers/BaseChatMesh.h index fd391b98..ab90d581 100644 --- a/src/helpers/BaseChatMesh.h +++ b/src/helpers/BaseChatMesh.h @@ -98,6 +98,7 @@ protected: virtual bool shouldAutoAddContactType(uint8_t type) const { return true; } virtual void onContactsFull() {}; virtual bool shouldOverwriteWhenFull() const { return false; } + virtual uint8_t getAutoAddMaxHops() const { return 0; } // 0 = no limit, 1 = direct (0 hops), N = up to N-1 hops virtual void onContactOverwrite(const uint8_t* pub_key) {}; virtual void onDiscoveredContact(ContactInfo& contact, bool is_new, uint8_t path_len, const uint8_t* path) = 0; virtual ContactInfo* processAck(const uint8_t *data) = 0; diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index e7f29ccf..1c3c6512 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -51,7 +51,7 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { file.read((uint8_t *)&_prefs->tx_power_dbm, sizeof(_prefs->tx_power_dbm)); // 76 file.read((uint8_t *)&_prefs->disable_fwd, sizeof(_prefs->disable_fwd)); // 77 file.read((uint8_t *)&_prefs->advert_interval, sizeof(_prefs->advert_interval)); // 78 - file.read((uint8_t *)pad, 1); // 79 : 1 byte unused + file.read((uint8_t *)&_prefs->sx126x_rx_boosted_gain, sizeof(_prefs->sx126x_rx_boosted_gain)); // 79 file.read((uint8_t *)&_prefs->rx_delay_base, sizeof(_prefs->rx_delay_base)); // 80 file.read((uint8_t *)&_prefs->tx_delay_factor, sizeof(_prefs->tx_delay_factor)); // 84 file.read((uint8_t *)&_prefs->guest_password[0], sizeof(_prefs->guest_password)); // 88 @@ -63,9 +63,9 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { file.read((uint8_t *)&_prefs->multi_acks, sizeof(_prefs->multi_acks)); // 115 file.read((uint8_t *)&_prefs->bw, sizeof(_prefs->bw)); // 116 file.read((uint8_t *)&_prefs->agc_reset_interval, sizeof(_prefs->agc_reset_interval)); // 120 - file.read((uint8_t *)&_prefs->sx126x_rx_boosted_gain, sizeof(_prefs->sx126x_rx_boosted_gain)); // 121 - file.read((uint8_t *)&_prefs->path_hash_mode, sizeof(_prefs->path_hash_mode)); // 122 - file.read(pad, 1); // 123 : 1 byte unused + file.read((uint8_t *)&_prefs->path_hash_mode, sizeof(_prefs->path_hash_mode)); // 121 + file.read((uint8_t *)&_prefs->loop_detect, sizeof(_prefs->loop_detect)); // 122 + file.read(pad, 1); // 123 file.read((uint8_t *)&_prefs->flood_max, sizeof(_prefs->flood_max)); // 124 file.read((uint8_t *)&_prefs->flood_advert_interval, sizeof(_prefs->flood_advert_interval)); // 125 file.read((uint8_t *)&_prefs->interference_threshold, sizeof(_prefs->interference_threshold)); // 126 @@ -141,7 +141,7 @@ void CommonCLI::savePrefs(FILESYSTEM* fs) { file.write((uint8_t *)&_prefs->tx_power_dbm, sizeof(_prefs->tx_power_dbm)); // 76 file.write((uint8_t *)&_prefs->disable_fwd, sizeof(_prefs->disable_fwd)); // 77 file.write((uint8_t *)&_prefs->advert_interval, sizeof(_prefs->advert_interval)); // 78 - file.write((uint8_t *)pad, 1); // 79 : 1 byte unused + file.write((uint8_t *)&_prefs->sx126x_rx_boosted_gain, sizeof(_prefs->sx126x_rx_boosted_gain)); // 79 file.write((uint8_t *)&_prefs->rx_delay_base, sizeof(_prefs->rx_delay_base)); // 80 file.write((uint8_t *)&_prefs->tx_delay_factor, sizeof(_prefs->tx_delay_factor)); // 84 file.write((uint8_t *)&_prefs->guest_password[0], sizeof(_prefs->guest_password)); // 88 @@ -153,9 +153,9 @@ void CommonCLI::savePrefs(FILESYSTEM* fs) { file.write((uint8_t *)&_prefs->multi_acks, sizeof(_prefs->multi_acks)); // 115 file.write((uint8_t *)&_prefs->bw, sizeof(_prefs->bw)); // 116 file.write((uint8_t *)&_prefs->agc_reset_interval, sizeof(_prefs->agc_reset_interval)); // 120 - file.write((uint8_t *)&_prefs->sx126x_rx_boosted_gain, sizeof(_prefs->sx126x_rx_boosted_gain)); // 121 - file.write((uint8_t *)&_prefs->path_hash_mode, sizeof(_prefs->path_hash_mode)); // 122 - file.write(pad, 1); // 123 : 1 byte unused + file.write((uint8_t *)&_prefs->path_hash_mode, sizeof(_prefs->path_hash_mode)); // 121 + file.write((uint8_t *)&_prefs->loop_detect, sizeof(_prefs->loop_detect)); // 122 + file.write(pad, 1); // 123 file.write((uint8_t *)&_prefs->flood_max, sizeof(_prefs->flood_max)); // 124 file.write((uint8_t *)&_prefs->flood_advert_interval, sizeof(_prefs->flood_advert_interval)); // 125 file.write((uint8_t *)&_prefs->interference_threshold, sizeof(_prefs->interference_threshold)); // 126 @@ -208,6 +208,10 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch // Reset clock getRTCClock()->setCurrentTime(1715770351); // 15 May 2024, 8:50pm _board->reboot(); // doesn't return + } else if (memcmp(command, "advert.zerohop", 14) == 0 && (command[14] == 0 || command[14] == ' ')) { + // send zerohop advert + _callbacks->sendSelfAdvertisement(1500, false); // longer delay, give CLI response time to be sent first + strcpy(reply, "OK - zerohop advert sent"); } else if (memcmp(command, "advert", 6) == 0) { // send flood advert _callbacks->sendSelfAdvertisement(1500, true); // longer delay, give CLI response time to be sent first @@ -339,6 +343,16 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch *reply = 0; // set null terminator } else if (memcmp(config, "path.hash.mode", 14) == 0) { sprintf(reply, "> %d", (uint32_t)_prefs->path_hash_mode); + } else if (memcmp(config, "loop.detect", 11) == 0) { + if (_prefs->loop_detect == LOOP_DETECT_OFF) { + strcpy(reply, "> off"); + } else if (_prefs->loop_detect == LOOP_DETECT_MINIMAL) { + strcpy(reply, "> minimal"); + } else if (_prefs->loop_detect == LOOP_DETECT_MODERATE) { + strcpy(reply, "> moderate"); + } else { + strcpy(reply, "> strict"); + } } else if (memcmp(config, "tx", 2) == 0 && (config[2] == 0 || config[2] == ' ')) { sprintf(reply, "> %d", (int32_t) _prefs->tx_power_dbm); } else if (memcmp(config, "freq", 4) == 0) { @@ -587,6 +601,26 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch } else { strcpy(reply, "Error, must be 0,1, or 2"); } + } else if (memcmp(config, "loop.detect ", 12) == 0) { + config += 12; + uint8_t mode; + if (memcmp(config, "off", 3) == 0) { + mode = LOOP_DETECT_OFF; + } else if (memcmp(config, "minimal", 7) == 0) { + mode = LOOP_DETECT_MINIMAL; + } else if (memcmp(config, "moderate", 8) == 0) { + mode = LOOP_DETECT_MODERATE; + } else if (memcmp(config, "strict", 6) == 0) { + mode = LOOP_DETECT_STRICT; + } else { + mode = 0xFF; + strcpy(reply, "Error, must be: off, minimal, moderate, or strict"); + } + if (mode != 0xFF) { + _prefs->loop_detect = mode; + savePrefs(); + strcpy(reply, "OK"); + } } else if (memcmp(config, "tx ", 3) == 0) { _prefs->tx_power_dbm = atoi(&config[3]); savePrefs(); @@ -733,6 +767,9 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch LocationProvider * l = _sensors->getLocationProvider(); if (l != NULL) { l->syncTime(); + strcpy(reply, "ok"); + } else { + strcpy(reply, "gps provider not found"); } } else if (memcmp(command, "gps setloc", 10) == 0) { _prefs->node_lat = _sensors->node_lat; @@ -762,7 +799,7 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch _prefs->advert_loc_policy = ADVERT_LOC_SHARE; savePrefs(); strcpy(reply, "ok"); - } else if (memcmp(command+11, "prefs", 4) == 0) { + } else if (memcmp(command+11, "prefs", 5) == 0) { _prefs->advert_loc_policy = ADVERT_LOC_PREFS; savePrefs(); strcpy(reply, "ok"); diff --git a/src/helpers/CommonCLI.h b/src/helpers/CommonCLI.h index 7005556e..7bb489d9 100644 --- a/src/helpers/CommonCLI.h +++ b/src/helpers/CommonCLI.h @@ -13,6 +13,11 @@ #define ADVERT_LOC_SHARE 1 #define ADVERT_LOC_PREFS 2 +#define LOOP_DETECT_OFF 0 +#define LOOP_DETECT_MINIMAL 1 +#define LOOP_DETECT_MODERATE 2 +#define LOOP_DETECT_STRICT 3 + struct NodePrefs { // persisted to file float airtime_factor; char node_name[32]; @@ -54,6 +59,7 @@ struct NodePrefs { // persisted to file char owner_info[120]; uint8_t sx126x_rx_boosted_gain; // power settings uint8_t path_hash_mode; // which path mode to use when sending + uint8_t loop_detect; }; class CommonCLICallbacks { diff --git a/src/helpers/RefCountedDigitalPin.h b/src/helpers/RefCountedDigitalPin.h index 753f6c30..f30c4c58 100644 --- a/src/helpers/RefCountedDigitalPin.h +++ b/src/helpers/RefCountedDigitalPin.h @@ -20,7 +20,10 @@ public: digitalWrite(_pin, _active); } } + void release() { + if (_claims == 0) return; // avoid negative _claims + _claims--; if (_claims == 0) { digitalWrite(_pin, !_active); diff --git a/src/helpers/radiolib/CustomLLCC68Wrapper.h b/src/helpers/radiolib/CustomLLCC68Wrapper.h index f7dd7a9f..9e783a95 100644 --- a/src/helpers/radiolib/CustomLLCC68Wrapper.h +++ b/src/helpers/radiolib/CustomLLCC68Wrapper.h @@ -2,6 +2,7 @@ #include "CustomLLCC68.h" #include "RadioLibWrappers.h" +#include "SX126xReset.h" class CustomLLCC68Wrapper : public RadioLibWrapper { public: @@ -19,4 +20,6 @@ public: int sf = ((CustomLLCC68 *)_radio)->spreadingFactor; return packetScoreInt(snr, sf, packet_len); } + + void doResetAGC() override { sx126xResetAGC((SX126x *)_radio); } }; diff --git a/src/helpers/radiolib/CustomLR1110.h b/src/helpers/radiolib/CustomLR1110.h index e4332013..b1f68080 100644 --- a/src/helpers/radiolib/CustomLR1110.h +++ b/src/helpers/radiolib/CustomLR1110.h @@ -20,6 +20,8 @@ class CustomLR1110 : public LR1110 { return len; } + float getFreqMHz() const { return freqMHz; } + bool isReceiving() { uint16_t irq = getIrqStatus(); bool detected = ((irq & RADIOLIB_LR11X0_IRQ_SYNC_WORD_HEADER_VALID) || (irq & RADIOLIB_LR11X0_IRQ_PREAMBLE_DETECTED)); diff --git a/src/helpers/radiolib/CustomLR1110Wrapper.h b/src/helpers/radiolib/CustomLR1110Wrapper.h index 947bb51d..a1e0a493 100644 --- a/src/helpers/radiolib/CustomLR1110Wrapper.h +++ b/src/helpers/radiolib/CustomLR1110Wrapper.h @@ -2,11 +2,13 @@ #include "CustomLR1110.h" #include "RadioLibWrappers.h" +#include "LR11x0Reset.h" class CustomLR1110Wrapper : public RadioLibWrapper { public: CustomLR1110Wrapper(CustomLR1110& radio, mesh::MainBoard& board) : RadioLibWrapper(radio, board) { } - bool isReceivingPacket() override { + void doResetAGC() override { lr11x0ResetAGC((LR11x0 *)_radio, ((CustomLR1110 *)_radio)->getFreqMHz()); } + bool isReceivingPacket() override { return ((CustomLR1110 *)_radio)->isReceiving(); } float getCurrentRSSI() override { diff --git a/src/helpers/radiolib/CustomSTM32WLxWrapper.h b/src/helpers/radiolib/CustomSTM32WLxWrapper.h index 9e2d0441..e3e52029 100644 --- a/src/helpers/radiolib/CustomSTM32WLxWrapper.h +++ b/src/helpers/radiolib/CustomSTM32WLxWrapper.h @@ -2,6 +2,7 @@ #include "CustomSTM32WLx.h" #include "RadioLibWrappers.h" +#include "SX126xReset.h" #include class CustomSTM32WLxWrapper : public RadioLibWrapper { @@ -20,4 +21,6 @@ public: int sf = ((CustomSTM32WLx *)_radio)->spreadingFactor; return packetScoreInt(snr, sf, packet_len); } + + void doResetAGC() override { sx126xResetAGC((SX126x *)_radio); } }; diff --git a/src/helpers/radiolib/CustomSX1262Wrapper.h b/src/helpers/radiolib/CustomSX1262Wrapper.h index 90243970..97630671 100644 --- a/src/helpers/radiolib/CustomSX1262Wrapper.h +++ b/src/helpers/radiolib/CustomSX1262Wrapper.h @@ -2,6 +2,7 @@ #include "CustomSX1262.h" #include "RadioLibWrappers.h" +#include "SX126xReset.h" #ifndef USE_SX1262 #define USE_SX1262 @@ -26,4 +27,6 @@ public: virtual void powerOff() override { ((CustomSX1262 *)_radio)->sleep(false); } + + void doResetAGC() override { sx126xResetAGC((SX126x *)_radio); } }; diff --git a/src/helpers/radiolib/CustomSX1268Wrapper.h b/src/helpers/radiolib/CustomSX1268Wrapper.h index 410b3665..1370f4cc 100644 --- a/src/helpers/radiolib/CustomSX1268Wrapper.h +++ b/src/helpers/radiolib/CustomSX1268Wrapper.h @@ -2,6 +2,7 @@ #include "CustomSX1268.h" #include "RadioLibWrappers.h" +#include "SX126xReset.h" #ifndef USE_SX1268 #define USE_SX1268 @@ -23,4 +24,6 @@ public: int sf = ((CustomSX1268 *)_radio)->spreadingFactor; return packetScoreInt(snr, sf, packet_len); } + + void doResetAGC() override { sx126xResetAGC((SX126x *)_radio); } }; diff --git a/src/helpers/radiolib/LR11x0Reset.h b/src/helpers/radiolib/LR11x0Reset.h new file mode 100644 index 00000000..d06ffc53 --- /dev/null +++ b/src/helpers/radiolib/LR11x0Reset.h @@ -0,0 +1,21 @@ +#pragma once + +#include + +// Full receiver reset for LR11x0-family chips (LR1110, LR1120, LR1121). +// Warm sleep powers down analog, calibrate(0x3F) refreshes all calibration blocks, +// then re-applies RX settings that calibration may reset. +inline void lr11x0ResetAGC(LR11x0* radio, float freqMHz) { + radio->sleep(true, 0); + radio->standby(RADIOLIB_LR11X0_STANDBY_RC, true); + + radio->calibrate(RADIOLIB_LR11X0_CALIBRATE_ALL); + + // calibrate(0x3F) defaults image calibration to 902-928MHz band. + // Re-calibrate for the actual operating frequency (band=4MHz matches RadioLib default). + radio->calibrateImageRejection(freqMHz - 4.0f, freqMHz + 4.0f); + +#ifdef RX_BOOSTED_GAIN + radio->setRxBoostedGainMode(RX_BOOSTED_GAIN); +#endif +} diff --git a/src/helpers/radiolib/RadioLibWrappers.cpp b/src/helpers/radiolib/RadioLibWrappers.cpp index cf3e1266..2216ca8f 100644 --- a/src/helpers/radiolib/RadioLibWrappers.cpp +++ b/src/helpers/radiolib/RadioLibWrappers.cpp @@ -53,13 +53,24 @@ void RadioLibWrapper::triggerNoiseFloorCalibrate(int threshold) { } } +void RadioLibWrapper::doResetAGC() { + _radio->sleep(); // warm sleep to reset analog frontend +} + void RadioLibWrapper::resetAGC() { // make sure we're not mid-receive of packet! if ((state & STATE_INT_READY) != 0 || isReceivingPacket()) return; - // NOTE: according to higher powers, just issuing RadioLib's startReceive() will reset the AGC. - // revisit this if a better impl is discovered. + doResetAGC(); state = STATE_IDLE; // trigger a startReceive() + + // Reset noise floor sampling so it reconverges from scratch. + // Without this, a stuck _noise_floor of -120 makes the sampling threshold + // too low (-106) to accept normal samples (~-105), self-reinforcing the + // stuck value even after the receiver has recovered. + _noise_floor = 0; + _num_floor_samples = 0; + _floor_sample_sum = 0; } void RadioLibWrapper::loop() { diff --git a/src/helpers/radiolib/RadioLibWrappers.h b/src/helpers/radiolib/RadioLibWrappers.h index 9ac1bbae..b338b03a 100644 --- a/src/helpers/radiolib/RadioLibWrappers.h +++ b/src/helpers/radiolib/RadioLibWrappers.h @@ -16,6 +16,7 @@ protected: void startRecv(); float packetScoreInt(float snr, int sf, int packet_len); virtual bool isReceivingPacket() =0; + virtual void doResetAGC(); public: RadioLibWrapper(PhysicalLayer& radio, mesh::MainBoard& board) : _radio(&radio), _board(&board) { n_recv = n_sent = 0; } diff --git a/src/helpers/radiolib/SX126xReset.h b/src/helpers/radiolib/SX126xReset.h new file mode 100644 index 00000000..39ddb73e --- /dev/null +++ b/src/helpers/radiolib/SX126xReset.h @@ -0,0 +1,37 @@ +#pragma once + +#include + +// Full receiver reset for all SX126x-family chips (SX1262, SX1268, LLCC68, STM32WLx). +// Warm sleep powers down analog, Calibrate(0x7F) refreshes ADC/PLL/image calibration, +// then re-applies RX settings that calibration may reset. +inline void sx126xResetAGC(SX126x* radio) { + radio->sleep(true); + radio->standby(RADIOLIB_SX126X_STANDBY_RC, true); + + uint8_t calData = RADIOLIB_SX126X_CALIBRATE_ALL; + radio->mod->SPIwriteStream(RADIOLIB_SX126X_CMD_CALIBRATE, &calData, 1, true, false); + radio->mod->hal->delay(5); + uint32_t start = millis(); + while (radio->mod->hal->digitalRead(radio->mod->getGpio())) { + if (millis() - start > 50) break; + radio->mod->hal->yield(); + } + + // Calibrate(0x7F) defaults image calibration to 902-928MHz band. + // Re-calibrate for the actual operating frequency. + radio->calibrateImage(radio->freqMHz); + +#ifdef SX126X_DIO2_AS_RF_SWITCH + radio->setDio2AsRfSwitch(SX126X_DIO2_AS_RF_SWITCH); +#endif +#ifdef SX126X_RX_BOOSTED_GAIN + radio->setRxBoostedGainMode(SX126X_RX_BOOSTED_GAIN); +#endif +#ifdef SX126X_REGISTER_PATCH + uint8_t r_data = 0; + radio->readRegister(0x8B5, &r_data, 1); + r_data |= 0x01; + radio->writeRegister(0x8B5, &r_data, 1); +#endif +} diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index a75d378c..07807011 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -683,7 +683,7 @@ void EnvironmentSensorManager::start_gps() { _location->begin(); _location->reset(); -#ifndef PIN_GPS_RESET +#ifndef PIN_GPS_EN MESH_DEBUG_PRINTLN("Start GPS is N/A on this board. Actual GPS state unchanged"); #endif } @@ -707,7 +707,9 @@ void EnvironmentSensorManager::loop() { static long next_gps_update = 0; #if ENV_INCLUDE_GPS - _location->loop(); + if (gps_active) { + _location->loop(); + } if (millis() > next_gps_update) { if(gps_active){ diff --git a/src/helpers/sensors/MicroNMEALocationProvider.h b/src/helpers/sensors/MicroNMEALocationProvider.h index 574570a3..1de75327 100644 --- a/src/helpers/sensors/MicroNMEALocationProvider.h +++ b/src/helpers/sensors/MicroNMEALocationProvider.h @@ -79,7 +79,10 @@ public : if (_pin_en != -1) { digitalWrite(_pin_en, !PIN_GPS_EN_ACTIVE); } - if (_peripher_power) _peripher_power->release(); + if (_pin_reset != -1) { + digitalWrite(_pin_reset, GPS_RESET_FORCE); + } + if (_peripher_power) _peripher_power->release(); } bool isEnabled() override { diff --git a/variants/heltec_tracker/platformio.ini b/variants/heltec_tracker/platformio.ini index e5abf79c..e0a8f5fa 100644 --- a/variants/heltec_tracker/platformio.ini +++ b/variants/heltec_tracker/platformio.ini @@ -33,6 +33,11 @@ build_flags = -D PIN_TFT_LEDA_CTL=21 ; LEDK (switches on/off via mosfet to create the ground) -D PIN_GPS_RX=33 -D PIN_GPS_TX=34 + -D PIN_GPS_EN=35 ; N-ch MOSFET Q2 drives P-ch high-side switch → active HIGH (default) + -D PIN_GPS_RESET=36 + -D PIN_GPS_RESET_ACTIVE=LOW + -D GPS_BAUD_RATE=115200 + -D ENV_INCLUDE_GPS=1 -D SX126X_DIO2_AS_RF_SWITCH=true -D SX126X_DIO3_TCXO_VOLTAGE=1.8 -D SX126X_CURRENT_LIMIT=140 diff --git a/variants/heltec_tracker/target.cpp b/variants/heltec_tracker/target.cpp index 88dfa0c5..0256be5c 100644 --- a/variants/heltec_tracker/target.cpp +++ b/variants/heltec_tracker/target.cpp @@ -16,7 +16,8 @@ WRAPPER_CLASS radio_driver(radio, board); ESP32RTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); -MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1); +// GPS_EN (GPIO35) drives N-ch MOSFET → P-ch high-side switch; GPS_RESET (GPIO36) active LOW +MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock, GPS_RESET, GPS_EN, &board.periph_power); HWTSensorManager sensors = HWTSensorManager(nmea); #ifdef DISPLAY_CLASS @@ -58,18 +59,16 @@ mesh::LocalIdentity radio_new_identity() { void HWTSensorManager::start_gps() { if (!gps_active) { - board.periph_power.claim(); - + _location->begin(); // Claims periph_power via RefCountedDigitalPin gps_active = true; - Serial1.println("$CFGSYS,h35155*68"); + Serial1.println("$CFGSYS,h35155*68"); // Configure GPS for all constellations } } void HWTSensorManager::stop_gps() { if (gps_active) { gps_active = false; - - board.periph_power.release(); + _location->stop(); // Releases periph_power via RefCountedDigitalPin } } diff --git a/variants/heltec_tracker/target.h b/variants/heltec_tracker/target.h index 2bfc7faa..081300b9 100644 --- a/variants/heltec_tracker/target.h +++ b/variants/heltec_tracker/target.h @@ -28,6 +28,7 @@ public: const char* getSettingName(int i) const override; const char* getSettingValue(int i) const override; bool setSettingValue(const char* name, const char* value) override; + LocationProvider* getLocationProvider() override { return _location; } }; extern HeltecV3Board board; diff --git a/variants/heltec_tracker_v2/HeltecTrackerV2Board.cpp b/variants/heltec_tracker_v2/HeltecTrackerV2Board.cpp index 4975d5cd..bd7f680e 100644 --- a/variants/heltec_tracker_v2/HeltecTrackerV2Board.cpp +++ b/variants/heltec_tracker_v2/HeltecTrackerV2Board.cpp @@ -6,18 +6,26 @@ void HeltecTrackerV2Board::begin() { pinMode(PIN_ADC_CTRL, OUTPUT); digitalWrite(PIN_ADC_CTRL, LOW); // Initially inactive + // Set up digital GPIO registers before releasing RTC hold. The hold latches + // the pad state including function select, so register writes accumulate + // without affecting the pad. On hold release, all changes apply atomically + // (IO MUX switches to digital GPIO with output already HIGH — no glitch). pinMode(P_LORA_PA_POWER, OUTPUT); digitalWrite(P_LORA_PA_POWER,HIGH); + rtc_gpio_hold_dis((gpio_num_t)P_LORA_PA_POWER); - rtc_gpio_hold_dis((gpio_num_t)P_LORA_PA_EN); pinMode(P_LORA_PA_EN, OUTPUT); digitalWrite(P_LORA_PA_EN,HIGH); + rtc_gpio_hold_dis((gpio_num_t)P_LORA_PA_EN); pinMode(P_LORA_PA_TX_EN, OUTPUT); digitalWrite(P_LORA_PA_TX_EN,LOW); - periph_power.begin(); - esp_reset_reason_t reason = esp_reset_reason(); + if (reason != ESP_RST_DEEPSLEEP) { + delay(1); // GC1109 startup time after cold power-on + } + + periph_power.begin(); if (reason == ESP_RST_DEEPSLEEP) { long wakeup_source = esp_sleep_get_ext1_wakeup_status(); if (wakeup_source & (1 << P_LORA_DIO_1)) { // received a LoRa packet (while in deep sleep) @@ -48,7 +56,9 @@ void HeltecTrackerV2Board::begin() { rtc_gpio_hold_en((gpio_num_t)P_LORA_NSS); - rtc_gpio_hold_en((gpio_num_t)P_LORA_PA_EN); //It also needs to be enabled in receive mode + // Hold GC1109 FEM pins during sleep to keep LNA active for RX wake + rtc_gpio_hold_en((gpio_num_t)P_LORA_PA_POWER); + rtc_gpio_hold_en((gpio_num_t)P_LORA_PA_EN); if (pin_wake_btn < 0) { esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet diff --git a/variants/heltec_tracker_v2/platformio.ini b/variants/heltec_tracker_v2/platformio.ini index e92b155f..18ee8628 100644 --- a/variants/heltec_tracker_v2/platformio.ini +++ b/variants/heltec_tracker_v2/platformio.ini @@ -18,11 +18,11 @@ build_flags = -D P_LORA_SCLK=9 -D P_LORA_MISO=11 -D P_LORA_MOSI=10 - -D P_LORA_PA_POWER=7 ;power en - -D P_LORA_PA_EN=4 - -D P_LORA_PA_TX_EN=46 ;enable tx - -D LORA_TX_POWER=10 ;If it is configured as 10 here, the final output will be 22 dbm. - -D MAX_LORA_TX_POWER=22 ;Max SX1262 output + -D P_LORA_PA_POWER=7 ; VFEM_Ctrl - GC1109 LDO power enable + -D P_LORA_PA_EN=4 ; CSD - GC1109 chip enable (HIGH=on) + -D P_LORA_PA_TX_EN=46 ; CPS - GC1109 PA mode (HIGH=full PA, LOW=bypass) + -D LORA_TX_POWER=10 ; 10dBm + ~11dB GC1109 gain = ~21dBm output + -D MAX_LORA_TX_POWER=22 ; Max SX1262 output -> ~28dBm at antenna -D SX126X_DIO2_AS_RF_SWITCH=true -D SX126X_DIO3_TCXO_VOLTAGE=1.8 -D SX126X_CURRENT_LIMIT=140 diff --git a/variants/heltec_v4/HeltecV4Board.cpp b/variants/heltec_v4/HeltecV4Board.cpp index 92f93437..8186f2d4 100644 --- a/variants/heltec_v4/HeltecV4Board.cpp +++ b/variants/heltec_v4/HeltecV4Board.cpp @@ -7,19 +7,26 @@ void HeltecV4Board::begin() { pinMode(PIN_ADC_CTRL, OUTPUT); digitalWrite(PIN_ADC_CTRL, LOW); // Initially inactive + // Set up digital GPIO registers before releasing RTC hold. The hold latches + // the pad state including function select, so register writes accumulate + // without affecting the pad. On hold release, all changes apply atomically + // (IO MUX switches to digital GPIO with output already HIGH — no glitch). pinMode(P_LORA_PA_POWER, OUTPUT); digitalWrite(P_LORA_PA_POWER,HIGH); + rtc_gpio_hold_dis((gpio_num_t)P_LORA_PA_POWER); - rtc_gpio_hold_dis((gpio_num_t)P_LORA_PA_EN); pinMode(P_LORA_PA_EN, OUTPUT); digitalWrite(P_LORA_PA_EN,HIGH); + rtc_gpio_hold_dis((gpio_num_t)P_LORA_PA_EN); pinMode(P_LORA_PA_TX_EN, OUTPUT); digitalWrite(P_LORA_PA_TX_EN,LOW); + esp_reset_reason_t reason = esp_reset_reason(); + if (reason != ESP_RST_DEEPSLEEP) { + delay(1); // GC1109 startup time after cold power-on + } periph_power.begin(); - - esp_reset_reason_t reason = esp_reset_reason(); if (reason == ESP_RST_DEEPSLEEP) { long wakeup_source = esp_sleep_get_ext1_wakeup_status(); if (wakeup_source & (1 << P_LORA_DIO_1)) { // received a LoRa packet (while in deep sleep) @@ -50,7 +57,9 @@ void HeltecV4Board::begin() { rtc_gpio_hold_en((gpio_num_t)P_LORA_NSS); - rtc_gpio_hold_en((gpio_num_t)P_LORA_PA_EN); //It also needs to be enabled in receive mode + // Hold GC1109 FEM pins during sleep to keep LNA active for RX wake + rtc_gpio_hold_en((gpio_num_t)P_LORA_PA_POWER); + rtc_gpio_hold_en((gpio_num_t)P_LORA_PA_EN); if (pin_wake_btn < 0) { esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet diff --git a/variants/heltec_v4/platformio.ini b/variants/heltec_v4/platformio.ini index 2d2766c5..74c8ef9c 100644 --- a/variants/heltec_v4/platformio.ini +++ b/variants/heltec_v4/platformio.ini @@ -23,7 +23,7 @@ build_flags = -D P_LORA_PA_TX_EN=46 ; PA CPS - GC1109 TX PA full(High) / bypass(Low) -D PIN_USER_BTN=0 -D PIN_VEXT_EN=36 - -D PIN_VEXT_EN_ACTIVE=LOW + -D PIN_VEXT_EN_ACTIVE=HIGH -D LORA_TX_POWER=10 ;If it is configured as 10 here, the final output will be 22 dbm. -D MAX_LORA_TX_POWER=22 ; Max SX1262 output -D SX126X_REGISTER_PATCH=1 ; Patch register 0x8B5 for improved RX @@ -55,8 +55,6 @@ build_flags = -D PIN_BOARD_SDA=17 -D PIN_BOARD_SCL=18 -D PIN_OLED_RESET=21 - -D ENV_PIN_SDA=4 - -D ENV_PIN_SCL=3 build_src_filter= ${Heltec_lora32_v4.build_src_filter} lib_deps = ${Heltec_lora32_v4.lib_deps} diff --git a/variants/heltec_v4/target.cpp b/variants/heltec_v4/target.cpp index 7afed3db..22b7ee25 100644 --- a/variants/heltec_v4/target.cpp +++ b/variants/heltec_v4/target.cpp @@ -24,7 +24,7 @@ AutoDiscoverRTCClock rtc_clock(fallback_clock); #endif #ifdef DISPLAY_CLASS - DISPLAY_CLASS display(&(board.periph_power)); + DISPLAY_CLASS display(NULL); MomentaryButton user_btn(PIN_USER_BTN, 1000, true); #endif diff --git a/variants/ikoka_handheld_nrf/platformio.ini b/variants/ikoka_handheld_nrf/platformio.ini index 2b2ff9ba..9f48cd41 100644 --- a/variants/ikoka_handheld_nrf/platformio.ini +++ b/variants/ikoka_handheld_nrf/platformio.ini @@ -1,5 +1,7 @@ [ikoka_handheld_nrf] extends = nrf52_base +board = seeed-xiao-afruitnrf52-nrf52840 +board_build.ldscript = boards/nrf52840_s140_v7.ld build_flags = ${nrf52_base.build_flags} ${sensor_base.build_flags} -I lib/nrf52/s140_nrf52_7.3.0_API/include @@ -49,7 +51,8 @@ build_src_filter = ${ikoka_handheld_nrf.build_src_filter} +<../examples/companion_radio/*.cpp> [env:ikoka_handheld_nrf_e22_30dbm_096_companion_radio_ble] -extends = ikoka_nrf52 +extends = ikoka_handheld_nrf +board_build.ldscript = boards/nrf52840_s140_v7_extrafs.ld build_flags = ${ikoka_handheld_nrf_ssd1306_companion.build_flags} -D BLE_PIN_CODE=123456 -D LORA_TX_POWER=20 @@ -57,7 +60,8 @@ build_src_filter = ${ikoka_handheld_nrf_ssd1306_companion.build_src_filter} + [env:ikoka_handheld_nrf_e22_30dbm_096_rotated_companion_radio_ble] -extends = ikoka_nrf52 +extends = ikoka_handheld_nrf +board_build.ldscript = boards/nrf52840_s140_v7_extrafs.ld build_flags = ${ikoka_handheld_nrf_ssd1306_companion.build_flags} -D BLE_PIN_CODE=123456 -D LORA_TX_POWER=20 @@ -66,20 +70,22 @@ build_src_filter = ${ikoka_handheld_nrf_ssd1306_companion.build_src_filter} + [env:ikoka_handheld_nrf_e22_30dbm_096_companion_radio_usb] -extends = ikoka_nrf52 +extends = ikoka_handheld_nrf +board_build.ldscript = boards/nrf52840_s140_v7_extrafs.ld build_flags = ${ikoka_handheld_nrf_ssd1306_companion.build_flags} -D LORA_TX_POWER=20 build_src_filter = ${ikoka_handheld_nrf_ssd1306_companion.build_src_filter} [env:ikoka_handheld_nrf_e22_30dbm_096_rotated_companion_radio_usb] -extends = ikoka_nrf52 +extends = ikoka_handheld_nrf +board_build.ldscript = boards/nrf52840_s140_v7_extrafs.ld build_flags = ${ikoka_handheld_nrf_ssd1306_companion.build_flags} -D LORA_TX_POWER=20 -D DISPLAY_ROTATION=2 build_src_filter = ${ikoka_handheld_nrf_ssd1306_companion.build_src_filter} [env:ikoka_handheld_nrf_e22_30dbm_repeater] -extends = ikoka_nrf52 +extends = ikoka_handheld_nrf build_flags = ${ikoka_handheld_nrf.build_flags} -D ADVERT_NAME='"ikoka_handheld Repeater"' @@ -92,7 +98,7 @@ build_src_filter = ${ikoka_handheld_nrf.build_src_filter} +<../examples/simple_repeater/*.cpp> [env:ikoka_handheld_nrf_e22_30dbm_room_server] -extends = ikoka_nrf52 +extends = ikoka_handheld_nrf build_flags = ${ikoka_handheld_nrf.build_flags} -D ADVERT_NAME='"ikoka_handheld Room"' diff --git a/variants/rak3401/RAK3401Board.cpp b/variants/rak3401/RAK3401Board.cpp index b9431c92..33e1de42 100644 --- a/variants/rak3401/RAK3401Board.cpp +++ b/variants/rak3401/RAK3401Board.cpp @@ -20,13 +20,29 @@ void RAK3401Board::begin() { Wire.begin(); + // PIN_3V3_EN (WB_IO2, P0.34) controls the 3V3_S switched peripheral rail + // AND the 5V boost regulator (U5) on the RAK13302 that powers the SKY66122 PA. + // Must stay HIGH during radio operation — do not toggle for power saving. pinMode(PIN_3V3_EN, OUTPUT); digitalWrite(PIN_3V3_EN, HIGH); -#ifdef P_LORA_PA_EN - // Initialize RAK13302 1W LoRa transceiver module PA control pin - pinMode(P_LORA_PA_EN, OUTPUT); - digitalWrite(P_LORA_PA_EN, LOW); // Start with PA disabled - delay(10); // Allow PA module to initialize + // Enable SKY66122-11 FEM on the RAK13302 module. + // CSD and CPS are tied together on the RAK13302 PCB, routed to IO3 (P0.21). + // HIGH = FEM active (LNA for RX, PA path available for TX). + // TX/RX switching (CTX) is handled by SX1262 DIO2 via SetDIO2AsRfSwitchCtrl. + pinMode(SX126X_POWER_EN, OUTPUT); + digitalWrite(SX126X_POWER_EN, HIGH); + delay(1); // SKY66122 turn-on settling time (tON = 3us typ) +} + +#ifdef NRF52_POWER_MANAGEMENT +void RAK3401Board::initiateShutdown(uint8_t reason) { + // Disable SKY66122 FEM (CSD+CPS LOW = shutdown, <1 uA) + digitalWrite(SX126X_POWER_EN, LOW); + + // Disable 3V3 switched peripherals and 5V boost + digitalWrite(PIN_3V3_EN, LOW); + + enterSystemOff(reason); +} #endif -} \ No newline at end of file diff --git a/variants/rak3401/RAK3401Board.h b/variants/rak3401/RAK3401Board.h index 20edf906..3a080d5e 100644 --- a/variants/rak3401/RAK3401Board.h +++ b/variants/rak3401/RAK3401Board.h @@ -38,13 +38,6 @@ public: return "RAK 3401"; } -#ifdef P_LORA_PA_EN - void onBeforeTransmit() override { - digitalWrite(P_LORA_PA_EN, HIGH); // Enable PA before transmission - } - - void onAfterTransmit() override { - digitalWrite(P_LORA_PA_EN, LOW); // Disable PA after transmission to save power - } -#endif + // TX/RX switching is handled by SX1262 DIO2 -> SKY66122 CTX (hardware-timed). + // No onBeforeTransmit/onAfterTransmit overrides needed. }; diff --git a/variants/rak3401/platformio.ini b/variants/rak3401/platformio.ini index f4bcef05..646933ca 100644 --- a/variants/rak3401/platformio.ini +++ b/variants/rak3401/platformio.ini @@ -12,6 +12,7 @@ build_flags = ${nrf52_base.build_flags} -D LORA_TX_POWER=22 -D SX126X_CURRENT_LIMIT=140 -D SX126X_RX_BOOSTED_GAIN=1 + -D SX126X_REGISTER_PATCH=1 ; Patch register 0x8B5 for improved RX with SKY66122 FEM build_src_filter = ${nrf52_base.build_src_filter} +<../variants/rak3401> + diff --git a/variants/rak3401/variant.h b/variants/rak3401/variant.h index 56fe0816..268aec53 100644 --- a/variants/rak3401/variant.h +++ b/variants/rak3401/variant.h @@ -147,8 +147,15 @@ static const uint8_t AREF = PIN_AREF; #define SX126X_BUSY (9) #define SX126X_RESET (4) -#define SX126X_POWER_EN (21) -// DIO2 controlls an antenna switch and the TCXO voltage is controlled by DIO3 +// SKY66122-11 FEM control on the RAK13302 module: +// CSD + CPS are tied together on the PCB, routed to WisBlock IO3 (P0.21). +// Setting IO3 HIGH enables the FEM (LNA for RX, PA path for TX). +// CTX is connected to SX1262 DIO2 — the radio handles TX/RX switching +// in hardware via SetDIO2AsRfSwitchCtrl (microsecond-accurate, no GPIO needed). +// The 5V boost for the PA is enabled by WB_IO2 (P0.34 = PIN_3V3_EN). +#define SX126X_POWER_EN (21) // P0.21 = IO3 -> SKY66122 CSD+CPS (FEM enable) + +// CTX is driven by SX1262 DIO2, not a GPIO #define SX126X_DIO2_AS_RF_SWITCH #define SX126X_DIO3_TCXO_VOLTAGE 1.8 @@ -159,7 +166,6 @@ static const uint8_t AREF = PIN_AREF; #define P_LORA_DIO_1 SX126X_DIO1 #define P_LORA_BUSY SX126X_BUSY #define P_LORA_RESET SX126X_RESET -#define P_LORA_PA_EN 31 // enables 3.3V periphery like GPS or IO Module // Do not toggle this for GPS power savings