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-06 13:01:39 -03:00
committed by Alan C. Assis
parent 790a197ed0
commit f37bc4546e
27 changed files with 6112 additions and 2 deletions
@@ -5,5 +5,6 @@ LPWAN
.. toctree::
:maxdepth: 1
lora_gw.rst
sx126x.rst
@@ -0,0 +1,128 @@
.. _lora_gw:
================================
LoRa gateway (concentrator) API
================================
A LoRa gateway does not listen to one channel at a time as an end device
does: a concentrator demodulates several channels at once, on two radio
front-ends, and every packet arrives with its own frequency, spreading factor
and coding rate. The commands that configure a radio, ``WLIOC_SETRADIOFREQ``
and the ``WLIOC_LORA_x`` family, therefore have nothing to act upon on such a
device, and what it needs instead is a channel plan, a way to be started and
stopped, and the counter its timestamps are taken from.
This is the device independent interface those drivers implement, declared in
``nuttx/wireless/lpwan/lora_gw.h``. An application drives a gateway through it
and never names a chip.
Character device
================
A driver of this class registers a character device, ``/dev/lora0`` by
convention, on which:
* ``read`` returns whole multiples of ``struct lora_gw_rxpkt_s``, oldest
first, as many as fit in the buffer. It blocks until at least one packet is
available unless the file was opened with ``O_NONBLOCK``, and fails with
``EINVAL`` if the buffer cannot hold one whole packet.
* ``write`` takes exactly one ``struct lora_gw_txpkt_s``. The packet carries
its own frequency, power, modulation and, for a LoRaWAN downlink,
``invert_pol``. It is sent immediately with ``LORA_GW_TX_IMMEDIATE`` or at a
concentrator timestamp with ``LORA_GW_TX_TIMESTAMPED``.
Both structures follow the layout of 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:
.. list-table::
:header-rows: 1
* - Field
- Unit
* - ``rssi_dbm10``, ``snr_db10``
- Tenths of a dBm or dB, as integers, so that no floating point is
needed in a driver
* - ``datarate``
- The spreading factor as a plain number, 7 to 12, not a bit mask
* - ``bandwidth``
- ``LORA_GW_BW_125K``, ``_250K`` or ``_500K``
* - ``coderate``
- ``enum wlioc_lora_cr_e``, shared with the end device drivers
* - ``status``
- ``LORA_GW_STAT_CRC_OK``, ``_CRC_BAD`` or ``_NO_CRC``
A packet reported as ``LORA_GW_STAT_CRC_BAD`` must never be forwarded as if
it were valid: those are mostly correlator false triggers.
IOCTL commands
==============
See ``nuttx/wireless/ioctl.h`` : ``WLIOC_GW_x``.
* ``WLIOC_GW_START`` resets the chip, loads the firmware of its internal
MCUs, calibrates it and starts receiving on the selected channel plan.
``WLIOC_GW_STOP`` stops it and ``WLIOC_GW_RESET`` does both in sequence.
* ``WLIOC_GW_SETREGION`` selects a channel plan by name, for example
``"AU915"``, while the concentrator is stopped. ``WLIOC_GW_GETREGION``
takes a ``struct lora_gw_regionreq_s``: an ``index`` of -1 describes the
active plan, and 0 upwards enumerates the supported ones until ``ENODEV``.
The description that comes back lists the centre frequency of each radio
and the frequency, radio and type of every channel.
* ``WLIOC_GW_GETSTATUS`` fills a ``struct lora_gw_status_s`` with the state
of the concentrator and its counters, including the packets dropped
because their CRC failed.
* ``WLIOC_GW_GETTRIGCNT`` reads the internal counter of the concentrator, in
microseconds. This is the time base a LoRaWAN network server schedules
downlinks against.
Whether the units of the packet should instead follow the ones of the end
device commands, that is, bandwidth in Hz and levels scaled by a hundred as
in ``struct wlioc_rx_hdr_s``, is a question for the common LoRa API rather
than for one driver, and is left as it is until that API materialises.
SX1301 driver
=============
The Semtech SX1301 is the baseband processor of a LoRaWAN gateway: eight
multi-SF demodulators, one LoRa standard demodulator and one FSK
demodulator, driven by two SX125x radio front-ends that are reached through
an SPI bridge inside the SX1301 itself. It is the first implementation of the
interface above and is enabled with ``CONFIG_LPWAN_SX1301``.
Options
-------
* ``CONFIG_LPWAN_SX1301_SPIFREQ`` is the SPI clock, up to 10 MHz.
* ``CONFIG_LPWAN_SX1301_DEFAULT_REGION`` is the channel plan selected when
the driver is registered: one of AU915, AU915-1, US915, US915-1, EU868,
AS923, KR920 or IN866. AU915 and US915 default to the second sub-band,
which is what The Things Network and the Brazilian deployments use.
* ``CONFIG_LPWAN_SX1301_PRIVATE_NETWORK`` switches the frame synchronisation
word from the public LoRaWAN one to the private one.
* ``CONFIG_LPWAN_SX1301_RXBADCRC`` and ``CONFIG_LPWAN_SX1301_RXNOCRC``
deliver the packets that a gateway normally drops, which is useful when
bringing a shield up against an unknown transmitter.
Board implementation
--------------------
The driver is registered with ``sx1301_register``, which takes the device
path, an SPI bus and a ``struct sx1301_lower_s``. That structure carries the
two things the chip needs from the board: a ``reset`` hook driving its reset
line, and an optional ``band_select`` hook for the shields whose front-end
filters are switched between 868 and 915 MHz by a pair of GPIOs. See
``nuttx/wireless/lpwan/sx1301.h``.
A worked example, with the wiring of an LRWAN_GS_HF1 shield, the expected
boot output and a gateway forwarding to a public network, is in the
:ref:`Nucleo F746ZG <nucleo-f746zg>` page.
@@ -1,9 +1,14 @@
.. _nucleo-f746zg:
================
ST Nucleo F746ZG
================
.. tags:: chip:stm32, chip:stm32f7, chip:stm32f746
.. figure:: nucleo-f746zg.jpg
:align: center
This page discusses issues unique to NuttX configurations for the STMicro
Nucleo-144 board. See ST document STM32 Nucleo-144 boards (UM1974):
@@ -313,6 +318,40 @@ and connect it as follows::
CD PC11 CN11-2
LoRa Concentrator Shield
------------------------
The board supports a LoRa gateway shield of the LRWAN_GS_HF1 family, such as
the RisingHF RHF0M301, which carries a Semtech SX1301 baseband processor and
two SX1257 radio front ends. The shield is wired to SPI4 on the morpho
connector::
FUNCTION GPIO CONNECTOR
------------ ---- ---------
SPI4_SCK PE12 CN11-49
SPI4_MISO PE13 CN11-47
SPI4_MOSI PE14 CN11-45
SPI4_CS PE11 CN11-53
SX1301_RESET PE15 active high
BAND_SET1 PD15 D9, front end filter select
BAND_SET2 PE9 D6, front end filter select
------------ ---- ---------
The chip select is driven as a plain output rather than by the hardware NSS,
as the concentrator needs it held low for a whole burst. The two band
selection lines drive the filter bank of the shield: 915 MHz uses SET1 low
and SET2 high, 868 MHz the other way around.
With ``CONFIG_LPWAN_SX1301`` selected, the board registers the concentrator
at ``/dev/lora0``, behind the device independent gateway interface. That
interface, the configuration options of the driver and the channel plans it
supports are documented in :ref:`lora_gw`.
The same shields usually carry a serial NOR flash on SPI5 (PF7 SCK, PF8
MISO, PF9 MOSI, PF6 CS). The pins are defined in ``include/board.h`` and the
chip select is handled by the board, but no MTD driver is registered for it
yet.
Configurations
==============
@@ -421,3 +460,68 @@ NOTES:
CONFIG_HOST_LINUX=y : Builds under Linux
CONFIG_ARM_TOOLCHAIN_GNU_EABI=y : ARM GNU for Linux
lorawan_gw
----------
Turns the board into a LoRaWAN gateway: the SX1301 concentrator on SPI4 and
the Ethernet interface with DHCP and DNS. The console is the virtual COM port
on USART3.
Selecting ``CONFIG_WIRELESS_LORA_PKT_FWD`` adds the Semtech UDP packet
forwarder of ``apps/wireless/lora_pkt_fwd``, which is what turns the
concentrator into a gateway and provides the ``lora`` command used below.
.. figure:: nucleo-f746zg-lora-sx1301.png
:align: center
The board with an LRWAN_GS_HF1 shield mounted on the morpho headers.
Build and flash::
$ ./tools/configure.sh nucleo-f746zg:lorawan_gw
$ make
$ cp nuttx.bin /media/<user>/NODE_F746ZG/
The forwarder and the concentrator are driven by the ``lora`` command, which
mirrors the AT command set of the vendor gateway firmwares::
nsh> lora # list the subcommands
nsh> lora sys # identity, network and channel plan
nsh> lora ch # show the channel plan
nsh> lora ch EU868 # change region: AU915, AU915-1, US915,
# US915-1, EU868, AS923, KR920, IN866
nsh> lora server <host> [up] [down] # network server, name or address
nsh> lora start # start the concentrator and forward
nsh> lora status # counters of both sides
nsh> lora stop
nsh> lora tx 917200000 7 hello # transmit one packet, for bring-up
The last one exists to bring a gateway up without a network server: it
sends a single packet with the polarity of an uplink, so any LoRa receiver
tuned to the same frequency, spreading factor and 125 kHz bandwidth sees
it.
The default region is the second sub-band of AU915 (channels 8 to 15 plus the
500 kHz channel 65), which is what The Things Network and the Brazilian
deployments use; ``AU915-1`` selects the first sub-band instead. The default
server and the gateway identifier come from the configuration
(``CONFIG_LORA_PKT_FWD_SERVER`` and ``CONFIG_LORA_PKT_FWD_EUI``) and both can
be changed at runtime.
A working session looks like this::
nsh> lora start
sx1301_reg_probe: SX1301 detected, version 0x67
sx1301_setup_radio: Radio A: PLL locked at 917100000 Hz
sx1301_setup_radio: Radio B: PLL locked at 917900000 Hz
sx1301_calibrate: Calibration done, status 0xbf
sx1301_agc_start: AGC running, radio map 0xf0
sx1301_start: Concentrator started, modems 0x0b
lora: forwarding to au1.cloud.thethings.network (up 1700, down 1700)
sx1301_receive: RX chain 0 SF10 915200000 Hz snr 14.0 dB size 23 status 0x10
lora: forwarded 1 packet(s)
Note that the sync word has to match the devices: the driver configures the
concentrator for a public LoRaWAN network, and
``CONFIG_LPWAN_SX1301_PRIVATE_NETWORK`` switches it to a private one.
Binary file not shown.

After

Width:  |  Height:  |  Size: 519 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB