Initial import

This commit is contained in:
2026-03-25 18:14:45 +01:00
commit d074cd2e43
99 changed files with 3781 additions and 0 deletions

59
.github/copilot-instructions.md vendored Normal file
View File

@@ -0,0 +1,59 @@
# Copilot instructions for loramodem
Repository at a glance
- Project: LoRa KISS modem (Zephyr RTOS)
- Purpose: Bridge a KISS serial interface to a LoRa radio (two threads: serial → LoRa, LoRa → serial).
- Key components: app/ (Zephyr application), drivers/lora/lr11xx (Semtech LR11xx driver), boards/ (board overlays).
Quick commands
- Build for a board (recommended):
west build -b <board> app
Example: west build -b heltec_wifi_lora32_v3 app
- Flash the built image:
west flash
- Open Zephyr menuconfig:
west build -b <board> app -t menuconfig
- CMake alternative (repo root):
cmake -S app -B build && cmake --build build
Testing & lint
- Testing strategy (from CLAUDE.md): unit tests run on host (arm64/x86) with a mocked HAL; integration tests run on target hardware. No repo-level test runner or lint targets are present by default — look for a tests/ or scripts/ directory if added.
High-level architecture (big picture)
- app/ contains the Zephyr application (app/src/*.c). main.c starts two cooperative threads:
- kiss_rx_thread: reads bytes from the KISS UART, decodes frames (kiss.c), handles SetHardware commands, and calls lora_modem_send().
- lora_rx_thread: polls lora_modem_recv() with a finite timeout and writes received payloads as KISS frames to the UART.
- KISS protocol implementation: app/src/kiss.c, kiss.h — encoder/decoder and SetHardware sub-command definitions.
- Radio abstraction: app/src/lora_modem.c, lora_modem.h — stores runtime params (g_params) protected by a mutex and snapshots params at send/recv time so changes take effect on the next call.
- HW driver: drivers/lora/lr11xx implements Zephyr's lora_driver_api. Hardware-specific SPI/GPIO HAL pieces live in lr11xx_hal.c.
- Build integration: app/CMakeLists.txt registers drivers/lora/lr11xx as an extra Zephyr module (ZEPHYR_EXTRA_MODULES).
Key repository conventions and patterns
- Chosen nodes and aliases:
- The KISS UART is resolved from a chosen node: loramodem_kiss_uart (board overlay must define it).
- LoRa device is expected as DT alias lora0 (DEVICE_DT_GET(DT_ALIAS(lora0))).
- Kconfig / defaults:
- app/prj.conf contains project defaults (frequency, SF, BW, TX power, stack sizes, heap pool setting used by the app).
- Runtime radio params are read from Kconfig at init (lora_modem_init) and stored in g_params.
- Radio parameter updates:
- lora_modem_set_params() validates values and updates g_params under mutex; changes take effect when lora_modem_send() or lora_modem_recv() is next called (thread-safe snapshotting).
- KISS SetHardware mapping:
- See kiss.h for sub-commands (SET_FREQ, GET_PARAMS, etc.) and main.c for how responses are encoded and sent.
- Board overlays and confs:
- Overlays live in boards/ or board-specific .overlay files. App-level overrides can live in app.overlay or per-board .conf files.
- Memory & testing notes (from CLAUDE.md):
- Target memory budget and testing strategy are documented in CLAUDE.md (unit tests on host, integration on target).
Files to read first when exploring code
- app/src/main.c, app/src/kiss.c/h, app/src/lora_modem.c/h, drivers/lora/lr11xx/{*.c,*.h}, app/prj.conf, app/CMakeLists.txt, CLAUDE.md
AI & editor configs found
- CLAUDE.md contains project constraints and test strategy; consult it for memory and testing guidance.
- .vscode/settings.json points the CMake source directory to app/ in the original developer setup.
- .claude/settings.local.json contains allowed webfetch domains for the Claude assistant.
If you (the user) want, Copilot sessions should:
- Prefer reading the files listed above first.
- Respect chosen DT nodes (loramodem_kiss_uart, alias lora0) and Kconfig defaults in app/prj.conf.
- When suggesting code changes, keep stack/heap and RAM budgets in CLAUDE.md in mind.

2
Kconfig Normal file
View File

@@ -0,0 +1,2 @@
# Root Kconfig — required by zephyr/module.yml (cmake: .)
# The application Kconfig is sourced from app/Kconfig via CMake.

25
app/CMakeLists.txt Normal file
View File

@@ -0,0 +1,25 @@
cmake_minimum_required(VERSION 3.20.0)
# Set BOARD_ROOT and DTS_ROOT to the repo root so that boards/<vendor>/<board>/
# is searched for out-of-tree board definitions.
# Must be set BEFORE find_package(Zephyr).
list(APPEND BOARD_ROOT ${CMAKE_CURRENT_LIST_DIR}/..)
list(APPEND DTS_ROOT ${CMAKE_CURRENT_LIST_DIR}/..)
# Register the LR11xx out-of-tree driver as an extra Zephyr module.
# It is only compiled when a semtech,lr11xx DTS node is enabled.
list(APPEND ZEPHYR_EXTRA_MODULES
${CMAKE_CURRENT_LIST_DIR}/../drivers/lora/lr11xx
)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(loramodem LANGUAGES C)
target_sources(app PRIVATE
src/main.c
src/kiss.c
src/lora_modem.c
)
target_include_directories(app PRIVATE src)

71
app/Kconfig Normal file
View File

@@ -0,0 +1,71 @@
mainmenu "LoRa KISS Modem Configuration"
source "Kconfig.zephyr"
menu "KISS Interface"
config LORAMODEM_KISS_IFACE_USB
bool "Use USB CDC-ACM as KISS serial interface"
depends on USB_DEVICE_STACK || USB_DEVICE_STACK_NEXT
help
Use USB CDC-ACM virtual serial port for the KISS interface.
Requires the host to enumerate the device before framing begins.
The board overlay must define the loramodem,kiss-uart chosen node
pointing to a cdc_acm_uart instance.
config LORAMODEM_KISS_IFACE_UART
bool "Use hardware UART as KISS serial interface"
default y if !LORAMODEM_KISS_IFACE_USB
help
Use a hardware UART for the KISS interface.
The board overlay must define the loramodem,kiss-uart chosen node
pointing to the desired uart instance.
endmenu
menu "LoRa Radio Parameters"
config LORAMODEM_LORA_FREQ_HZ
int "Center frequency (Hz)"
default 915000000
help
LoRa radio center frequency in Hz.
Common values: 915000000 (US/AU), 868000000 (EU), 433000000 (Asia).
config LORAMODEM_LORA_SF
int "Spreading factor"
default 9
range 7 12
help
LoRa spreading factor. Higher values increase range and time-on-air.
config LORAMODEM_LORA_BW
int "Bandwidth (kHz)"
default 125
help
LoRa bandwidth in kHz. Valid values: 125, 250, 500.
config LORAMODEM_LORA_CR
int "Coding rate denominator"
default 5
range 5 8
help
LoRa coding rate as the denominator N of 4/N.
5 = 4/5 (lowest overhead), 8 = 4/8 (highest redundancy).
config LORAMODEM_LORA_TX_POWER_DBM
int "TX power (dBm)"
default 14
range 2 22
config LORAMODEM_LORA_PREAMBLE_LEN
int "Preamble length (symbols)"
default 8
range 6 65535
endmenu
config LORAMODEM_MAX_PACKET_SIZE
int "Maximum LoRa packet payload size (bytes)"
default 255
range 1 255

28
app/prj.conf Normal file
View File

@@ -0,0 +1,28 @@
# LoRa KISS Modem — base project configuration
# Board-specific .conf overlays extend or override these settings.
# Logging
CONFIG_LOG=y
CONFIG_PRINTK=y
# LoRa subsystem
CONFIG_LORA=y
# UART (interrupt-driven for non-blocking byte-by-byte processing)
CONFIG_SERIAL=y
CONFIG_UART_INTERRUPT_DRIVEN=y
# Heap for KISS frame buffers and dynamic allocations
CONFIG_HEAP_MEM_POOL_SIZE=4096
# Zephyr kernel threads
CONFIG_MAIN_STACK_SIZE=2048
# Default radio parameters (US 915 MHz, SF9, BW125, CR 4/5)
CONFIG_LORAMODEM_LORA_FREQ_HZ=915000000
CONFIG_LORAMODEM_LORA_SF=9
CONFIG_LORAMODEM_LORA_BW=125
CONFIG_LORAMODEM_LORA_CR=5
CONFIG_LORAMODEM_LORA_TX_POWER_DBM=14
CONFIG_LORAMODEM_LORA_PREAMBLE_LEN=8
CONFIG_LORAMODEM_MAX_PACKET_SIZE=255

109
app/src/kiss.c Normal file
View File

@@ -0,0 +1,109 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* KISS protocol encoder and decoder implementation.
*/
#include "kiss.h"
#include <errno.h>
#include <string.h>
int kiss_encode_cmd(uint8_t cmd, const uint8_t *data, size_t data_len,
uint8_t *out_buf, size_t out_size)
{
size_t out_idx = 0;
/* Worst-case output: FEND + CMD + (2 * data_len) + FEND */
if (out_size < (2 + 2 * data_len + 1)) {
return -ENOMEM;
}
out_buf[out_idx++] = KISS_FEND;
out_buf[out_idx++] = cmd;
for (size_t i = 0; i < data_len; i++) {
if (data[i] == KISS_FEND) {
out_buf[out_idx++] = KISS_FESC;
out_buf[out_idx++] = KISS_TFEND;
} else if (data[i] == KISS_FESC) {
out_buf[out_idx++] = KISS_FESC;
out_buf[out_idx++] = KISS_TFESC;
} else {
out_buf[out_idx++] = data[i];
}
}
out_buf[out_idx++] = KISS_FEND;
return (int)out_idx;
}
int kiss_encode(const uint8_t *data, size_t data_len,
uint8_t *out_buf, size_t out_size)
{
return kiss_encode_cmd(KISS_CMD_DATA, data, data_len, out_buf, out_size);
}
void kiss_decoder_init(struct kiss_decoder_ctx *ctx,
uint8_t *buf, size_t size)
{
ctx->frame_buf = buf;
ctx->frame_buf_size = size;
ctx->frame_len = 0;
ctx->in_frame = false;
ctx->in_escape = false;
ctx->cmd_received = false;
ctx->cmd = 0;
}
bool kiss_decoder_feed(struct kiss_decoder_ctx *ctx, uint8_t byte)
{
if (byte == KISS_FEND) {
if (ctx->in_frame && ctx->frame_len > 0) {
/* Closing delimiter — frame complete */
ctx->in_frame = false;
ctx->in_escape = false;
ctx->cmd_received = false;
return true;
}
/* Opening delimiter (or spurious FEND) — start fresh */
ctx->in_frame = true;
ctx->in_escape = false;
ctx->cmd_received = false;
ctx->frame_len = 0;
return false;
}
if (!ctx->in_frame) {
return false;
}
/* Handle escape sequences */
if (byte == KISS_FESC) {
ctx->in_escape = true;
return false;
}
if (ctx->in_escape) {
ctx->in_escape = false;
if (byte == KISS_TFEND) {
byte = KISS_FEND;
} else if (byte == KISS_TFESC) {
byte = KISS_FESC;
}
/* Unknown escape sequences: discard the escape, keep the byte */
}
/* First unescaped byte after opening FEND is the CMD byte */
if (!ctx->cmd_received) {
ctx->cmd = byte;
ctx->cmd_received = true;
return false;
}
/* Append to payload buffer (silently drop if full) */
if (ctx->frame_len < ctx->frame_buf_size) {
ctx->frame_buf[ctx->frame_len++] = byte;
}
return false;
}

125
app/src/kiss.h Normal file
View File

@@ -0,0 +1,125 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* KISS (Keep It Simple Stupid) protocol encoder/decoder.
*
* Frame format:
* FEND | CMD | <data with escaping> | FEND
*
* Escape sequences (inside frame data only):
* 0xC0 → 0xDB 0xDC (FESC TFEND)
* 0xDB → 0xDB 0xDD (FESC TFESC)
*/
#ifndef KISS_H
#define KISS_H
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
/* Special bytes */
#define KISS_FEND 0xC0u /* Frame delimiter */
#define KISS_FESC 0xDBu /* Escape byte */
#define KISS_TFEND 0xDCu /* Escaped FEND */
#define KISS_TFESC 0xDDu /* Escaped FESC */
/* Command codes (upper nibble = port 0) */
#define KISS_CMD_DATA 0x00u
#define KISS_CMD_TXDELAY 0x01u
#define KISS_CMD_PERSISTENCE 0x02u
#define KISS_CMD_SLOTTIME 0x03u
#define KISS_CMD_TXTAIL 0x04u
#define KISS_CMD_FULLDUPLEX 0x05u
#define KISS_CMD_SETHARDWARE 0x06u
#define KISS_CMD_RETURN 0xFFu
/*
* SetHardware (0x06) sub-commands — device-specific radio configuration.
*
* Frame payload format: [sub-cmd 1 byte] [params...]
*
* SET commands change a parameter and apply it immediately.
* GET commands cause the device to respond with a SetHardware frame
* containing the same sub-command byte followed by the current value(s).
*
* Frequency: uint32_t big-endian, Hz (e.g. 0x36892580 = 915000000)
* BW: uint16_t big-endian, kHz (125, 250, or 500)
* SF: uint8_t 7..12
* CR: uint8_t coding-rate denominator 5..8 (meaning 4/5 .. 4/8)
* TX power: int8_t dBm
*/
#define KISS_HW_SET_FREQ 0x01u /* payload: 4 bytes freq_hz (BE uint32) */
#define KISS_HW_GET_FREQ 0x02u /* no params; response: sub-cmd + 4 bytes */
#define KISS_HW_SET_SF 0x03u /* payload: 1 byte sf (7-12) */
#define KISS_HW_SET_BW 0x04u /* payload: 2 bytes bw_khz (BE uint16: 125/250/500) */
#define KISS_HW_SET_CR 0x05u /* payload: 1 byte cr denom (5-8) */
#define KISS_HW_SET_TXPWR 0x06u /* payload: 1 byte tx_power_dbm (int8) */
#define KISS_HW_GET_PARAMS 0x07u /* no params; response: sub-cmd + all params (10 bytes) */
/*
* GET_PARAMS response payload (10 bytes after sub-cmd byte):
* [0..3] freq_hz BE uint32
* [4] sf uint8
* [5..6] bw_khz BE uint16
* [7] cr uint8 (denominator)
* [8] tx_power int8 (dBm)
* [9] preamble uint8
*/
/**
* @brief Encode data into a KISS frame with an explicit command byte.
*
* Writes FEND + cmd + escaped data + FEND into @p out_buf.
*
* @param cmd KISS command byte (e.g. KISS_CMD_DATA, KISS_CMD_SETHARDWARE).
* @param data Payload bytes (may be NULL when data_len == 0).
* @param data_len Length of payload.
* @param out_buf Output buffer for the encoded frame.
* @param out_size Size of the output buffer.
* @return Number of bytes written, or -ENOMEM if @p out_buf is too small.
*/
int kiss_encode_cmd(uint8_t cmd, const uint8_t *data, size_t data_len,
uint8_t *out_buf, size_t out_size);
/**
* @brief Encode raw data into a KISS data frame (CMD = 0x00).
*
* Convenience wrapper around kiss_encode_cmd() using KISS_CMD_DATA.
*/
int kiss_encode(const uint8_t *data, size_t data_len,
uint8_t *out_buf, size_t out_size);
/**
* @brief KISS decoder context. Maintains state between calls to kiss_decoder_feed().
*/
struct kiss_decoder_ctx {
uint8_t *frame_buf; /**< Caller-supplied buffer for decoded payload. */
size_t frame_buf_size; /**< Size of frame_buf. */
size_t frame_len; /**< Number of payload bytes decoded so far. */
bool in_frame; /**< True after the opening FEND has been seen. */
bool in_escape; /**< True after FESC has been seen. */
bool cmd_received; /**< True once the CMD byte has been consumed. */
uint8_t cmd; /**< KISS command byte of the current frame. */
};
/**
* @brief Initialize a KISS decoder context.
*
* @param ctx Context to initialize.
* @param buf Buffer for decoded frame payload.
* @param size Size of @p buf.
*/
void kiss_decoder_init(struct kiss_decoder_ctx *ctx,
uint8_t *buf, size_t size);
/**
* @brief Feed one byte into the KISS decoder state machine.
*
* @param ctx Decoder context.
* @param byte Next byte from the serial stream.
* @return true when a complete frame has been received.
* ctx->frame_buf[0..ctx->frame_len-1] holds the payload.
* ctx->cmd holds the KISS command byte.
*/
bool kiss_decoder_feed(struct kiss_decoder_ctx *ctx, uint8_t byte);
#endif /* KISS_H */

218
app/src/lora_modem.c Normal file
View File

@@ -0,0 +1,218 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* LoRa modem abstraction layer.
* Maps runtime lora_radio_params to Zephyr's lora_modem_config enums
* and provides a simple send/recv API for the KISS bridge.
*
* Runtime parameters are stored in g_params, protected by g_params_mutex.
* send() and recv() snapshot the params under the mutex before configuring
* the radio, so a concurrent frequency change takes effect on the next call.
*/
#include "lora_modem.h"
#include <zephyr/drivers/lora.h>
#include <zephyr/devicetree.h>
#include <zephyr/logging/log.h>
#include <string.h>
LOG_MODULE_REGISTER(lora_modem, LOG_LEVEL_INF);
#define LORA_DEV DEVICE_DT_GET(DT_ALIAS(lora0))
/* ── Runtime parameter state ─────────────────────────────────────────────── */
static struct lora_radio_params g_params;
static K_MUTEX_DEFINE(g_params_mutex);
/* ── Enum conversion helpers ─────────────────────────────────────────────── */
static enum lora_signal_bandwidth bw_to_enum(uint16_t bw_khz)
{
switch (bw_khz) {
case 500: return BW_500_KHZ;
case 250: return BW_250_KHZ;
case 125: /* fall through */
default: return BW_125_KHZ;
}
}
static enum lora_datarate sf_to_enum(uint8_t sf)
{
switch (sf) {
case 6: return SF_6;
case 7: return SF_7;
case 8: return SF_8;
case 9: return SF_9;
case 10: return SF_10;
case 11: return SF_11;
case 12: /* fall through */
default: return SF_12;
}
}
static enum lora_coding_rate cr_to_enum(uint8_t cr_denom)
{
switch (cr_denom) {
case 5: return CR_4_5;
case 6: return CR_4_6;
case 7: return CR_4_7;
case 8: /* fall through */
default: return CR_4_8;
}
}
/* Build a lora_modem_config from a params snapshot */
static void params_to_cfg(const struct lora_radio_params *p,
struct lora_modem_config *cfg, bool tx)
{
cfg->frequency = p->freq_hz;
cfg->bandwidth = bw_to_enum(p->bw_khz);
cfg->datarate = sf_to_enum(p->sf);
cfg->coding_rate = cr_to_enum(p->cr);
cfg->preamble_len = p->preamble_len;
cfg->tx_power = p->tx_power;
cfg->tx = tx;
}
/* ── Public API ──────────────────────────────────────────────────────────── */
int lora_modem_init(void)
{
const struct device *lora_dev = LORA_DEV;
if (!device_is_ready(lora_dev)) {
LOG_ERR("LoRa device not ready");
return -ENODEV;
}
/* Load compile-time defaults into runtime params */
g_params.freq_hz = CONFIG_LORAMODEM_LORA_FREQ_HZ;
g_params.sf = CONFIG_LORAMODEM_LORA_SF;
g_params.bw_khz = CONFIG_LORAMODEM_LORA_BW;
g_params.cr = CONFIG_LORAMODEM_LORA_CR;
g_params.tx_power = CONFIG_LORAMODEM_LORA_TX_POWER_DBM;
g_params.preamble_len = CONFIG_LORAMODEM_LORA_PREAMBLE_LEN;
LOG_INF("LoRa init: %u Hz SF%u BW%ukHz CR4/%u %d dBm",
g_params.freq_hz, g_params.sf, g_params.bw_khz,
g_params.cr, g_params.tx_power);
struct lora_modem_config cfg;
params_to_cfg(&g_params, &cfg, true);
return lora_config(lora_dev, &cfg);
}
void lora_modem_get_params(struct lora_radio_params *params)
{
k_mutex_lock(&g_params_mutex, K_FOREVER);
*params = g_params;
k_mutex_unlock(&g_params_mutex);
}
int lora_modem_set_params(const struct lora_radio_params *params)
{
if (params->sf < 6 || params->sf > 12) {
return -EINVAL;
}
if (params->bw_khz != 125 && params->bw_khz != 250 &&
params->bw_khz != 500) {
return -EINVAL;
}
if (params->cr < 5 || params->cr > 8) {
return -EINVAL;
}
if (params->freq_hz == 0) {
return -EINVAL;
}
k_mutex_lock(&g_params_mutex, K_FOREVER);
g_params = *params;
k_mutex_unlock(&g_params_mutex);
LOG_INF("Params updated: %u Hz SF%u BW%ukHz CR4/%u %d dBm",
params->freq_hz, params->sf, params->bw_khz,
params->cr, params->tx_power);
return 0;
}
int lora_modem_set_freq(uint32_t freq_hz)
{
if (freq_hz == 0) {
return -EINVAL;
}
k_mutex_lock(&g_params_mutex, K_FOREVER);
g_params.freq_hz = freq_hz;
k_mutex_unlock(&g_params_mutex);
LOG_INF("Frequency set to %u Hz", freq_hz);
return 0;
}
int lora_modem_send(const uint8_t *data, size_t len)
{
const struct device *lora_dev = LORA_DEV;
struct lora_radio_params snap;
struct lora_modem_config tx_cfg;
int ret;
k_mutex_lock(&g_params_mutex, K_FOREVER);
snap = g_params;
k_mutex_unlock(&g_params_mutex);
params_to_cfg(&snap, &tx_cfg, true);
ret = lora_config(lora_dev, &tx_cfg);
if (ret < 0) {
LOG_ERR("lora_config(TX) failed: %d", ret);
return ret;
}
ret = lora_send(lora_dev, (uint8_t *)data, (uint32_t)len);
if (ret < 0) {
LOG_ERR("lora_send failed: %d", ret);
}
return ret;
}
int lora_modem_recv(uint8_t *buf, size_t buf_size, k_timeout_t timeout,
int16_t *rssi_out, int8_t *snr_out)
{
const struct device *lora_dev = LORA_DEV;
struct lora_radio_params snap;
struct lora_modem_config rx_cfg;
int ret;
k_mutex_lock(&g_params_mutex, K_FOREVER);
snap = g_params;
k_mutex_unlock(&g_params_mutex);
params_to_cfg(&snap, &rx_cfg, false);
ret = lora_config(lora_dev, &rx_cfg);
if (ret < 0) {
LOG_ERR("lora_config(RX) failed: %d", ret);
return ret;
}
int16_t rssi = 0;
int8_t snr = 0;
ret = lora_recv(lora_dev, buf, (uint8_t)buf_size, timeout, &rssi, &snr);
if (ret >= 0) {
LOG_DBG("RX %d bytes RSSI=%d SNR=%d", ret, rssi, snr);
if (rssi_out) {
*rssi_out = rssi;
}
if (snr_out) {
*snr_out = snr;
}
}
return ret;
}

92
app/src/lora_modem.h Normal file
View File

@@ -0,0 +1,92 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* LoRa modem abstraction layer.
* Wraps Zephyr's lora_driver_api with application-level configuration.
*/
#ifndef LORA_MODEM_H
#define LORA_MODEM_H
#include <stdint.h>
#include <stddef.h>
#include <zephyr/kernel.h>
/**
* @brief Runtime radio parameters.
*
* All fields are in user-friendly units. Modified at runtime via
* lora_modem_set_params() / lora_modem_set_freq(); read via
* lora_modem_get_params().
*/
struct lora_radio_params {
uint32_t freq_hz; /**< Center frequency in Hz */
uint8_t sf; /**< Spreading factor (7-12) */
uint16_t bw_khz; /**< Bandwidth in kHz (125, 250, or 500) */
uint8_t cr; /**< Coding rate denominator (5..8, meaning 4/5..4/8) */
int8_t tx_power; /**< TX power in dBm */
uint8_t preamble_len; /**< Preamble length in symbols */
};
/**
* @brief Initialize and configure the LoRa radio.
*
* Loads compile-time Kconfig defaults into the runtime params struct
* and applies them. Call once at startup before any send/recv.
*
* @return 0 on success, negative errno on failure.
*/
int lora_modem_init(void);
/**
* @brief Get the current radio parameters.
*
* @param params Output: filled with current settings.
*/
void lora_modem_get_params(struct lora_radio_params *params);
/**
* @brief Set all radio parameters at once.
*
* Parameters are stored for use on the next send/recv call.
* The radio is NOT immediately reconfigured; the change takes effect
* at the start of the next lora_modem_send() or lora_modem_recv() call.
*
* @param params New parameter values.
* @return 0 on success, -EINVAL if a parameter is out of range.
*/
int lora_modem_set_params(const struct lora_radio_params *params);
/**
* @brief Change only the center frequency.
*
* Convenience wrapper around lora_modem_set_params() for the common
* case of a frequency-only change.
*
* @param freq_hz New center frequency in Hz.
* @return 0 on success, -EINVAL if out of range.
*/
int lora_modem_set_freq(uint32_t freq_hz);
/**
* @brief Transmit a packet over LoRa.
*
* @param data Payload bytes.
* @param len Payload length (must be <= CONFIG_LORAMODEM_MAX_PACKET_SIZE).
* @return 0 on success, negative errno on failure.
*/
int lora_modem_send(const uint8_t *data, size_t len);
/**
* @brief Receive a packet from LoRa (blocking).
*
* @param buf Buffer for received payload.
* @param buf_size Size of @p buf.
* @param timeout How long to wait (use K_FOREVER to wait indefinitely).
* @param rssi_out If non-NULL, receives RSSI of the packet (dBm).
* @param snr_out If non-NULL, receives SNR of the packet (dB).
* @return Number of bytes received (>= 0), or negative errno on error/timeout.
*/
int lora_modem_recv(uint8_t *buf, size_t buf_size, k_timeout_t timeout,
int16_t *rssi_out, int8_t *snr_out);
#endif /* LORA_MODEM_H */

362
app/src/main.c Normal file
View File

@@ -0,0 +1,362 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* LoRa KISS Modem — main application.
*
* Two threads bridge a KISS serial interface to a LoRa radio:
*
* kiss_rx_thread: reads bytes from the KISS UART, decodes KISS frames,
* dispatches data frames to lora_modem_send(), and
* handles SetHardware (0x06) frames for radio parameter
* changes (frequency, SF, BW, CR, TX power).
*
* lora_rx_thread: loops with a 1-second timeout on lora_modem_recv();
* on each successful receive it encodes the payload as
* a KISS data frame and writes it to the KISS UART.
* The finite timeout ensures frequency changes applied
* by kiss_rx_thread take effect within 1 second.
*
* SetHardware sub-command protocol (see kiss.h for KISS_HW_* defines):
*
* Host → device: FEND | 0x06 | <sub-cmd> [params...] | FEND
* Device → host: FEND | 0x06 | <sub-cmd> [current-value...] | FEND
* (only sent in response to GET commands)
*
* SET_FREQ (0x01): payload = 4 bytes freq_hz big-endian uint32
* GET_FREQ (0x02): no params; response = sub-cmd + 4-byte freq
* SET_SF (0x03): payload = 1 byte sf (7-12)
* SET_BW (0x04): payload = 2 bytes bw_khz big-endian uint16 (125/250/500)
* SET_CR (0x05): payload = 1 byte cr denominator (5-8)
* SET_TXPWR(0x06): payload = 1 byte tx_power_dbm (int8)
* GET_PARAMS(0x07): no params; response = sub-cmd + 10-byte param block
*
* GET_PARAMS response (10 bytes after sub-cmd):
* [0..3] freq_hz BE uint32
* [4] sf uint8
* [5..6] bw_khz BE uint16
* [7] cr uint8 (denominator)
* [8] tx_power int8 (dBm)
* [9] preamble uint8
*/
#include "kiss.h"
#include "lora_modem.h"
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/uart.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(main, LOG_LEVEL_INF);
/* Resolve the KISS UART from the board overlay */
#define KISS_UART_NODE DT_CHOSEN(loramodem_kiss_uart)
#define KISS_UART DEVICE_DT_GET(KISS_UART_NODE)
/* Thread stack sizes */
#define KISS_RX_STACK_SIZE 2048
#define LORA_RX_STACK_SIZE 2048
/* lora_rx_fn polls with this timeout so frequency changes take effect promptly */
#define LORA_RX_TIMEOUT_MS 1000
/* Packet buffers */
static uint8_t lora_rx_buf[CONFIG_LORAMODEM_MAX_PACKET_SIZE];
/* kiss_tx_buf is shared by both threads — protected by uart_tx_mutex */
static uint8_t kiss_tx_buf[CONFIG_LORAMODEM_MAX_PACKET_SIZE * 2 + 4];
/* Mutex protecting writes to the KISS UART (used by both threads) */
static K_MUTEX_DEFINE(uart_tx_mutex);
/* ── UART helpers ─────────────────────────────────────────────────────────── */
static void uart_write_bytes(const struct device *uart,
const uint8_t *buf, size_t len)
{
for (size_t i = 0; i < len; i++) {
uart_poll_out(uart, buf[i]);
}
}
/* Encode and send a KISS SetHardware response frame to the host */
static void send_hw_response(const struct device *uart,
const uint8_t *payload, size_t payload_len)
{
k_mutex_lock(&uart_tx_mutex, K_FOREVER);
int enc = kiss_encode_cmd(KISS_CMD_SETHARDWARE,
payload, payload_len,
kiss_tx_buf, sizeof(kiss_tx_buf));
if (enc > 0) {
uart_write_bytes(uart, kiss_tx_buf, (size_t)enc);
}
k_mutex_unlock(&uart_tx_mutex);
}
/* ── SetHardware dispatcher ──────────────────────────────────────────────── */
static void handle_sethardware(const struct device *uart,
const uint8_t *payload, size_t len)
{
if (len == 0) {
LOG_WRN("SetHardware: empty payload");
return;
}
uint8_t sub_cmd = payload[0];
const uint8_t *args = payload + 1;
size_t args_len = len - 1;
switch (sub_cmd) {
case KISS_HW_SET_FREQ: {
if (args_len < 4) {
LOG_WRN("SET_FREQ: need 4 bytes, got %zu", args_len);
return;
}
uint32_t freq = ((uint32_t)args[0] << 24) |
((uint32_t)args[1] << 16) |
((uint32_t)args[2] << 8) |
((uint32_t)args[3]);
if (lora_modem_set_freq(freq) < 0) {
LOG_WRN("SET_FREQ: invalid frequency %u Hz", freq);
}
break;
}
case KISS_HW_GET_FREQ: {
struct lora_radio_params p;
lora_modem_get_params(&p);
uint8_t resp[5];
resp[0] = KISS_HW_GET_FREQ;
resp[1] = (uint8_t)(p.freq_hz >> 24);
resp[2] = (uint8_t)(p.freq_hz >> 16);
resp[3] = (uint8_t)(p.freq_hz >> 8);
resp[4] = (uint8_t)(p.freq_hz);
send_hw_response(uart, resp, sizeof(resp));
break;
}
case KISS_HW_SET_SF: {
if (args_len < 1) {
LOG_WRN("SET_SF: need 1 byte");
return;
}
struct lora_radio_params p;
lora_modem_get_params(&p);
p.sf = args[0];
if (lora_modem_set_params(&p) < 0) {
LOG_WRN("SET_SF: invalid SF %u", args[0]);
}
break;
}
case KISS_HW_SET_BW: {
if (args_len < 2) {
LOG_WRN("SET_BW: need 2 bytes");
return;
}
uint16_t bw = ((uint16_t)args[0] << 8) | args[1];
struct lora_radio_params p;
lora_modem_get_params(&p);
p.bw_khz = bw;
if (lora_modem_set_params(&p) < 0) {
LOG_WRN("SET_BW: invalid BW %u kHz", bw);
}
break;
}
case KISS_HW_SET_CR: {
if (args_len < 1) {
LOG_WRN("SET_CR: need 1 byte");
return;
}
struct lora_radio_params p;
lora_modem_get_params(&p);
p.cr = args[0];
if (lora_modem_set_params(&p) < 0) {
LOG_WRN("SET_CR: invalid CR %u", args[0]);
}
break;
}
case KISS_HW_SET_TXPWR: {
if (args_len < 1) {
LOG_WRN("SET_TXPWR: need 1 byte");
return;
}
struct lora_radio_params p;
lora_modem_get_params(&p);
p.tx_power = (int8_t)args[0];
lora_modem_set_params(&p);
break;
}
case KISS_HW_GET_PARAMS: {
struct lora_radio_params p;
lora_modem_get_params(&p);
uint8_t resp[11];
resp[0] = KISS_HW_GET_PARAMS;
resp[1] = (uint8_t)(p.freq_hz >> 24);
resp[2] = (uint8_t)(p.freq_hz >> 16);
resp[3] = (uint8_t)(p.freq_hz >> 8);
resp[4] = (uint8_t)(p.freq_hz);
resp[5] = p.sf;
resp[6] = (uint8_t)(p.bw_khz >> 8);
resp[7] = (uint8_t)(p.bw_khz);
resp[8] = p.cr;
resp[9] = (uint8_t)p.tx_power;
resp[10] = p.preamble_len;
send_hw_response(uart, resp, sizeof(resp));
break;
}
default:
LOG_WRN("SetHardware: unknown sub-cmd 0x%02x", sub_cmd);
break;
}
}
/* ── KISS RX thread: serial → LoRa ────────────────────────────────────────── */
K_THREAD_STACK_DEFINE(kiss_rx_stack, KISS_RX_STACK_SIZE);
static struct k_thread kiss_rx_thread;
static void kiss_rx_fn(void *p1, void *p2, void *p3)
{
ARG_UNUSED(p1);
ARG_UNUSED(p2);
ARG_UNUSED(p3);
const struct device *uart = KISS_UART;
struct kiss_decoder_ctx dec;
static uint8_t dec_buf[CONFIG_LORAMODEM_MAX_PACKET_SIZE];
kiss_decoder_init(&dec, dec_buf, sizeof(dec_buf));
#if defined(CONFIG_LORAMODEM_KISS_IFACE_USB)
uint32_t dtr = 0;
LOG_INF("Waiting for USB host...");
while (!dtr) {
uart_line_ctrl_get(uart, UART_LINE_CTRL_DTR, &dtr);
k_sleep(K_MSEC(100));
}
LOG_INF("USB host connected");
#endif
LOG_INF("KISS RX thread started");
uint8_t byte;
while (true) {
if (uart_poll_in(uart, &byte) == 0) {
if (kiss_decoder_feed(&dec, byte)) {
if (dec.cmd == KISS_CMD_DATA &&
dec.frame_len > 0) {
LOG_DBG("TX %zu bytes via LoRa",
dec.frame_len);
lora_modem_send(dec.frame_buf,
dec.frame_len);
} else if (dec.cmd == KISS_CMD_SETHARDWARE) {
handle_sethardware(uart,
dec.frame_buf,
dec.frame_len);
}
kiss_decoder_init(&dec, dec_buf,
sizeof(dec_buf));
}
} else {
k_yield();
}
}
}
/* ── LoRa RX thread: LoRa → serial ────────────────────────────────────────── */
K_THREAD_STACK_DEFINE(lora_rx_stack, LORA_RX_STACK_SIZE);
static struct k_thread lora_rx_thread;
static void lora_rx_fn(void *p1, void *p2, void *p3)
{
ARG_UNUSED(p1);
ARG_UNUSED(p2);
ARG_UNUSED(p3);
const struct device *uart = KISS_UART;
int16_t rssi;
int8_t snr;
LOG_INF("LoRa RX thread started");
while (true) {
/*
* Use a finite timeout so any frequency change applied by
* kiss_rx_thread takes effect within LORA_RX_TIMEOUT_MS.
* lora_modem_recv() snapshots g_params at the start of each
* call, so the new frequency is picked up automatically on
* the next iteration.
*/
int len = lora_modem_recv(lora_rx_buf, sizeof(lora_rx_buf),
K_MSEC(LORA_RX_TIMEOUT_MS),
&rssi, &snr);
if (len == -ETIMEDOUT) {
continue;
}
if (len < 0) {
LOG_WRN("lora_modem_recv error: %d", len);
continue;
}
LOG_DBG("RX %d bytes RSSI=%d SNR=%d", len, rssi, snr);
k_mutex_lock(&uart_tx_mutex, K_FOREVER);
int enc_len = kiss_encode(lora_rx_buf, (size_t)len,
kiss_tx_buf, sizeof(kiss_tx_buf));
if (enc_len > 0) {
uart_write_bytes(uart, kiss_tx_buf, (size_t)enc_len);
} else {
LOG_WRN("kiss_encode error: %d", enc_len);
}
k_mutex_unlock(&uart_tx_mutex);
}
}
/* ── Entry point ──────────────────────────────────────────────────────────── */
int main(void)
{
int ret;
LOG_INF("LoRa KISS Modem starting");
ret = lora_modem_init();
if (ret < 0) {
LOG_ERR("LoRa modem init failed: %d", ret);
return ret;
}
k_thread_create(&kiss_rx_thread, kiss_rx_stack,
K_THREAD_STACK_SIZEOF(kiss_rx_stack),
kiss_rx_fn, NULL, NULL, NULL,
K_PRIO_COOP(5), 0, K_NO_WAIT);
k_thread_name_set(&kiss_rx_thread, "kiss_rx");
k_thread_create(&lora_rx_thread, lora_rx_stack,
K_THREAD_STACK_SIZEOF(lora_rx_stack),
lora_rx_fn, NULL, NULL, NULL,
K_PRIO_COOP(5), 0, K_NO_WAIT);
k_thread_name_set(&lora_rx_thread, "lora_rx");
return 0;
}

View File

@@ -0,0 +1,6 @@
if BOARD_HELTEC_MESHPOCKET
config BOARD
default "heltec_meshpocket"
endif # BOARD_HELTEC_MESHPOCKET

View File

@@ -0,0 +1,2 @@
config BOARD_HELTEC_MESHPOCKET
select SOC_NRF52840_QIAA

View File

@@ -0,0 +1,2 @@
# Heltec MeshPocket — board.cmake
include(${ZEPHYR_BASE}/boards/nordic/common/board.cmake)

View File

@@ -0,0 +1,7 @@
board:
name: heltec_meshpocket
full_name: "Heltec MeshPocket"
vendor: heltec
url: https://heltec.org/project/mesh-pocket/
socs:
- name: nrf52840

View File

@@ -0,0 +1,47 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* Heltec MeshPocket — pin control definitions
* SoC: nRF52840-QIAA
*/
#include <nordic/nrf52840.dtsi>
#include <dt-bindings/pinctrl/nrf-pinctrl.h>
&pinctrl {
uart0_default: uart0_default {
group1 {
psels = <NRF_PSEL(UART_TX, 0, 6)>;
};
group2 {
psels = <NRF_PSEL(UART_RX, 0, 8)>;
bias-pull-up;
};
};
uart0_sleep: uart0_sleep {
group1 {
psels = <NRF_PSEL(UART_TX, 0, 6)>,
<NRF_PSEL(UART_RX, 0, 8)>;
low-power-enable;
};
};
/* SPI1 for SX1262 */
spi1_default: spi1_default {
group1 {
psels = <NRF_PSEL(SPIM_SCK, 0, 26)>,
<NRF_PSEL(SPIM_MOSI, 0, 27)>,
<NRF_PSEL(SPIM_MISO, 0, 28)>;
};
};
spi1_sleep: spi1_sleep {
group1 {
psels = <NRF_PSEL(SPIM_SCK, 0, 26)>,
<NRF_PSEL(SPIM_MOSI, 0, 27)>,
<NRF_PSEL(SPIM_MISO, 0, 28)>;
low-power-enable;
};
};
};

View File

@@ -0,0 +1,96 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* Heltec MeshPocket — Device Tree Source
* SoC: nRF52840-QIAA
* Radio: SX1262 (crystal oscillator, no TCXO)
*/
/dts-v1/;
#include <nordic/nrf52840_qiaa.dtsi>
#include "heltec_meshpocket-pinctrl.dtsi"
/ {
model = "Heltec MeshPocket";
compatible = "heltec,meshpocket";
chosen {
zephyr,sram = &sram0;
zephyr,flash = &flash0;
zephyr,console = &uart0;
zephyr,shell-uart = &uart0;
zephyr,code-partition = &slot0_partition;
};
aliases {
lora0 = &lora;
};
};
&uicr {
gpio-as-nreset;
};
&gpiote {
status = "okay";
};
&gpio0 {
status = "okay";
};
&gpio1 {
status = "okay";
};
&uart0 {
compatible = "nordic,nrf-uarte";
status = "okay";
current-speed = <115200>;
pinctrl-0 = <&uart0_default>;
pinctrl-1 = <&uart0_sleep>;
pinctrl-names = "default", "sleep";
};
&spi1 {
compatible = "nordic,nrf-spim";
status = "okay";
pinctrl-0 = <&spi1_default>;
pinctrl-names = "default";
cs-gpios = <&gpio0 24 GPIO_ACTIVE_LOW>;
lora: lora@0 {
compatible = "semtech,sx1262";
reg = <0>;
spi-max-frequency = <1000000>;
reset-gpios = <&gpio0 21 GPIO_ACTIVE_LOW>;
busy-gpios = <&gpio1 3 GPIO_ACTIVE_HIGH>;
dio1-gpios = <&gpio1 4 GPIO_ACTIVE_HIGH>;
dio2-tx-enable;
};
};
&flash0 {
partitions {
compatible = "fixed-partitions";
#address-cells = <1>;
#size-cells = <1>;
boot_partition: partition@0 {
label = "mcuboot";
reg = <0x00000000 0x0000C000>;
};
slot0_partition: partition@c000 {
label = "image-0";
reg = <0x0000C000 0x00067000>;
};
slot1_partition: partition@73000 {
label = "image-1";
reg = <0x00073000 0x00067000>;
};
storage_partition: partition@da000 {
label = "storage";
reg = <0x000da000 0x00006000>;
};
};
};

View File

@@ -0,0 +1,7 @@
CONFIG_SOC_SERIES_NRF52X=y
CONFIG_BOARD_HELTEC_MESHPOCKET=y
CONFIG_FLASH=y
CONFIG_FLASH_MAP=y
CONFIG_UART_CONSOLE=y
CONFIG_SPI=y
CONFIG_GPIO=y

View File

@@ -0,0 +1,6 @@
if BOARD_HELTEC_T114
config BOARD
default "heltec_t114"
endif # BOARD_HELTEC_T114

View File

@@ -0,0 +1,2 @@
config BOARD_HELTEC_T114
select SOC_NRF52840_QIAA

View File

@@ -0,0 +1,7 @@
# Heltec T114 — board.cmake
# Uses nrfjprog or pyocd for flashing via SWD.
# The board exposes a SWD header for programming.
board_runner_args(nrfjprog "--nrf-family=NRF52")
board_runner_args(pyocd "--target=nrf52840")
include(${ZEPHYR_BASE}/boards/common/nrfjprog.board.cmake)
include(${ZEPHYR_BASE}/boards/common/pyocd.board.cmake)

View File

@@ -0,0 +1,7 @@
board:
name: heltec_t114
full_name: "Heltec Mesh Node T114"
vendor: heltec
url: https://heltec.org/project/mesh-node-t114/
socs:
- name: nrf52840

View File

@@ -0,0 +1,46 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* Heltec T114 — pin control definitions
*/
#include <nordic/nrf52840.dtsi>
#include <dt-bindings/pinctrl/nrf-pinctrl.h>
&pinctrl {
uart1_default: uart1_default {
group1 {
psels = <NRF_PSEL(UART_TX, 0, 10)>;
};
group2 {
psels = <NRF_PSEL(UART_RX, 0, 9)>;
bias-pull-up;
};
};
uart1_sleep: uart1_sleep {
group1 {
psels = <NRF_PSEL(UART_TX, 0, 10)>,
<NRF_PSEL(UART_RX, 0, 9)>;
low-power-enable;
};
};
/* SPI1 for LR1110 */
spi1_default: spi1_default {
group1 {
psels = <NRF_PSEL(SPIM_SCK, 0, 19)>,
<NRF_PSEL(SPIM_MOSI, 0, 22)>,
<NRF_PSEL(SPIM_MISO, 0, 23)>;
};
};
spi1_sleep: spi1_sleep {
group1 {
psels = <NRF_PSEL(SPIM_SCK, 0, 19)>,
<NRF_PSEL(SPIM_MOSI, 0, 22)>,
<NRF_PSEL(SPIM_MISO, 0, 23)>;
low-power-enable;
};
};
};

View File

@@ -0,0 +1,115 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* Heltec Mesh Node T114 — Device Tree Source
* SoC: nRF52840 QIAAVariant
* Radio: SX1262
*
* NOTE: GPIO pin numbers are placeholders. Verify against the T114
* schematic/documentation before using with real hardware.
* The T114 uses the nRF52840 SoC in a similar form factor to the
* RAK4631, but with different GPIO assignments.
*/
/dts-v1/;
#include <nordic/nrf52840_qiaa.dtsi>
#include "heltec_t114-pinctrl.dtsi"
/ {
model = "Heltec Mesh Node T114";
compatible = "heltec,mesh-node-t114";
chosen {
zephyr,sram = &sram0;
zephyr,flash = &flash0;
zephyr,code-partition = &slot0_partition;
};
aliases {
lora0 = &lora;
led0 = &led0;
};
leds {
compatible = "gpio-leds";
led0: led_0 {
/* TODO: verify LED GPIO from T114 schematic */
gpios = <&gpio0 13 GPIO_ACTIVE_LOW>;
label = "LED 0";
};
};
};
&uicr {
gpio-as-nreset;
};
&gpiote {
status = "okay";
};
&gpio0 {
status = "okay";
};
&gpio1 {
status = "okay";
};
&uart1 {
compatible = "nordic,nrf-uarte";
status = "okay";
current-speed = <115200>;
pinctrl-0 = <&uart1_default>;
pinctrl-1 = <&uart1_sleep>;
pinctrl-names = "default", "sleep";
};
&spi1 {
compatible = "nordic,nrf-spim";
status = "okay";
pinctrl-0 = <&spi1_default>;
pinctrl-names = "default";
lora: lora@0 {
compatible = "semtech,sx1262";
reg = <0>;
spi-max-frequency = <1000000>;
reset-gpios = <&gpio0 25 GPIO_ACTIVE_LOW>;
busy-gpios = <&gpio0 17 GPIO_ACTIVE_HIGH>;
dio1-gpios = <&gpio0 20 GPIO_ACTIVE_HIGH>;
dio2-tx-enable;
dio3-tcxo-voltage = <SX126X_DIO3_TCXO_3V3>;
tcxo-power-startup-delay-ms = <5>;
};
};
zephyr_udc0: &usbd {
compatible = "nordic,nrf-usbd";
status = "okay";
};
&flash0 {
partitions {
compatible = "fixed-partitions";
#address-cells = <1>;
#size-cells = <1>;
boot_partition: partition@0 {
label = "mcuboot";
reg = <0x00000000 0x0000C000>;
};
slot0_partition: partition@c000 {
label = "image-0";
reg = <0x0000C000 0x00067000>;
};
slot1_partition: partition@73000 {
label = "image-1";
reg = <0x00073000 0x00067000>;
};
storage_partition: partition@da000 {
label = "storage";
reg = <0x000da000 0x00006000>;
};
};
};

View File

@@ -0,0 +1,8 @@
CONFIG_SOC_NRF52840_QIAA=y
CONFIG_BOARD_HELTEC_T114=y
CONFIG_FLASH=y
CONFIG_FLASH_MAP=y
CONFIG_GPIO=y
CONFIG_SPI=y
CONFIG_USB_DEVICE_STACK=y
CONFIG_NRFX_POWER=y

View File

@@ -0,0 +1,6 @@
if BOARD_HELTEC_WIFI_LORA32_V3
config BOARD
default "heltec_wifi_lora32_v3"
endif # BOARD_HELTEC_WIFI_LORA32_V3

View File

@@ -0,0 +1,3 @@
config BOARD_HELTEC_WIFI_LORA32_V3
select SOC_ESP32S3_PROCPU if BOARD_HELTEC_WIFI_LORA32_V3_PROCPU
select SOC_ESP32S3_APPCPU if BOARD_HELTEC_WIFI_LORA32_V3_APPCPU

View File

@@ -0,0 +1,3 @@
# Heltec WiFi LoRa 32 V3 — board.cmake
# Uses esptool for flashing over CP2102 USB-UART bridge
include(${ZEPHYR_BASE}/boards/espressif/common/board.cmake)

View File

@@ -0,0 +1,10 @@
board:
name: heltec_wifi_lora32_v3
full_name: "Heltec WiFi LoRa 32 V3"
vendor: heltec
url: https://heltec.org/project/wifi-lora-32-v3/
socs:
- name: esp32s3
variants:
- name: procpu
- name: appcpu

View File

@@ -0,0 +1,34 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* Heltec WiFi LoRa 32 V3 — pin control definitions
*/
#include <zephyr/dt-bindings/pinctrl/esp-pinctrl-common.h>
#include <dt-bindings/pinctrl/esp32s3-pinctrl.h>
#include <zephyr/dt-bindings/pinctrl/esp32s3-gpio-sigmap.h>
&pinctrl {
uart0_default: uart0_default {
group1 {
pinmux = <UART0_TX_GPIO43>;
output-enable;
};
group2 {
pinmux = <UART0_RX_GPIO44>;
bias-pull-up;
};
};
spim2_default: spim2_default {
group1 {
pinmux = <SPIM2_MISO_GPIO11>,
<SPIM2_SCLK_GPIO9>,
<SPIM2_CSEL_GPIO8>;
};
group2 {
pinmux = <SPIM2_MOSI_GPIO10>;
output-enable;
};
};
};

View File

@@ -0,0 +1,109 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* Heltec WiFi LoRa 32 V3 — Device Tree Source (PROCPU)
*
* SoC: ESP32-S3
* Radio: SX1262
* Console: UART0 via CP2102 USB-UART bridge (GPIO43=TX, GPIO44=RX)
*/
/dts-v1/;
#include <espressif/esp32s3/esp32s3_fn8r2.dtsi>
#include "heltec_wifi_lora32_v3-pinctrl.dtsi"
#include <zephyr/dt-bindings/input/input-event-codes.h>
/ {
model = "Heltec WiFi LoRa 32 V3 PROCPU";
compatible = "heltec,wifi-lora-32-v3";
chosen {
zephyr,sram = &sram0;
zephyr,flash = &flash0;
zephyr,console = &uart0;
zephyr,shell-uart = &uart0;
zephyr,flash-controller = &flash;
};
aliases {
lora0 = &lora;
led0 = &led_white;
};
leds {
compatible = "gpio-leds";
led_white: led_0 {
gpios = <&gpio0 35 GPIO_ACTIVE_HIGH>;
label = "White LED";
};
};
buttons {
compatible = "gpio-keys";
boot_button: button_0 {
gpios = <&gpio0 0 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>;
label = "Boot Button";
zephyr,code = <INPUT_KEY_0>;
};
};
};
&uart0 {
status = "okay";
current-speed = <115200>;
pinctrl-0 = <&uart0_default>;
pinctrl-names = "default";
};
&spi2 {
status = "okay";
#address-cells = <1>;
#size-cells = <0>;
pinctrl-0 = <&spim2_default>;
pinctrl-names = "default";
cs-gpios = <&gpio0 8 GPIO_ACTIVE_LOW>;
lora: lora@0 {
compatible = "semtech,sx1262";
reg = <0>;
spi-max-frequency = <4000000>;
reset-gpios = <&gpio0 12 (GPIO_OPEN_DRAIN | GPIO_ACTIVE_LOW)>;
busy-gpios = <&gpio0 13 GPIO_ACTIVE_HIGH>;
dio1-gpios = <&gpio0 14 GPIO_ACTIVE_HIGH>;
dio2-tx-enable;
tcxo-power-startup-delay-ms = <5>;
};
};
&gpio0 {
status = "okay";
};
&gpio1 {
status = "okay";
};
&flash0 {
status = "okay";
partitions {
compatible = "fixed-partitions";
#address-cells = <1>;
#size-cells = <1>;
boot_partition: partition@0 {
label = "mcuboot";
reg = <0x00000000 0x00010000>;
};
slot0_partition: partition@10000 {
label = "image-0";
reg = <0x00010000 0x00300000>;
};
slot1_partition: partition@310000 {
label = "image-1";
reg = <0x00310000 0x00300000>;
};
storage_partition: partition@610000 {
label = "storage";
reg = <0x00610000 0x001f0000>;
};
};
};

View File

@@ -0,0 +1,7 @@
CONFIG_SOC_SERIES_ESP32S3=y
CONFIG_BOARD_HELTEC_WIFI_LORA32_V3=y
CONFIG_FLASH=y
CONFIG_FLASH_MAP=y
CONFIG_UART_CONSOLE=y
CONFIG_SPI=y
CONFIG_GPIO=y

View File

@@ -0,0 +1,6 @@
if BOARD_HELTEC_WIFI_LORA32_V4
config BOARD
default "heltec_wifi_lora32_v4"
endif # BOARD_HELTEC_WIFI_LORA32_V4

View File

@@ -0,0 +1,3 @@
config BOARD_HELTEC_WIFI_LORA32_V4
select SOC_ESP32S3_PROCPU if BOARD_HELTEC_WIFI_LORA32_V4_PROCPU
select SOC_ESP32S3_APPCPU if BOARD_HELTEC_WIFI_LORA32_V4_APPCPU

View File

@@ -0,0 +1,3 @@
# Heltec WiFi LoRa 32 V4 — board.cmake
# Uses esptool for flashing over USB-C (direct ESP32-S3 USB)
include(${ZEPHYR_BASE}/boards/espressif/common/board.cmake)

View File

@@ -0,0 +1,10 @@
board:
name: heltec_wifi_lora32_v4
full_name: "Heltec WiFi LoRa 32 V4"
vendor: heltec
url: https://heltec.org/project/wifi-lora-32-v4/
socs:
- name: esp32s3
variants:
- name: procpu
- name: appcpu

View File

@@ -0,0 +1,23 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* Heltec WiFi LoRa 32 V4 — pin control definitions
*/
#include <zephyr/dt-bindings/pinctrl/esp-pinctrl-common.h>
#include <dt-bindings/pinctrl/esp32s3-pinctrl.h>
#include <zephyr/dt-bindings/pinctrl/esp32s3-gpio-sigmap.h>
&pinctrl {
spim2_default: spim2_default {
group1 {
pinmux = <SPIM2_MISO_GPIO11>,
<SPIM2_SCLK_GPIO9>,
<SPIM2_CSEL_GPIO8>;
};
group2 {
pinmux = <SPIM2_MOSI_GPIO10>;
output-enable;
};
};
};

View File

@@ -0,0 +1,111 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* Heltec WiFi LoRa 32 V4 — Device Tree Source (PROCPU)
*
* Based on Heltec WiFi LoRa 32 V3 with the following differences:
* - No CP2102 USB-UART bridge; direct ESP32-S3 USB-C
* - Console routed to usb_serial instead of uart0
*/
/dts-v1/;
#include <espressif/esp32s3/esp32s3_fn8r2.dtsi>
#include "heltec_wifi_lora32_v4-pinctrl.dtsi"
#include <zephyr/dt-bindings/input/input-event-codes.h>
/ {
model = "Heltec WiFi LoRa 32 V4 PROCPU";
compatible = "heltec,wifi-lora-32-v4";
chosen {
zephyr,sram = &sram0;
zephyr,flash = &flash0;
zephyr,console = &usb_serial;
zephyr,shell-uart = &usb_serial;
zephyr,flash-controller = &flash;
};
aliases {
lora0 = &lora;
led0 = &led_white;
};
leds {
compatible = "gpio-leds";
led_white: led_0 {
/* V4 LED pin — verify from schematic */
gpios = <&gpio0 35 GPIO_ACTIVE_HIGH>;
label = "White LED";
};
};
buttons {
compatible = "gpio-keys";
boot_button: button_0 {
gpios = <&gpio0 0 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>;
label = "Boot Button";
zephyr,code = <INPUT_KEY_0>;
};
};
};
&usb_serial {
status = "okay";
};
&uart0 {
status = "disabled";
};
&spi2 {
status = "okay";
#address-cells = <1>;
#size-cells = <0>;
pinctrl-0 = <&spim2_default>;
pinctrl-names = "default";
cs-gpios = <&gpio0 8 GPIO_ACTIVE_LOW>;
lora: lora@0 {
compatible = "semtech,sx1262";
reg = <0>;
spi-max-frequency = <4000000>;
reset-gpios = <&gpio0 12 (GPIO_OPEN_DRAIN | GPIO_ACTIVE_LOW)>;
busy-gpios = <&gpio0 13 GPIO_ACTIVE_HIGH>;
dio1-gpios = <&gpio0 14 GPIO_ACTIVE_HIGH>;
dio2-tx-enable;
tcxo-power-startup-delay-ms = <5>;
};
};
&gpio0 {
status = "okay";
};
&gpio1 {
status = "okay";
};
&flash0 {
status = "okay";
partitions {
compatible = "fixed-partitions";
#address-cells = <1>;
#size-cells = <1>;
boot_partition: partition@0 {
label = "mcuboot";
reg = <0x00000000 0x00010000>;
};
slot0_partition: partition@10000 {
label = "image-0";
reg = <0x00010000 0x00300000>;
};
slot1_partition: partition@310000 {
label = "image-1";
reg = <0x00310000 0x00300000>;
};
storage_partition: partition@610000 {
label = "storage";
reg = <0x00610000 0x001f0000>;
};
};
};

View File

@@ -0,0 +1,7 @@
CONFIG_SOC_SERIES_ESP32S3=y
CONFIG_BOARD_HELTEC_WIFI_LORA32_V4=y
CONFIG_FLASH=y
CONFIG_FLASH_MAP=y
CONFIG_UART_CONSOLE=y
CONFIG_SPI=y
CONFIG_GPIO=y

10
boards/heltec_t114.conf Normal file
View File

@@ -0,0 +1,10 @@
# Heltec T114 (Mesh Node T114) — nRF52840 + SX1262
# Uses USB CDC-ACM
CONFIG_LORAMODEM_KISS_IFACE_USB=y
CONFIG_USB_DEVICE_STACK=y
CONFIG_USB_CDC_ACM=y
CONFIG_UART_LINE_CTRL=y
# SX1262 driver
CONFIG_LORA_SX12XX=y

View File

@@ -0,0 +1,17 @@
/*
* Heltec T114 (Mesh Node T114) — application DTS overlay
* KISS interface: USB CDC-ACM
* LoRa: SX1262 on SPI1 (defined in custom board DTS)
*/
/ {
chosen {
loramodem,kiss-uart = &cdc_acm_uart0;
};
};
&zephyr_udc0 {
cdc_acm_uart0: cdc_acm_uart0 {
compatible = "zephyr,cdc-acm-uart";
};
};

View File

@@ -0,0 +1,7 @@
# Heltec WiFi LoRa 32 V2 — ESP32 + SX1276
# Uses hardware UART over CP2102 USB-Serial bridge
CONFIG_LORAMODEM_KISS_IFACE_UART=y
# SX1276 driver (via loramac-node)
CONFIG_LORA_SX12XX=y

View File

@@ -0,0 +1,11 @@
/*
* Heltec WiFi LoRa 32 V2 — application DTS overlay
* KISS interface: UART0 (via CP2102 USB-Serial bridge)
* LoRa: SX1276 on SPI2 (already defined in upstream board DTS)
*/
/ {
chosen {
loramodem,kiss-uart = &uart0;
};
};

View File

@@ -0,0 +1,10 @@
# Heltec WiFi LoRa 32 V3 (procpu) — ESP32-S3 + SX1262
# Uses USB CDC-ACM (direct ESP32-S3 USB, no CP2102)
CONFIG_LORAMODEM_KISS_IFACE_USB=y
CONFIG_USB_DEVICE_STACK=y
CONFIG_USB_CDC_ACM=y
CONFIG_UART_LINE_CTRL=y
# SX1262 driver
CONFIG_LORA_SX12XX=y

View File

@@ -0,0 +1,17 @@
/*
* Heltec WiFi LoRa 32 V3 (procpu) — application DTS overlay
* KISS interface: USB CDC-ACM
* LoRa: SX1262 on SPI2 (already defined in upstream board DTS)
*/
/ {
chosen {
loramodem,kiss-uart = &cdc_acm_uart0;
};
};
&zephyr_udc0 {
cdc_acm_uart0: cdc_acm_uart0 {
compatible = "zephyr,cdc-acm-uart";
};
};

View File

@@ -0,0 +1,10 @@
# Heltec WiFi LoRa 32 V4 (procpu) — ESP32-S3 + SX1262
# Uses USB CDC-ACM (direct USB-C, no CP2102)
CONFIG_LORAMODEM_KISS_IFACE_USB=y
CONFIG_USB_DEVICE_STACK=y
CONFIG_USB_CDC_ACM=y
CONFIG_UART_LINE_CTRL=y
# SX1262 driver
CONFIG_LORA_SX12XX=y

View File

@@ -0,0 +1,17 @@
/*
* Heltec WiFi LoRa 32 V4 (procpu) — application DTS overlay
* KISS interface: USB CDC-ACM
* LoRa: SX1262 on SPI2 (defined in custom board DTS)
*/
/ {
chosen {
loramodem,kiss-uart = &cdc_acm_uart0;
};
};
&zephyr_udc0 {
cdc_acm_uart0: cdc_acm_uart0 {
compatible = "zephyr,cdc-acm-uart";
};
};

View File

@@ -0,0 +1,6 @@
if BOARD_LILYGO_TBEAM
config BOARD
default "lilygo_tbeam"
endif # BOARD_LILYGO_TBEAM

View File

@@ -0,0 +1,3 @@
config BOARD_LILYGO_TBEAM
select SOC_ESP32_PROCPU if BOARD_LILYGO_TBEAM_PROCPU
select SOC_ESP32_APPCPU if BOARD_LILYGO_TBEAM_APPCPU

View File

@@ -0,0 +1,3 @@
# LilyGo T-Beam — board.cmake
# Uses esptool for flashing over USB-to-serial (CP2104)
include(${ZEPHYR_BASE}/boards/espressif/common/board.cmake)

View File

@@ -0,0 +1,10 @@
board:
name: lilygo_tbeam
full_name: "LilyGo T-Beam"
vendor: lilygo
url: https://www.lilygo.cc/products/t-beam-v1-1
socs:
- name: esp32
variants:
- name: procpu
- name: appcpu

View File

@@ -0,0 +1,36 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* LilyGo T-Beam V1.1 — pin control definitions
* SoC: ESP32-D0WD
*/
#include <zephyr/dt-bindings/pinctrl/esp-pinctrl-common.h>
#include <dt-bindings/pinctrl/esp32-pinctrl.h>
#include <zephyr/dt-bindings/pinctrl/esp32-gpio-sigmap.h>
&pinctrl {
uart0_default: uart0_default {
group1 {
pinmux = <UART0_TX_GPIO1>;
output-enable;
};
group2 {
pinmux = <UART0_RX_GPIO3>;
bias-pull-up;
};
};
/* SPI3 (VSPI) for SX1276/SX1278 */
spim3_default: spim3_default {
group1 {
pinmux = <SPIM3_MISO_GPIO19>,
<SPIM3_SCLK_GPIO5>,
<SPIM3_CSEL_GPIO18>;
};
group2 {
pinmux = <SPIM3_MOSI_GPIO27>;
output-enable;
};
};
};

View File

@@ -0,0 +1,102 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* LilyGo T-Beam V1.1 — Device Tree Source (PROCPU)
*
* SoC: ESP32-D0WD
* Radio: SX1276 / SX1278 (LORA32 module)
* Flash: 4 MB
*
* LoRa: MOSI=27 MISO=19 SCK=5 NSS=18 RST=23 DIO0=26
* UART0: TX=1 RX=3 (via CP2104 USB-serial)
*/
/dts-v1/;
#include <espressif/esp32/esp32_d0wd_v3.dtsi>
#include "lilygo_tbeam-pinctrl.dtsi"
#include <zephyr/dt-bindings/input/input-event-codes.h>
/ {
model = "LilyGo T-Beam V1.1 PROCPU";
compatible = "lilygo,t-beam";
chosen {
zephyr,sram = &sram0;
zephyr,flash = &flash0;
zephyr,console = &uart0;
zephyr,shell-uart = &uart0;
zephyr,flash-controller = &flash;
};
aliases {
lora0 = &lora;
};
buttons {
compatible = "gpio-keys";
boot_button: button_0 {
gpios = <&gpio0 38 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>;
label = "Boot Button";
zephyr,code = <INPUT_KEY_0>;
};
};
};
&uart0 {
status = "okay";
current-speed = <115200>;
pinctrl-0 = <&uart0_default>;
pinctrl-names = "default";
};
&spi3 {
status = "okay";
#address-cells = <1>;
#size-cells = <0>;
pinctrl-0 = <&spim3_default>;
pinctrl-names = "default";
cs-gpios = <&gpio0 18 GPIO_ACTIVE_LOW>;
lora: lora@0 {
compatible = "semtech,sx1276";
reg = <0>;
spi-max-frequency = <8000000>;
reset-gpios = <&gpio0 23 GPIO_ACTIVE_LOW>;
dio1-gpios = <&gpio0 26 GPIO_ACTIVE_HIGH>;
power-amplifier-output = "pa-boost";
};
};
&gpio0 {
status = "okay";
};
&gpio1 {
status = "okay";
};
&flash0 {
status = "okay";
partitions {
compatible = "fixed-partitions";
#address-cells = <1>;
#size-cells = <1>;
boot_partition: partition@0 {
label = "mcuboot";
reg = <0x00000000 0x00010000>;
};
slot0_partition: partition@10000 {
label = "image-0";
reg = <0x00010000 0x00180000>;
};
slot1_partition: partition@190000 {
label = "image-1";
reg = <0x00190000 0x00180000>;
};
storage_partition: partition@310000 {
label = "storage";
reg = <0x00310000 0x000F0000>;
};
};
};

View File

@@ -0,0 +1,7 @@
CONFIG_SOC_SERIES_ESP32=y
CONFIG_BOARD_LILYGO_TBEAM=y
CONFIG_FLASH=y
CONFIG_FLASH_MAP=y
CONFIG_UART_CONSOLE=y
CONFIG_SPI=y
CONFIG_GPIO=y

View File

@@ -0,0 +1,6 @@
if BOARD_RAK_WISMESH_POCKET
config BOARD
default "rak_wismesh_pocket"
endif # BOARD_RAK_WISMESH_POCKET

View File

@@ -0,0 +1,2 @@
config BOARD_RAK_WISMESH_POCKET
select SOC_NRF52840_QIAA

View File

@@ -0,0 +1,9 @@
# RAK WisMesh Pocket — board.cmake
# Uses nrfjprog or pyocd for flashing via SWD.
# J-Link is the recommended debugger for RAK WisBlock-family boards.
board_runner_args(nrfjprog "--nrf-family=NRF52")
board_runner_args(jlink "--device=nRF52840_xxAA" "--speed=4000" "--reset-after-load")
board_runner_args(pyocd "--target=nrf52840")
include(${ZEPHYR_BASE}/boards/common/nrfjprog.board.cmake)
include(${ZEPHYR_BASE}/boards/common/jlink.board.cmake)
include(${ZEPHYR_BASE}/boards/common/pyocd.board.cmake)

View File

@@ -0,0 +1,7 @@
board:
name: rak_wismesh_pocket
full_name: "RAK WisMesh Pocket"
vendor: rak
url: https://docs.rakwireless.com/Product-Categories/WisMesh/
socs:
- name: nrf52840

View File

@@ -0,0 +1,47 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* RAK WisMesh Pocket — pin control definitions
* Matches RAK4631 GPIO assignments. Verify from WisMesh Pocket schematic.
*/
#include <nordic/nrf52840.dtsi>
#include <dt-bindings/pinctrl/nrf-pinctrl.h>
&pinctrl {
uart0_default: uart0_default {
group1 {
psels = <NRF_PSEL(UART_TX, 0, 19)>;
};
group2 {
psels = <NRF_PSEL(UART_RX, 0, 18)>;
bias-pull-up;
};
};
uart0_sleep: uart0_sleep {
group1 {
psels = <NRF_PSEL(UART_TX, 0, 19)>,
<NRF_PSEL(UART_RX, 0, 18)>;
low-power-enable;
};
};
/* SPI1 for SX1262 — RAK4631 pin assignments */
spi1_default: spi1_default {
group1 {
psels = <NRF_PSEL(SPIM_SCK, 1, 3)>,
<NRF_PSEL(SPIM_MOSI, 1, 2)>,
<NRF_PSEL(SPIM_MISO, 1, 1)>;
};
};
spi1_sleep: spi1_sleep {
group1 {
psels = <NRF_PSEL(SPIM_SCK, 1, 3)>,
<NRF_PSEL(SPIM_MOSI, 1, 2)>,
<NRF_PSEL(SPIM_MISO, 1, 1)>;
low-power-enable;
};
};
};

View File

@@ -0,0 +1,119 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* RAK WisMesh Pocket — Device Tree Source
* SoC: nRF52840 (same as RAK4631 WisBlock Core)
* Radio: SX1262
*
* The WisMesh Pocket is derived from the RAK4631 with a different
* form factor optimized for portable deployment. GPIO assignments
* are assumed identical to RAK4631; verify against the WisMesh
* Pocket schematic before using with real hardware.
*/
/dts-v1/;
#include <nordic/nrf52840_qiaa.dtsi>
#include "rak_wismesh_pocket-pinctrl.dtsi"
/ {
model = "RAK WisMesh Pocket";
compatible = "rak,wismesh-pocket";
chosen {
zephyr,sram = &sram0;
zephyr,flash = &flash0;
zephyr,code-partition = &slot0_partition;
};
aliases {
lora0 = &lora;
led0 = &led_green;
};
leds {
compatible = "gpio-leds";
led_green: led_0 {
/* RAK4631 green LED — verify for WisMesh Pocket */
gpios = <&gpio1 1 GPIO_ACTIVE_HIGH>;
label = "Green LED";
};
};
};
&uicr {
gpio-as-nreset;
};
&gpiote {
status = "okay";
};
&gpio0 {
status = "okay";
};
&gpio1 {
status = "okay";
};
&uart0 {
compatible = "nordic,nrf-uarte";
status = "okay";
current-speed = <115200>;
pinctrl-0 = <&uart0_default>;
pinctrl-names = "default";
};
/*
* SX1262 on SPI1
* GPIO assignments match RAK4631 — verify for WisMesh Pocket.
*/
&spi1 {
compatible = "nordic,nrf-spim";
status = "okay";
pinctrl-0 = <&spi1_default>;
pinctrl-names = "default";
cs-gpios = <&gpio1 10 GPIO_ACTIVE_LOW>;
lora: lora@0 {
compatible = "semtech,sx1262";
reg = <0>;
spi-max-frequency = <1000000>;
reset-gpios = <&gpio1 6 GPIO_ACTIVE_LOW>;
busy-gpios = <&gpio1 14 GPIO_ACTIVE_HIGH>;
dio1-gpios = <&gpio1 15 GPIO_ACTIVE_HIGH>;
dio2-tx-enable;
dio3-tcxo-voltage = <SX126X_DIO3_TCXO_3V3>;
tcxo-power-startup-delay-ms = <5>;
};
};
zephyr_udc0: &usbd {
compatible = "nordic,nrf-usbd";
status = "okay";
};
&flash0 {
partitions {
compatible = "fixed-partitions";
#address-cells = <1>;
#size-cells = <1>;
boot_partition: partition@0 {
label = "mcuboot";
reg = <0x00000000 0x0000C000>;
};
slot0_partition: partition@c000 {
label = "image-0";
reg = <0x0000C000 0x00067000>;
};
slot1_partition: partition@73000 {
label = "image-1";
reg = <0x00073000 0x00067000>;
};
storage_partition: partition@da000 {
label = "storage";
reg = <0x000da000 0x00006000>;
};
};
};

View File

@@ -0,0 +1,8 @@
CONFIG_SOC_NRF52840_QIAA=y
CONFIG_BOARD_RAK_WISMESH_POCKET=y
CONFIG_FLASH=y
CONFIG_FLASH_MAP=y
CONFIG_GPIO=y
CONFIG_SPI=y
CONFIG_USB_DEVICE_STACK=y
CONFIG_NRFX_POWER=y

View File

@@ -0,0 +1,6 @@
if BOARD_RAK_WISMESH_TAG
config BOARD
default "rak_wismesh_tag"
endif # BOARD_RAK_WISMESH_TAG

View File

@@ -0,0 +1,2 @@
config BOARD_RAK_WISMESH_TAG
select SOC_NRF52840_QIAA

View File

@@ -0,0 +1,9 @@
# RAK WisMesh Tag — board.cmake
# Uses nrfjprog or pyocd for flashing via SWD.
# J-Link is the recommended debugger for RAK WisBlock-family boards.
board_runner_args(nrfjprog "--nrf-family=NRF52")
board_runner_args(jlink "--device=nRF52840_xxAA" "--speed=4000" "--reset-after-load")
board_runner_args(pyocd "--target=nrf52840")
include(${ZEPHYR_BASE}/boards/common/nrfjprog.board.cmake)
include(${ZEPHYR_BASE}/boards/common/jlink.board.cmake)
include(${ZEPHYR_BASE}/boards/common/pyocd.board.cmake)

View File

@@ -0,0 +1,7 @@
board:
name: rak_wismesh_tag
full_name: "RAK WisMesh Tag"
vendor: rak
url: https://docs.rakwireless.com/Product-Categories/WisMesh/
socs:
- name: nrf52840

View File

@@ -0,0 +1,47 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* RAK WisMesh Tag — pin control definitions
* Matches RAK4631 GPIO assignments. Verify from WisMesh Tag schematic.
*/
#include <nordic/nrf52840.dtsi>
#include <dt-bindings/pinctrl/nrf-pinctrl.h>
&pinctrl {
uart0_default: uart0_default {
group1 {
psels = <NRF_PSEL(UART_TX, 0, 19)>;
};
group2 {
psels = <NRF_PSEL(UART_RX, 0, 18)>;
bias-pull-up;
};
};
uart0_sleep: uart0_sleep {
group1 {
psels = <NRF_PSEL(UART_TX, 0, 19)>,
<NRF_PSEL(UART_RX, 0, 18)>;
low-power-enable;
};
};
/* SPI1 for SX1262 — RAK4631 pin assignments */
spi1_default: spi1_default {
group1 {
psels = <NRF_PSEL(SPIM_SCK, 1, 3)>,
<NRF_PSEL(SPIM_MOSI, 1, 2)>,
<NRF_PSEL(SPIM_MISO, 1, 1)>;
};
};
spi1_sleep: spi1_sleep {
group1 {
psels = <NRF_PSEL(SPIM_SCK, 1, 3)>,
<NRF_PSEL(SPIM_MOSI, 1, 2)>,
<NRF_PSEL(SPIM_MISO, 1, 1)>;
low-power-enable;
};
};
};

View File

@@ -0,0 +1,119 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* RAK WisMesh Tag — Device Tree Source
* SoC: nRF52840 (same as RAK4631 WisBlock Core)
* Radio: SX1262
*
* The WisMesh Tag is derived from the RAK4631 with a different
* form factor. GPIO assignments are assumed identical to RAK4631;
* verify against the WisMesh Tag schematic before using with
* real hardware.
*/
/dts-v1/;
#include <nordic/nrf52840_qiaa.dtsi>
#include "rak_wismesh_tag-pinctrl.dtsi"
/ {
model = "RAK WisMesh Tag";
compatible = "rak,wismesh-tag";
chosen {
zephyr,sram = &sram0;
zephyr,flash = &flash0;
zephyr,code-partition = &slot0_partition;
};
aliases {
lora0 = &lora;
led0 = &led_green;
};
leds {
compatible = "gpio-leds";
led_green: led_0 {
/* RAK4631 green LED — verify for WisMesh Tag */
gpios = <&gpio1 1 GPIO_ACTIVE_HIGH>;
label = "Green LED";
};
};
};
&uicr {
gpio-as-nreset;
};
&gpiote {
status = "okay";
};
&gpio0 {
status = "okay";
};
&gpio1 {
status = "okay";
};
&uart0 {
compatible = "nordic,nrf-uarte";
status = "okay";
current-speed = <115200>;
pinctrl-0 = <&uart0_default>;
pinctrl-names = "default";
};
/*
* SX1262 on SPI1
* GPIO assignments match RAK4631 — verify for WisMesh Tag.
*/
&spi1 {
compatible = "nordic,nrf-spim";
status = "okay";
pinctrl-0 = <&spi1_default>;
pinctrl-names = "default";
cs-gpios = <&gpio1 10 GPIO_ACTIVE_LOW>;
lora: lora@0 {
compatible = "semtech,sx1262";
reg = <0>;
spi-max-frequency = <1000000>;
reset-gpios = <&gpio1 6 GPIO_ACTIVE_LOW>;
busy-gpios = <&gpio1 14 GPIO_ACTIVE_HIGH>;
dio1-gpios = <&gpio1 15 GPIO_ACTIVE_HIGH>;
dio2-tx-enable;
dio3-tcxo-voltage = <SX126X_DIO3_TCXO_3V3>;
tcxo-power-startup-delay-ms = <5>;
};
};
zephyr_udc0: &usbd {
compatible = "nordic,nrf-usbd";
status = "okay";
};
&flash0 {
partitions {
compatible = "fixed-partitions";
#address-cells = <1>;
#size-cells = <1>;
boot_partition: partition@0 {
label = "mcuboot";
reg = <0x00000000 0x0000C000>;
};
slot0_partition: partition@c000 {
label = "image-0";
reg = <0x0000C000 0x00067000>;
};
slot1_partition: partition@73000 {
label = "image-1";
reg = <0x00073000 0x00067000>;
};
storage_partition: partition@da000 {
label = "storage";
reg = <0x000da000 0x00006000>;
};
};
};

View File

@@ -0,0 +1,8 @@
CONFIG_SOC_NRF52840_QIAA=y
CONFIG_BOARD_RAK_WISMESH_TAG=y
CONFIG_FLASH=y
CONFIG_FLASH_MAP=y
CONFIG_GPIO=y
CONFIG_SPI=y
CONFIG_USB_DEVICE_STACK=y
CONFIG_NRFX_POWER=y

View File

@@ -0,0 +1,10 @@
# RAK WisBlock (RAK4631) — nRF52840 + SX1262
# Uses USB CDC-ACM
CONFIG_LORAMODEM_KISS_IFACE_USB=y
CONFIG_USB_DEVICE_STACK=y
CONFIG_USB_CDC_ACM=y
CONFIG_UART_LINE_CTRL=y
# SX1262 driver
CONFIG_LORA_SX12XX=y

View File

@@ -0,0 +1,17 @@
/*
* RAK WisBlock (RAK4631 / nRF52840) — application DTS overlay
* KISS interface: USB CDC-ACM
* LoRa: SX1262 on SPI1 (already defined in upstream board DTS)
*/
/ {
chosen {
loramodem,kiss-uart = &cdc_acm_uart0;
};
};
&zephyr_udc0 {
cdc_acm_uart0: cdc_acm_uart0 {
compatible = "zephyr,cdc-acm-uart";
};
};

View File

@@ -0,0 +1,10 @@
# RAK WisMesh Pocket — nRF52840 + SX1262
# Uses USB CDC-ACM
CONFIG_LORAMODEM_KISS_IFACE_USB=y
CONFIG_USB_DEVICE_STACK=y
CONFIG_USB_CDC_ACM=y
CONFIG_UART_LINE_CTRL=y
# SX1262 driver
CONFIG_LORA_SX12XX=y

View File

@@ -0,0 +1,17 @@
/*
* RAK WisMesh Pocket — application DTS overlay
* KISS interface: USB CDC-ACM
* LoRa: SX1262 on SPI1 (defined in custom board DTS)
*/
/ {
chosen {
loramodem,kiss-uart = &cdc_acm_uart0;
};
};
&zephyr_udc0 {
cdc_acm_uart0: cdc_acm_uart0 {
compatible = "zephyr,cdc-acm-uart";
};
};

View File

@@ -0,0 +1,10 @@
# RAK WisMesh Tag — nRF52840 + SX1262
# Uses USB CDC-ACM
CONFIG_LORAMODEM_KISS_IFACE_USB=y
CONFIG_USB_DEVICE_STACK=y
CONFIG_USB_CDC_ACM=y
CONFIG_UART_LINE_CTRL=y
# SX1262 driver
CONFIG_LORA_SX12XX=y

View File

@@ -0,0 +1,17 @@
/*
* RAK WisMesh Tag — application DTS overlay
* KISS interface: USB CDC-ACM
* LoRa: SX1262 on SPI1 (defined in custom board DTS)
*/
/ {
chosen {
loramodem,kiss-uart = &cdc_acm_uart0;
};
};
&zephyr_udc0 {
cdc_acm_uart0: cdc_acm_uart0 {
compatible = "zephyr,cdc-acm-uart";
};
};

View File

@@ -0,0 +1,6 @@
if BOARD_SEEED_WIO_TRACKER
config BOARD
default "seeed_wio_tracker"
endif # BOARD_SEEED_WIO_TRACKER

View File

@@ -0,0 +1,2 @@
config BOARD_SEEED_WIO_TRACKER
select SOC_NRF52840_QIAA

View File

@@ -0,0 +1,6 @@
# Seeed Wio Tracker 1110 — board.cmake
# Uses nrfjprog or pyocd for flashing via SWD.
board_runner_args(nrfjprog "--nrf-family=NRF52")
board_runner_args(pyocd "--target=nrf52840")
include(${ZEPHYR_BASE}/boards/common/nrfjprog.board.cmake)
include(${ZEPHYR_BASE}/boards/common/pyocd.board.cmake)

View File

@@ -0,0 +1,7 @@
board:
name: seeed_wio_tracker
full_name: "Seeed Wio Tracker 1110"
vendor: seeed
url: https://wiki.seeedstudio.com/wio_tracker_1110_intro/
socs:
- name: nrf52840

View File

@@ -0,0 +1,47 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* Seeed Wio Tracker 1110 — pin control definitions
* NOTE: GPIO numbers are placeholders. Verify from schematic.
*/
#include <nordic/nrf52840.dtsi>
#include <dt-bindings/pinctrl/nrf-pinctrl.h>
&pinctrl {
uart0_default: uart0_default {
group1 {
psels = <NRF_PSEL(UART_TX, 0, 25)>;
};
group2 {
psels = <NRF_PSEL(UART_RX, 0, 24)>;
bias-pull-up;
};
};
uart0_sleep: uart0_sleep {
group1 {
psels = <NRF_PSEL(UART_TX, 0, 25)>,
<NRF_PSEL(UART_RX, 0, 24)>;
low-power-enable;
};
};
/* SPI1 for LR1110 — verify pins from Wio Tracker 1110 schematic */
spi1_default: spi1_default {
group1 {
psels = <NRF_PSEL(SPIM_SCK, 0, 21)>,
<NRF_PSEL(SPIM_MOSI, 0, 22)>,
<NRF_PSEL(SPIM_MISO, 0, 23)>;
};
};
spi1_sleep: spi1_sleep {
group1 {
psels = <NRF_PSEL(SPIM_SCK, 0, 21)>,
<NRF_PSEL(SPIM_MOSI, 0, 22)>,
<NRF_PSEL(SPIM_MISO, 0, 23)>;
low-power-enable;
};
};
};

View File

@@ -0,0 +1,117 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* Seeed Wio Tracker 1110 — Device Tree Source
* SoC: nRF52840
* Radio: Semtech LR1110 (LoRa + GNSS multi-protocol transceiver)
*
* NOTE: GPIO pin numbers are placeholders. Verify against the
* Wio Tracker 1110 schematic before using with real hardware.
* The LR1110 SPI interface differs from SX126x: it uses 2-byte
* opcodes and requires the BUSY pin to be checked between transactions.
*/
/dts-v1/;
#include <nordic/nrf52840_qiaa.dtsi>
#include "seeed_wio_tracker-pinctrl.dtsi"
/ {
model = "Seeed Wio Tracker 1110";
compatible = "seeed,wio-tracker-1110";
chosen {
zephyr,sram = &sram0;
zephyr,flash = &flash0;
zephyr,code-partition = &slot0_partition;
};
aliases {
lora0 = &lora;
led0 = &led_green;
};
leds {
compatible = "gpio-leds";
led_green: led_0 {
/* TODO: verify LED GPIO from Wio Tracker 1110 schematic */
gpios = <&gpio0 13 GPIO_ACTIVE_LOW>;
label = "Green LED";
};
};
};
&uicr {
gpio-as-nreset;
};
&gpiote {
status = "okay";
};
&gpio0 {
status = "okay";
};
&gpio1 {
status = "okay";
};
&uart0 {
compatible = "nordic,nrf-uarte";
status = "okay";
current-speed = <115200>;
pinctrl-0 = <&uart0_default>;
pinctrl-names = "default";
};
/*
* LR1110 on SPI1
* Pin assignments are placeholders — must be verified from schematic.
* The LR1110 BUSY and EVENT (DIO9) pins are both required by the driver.
*/
&spi1 {
compatible = "nordic,nrf-spim";
status = "okay";
pinctrl-0 = <&spi1_default>;
pinctrl-names = "default";
cs-gpios = <&gpio0 24 GPIO_ACTIVE_LOW>;
lora: lora@0 {
compatible = "semtech,lr11xx";
reg = <0>;
spi-max-frequency = <4000000>;
reset-gpios = <&gpio0 25 GPIO_ACTIVE_LOW>;
busy-gpios = <&gpio0 26 GPIO_ACTIVE_HIGH>;
event-gpios = <&gpio0 27 GPIO_ACTIVE_HIGH>;
};
};
zephyr_udc0: &usbd {
compatible = "nordic,nrf-usbd";
status = "okay";
};
&flash0 {
partitions {
compatible = "fixed-partitions";
#address-cells = <1>;
#size-cells = <1>;
boot_partition: partition@0 {
label = "mcuboot";
reg = <0x00000000 0x0000C000>;
};
slot0_partition: partition@c000 {
label = "image-0";
reg = <0x0000C000 0x00067000>;
};
slot1_partition: partition@73000 {
label = "image-1";
reg = <0x00073000 0x00067000>;
};
storage_partition: partition@da000 {
label = "storage";
reg = <0x000da000 0x00006000>;
};
};
};

View File

@@ -0,0 +1,8 @@
CONFIG_SOC_NRF52840_QIAA=y
CONFIG_BOARD_SEEED_WIO_TRACKER=y
CONFIG_FLASH=y
CONFIG_FLASH_MAP=y
CONFIG_GPIO=y
CONFIG_SPI=y
CONFIG_USB_DEVICE_STACK=y
CONFIG_NRFX_POWER=y

View File

@@ -0,0 +1,10 @@
# Seeed Wio Tracker 1110 — nRF52840 + LR1110
# Uses USB CDC-ACM
CONFIG_LORAMODEM_KISS_IFACE_USB=y
CONFIG_USB_DEVICE_STACK=y
CONFIG_USB_CDC_ACM=y
CONFIG_UART_LINE_CTRL=y
# LR11xx driver (out-of-tree)
CONFIG_LORA_LR11XX=y

View File

@@ -0,0 +1,17 @@
/*
* Seeed Wio Tracker 1110 — application DTS overlay
* KISS interface: USB CDC-ACM
* LoRa: LR1110 on SPI1 (defined in custom board DTS)
*/
/ {
chosen {
loramodem,kiss-uart = &cdc_acm_uart0;
};
};
&zephyr_udc0 {
cdc_acm_uart0: cdc_acm_uart0 {
compatible = "zephyr,cdc-acm-uart";
};
};

View File

@@ -0,0 +1,6 @@
if BOARD_SENSECAP_T1000E
config BOARD
default "sensecap_t1000e"
endif # BOARD_SENSECAP_T1000E

View File

@@ -0,0 +1,2 @@
config BOARD_SENSECAP_T1000E
select SOC_NRF52840_QIAA

View File

@@ -0,0 +1,6 @@
# SenseCAP T1000-E — board.cmake
# Uses nrfjprog or pyocd for flashing via SWD.
board_runner_args(nrfjprog "--nrf-family=NRF52")
board_runner_args(pyocd "--target=nrf52840")
include(${ZEPHYR_BASE}/boards/common/nrfjprog.board.cmake)
include(${ZEPHYR_BASE}/boards/common/pyocd.board.cmake)

View File

@@ -0,0 +1,7 @@
board:
name: sensecap_t1000e
full_name: "SenseCAP T1000-E"
vendor: sensecap
url: https://www.seeedstudio.com/SenseCAP-Card-Tracker-T1000-E-p-5913.html
socs:
- name: nrf52840

View File

@@ -0,0 +1,47 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* SenseCAP T1000-E — pin control definitions
* NOTE: GPIO numbers are placeholders. Verify from schematic.
*/
#include <nordic/nrf52840.dtsi>
#include <dt-bindings/pinctrl/nrf-pinctrl.h>
&pinctrl {
uart0_default: uart0_default {
group1 {
psels = <NRF_PSEL(UART_TX, 0, 25)>;
};
group2 {
psels = <NRF_PSEL(UART_RX, 0, 24)>;
bias-pull-up;
};
};
uart0_sleep: uart0_sleep {
group1 {
psels = <NRF_PSEL(UART_TX, 0, 25)>,
<NRF_PSEL(UART_RX, 0, 24)>;
low-power-enable;
};
};
/* SPI1 for LR1110 — verify pins from T1000-E schematic */
spi1_default: spi1_default {
group1 {
psels = <NRF_PSEL(SPIM_SCK, 0, 17)>,
<NRF_PSEL(SPIM_MOSI, 0, 18)>,
<NRF_PSEL(SPIM_MISO, 0, 19)>;
};
};
spi1_sleep: spi1_sleep {
group1 {
psels = <NRF_PSEL(SPIM_SCK, 0, 17)>,
<NRF_PSEL(SPIM_MOSI, 0, 18)>,
<NRF_PSEL(SPIM_MISO, 0, 19)>;
low-power-enable;
};
};
};

View File

@@ -0,0 +1,116 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* SenseCAP T1000-E — Device Tree Source
* SoC: nRF52840
* Radio: Semtech LR1110 (LoRa + GNSS multi-protocol transceiver)
*
* NOTE: GPIO pin numbers are placeholders. Verify against the
* SenseCAP T1000-E schematic before using with real hardware.
* The T1000-E is a different product from the Seeed Wio Tracker 1110,
* despite sharing the same SoC and radio chip.
*/
/dts-v1/;
#include <nordic/nrf52840_qiaa.dtsi>
#include "sensecap_t1000e-pinctrl.dtsi"
/ {
model = "SenseCAP T1000-E";
compatible = "sensecap,t1000e";
chosen {
zephyr,sram = &sram0;
zephyr,flash = &flash0;
zephyr,code-partition = &slot0_partition;
};
aliases {
lora0 = &lora;
led0 = &led_green;
};
leds {
compatible = "gpio-leds";
led_green: led_0 {
/* TODO: verify LED GPIO from T1000-E schematic */
gpios = <&gpio0 14 GPIO_ACTIVE_LOW>;
label = "Green LED";
};
};
};
&uicr {
gpio-as-nreset;
};
&gpiote {
status = "okay";
};
&gpio0 {
status = "okay";
};
&gpio1 {
status = "okay";
};
&uart0 {
compatible = "nordic,nrf-uarte";
status = "okay";
current-speed = <115200>;
pinctrl-0 = <&uart0_default>;
pinctrl-names = "default";
};
/*
* LR1110 on SPI1
* Pin assignments are placeholders — must be verified from schematic.
*/
&spi1 {
compatible = "nordic,nrf-spim";
status = "okay";
pinctrl-0 = <&spi1_default>;
pinctrl-names = "default";
cs-gpios = <&gpio1 0 GPIO_ACTIVE_LOW>;
lora: lora@0 {
compatible = "semtech,lr11xx";
reg = <0>;
spi-max-frequency = <4000000>;
reset-gpios = <&gpio1 1 GPIO_ACTIVE_LOW>;
busy-gpios = <&gpio1 2 GPIO_ACTIVE_HIGH>;
event-gpios = <&gpio1 3 GPIO_ACTIVE_HIGH>;
};
};
zephyr_udc0: &usbd {
compatible = "nordic,nrf-usbd";
status = "okay";
};
&flash0 {
partitions {
compatible = "fixed-partitions";
#address-cells = <1>;
#size-cells = <1>;
boot_partition: partition@0 {
label = "mcuboot";
reg = <0x00000000 0x0000C000>;
};
slot0_partition: partition@c000 {
label = "image-0";
reg = <0x0000C000 0x00067000>;
};
slot1_partition: partition@73000 {
label = "image-1";
reg = <0x00073000 0x00067000>;
};
storage_partition: partition@da000 {
label = "storage";
reg = <0x000da000 0x00006000>;
};
};
};

View File

@@ -0,0 +1,8 @@
CONFIG_SOC_NRF52840_QIAA=y
CONFIG_BOARD_SENSECAP_T1000E=y
CONFIG_FLASH=y
CONFIG_FLASH_MAP=y
CONFIG_GPIO=y
CONFIG_SPI=y
CONFIG_USB_DEVICE_STACK=y
CONFIG_NRFX_POWER=y

View File

@@ -0,0 +1,10 @@
# SenseCAP T1000-E — nRF52840 + LR1110
# Uses USB CDC-ACM
CONFIG_LORAMODEM_KISS_IFACE_USB=y
CONFIG_USB_DEVICE_STACK=y
CONFIG_USB_CDC_ACM=y
CONFIG_UART_LINE_CTRL=y
# LR11xx driver (out-of-tree)
CONFIG_LORA_LR11XX=y

View File

@@ -0,0 +1,17 @@
/*
* SenseCAP T1000-E — application DTS overlay
* KISS interface: USB CDC-ACM
* LoRa: LR1110 on SPI1 (defined in custom board DTS)
*/
/ {
chosen {
loramodem,kiss-uart = &cdc_acm_uart0;
};
};
&zephyr_udc0 {
cdc_acm_uart0: cdc_acm_uart0 {
compatible = "zephyr,cdc-acm-uart";
};
};

View 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(.)

View 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.

View 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)

