Merge remote-tracking branch 'origin/develop'
This commit is contained in:
@@ -33,6 +33,7 @@ __pycache__
|
|||||||
*~
|
*~
|
||||||
|
|
||||||
venv/
|
venv/
|
||||||
|
.venv/
|
||||||
release/
|
release/
|
||||||
.vscode/extensions.json
|
.vscode/extensions.json
|
||||||
/compile_commands.json
|
/compile_commands.json
|
||||||
|
|||||||
@@ -76,8 +76,10 @@ bool NotifiedWorkerThread::notifyLater(uint32_t delay, uint32_t v, bool overwrit
|
|||||||
|
|
||||||
void NotifiedWorkerThread::checkNotification()
|
void NotifiedWorkerThread::checkNotification()
|
||||||
{
|
{
|
||||||
auto n = notification;
|
// Atomically read and clear. (This avoids a potential race condition where an interrupt handler could set a new notification
|
||||||
notification = 0; // clear notification
|
// after checkNotification reads but before it clears, which would cause us to miss that notification until the next one comes
|
||||||
|
// in.)
|
||||||
|
auto n = notification.exchange(0); // read+clear atomically: like `n = notification; notification = 0;` but interrupt-safe
|
||||||
if (n) {
|
if (n) {
|
||||||
onNotify(n);
|
onNotify(n);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "OSThread.h"
|
#include "OSThread.h"
|
||||||
|
#include <atomic>
|
||||||
|
|
||||||
namespace concurrency
|
namespace concurrency
|
||||||
{
|
{
|
||||||
@@ -13,7 +14,7 @@ class NotifiedWorkerThread : public OSThread
|
|||||||
/**
|
/**
|
||||||
* The notification that was most recently used to wake the thread. Read from runOnce()
|
* The notification that was most recently used to wake the thread. Read from runOnce()
|
||||||
*/
|
*/
|
||||||
uint32_t notification = 0;
|
std::atomic<uint32_t> notification{0};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
NotifiedWorkerThread(const char *name) : OSThread(name) {}
|
NotifiedWorkerThread(const char *name) : OSThread(name) {}
|
||||||
|
|||||||
@@ -1524,7 +1524,15 @@ void InkHUD::MenuApplet::onButtonShortPress()
|
|||||||
if (!settings->joystick.enabled) {
|
if (!settings->joystick.enabled) {
|
||||||
if (!cursorShown) {
|
if (!cursorShown) {
|
||||||
cursorShown = true;
|
cursorShown = true;
|
||||||
|
// Select the first item that isn't a header
|
||||||
cursor = 0;
|
cursor = 0;
|
||||||
|
while (cursor < items.size() && items.at(cursor).isHeader) {
|
||||||
|
cursor++;
|
||||||
|
}
|
||||||
|
if (cursor >= items.size()) {
|
||||||
|
cursorShown = false;
|
||||||
|
cursor = 0;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
do {
|
do {
|
||||||
cursor = (cursor + 1) % items.size();
|
cursor = (cursor + 1) % items.size();
|
||||||
@@ -1576,7 +1584,15 @@ void InkHUD::MenuApplet::onNavUp()
|
|||||||
|
|
||||||
if (!cursorShown) {
|
if (!cursorShown) {
|
||||||
cursorShown = true;
|
cursorShown = true;
|
||||||
cursor = 0;
|
// Select the last item that isn't a header
|
||||||
|
cursor = items.size() - 1;
|
||||||
|
while (items.at(cursor).isHeader) {
|
||||||
|
if (cursor == 0) {
|
||||||
|
cursorShown = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
cursor--;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
do {
|
do {
|
||||||
if (cursor == 0)
|
if (cursor == 0)
|
||||||
@@ -1597,7 +1613,15 @@ void InkHUD::MenuApplet::onNavDown()
|
|||||||
|
|
||||||
if (!cursorShown) {
|
if (!cursorShown) {
|
||||||
cursorShown = true;
|
cursorShown = true;
|
||||||
|
// Select the first item that isn't a header
|
||||||
cursor = 0;
|
cursor = 0;
|
||||||
|
while (cursor < items.size() && items.at(cursor).isHeader) {
|
||||||
|
cursor++;
|
||||||
|
}
|
||||||
|
if (cursor >= items.size()) {
|
||||||
|
cursorShown = false;
|
||||||
|
cursor = 0;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
do {
|
do {
|
||||||
cursor = (cursor + 1) % items.size();
|
cursor = (cursor + 1) % items.size();
|
||||||
|
|||||||
+6
-6
@@ -192,8 +192,6 @@ bool kb_found = false;
|
|||||||
// global bool to record that on-screen keyboard (OSK) is present
|
// global bool to record that on-screen keyboard (OSK) is present
|
||||||
bool osk_found = false;
|
bool osk_found = false;
|
||||||
|
|
||||||
unsigned long last_listen = 0;
|
|
||||||
|
|
||||||
// The I2C address of the RTC Module (if found)
|
// The I2C address of the RTC Module (if found)
|
||||||
ScanI2C::DeviceAddress rtc_found = ScanI2C::ADDRESS_NONE;
|
ScanI2C::DeviceAddress rtc_found = ScanI2C::ADDRESS_NONE;
|
||||||
// The I2C address of the Accelerometer (if found)
|
// The I2C address of the Accelerometer (if found)
|
||||||
@@ -1121,10 +1119,12 @@ void loop()
|
|||||||
#endif
|
#endif
|
||||||
power->powerCommandsCheck();
|
power->powerCommandsCheck();
|
||||||
|
|
||||||
if (RadioLibInterface::instance != nullptr && !Throttle::isWithinTimespanMs(last_listen, 1000 * 60) &&
|
if (RadioLibInterface::instance != nullptr) {
|
||||||
!(RadioLibInterface::instance->isSending() || RadioLibInterface::instance->isActivelyReceiving())) {
|
static uint32_t lastRadioMissedIrqPoll;
|
||||||
RadioLibInterface::instance->startReceive();
|
if (!Throttle::isWithinTimespanMs(lastRadioMissedIrqPoll, 1000)) {
|
||||||
LOG_DEBUG("attempting AGC reset");
|
lastRadioMissedIrqPoll = millis();
|
||||||
|
RadioLibInterface::instance->pollMissedIrqs();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef DEBUG_STACK
|
#ifdef DEBUG_STACK
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ extern ScanI2C::DeviceAddress cardkb_found;
|
|||||||
extern uint8_t kb_model;
|
extern uint8_t kb_model;
|
||||||
extern bool kb_found;
|
extern bool kb_found;
|
||||||
extern bool osk_found;
|
extern bool osk_found;
|
||||||
extern unsigned long last_listen;
|
|
||||||
extern ScanI2C::DeviceAddress rtc_found;
|
extern ScanI2C::DeviceAddress rtc_found;
|
||||||
extern ScanI2C::DeviceAddress accelerometer_found;
|
extern ScanI2C::DeviceAddress accelerometer_found;
|
||||||
extern ScanI2C::FoundDevice rgb_found;
|
extern ScanI2C::FoundDevice rgb_found;
|
||||||
|
|||||||
@@ -263,6 +263,7 @@ template <typename T> void LR11x0Interface<T>::startReceive()
|
|||||||
|
|
||||||
// Must be done AFTER, starting transmit, because startTransmit clears (possibly stale) interrupt pending register bits
|
// Must be done AFTER, starting transmit, because startTransmit clears (possibly stale) interrupt pending register bits
|
||||||
enableInterrupt(isrRxLevel0);
|
enableInterrupt(isrRxLevel0);
|
||||||
|
checkRxDoneIrqFlag();
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -301,6 +301,7 @@ void RF95Interface::startReceive()
|
|||||||
|
|
||||||
// Must be done AFTER, starting receive, because startReceive clears (possibly stale) interrupt pending register bits
|
// Must be done AFTER, starting receive, because startReceive clears (possibly stale) interrupt pending register bits
|
||||||
enableInterrupt(isrRxLevel0);
|
enableInterrupt(isrRxLevel0);
|
||||||
|
checkRxDoneIrqFlag();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RF95Interface::isChannelActive()
|
bool RF95Interface::isChannelActive()
|
||||||
|
|||||||
@@ -517,12 +517,26 @@ void RadioLibInterface::handleReceiveInterrupt()
|
|||||||
|
|
||||||
void RadioLibInterface::startReceive()
|
void RadioLibInterface::startReceive()
|
||||||
{
|
{
|
||||||
// Note the updated timestamp, to avoid unneeded AGC resets
|
|
||||||
last_listen = millis();
|
|
||||||
isReceiving = true;
|
isReceiving = true;
|
||||||
powerMon->setState(meshtastic_PowerMon_State_Lora_RXOn);
|
powerMon->setState(meshtastic_PowerMon_State_Lora_RXOn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void RadioLibInterface::pollMissedIrqs()
|
||||||
|
{
|
||||||
|
// RadioLibInterface::enableInterrupt uses EDGE-TRIGGERED interrupts. Poll as a backup to catch missed edges.
|
||||||
|
if (isReceiving) {
|
||||||
|
checkRxDoneIrqFlag();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RadioLibInterface::checkRxDoneIrqFlag()
|
||||||
|
{
|
||||||
|
if (iface->checkIrq(RADIOLIB_IRQ_RX_DONE)) {
|
||||||
|
LOG_WARN("caught missed RX_DONE");
|
||||||
|
notify(ISR_RX, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void RadioLibInterface::configHardwareForSend()
|
void RadioLibInterface::configHardwareForSend()
|
||||||
{
|
{
|
||||||
powerMon->setState(meshtastic_PowerMon_State_Lora_TXOn);
|
powerMon->setState(meshtastic_PowerMon_State_Lora_TXOn);
|
||||||
|
|||||||
@@ -112,6 +112,11 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified
|
|||||||
*/
|
*/
|
||||||
virtual void enableInterrupt(void (*)()) = 0;
|
virtual void enableInterrupt(void (*)()) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Poll as a backup to catch missed edge-triggered interrupts.
|
||||||
|
*/
|
||||||
|
void pollMissedIrqs();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Debugging counts
|
* Debugging counts
|
||||||
*/
|
*/
|
||||||
@@ -264,4 +269,6 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
bool removePendingTXPacket(NodeNum from, PacketId id, uint32_t hop_limit_lt) override;
|
bool removePendingTXPacket(NodeNum from, PacketId id, uint32_t hop_limit_lt) override;
|
||||||
|
|
||||||
|
void checkRxDoneIrqFlag();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,6 +6,10 @@
|
|||||||
#ifdef ARCH_PORTDUINO
|
#ifdef ARCH_PORTDUINO
|
||||||
#include "PortduinoGlue.h"
|
#include "PortduinoGlue.h"
|
||||||
#endif
|
#endif
|
||||||
|
#if defined(USE_GC1109_PA) && defined(ARCH_ESP32)
|
||||||
|
#include <driver/rtc_io.h>
|
||||||
|
#include <esp_sleep.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include "Throttle.h"
|
#include "Throttle.h"
|
||||||
|
|
||||||
@@ -55,14 +59,33 @@ template <typename T> bool SX126xInterface<T>::init()
|
|||||||
#if defined(USE_GC1109_PA)
|
#if defined(USE_GC1109_PA)
|
||||||
// GC1109 FEM chip initialization
|
// GC1109 FEM chip initialization
|
||||||
// See variant.h for full pin mapping and control logic documentation
|
// See variant.h for full pin mapping and control logic documentation
|
||||||
|
//
|
||||||
|
// On deep sleep wake, PA_POWER and PA_EN are held HIGH by RTC latch (set in
|
||||||
|
// enableLoraInterrupt). We configure GPIO registers before releasing the hold
|
||||||
|
// so the pad transitions atomically from held-HIGH to register-HIGH with no
|
||||||
|
// power glitch. On cold boot the hold_dis is a harmless no-op.
|
||||||
|
|
||||||
// VFEM_Ctrl (LORA_PA_POWER): Power enable for GC1109 LDO (always on)
|
// VFEM_Ctrl (LORA_PA_POWER): Power enable for GC1109 LDO (always on)
|
||||||
pinMode(LORA_PA_POWER, OUTPUT);
|
pinMode(LORA_PA_POWER, OUTPUT);
|
||||||
digitalWrite(LORA_PA_POWER, HIGH);
|
digitalWrite(LORA_PA_POWER, HIGH);
|
||||||
|
rtc_gpio_hold_dis((gpio_num_t)LORA_PA_POWER);
|
||||||
|
|
||||||
|
// TLV75733P LDO has ~550us startup time (datasheet tSTR). On cold boot, wait
|
||||||
|
// for VBAT to stabilise before driving CSD/CPS, per GC1109 requirement:
|
||||||
|
// "VBAT must be prior to CSD/CPS/CTX for the power on sequence"
|
||||||
|
// On deep sleep wake the LDO was held on via RTC latch, so no delay needed.
|
||||||
|
#if defined(ARCH_ESP32)
|
||||||
|
if (esp_sleep_get_wakeup_cause() == ESP_SLEEP_WAKEUP_UNDEFINED) {
|
||||||
|
delayMicroseconds(1000);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
delayMicroseconds(1000);
|
||||||
|
#endif
|
||||||
|
|
||||||
// CSD (LORA_PA_EN): Chip enable - must be HIGH to enable GC1109 for both RX and TX
|
// CSD (LORA_PA_EN): Chip enable - must be HIGH to enable GC1109 for both RX and TX
|
||||||
pinMode(LORA_PA_EN, OUTPUT);
|
pinMode(LORA_PA_EN, OUTPUT);
|
||||||
digitalWrite(LORA_PA_EN, HIGH);
|
digitalWrite(LORA_PA_EN, HIGH);
|
||||||
|
rtc_gpio_hold_dis((gpio_num_t)LORA_PA_EN);
|
||||||
|
|
||||||
// CPS (LORA_PA_TX_EN): PA mode select - HIGH enables full PA during TX, LOW for RX (don't care)
|
// CPS (LORA_PA_TX_EN): PA mode select - HIGH enables full PA during TX, LOW for RX (don't care)
|
||||||
// Note: TX/RX path switching (CTX) is handled by DIO2 via SX126X_DIO2_AS_RF_SWITCH
|
// Note: TX/RX path switching (CTX) is handled by DIO2 via SX126X_DIO2_AS_RF_SWITCH
|
||||||
@@ -171,6 +194,17 @@ template <typename T> bool SX126xInterface<T>::init()
|
|||||||
LOG_INFO("Set RX gain to power saving mode (boosted mode off); result: %d", result);
|
LOG_INFO("Set RX gain to power saving mode (boosted mode off); result: %d", result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef USE_GC1109_PA
|
||||||
|
// Undocumented SX1262 register patch recommended by Heltec/Semtech for improved RX sensitivity
|
||||||
|
// on boards with the GC1109 FEM. Sets bit 0 of register 0x8B5.
|
||||||
|
// Reference: https://github.com/meshcore-dev/MeshCore/pull/1398
|
||||||
|
if (module.SPIsetRegValue(0x8B5, 0x01, 0, 0) == RADIOLIB_ERR_NONE) {
|
||||||
|
LOG_INFO("Applied SX1262 register 0x8B5 patch for GC1109 RX improvement");
|
||||||
|
} else {
|
||||||
|
LOG_WARN("Failed to apply SX1262 register 0x8B5 patch for GC1109");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
#if 0
|
#if 0
|
||||||
// Read/write a register we are not using (only used for FSK mode) to test SPI comms
|
// Read/write a register we are not using (only used for FSK mode) to test SPI comms
|
||||||
uint8_t crcLSB = 0;
|
uint8_t crcLSB = 0;
|
||||||
@@ -328,6 +362,7 @@ template <typename T> void SX126xInterface<T>::startReceive()
|
|||||||
|
|
||||||
// Must be done AFTER, starting transmit, because startTransmit clears (possibly stale) interrupt pending register bits
|
// Must be done AFTER, starting transmit, because startTransmit clears (possibly stale) interrupt pending register bits
|
||||||
enableInterrupt(isrRxLevel0);
|
enableInterrupt(isrRxLevel0);
|
||||||
|
checkRxDoneIrqFlag();
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -271,6 +271,7 @@ template <typename T> void SX128xInterface<T>::startReceive()
|
|||||||
|
|
||||||
// Must be done AFTER, starting transmit, because startTransmit clears (possibly stale) interrupt pending register bits
|
// Must be done AFTER, starting transmit, because startTransmit clears (possibly stale) interrupt pending register bits
|
||||||
enableInterrupt(isrRxLevel0);
|
enableInterrupt(isrRxLevel0);
|
||||||
|
checkRxDoneIrqFlag();
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+14
-2
@@ -162,6 +162,13 @@ void initDeepSleep()
|
|||||||
if (wakeCause != ESP_SLEEP_WAKEUP_UNDEFINED) {
|
if (wakeCause != ESP_SLEEP_WAKEUP_UNDEFINED) {
|
||||||
LOG_DEBUG("Disable any holds on RTC IO pads");
|
LOG_DEBUG("Disable any holds on RTC IO pads");
|
||||||
for (uint8_t i = 0; i <= GPIO_NUM_MAX; i++) {
|
for (uint8_t i = 0; i <= GPIO_NUM_MAX; i++) {
|
||||||
|
#if defined(USE_GC1109_PA)
|
||||||
|
// Skip GC1109 FEM power pins - they are held HIGH during deep sleep to keep
|
||||||
|
// the LNA active for RX wake. Released later in SX126xInterface::init() after
|
||||||
|
// GPIO registers are set HIGH first, avoiding a power glitch.
|
||||||
|
if (i == LORA_PA_POWER || i == LORA_PA_EN)
|
||||||
|
continue;
|
||||||
|
#endif
|
||||||
if (rtc_gpio_is_valid_gpio((gpio_num_t)i))
|
if (rtc_gpio_is_valid_gpio((gpio_num_t)i))
|
||||||
rtc_gpio_hold_dis((gpio_num_t)i);
|
rtc_gpio_hold_dis((gpio_num_t)i);
|
||||||
|
|
||||||
@@ -556,8 +563,13 @@ void enableLoraInterrupt()
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(USE_GC1109_PA)
|
#if defined(USE_GC1109_PA)
|
||||||
gpio_pullup_en((gpio_num_t)LORA_PA_POWER);
|
// Keep GC1109 FEM powered during deep sleep so LNA remains active for RX wake.
|
||||||
gpio_pullup_en((gpio_num_t)LORA_PA_EN);
|
// Set PA_POWER and PA_EN HIGH (overrides SX126xInterface::sleep() shutdown),
|
||||||
|
// then latch with RTC hold so the state survives deep sleep.
|
||||||
|
digitalWrite(LORA_PA_POWER, HIGH);
|
||||||
|
rtc_gpio_hold_en((gpio_num_t)LORA_PA_POWER);
|
||||||
|
digitalWrite(LORA_PA_EN, HIGH);
|
||||||
|
rtc_gpio_hold_en((gpio_num_t)LORA_PA_EN);
|
||||||
gpio_pulldown_en((gpio_num_t)LORA_PA_TX_EN);
|
gpio_pulldown_en((gpio_num_t)LORA_PA_TX_EN);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||
@@ -52,6 +52,12 @@ extern "C" {
|
|||||||
|
|
||||||
#define LED_STATE_ON 1 // State when LED is litted
|
#define LED_STATE_ON 1 // State when LED is litted
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Buttons
|
||||||
|
*/
|
||||||
|
#define PIN_BUTTON1 (9)
|
||||||
|
#define BUTTON_NEED_PULLUP
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Analog pins
|
* Analog pins
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user