Merge upstream companion-v1.17.1 into power-saving

Pulls in real fixes: scoped-reply routing, RX boosted-gain restored
correctly after AGC reset, T-Echo Lite/Card SPI pin corrections
(unused pins now map to NRFX_SPIM_PIN_NOT_USED via 0 instead of -1)
and TCXO voltage fix, Heltec T096/T1 and MeshPocket pin fixes,
T-beam Supreme S3 display fix, LR2021 preamble/IRQ timeout handling.

None of the touched variants overlap with our active Heltec V3/V4,
Cardputer ADV, GAT562, or WioTracker builds.

# Conflicts:
#	examples/companion_radio/MyMesh.cpp
#	examples/companion_radio/MyMesh.h
#	examples/companion_radio/NodePrefs.h
#	src/helpers/radiolib/CustomSX1262Wrapper.h
#	src/helpers/radiolib/RadioLibWrappers.cpp
#	src/helpers/ui/SH1106Display.cpp
#	variants/lilygo_techo_lite/platformio.ini
#	variants/lilygo_techo_lite/variant.h
This commit is contained in:
MarekZegare4
2026-08-14 15:58:23 +02:00
57 changed files with 820 additions and 193 deletions
-2
View File
@@ -27,11 +27,9 @@ bool Identity::verify(const uint8_t* sig, const uint8_t* message, int msg_len) c
// needs much less, around 600-700bytes. The CC310 workspace is static, faster,
// should save power at scale as well.
static CRYS_ECEDW_TempBuff_t cc310_tmp;
nRFCrypto.begin();
CRYSError_t rc = CRYS_ECEDW_Verify((uint8_t*)sig, CRYS_ECEDW_SIGNATURE_BYTES,
(uint8_t*)pub_key, CRYS_ECEDW_MOD_SIZE_IN_BYTES,
(uint8_t*)message, (size_t)msg_len, &cc310_tmp);
nRFCrypto.end();
return rc == CRYS_OK;
#elif 0
// NOTE: memory corruption bug was found in this function!!
+4
View File
@@ -67,6 +67,10 @@ public:
virtual bool setLoRaFemLnaEnabled(bool enable) { return false; }
virtual bool canControlLoRaFemLna() const { return false; }
virtual bool isLoRaFemLnaEnabled() const { return false; }
// Software-selectable external FEM transmit gain. This is not a PA power switch.
virtual bool setLoRaFemPaGainEnabled(bool enable) { return false; }
virtual bool canControlLoRaFemPaGain() const { return false; }
virtual bool isLoRaFemPaGainEnabled() const { return false; }
// Power management interface (boards with power management override these)
virtual bool isExternalPowered() { return false; }
-12
View File
@@ -24,9 +24,7 @@ uint32_t RNG::nextInt(uint32_t _min, uint32_t _max) {
void Utils::sha256(uint8_t *hash, size_t hash_len, const uint8_t* msg, int msg_len) {
#ifdef USE_CC310_HW_CRYPTO
static CRYS_HASH_Result_t result;
nRFCrypto.begin();
CRYS_HASH(CRYS_HASH_SHA256_mode, (uint8_t*)msg, (size_t)msg_len, result);
nRFCrypto.end();
memcpy(hash, result, hash_len);
#else
SHA256 sha;
@@ -39,12 +37,10 @@ void Utils::sha256(uint8_t *hash, size_t hash_len, const uint8_t* frag1, int fra
#ifdef USE_CC310_HW_CRYPTO
static CRYS_HASHUserContext_t ctx;
static CRYS_HASH_Result_t result;
nRFCrypto.begin();
CRYS_HASH_Init(&ctx, CRYS_HASH_SHA256_mode);
CRYS_HASH_Update(&ctx, (uint8_t*)frag1, (size_t)frag1_len);
CRYS_HASH_Update(&ctx, (uint8_t*)frag2, (size_t)frag2_len);
CRYS_HASH_Finish(&ctx, result);
nRFCrypto.end();
memcpy(hash, result, hash_len);
#else
SHA256 sha;
@@ -62,7 +58,6 @@ int Utils::decrypt(const uint8_t* shared_secret, uint8_t* dest, const uint8_t* s
const uint8_t* sp = src;
size_t dummy_out = 0;
nRFCrypto.begin();
SaSi_AesInit(&ctx, SASI_AES_DECRYPT, SASI_AES_MODE_ECB, SASI_AES_PADDING_NONE);
SaSi_AesSetKey(&ctx, SASI_AES_USER_KEY, &keyData, sizeof(keyData));
while (sp - src < src_len) {
@@ -71,7 +66,6 @@ int Utils::decrypt(const uint8_t* shared_secret, uint8_t* dest, const uint8_t* s
}
SaSi_AesFinish(&ctx, 0, NULL, 0, NULL, &dummy_out);
SaSi_AesFree(&ctx);
nRFCrypto.end();
return sp - src;
#else
AES128 aes;
@@ -95,7 +89,6 @@ int Utils::encrypt(const uint8_t* shared_secret, uint8_t* dest, const uint8_t* s
uint8_t* dp = dest;
size_t dummy_out = 0;
nRFCrypto.begin();
SaSi_AesInit(&ctx, SASI_AES_ENCRYPT, SASI_AES_MODE_ECB, SASI_AES_PADDING_NONE);
SaSi_AesSetKey(&ctx, SASI_AES_USER_KEY, &keyData, sizeof(keyData));
while (src_len >= 16) {
@@ -110,7 +103,6 @@ int Utils::encrypt(const uint8_t* shared_secret, uint8_t* dest, const uint8_t* s
}
SaSi_AesFinish(&ctx, 0, NULL, 0, NULL, &dummy_out);
SaSi_AesFree(&ctx);
nRFCrypto.end();
return dp - dest;
#else
AES128 aes;
@@ -138,11 +130,9 @@ int Utils::encryptThenMAC(const uint8_t* shared_secret, uint8_t* dest, const uin
#ifdef USE_CC310_HW_CRYPTO
static CRYS_HMACUserContext_t hmac_ctx;
static CRYS_HASH_Result_t hmac_result;
nRFCrypto.begin();
CRYS_HMAC_Init(&hmac_ctx, CRYS_HASH_SHA256_mode, (uint8_t*)shared_secret, PUB_KEY_SIZE);
CRYS_HMAC_Update(&hmac_ctx, dest + CIPHER_MAC_SIZE, enc_len);
CRYS_HMAC_Finish(&hmac_ctx, hmac_result);
nRFCrypto.end();
memcpy(dest, hmac_result, CIPHER_MAC_SIZE);
#else
SHA256 sha;
@@ -162,11 +152,9 @@ int Utils::MACThenDecrypt(const uint8_t* shared_secret, uint8_t* dest, const uin
{
static CRYS_HMACUserContext_t hmac_ctx;
static CRYS_HASH_Result_t hmac_result;
nRFCrypto.begin();
CRYS_HMAC_Init(&hmac_ctx, CRYS_HASH_SHA256_mode, (uint8_t*)shared_secret, PUB_KEY_SIZE);
CRYS_HMAC_Update(&hmac_ctx, (uint8_t*)(src + CIPHER_MAC_SIZE), src_len - CIPHER_MAC_SIZE);
CRYS_HMAC_Finish(&hmac_ctx, hmac_result);
nRFCrypto.end();
memcpy(hmac, hmac_result, CIPHER_MAC_SIZE);
}
#else
+29
View File
@@ -133,6 +133,7 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { // Legacy
// sanitise settings
_prefs->rx_boosted_gain = constrain(_prefs->rx_boosted_gain, 0, 1); // boolean
_prefs->radio_fem_rxgain = constrain(_prefs->radio_fem_rxgain, 0, 1); // boolean
_prefs->radio_fem_txgain = constrain(_prefs->radio_fem_txgain, 0, 1); // boolean
_prefs->cad_enabled = constrain(_prefs->cad_enabled, 0, 1); // boolean
file.close();
@@ -562,6 +563,28 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep
} else {
strcpy(reply, "Error: state must be on or off");
}
} else if (memcmp(config, "radio.fem.txgain ", 17) == 0) {
if (!_board->canControlLoRaFemPaGain()) {
strcpy(reply, "Error: unsupported");
} else if (memcmp(&config[17], "on", 2) == 0) {
if (_board->setLoRaFemPaGainEnabled(true)) {
_prefs->radio_fem_txgain = 1;
savePrefs();
strcpy(reply, "OK - LoRa FEM TX gain on");
} else {
strcpy(reply, "Error: failed to apply LoRa FEM TX gain");
}
} else if (memcmp(&config[17], "off", 3) == 0) {
if (_board->setLoRaFemPaGainEnabled(false)) {
_prefs->radio_fem_txgain = 0;
savePrefs();
strcpy(reply, "OK - LoRa FEM TX gain off");
} else {
strcpy(reply, "Error: failed to apply LoRa FEM TX gain");
}
} else {
strcpy(reply, "Error: state must be on or off");
}
} else if (memcmp(config, "radio ", 6) == 0) {
strcpy(tmp, &config[6]);
const char *parts[4];
@@ -827,6 +850,12 @@ void CommonCLI::handleGetCmd(uint32_t sender_timestamp, char* command, char* rep
} else {
sprintf(reply, "> %s", _board->isLoRaFemLnaEnabled() ? "on" : "off");
}
} else if (memcmp(config, "radio.fem.txgain", 16) == 0) {
if (!_board->canControlLoRaFemPaGain()) {
strcpy(reply, "Error: unsupported");
} else {
sprintf(reply, "> %s", _board->isLoRaFemPaGainEnabled() ? "on" : "off");
}
} else if (memcmp(config, "radio", 5) == 0) {
char freq[16], bw[16];
strcpy(freq, StrHelper::ftoa(_prefs->freq));
+3 -1
View File
@@ -65,6 +65,7 @@ public:
char owner_info[120];
uint8_t rx_boosted_gain = 0; // power settings
uint8_t radio_fem_rxgain = 0; // LoRa FEM RX gain setting
uint8_t radio_fem_txgain = 0; // LoRa FEM TX gain setting
uint8_t path_hash_mode = 0; // which path mode to use when sending
uint8_t loop_detect = 0;
uint8_t cad_enabled = 0; // hardware Channel Activity Detection before TX (boolean)
@@ -82,7 +83,8 @@ private:
def("cad", _parent->cad_enabled);
def("int_thr", _parent->interference_threshold);
def("rxgain", _parent->rx_boosted_gain);
def("fem_rxgain", _parent->rx_boosted_gain);
def("fem_rxgain", _parent->radio_fem_rxgain);
def("fem_txgain", _parent->radio_fem_txgain);
def("tx", _parent->tx_power_dbm);
def("af", _parent->airtime_factor);
def("rxdelay", _parent->rx_delay_base);
+13
View File
@@ -5,6 +5,10 @@
#include <bluefruit.h>
#include <nrf_soc.h>
#ifdef USE_CC310_HW_CRYPTO
#include <Adafruit_nRFCrypto.h>
#endif
static BLEDfu bledfu;
static void connect_callback(uint16_t conn_handle) {
@@ -21,6 +25,11 @@ static void disconnect_callback(uint16_t conn_handle, uint8_t reason) {
void NRF52Board::begin() {
startup_reason = BD_STARTUP_NORMAL;
#ifdef USE_CC310_HW_CRYPTO
// CC310 TRNG is higher quality and environment-independent vs radio RSSI noise.
nRFCrypto.begin();
#endif
}
#ifdef NRF52_POWER_MANAGEMENT
@@ -352,6 +361,10 @@ void NRF52Board::shutdownPeripherals() {
sensors.getLocationProvider()->stop();
}
#ifdef USE_CC310_HW_CRYPTO
nRFCrypto.end();
#endif
// Flush serial buffers
Serial.flush();
delay(100);
+68
View File
@@ -0,0 +1,68 @@
#pragma once
#include <Packet.h>
namespace mesh {
/**
* \brief Test a flood packet against the configured hop limits.
* \param packet inbound flood packet (caller has already checked isRouteFlood())
* \param flood_max max hops for any flood packet
* \param flood_max_unscoped max hops for ROUTE_TYPE_FLOOD (ie. un-scoped) packets
* \param flood_max_advert max hops for ADVERT packets
* \returns true if the packet has exceeded a limit, and must not be forwarded
*/
inline bool isFloodHopLimitExceeded(const Packet* packet, uint8_t flood_max,
uint8_t flood_max_unscoped, uint8_t flood_max_advert) {
uint8_t hops = packet->getPathHashCount();
if (hops >= flood_max) return true;
if (packet->getRouteType() == ROUTE_TYPE_FLOOD && hops >= flood_max_unscoped) return true;
if (packet->getPayloadType() == PAYLOAD_TYPE_ADVERT && hops >= flood_max_advert) return true;
return false;
}
/**
* \brief How a server routes a reply back to the requesting client.
*/
enum ReplyRoute : uint8_t {
REPLY_ROUTE_PATH_RETURN, // request arrived by flood: reply with a PATH return, flooded back
REPLY_ROUTE_DIRECT_SUPPLIED, // reply DIRECT, along the return path supplied in the request
REPLY_ROUTE_DIRECT_OUT_PATH, // reply DIRECT, along the out_path already stored for this client
REPLY_ROUTE_FLOOD, // no return path known: flood the reply
};
/**
* \param inbound_is_flood the request arrived as a flood packet
* \param have_supplied_path the request payload carried an explicit reply path
* \param have_out_path this server already has a stored out_path for the client
*/
inline ReplyRoute chooseReplyRoute(bool inbound_is_flood, bool have_supplied_path, bool have_out_path) {
if (inbound_is_flood) return REPLY_ROUTE_PATH_RETURN;
if (have_supplied_path) return REPLY_ROUTE_DIRECT_SUPPLIED;
if (have_out_path) return REPLY_ROUTE_DIRECT_OUT_PATH;
return REPLY_ROUTE_FLOOD;
}
/**
* \brief Which transport scope a flooded reply should be sent with.
*/
enum ReplyScope : uint8_t {
REPLY_SCOPE_REQUEST, // re-use the scope the request arrived on
REPLY_SCOPE_DEFAULT, // fall back to this node's default region scope
REPLY_SCOPE_NONE, // send un-scoped (ROUTE_TYPE_FLOOD)
};
/**
* \param request_scope_known request arrived scoped, and we resolved its Region's key
* \param request_was_unscoped_flood request arrived as an un-scoped flood
* \param default_scope_known this node has a default Region with a usable transport key
*/
inline ReplyScope chooseReplyScope(bool request_scope_known, bool request_was_unscoped_flood,
bool default_scope_known) {
if (request_scope_known) return REPLY_SCOPE_REQUEST;
if (request_was_unscoped_flood) return REPLY_SCOPE_NONE; // requester chose un-scoped, so mirror it
if (default_scope_known) return REPLY_SCOPE_DEFAULT; // scope unknowable: DIRECT, or unresolved Region
return REPLY_SCOPE_NONE;
}
}
+2 -1
View File
@@ -19,7 +19,6 @@ public:
((CustomLR1110 *)_radio)->setMaxPayloadMillis(pm.payloadMillis);
}
void doResetAGC() override { lr11x0ResetAGC((LR11x0 *)_radio, ((CustomLR1110 *)_radio)->getFreqMHz()); }
bool isReceivingPacket() override {
return ((CustomLR1110 *)_radio)->isReceiving();
}
@@ -50,4 +49,6 @@ public:
bool getRxBoostedGainMode() const override {
return ((CustomLR1110 *)_radio)->getRxBoostedGainMode();
}
void doResetAGC() override { lr11x0ResetAGC((LR11x0 *)_radio, ((CustomLR1110 *)_radio)->getFreqMHz(), getRxBoostedGainMode()); }
};
+57 -2
View File
@@ -4,6 +4,10 @@
#include "MeshCore.h"
class CustomLR2021 : public LR2021 {
uint32_t _preambleMillis = 66;
uint32_t _maxPayloadMillis = 3934;
uint32_t _activityAt = 0;
bool _headerSeen = false;
bool _rx_boosted = false;
public:
@@ -66,11 +70,62 @@ class CustomLR2021 : public LR2021 {
bool getRxBoostedGainMode() const { return _rx_boosted; }
int16_t startReceive() override {
// include the PREAMBLE_DETECTED irq bit in reported flags
return LR2021::startReceive(RADIOLIB_LR2021_RX_TIMEOUT_INF, RADIOLIB_IRQ_RX_DEFAULT_FLAGS | (1UL << RADIOLIB_LR2021_IRQ_PREAMBLE_DETECTED), RADIOLIB_IRQ_RX_DEFAULT_MASK, 0);
}
bool isReceiving() {
uint32_t irq = getIrqStatus();
bool detected = ((irq & RADIOLIB_LR2021_IRQ_SYNCWORD_VALID) || (irq & RADIOLIB_LR2021_IRQ_PREAMBLE_DETECTED));
return detected;
bool preamble = irq & RADIOLIB_LR2021_IRQ_PREAMBLE_DETECTED; // bit 5
bool header = irq & RADIOLIB_LR2021_IRQ_LORA_HEADER_VALID; // bit 6
bool hdrErr = irq & RADIOLIB_LR2021_IRQ_LORA_HDR_CRC_ERROR; // bit 9
uint32_t now = millis();
if (hdrErr) {
clearIrqFlags(RADIOLIB_LR2021_IRQ_PREAMBLE_DETECTED | RADIOLIB_LR2021_IRQ_LORA_HEADER_VALID | RADIOLIB_LR2021_IRQ_LORA_HDR_CRC_ERROR);
_activityAt = 0;
_headerSeen = false;
return false;
}
if (!header && _headerSeen) {
// something cleared the header flag, reset our state.
_activityAt = 0; _headerSeen = false;
return false;
}
if (header) {
if (!_headerSeen) { _headerSeen = true; _activityAt = now; };
if (now - _activityAt > _maxPayloadMillis) {
MESH_DEBUG_PRINTLN("Clearing header IRQ after %ums", _maxPayloadMillis);
clearIrqFlags(RADIOLIB_LR2021_IRQ_PREAMBLE_DETECTED | RADIOLIB_LR2021_IRQ_LORA_HEADER_VALID | RADIOLIB_LR2021_IRQ_LORA_HDR_CRC_ERROR);
_activityAt = 0; _headerSeen = false;
return false;
}
return true;
}
if (preamble) {
if (_activityAt == 0) _activityAt = now;
if (now - _activityAt > _preambleMillis) {
clearIrqFlags(RADIOLIB_LR2021_IRQ_PREAMBLE_DETECTED);
_activityAt = 0;
MESH_DEBUG_PRINTLN("Clearing preamble IRQ after %ums", _preambleMillis);
return false;
}
return true;
}
_activityAt = 0; _headerSeen = false;
return false;
}
void setPreambleMillis(uint32_t preambleMillis) {
_preambleMillis = preambleMillis;
MESH_DEBUG_PRINTLN("Set _preambleMillis=%u", _preambleMillis);
}
void setMaxPayloadMillis(uint32_t payloadMillis) {
_maxPayloadMillis = payloadMillis;
MESH_DEBUG_PRINTLN("Set _maxPayloadMillis=%u", _maxPayloadMillis);
}
uint8_t getSpreadingFactor() const { return spreadingFactor; }
};
@@ -22,6 +22,10 @@ public:
((CustomLR2021 *)_radio)->setCodingRate(cr);
updatePreamble(sf);
applySideDetectorConfig();
PacketMillis pm = calcMaxPacketMillis(sf, bw, cr, preambleLengthForSF(sf));
((CustomLR2021 *)_radio)->setPreambleMillis(pm.preambleMillis);
((CustomLR2021 *)_radio)->setMaxPayloadMillis(pm.payloadMillis);
}
bool configSideDetectors(const uint8_t* sideDetSFs, uint8_t num, float bw) override {
+1 -1
View File
@@ -35,5 +35,5 @@ public:
}
uint8_t getSpreadingFactor() const override { return ((CustomSTM32WLx *)_radio)->spreadingFactor; }
void doResetAGC() override { sx126xResetAGC((SX126x *)_radio); }
void doResetAGC() override { sx126xResetAGC((SX126x *)_radio, getRxBoostedGainMode()); }
};
+1 -2
View File
@@ -54,8 +54,6 @@ public:
((CustomSX1262 *)_radio)->sleep(false);
}
void doResetAGC() override { sx126xResetAGC((SX126x *)_radio); }
// Power-save RX = hardware RX duty-cycle (SX126x SetRxDutyCycle, datasheet
// 13.1.7). The chip's sequencer cycles RX↔sleep on its own, latches a preamble
// of the configured length and then stays in RX to receive the packet, raising
@@ -106,4 +104,5 @@ public:
((CustomSX1262 *)_radio)->setRxBoostedGainMode(_wd_rx_boosted_gain);
return true;
}
void doResetAGC() override { sx126xResetAGC((SX126x *)_radio, getRxBoostedGainMode()); }
};
+2 -2
View File
@@ -38,12 +38,12 @@ public:
}
uint8_t getSpreadingFactor() const override { return ((CustomSX1268 *)_radio)->spreadingFactor; }
void doResetAGC() override { sx126xResetAGC((SX126x *)_radio); }
bool setRxBoostedGainMode(bool en) override {
return ((CustomSX1268 *)_radio)->setRxBoostedGainMode(en) == RADIOLIB_ERR_NONE;
}
bool getRxBoostedGainMode() const override {
return ((CustomSX1268 *)_radio)->getRxBoostedGainMode();
}
void doResetAGC() override { sx126xResetAGC((SX126x *)_radio, getRxBoostedGainMode()); }
};
+2 -2
View File
@@ -5,7 +5,7 @@
// 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) {
inline void lr11x0ResetAGC(LR11x0* radio, float freqMHz, bool rx_boost_gain) {
radio->sleep(true, 0);
radio->standby(RADIOLIB_LR11X0_STANDBY_RC, true);
@@ -16,6 +16,6 @@ inline void lr11x0ResetAGC(LR11x0* radio, float freqMHz) {
radio->calibrateImageRejection(freqMHz - 4.0f, freqMHz + 4.0f);
#ifdef RX_BOOSTED_GAIN
radio->setRxBoostedGainMode(RX_BOOSTED_GAIN);
radio->setRxBoostedGainMode(rx_boost_gain);
#endif
}
+4 -2
View File
@@ -19,7 +19,7 @@ static volatile uint8_t state = STATE_IDLE;
// this function is called when a complete packet
// is transmitted by the module
static
static
#if defined(ESP8266) || defined(ESP32)
ICACHE_RAM_ATTR
#endif
@@ -193,7 +193,9 @@ void RadioLibWrapper::loop() {
}
_floor_sample_sum = 0;
#ifdef MESH_DEBUG_NOISE_FLOOR
MESH_DEBUG_PRINTLN("RadioLibWrapper: noise_floor = %d", (int)_noise_floor);
#endif
if (_nf_calib_active) {
_nf_calib_active = false; // fresh floor published -- back to duty-cycle
@@ -344,7 +346,7 @@ PacketMillis RadioLibWrapper::calcMaxPacketMillis(uint8_t sf, float bw, uint8_t
// preamble + syncword + sfd + header
uint32_t preamble_us = (((preambleSymbols + 8) * 4 + sfCoeff1_x4) * tsym_us) / 4;
// airtime for max packet at current radio settings
uint32_t total_us = _radio->getTimeOnAir(MAX_TRANS_UNIT);
// airtime for payload only (no preamble, header or SOF)
+3 -3
View File
@@ -166,10 +166,10 @@ public:
void random(uint8_t* dest, size_t sz) override {
#ifdef USE_CC310_HW_CRYPTO
// CC310 TRNG is higher quality and environment-independent vs radio RSSI noise.
nRFCrypto.begin();
nRFCrypto.Random.generate(dest, (uint16_t)sz);
nRFCrypto.end();
for (int i = 0; i < sz; i++) {
dest[i] ^= _radio->randomByte() ^ (::random(0, 256) & 0xFF); // combine with Radio's entropy
}
#else
for (int i = 0; i < sz; i++) {
dest[i] = _radio->randomByte() ^ (::random(0, 256) & 0xFF);
+2 -2
View File
@@ -5,7 +5,7 @@
// 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) {
inline void sx126xResetAGC(SX126x* radio, bool rx_boost_gain) {
radio->sleep(true);
radio->standby(RADIOLIB_SX126X_STANDBY_RC, true);
@@ -26,7 +26,7 @@ inline void sx126xResetAGC(SX126x* radio) {
radio->setDio2AsRfSwitch(SX126X_DIO2_AS_RF_SWITCH);
#endif
#ifdef SX126X_RX_BOOSTED_GAIN
radio->setRxBoostedGainMode(SX126X_RX_BOOSTED_GAIN);
radio->setRxBoostedGainMode(rx_boost_gain);
#endif
#ifdef SX126X_REGISTER_PATCH
uint8_t r_data = 0;
+17 -1
View File
@@ -12,7 +12,23 @@ bool SH1106Display::i2c_probe(TwoWire &wire, uint8_t addr)
bool SH1106Display::begin()
{
return display.begin(DISPLAY_ADDRESS, true) && i2c_probe(Wire, DISPLAY_ADDRESS);
// Wire must already be initialised by board.begin() before this is called.
// Boards with non-standard SH1106 addresses should define DISPLAY_ADDRESS
// in their variant/platformio configuration. The SA0 strap selects 0x3C or
// 0x3D and differs between revisions of the same board (e.g. T-Beam
// Supreme), so fall back to the other address of the pair.
uint8_t addr = 0;
if (i2c_probe(Wire, DISPLAY_ADDRESS)) {
addr = DISPLAY_ADDRESS;
} else if (i2c_probe(Wire, DISPLAY_ADDRESS ^ 1)) {
addr = DISPLAY_ADDRESS ^ 1;
}
// Run the Adafruit init even when no panel answered: it is what allocates
// the frame buffer and the I2C device. Skipping it leaves i2c_dev and
// spi_dev NULL, and UITask::begin() calls turnOn() regardless of our
// return value, which then dereferences the null spi_dev.
bool ok = display.begin(addr ? addr : DISPLAY_ADDRESS, true);
return addr != 0 && ok;
}
void SH1106Display::turnOn()