Skip to content

wireless/lpwan: add SX1301 concentrator, board support and SX127X fixes - #19675

Open
JorgeGzm wants to merge 4 commits into
apache:masterfrom
JorgeGzm:add_lora_gateway
Open

wireless/lpwan: add SX1301 concentrator, board support and SX127X fixes#19675
JorgeGzm wants to merge 4 commits into
apache:masterfrom
JorgeGzm:add_lora_gateway

Conversation

@JorgeGzm

@JorgeGzm JorgeGzm commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Summary

nucleo-f746zg-lora-sx1301 b-l072z-lrwan1

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 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 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 common
WLIOC_GW_* space rather than in a per chip block, and sx1301.h keeps only
sx1301_register() and the board hooks. 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, and the rest of the packet keeps
the 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() with read() and lgw_send() with write().

Two details of the Semtech 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.

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
three defects found while making that work are fixed:

  • Frequency. SX127X_FSTEP divided by a step truncated to 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 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.
  • Band. The high or low frequency front end is now selected from the
    configured frequency instead of being left at the reset value.
  • Unused function. sx127x_rx_watchdog() is only wired into the FSK and
    OOK 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:sx127x is such a configuration and does
    not 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 mapping
stayed at its reset value: a GPIO interrupt on any port other than port A
silently never fired. nucleo-l073rz has DIO0 of its radio on PA10 and works
by 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 the
    frequency 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 working
    GPIO interrupts outside port A; nothing that worked before stops working.
  • Boards: nucleo-f746zg gains SPI4 and SPI5 pin definitions and the
    concentrator glue; b-l072z-lrwan1 gains the TCXO line and the corrected
    button pin.
  • Documentation: a page of its own for the gateway interface and one for the
    SX127X driver, under components/drivers/character/wireless/lpwan, with the
    two 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

  • arm-none-eabi-gcc 13.2.1 (Arm GNU Toolchain 13.2.rel1)
  • xtensa-esp32-elf-gcc from the NuttX SDK, for the ESP32 board

Every build below uses the warning flags of the CI:
make EXTRAFLAGS="-Wno-cpp -Werror".

Build

Configuration text data bss
nucleo-f746zg:lorawan_gw (new) 342246 1568 37296
nucleo-f746zg:lorawan_gw + WIRELESS_LORA_PKT_FWD 362588 1680 41520
nucleo-f746zg:nsh 140010 888 4952
b-l072z-lrwan1:lorawan_tx (new) 81500 1072 3416
b-l072z-lrwan1:lorawan_beacon (new) 88408 1072 3424
b-l072z-lrwan1:sx127x 82824 1072 3432
nucleo-l073rz:sx127x 80268 1072 3404
nucleo-f091rc:sx127x 80556 1072 3408
nrf52840-dk:sx127x 101048 1084 6044
heltec_wifi_lora32:sx1276 116324 21040 9096

The lorawan_gw configuration does not select WIRELESS_LORA_PKT_FWD,
because that symbol comes from the companion pull request of nuttx-apps and
would make the defconfig of this repository diverge from what refresh.sh
produces against the master of the other one. The second line above is the
same configuration with the forwarder enabled from menuconfig, which is what
the 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:sx127x does not build on master, which is what the unused
function fix addresses:

$ make -j EXTRAFLAGS="-Wno-cpp -Werror"
wireless/lpwan/sx127x/sx127x.c:1291:13: error: 'sx127x_rx_watchdog' defined but
not used [-Werror=unused-function]

nxstyle reports nothing on the changed files, refresh.sh leaves every
touched configuration unchanged, and the documentation builds with
sphinx -W.

Concentrator, on hardware

Nucleo F746ZG with a RisingHF RHF0M301 shield, lorawan_gw:

nsh> lora start
sx1301_reg_probe: SX1301 detected, version 0x67
sx1301_setup_radio: Radio A: SX125x version 0x21
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_start: Arbiter firmware version 1 (expected 1)
sx1301_agc_start: AGC running, radio map 0xf0
sx1301_start: Concentrator started, modems 0x0b

Receiving a LoRaWAN device

An end device based on an SX1276 sending its join request, AU915 first
sub-band:

sx1301_receive: RX chain 0 SF10 915200000 Hz snr 14.0 dB size 23 status 0x10
sx1301_receive: RX chain 3 SF10 915800000 Hz snr 13.7 dB size 23 status 0x10

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:

lora: forwarding to au1.cloud.thethings.network (up 1700, down 1700)
lora: forwarded 1 packet(s)
RX OK: 1   RX FWD: 1   PUSH: 4 sent, 3 acked   PULL: 19 sent, 18 acked

Receiving an SX127X, which is what the SX127X fixes are for

B-L072Z-LRWAN1 running lorawan_tx and transmitting on 917.2 MHz with
sx127x -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:

sx1301_receive: RX chain 5 SF7 917800000 Hz snr 0.2 dB size 32 status 0x11
sx1301_receive: RX chain 5 SF7 917800000 Hz snr -1.7 dB size 32 status 0x11

After it, on the requested channel and with a valid CRC:

sx1301_receive: RX chain 2 SF7 917200000 Hz snr 8.2 dB size 32 status 0x10
sx1301_receive: RX chain 2 SF7 917200000 Hz snr 8.5 dB size 32 status 0x10

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:

