Browse Source

public chat receive fix

pull/2568/head
Christos Themelis 1 month ago
parent
commit
6e9b148e81
  1. 55
      src/helpers/esp32/SenseCapHAL.h
  2. 27
      src/helpers/esp32/SenseCapSX1262Wrapper.h
  3. 19
      src/helpers/radiolib/RadioLibWrappers.cpp
  4. 8
      src/helpers/radiolib/RadioLibWrappers.h
  5. 2
      variants/sensecap_indicator-espnow/platformio.ini
  6. 5
      variants/sensecap_indicator-espnow/target.h

55
src/helpers/esp32/SenseCapHAL.h

@ -42,6 +42,22 @@ class SenseCapHAL : public ArduinoHal {
int _sclk, _miso, _mosi; // SPI data pins
SemaphoreHandle_t _mutex; // shared Wire mutex (set via setMutex() after creation)
// ── Deferred IRQ dispatch ──────────────────────────────────────────────
// The TCA9535 /INT fires for ANY input change on Port 0, not just DIO1.
// BUSY transitions, touch-INT (pin 6), and other inputs all trigger GPIO 42.
// We cannot do I2C inside an ISR, so the raw ISR just sets a flag.
// dispatchPendingIrq() (called from the main loop via recvRaw) reads Port 0
// to verify DIO1 is actually HIGH before forwarding the event to RadioLib.
inline static volatile bool _s_pending = false;
inline static void (*_s_cb)(void) = nullptr;
uint8_t _irqBit = 0; // Port 0 bit-mask for DIO1, set in attachInterrupt()
// NOTE: no IRAM_ATTR — class-static IRAM functions cause Xtensa literal-pool
// linker errors when defined inline in a header. Running from flash is fine
// here: the SenseCAP uses ESP32-S3 with no mid-operation flash cache disables,
// and LoRa packet timescales (tens of ms) dwarf any flash-cache miss latency.
static void _rawIsr() { _s_pending = true; }
// A pin is on the expander when its upper nibble equals IO_EXPANDER
static bool isExp(uint32_t pin) {
return (pin & ~0x0Fu) == (uint32_t)IO_EXPANDER;
@ -229,24 +245,49 @@ public:
// Called with either:
// a) raw expander pin (e.g. 0x43) — direct call path
// b) IO_EXPANDER_IRQ (42) — via pinToInterrupt() path (RadioLib 7.x)
// In both cases redirect to GPIO 42 FALLING (TCA9535 /INT goes LOW when DIO1 rises).
//
// DEFERRED DISPATCH: instead of calling `cb` (= RadioLib's setFlag) directly
// from the ISR, we attach a lightweight raw ISR that only sets _s_pending.
// The real dispatch (reading Port 0 to verify DIO1 is HIGH) happens later
// in dispatchPendingIrq(), called from the main loop via recvRaw().
// This prevents touch-INT (pin 6), BUSY, and other Port 0 input changes
// from triggering false radio-interrupt events.
void attachInterrupt(uint32_t pin, void (*cb)(void), uint32_t mode) override {
if (isExp(pin) || pin == (uint32_t)IO_EXPANDER_IRQ) {
_s_cb = cb; // save RadioLib's setFlag callback
_irqBit = isExp(pin) ? expBit(pin) // direct expander pin → derive bit
: (uint8_t)(1u << 3u); // pin=42 path → DIO1 is bit 3
// Clear any pending TCA9535 /INT by reading BOTH port registers.
// /INT stays LOW until the port that triggered it is read; floating
// Port 1 inputs keep /INT stuck even after reading Port 0 alone.
readReg(0x00); // Input Port 0 (radio pins)
readReg(0x01); // Input Port 1 (prevent spurious /INT from port 1)
readReg(0x00);
readReg(0x01);
::pinMode(IO_EXPANDER_IRQ, INPUT_PULLUP);
int gpio42 = ::digitalRead(IO_EXPANDER_IRQ);
::attachInterrupt(digitalPinToInterrupt(IO_EXPANDER_IRQ), cb, FALLING);
Serial.printf("[SenseCapHAL] DIO1 interrupt → GPIO %d state=%d (FALLING)\n",
::attachInterrupt(digitalPinToInterrupt(IO_EXPANDER_IRQ), _rawIsr, FALLING);
Serial.printf("[SenseCapHAL] DIO1 interrupt → GPIO %d state=%d (FALLING, deferred)\n",
IO_EXPANDER_IRQ, gpio42);
} else {
ArduinoHal::attachInterrupt(pin, cb, mode);
}
}
// ── dispatchPendingIrq ────────────────────────────────────────────────
// Call from the main loop (NOT from ISR). Reads Port 0 to check whether
// DIO1 is actually HIGH. If so, forwards the event to RadioLib (setFlag).
// Ignores spurious triggers from BUSY, touch-INT, or any other Port 0 input.
void dispatchPendingIrq() {
if (!_s_pending) return;
_s_pending = false;
// Reading Port 0 also acknowledges the TCA9535 /INT for ALL inputs,
// including touch INT and BUSY — preventing /INT from staying stuck LOW.
uint8_t port0 = readReg(0x00);
if (_irqBit && (port0 & _irqBit)) {
// DIO1 is HIGH → this is a real radio interrupt (RX_DONE or TX_DONE)
if (_s_cb) _s_cb();
}
// else: spurious — BUSY transition, touch INT, etc. Do nothing.
}
void detachInterrupt(uint32_t pin) override {
if (isExp(pin) || pin == (uint32_t)IO_EXPANDER_IRQ) {
::detachInterrupt(digitalPinToInterrupt(IO_EXPANDER_IRQ));

27
src/helpers/esp32/SenseCapSX1262Wrapper.h

@ -0,0 +1,27 @@
#pragma once
// SenseCapSX1262Wrapper — extends CustomSX1262Wrapper with a DIO1-verified
// dispatchPendingIrq() override.
//
// On SenseCAP Indicator the SX1262 DIO1, BUSY, and touch-INT all share the
// same TCA9535 /INT line (GPIO 42). The raw ISR in SenseCapHAL only sets a
// pending flag; this override reads Port 0 via I2C and calls setFlag() only
// when DIO1 is actually HIGH, silencing spurious triggers from BUSY
// transitions, touch events, or any other Port 0 input change.
#include <helpers/radiolib/CustomSX1262Wrapper.h>
#include "SenseCapHAL.h"
class SenseCapSX1262Wrapper : public CustomSX1262Wrapper {
public:
SenseCapSX1262Wrapper(CustomSX1262& radio, mesh::MainBoard& board)
: CustomSX1262Wrapper(radio, board) {}
protected:
void dispatchPendingIrq() override {
// Access HAL via Module (Module::hal is public in RadioLib 7.x)
auto* hal = static_cast<SenseCapHAL*>(
static_cast<CustomSX1262*>(_radio)->getMod()->hal);
hal->dispatchPendingIrq();
}
};

19
src/helpers/radiolib/RadioLibWrappers.cpp

@ -47,7 +47,11 @@ void RadioLibWrapper::idle() {
void RadioLibWrapper::triggerNoiseFloorCalibrate(int threshold) {
_threshold = threshold;
if (_num_floor_samples >= NUM_NOISE_FLOOR_SAMPLES) { // ignore trigger if currently sampling
// Only restart sampling when threshold > 0 (interference detection enabled).
// With threshold=0 the noise floor is never used, so periodic SPI re-sampling
// is pointless — and on boards with a shared-bus IRQ (e.g. TCA9535) the BUSY
// transitions generated by those SPI calls fire spurious interrupts that break RX.
if (threshold > 0 && _num_floor_samples >= NUM_NOISE_FLOOR_SAMPLES) {
_num_floor_samples = 0;
_floor_sample_sum = 0;
}
@ -96,6 +100,11 @@ bool RadioLibWrapper::isInRecvMode() const {
}
int RadioLibWrapper::recvRaw(uint8_t* bytes, int sz) {
// Allow subclasses to gate STATE_INT_READY on the physical IRQ pin.
// On boards with a shared-bus /INT (e.g. TCA9535), this reads DIO1
// via I2C and only sets STATE_INT_READY when the radio actually asserted it.
dispatchPendingIrq();
int len = 0;
if (state & STATE_INT_READY) {
len = _radio->getPacketLength();
@ -115,7 +124,13 @@ int RadioLibWrapper::recvRaw(uint8_t* bytes, int sz) {
state = STATE_IDLE; // need another startReceive()
}
if (state != STATE_RX && !isReceivingPacket()) {
if (state != STATE_RX) {
// Note: we intentionally do NOT call isReceivingPacket() here.
// On boards where DIO1/BUSY share a TCA9535 /INT line, isReceivingPacket()
// does an SPI read which generates another BUSY pulse → another spurious
// interrupt → infinite pkt_len=0 loop. Calling startReceive() unconditionally
// is safe: if pkt_len was 0 the packet was not yet complete (or the interrupt
// was fully spurious), so restarting RX is correct.
int err = _radio->startReceive();
if (err == RADIOLIB_ERR_NONE) {
state = STATE_RX;

8
src/helpers/radiolib/RadioLibWrappers.h

@ -17,6 +17,14 @@ protected:
float packetScoreInt(float snr, int sf, int packet_len);
virtual bool isReceivingPacket() =0;
// Called at the start of recvRaw() before checking STATE_INT_READY.
// Override on boards where the radio IRQ line is shared with other inputs
// (e.g. TCA9535 Port 0 also carries BUSY and touch INT). The override
// must read the physical IRQ pin and, ONLY if the radio has actually
// asserted it, call setFlag() to set STATE_INT_READY. Default: no-op
// (interrupt-driven boards set STATE_INT_READY directly in the ISR).
virtual void dispatchPendingIrq() {}
public:
RadioLibWrapper(PhysicalLayer& radio, mesh::MainBoard& board) : _radio(&radio), _board(&board) { n_recv = n_sent = 0; }

2
variants/sensecap_indicator-espnow/platformio.ini

@ -43,7 +43,7 @@ build_flags =
-D LV_CONF_PATH=lv_conf.h
-D SEEED_SENSECAP_INDICATOR
-D RADIO_CLASS=CustomSX1262
-D WRAPPER_CLASS=CustomSX1262Wrapper
-D WRAPPER_CLASS=SenseCapSX1262Wrapper
-D PIN_USER_BTN=38
-D LANG_GR
-D LORA_FREQ=869.525

5
variants/sensecap_indicator-espnow/target.h

@ -10,7 +10,8 @@
#include <helpers/radiolib/RadioLibWrappers.h>
#include <helpers/radiolib/CustomSX1262Wrapper.h>
#include <helpers/AutoDiscoverRTCClock.h>
#include <helpers/esp32/SenseCapHAL.h> // TCA9535 IO expander HAL for RadioLib
#include <helpers/esp32/SenseCapHAL.h> // TCA9535 IO expander HAL for RadioLib
#include <helpers/esp32/SenseCapSX1262Wrapper.h> // DIO1-verified IRQ dispatch
#include <helpers/SensorManager.h>
#include <helpers/sensors/EnvironmentSensorManager.h>
@ -34,7 +35,7 @@ extern ESP32Board board;
// -------------------------------------------------
// Radio (SX1262 - SenseCAP uses SX1262)
// -------------------------------------------------
extern CustomSX1262Wrapper radio_driver;
extern SenseCapSX1262Wrapper radio_driver;
// -------------------------------------------------

Loading…
Cancel
Save