mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-10-07 18:26:39 +00:00
fix(radio): keep the radio serviced while an e-ink refresh blocks the loop
GxEPD2 polls the panel's BUSY pin for the whole refresh (up to ~1-2s), stalling the main loop. A TX finishing in that window left the SX126x in standby -- deaf -- until the refresh ended; packets received meanwhile could be lost (only the latest is readable, and a later CRC failure rejects a good one still pending). Hook GxEPD2's busy callback to finish TX / drain RX into a small staging queue and re-arm right away, without touching dispatch/UI. Wired only where the display driver supports it (GxEPD2: the two e-ink Solo boards); OLED and sim builds are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -15,6 +15,16 @@
|
|||||||
static bool g_sim_ready = false;
|
static bool g_sim_ready = false;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef DISPLAY_HAS_BUSY_PUMP
|
||||||
|
// Run from the e-ink driver's BUSY-pin wait (DisplayDriver::setBusyPumpFn(),
|
||||||
|
// wired in setup() below) so packets landing during a refresh are pulled off
|
||||||
|
// the radio as they arrive, and a TX that finishes mid-refresh goes straight
|
||||||
|
// back to listening -- see RadioLibWrapper::pumpRecvDuringBlockingWait().
|
||||||
|
static void pumpRadioDuringDisplayBusyWait(void*) {
|
||||||
|
radio_driver.pumpRecvDuringBlockingWait();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// Believe it or not, this std C function is busted on some platforms!
|
// Believe it or not, this std C function is busted on some platforms!
|
||||||
static uint32_t _atoi(const char* sp) {
|
static uint32_t _atoi(const char* sp) {
|
||||||
uint32_t n = 0;
|
uint32_t n = 0;
|
||||||
@@ -293,6 +303,9 @@ void setup() {
|
|||||||
if (disp && the_mesh.getNodePrefs())
|
if (disp && the_mesh.getNodePrefs())
|
||||||
disp->setBrightness(the_mesh.getNodePrefs()->display_brightness);
|
disp->setBrightness(the_mesh.getNodePrefs()->display_brightness);
|
||||||
ui_task.begin(disp, &sensors, the_mesh.getNodePrefs()); // still want to pass this in as dependency, as prefs might be moved
|
ui_task.begin(disp, &sensors, the_mesh.getNodePrefs()); // still want to pass this in as dependency, as prefs might be moved
|
||||||
|
#ifdef DISPLAY_HAS_BUSY_PUMP
|
||||||
|
if (disp) disp->setBusyPumpFn(pumpRadioDuringDisplayBusyWait, nullptr);
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef NRF52_PLATFORM
|
#ifdef NRF52_PLATFORM
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ public:
|
|||||||
float getCurrentRSSI() override {
|
float getCurrentRSSI() override {
|
||||||
return ((CustomLLCC68 *)_radio)->getRSSI(false);
|
return ((CustomLLCC68 *)_radio)->getRSSI(false);
|
||||||
}
|
}
|
||||||
float getLastRSSI() const override { return ((CustomLLCC68 *)_radio)->getRSSI(); }
|
float readLiveRSSI() const override { return ((CustomLLCC68 *)_radio)->getRSSI(); }
|
||||||
float getLastSNR() const override { return ((CustomLLCC68 *)_radio)->getSNR(); }
|
float readLiveSNR() const override { return ((CustomLLCC68 *)_radio)->getSNR(); }
|
||||||
|
|
||||||
float packetScore(float snr, int packet_len) override {
|
float packetScore(float snr, int packet_len) override {
|
||||||
int sf = ((CustomLLCC68 *)_radio)->spreadingFactor;
|
int sf = ((CustomLLCC68 *)_radio)->spreadingFactor;
|
||||||
|
|||||||
@@ -38,8 +38,8 @@ public:
|
|||||||
_radio->setPreambleLength(preambleLengthForSF(getSpreadingFactor())); // overcomes weird issues with small and big pkts
|
_radio->setPreambleLength(preambleLengthForSF(getSpreadingFactor())); // overcomes weird issues with small and big pkts
|
||||||
}
|
}
|
||||||
|
|
||||||
float getLastRSSI() const override { return ((CustomLR1110 *)_radio)->getRSSI(); }
|
float readLiveRSSI() const override { return ((CustomLR1110 *)_radio)->getRSSI(); }
|
||||||
float getLastSNR() const override { return ((CustomLR1110 *)_radio)->getSNR(); }
|
float readLiveSNR() const override { return ((CustomLR1110 *)_radio)->getSNR(); }
|
||||||
|
|
||||||
uint8_t getSpreadingFactor() const override { return ((CustomLR1110 *)_radio)->getSpreadingFactor(); }
|
uint8_t getSpreadingFactor() const override { return ((CustomLR1110 *)_radio)->getSpreadingFactor(); }
|
||||||
|
|
||||||
|
|||||||
@@ -88,8 +88,8 @@ public:
|
|||||||
_radio->setPreambleLength(preambleLengthForSF(getSpreadingFactor())); // overcomes weird issues with small and big pkts
|
_radio->setPreambleLength(preambleLengthForSF(getSpreadingFactor())); // overcomes weird issues with small and big pkts
|
||||||
}
|
}
|
||||||
|
|
||||||
float getLastRSSI() const override { return ((CustomLR2021 *)_radio)->getRSSI(); }
|
float readLiveRSSI() const override { return ((CustomLR2021 *)_radio)->getRSSI(); }
|
||||||
float getLastSNR() const override { return ((CustomLR2021 *)_radio)->getSNR(); }
|
float readLiveSNR() const override { return ((CustomLR2021 *)_radio)->getSNR(); }
|
||||||
|
|
||||||
uint8_t getSpreadingFactor() const override { return ((CustomLR2021 *)_radio)->getSpreadingFactor(); }
|
uint8_t getSpreadingFactor() const override { return ((CustomLR2021 *)_radio)->getSpreadingFactor(); }
|
||||||
|
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ public:
|
|||||||
float getCurrentRSSI() override {
|
float getCurrentRSSI() override {
|
||||||
return ((CustomSTM32WLx *)_radio)->getRSSI(false);
|
return ((CustomSTM32WLx *)_radio)->getRSSI(false);
|
||||||
}
|
}
|
||||||
float getLastRSSI() const override { return ((CustomSTM32WLx *)_radio)->getRSSI(); }
|
float readLiveRSSI() const override { return ((CustomSTM32WLx *)_radio)->getRSSI(); }
|
||||||
float getLastSNR() const override { return ((CustomSTM32WLx *)_radio)->getSNR(); }
|
float readLiveSNR() const override { return ((CustomSTM32WLx *)_radio)->getSNR(); }
|
||||||
|
|
||||||
float packetScore(float snr, int packet_len) override {
|
float packetScore(float snr, int packet_len) override {
|
||||||
int sf = ((CustomSTM32WLx *)_radio)->spreadingFactor;
|
int sf = ((CustomSTM32WLx *)_radio)->spreadingFactor;
|
||||||
|
|||||||
@@ -42,8 +42,8 @@ public:
|
|||||||
float getCurrentRSSI() override {
|
float getCurrentRSSI() override {
|
||||||
return ((CustomSX1262 *)_radio)->getRSSI(false);
|
return ((CustomSX1262 *)_radio)->getRSSI(false);
|
||||||
}
|
}
|
||||||
float getLastRSSI() const override { return ((CustomSX1262 *)_radio)->getRSSI(); }
|
float readLiveRSSI() const override { return ((CustomSX1262 *)_radio)->getRSSI(); }
|
||||||
float getLastSNR() const override { return ((CustomSX1262 *)_radio)->getSNR(); }
|
float readLiveSNR() const override { return ((CustomSX1262 *)_radio)->getSNR(); }
|
||||||
|
|
||||||
float packetScore(float snr, int packet_len) override {
|
float packetScore(float snr, int packet_len) override {
|
||||||
int sf = ((CustomSX1262 *)_radio)->spreadingFactor;
|
int sf = ((CustomSX1262 *)_radio)->spreadingFactor;
|
||||||
|
|||||||
@@ -29,8 +29,8 @@ public:
|
|||||||
float getCurrentRSSI() override {
|
float getCurrentRSSI() override {
|
||||||
return ((CustomSX1268 *)_radio)->getRSSI(false);
|
return ((CustomSX1268 *)_radio)->getRSSI(false);
|
||||||
}
|
}
|
||||||
float getLastRSSI() const override { return ((CustomSX1268 *)_radio)->getRSSI(); }
|
float readLiveRSSI() const override { return ((CustomSX1268 *)_radio)->getRSSI(); }
|
||||||
float getLastSNR() const override { return ((CustomSX1268 *)_radio)->getSNR(); }
|
float readLiveSNR() const override { return ((CustomSX1268 *)_radio)->getSNR(); }
|
||||||
|
|
||||||
float packetScore(float snr, int packet_len) override {
|
float packetScore(float snr, int packet_len) override {
|
||||||
int sf = ((CustomSX1268 *)_radio)->spreadingFactor;
|
int sf = ((CustomSX1268 *)_radio)->spreadingFactor;
|
||||||
|
|||||||
@@ -25,8 +25,8 @@ public:
|
|||||||
float getCurrentRSSI() override {
|
float getCurrentRSSI() override {
|
||||||
return ((CustomSX1276 *)_radio)->getRSSI(false);
|
return ((CustomSX1276 *)_radio)->getRSSI(false);
|
||||||
}
|
}
|
||||||
float getLastRSSI() const override { return ((CustomSX1276 *)_radio)->getRSSI(); }
|
float readLiveRSSI() const override { return ((CustomSX1276 *)_radio)->getRSSI(); }
|
||||||
float getLastSNR() const override { return ((CustomSX1276 *)_radio)->getSNR(); }
|
float readLiveSNR() const override { return ((CustomSX1276 *)_radio)->getSNR(); }
|
||||||
|
|
||||||
float packetScore(float snr, int packet_len) override {
|
float packetScore(float snr, int packet_len) override {
|
||||||
int sf = ((CustomSX1276 *)_radio)->spreadingFactor;
|
int sf = ((CustomSX1276 *)_radio)->spreadingFactor;
|
||||||
|
|||||||
@@ -253,6 +253,23 @@ bool RadioLibWrapper::isInRecvMode() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int RadioLibWrapper::recvRaw(uint8_t* bytes, int sz) {
|
int RadioLibWrapper::recvRaw(uint8_t* bytes, int sz) {
|
||||||
|
if (_pump_count > 0) {
|
||||||
|
// pumpRecvDuringBlockingWait() already pulled this off the chip while
|
||||||
|
// the main loop was stuck (e-ink refresh) -- hand over the staged copy.
|
||||||
|
// Any packet that has landed since stays flagged and is read live once
|
||||||
|
// the queue is empty.
|
||||||
|
uint8_t slot = _pump_head;
|
||||||
|
int len = _pump_len[slot];
|
||||||
|
if (len > sz) len = sz;
|
||||||
|
memcpy(bytes, _pump_data[slot], len);
|
||||||
|
_pumped_pop_snr = _pump_snr[slot];
|
||||||
|
_pumped_pop_rssi = _pump_rssi[slot];
|
||||||
|
_last_recv_was_pumped = true;
|
||||||
|
_pump_head = (_pump_head + 1) % PUMP_QUEUE_SIZE;
|
||||||
|
_pump_count--;
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
int len = 0;
|
int len = 0;
|
||||||
if (state & STATE_INT_READY) {
|
if (state & STATE_INT_READY) {
|
||||||
len = _radio->getPacketLength();
|
len = _radio->getPacketLength();
|
||||||
@@ -268,6 +285,7 @@ int RadioLibWrapper::recvRaw(uint8_t* bytes, int sz) {
|
|||||||
n_recv++;
|
n_recv++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
_last_recv_was_pumped = false;
|
||||||
#if defined(USE_LR2021)
|
#if defined(USE_LR2021)
|
||||||
state = STATE_RX; // LR2021 stays in Rx after readData, calling startReceive while still in Rx throws -706 errors
|
state = STATE_RX; // LR2021 stays in Rx after readData, calling startReceive while still in Rx throws -706 errors
|
||||||
#else
|
#else
|
||||||
@@ -281,6 +299,49 @@ int RadioLibWrapper::recvRaw(uint8_t* bytes, int sz) {
|
|||||||
return len;
|
return len;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// See the class comment in RadioLibWrappers.h. STATE_INT_READY is shared by
|
||||||
|
// the TX-done and RX-done interrupts, so the base state disambiguates them;
|
||||||
|
// anything else (mid-TX, idle) is left alone.
|
||||||
|
void RadioLibWrapper::pumpRecvDuringBlockingWait() {
|
||||||
|
if (state == (uint8_t)(STATE_TX_WAIT | STATE_INT_READY)) {
|
||||||
|
// After TX_DONE the chip falls back to standby: deaf until the main loop
|
||||||
|
// reaches isSendComplete()/onSendFinished(). Do the radio side of
|
||||||
|
// onSendFinished() now and go straight back to RX; those two then only
|
||||||
|
// report it (see _tx_done_early).
|
||||||
|
_radio->finishTransmit();
|
||||||
|
_board->onAfterTransmit();
|
||||||
|
n_sent++;
|
||||||
|
_tx_done_early = true;
|
||||||
|
state = STATE_IDLE;
|
||||||
|
armRecv();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (state != (uint8_t)(STATE_RX | STATE_INT_READY)) return;
|
||||||
|
if (_pump_count >= PUMP_QUEUE_SIZE) return; // staging full -- let the main loop drain it via recvRaw() first
|
||||||
|
|
||||||
|
uint8_t slot = (_pump_head + _pump_count) % PUMP_QUEUE_SIZE;
|
||||||
|
int len = _radio->getPacketLength();
|
||||||
|
if (len > 0) {
|
||||||
|
if (len > MAX_TRANS_UNIT) len = MAX_TRANS_UNIT;
|
||||||
|
int err = _radio->readData(_pump_data[slot], len);
|
||||||
|
if (err == RADIOLIB_ERR_NONE) {
|
||||||
|
_pump_len[slot] = (uint8_t)len;
|
||||||
|
_pump_snr[slot] = readLiveSNR();
|
||||||
|
_pump_rssi[slot] = readLiveRSSI();
|
||||||
|
_pump_count++;
|
||||||
|
n_recv++;
|
||||||
|
} else {
|
||||||
|
n_recv_errors++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#if defined(USE_LR2021)
|
||||||
|
state = STATE_RX;
|
||||||
|
#else
|
||||||
|
state = STATE_IDLE;
|
||||||
|
#endif
|
||||||
|
armRecv();
|
||||||
|
}
|
||||||
|
|
||||||
uint32_t RadioLibWrapper::getEstAirtimeFor(int len_bytes) {
|
uint32_t RadioLibWrapper::getEstAirtimeFor(int len_bytes) {
|
||||||
return _radio->getTimeOnAir(len_bytes) / 1000;
|
return _radio->getTimeOnAir(len_bytes) / 1000;
|
||||||
}
|
}
|
||||||
@@ -299,6 +360,9 @@ bool RadioLibWrapper::startSendRaw(const uint8_t* bytes, int len) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool RadioLibWrapper::isSendComplete() {
|
bool RadioLibWrapper::isSendComplete() {
|
||||||
|
// Already finished by pumpRecvDuringBlockingWait(): state now belongs to
|
||||||
|
// the re-armed RX (and may flag a newly received packet), so don't touch it.
|
||||||
|
if (_tx_done_early) return true;
|
||||||
if (state & STATE_INT_READY) {
|
if (state & STATE_INT_READY) {
|
||||||
state = STATE_IDLE;
|
state = STATE_IDLE;
|
||||||
n_sent++;
|
n_sent++;
|
||||||
@@ -308,6 +372,10 @@ bool RadioLibWrapper::isSendComplete() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void RadioLibWrapper::onSendFinished() {
|
void RadioLibWrapper::onSendFinished() {
|
||||||
|
if (_tx_done_early) {
|
||||||
|
_tx_done_early = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
_radio->finishTransmit();
|
_radio->finishTransmit();
|
||||||
_board->onAfterTransmit();
|
_board->onAfterTransmit();
|
||||||
state = STATE_IDLE;
|
state = STATE_IDLE;
|
||||||
@@ -336,10 +404,13 @@ bool RadioLibWrapper::isChannelActive() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
float RadioLibWrapper::getLastRSSI() const {
|
float RadioLibWrapper::getLastRSSI() const {
|
||||||
return _radio->getRSSI();
|
// A packet handed back via the pump-queue path (recvRaw()) needs the
|
||||||
|
// reading captured when it was pulled off the chip, not whatever's newest
|
||||||
|
// in the registers now -- see the field comments in RadioLibWrappers.h.
|
||||||
|
return _last_recv_was_pumped ? _pumped_pop_rssi : readLiveRSSI();
|
||||||
}
|
}
|
||||||
float RadioLibWrapper::getLastSNR() const {
|
float RadioLibWrapper::getLastSNR() const {
|
||||||
return _radio->getSNR();
|
return _last_recv_was_pumped ? _pumped_pop_snr : readLiveSNR();
|
||||||
}
|
}
|
||||||
|
|
||||||
float RadioLibWrapper::packetScoreInt(float snr, int sf, int packet_len) {
|
float RadioLibWrapper::packetScoreInt(float snr, int sf, int packet_len) {
|
||||||
|
|||||||
@@ -89,6 +89,47 @@ protected:
|
|||||||
// firmware defaults (frequency/bandwidth/SF/CR/TX power/preamble/gain).
|
// firmware defaults (frequency/bandwidth/SF/CR/TX power/preamble/gain).
|
||||||
virtual bool radioHardReset() { return false; }
|
virtual bool radioHardReset() { return false; }
|
||||||
|
|
||||||
|
// --- Busy-wait packet pump (see pumpRecvDuringBlockingWait() below) ---
|
||||||
|
// A display driver's e-ink busy-wait can block the main loop for over a
|
||||||
|
// second per refresh (GxEPD2 setBusyCallback()). The radio stays in
|
||||||
|
// continuous RX meanwhile, but only the latest packet is readable: the
|
||||||
|
// chip reports a single length/offset, so if a second packet completes
|
||||||
|
// before the loop gets back to recvRaw(), the first is lost -- and IRQ
|
||||||
|
// flags are sticky, so a later CRC-failed packet makes readData() reject
|
||||||
|
// a good one still waiting. pumpRecvDuringBlockingWait() pulls each packet
|
||||||
|
// into this small staging queue as it lands, deliberately WITHOUT touching
|
||||||
|
// packet parsing/dispatch (Dispatcher::checkRecv() does that later, back
|
||||||
|
// on the main loop) -- reaching into routing/UI from here would reenter
|
||||||
|
// the very display code that's blocked calling us.
|
||||||
|
// A TX finishing mid-refresh is worse: after TX_DONE the chip falls back to
|
||||||
|
// standby and hears nothing at all until re-armed -- so the pump also
|
||||||
|
// finishes the TX and resumes RX right away.
|
||||||
|
static const uint8_t PUMP_QUEUE_SIZE = 2;
|
||||||
|
uint8_t _pump_data[PUMP_QUEUE_SIZE][MAX_TRANS_UNIT];
|
||||||
|
uint8_t _pump_len[PUMP_QUEUE_SIZE];
|
||||||
|
float _pump_snr[PUMP_QUEUE_SIZE];
|
||||||
|
float _pump_rssi[PUMP_QUEUE_SIZE];
|
||||||
|
uint8_t _pump_count = 0, _pump_head = 0;
|
||||||
|
// Set whenever recvRaw() hands back a packet that pumpRecvDuringBlockingWait()
|
||||||
|
// already pulled off the chip, so getLastRSSI()/getLastSNR() (below) report
|
||||||
|
// the reading captured at THAT read -- not whatever's newest in the chip's
|
||||||
|
// registers if another packet has landed (live or pumped) since.
|
||||||
|
bool _last_recv_was_pumped = false;
|
||||||
|
float _pumped_pop_snr = 0, _pumped_pop_rssi = 0;
|
||||||
|
// The pump already did onSendFinished()'s radio work and re-armed RX, so
|
||||||
|
// isSendComplete() reports done and onSendFinished() skips the radio part
|
||||||
|
// (it would drop the chip back to standby and clobber a pending RX flag).
|
||||||
|
// A subclass onSendFinished() extension then runs with RX already armed.
|
||||||
|
bool _tx_done_early = false;
|
||||||
|
|
||||||
|
// Chip-specific SNR/RSSI register reads. Implemented per radio type
|
||||||
|
// (renamed from what used to be each subclass's getLastRSSI()/getLastSNR()
|
||||||
|
// override) so the base class can route getLastRSSI()/getLastSNR() through
|
||||||
|
// the pumped-packet cache above instead of every subclass needing to know
|
||||||
|
// about it.
|
||||||
|
virtual float readLiveRSSI() const = 0;
|
||||||
|
virtual float readLiveSNR() const = 0;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
RadioLibWrapper(PhysicalLayer& radio, mesh::MainBoard& board) : _radio(&radio), _board(&board), _preamble_sf(0) { n_recv = n_sent = 0; }
|
RadioLibWrapper(PhysicalLayer& radio, mesh::MainBoard& board) : _radio(&radio), _board(&board), _preamble_sf(0) { n_recv = n_sent = 0; }
|
||||||
|
|
||||||
@@ -144,6 +185,12 @@ public:
|
|||||||
|
|
||||||
void loop() override;
|
void loop() override;
|
||||||
|
|
||||||
|
// Called only from a display driver's busy-wait callback (see
|
||||||
|
// DisplayDriver::setBusyPumpFn()) while the main loop is blocked -- see
|
||||||
|
// the class comment above. Never call this from anywhere that could end
|
||||||
|
// up back inside display/UI code.
|
||||||
|
void pumpRecvDuringBlockingWait();
|
||||||
|
|
||||||
uint32_t getPacketsRecv() const { return n_recv; }
|
uint32_t getPacketsRecv() const { return n_recv; }
|
||||||
uint32_t getPacketsRecvErrors() const { return n_recv_errors; }
|
uint32_t getPacketsRecvErrors() const { return n_recv_errors; }
|
||||||
uint32_t getPacketsSent() const { return n_sent; }
|
uint32_t getPacketsSent() const { return n_sent; }
|
||||||
|
|||||||
@@ -30,6 +30,14 @@ protected:
|
|||||||
unsigned long _marquee_next_at = 0;
|
unsigned long _marquee_next_at = 0;
|
||||||
DisplayDriver(int w, int h) { _w = w; _h = h; }
|
DisplayDriver(int w, int h) { _w = w; _h = h; }
|
||||||
void setDimensions(int w, int h) { _w = w; _h = h; }
|
void setDimensions(int w, int h) { _w = w; _h = h; }
|
||||||
|
|
||||||
|
// Registered via setBusyPumpFn() below; drivers whose underlying vendor
|
||||||
|
// library exposes a hook for its blocking hardware wait (e.g. GxEPD2's
|
||||||
|
// setBusyCallback()) call this from it. Default no-op, so any driver/board
|
||||||
|
// that never wires one up behaves exactly as before.
|
||||||
|
void (*_busy_pump_fn)(void*) = nullptr;
|
||||||
|
void* _busy_pump_ctx = nullptr;
|
||||||
|
void callBusyPump() { if (_busy_pump_fn) _busy_pump_fn(_busy_pump_ctx); }
|
||||||
public:
|
public:
|
||||||
enum Color { DARK=0, LIGHT, RED, GREEN, BLUE, YELLOW, ORANGE }; // on b/w screen, colors will be !=0 synonym of light
|
enum Color { DARK=0, LIGHT, RED, GREEN, BLUE, YELLOW, ORANGE }; // on b/w screen, colors will be !=0 synonym of light
|
||||||
|
|
||||||
@@ -499,6 +507,14 @@ public:
|
|||||||
virtual void setFullRefreshInterval(uint8_t n) { } // e-ink: do full refresh every n partial refreshes (0=never)
|
virtual void setFullRefreshInterval(uint8_t n) { } // e-ink: do full refresh every n partial refreshes (0=never)
|
||||||
virtual void endFrame() = 0;
|
virtual void endFrame() = 0;
|
||||||
|
|
||||||
|
// Called from board setup to run safe background work during a blocking
|
||||||
|
// hardware wait (currently: an e-ink panel's BUSY-pin poll, which can run
|
||||||
|
// for over a second on a full refresh and would otherwise stall the whole
|
||||||
|
// main loop -- see RadioLibWrapper::pumpRecvDuringBlockingWait(), the
|
||||||
|
// motivating use). fn must not touch this display or any UI state: it can
|
||||||
|
// run reentrantly, nested inside the very call it's servicing.
|
||||||
|
void setBusyPumpFn(void (*fn)(void*), void* ctx) { _busy_pump_fn = fn; _busy_pump_ctx = ctx; }
|
||||||
|
|
||||||
#ifdef ENABLE_SCREENSHOT
|
#ifdef ENABLE_SCREENSHOT
|
||||||
// Screenshot support — return raw framebuffer and its size in bytes.
|
// Screenshot support — return raw framebuffer and its size in bytes.
|
||||||
// 0=OLED (page-based, column-major), 1=e-ink (row-major, MSB-first, 1=white/0=black).
|
// 0=OLED (page-based, column-major), 1=e-ink (row-major, MSB-first, 1=white/0=black).
|
||||||
|
|||||||
@@ -84,6 +84,10 @@ bool GxEPDDisplay::begin() {
|
|||||||
SPI1.begin();
|
SPI1.begin();
|
||||||
#endif
|
#endif
|
||||||
display.init(115200, true, 2, false);
|
display.init(115200, true, 2, false);
|
||||||
|
// Runs ~every 1ms while _waitWhileBusy() polls the panel's BUSY pin, i.e.
|
||||||
|
// for as long as a refresh blocks the main loop. See busyCallbackTrampoline
|
||||||
|
// and callBusyPump()/setBusyPumpFn() (DisplayDriver.h).
|
||||||
|
display.epd2.setBusyCallback(busyCallbackTrampoline, this);
|
||||||
display.setRotation(DISPLAY_ROTATION);
|
display.setRotation(DISPLAY_ROTATION);
|
||||||
setTextSize(1);
|
setTextSize(1);
|
||||||
display.setPartialWindow(0, 0, display.width(), display.height());
|
display.setPartialWindow(0, 0, display.width(), display.height());
|
||||||
|
|||||||
@@ -24,6 +24,11 @@
|
|||||||
#include "DisplayDriver.h"
|
#include "DisplayDriver.h"
|
||||||
#include "MiscFixedFont.h"
|
#include "MiscFixedFont.h"
|
||||||
|
|
||||||
|
// This driver calls callBusyPump() during its BUSY-pin waits; app code keys
|
||||||
|
// its setBusyPumpFn() wiring off this so it isn't compiled for displays (or
|
||||||
|
// the sim, whose radio isn't a RadioLibWrapper) that never would.
|
||||||
|
#define DISPLAY_HAS_BUSY_PUMP 1
|
||||||
|
|
||||||
#ifndef DISPLAY_ROTATION
|
#ifndef DISPLAY_ROTATION
|
||||||
#define DISPLAY_ROTATION 0
|
#define DISPLAY_ROTATION 0
|
||||||
#endif
|
#endif
|
||||||
@@ -68,6 +73,18 @@ class GxEPDDisplay : public DisplayDriver {
|
|||||||
uint8_t glyphXAdvance(uint32_t cp, int sc);
|
uint8_t glyphXAdvance(uint32_t cp, int sc);
|
||||||
int scale() const { return (width() >= height()) ? 2 : 1; }
|
int scale() const { return (width() >= height()) ? 2 : 1; }
|
||||||
|
|
||||||
|
// GxEPD2_EPD::setBusyCallback() wants a plain function pointer with a
|
||||||
|
// void* param, not a member function -- this trampolines back into the
|
||||||
|
// instance so callBusyPump() (DisplayDriver.h) can reach whatever board
|
||||||
|
// setup registered via setBusyPumpFn(). _waitWhileBusy() calls the callback
|
||||||
|
// *instead of* its own delay(1), so keep that delay here: without it the
|
||||||
|
// wait becomes a hard spin that starves the RTOS idle task (no CPU sleep)
|
||||||
|
// and equal-priority tasks for the whole refresh.
|
||||||
|
static void busyCallbackTrampoline(const void* param) {
|
||||||
|
((GxEPDDisplay*)param)->callBusyPump();
|
||||||
|
delay(1);
|
||||||
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
#if defined(EINK_DISPLAY_MODEL)
|
#if defined(EINK_DISPLAY_MODEL)
|
||||||
GxEPDDisplay() : DisplayDriver(EINK_DISP_W, EINK_DISP_H),
|
GxEPDDisplay() : DisplayDriver(EINK_DISP_W, EINK_DISP_H),
|
||||||
|
|||||||
Reference in New Issue
Block a user