nsh> xd 0x4001000c 4        # SYSCFG_EXTICR2, holds EXTI line 4
0000: 00 00 00 00           # before: line 4 on port A, while DIO0 is PB4
0000: 01 00 00 00           # after:  line 4 on port B

Transmitting

The concentrator sends a packet with lora tx 917200000 7 hello-from-gateway
and two B-L072Z-LRWAN1 boards listening on that frequency receive it:

board A> Received:  SNR = 6  RSSI = -29  len = 18
board B> Received:  SNR = 7  RSSI = -30  len = 18
         0x68 0x65 0x6c 0x6c 0x6f 0x2d 0x66 0x72 0x6f 0x6d

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.

@github-actions github-actions Bot added Arch: arm Issues related to ARM (32-bit) architecture Area: Drivers Drivers issues Size: XL The size of the change in this PR is very large. Consider breaking down the PR into smaller pieces. Board: arm labels Aug 4, 2026
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown

MemBrowse Memory Report

No memory changes detected for:

xiaoxiang781216
xiaoxiang781216 previously approved these changes Aug 4, 2026
acassis
acassis previously approved these changes Aug 4, 2026
Comment thread Documentation/platforms/arm/stm32l0/boards/b-l072z-lrwan1/index.rst
Comment thread Documentation/platforms/arm/stm32f7/boards/nucleo-f746zg/index.rst
cederom
cederom previously approved these changes Aug 4, 2026

@cederom cederom left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
JorgeGzm dismissed stale reviews from cederom and acassis via e9141d6 August 4, 2026 16:21
Comment thread Documentation/platforms/arm/stm32f7/boards/nucleo-f746zg/index.rst
Comment thread Documentation/components/drivers/character/wireless/lpwan/lora_gw.rst Outdated
cederom
cederom previously approved these changes Aug 4, 2026

@cederom cederom left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the documentation updates @JorgeGzm :-)

cederom
cederom previously approved these changes Aug 4, 2026
Comment thread drivers/wireless/lpwan/sx1301/sx1301_region.c Outdated
Comment thread drivers/wireless/lpwan/sx1301/sx1301_region.c Outdated
Comment thread drivers/wireless/lpwan/Make.defs
Comment thread arch/arm/src/stm32l0/stm32l0_rcc.c Outdated

@raiden00pl raiden00pl left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please split it into more commits and don't mix multiple not related changes in one commit

@JorgeGzm
JorgeGzm force-pushed the add_lora_gateway branch 2 times, most recently from e8578d1 to 9d65dea Compare August 5, 2026 12:53
@linguini1

Copy link
Copy Markdown
Contributor

When you use AI to write your PR descriptions, please take some time to reduce down the size to what actually needs to be read. This is a massive wall of text.

@JorgeGzm JorgeGzm closed this Aug 5, 2026
@JorgeGzm JorgeGzm reopened this Aug 5, 2026
@acassis

acassis commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

When you use AI to write your PR descriptions, please take some time to reduce down the size to what actually needs to be read. This is a massive wall of text.

@JorgeGzm I think this comments in the Summary should be included in the Documentation page

STM32_STM32L0 does not select STM32_HAVE_SYSCFG, so STM32_SYSCFG does not
exist for this family and the clock of the peripheral is never enabled.  The
mapping of a pin to an EXTI line lives in SYSCFG_EXTICR, so every write to it
was dropped and a GPIO interrupt on any port other than port A never fired.

STM32_SYSCFG is default y, so no configuration changes.

Assisted-by: Claude Code 4.8
Signed-off-by: Jorge Guzman <jorge.gzm@gmail.com>
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>
…only build

- The frequency step was truncated to 61 Hz, while it is FXOSC/(2**19),
  about 61.035 Hz.  The error puts a 915 MHz channel more than 500 kHz away
  from the requested frequency, outside its own bandwidth.

- The low or high frequency front end was left at its reset value, so a board
  wired for 868 or 915 MHz neither transmitted nor received.

- sx127x_rx_watchdog() is only used by the FSK and OOK path but was compiled
  whenever receive support was on, so a LoRa only configuration failed to
  build with -Werror.  nrf52840-dk:sx127x is such a configuration.

Adds the sync word, the default bandwidth and the default spreading factor as
configuration options, all defaulting to the previous behaviour, and a page
for the driver under components/drivers.

Assisted-by: Claude Code 4.8
Signed-off-by: Jorge Guzman <jorge.gzm@gmail.com>
…gurations

The 32 MHz TCXO that clocks the SX1276 is powered from PA12 and was never
driven, so the radio had no clock at all.  The user button was copied from
the Nucleo L073RZ and left on PC13, which carries DIO3 of the radio on this
board; it is PB2.

Adds lorawan_tx and lorawan_beacon, with the radio defaults of a public
LoRaWAN network in the 915 MHz band, and fills in the board page.

Assisted-by: Claude Code 4.8
Signed-off-by: Jorge Guzman <jorge.gzm@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Arch: arm Issues related to ARM (32-bit) architecture Area: Drivers Drivers issues Board: arm Size: XL The size of the change in this PR is very large. Consider breaking down the PR into smaller pieces.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants