LoRaRadioBase volatile → atomic_t

LR1110 BUSY pin → interrupt-driven
This commit is contained in:
liquidraver
2026-03-04 22:36:34 +01:00
parent d949ca4f6e
commit 30379ee48a
5 changed files with 103 additions and 64 deletions
+2 -2
View File
@@ -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. */
+33 -31
View File
@@ -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);
}
}
+15 -15
View File
@@ -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 */
+2 -2
View File
@@ -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). */
@@ -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) {