wireless/lpwan: add SX1301 concentrator, board support and SX127X fixes - #19675
Open
JorgeGzm wants to merge 1 commit into
Open
wireless/lpwan: add SX1301 concentrator, board support and SX127X fixes#19675JorgeGzm wants to merge 1 commit into
JorgeGzm wants to merge 1 commit into
Conversation
JorgeGzm
requested review from
gustavonihei,
jerpelea,
linguini1,
masayuki2009,
pkarashchenko,
raiden00pl,
simbit18 and
xiaoxiang781216
as code owners
August 4, 2026 00:51
JorgeGzm
force-pushed
the
add_lora_gateway
branch
from
August 4, 2026 00:59
e913b7b to
f677fe3
Compare
xiaoxiang781216
previously approved these changes
Aug 4, 2026
JorgeGzm
force-pushed
the
add_lora_gateway
branch
from
August 4, 2026 13:05
f677fe3 to
771232f
Compare
acassis
previously approved these changes
Aug 4, 2026
cederom
reviewed
Aug 4, 2026
cederom
reviewed
Aug 4, 2026
cederom
previously approved these changes
Aug 4, 2026
cederom
left a comment
Contributor
There was a problem hiding this comment.
Thank you @JorgeGzm :-)
I would just put dedicated documentation page for lora / sx / etc and then reference it from a board page. We has too many details scattered across different board, would be nice to have all in one place and then just reference :-)
JorgeGzm
force-pushed
the
add_lora_gateway
branch
from
August 4, 2026 16:21
771232f to
e9141d6
Compare
cederom
reviewed
Aug 4, 2026
cederom
reviewed
Aug 4, 2026
Add a character driver for the Semtech SX1301, the baseband processor of
a LoRaWAN gateway, together with the two SX125x radio front ends that it
drives through its internal SPI bridge. The driver resets the chip,
loads the three MCU firmware images, runs the calibration pass and the
AGC handshake, and then exposes the receive FIFO and the transmitter
through read(), write() and a small set of ioctls. Eight channel plans
are provided, from AU915 to IN866, and the firmware images are const so
that they stay in flash instead of taking 24 KiB of RAM.
What an application sees is device independent. A concentrator has no
radio frequency, spreading factor or coding rate of its own, since those
belong to each packet, so the commands of the common wireless API have
nothing to act upon and the interface is instead a channel plan, a start
and stop, and the counter downlinks are scheduled against. That contract
lives in nuttx/wireless/lpwan/lora_gw.h with the commands allocated in the
common WLIOC_GW space rather than in a per chip block, and sx1301.h keeps
only the registration function and the board hooks, so a second
concentrator driver implements the same interface and the same application
drives it. The coding rate is the existing enum wlioc_lora_cr_e; the rest
of the packet keeps the layout and the values of the userspace HAL that
gateway software is written against elsewhere.
Two details of the reference HAL are easy to miss, and both were found on
hardware:
- The bridge that reaches the radios needs time between the chip select
pulse and the read back register. A Linux host gets that for free
from the cost of a spidev transaction; here the register writes are
fast enough that the radios always read back as zero, which looks
exactly like a dead board.
- The frame synchronisation registers come up configured for a private
network. A gateway that leaves them alone never detects a single
frame of a public LoRaWAN device.
Packets whose CRC failed, or that carried no CRC at all, are counted and
dropped rather than delivered, as the reference packet forwarder does,
and the timestamp of a received packet gets the correction of the
reference HAL so that a downlink lands inside the receive window of the
device.
Board support is added for the Nucleo F746ZG carrying a shield of the
LRWAN_GS_HF1 family on SPI4, with a lorawan_gw configuration that brings
up Ethernet, DHCP and DNS along with the concentrator.
The SX127X driver gains what an SX1276 needs to talk to such a gateway,
and two bugs found while making that work are fixed:
- The frequency of the synthesiser was computed by dividing by a
truncated step of 61 Hz, while a step is FXOSC/2**19, about 61.035 Hz.
The 0.06 per cent error puts a 915 MHz channel more than 500 kHz away
from where it was asked for, which is outside its bandwidth: the
frames were transmitted, but on the wrong channel and undecodable.
The conversion now scales first and divides afterwards.
- On STM32L0 the SYSCFG clock was never enabled, because the symbol
that guarded it does not exist in Kconfig for this chip. The mapping
of a pin to an EXTI line lives in SYSCFG_EXTICR, so every write to it
was dropped and the mapping stayed at its reset value: a GPIO
interrupt on any port other than port A silently never fired. On this
board that is DIO0 of the radio, so a transmission never completed and
the driver waited forever. The same pattern is present in the STM32C0
and STM32G0 clock setup and was left alone, as no board was available
to test them.
Beyond that, the high or low band of the front end is now selected from
the frequency rather than left at the reset value, and the LoRa sync word,
bandwidth and spreading factor became configurable, since the ioctls that
would set them at runtime are not implemented. On the B-L072Z-LRWAN1 the
TCXO that clocks the radio is now powered before the driver is registered,
the user button is corrected to PB2 because PC13 carries DIO3 of the radio
on this board, and two configurations are added for use as a LoRa
transmitter.
The gateway interface and the SX127X driver each get a page of their own
under Documentation, so that what is common to every board using them is
written once and the board pages only describe their wiring and refer to
it. Those references go through a label at the top of each page rather
than through its path, so that moving a document later does not break
them.
Tested with both boards: the concentrator starts, both radio PLLs lock,
and the join request of a LoRaWAN device is received with a valid CRC and
forwarded to a network server. With the fixes above, an SX1276 on the
B-L072Z-LRWAN1 transmits continuously and its frames arrive on the
expected channel of the gateway with a valid CRC and a signal to noise
ratio of about 8 dB. Two of those boards also talk to each other in both
directions with the sx127x example, which exercises the receive path and
its completion interrupt as well.
Signed-off-by: Jorge Guzman <jorge.gzm@gmail.com>
Assisted-by: Claude Code 4.8
JorgeGzm
force-pushed
the
add_lora_gateway
branch
from
August 4, 2026 19:36
e9141d6 to
e55adee
Compare
cederom
approved these changes
Aug 4, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a character driver for the Semtech SX1301, the baseband processor of a
LoRaWAN gateway, together with the two SX125x radio front ends that it drives
through its internal SPI bridge. The driver resets the chip, loads the three
MCU firmware images, runs the calibration pass and the AGC handshake, and then
exposes the receive FIFO and the transmitter through
read(),write()and asmall set of ioctls. Eight channel plans are provided, from AU915 to IN866, and
the firmware images are
constso that they stay in flash instead of taking24 KiB of RAM.
What an application sees is device independent. A concentrator has no radio
frequency, spreading factor or coding rate of its own, since those belong to
each packet, so the commands of the common wireless API have nothing to act
upon on such a device and the interface it needs is instead a channel plan, a
start and stop, and the counter downlinks are scheduled against. That contract
is
nuttx/wireless/lpwan/lora_gw.h, with the commands allocated in the commonWLIOC_GW_*space rather than in a per chip block, andsx1301.hkeeps onlysx1301_register()and the board hooks. A second concentrator driverimplements the same interface and the same application drives it; the coding
rate is the existing
enum wlioc_lora_cr_e, and the rest of the packet keepsthe layout and the values of the userspace HAL that gateway software is
written against elsewhere, so that such an application ports by replacing
lgw_receive()withread()andlgw_send()withwrite().Two details of the Semtech reference HAL are easy to miss, and both were found
on hardware:
and the read back register. A Linux host gets that for free from the cost of
a spidev transaction; here the register writes are fast enough that the
radios always read back as zero, which looks exactly like a dead board.
network. A gateway that leaves them alone never detects a single frame of a
public LoRaWAN device.
Board support is added for the Nucleo F746ZG carrying a shield of the
LRWAN_GS_HF1 family on SPI4, with a
lorawan_gwconfiguration that brings upEthernet, DHCP and DNS along with the concentrator.
The SX127X driver gains what an SX1276 needs to talk to such a gateway, and
three defects found while making that work are fixed:
SX127X_FSTEPdivided by a step truncated to 61 Hz, while astep is
FXOSC/2**19, about 61.035 Hz. The 0.06 per cent error puts a915 MHz channel more than 500 kHz away from the requested frequency, outside
its own bandwidth: frames were transmitted, but on the wrong channel and
undecodable by anything tuned correctly. Two NuttX radios still talked to
each other, since both were wrong by the same amount.
configured frequency instead of being left at the reset value.
sx127x_rx_watchdog()is only wired into the FSK andOOK receive path, but was compiled whenever receive support was on. A LoRa
only configuration therefore failed to build with the flags of the CI. This
is not hypothetical:
nrf52840-dk:sx127xis such a configuration and doesnot build on master today (see Testing).
On STM32L0 the SYSCFG clock was never enabled, because the symbol that guarded
it does not exist in Kconfig for this chip. The mapping of a pin to an EXTI
line lives in
SYSCFG_EXTICR, so every write to it was dropped and the mappingstayed at its reset value: a GPIO interrupt on any port other than port A
silently never fired.
nucleo-l073rzhas DIO0 of its radio on PA10 and worksby accident, which is probably why this went unnoticed; the B-L072Z-LRWAN1 has
it on PB4, so a transmission never completed and the driver waited forever.
The same pattern is present in the STM32C0 and STM32G0 clock setup and was left
alone, as no board was available to test them.
On the B-L072Z-LRWAN1 the TCXO that clocks the radio is now powered before the
driver is registered, the user button is corrected to PB2 because PC13 carries
DIO3 of the radio on this board, and two configurations are added for use as a
LoRa transmitter.
Impact
New driver, new board support, and one fix per existing area:
drivers/wireless/lpwan/sx1301: new, off by default (LPWAN_SX1301).drivers/wireless/lpwan/sx127x: the frequency and band fixes change thefrequency actually used on the air, in the direction of the requested one.
Anyone who compensated for the old error in an application will notice. The
three new Kconfig options keep the previous behaviour by default (sync word
0x12, 7.8 kHz bandwidth, spreading factor 7).
arch/arm/src/stm32l0: one clock enable. Every STM32L0 board gains workingGPIO interrupts outside port A; nothing that worked before stops working.
nucleo-f746zggains SPI4 and SPI5 pin definitions and theconcentrator glue;
b-l072z-lrwan1gains the TCXO line and the correctedbutton pin.
SX127X driver, under
components/drivers/character/wireless/lpwan, with thetwo board pages trimmed to their wiring and pointing at them. This is what
@cederom asked for in review: what is common to every board using a radio is
written once, in one place, and referenced from the boards.
Testing
Host: Ubuntu 24.04.4 LTS, x86_64, Linux 7.0.0-28-generic
Every build below uses the warning flags of the CI:
make EXTRAFLAGS="-Wno-cpp -Werror".Build
nucleo-f746zg:lorawan_gw(new)nucleo-f746zg:lorawan_gw+WIRELESS_LORA_PKT_FWDnucleo-f746zg:nshb-l072z-lrwan1:lorawan_tx(new)b-l072z-lrwan1:lorawan_beacon(new)b-l072z-lrwan1:sx127xnucleo-l073rz:sx127xnucleo-f091rc:sx127xnrf52840-dk:sx127xheltec_wifi_lora32:sx1276The
lorawan_gwconfiguration does not selectWIRELESS_LORA_PKT_FWD,because that symbol comes from the companion pull request of
nuttx-appsandwould make the defconfig of this repository diverge from what
refresh.shproduces against the master of the other one. The second line above is the
same configuration with the forwarder enabled from
menuconfig, which is whatthe documentation tells the user to do; the line can go into the defconfig once
the application is merged.
All of them build with no warning and no error. The last five are the other
users of the SX127X driver in the tree, built to show that the driver changes
do not regress them.
nrf52840-dk:sx127xdoes not build on master, which is what the unusedfunction fix addresses:
nxstylereports nothing on the changed files,refresh.shleaves everytouched configuration unchanged, and the documentation builds with
sphinx -W.Concentrator, on hardware
Nucleo F746ZG with a RisingHF RHF0M301 shield,
lorawan_gw:Receiving a LoRaWAN device
An end device based on an SX1276 sending its join request, AU915 first
sub-band:
Status 0x10 is a valid CRC, and 23 bytes is the exact length of a join
request. Forwarded to a network server over the internet with the forwarder of
the companion pull request in nuttx-apps:
Receiving an SX127X, which is what the SX127X fixes are for
B-L072Z-LRWAN1 running
lorawan_txand transmitting on 917.2 MHz withsx127x -m 0 -f 917200000 -t -p 0 -l 32.Before the frequency fix the frames arrived on the channel 600 kHz above the
requested one and never passed the CRC:
After it, on the requested channel and with a valid CRC:
Before the SYSCFG fix the same board sent one frame per reset and then blocked
forever waiting for the transmit interrupt; after it, 509 frames in 30 s. The
mapping register, read from the running firmware, confirms why:
Transmitting
The concentrator sends a packet with
lora tx 917200000 7 hello-from-gatewayand two B-L072Z-LRWAN1 boards listening on that frequency receive it:
Five transmissions, five receptions on each board.
The Docker based CI lists were not run locally; they are left to the CI of the
pull request.