mirror of
https://github.com/liquidraver/ZephCore.git
synced 2026-09-01 21:08:19 +00:00
LoRaRadioBase volatile → atomic_t
LR1110 BUSY pin → interrupt-driven
This commit is contained in:
@@ -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. */
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user