Initial import
This commit is contained in:
11
drivers/lora/lr11xx/CMakeLists.txt
Normal file
11
drivers/lora/lr11xx/CMakeLists.txt
Normal file
@@ -0,0 +1,11 @@
|
||||
# Copyright (c) 2024 loramodem contributors
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
zephyr_library()
|
||||
|
||||
zephyr_library_sources_ifdef(CONFIG_LORA_LR11XX
|
||||
lr11xx.c
|
||||
lr11xx_hal.c
|
||||
)
|
||||
|
||||
zephyr_library_include_directories(.)
|
||||
16
drivers/lora/lr11xx/Kconfig
Normal file
16
drivers/lora/lr11xx/Kconfig
Normal file
@@ -0,0 +1,16 @@
|
||||
# Copyright (c) 2024 loramodem contributors
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
config LORA_LR11XX
|
||||
bool "Semtech LR11xx LoRa driver"
|
||||
default y
|
||||
depends on DT_HAS_SEMTECH_LR11XX_ENABLED
|
||||
depends on SPI
|
||||
select LORA
|
||||
help
|
||||
Enable driver for Semtech LR1110/LR1120 multi-protocol radio
|
||||
transceivers. This driver implements Zephyr's LoRa subsystem API
|
||||
for LoRa packet mode operation only.
|
||||
|
||||
The LR11xx uses a 2-byte SPI opcode protocol. BUSY pin must be
|
||||
polled between transactions.
|
||||
479
drivers/lora/lr11xx/lr11xx.c
Normal file
479
drivers/lora/lr11xx/lr11xx.c
Normal file
@@ -0,0 +1,479 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Semtech LR11xx LoRa driver for Zephyr RTOS.
|
||||
*
|
||||
* Implements Zephyr's lora_driver_api for LoRa packet mode operation.
|
||||
* Supports: LR1110, LR1120.
|
||||
*
|
||||
* The LR11xx SPI protocol uses 2-byte opcodes and requires BUSY polling
|
||||
* between transactions. See lr11xx.h for opcode definitions.
|
||||
*
|
||||
* Only LoRa packet mode is implemented. LoRaWAN and GNSS functions
|
||||
* of the LR11xx are not used by the KISS modem application.
|
||||
*/
|
||||
|
||||
#define DT_DRV_COMPAT semtech_lr11xx
|
||||
|
||||
#include "lr11xx.h"
|
||||
|
||||
#include <zephyr/drivers/lora.h>
|
||||
#include <zephyr/drivers/gpio.h>
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/logging/log.h>
|
||||
|
||||
LOG_MODULE_REGISTER(lr11xx, CONFIG_LORA_LOG_LEVEL);
|
||||
|
||||
/* ── Helpers to map Zephyr lora_modem_config to LR11xx codes ─────────────── */
|
||||
|
||||
static uint8_t zephyr_sf_to_lr11xx(enum lora_datarate dr)
|
||||
{
|
||||
switch (dr) {
|
||||
case SF_5: return LR11XX_LORA_SF5;
|
||||
case SF_6: return LR11XX_LORA_SF6;
|
||||
case SF_7: return LR11XX_LORA_SF7;
|
||||
case SF_8: return LR11XX_LORA_SF8;
|
||||
case SF_9: return LR11XX_LORA_SF9;
|
||||
case SF_10: return LR11XX_LORA_SF10;
|
||||
case SF_11: return LR11XX_LORA_SF11;
|
||||
case SF_12: /* fall through */
|
||||
default: return LR11XX_LORA_SF12;
|
||||
}
|
||||
}
|
||||
|
||||
static uint8_t zephyr_bw_to_lr11xx(enum lora_signal_bandwidth bw)
|
||||
{
|
||||
switch (bw) {
|
||||
case BW_500_KHZ: return LR11XX_LORA_BW_500;
|
||||
case BW_250_KHZ: return LR11XX_LORA_BW_250;
|
||||
case BW_125_KHZ: /* fall through */
|
||||
default: return LR11XX_LORA_BW_125;
|
||||
}
|
||||
}
|
||||
|
||||
static uint8_t zephyr_cr_to_lr11xx(enum lora_coding_rate cr)
|
||||
{
|
||||
switch (cr) {
|
||||
case CR_4_5: return LR11XX_LORA_CR_4_5;
|
||||
case CR_4_6: return LR11XX_LORA_CR_4_6;
|
||||
case CR_4_7: return LR11XX_LORA_CR_4_7;
|
||||
case CR_4_8: /* fall through */
|
||||
default: return LR11XX_LORA_CR_4_8;
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Radio configuration ─────────────────────────────────────────────────── */
|
||||
|
||||
static int lr11xx_configure(const struct device *dev,
|
||||
struct lora_modem_config *cfg)
|
||||
{
|
||||
int ret;
|
||||
uint8_t params[8];
|
||||
|
||||
/* Enter standby (RC oscillator) */
|
||||
params[0] = LR11XX_STANDBY_RC;
|
||||
ret = lr11xx_hal_write_cmd(dev, LR11XX_CMD_SET_STANDBY, params, 1);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Set packet type to LoRa */
|
||||
params[0] = LR11XX_PKT_TYPE_LORA;
|
||||
ret = lr11xx_hal_write_cmd(dev, LR11XX_CMD_SET_PACKET_TYPE, params, 1);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Set RF frequency */
|
||||
uint32_t freq = (uint32_t)cfg->frequency;
|
||||
|
||||
params[0] = (freq >> 24) & 0xFF;
|
||||
params[1] = (freq >> 16) & 0xFF;
|
||||
params[2] = (freq >> 8) & 0xFF;
|
||||
params[3] = (freq >> 0) & 0xFF;
|
||||
ret = lr11xx_hal_write_cmd(dev, LR11XX_CMD_SET_RF_FREQUENCY, params, 4);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Set TX power and ramp time (ramp 40 us = 0x04) */
|
||||
params[0] = (uint8_t)cfg->tx_power;
|
||||
params[1] = 0x04; /* Ramp time */
|
||||
ret = lr11xx_hal_write_cmd(dev, LR11XX_CMD_SET_TX_PARAMS, params, 2);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Set modulation parameters (LoRa):
|
||||
* [0] SF, [1] BW, [2] CR, [3] LDRO (low data rate optimize)
|
||||
*/
|
||||
params[0] = zephyr_sf_to_lr11xx(cfg->datarate);
|
||||
params[1] = zephyr_bw_to_lr11xx(cfg->bandwidth);
|
||||
params[2] = zephyr_cr_to_lr11xx(cfg->coding_rate);
|
||||
params[3] = 0x00; /* LDRO: auto would require SF/BW calculation */
|
||||
ret = lr11xx_hal_write_cmd(dev, LR11XX_CMD_SET_MODULATION_PARAMS,
|
||||
params, 4);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Set packet parameters (LoRa):
|
||||
* [0:1] preamble length, [2] header type (0=explicit),
|
||||
* [3] payload length, [4] CRC mode (1=on),
|
||||
* [5] IQ inversion (0=standard)
|
||||
*/
|
||||
uint16_t preamble = (uint16_t)cfg->preamble_len;
|
||||
|
||||
params[0] = (preamble >> 8) & 0xFF;
|
||||
params[1] = (preamble >> 0) & 0xFF;
|
||||
params[2] = 0x00; /* explicit header */
|
||||
params[3] = 0xFF; /* max payload (will be overridden per-packet) */
|
||||
params[4] = 0x01; /* CRC on */
|
||||
params[5] = 0x00; /* standard IQ */
|
||||
ret = lr11xx_hal_write_cmd(dev, LR11XX_CMD_SET_PACKET_PARAMS,
|
||||
params, 6);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Set buffer base addresses (TX=0, RX=0) */
|
||||
params[0] = 0x00;
|
||||
params[1] = 0x00;
|
||||
ret = lr11xx_hal_write_cmd(dev, LR11XX_CMD_SET_BUFFER_BASE_ADDR,
|
||||
params, 2);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Configure DIO IRQ routing to EVENT/DIO9 */
|
||||
uint32_t irq_mask = LR11XX_DIO9_MASK;
|
||||
|
||||
params[0] = (irq_mask >> 24) & 0xFF;
|
||||
params[1] = (irq_mask >> 16) & 0xFF;
|
||||
params[2] = (irq_mask >> 8) & 0xFF;
|
||||
params[3] = (irq_mask >> 0) & 0xFF;
|
||||
/* DIO9 enable mask = same as IRQ mask */
|
||||
params[4] = params[0];
|
||||
params[5] = params[1];
|
||||
params[6] = params[2];
|
||||
params[7] = params[3];
|
||||
ret = lr11xx_hal_write_cmd(dev, LR11XX_CMD_SET_DIO_IRQ_PARAMS,
|
||||
params, 8);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* ── Send ────────────────────────────────────────────────────────────────── */
|
||||
|
||||
static int lr11xx_send(const struct device *dev,
|
||||
uint8_t *data, uint32_t data_len)
|
||||
{
|
||||
struct lr11xx_data *drv = dev->data;
|
||||
int ret;
|
||||
uint8_t params[3];
|
||||
|
||||
/* Clear pending IRQ flags */
|
||||
uint32_t clr = LR11XX_IRQ_ALL;
|
||||
|
||||
params[0] = (clr >> 24) & 0xFF;
|
||||
params[1] = (clr >> 16) & 0xFF;
|
||||
params[2] = (clr >> 8) & 0xFF;
|
||||
params[3] = (clr >> 0) & 0xFF;
|
||||
lr11xx_hal_write_cmd(dev, LR11XX_CMD_CLEAR_IRQ_STATUS, params, 4);
|
||||
|
||||
/* Write payload to TX buffer at offset 0 */
|
||||
uint8_t *tx_buf = k_malloc(data_len + 1);
|
||||
|
||||
if (!tx_buf) {
|
||||
return -ENOMEM;
|
||||
}
|
||||
tx_buf[0] = 0x00; /* buffer offset */
|
||||
memcpy(&tx_buf[1], data, data_len);
|
||||
|
||||
/* Use opcode 0x010A (WriteBuffer8) to write payload */
|
||||
ret = lr11xx_hal_write_cmd(dev, 0x010Au, tx_buf, data_len + 1);
|
||||
k_free(tx_buf);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Update packet params with actual payload length */
|
||||
uint8_t pkt_params[6];
|
||||
|
||||
pkt_params[0] = 0x00;
|
||||
pkt_params[1] = 0x08; /* preamble length high byte */
|
||||
pkt_params[2] = 0x00; /* explicit header */
|
||||
pkt_params[3] = (uint8_t)data_len;
|
||||
pkt_params[4] = 0x01; /* CRC on */
|
||||
pkt_params[5] = 0x00; /* standard IQ */
|
||||
ret = lr11xx_hal_write_cmd(dev, LR11XX_CMD_SET_PACKET_PARAMS,
|
||||
pkt_params, 6);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Reset the event semaphore and trigger TX */
|
||||
k_sem_reset(&drv->event_sem);
|
||||
|
||||
params[0] = 0x00; /* TX timeout = 0 (no timeout) */
|
||||
params[1] = 0x00;
|
||||
params[2] = 0x00;
|
||||
ret = lr11xx_hal_write_cmd(dev, LR11XX_CMD_SET_TX, params, 3);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Wait for TX_DONE IRQ */
|
||||
ret = k_sem_take(&drv->event_sem, K_MSEC(5000));
|
||||
if (ret < 0) {
|
||||
LOG_WRN("TX timeout");
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
/* Read and check IRQ status */
|
||||
ret = lr11xx_hal_write_cmd(dev, LR11XX_CMD_GET_IRQ_STATUS, NULL, 0);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint8_t irq_buf[4];
|
||||
|
||||
ret = lr11xx_hal_read_resp(dev, irq_buf, sizeof(irq_buf));
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint32_t irq = ((uint32_t)irq_buf[0] << 24) |
|
||||
((uint32_t)irq_buf[1] << 16) |
|
||||
((uint32_t)irq_buf[2] << 8) |
|
||||
((uint32_t)irq_buf[3]);
|
||||
|
||||
if (!(irq & LR11XX_IRQ_TX_DONE)) {
|
||||
LOG_WRN("TX: unexpected IRQ 0x%08x", irq);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/* Clear IRQ flags */
|
||||
params[0] = (irq >> 24) & 0xFF;
|
||||
params[1] = (irq >> 16) & 0xFF;
|
||||
params[2] = (irq >> 8) & 0xFF;
|
||||
params[3] = (irq >> 0) & 0xFF;
|
||||
lr11xx_hal_write_cmd(dev, LR11XX_CMD_CLEAR_IRQ_STATUS, params, 4);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ── Receive ─────────────────────────────────────────────────────────────── */
|
||||
|
||||
static int lr11xx_recv(const struct device *dev,
|
||||
uint8_t *data, uint8_t size,
|
||||
k_timeout_t timeout,
|
||||
int16_t *rssi, int8_t *snr)
|
||||
{
|
||||
struct lr11xx_data *drv = dev->data;
|
||||
int ret;
|
||||
uint8_t params[3];
|
||||
|
||||
/* Clear pending IRQs */
|
||||
uint32_t clr = LR11XX_IRQ_ALL;
|
||||
|
||||
params[0] = (clr >> 24) & 0xFF;
|
||||
params[1] = (clr >> 16) & 0xFF;
|
||||
params[2] = (clr >> 8) & 0xFF;
|
||||
params[3] = (clr >> 0) & 0xFF;
|
||||
lr11xx_hal_write_cmd(dev, LR11XX_CMD_CLEAR_IRQ_STATUS, params, 4);
|
||||
|
||||
/* Enter continuous RX */
|
||||
k_sem_reset(&drv->event_sem);
|
||||
|
||||
params[0] = 0xFF; /* RX timeout: 0xFFFFFF = single with preamble timeout */
|
||||
params[1] = 0xFF;
|
||||
params[2] = 0xFF;
|
||||
ret = lr11xx_hal_write_cmd(dev, LR11XX_CMD_SET_RX, params, 3);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Wait for RX_DONE (or timeout) */
|
||||
ret = k_sem_take(&drv->event_sem, timeout);
|
||||
if (ret < 0) {
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
/* Read IRQ status */
|
||||
ret = lr11xx_hal_write_cmd(dev, LR11XX_CMD_GET_IRQ_STATUS, NULL, 0);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint8_t irq_buf[4];
|
||||
|
||||
ret = lr11xx_hal_read_resp(dev, irq_buf, sizeof(irq_buf));
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint32_t irq = ((uint32_t)irq_buf[0] << 24) |
|
||||
((uint32_t)irq_buf[1] << 16) |
|
||||
((uint32_t)irq_buf[2] << 8) |
|
||||
((uint32_t)irq_buf[3]);
|
||||
|
||||
/* Clear IRQ flags */
|
||||
params[0] = (irq >> 24) & 0xFF;
|
||||
params[1] = (irq >> 16) & 0xFF;
|
||||
params[2] = (irq >> 8) & 0xFF;
|
||||
params[3] = (irq >> 0) & 0xFF;
|
||||
lr11xx_hal_write_cmd(dev, LR11XX_CMD_CLEAR_IRQ_STATUS, params, 4);
|
||||
|
||||
if (irq & (LR11XX_IRQ_TIMEOUT | LR11XX_IRQ_CRC_ERROR)) {
|
||||
LOG_DBG("RX error IRQ=0x%08x", irq);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
if (!(irq & LR11XX_IRQ_RX_DONE)) {
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/* Get RX buffer status: payload_len, rx_start_ptr */
|
||||
ret = lr11xx_hal_write_cmd(dev, LR11XX_CMD_GET_RX_BUFFER_STATUS, NULL, 0);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint8_t rxbuf_status[2];
|
||||
|
||||
ret = lr11xx_hal_read_resp(dev, rxbuf_status, sizeof(rxbuf_status));
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint8_t payload_len = rxbuf_status[0];
|
||||
uint8_t offset = rxbuf_status[1];
|
||||
|
||||
if (payload_len > size) {
|
||||
payload_len = size;
|
||||
}
|
||||
|
||||
/* Read payload from RX buffer */
|
||||
uint8_t read_params[2] = { offset, payload_len };
|
||||
|
||||
ret = lr11xx_hal_write_cmd(dev, LR11XX_CMD_READ_BUFFER,
|
||||
read_params, sizeof(read_params));
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = lr11xx_hal_read_resp(dev, data, payload_len);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Get packet status (RSSI, SNR) */
|
||||
ret = lr11xx_hal_write_cmd(dev, LR11XX_CMD_GET_PACKET_STATUS, NULL, 0);
|
||||
if (ret < 0) {
|
||||
goto skip_status;
|
||||
}
|
||||
|
||||
uint8_t pkt_status[3];
|
||||
|
||||
ret = lr11xx_hal_read_resp(dev, pkt_status, sizeof(pkt_status));
|
||||
if (ret == 0) {
|
||||
/* rssi_pkt = -pkt_status[0] / 2 dBm */
|
||||
if (rssi) {
|
||||
*rssi = -(int16_t)pkt_status[0] / 2;
|
||||
}
|
||||
/* snr_pkt = pkt_status[1] / 4 dB (signed) */
|
||||
if (snr) {
|
||||
*snr = (int8_t)pkt_status[1] / 4;
|
||||
}
|
||||
}
|
||||
|
||||
skip_status:
|
||||
return (int)payload_len;
|
||||
}
|
||||
|
||||
/* ── Initialization ──────────────────────────────────────────────────────── */
|
||||
|
||||
static int lr11xx_init(const struct device *dev)
|
||||
{
|
||||
const struct lr11xx_config *cfg = dev->config;
|
||||
struct lr11xx_data *drv = dev->data;
|
||||
int ret;
|
||||
|
||||
if (!spi_is_ready_dt(&cfg->spi)) {
|
||||
LOG_ERR("SPI bus not ready");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
ret = gpio_pin_configure_dt(&cfg->reset, GPIO_OUTPUT_ACTIVE);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = gpio_pin_configure_dt(&cfg->busy, GPIO_INPUT);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = gpio_pin_configure_dt(&cfg->event, GPIO_INPUT);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
k_sem_init(&drv->event_sem, 0, 1);
|
||||
|
||||
/* Set up interrupt on EVENT/DIO9 pin */
|
||||
ret = gpio_pin_interrupt_configure_dt(&cfg->event,
|
||||
GPIO_INT_EDGE_TO_ACTIVE);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
gpio_init_callback(&drv->event_cb, lr11xx_hal_event_handler,
|
||||
BIT(cfg->event.pin));
|
||||
ret = gpio_add_callback(cfg->event.port, &drv->event_cb);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Hardware reset */
|
||||
ret = lr11xx_hal_reset(dev);
|
||||
if (ret < 0) {
|
||||
LOG_ERR("Reset failed: %d", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
LOG_INF("LR11xx initialized");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ── Driver API ──────────────────────────────────────────────────────────── */
|
||||
|
||||
static const struct lora_driver_api lr11xx_api = {
|
||||
.config = lr11xx_configure,
|
||||
.send = lr11xx_send,
|
||||
.recv = lr11xx_recv,
|
||||
};
|
||||
|
||||
/* ── Device instantiation macro ─────────────────────────────────────────── */
|
||||
|
||||
#define LR11XX_DEFINE(n) \
|
||||
static struct lr11xx_data lr11xx_data_##n; \
|
||||
\
|
||||
static const struct lr11xx_config lr11xx_config_##n = { \
|
||||
.spi = SPI_DT_SPEC_INST_GET(n, \
|
||||
SPI_WORD_SET(8) | SPI_TRANSFER_MSB, 0), \
|
||||
.reset = GPIO_DT_SPEC_INST_GET(n, reset_gpios), \
|
||||
.busy = GPIO_DT_SPEC_INST_GET(n, busy_gpios), \
|
||||
.event = GPIO_DT_SPEC_INST_GET(n, event_gpios), \
|
||||
}; \
|
||||
\
|
||||
DEVICE_DT_INST_DEFINE(n, \
|
||||
lr11xx_init, NULL, \
|
||||
&lr11xx_data_##n, &lr11xx_config_##n, \
|
||||
POST_KERNEL, CONFIG_LORA_INIT_PRIORITY, \
|
||||
&lr11xx_api);
|
||||
|
||||
DT_INST_FOREACH_STATUS_OKAY(LR11XX_DEFINE)
|
||||
107
drivers/lora/lr11xx/lr11xx.h
Normal file
107
drivers/lora/lr11xx/lr11xx.h
Normal file
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Semtech LR11xx private driver header.
|
||||
* Defines SPI opcodes, register constants, and internal types.
|
||||
*/
|
||||
|
||||
#ifndef LR11XX_H
|
||||
#define LR11XX_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <zephyr/drivers/spi.h>
|
||||
#include <zephyr/drivers/gpio.h>
|
||||
#include <zephyr/kernel.h>
|
||||
|
||||
/* ── SPI opcode definitions ──────────────────────────────────────────────── */
|
||||
|
||||
/* System commands */
|
||||
#define LR11XX_CMD_RESET_STATS 0x0200u
|
||||
#define LR11XX_CMD_GET_STATUS 0x0100u
|
||||
#define LR11XX_CMD_SET_STANDBY 0x011Bu /* Note: opcode varies by fw */
|
||||
#define LR11XX_CMD_SET_PACKET_TYPE 0x010Au
|
||||
#define LR11XX_CMD_SET_RF_FREQUENCY 0x010Eu
|
||||
#define LR11XX_CMD_SET_MODULATION_PARAMS 0x0115u
|
||||
#define LR11XX_CMD_SET_PACKET_PARAMS 0x0116u
|
||||
#define LR11XX_CMD_SET_TX_PARAMS 0x0110u
|
||||
#define LR11XX_CMD_SET_DIO_IRQ_PARAMS 0x0113u
|
||||
#define LR11XX_CMD_CLEAR_IRQ_STATUS 0x0114u
|
||||
#define LR11XX_CMD_GET_IRQ_STATUS 0x0112u
|
||||
#define LR11XX_CMD_SET_RX 0x0107u
|
||||
#define LR11XX_CMD_SET_TX 0x0108u
|
||||
#define LR11XX_CMD_GET_RX_BUFFER_STATUS 0x011Cu
|
||||
#define LR11XX_CMD_READ_BUFFER 0x011Eu
|
||||
#define LR11XX_CMD_GET_PACKET_STATUS 0x011Du
|
||||
#define LR11XX_CMD_SET_BUFFER_BASE_ADDR 0x010Fu
|
||||
|
||||
/* Packet type: LoRa = 0x01 */
|
||||
#define LR11XX_PKT_TYPE_LORA 0x01u
|
||||
|
||||
/* Standby mode: RC oscillator = 0x00, XOSC = 0x01 */
|
||||
#define LR11XX_STANDBY_RC 0x00u
|
||||
#define LR11XX_STANDBY_XOSC 0x01u
|
||||
|
||||
/* IRQ bitmasks */
|
||||
#define LR11XX_IRQ_TX_DONE BIT(2)
|
||||
#define LR11XX_IRQ_RX_DONE BIT(3)
|
||||
#define LR11XX_IRQ_TIMEOUT BIT(8)
|
||||
#define LR11XX_IRQ_CRC_ERROR BIT(10)
|
||||
#define LR11XX_IRQ_ALL 0xFFFFFFFFu
|
||||
|
||||
/* DIO mask for routing IRQs to EVENT/DIO9 */
|
||||
#define LR11XX_DIO9_MASK (LR11XX_IRQ_TX_DONE | LR11XX_IRQ_RX_DONE | \
|
||||
LR11XX_IRQ_TIMEOUT | LR11XX_IRQ_CRC_ERROR)
|
||||
|
||||
/* LoRa spreading factor codes */
|
||||
#define LR11XX_LORA_SF5 0x05u
|
||||
#define LR11XX_LORA_SF6 0x06u
|
||||
#define LR11XX_LORA_SF7 0x07u
|
||||
#define LR11XX_LORA_SF8 0x08u
|
||||
#define LR11XX_LORA_SF9 0x09u
|
||||
#define LR11XX_LORA_SF10 0x0Au
|
||||
#define LR11XX_LORA_SF11 0x0Bu
|
||||
#define LR11XX_LORA_SF12 0x0Cu
|
||||
|
||||
/* LoRa bandwidth codes */
|
||||
#define LR11XX_LORA_BW_125 0x04u
|
||||
#define LR11XX_LORA_BW_250 0x05u
|
||||
#define LR11XX_LORA_BW_500 0x06u
|
||||
|
||||
/* LoRa coding rate codes */
|
||||
#define LR11XX_LORA_CR_4_5 0x01u
|
||||
#define LR11XX_LORA_CR_4_6 0x02u
|
||||
#define LR11XX_LORA_CR_4_7 0x03u
|
||||
#define LR11XX_LORA_CR_4_8 0x04u
|
||||
|
||||
/* RX timeout: 0 = continuous, 0xFFFFFF = single (timeout on preamble) */
|
||||
#define LR11XX_RX_CONTINUOUS 0x000000u
|
||||
|
||||
/* ── Driver config/data structures ───────────────────────────────────────── */
|
||||
|
||||
struct lr11xx_config {
|
||||
struct spi_dt_spec spi;
|
||||
struct gpio_dt_spec reset;
|
||||
struct gpio_dt_spec busy;
|
||||
struct gpio_dt_spec event;
|
||||
};
|
||||
|
||||
struct lr11xx_data {
|
||||
struct gpio_callback event_cb;
|
||||
struct k_sem event_sem;
|
||||
uint32_t last_irq;
|
||||
};
|
||||
|
||||
/* ── HAL function prototypes (implemented in lr11xx_hal.c) ───────────────── */
|
||||
|
||||
int lr11xx_hal_reset(const struct device *dev);
|
||||
int lr11xx_hal_wait_busy(const struct device *dev, k_timeout_t timeout);
|
||||
int lr11xx_hal_write_cmd(const struct device *dev,
|
||||
uint16_t opcode,
|
||||
const uint8_t *params, size_t params_len);
|
||||
int lr11xx_hal_read_resp(const struct device *dev,
|
||||
uint8_t *buf, size_t len);
|
||||
void lr11xx_hal_event_handler(const struct device *port,
|
||||
struct gpio_callback *cb, uint32_t pins);
|
||||
|
||||
#endif /* LR11XX_H */
|
||||
129
drivers/lora/lr11xx/lr11xx_hal.c
Normal file
129
drivers/lora/lr11xx/lr11xx_hal.c
Normal file
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* LR11xx Zephyr HAL — SPI and GPIO low-level operations.
|
||||
*
|
||||
* The LR11xx SPI protocol:
|
||||
* WRITE: Assert NSS → send 2-byte opcode + param bytes → deassert NSS
|
||||
* READ: Assert NSS → send 2-byte opcode → deassert NSS
|
||||
* Wait for BUSY low
|
||||
* Assert NSS → read STAT1 + STAT2 + N response bytes → deassert NSS
|
||||
*
|
||||
* All transactions must wait for BUSY=low before asserting NSS.
|
||||
*/
|
||||
|
||||
#include "lr11xx.h"
|
||||
|
||||
#include <zephyr/drivers/gpio.h>
|
||||
#include <zephyr/drivers/spi.h>
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/logging/log.h>
|
||||
|
||||
LOG_MODULE_DECLARE(lr11xx, LOG_LEVEL_DBG);
|
||||
|
||||
#define BUSY_POLL_INTERVAL_US 100
|
||||
#define BUSY_TIMEOUT_MS 1000
|
||||
|
||||
int lr11xx_hal_reset(const struct device *dev)
|
||||
{
|
||||
const struct lr11xx_config *cfg = dev->config;
|
||||
int ret;
|
||||
|
||||
ret = gpio_pin_set_dt(&cfg->reset, 1);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
k_sleep(K_USEC(200));
|
||||
ret = gpio_pin_set_dt(&cfg->reset, 0);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
k_sleep(K_MSEC(10));
|
||||
|
||||
return lr11xx_hal_wait_busy(dev, K_MSEC(BUSY_TIMEOUT_MS));
|
||||
}
|
||||
|
||||
int lr11xx_hal_wait_busy(const struct device *dev, k_timeout_t timeout)
|
||||
{
|
||||
const struct lr11xx_config *cfg = dev->config;
|
||||
uint32_t start = k_uptime_get_32();
|
||||
uint32_t timeout_ms = k_ticks_to_ms_floor32(timeout.ticks);
|
||||
|
||||
while (gpio_pin_get_dt(&cfg->busy)) {
|
||||
if (timeout_ms > 0 &&
|
||||
(k_uptime_get_32() - start) > timeout_ms) {
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
k_busy_wait(BUSY_POLL_INTERVAL_US);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int lr11xx_hal_write_cmd(const struct device *dev,
|
||||
uint16_t opcode,
|
||||
const uint8_t *params, size_t params_len)
|
||||
{
|
||||
const struct lr11xx_config *cfg = dev->config;
|
||||
int ret;
|
||||
|
||||
ret = lr11xx_hal_wait_busy(dev, K_MSEC(BUSY_TIMEOUT_MS));
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint8_t opcode_buf[2] = {
|
||||
(uint8_t)(opcode >> 8),
|
||||
(uint8_t)(opcode & 0xFF),
|
||||
};
|
||||
|
||||
const struct spi_buf tx_bufs[] = {
|
||||
{ .buf = opcode_buf, .len = sizeof(opcode_buf) },
|
||||
{ .buf = (void *)params, .len = params_len },
|
||||
};
|
||||
const struct spi_buf_set tx = {
|
||||
.buffers = tx_bufs,
|
||||
.count = (params_len > 0) ? 2 : 1,
|
||||
};
|
||||
|
||||
return spi_write_dt(&cfg->spi, &tx);
|
||||
}
|
||||
|
||||
int lr11xx_hal_read_resp(const struct device *dev,
|
||||
uint8_t *buf, size_t len)
|
||||
{
|
||||
const struct lr11xx_config *cfg = dev->config;
|
||||
int ret;
|
||||
|
||||
ret = lr11xx_hal_wait_busy(dev, K_MSEC(BUSY_TIMEOUT_MS));
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* First two bytes are STAT1 and STAT2 — discard for now */
|
||||
uint8_t status[2];
|
||||
uint8_t dummy_opcode[2] = {0x00, 0x00};
|
||||
|
||||
const struct spi_buf tx_bufs[] = {
|
||||
{ .buf = dummy_opcode, .len = sizeof(dummy_opcode) },
|
||||
};
|
||||
struct spi_buf rx_bufs[] = {
|
||||
{ .buf = status, .len = sizeof(status) },
|
||||
{ .buf = buf, .len = len },
|
||||
};
|
||||
const struct spi_buf_set tx = { .buffers = tx_bufs, .count = 1 };
|
||||
const struct spi_buf_set rx = { .buffers = rx_bufs, .count = (len > 0) ? 2 : 1 };
|
||||
|
||||
return spi_transceive_dt(&cfg->spi, &tx, &rx);
|
||||
}
|
||||
|
||||
void lr11xx_hal_event_handler(const struct device *port,
|
||||
struct gpio_callback *cb, uint32_t pins)
|
||||
{
|
||||
ARG_UNUSED(port);
|
||||
ARG_UNUSED(pins);
|
||||
|
||||
struct lr11xx_data *data =
|
||||
CONTAINER_OF(cb, struct lr11xx_data, event_cb);
|
||||
|
||||
k_sem_give(&data->event_sem);
|
||||
}
|
||||
Reference in New Issue
Block a user