wireless/lpwan: add SX1301 LoRa concentrator driver

Character driver for the Semtech SX1301, the baseband processor of a LoRaWAN
gateway, and the two SX125x radios it drives.  Received packets come from
read(), downlinks go to write(), and the channel plan, the start and the stop
are ioctls.

The interface is device independent, in nuttx/wireless/lpwan/lora_gw.h with
the commands in the common WLIOC_GW_* space, so another concentrator driver
can implement it and the same application drive it.

Adds a lorawan_gw configuration for the Nucleo F746ZG with a shield of the
LRWAN_GS_HF1 family.  Off by default (LPWAN_SX1301).

Assisted-by: Claude Code 4.8
Signed-off-by: Jorge Guzman <jorge.gzm@gmail.com>
This commit is contained in:
Jorge Guzman 2026-08-05 13:35:50 -03:00 committed by Alan C. Assis
parent 790a197ed0
commit f37bc4546e
27 changed files with 6112 additions and 2 deletions

View file

@ -182,6 +182,46 @@
#define _WLIOC_OOK_COMMANDS 2 /* ! Must be corrected after changes to commands.
* ! Equal to the amount of commands above. */
/****************************************************************************
* LoRa gateway common IOCTL commands
****************************************************************************/
/* A LoRa gateway receives on several channels at once, so the frequency, the
* spreading factor and the coding rate belong to each packet rather than
* being settings of one radio: the commands above have nothing to act upon.
* What such a concentrator needs is a channel plan, a way to be started and
* stopped, and the counter its timestamps are taken from. The types these
* commands take are in include/nuttx/wireless/lpwan/lora_gw.h.
*/
/* Offsets. Must follow WLIOC_OOK */
#define _WLIOC_GW_OFFS _WLIOC_OOK_OFFS+_WLIOC_OOK_COMMANDS
#define _WLIOC_GW(x) _WLCIOC(_WLIOC_GW_OFFS+x)
/* Commands */
#define WLIOC_GW_START _WLIOC_GW(0) /* arg: none. Load the firmware, */
/* calibrate and start receiving */
#define WLIOC_GW_STOP _WLIOC_GW(1) /* arg: none */
#define WLIOC_GW_RESET _WLIOC_GW(2) /* arg: none. Stop then start */
#define WLIOC_GW_SETREGION _WLIOC_GW(3) /* arg: Pointer to a NUL */
/* terminated channel plan name */
#define WLIOC_GW_GETREGION _WLIOC_GW(4) /* arg: Pointer to */
/* struct lora_gw_regionreq_s */
#define WLIOC_GW_GETSTATUS _WLIOC_GW(5) /* arg: Pointer to */
/* struct lora_gw_status_s */
#define WLIOC_GW_GETTRIGCNT _WLIOC_GW(6) /* arg: Pointer to uint32_t, */
/* concentrator counter in us */
/* Number of commands */
#define _WLIOC_GW_COMMANDS 7 /* ! Must be corrected after changes to cmds.
* ! Equal to the amount of commands above. */
/****************************************************************************
* Device-specific IOCTL commands
****************************************************************************/
@ -191,9 +231,9 @@
* LoRa API. These commands are currently only used by the RN2XX3 driver.
*/
/* Offsets. Must follow WLIOC_OOK */
/* Offsets. Must follow WLIOC_GW */
#define _WLIOC_RN2XX3_OFFS _WLIOC_OOK_OFFS+_WLIOC_OOK_COMMANDS
#define _WLIOC_RN2XX3_OFFS _WLIOC_GW_OFFS+_WLIOC_GW_COMMANDS
#define _WLIOC_RN2XX3(x) _WLCIOC(_WLIOC_RN2XX3_OFFS+x)
/* Commands */

View file

@ -0,0 +1,195 @@
/****************************************************************************
* include/nuttx/wireless/lpwan/lora_gw.h
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
#ifndef __INCLUDE_NUTTX_WIRELESS_LPWAN_LORA_GW_H
#define __INCLUDE_NUTTX_WIRELESS_LPWAN_LORA_GW_H
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <nuttx/wireless/ioctl.h>
#include <stdint.h>
#include <stdbool.h>
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
/* Device independent interface of a LoRa gateway, that is, of a concentrator
* demodulating several channels at once instead of one channel at a time as
* an end device does. A driver of this class registers a character device
* where read() returns whole struct lora_gw_rxpkt_s records, write() takes
* one struct lora_gw_txpkt_s, and the channel plan and the state of the chip
* are reached with the WLIOC_GW_* commands of nuttx/wireless/ioctl.h.
*
* The layout below follows the userspace HAL that Semtech publishes for this
* family of chips, which is what gateway software is written against on
* other systems, so that such an application ports by replacing its
* lgw_receive() with read() and its lgw_send() with write(). Two things
* deliberately differ: the signal levels are integers scaled by ten instead
* of floats, and the spreading factor is the plain number, not a bit mask.
*
* Whether the units should instead follow the ones of the end device
* commands of nuttx/wireless/ioctl.h, that is, bandwidth in Hz and levels
* scaled by a hundred, is a question for the common LoRa API rather than for
* one driver, and is left as it is until that API materialises.
*/
#define LORA_GW_MULTI_NB 8 /* Multi-SF IF chains (IF0..IF7) */
#define LORA_GW_IF_CHAIN_NB 10 /* 8 multi-SF + LoRa standard + FSK */
#define LORA_GW_RF_CHAIN_NB 2 /* Radio A and radio B */
#define LORA_GW_MAX_PAYLOAD 256 /* Maximum PHY payload */
#define LORA_GW_REGION_NAMELEN 12
#define LORA_GW_REGION_DESCLEN 48
/* Bandwidth codes */
#define LORA_GW_BW_UNDEFINED 0x00
#define LORA_GW_BW_125K 0x04
#define LORA_GW_BW_250K 0x05
#define LORA_GW_BW_500K 0x06
/* Modulation codes */
#define LORA_GW_MOD_LORA 0x10
#define LORA_GW_MOD_FSK 0x20
/* Packet status. A concentrator distinguishes a packet whose CRC was
* checked and passed from one that failed and from one that carried no CRC
* at all. A gateway must never forward LORA_GW_STAT_CRC_BAD as if it were
* valid: those are mostly correlator false triggers.
*/
#define LORA_GW_STAT_UNDEFINED 0x00
#define LORA_GW_STAT_NO_CRC 0x01
#define LORA_GW_STAT_CRC_OK 0x10
#define LORA_GW_STAT_CRC_BAD 0x11
/* TX modes */
#define LORA_GW_TX_IMMEDIATE 0
#define LORA_GW_TX_TIMESTAMPED 1
/* Channel types reported by WLIOC_GW_GETREGION */
#define LORA_GW_CHAN_OFF 0
#define LORA_GW_CHAN_MULTI_SF 1 /* One of IF0..IF7, SF7..SF12 */
#define LORA_GW_CHAN_STD 2 /* IF8, single SF/BW (LoRa standard) */
#define LORA_GW_CHAN_FSK 3 /* IF9 */
/****************************************************************************
* Public Types
****************************************************************************/
/* A packet received by the concentrator. read() returns whole multiples of
* this structure, newest last.
*
* RSSI and SNR are scaled integers (tenths of a dBm/dB) so that no floating
* point is needed in the driver.
*/
struct lora_gw_rxpkt_s
{
uint32_t freq_hz; /* Absolute frequency of the receiving channel */
uint32_t count_us; /* Concentrator timestamp, corrected as per the
* reference HAL */
int16_t rssi_dbm10; /* RSSI in 0.1 dBm units */
int16_t snr_db10; /* Signal to noise ratio in 0.1 dB units */
uint16_t size; /* Payload size in bytes */
uint8_t if_chain; /* Demodulator index 0..9 */
uint8_t rf_chain; /* Radio 0 (A) or 1 (B) */
uint8_t status; /* One of LORA_GW_STAT_* */
uint8_t modulation; /* LORA_GW_MOD_* */
uint8_t bandwidth; /* LORA_GW_BW_* */
uint8_t datarate; /* Spreading factor, 7..12 */
uint8_t coderate; /* enum wlioc_lora_cr_e */
uint8_t payload[LORA_GW_MAX_PAYLOAD];
};
/* A packet to transmit. write() takes exactly one of these. */
struct lora_gw_txpkt_s
{
uint32_t freq_hz;
uint32_t count_us; /* On-air time for LORA_GW_TX_TIMESTAMPED */
uint16_t size;
uint16_t preamble; /* 0 selects the LoRaWAN default of 8 symbols */
uint8_t tx_mode; /* LORA_GW_TX_IMMEDIATE or _TIMESTAMPED */
int8_t rf_power; /* Requested antenna power in dBm */
uint8_t rf_chain;
uint8_t modulation;
uint8_t bandwidth;
uint8_t datarate;
uint8_t coderate; /* enum wlioc_lora_cr_e */
bool invert_pol; /* True for LoRaWAN downlinks */
bool no_crc;
bool no_header;
uint8_t payload[LORA_GW_MAX_PAYLOAD];
};
/* Concentrator counters and state */
struct lora_gw_status_s
{
bool started;
uint32_t rx_ok; /* Packets with a valid CRC */
uint32_t rx_bad; /* Packets dropped because the CRC failed */
uint32_t rx_nocrc; /* Packets received without CRC */
uint32_t rx_err; /* FIFO read errors */
uint32_t tx_ok;
uint32_t tx_err;
};
/* One entry of the channel plan of a region */
struct lora_gw_chaninfo_s
{
uint32_t freq_hz;
uint8_t rf_chain;
uint8_t type; /* LORA_GW_CHAN_* */
uint8_t bandwidth; /* LORA_GW_BW_* */
uint8_t datarate; /* SF for LORA_GW_CHAN_STD, 0 for multi-SF */
bool enable;
};
/* A complete region description */
struct lora_gw_regioninfo_s
{
char name[LORA_GW_REGION_NAMELEN];
char desc[LORA_GW_REGION_DESCLEN];
uint32_t radio_freq[LORA_GW_RF_CHAIN_NB];
struct lora_gw_chaninfo_s channels[LORA_GW_IF_CHAIN_NB];
};
/* Argument of WLIOC_GW_GETREGION */
struct lora_gw_regionreq_s
{
int index; /* -1: active region, else 0..n */
struct lora_gw_regioninfo_s info; /* Returned description */
};
#endif /* __INCLUDE_NUTTX_WIRELESS_LPWAN_LORA_GW_H */

View file

@ -0,0 +1,108 @@
/****************************************************************************
* include/nuttx/wireless/lpwan/sx1301.h
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
#ifndef __INCLUDE_NUTTX_WIRELESS_LPWAN_SX1301_H
#define __INCLUDE_NUTTX_WIRELESS_LPWAN_SX1301_H
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <nuttx/spi/spi.h>
#include <nuttx/wireless/lpwan/lora_gw.h>
#include <stdbool.h>
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
/* The SX1301 is a LoRa concentrator (gateway baseband processor) with 8
* multi-SF demodulators, one LoRa standard demodulator (IF8) and one FSK
* demodulator (IF9), driven by two SX125x radio front-ends accessed
* indirectly through the SX1301 internal SPI bridge.
*
* Only the board glue is here: everything an application deals with is the
* gateway interface of nuttx/wireless/lpwan/lora_gw.h.
*/
/****************************************************************************
* Public Types
****************************************************************************/
/* Board specific hooks. The SX1301 needs a reset line and, on the
* LRWAN_GS_HF1 class of shields, a pair of GPIOs selecting the RF band of
* the front-end filters.
*/
struct sx1301_lower_s
{
/* Drive the reset line. 'assert' true holds the chip in reset. */
CODE void (*reset)(FAR const struct sx1301_lower_s *lower, bool assert);
/* Select the RF band of the shield, in MHz (868 or 915). May be NULL on
* boards with a single band.
*/
CODE void (*band_select)(FAR const struct sx1301_lower_s *lower,
int band_mhz);
};
/****************************************************************************
* Public Function Prototypes
****************************************************************************/
#ifdef __cplusplus
#define EXTERN extern "C"
extern "C"
{
#else
#define EXTERN extern
#endif
/****************************************************************************
* Name: sx1301_register
*
* Description:
* Register the SX1301 concentrator character driver.
*
* Input Parameters:
* devpath - The full path to the driver to register, e.g. "/dev/lora0"
* spi - An instance of the SPI interface wired to the SX1301
* lower - Board specific reset and band selection hooks
*
* Returned Value:
* Zero on success; a negated errno value on failure.
*
****************************************************************************/
int sx1301_register(FAR const char *devpath, FAR struct spi_dev_s *spi,
FAR const struct sx1301_lower_s *lower);
#undef EXTERN
#ifdef __cplusplus
}
#endif
#endif /* __INCLUDE_NUTTX_WIRELESS_LPWAN_SX1301_H */