From 30379ee48a26e1c02f6ece75c202c362a7ddc770 Mon Sep 17 00:00:00 2001 From: liquidraver <504870+liquidraver@users.noreply.github.com> Date: Wed, 4 Mar 2026 22:36:34 +0100 Subject: [PATCH] =?UTF-8?q?LoRaRadioBase=20volatile=20=E2=86=92=20atomic?= =?UTF-8?q?=5Ft=20LR1110=20BUSY=20pin=20=E2=86=92=20interrupt-driven?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- zephcore/adapters/radio/LR1110Radio.cpp | 4 +- zephcore/adapters/radio/LoRaRadioBase.cpp | 64 +++++++++--------- zephcore/adapters/radio/LoRaRadioBase.h | 30 ++++----- zephcore/adapters/radio/SX126xRadio.cpp | 4 +- .../adapters/radio/lr11xx/lr11xx_hal_zephyr.c | 65 +++++++++++++++---- 5 files changed, 103 insertions(+), 64 deletions(-) diff --git a/zephcore/adapters/radio/LR1110Radio.cpp b/zephcore/adapters/radio/LR1110Radio.cpp index de0fdd0..d372b6a 100644 --- a/zephcore/adapters/radio/LR1110Radio.cpp +++ b/zephcore/adapters/radio/LR1110Radio.cpp @@ -46,10 +46,10 @@ void LR1110Radio::hwStartReceive() int ret = lora_recv_async(_dev, rxCallbackStatic, this); if (ret < 0) { LOG_ERR("lora_recv_async failed: %d", ret); - _in_recv_mode = false; + atomic_set(&_in_recv_mode, 0); return; } - _in_recv_mode = true; + atomic_set(&_in_recv_mode, 1); /* RX boost: set once via setRxBoost(), LR1110 SetRxBoosted * command persists through SetRx calls. */ diff --git a/zephcore/adapters/radio/LoRaRadioBase.cpp b/zephcore/adapters/radio/LoRaRadioBase.cpp index 57c9a1f..1c49c64 100644 --- a/zephcore/adapters/radio/LoRaRadioBase.cpp +++ b/zephcore/adapters/radio/LoRaRadioBase.cpp @@ -22,7 +22,7 @@ namespace mesh { LoRaRadioBase::LoRaRadioBase(const struct device *lora_dev, MainBoard &board, NodePrefs *prefs) : _dev(lora_dev), _prefs(prefs), _board(&board), - _in_recv_mode(false), _tx_active(false), + _in_recv_mode(0), _tx_active(0), _last_rssi(0), _last_snr(0), _rx_head(0), _rx_tail(0), _noise_floor(DEFAULT_NOISE_FLOOR), _calibration_threshold(0), _ema_unguarded(0), @@ -52,7 +52,7 @@ void LoRaRadioBase::txWaitThreadFn(void *p1, void *p2, void *p3) for (;;) { k_sem_take(&self->_tx_start_sem, K_FOREVER); - if (!self->_tx_active) { + if (!atomic_get(&self->_tx_active)) { continue; } @@ -73,8 +73,8 @@ void LoRaRadioBase::txWaitThreadFn(void *p1, void *p2, void *p3) k_poll_signal_reset(&self->_tx_signal); self->_board->onAfterTransmit(); self->startReceive(); - self->_tx_active = false; - self->_packets_sent++; + atomic_set(&self->_tx_active, 0); + atomic_inc(&self->_packets_sent); if (self->_tx_done_cb) { self->_tx_done_cb(self->_tx_done_cb_user_data); } @@ -86,7 +86,7 @@ void LoRaRadioBase::txWaitThreadFn(void *p1, void *p2, void *p3) LOG_ERR("TX wait: TIMEOUT!"); self->_board->onAfterTransmit(); self->startReceive(); - self->_tx_active = false; + atomic_set(&self->_tx_active, 0); if (self->_tx_done_cb) { self->_tx_done_cb(self->_tx_done_cb_user_data); } @@ -97,8 +97,8 @@ void LoRaRadioBase::txWaitThreadFn(void *p1, void *p2, void *p3) k_poll_signal_reset(&self->_tx_signal); self->_board->onAfterTransmit(); self->startReceive(); - self->_tx_active = false; - self->_packets_sent++; + atomic_set(&self->_tx_active, 0); + atomic_inc(&self->_packets_sent); LOG_INF("TX complete, RX restarted"); if (self->_tx_done_cb) { @@ -110,7 +110,7 @@ void LoRaRadioBase::txWaitThreadFn(void *p1, void *p2, void *p3) k_poll_signal_reset(&self->_tx_signal); self->_board->onAfterTransmit(); self->startReceive(); - self->_tx_active = false; + atomic_set(&self->_tx_active, 0); if (self->_tx_done_cb) { self->_tx_done_cb(self->_tx_done_cb_user_data); @@ -141,9 +141,9 @@ void LoRaRadioBase::rxCallbackStatic(const struct device *dev, uint8_t *data, /* NULL data = RX error (CRC/header error) */ if (data == NULL && size == 0) { - self->_packets_recv_errors++; + atomic_inc(&self->_packets_recv_errors); LOG_DBG("RX error (CRC/header), total errors: %u", - self->_packets_recv_errors); + (uint32_t)atomic_get(&self->_packets_recv_errors)); return; } @@ -152,27 +152,28 @@ void LoRaRadioBase::rxCallbackStatic(const struct device *dev, uint8_t *data, /* Ring buffer write — SPSC: only ISR writes _rx_head, only main * thread writes _rx_tail. On overflow, drop the NEW packet to * preserve this invariant (ISR must never touch _rx_tail). */ - uint8_t next_head = (self->_rx_head + 1) % RX_RING_SIZE; - if (next_head == self->_rx_tail) { + uint8_t head = (uint8_t)atomic_get(&self->_rx_head); + uint8_t next_head = (head + 1) % RX_RING_SIZE; + if (next_head == (uint8_t)atomic_get(&self->_rx_tail)) { LOG_WRN("RX ring full, dropping new packet"); - self->_packets_recv_errors++; + atomic_inc(&self->_packets_recv_errors); if (self->_rx_cb) { self->_rx_cb(self->_rx_cb_user_data); } return; } - RxPacket *pkt = &self->_rx_ring[self->_rx_head]; + RxPacket *pkt = &self->_rx_ring[head]; uint16_t copy_len = (size > sizeof(pkt->data)) ? sizeof(pkt->data) : size; memcpy(pkt->data, data, copy_len); pkt->len = copy_len; pkt->rssi = rssi; pkt->snr = snr; - self->_rx_head = next_head; + atomic_set(&self->_rx_head, next_head); self->_last_rssi = (float)rssi; self->_last_snr = (float)snr; - self->_packets_recv++; + atomic_inc(&self->_packets_recv); if (self->_rx_cb) { self->_rx_cb(self->_rx_cb_user_data); @@ -337,7 +338,7 @@ void LoRaRadioBase::begin() void LoRaRadioBase::reconfigure() { hwCancelReceive(); - _in_recv_mode = false; + atomic_set(&_in_recv_mode, 0); _config_cached = false; /* Force full reconfigure */ startReceive(); @@ -375,11 +376,12 @@ void LoRaRadioBase::startReceive() int LoRaRadioBase::recvRaw(uint8_t *bytes, int sz) { - if (_rx_head == _rx_tail) { + uint8_t tail = (uint8_t)atomic_get(&_rx_tail); + if (atomic_get(&_rx_head) == tail) { return 0; } - RxPacket *pkt = &_rx_ring[_rx_tail]; + RxPacket *pkt = &_rx_ring[tail]; uint16_t len = pkt->len; if (len > (uint16_t)sz) { len = (uint16_t)sz; @@ -388,7 +390,7 @@ int LoRaRadioBase::recvRaw(uint8_t *bytes, int sz) memcpy(bytes, pkt->data, len); _last_rssi = (float)pkt->rssi; _last_snr = (float)pkt->snr; - _rx_tail = (_rx_tail + 1) % RX_RING_SIZE; + atomic_set(&_rx_tail, (tail + 1) % RX_RING_SIZE); return (int)len; } @@ -399,8 +401,8 @@ bool LoRaRadioBase::startSendRaw(const uint8_t *bytes, int len) } _board->onBeforeTransmit(); - _tx_active = true; - _in_recv_mode = false; + atomic_set(&_tx_active, 1); + atomic_set(&_in_recv_mode, 0); hwCancelReceive(); configureTx(); @@ -412,7 +414,7 @@ bool LoRaRadioBase::startSendRaw(const uint8_t *bytes, int len) if (ret < 0) { LOG_ERR("hwSendAsync failed: %d", ret); _board->onAfterTransmit(); - _tx_active = false; + atomic_set(&_tx_active, 0); startReceive(); return false; } @@ -424,7 +426,7 @@ bool LoRaRadioBase::startSendRaw(const uint8_t *bytes, int len) bool LoRaRadioBase::isSendComplete() { - return !_tx_active; + return !atomic_get(&_tx_active); } void LoRaRadioBase::onSendFinished() @@ -434,7 +436,7 @@ void LoRaRadioBase::onSendFinished() bool LoRaRadioBase::isInRecvMode() const { - return _in_recv_mode; + return atomic_get(&_in_recv_mode) != 0; } float LoRaRadioBase::getLastRSSI() const @@ -499,7 +501,7 @@ void LoRaRadioBase::triggerNoiseFloorCalibrate(int threshold) { _calibration_threshold = threshold; - if (!_in_recv_mode || _tx_active) { + if (!atomic_get(&_in_recv_mode) || atomic_get(&_tx_active)) { return; } @@ -591,7 +593,7 @@ void LoRaRadioBase::resetAGC() /* Don't reset AGC while transmitting or receiving — warm sleep would * abort the TX or corrupt the incoming packet. maintenanceLoop() * will retry next housekeeping cycle. */ - if (_tx_active || isReceiving()) { + if (atomic_get(&_tx_active) || isReceiving()) { return; } @@ -599,7 +601,7 @@ void LoRaRadioBase::resetAGC() /* Warm sleep + calibrate leaves the radio in STANDBY. * Restart receive if we were in RX mode. */ - if (_in_recv_mode) { + if (atomic_get(&_in_recv_mode)) { startReceive(); } @@ -612,7 +614,7 @@ void LoRaRadioBase::resetAGC() bool LoRaRadioBase::isReceiving() { - if (!_in_recv_mode || _tx_active) { + if (!atomic_get(&_in_recv_mode) || atomic_get(&_tx_active)) { return false; } if (hwIsPreambleDetected()) { @@ -639,7 +641,7 @@ void LoRaRadioBase::enableRxDutyCycle(bool enable) { _rx_duty_cycle_enabled = enable; LOG_INF("RX duty cycle %s", enable ? "enabled" : "disabled"); - if (_in_recv_mode) { + if (atomic_get(&_in_recv_mode)) { hwSetRxDutyCycle(enable); } } @@ -649,7 +651,7 @@ void LoRaRadioBase::setRxBoost(bool enable) _rx_boost_enabled = enable; LOG_INF("RX boost %s (+3dB sensitivity, +2mA)", enable ? "enabled" : "disabled"); - if (_in_recv_mode) { + if (atomic_get(&_in_recv_mode)) { hwSetRxBoost(enable); } } diff --git a/zephcore/adapters/radio/LoRaRadioBase.h b/zephcore/adapters/radio/LoRaRadioBase.h index fc3532a..a50f88a 100644 --- a/zephcore/adapters/radio/LoRaRadioBase.h +++ b/zephcore/adapters/radio/LoRaRadioBase.h @@ -49,13 +49,13 @@ public: float getLastSNR() const override; /* Packet statistics */ - uint32_t getPacketsRecv() const override { return _packets_recv; } - uint32_t getPacketsSent() const override { return _packets_sent; } - uint32_t getPacketsRecvErrors() const override { return _packets_recv_errors; } + uint32_t getPacketsRecv() const override { return (uint32_t)atomic_get(&_packets_recv); } + uint32_t getPacketsSent() const override { return (uint32_t)atomic_get(&_packets_sent); } + uint32_t getPacketsRecvErrors() const override { return (uint32_t)atomic_get(&_packets_recv_errors); } void resetStats() { - _packets_recv = 0; - _packets_sent = 0; - _packets_recv_errors = 0; + atomic_set(&_packets_recv, 0); + atomic_set(&_packets_sent, 0); + atomic_set(&_packets_recv_errors, 0); } /* Advanced radio features */ @@ -117,10 +117,10 @@ protected: const struct device *_dev; NodePrefs *_prefs; MainBoard *_board; - volatile bool _in_recv_mode; - volatile bool _tx_active; - volatile float _last_rssi; - volatile float _last_snr; + atomic_t _in_recv_mode; + atomic_t _tx_active; + volatile float _last_rssi; /* word-aligned float — atomic on ARM */ + volatile float _last_snr; /* word-aligned float — atomic on ARM */ /* RX ring buffer */ struct RxPacket { @@ -130,8 +130,8 @@ protected: int8_t snr; }; RxPacket _rx_ring[RX_RING_SIZE]; - volatile uint8_t _rx_head; - volatile uint8_t _rx_tail; + atomic_t _rx_head; + atomic_t _rx_tail; /* TX buffer + signal */ uint8_t _tx_buf[256]; @@ -171,9 +171,9 @@ private: bool _tx_thread_running; /* Packet statistics */ - volatile uint32_t _packets_recv; - volatile uint32_t _packets_sent; - volatile uint32_t _packets_recv_errors; + atomic_t _packets_recv; + atomic_t _packets_sent; + atomic_t _packets_recv_errors; }; } /* namespace mesh */ diff --git a/zephcore/adapters/radio/SX126xRadio.cpp b/zephcore/adapters/radio/SX126xRadio.cpp index 762ed76..3e2429e 100644 --- a/zephcore/adapters/radio/SX126xRadio.cpp +++ b/zephcore/adapters/radio/SX126xRadio.cpp @@ -46,10 +46,10 @@ void SX126xRadio::hwStartReceive() int ret = lora_recv_async(_dev, rxCallbackStatic, this); if (ret < 0) { LOG_ERR("lora_recv_async failed: %d", ret); - _in_recv_mode = false; + atomic_set(&_in_recv_mode, 0); return; } - _in_recv_mode = true; + atomic_set(&_in_recv_mode, 1); /* RX boost: set once via setRxBoost(), preserved by SX126x * hardware retention registers (DS §9.6). */ diff --git a/zephcore/adapters/radio/lr11xx/lr11xx_hal_zephyr.c b/zephcore/adapters/radio/lr11xx/lr11xx_hal_zephyr.c index 7440d13..2faab85 100644 --- a/zephcore/adapters/radio/lr11xx/lr11xx_hal_zephyr.c +++ b/zephcore/adapters/radio/lr11xx/lr11xx_hal_zephyr.c @@ -29,6 +29,10 @@ static struct lr11xx_hal_context *current_ctx = NULL; */ static struct k_work dio1_work; +/* BUSY pin interrupt — wakes wait_on_busy() via semaphore instead of polling */ +static struct gpio_callback busy_gpio_cb; +static K_SEM_DEFINE(busy_sem, 0, 1); + static void dio1_work_handler(struct k_work *work) { ARG_UNUSED(work); @@ -37,28 +41,53 @@ static void dio1_work_handler(struct k_work *work) } } +static void busy_isr_callback(const struct device *dev, struct gpio_callback *cb, + uint32_t pins) +{ + ARG_UNUSED(dev); + ARG_UNUSED(cb); + ARG_UNUSED(pins); + k_sem_give(&busy_sem); +} + /* Track the last SPI opcode for debugging BUSY stuck */ static uint16_t last_opcode; static int64_t last_cmd_time; /** - * @brief Wait until BUSY pin goes low or timeout + * @brief Wait until BUSY pin goes low or timeout. + * + * Uses GPIO interrupt + semaphore instead of polling. The CPU sleeps + * while waiting, saving power during long BUSY periods (reset, sleep + * wake, firmware commands). For sub-microsecond waits the fast-path + * check returns immediately without touching the interrupt at all. */ static lr11xx_hal_status_t wait_on_busy(struct lr11xx_hal_context *ctx) { - int64_t start = k_uptime_get(); - int loops = 0; + /* Fast path: already ready */ + if (!gpio_pin_get_dt(&ctx->busy)) { + return LR11XX_HAL_STATUS_OK; + } - while (gpio_pin_get_dt(&ctx->busy)) { - if ((k_uptime_get() - start) > LR11XX_BUSY_TIMEOUT_MS) { - LOG_ERR("BUSY timeout! last_op=0x%04x sent_at=%lld (%lld ms ago) DIO1=%d", - last_opcode, last_cmd_time, - k_uptime_get() - last_cmd_time, - gpio_pin_get_dt(&ctx->dio1)); - return LR11XX_HAL_STATUS_ERROR; - } - k_usleep(100); /* 100us — yields CPU so other threads can run */ - loops++; + k_sem_reset(&busy_sem); + gpio_pin_interrupt_configure_dt(&ctx->busy, GPIO_INT_EDGE_TO_INACTIVE); + + /* Re-check after enabling interrupt to close the race window where + * BUSY dropped between our first check and the interrupt enable. */ + if (!gpio_pin_get_dt(&ctx->busy)) { + gpio_pin_interrupt_configure_dt(&ctx->busy, GPIO_INT_DISABLE); + return LR11XX_HAL_STATUS_OK; + } + + int ret = k_sem_take(&busy_sem, K_MSEC(LR11XX_BUSY_TIMEOUT_MS)); + gpio_pin_interrupt_configure_dt(&ctx->busy, GPIO_INT_DISABLE); + + if (ret == -EAGAIN) { + LOG_ERR("BUSY timeout! last_op=0x%04x sent_at=%lld (%lld ms ago) DIO1=%d", + last_opcode, last_cmd_time, + k_uptime_get() - last_cmd_time, + gpio_pin_get_dt(&ctx->dio1)); + return LR11XX_HAL_STATUS_ERROR; } return LR11XX_HAL_STATUS_OK; @@ -128,13 +157,21 @@ int lr11xx_hal_init(struct lr11xx_hal_context *ctx) return ret; } - /* Configure BUSY as input */ + /* Configure BUSY as input with interrupt support */ ret = gpio_pin_configure_dt(&ctx->busy, GPIO_INPUT); if (ret < 0) { LOG_ERR("Failed to configure BUSY: %d", ret); return ret; } + /* Set up BUSY interrupt callback (interrupt enabled on-demand by wait_on_busy) */ + gpio_init_callback(&busy_gpio_cb, busy_isr_callback, BIT(ctx->busy.pin)); + ret = gpio_add_callback(ctx->busy.port, &busy_gpio_cb); + if (ret < 0) { + LOG_ERR("Failed to add BUSY callback: %d", ret); + return ret; + } + /* Configure DIO1 as input with interrupt */ ret = gpio_pin_configure_dt(&ctx->dio1, GPIO_INPUT); if (ret < 0) {