View 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 */

View 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);
}

View File

@@ -0,0 +1,46 @@
# Copyright (c) 2024 loramodem contributors
# SPDX-License-Identifier: Apache-2.0
description: |
Semtech LR1110/LR1120 multi-protocol radio transceiver.
This driver implements Zephyr's LoRa subsystem API for LoRa packet mode
only. LoRaWAN and GNSS capabilities of the LR11xx are not used.
The LR11xx uses a 2-byte SPI opcode protocol that differs from the
SX126x family: commands consist of a 2-byte opcode followed by
parameters. Responses are read in a separate transaction after
asserting NSS, beginning with STAT1 and STAT2 status bytes.
compatible: "semtech,lr11xx"
include: spi-device.yaml
properties:
reset-gpios:
type: phandle-array
required: true
description: |
GPIO connected to NRESET (active low). Drive low for >= 100 us
to reset the chip.
busy-gpios:
type: phandle-array
required: true
description: |
GPIO connected to the BUSY pin (active high).
The host must wait for BUSY to go low before starting a new
SPI transaction.
event-gpios:
type: phandle-array
required: true
description: |
GPIO connected to the DIO9 / EVENT pin (active high).
Used for RX packet-ready and TX-done interrupts.
lf-clk-gpios:
type: phandle-array
description: |
Optional: GPIO to enable/disable the LF clock output from LR11xx.
Some board designs use this to drive an external 32.768 kHz crystal.

18
west.yml Normal file
View File

@@ -0,0 +1,18 @@
manifest:
self:
path: loramodem
remotes:
- name: zephyrproject-rtos
url-base: https://github.com/zephyrproject-rtos
projects:
- name: zephyr
remote: zephyrproject-rtos
revision: v3.7.0
import:
name-allowlist:
- cmsis
- hal_nordic
- hal_espressif
- loramac-node

9
zephyr/module.yml Normal file
View File

@@ -0,0 +1,9 @@
name: loramodem
build:
cmake: .
kconfig: Kconfig
settings:
board_root: .
dts_root: .