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

@ -5,5 +5,6 @@ LPWAN
.. toctree::
:maxdepth: 1
lora_gw.rst
sx126x.rst

View file

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

View file

@ -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

View file

@ -0,0 +1,85 @@
#
# This file is autogenerated: PLEASE DO NOT EDIT IT.
#
# You can use "make menuconfig" to make any modifications to the installed .config file.
# You can then do "make savedefconfig" to generate a new defconfig file that includes your
# modifications.
#
# CONFIG_ARCH_FPU is not set
CONFIG_ARCH="arm"
CONFIG_ARCH_BOARD="nucleo-f746zg"
CONFIG_ARCH_BOARD_NUCLEO_F746ZG=y
CONFIG_ARCH_CHIP="stm32f7"
CONFIG_ARCH_CHIP_STM32=y
CONFIG_ARCH_CHIP_STM32F746ZG=y
CONFIG_ARCH_CHIP_STM32F7=y
CONFIG_ARCH_STACKDUMP=y
CONFIG_ARMV7M_DCACHE=y
CONFIG_ARMV7M_DCACHE_WRITETHROUGH=y
CONFIG_ARMV7M_DTCM=y
CONFIG_ARMV7M_ICACHE=y
CONFIG_BOARD_LOOPSPERMSEC=43103
CONFIG_BUILTIN=y
CONFIG_DEBUG_ASSERTIONS=y
CONFIG_DEBUG_FEATURES=y
CONFIG_DEBUG_SYMBOLS=y
CONFIG_DEBUG_WIRELESS=y
CONFIG_DEBUG_WIRELESS_ERROR=y
CONFIG_DEBUG_WIRELESS_INFO=y
CONFIG_DEBUG_WIRELESS_WARN=y
CONFIG_DEFAULT_TASK_STACKSIZE=4096
CONFIG_DRIVERS_LPWAN=y
CONFIG_DRIVERS_WIRELESS=y
CONFIG_ETH0_PHY_LAN8742A=y
CONFIG_FS_PROCFS=y
CONFIG_INIT_ENTRYPOINT="nsh_main"
CONFIG_LINE_MAX=64
CONFIG_LPWAN_SX1301=y
CONFIG_MM_REGIONS=2
CONFIG_NET=y
CONFIG_NETDB_DNSCLIENT=y
CONFIG_NETINIT_DHCPC=y
CONFIG_NETINIT_THREAD=y
CONFIG_NETUTILS_DHCPC=y
CONFIG_NET_BROADCAST=y
CONFIG_NET_ETH_PKTSIZE=1500
CONFIG_NET_ICMP_SOCKET=y
CONFIG_NET_LOOPBACK=y
CONFIG_NET_STATISTICS=y
CONFIG_NET_TCP=y
CONFIG_NET_UDP=y
CONFIG_NET_UDP_CHECKSUMS=y
CONFIG_NSH_BUILTIN_APPS=y
CONFIG_NSH_FILEIOSIZE=512
CONFIG_NSH_READLINE=y
CONFIG_NUCLEO_F746ZG_CONSOLE_VIRTUAL=y
CONFIG_PREALLOC_TIMERS=4
CONFIG_RAM_SIZE=245760
CONFIG_RAM_START=0x20010000
CONFIG_RAW_BINARY=y
CONFIG_RR_INTERVAL=200
CONFIG_SCHED_HPWORK=y
CONFIG_SCHED_LPWORK=y
CONFIG_SCHED_WAITPID=y
CONFIG_STACK_COLORATION=y
CONFIG_START_DAY=30
CONFIG_START_MONTH=7
CONFIG_START_YEAR=2026
CONFIG_STM32_DTCMEXCLUDE=y
CONFIG_STM32_ETHMAC=y
CONFIG_STM32_PHYADDR=0
CONFIG_STM32_PHYSR=31
CONFIG_STM32_PHYSR_100FD=0x0018
CONFIG_STM32_PHYSR_100HD=0x0008
CONFIG_STM32_PHYSR_10FD=0x0014
CONFIG_STM32_PHYSR_10HD=0x0004
CONFIG_STM32_PHYSR_ALTCONFIG=y
CONFIG_STM32_PHYSR_ALTMODE=0x001c
CONFIG_STM32_SERIALBRK_BSDCOMPAT=y
CONFIG_STM32_SERIAL_DISABLE_REORDERING=y
CONFIG_STM32_SPI4=y
CONFIG_STM32_USART_BREAKS=y
CONFIG_SYSTEM_NSH=y
CONFIG_SYSTEM_PING=y
CONFIG_TASK_NAME_SIZE=24
CONFIG_USART3_SERIAL_CONSOLE=y

View file

@ -483,6 +483,49 @@
#define GPIO_SPI3_MOSI (GPIO_SPI3_MOSI_2|GPIO_SPEED_50MHz)
#define GPIO_SPI3_SCK (GPIO_SPI3_SCK_1|GPIO_SPEED_50MHz)
/* SPI4 is wired to the LoRa concentrator of the LRWAN_GS_HF1 class of
* shields, on the Morpho connector:
*
* PE12 SPI4_SCK CN11-49
* PE13 SPI4_MISO CN11-47
* PE14 SPI4_MOSI CN11-45
* PE11 SPI4_CS CN11-53, driven as a plain output
*/
#define GPIO_SPI4_MISO (GPIO_SPI4_MISO_2|GPIO_SPEED_50MHz)
#define GPIO_SPI4_MOSI (GPIO_SPI4_MOSI_2|GPIO_SPEED_50MHz)
#define GPIO_SPI4_SCK (GPIO_SPI4_SCK_2|GPIO_SPEED_50MHz)
#define GPIO_SPI4_CS0 (GPIO_OUTPUT|GPIO_PUSHPULL|GPIO_SPEED_50MHz| \
GPIO_OUTPUT_SET|GPIO_PORTE|GPIO_PIN11)
/* SPI5 is wired to the serial NOR flash of the same shields, on CN11:
*
* PF7 SPI5_SCK CN11-11
* PF8 SPI5_MISO CN11-54
* PF9 SPI5_MOSI CN11-56
* PF6 SPI5_CS CN11-9, driven as a plain output
*/
#define GPIO_SPI5_MISO (GPIO_SPI5_MISO_1|GPIO_SPEED_50MHz)
#define GPIO_SPI5_MOSI (GPIO_SPI5_MOSI_1|GPIO_SPEED_50MHz)
#define GPIO_SPI5_SCK (GPIO_SPI5_SCK_1|GPIO_SPEED_50MHz)
#define GPIO_SPI5_CS0 (GPIO_OUTPUT|GPIO_PUSHPULL|GPIO_SPEED_50MHz| \
GPIO_OUTPUT_SET|GPIO_PORTF|GPIO_PIN6)
/* LoRa concentrator control lines of the shield. The reset line is active
* high, and the two band selection lines drive the front-end filter bank:
* 915 MHz needs SET1 low and SET2 high, 868 MHz the other way around.
*/
#define GPIO_SX1301_RESET (GPIO_OUTPUT|GPIO_PUSHPULL|GPIO_SPEED_2MHz| \
GPIO_OUTPUT_SET|GPIO_PORTE|GPIO_PIN15)
#define GPIO_SX1301_BAND1 (GPIO_OUTPUT|GPIO_PUSHPULL|GPIO_SPEED_2MHz| \
GPIO_OUTPUT_CLEAR|GPIO_PORTD|GPIO_PIN15)
#define GPIO_SX1301_BAND2 (GPIO_OUTPUT|GPIO_PUSHPULL|GPIO_SPEED_2MHz| \
GPIO_OUTPUT_CLEAR|GPIO_PORTE|GPIO_PIN9)
/* I2C
*
*

View file

@ -68,6 +68,10 @@ if(CONFIG_USBDEV_COMPOSITE)
list(APPEND SRCS stm32_composite.c)
endif()
if(CONFIG_LPWAN_SX1301)
list(APPEND SRCS stm32_sx1301.c)
endif()
target_sources(board PRIVATE ${SRCS})
set_property(GLOBAL PROPERTY LD_SCRIPT "${NUTTX_BOARD_DIR}/scripts/flash.ld")

View file

@ -70,6 +70,10 @@ ifeq ($(CONFIG_USBDEV_COMPOSITE),y)
CSRCS += stm32_composite.c
endif
ifeq ($(CONFIG_LPWAN_SX1301),y)
CSRCS += stm32_sx1301.c
endif
DEPPATH += --dep-path board
VPATH += :board
CFLAGS += ${INCDIR_PREFIX}$(TOPDIR)$(DELIM)arch$(DELIM)$(CONFIG_ARCH)$(DELIM)src$(DELIM)board$(DELIM)board

View file

@ -329,4 +329,17 @@ int stm32_gpio_initialize(void);
#endif
#endif /* __ASSEMBLY__ */
/****************************************************************************
* Name: stm32_sx1301_initialize
*
* Description:
* Initialise SPI4 and register the SX1301 LoRa concentrator driver at
* 'devpath'.
*
****************************************************************************/
#ifdef CONFIG_LPWAN_SX1301
int stm32_sx1301_initialize(FAR const char *devpath);
#endif
#endif /* __BOARDS_ARM_STM32F7_NUCLEO_F746ZG_SRC_NUCLEO_F746ZG_H */

View file

@ -56,6 +56,10 @@
# include "stm32_spitest.h"
#endif
#ifdef CONFIG_LPWAN_SX1301
# include <nuttx/wireless/lpwan/sx1301.h>
#endif
#ifdef CONFIG_SYSTEMTICK_HOOK
# include <semaphore.h>
#endif
@ -290,6 +294,16 @@ int stm32_bringup(void)
#endif
UNUSED(ret);
#ifdef CONFIG_LPWAN_SX1301
/* Register the LoRa concentrator of the gateway shield */
ret = stm32_sx1301_initialize("/dev/lora0");
if (ret < 0)
{
syslog(LOG_ERR, "ERROR: stm32_sx1301_initialize failed: %d\n", ret);
}
#endif
return OK;
}

View file

@ -131,6 +131,58 @@ static const uint32_t g_spi3gpio[] =
};
#endif
#if defined(CONFIG_STM32_SPI4)
static const uint32_t g_spi4gpio[] =
{
# if defined(GPIO_SPI4_CS0)
GPIO_SPI4_CS0,
# else
0,
# endif
# if defined(GPIO_SPI4_CS1)
GPIO_SPI4_CS1,
# else
0,
# endif
# if defined(GPIO_SPI4_CS2)
GPIO_SPI4_CS2,
# else
0,
# endif
# if defined(GPIO_SPI4_CS3)
GPIO_SPI4_CS3
# else
0
# endif
};
#endif
#if defined(CONFIG_STM32_SPI5)
static const uint32_t g_spi5gpio[] =
{
# if defined(GPIO_SPI5_CS0)
GPIO_SPI5_CS0,
# else
0,
# endif
# if defined(GPIO_SPI5_CS1)
GPIO_SPI5_CS1,
# else
0,
# endif
# if defined(GPIO_SPI5_CS2)
GPIO_SPI5_CS2,
# else
0,
# endif
# if defined(GPIO_SPI5_CS3)
GPIO_SPI5_CS3
# else
0
# endif
};
#endif
/****************************************************************************
* Public Functions
****************************************************************************/
@ -177,6 +229,26 @@ void weak_function stm32_spidev_initialize(void)
}
}
#endif
#if defined(CONFIG_STM32_SPI4)
for (int i = 0; i < nitems(g_spi4gpio); i++)
{
if (g_spi4gpio[i] != 0)
{
stm32_configgpio(g_spi4gpio[i]);
}
}
#endif
#if defined(CONFIG_STM32_SPI5)
for (int i = 0; i < nitems(g_spi5gpio); i++)
{
if (g_spi5gpio[i] != 0)
{
stm32_configgpio(g_spi5gpio[i]);
}
}
#endif
}
/****************************************************************************
@ -273,8 +345,17 @@ uint8_t stm32_spi3status(struct spi_dev_s *dev, uint32_t devid)
void stm32_spi4select(struct spi_dev_s *dev,
uint32_t devid, bool selected)
{
uint32_t index = SPIDEVID_INDEX(devid);
spiinfo("devid: %d CS: %s\n",
(int)devid, selected ? "assert" : "de-assert");
/* The chip select of the LoRa concentrator is a plain output, active low */
if (index < nitems(g_spi4gpio) && g_spi4gpio[index] != 0)
{
stm32_gpiowrite(g_spi4gpio[index], !selected);
}
}
uint8_t stm32_spi4status(struct spi_dev_s *dev, uint32_t devid)
@ -287,8 +368,17 @@ uint8_t stm32_spi4status(struct spi_dev_s *dev, uint32_t devid)
void stm32_spi5select(struct spi_dev_s *dev,
uint32_t devid, bool selected)
{
uint32_t index = SPIDEVID_INDEX(devid);
spiinfo("devid: %d CS: %s\n",
(int)devid, selected ? "assert" : "de-assert");
/* The chip select of the serial NOR flash is a plain output, active low */
if (index < nitems(g_spi5gpio) && g_spi5gpio[index] != 0)
{
stm32_gpiowrite(g_spi5gpio[index], !selected);
}
}
uint8_t stm32_spi5status(struct spi_dev_s *dev, uint32_t devid)

View file

@ -0,0 +1,167 @@
/****************************************************************************
* boards/arm/stm32f7/nucleo-f746zg/src/stm32_sx1301.c
*
* 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.
*
****************************************************************************/
/* Support for a LoRa concentrator shield of the LRWAN_GS_HF1 family (for
* instance the RisingHF RHF0M301, an SX1301 with two SX1257 front-ends) on
* SPI4 of the Morpho connector. See include/board.h for the pin map.
*/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <debug.h>
#include <errno.h>
#include <nuttx/spi/spi.h>
#include <nuttx/wireless/lpwan/sx1301.h>
#include <arch/board/board.h>
#include "stm32_gpio.h"
#include "stm32_spi.h"
#include "nucleo-f746zg.h"
#ifdef CONFIG_LPWAN_SX1301
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
#define SX1301_SPI_BUS 4
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
static void stm32_sx1301_reset(FAR const struct sx1301_lower_s *lower,
bool assert);
static void stm32_sx1301_band(FAR const struct sx1301_lower_s *lower,
int band_mhz);
/****************************************************************************
* Private Data
****************************************************************************/
static const struct sx1301_lower_s g_sx1301_lower =
{
.reset = stm32_sx1301_reset,
.band_select = stm32_sx1301_band
};
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: stm32_sx1301_reset
*
* Description:
* Drive the reset line of the concentrator, which is active high.
*
****************************************************************************/
static void stm32_sx1301_reset(FAR const struct sx1301_lower_s *lower,
bool assert)
{
UNUSED(lower);
stm32_gpiowrite(GPIO_SX1301_RESET, assert);
}
/****************************************************************************
* Name: stm32_sx1301_band
*
* Description:
* Select the band of the front-end filter bank of the shield.
*
****************************************************************************/
static void stm32_sx1301_band(FAR const struct sx1301_lower_s *lower,
int band_mhz)
{
UNUSED(lower);
if (band_mhz >= 900)
{
stm32_gpiowrite(GPIO_SX1301_BAND1, false);
stm32_gpiowrite(GPIO_SX1301_BAND2, true);
}
else
{
stm32_gpiowrite(GPIO_SX1301_BAND1, true);
stm32_gpiowrite(GPIO_SX1301_BAND2, false);
}
ninfo("Band set to %d MHz\n", band_mhz);
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: stm32_sx1301_initialize
*
* Description:
* Initialise SPI4 and register the concentrator driver.
*
****************************************************************************/
int stm32_sx1301_initialize(FAR const char *devpath)
{
FAR struct spi_dev_s *spi;
int ret;
/* Control lines first, so that the chip stays in reset until it is
* started.
*/
stm32_configgpio(GPIO_SX1301_RESET);
stm32_configgpio(GPIO_SX1301_BAND1);
stm32_configgpio(GPIO_SX1301_BAND2);
stm32_gpiowrite(GPIO_SX1301_RESET, true);
spi = stm32_spibus_initialize(SX1301_SPI_BUS);
if (spi == NULL)
{
syslog(LOG_ERR, "ERROR: cannot initialise SPI port %d\n",
SX1301_SPI_BUS);
return -ENODEV;
}
ret = sx1301_register(devpath, spi, &g_sx1301_lower);
if (ret < 0)
{
syslog(LOG_ERR, "ERROR: cannot register the concentrator: %d\n", ret);
return ret;
}
syslog(LOG_INFO, "SX1301 concentrator at %s, SPI%d\n", devpath,
SX1301_SPI_BUS);
return OK;
}
#endif /* CONFIG_LPWAN_SX1301 */

View file

@ -5,6 +5,15 @@
if DRIVERS_LPWAN
config LPWAN_LORA_GW
bool
default n
---help---
Selected by the driver of a LoRa gateway, that is, of a concentrator
registering the character device described by
include/nuttx/wireless/lpwan/lora_gw.h. An application driving a
gateway depends on this symbol rather than on one particular chip.
config LPWAN_RN2XX3
bool "Microchip RN2xx3 driver support"
default n
@ -30,4 +39,17 @@ config LPWAN_SX127X
source "drivers/wireless/lpwan/sx127x/Kconfig"
config LPWAN_SX1301
bool "SX1301 LoRa concentrator (gateway) support"
default n
select SPI
select LPWAN_LORA_GW
---help---
This option adds driver support for the Semtech SX1301 LoRa
concentrator, the baseband processor of a LoRaWAN gateway, together
with its two SX125x radio front-ends. Registers a character device
delivering received packets and accepting downlinks.
source "drivers/wireless/lpwan/sx1301/Kconfig"
endif # DRIVERS_LPWAN

View file

@ -26,6 +26,7 @@ ifeq ($(CONFIG_DRIVERS_LPWAN),y)
include wireless/lpwan/sx127x/Make.defs
include wireless/lpwan/sx126x/Make.defs
include wireless/lpwan/sx1301/Make.defs
include wireless/lpwan/rn2xx3/Make.defs
endif # CONFIG_DRIVERS_LPWAN

View file

@ -0,0 +1,26 @@
# ##############################################################################
# drivers/wireless/lpwan/sx1301/CMakeLists.txt
#
# 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.
#
# ##############################################################################
if(CONFIG_LPWAN_SX1301)
target_sources(drivers PRIVATE sx1301.c sx1301_reg.c sx1301_region.c
sx1301_fw.c)
target_include_directories(drivers PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
endif()

View file

@ -0,0 +1,57 @@
#
# For a description of the syntax of this configuration file,
# see the file kconfig-language.txt in the NuttX tools repository.
#
if LPWAN_SX1301
config LPWAN_SX1301_SPIFREQ
int "SX1301 SPI frequency"
default 8000000
---help---
SPI clock frequency used to talk to the concentrator. The SX1301
accepts up to 10 MHz.
config LPWAN_SX1301_DEFAULT_REGION
string "Default channel plan"
default "AU915"
---help---
Channel plan selected when the driver is registered. One of AU915,
AU915-1, US915, US915-1, EU868, AS923, KR920 or IN866, and it can be
changed at runtime with WLIOC_GW_SETREGION.
AU915 and US915 default to the second sub-band (channels 8 to 15 plus
the 500 kHz channel 65), which is what The Things Network and the
Brazilian deployments use. The "-1" variants select the first
sub-band (channels 0 to 7 plus channel 64).
config LPWAN_SX1301_PRIVATE_NETWORK
bool "Private network sync word"
default n
---help---
Use the sync word of a private LoRa network (frame synchronisation
peaks at 1 and 2, matching 0x12 on an SX127x) instead of the public
LoRaWAN one (peaks at 3 and 4, matching 0x34). A gateway that does
not match the end devices never detects a frame.
config LPWAN_SX1301_RXBADCRC
bool "Report packets with a bad CRC"
default n
---help---
By default a packet whose CRC failed is counted and dropped, since
most of them are correlator false triggers and a gateway must not
forward them to the network server. Enable this to have them
returned by read() with status LORA_GW_STAT_CRC_BAD, which is useful
when debugging the receive chain.
config LPWAN_SX1301_RXNOCRC
bool "Report packets received without CRC"
default n
---help---
A LoRaWAN uplink always carries a CRC, so a packet arriving without
one is either a correlator false trigger or the downlink of another
gateway nearby. Both are counted and dropped by default, as the
reference packet forwarder does. Enable this to have them returned
by read() with status LORA_GW_STAT_NO_CRC.
endif # LPWAN_SX1301

View file

@ -0,0 +1,35 @@
############################################################################
# drivers/wireless/lpwan/sx1301/Make.defs
#
# 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.
#
############################################################################
# Include SX1301 concentrator driver into the build
ifeq ($(CONFIG_LPWAN_SX1301),y)
CSRCS += sx1301.c sx1301_reg.c sx1301_region.c sx1301_fw.c
# Include SX1301 build support
DEPPATH += --dep-path wireless$(DELIM)lpwan$(DELIM)sx1301
VPATH += :wireless$(DELIM)lpwan$(DELIM)sx1301
CFLAGS += ${INCDIR_PREFIX}$(TOPDIR)$(DELIM)drivers$(DELIM)wireless$(DELIM)lpwan$(DELIM)sx1301
endif # CONFIG_LPWAN_SX1301

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,56 @@
/****************************************************************************
* drivers/wireless/lpwan/sx1301/sx1301_fw.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 __DRIVERS_WIRELESS_LPWAN_SX1301_SX1301_FW_H
#define __DRIVERS_WIRELESS_LPWAN_SX1301_SX1301_FW_H
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <stdint.h>
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
/* Both internal MCUs have an 8 KiB program memory */
#define SX1301_MCU_FW_SIZE 8192
/* Firmware versions, as read back from the MCU RAM at address 0x20 */
#define SX1301_FW_VERSION_AGC 4
#define SX1301_FW_VERSION_ARB 1
#define SX1301_FW_VERSION_CAL 2
/****************************************************************************
* Public Data
****************************************************************************/
extern const uint8_t g_sx1301_agc_fw[SX1301_MCU_FW_SIZE];
extern const uint8_t g_sx1301_arb_fw[SX1301_MCU_FW_SIZE];
extern const uint8_t g_sx1301_cal_fw[SX1301_MCU_FW_SIZE];
#endif /* __DRIVERS_WIRELESS_LPWAN_SX1301_SX1301_FW_H */

View file

@ -0,0 +1,243 @@
/****************************************************************************
* drivers/wireless/lpwan/sx1301/sx1301_priv.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 __DRIVERS_WIRELESS_LPWAN_SX1301_SX1301_PRIV_H
#define __DRIVERS_WIRELESS_LPWAN_SX1301_SX1301_PRIV_H
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <stdint.h>
#include <stdbool.h>
#include <nuttx/mutex.h>
#include <nuttx/spi/spi.h>
#include <nuttx/wireless/lpwan/lora_gw.h>
#include <nuttx/wireless/lpwan/sx1301.h>
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
/* Register pages. The page is selected by the low bits of address 0x00 and
* a few registers (the FIFO and PROM ports) are page independent; those are
* accessed with SX1301_PAGE_ANY, which does not switch the page at all.
*/
#define SX1301_PAGE_0 0
#define SX1301_PAGE_1 1
#define SX1301_PAGE_2 2
#define SX1301_PAGE_3 3
#define SX1301_PAGE_ANY 0xff
/* SPI protocol of the SX1301: two byte transactions, MSB of the first byte
* selects a write. There is no FPGA/mux on this class of shield, so the
* radios are reached through the internal bridge on page 2 instead.
*/
#define SX1301_SPI_WRITE_FLAG 0x80
#define SX1301_SPI_ADDR_MASK 0x7f
/* Register map -- page independent */
#define SX1301_REG_PAGE 0x00 /* Bits 1:0 page, bit 7 soft reset */
#define SX1301_REG_VERSION 0x01
#define SX1301_REG_RX_BUF_ADDR 0x02 /* 16 bit read pointer (0x02..0x03) */
#define SX1301_REG_RX_BUF_DATA 0x04 /* Auto incrementing data port */
#define SX1301_REG_TX_BUF_ADDR 0x05
#define SX1301_REG_TX_BUF_DATA 0x06 /* Write port; reads give the counter */
#define SX1301_REG_TIMESTAMP 0x06 /* 32 bit concentrator counter (RO) */
#define SX1301_REG_PROM_ADDR 0x09
#define SX1301_REG_PROM_DATA 0x0a
#define SX1301_REG_RX_NUM_STORED 0x0b /* Packets pending; write 0 to pop */
#define SX1301_REG_RX_ADDR_PTR 0x0c /* 16 bit (0x0c..0x0d), RO */
#define SX1301_REG_RX_STATUS 0x0e /* CRC status of the pending packet */
#define SX1301_REG_RX_PLD_SIZE 0x0f
#define SX1301_REG_GLOBAL_EN 0x10 /* b0 MBWSSF, b1 CONC, b2 FSK, b3 EN */
#define SX1301_REG_CLK_CTRL 0x11 /* b0 CLK32M, b1 CLKHS */
#define SX1301_REG_AGC_STATUS 0x20
#define SX1301_REG_CHIP_ID 0x7e
#define SX1301_REG_EMERGENCY 0x7f /* b0 force host control */
/* Register map -- page 0 */
#define SX1301_REG_GPIO_SELECT 0x1c
#define SX1301_REG_GPIO_MODE 0x1d
#define SX1301_REG_RADIO_SELECT 0x23
#define SX1301_REG_IF_FREQ_BASE 0x24 /* IF0..IF7, two bytes each */
#define SX1301_REG_IF_FREQ_8 0x34
#define SX1301_REG_IF_FREQ_9 0x36
#define SX1301_REG_CORR_EN_BASE 0x41 /* CORR0..CORR7 detect enable */
#define SX1301_REG_CORR_TUNE 0x4e /* b3:0 same peak, b6:4 mac gain */
#define SX1301_REG_FRAME_SYNCH 0x5f /* b3:0 peak1, b7:4 peak2 */
#define SX1301_REG_FREQ_DRIFT 0x5d
#define SX1301_REG_PPM_OFFSET 0x64
#define SX1301_REG_GAIN_OFFSET 0x68 /* b3:0 dec, b7:4 chan */
#define SX1301_REG_FORCE_CTRL 0x69 /* b1 radio, b2 front end, b3 filter */
#define SX1301_REG_MCU_CTRL 0x6a /* b0/b1 reset, b2/b3 PROM mux */
#define SX1301_REG_RSSI_BB_DFLT 0x6c
#define SX1301_REG_RSSI_DEC_DFLT 0x6d
#define SX1301_REG_RSSI_CHAN_DFT 0x6e
#define SX1301_REG_RSSI_BB_ALPHA 0x6f
#define SX1301_REG_RSSI_DEC_ALPH 0x70
#define SX1301_REG_RSSI_CHN_ALPH 0x71
/* Register map -- page 1 (transmitter and LoRa standard demodulator) */
#define SX1301_REG_TX_TRIG 0x21 /* b0 immediate, b1 timestamped */
#define SX1301_REG_TX_START_DLY 0x22 /* 16 bit (0x22..0x23) */
#define SX1301_REG_TX_FRAME_SYNC 0x24
#define SX1301_REG_TX_OFFSET_I 0x27
#define SX1301_REG_TX_OFFSET_Q 0x28
#define SX1301_REG_TX_GAIN 0x2a /* b1:0 digital gain, b7 swap IQ */
#define SX1301_REG_MBWSSF_SYNCH 0x2e /* b3:0 peak1, b7:4 peak2 */
#define SX1301_REG_MBWSSF_DRIFT 0x35
#define SX1301_REG_MBWSSF_BW_SEL 0x3a /* b1:0 bandwidth, b2 radio select */
#define SX1301_REG_MBWSSF_PPM 0x3b
#define SX1301_REG_MBWSSF_SF 0x3c
#define SX1301_REG_TX_STATUS 0x3e
/* Register map -- page 2 (radio bridge and MCU RAM debug ports) */
#define SX1301_REG_RADIO_A_DATA 0x21
#define SX1301_REG_RADIO_A_RB 0x22
#define SX1301_REG_RADIO_A_ADDR 0x23
#define SX1301_REG_RADIO_A_CS 0x25
#define SX1301_REG_RADIO_B_DATA 0x26
#define SX1301_REG_RADIO_B_RB 0x27
#define SX1301_REG_RADIO_B_ADDR 0x28
#define SX1301_REG_RADIO_B_CS 0x2a
#define SX1301_REG_RADIO_CFG 0x2b /* b0 A enable, b1 B enable, b2 reset */
#define SX1301_REG_DBG_ARB_DATA 0x40
#define SX1301_REG_DBG_AGC_DATA 0x41
#define SX1301_REG_DBG_ARB_ADDR 0x50
#define SX1301_REG_DBG_AGC_ADDR 0x51
/* Expected identification values */
#define SX1301_CHIP_VERSION 0x67
#define SX1301_CHIP_ID_VALUE 0x01
/* Sixteen bytes of metadata follow the payload in the RX data buffer */
#define SX1301_RX_METADATA_NB 16
/* Largest burst that can be issued with a zero filled MOSI buffer. Big
* enough for a full payload plus its metadata.
*/
#define SX1301_ZEROBUF_SIZE (LORA_GW_MAX_PAYLOAD + SX1301_RX_METADATA_NB)
/* Convert an intermediate frequency in Hz into the 13 bit signed register
* value: value = (if_hz << 5) / 15625.
*/
#define SX1301_IF_HZ_TO_REG(f) ((int32_t)(((int64_t)(f) << 5) / 15625))
/****************************************************************************
* Public Types
****************************************************************************/
/* Radio front-end configuration, derived from the active region */
struct sx1301_rfconf_s
{
bool enable;
bool tx_enable;
uint32_t freq_hz;
int16_t rssi_offset_dbm10; /* RSSI offset in 0.1 dBm units */
};
/* Multi-SF demodulator configuration (IF0..IF7) */
struct sx1301_ifconf_s
{
bool enable;
uint8_t rf_chain;
int32_t freq_hz; /* Offset from the radio centre frequency */
};
/* LoRa standard demodulator configuration (IF8) */
struct sx1301_stdconf_s
{
bool enable;
uint8_t rf_chain;
uint8_t bandwidth;
uint8_t datarate;
int32_t freq_hz;
};
/* Driver state */
struct sx1301_dev_s
{
FAR struct spi_dev_s *spi;
FAR const struct sx1301_lower_s *lower;
mutex_t lock; /* Exclusive access to the chip and state */
uint8_t page; /* Currently selected register page */
uint8_t crefs; /* Number of open references */
bool connected; /* SPI probed and chip identified */
bool started; /* Concentrator running */
int region; /* Active region index */
uint8_t radio_select; /* Radio mapping bitmask of IF0..IF7 */
struct lora_gw_status_s status;
struct sx1301_rfconf_s rf[LORA_GW_RF_CHAIN_NB];
struct sx1301_ifconf_s ifc[LORA_GW_MULTI_NB];
struct sx1301_stdconf_s std;
};
/****************************************************************************
* Public Function Prototypes
****************************************************************************/
/* Register access, sx1301_reg.c ********************************************/
int sx1301_reg_probe(FAR struct sx1301_dev_s *priv);
int sx1301_reg_write(FAR struct sx1301_dev_s *priv, uint8_t page,
uint8_t addr, uint8_t value);
int sx1301_reg_read(FAR struct sx1301_dev_s *priv, uint8_t page,
uint8_t addr, FAR uint8_t *value);
int sx1301_reg_wrburst(FAR struct sx1301_dev_s *priv, uint8_t page,
uint8_t addr, FAR const uint8_t *buffer,
size_t buflen);
int sx1301_reg_rdburst(FAR struct sx1301_dev_s *priv, uint8_t page,
uint8_t addr, FAR uint8_t *buffer, size_t buflen);
int sx1301_reg_setbit(FAR struct sx1301_dev_s *priv, uint8_t page,
uint8_t addr, uint8_t bit, bool value);
int sx1301_reg_write13s(FAR struct sx1301_dev_s *priv, uint8_t page,
uint8_t addr, int32_t value);
/* Region handling, sx1301_region.c *****************************************/
int sx1301_region_count(void);
int sx1301_region_byname(FAR const char *name);
int sx1301_region_getinfo(int region,
FAR struct lora_gw_regioninfo_s *info);
int sx1301_region_apply(FAR struct sx1301_dev_s *priv, int region);
#endif /* __DRIVERS_WIRELESS_LPWAN_SX1301_SX1301_PRIV_H */

View file

@ -0,0 +1,392 @@
/****************************************************************************
* drivers/wireless/lpwan/sx1301/sx1301_reg.c
*
* 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.
*
****************************************************************************/
/* Page aware register access for the SX1301.
*
* The SX1301 uses a plain two byte SPI protocol:
*
* write: [0x80 | addr] [data]
* read: [0x00 | addr] [dummy] -> data comes back in the second byte
*
* Bursts keep the same one byte header and let the chip auto increment the
* address internally, which is how the 8 KiB MCU firmware images and the RX
* FIFO are moved. The chip select must stay asserted for the whole burst,
* hence the explicit SPI_SELECT() around each transaction instead of one
* SPI_SNDBLOCK() per buffer.
*/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <debug.h>
#include <errno.h>
#include <string.h>
#include <nuttx/wireless/wireless.h>
#include "sx1301_priv.h"
/****************************************************************************
* Private Data
****************************************************************************/
/* MOSI filler used during burst reads. The reference HAL clocks out zeros
* (the Linux spidev driver sends zeros for a NULL tx buffer), while
* SPI_EXCHANGE() with a NULL tx buffer sends 0xff on most NuttX ports. The
* SX1301 ignores MOSI during the data phase of a read, but keeping the same
* pattern as the reference removes one variable when debugging silence on
* the bus.
*/
static const uint8_t g_sx1301_zerobuf[SX1301_ZEROBUF_SIZE];
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: sx1301_lock
*
* Description:
* Take the SPI bus and configure it for the SX1301.
*
****************************************************************************/
static void sx1301_lock(FAR struct sx1301_dev_s *priv)
{
SPI_LOCK(priv->spi, true);
SPI_SETMODE(priv->spi, SPIDEV_MODE0);
SPI_SETBITS(priv->spi, 8);
SPI_SETFREQUENCY(priv->spi, CONFIG_LPWAN_SX1301_SPIFREQ);
}
/****************************************************************************
* Name: sx1301_unlock
****************************************************************************/
static void sx1301_unlock(FAR struct sx1301_dev_s *priv)
{
SPI_LOCK(priv->spi, false);
}
/****************************************************************************
* Name: sx1301_rawwrite
*
* Description:
* Single register write without any page handling. The bus must already
* be locked.
*
****************************************************************************/
static void sx1301_rawwrite(FAR struct sx1301_dev_s *priv, uint8_t addr,
uint8_t value)
{
uint8_t txbuf[2];
txbuf[0] = SX1301_SPI_WRITE_FLAG | (addr & SX1301_SPI_ADDR_MASK);
txbuf[1] = value;
SPI_SELECT(priv->spi, SPIDEV_LPWAN(0), true);
SPI_SNDBLOCK(priv->spi, txbuf, 2);
SPI_SELECT(priv->spi, SPIDEV_LPWAN(0), false);
}
/****************************************************************************
* Name: sx1301_rawread
*
* Description:
* Single register read without any page handling. The bus must already
* be locked.
*
****************************************************************************/
static void sx1301_rawread(FAR struct sx1301_dev_s *priv, uint8_t addr,
FAR uint8_t *value)
{
uint8_t txbuf[2];
uint8_t rxbuf[2];
txbuf[0] = addr & SX1301_SPI_ADDR_MASK;
txbuf[1] = 0;
SPI_SELECT(priv->spi, SPIDEV_LPWAN(0), true);
SPI_EXCHANGE(priv->spi, txbuf, rxbuf, 2);
SPI_SELECT(priv->spi, SPIDEV_LPWAN(0), false);
*value = rxbuf[1];
}
/****************************************************************************
* Name: sx1301_setpage
*
* Description:
* Select a register page, if it is not the current one. The bus must
* already be locked. SX1301_PAGE_ANY leaves the page untouched, for the
* registers that are page independent.
*
****************************************************************************/
static void sx1301_setpage(FAR struct sx1301_dev_s *priv, uint8_t page)
{
if (page == SX1301_PAGE_ANY || page == priv->page)
{
return;
}
sx1301_rawwrite(priv, SX1301_REG_PAGE, page & 0x03);
priv->page = page;
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: sx1301_reg_write
****************************************************************************/
int sx1301_reg_write(FAR struct sx1301_dev_s *priv, uint8_t page,
uint8_t addr, uint8_t value)
{
sx1301_lock(priv);
sx1301_setpage(priv, page);
sx1301_rawwrite(priv, addr, value);
sx1301_unlock(priv);
/* Writing the page register by hand invalidates the cached page */
if (addr == SX1301_REG_PAGE)
{
priv->page = value & 0x03;
}
return OK;
}
/****************************************************************************
* Name: sx1301_reg_read
****************************************************************************/
int sx1301_reg_read(FAR struct sx1301_dev_s *priv, uint8_t page,
uint8_t addr, FAR uint8_t *value)
{
if (value == NULL)
{
return -EINVAL;
}
sx1301_lock(priv);
sx1301_setpage(priv, page);
sx1301_rawread(priv, addr, value);
sx1301_unlock(priv);
return OK;
}
/****************************************************************************
* Name: sx1301_reg_wrburst
*
* Description:
* Burst write. The address byte is followed by the whole buffer with the
* chip select held low; the SX1301 increments its internal pointer.
*
****************************************************************************/
int sx1301_reg_wrburst(FAR struct sx1301_dev_s *priv, uint8_t page,
uint8_t addr, FAR const uint8_t *buffer,
size_t buflen)
{
uint8_t hdr;
if (buffer == NULL || buflen == 0)
{
return -EINVAL;
}
hdr = SX1301_SPI_WRITE_FLAG | (addr & SX1301_SPI_ADDR_MASK);
sx1301_lock(priv);
sx1301_setpage(priv, page);
SPI_SELECT(priv->spi, SPIDEV_LPWAN(0), true);
SPI_SNDBLOCK(priv->spi, &hdr, 1);
SPI_SNDBLOCK(priv->spi, buffer, buflen);
SPI_SELECT(priv->spi, SPIDEV_LPWAN(0), false);
sx1301_unlock(priv);
return OK;
}
/****************************************************************************
* Name: sx1301_reg_rdburst
*
* Description:
* Burst read. The first byte clocked back during the address phase is
* discarded, the rest is the payload.
*
****************************************************************************/
int sx1301_reg_rdburst(FAR struct sx1301_dev_s *priv, uint8_t page,
uint8_t addr, FAR uint8_t *buffer, size_t buflen)
{
uint8_t hdr;
size_t chunk;
size_t off;
if (buffer == NULL || buflen == 0)
{
return -EINVAL;
}
hdr = addr & SX1301_SPI_ADDR_MASK;
sx1301_lock(priv);
sx1301_setpage(priv, page);
SPI_SELECT(priv->spi, SPIDEV_LPWAN(0), true);
SPI_SNDBLOCK(priv->spi, &hdr, 1);
/* Clock out zeros while reading, in chunks of the filler buffer */
for (off = 0; off < buflen; off += chunk)
{
chunk = buflen - off;
if (chunk > SX1301_ZEROBUF_SIZE)
{
chunk = SX1301_ZEROBUF_SIZE;
}
SPI_EXCHANGE(priv->spi, g_sx1301_zerobuf, buffer + off, chunk);
}
SPI_SELECT(priv->spi, SPIDEV_LPWAN(0), false);
sx1301_unlock(priv);
return OK;
}
/****************************************************************************
* Name: sx1301_reg_setbit
*
* Description:
* Read modify write of a single bit.
*
****************************************************************************/
int sx1301_reg_setbit(FAR struct sx1301_dev_s *priv, uint8_t page,
uint8_t addr, uint8_t bit, bool value)
{
uint8_t regval;
int ret;
ret = sx1301_reg_read(priv, page, addr, &regval);
if (ret < 0)
{
return ret;
}
if (value)
{
regval |= 1 << bit;
}
else
{
regval &= ~(1 << bit);
}
return sx1301_reg_write(priv, page, addr, regval);
}
/****************************************************************************
* Name: sx1301_reg_write13s
*
* Description:
* Write a 13 bit signed value spanning two consecutive registers, which is
* how the intermediate frequency of every demodulator is programmed.
*
****************************************************************************/
int sx1301_reg_write13s(FAR struct sx1301_dev_s *priv, uint8_t page,
uint8_t addr, int32_t value)
{
uint16_t raw = value & 0x1fff;
int ret;
ret = sx1301_reg_write(priv, page, addr, raw & 0xff);
if (ret < 0)
{
return ret;
}
return sx1301_reg_write(priv, page, addr + 1, (raw >> 8) & 0x1f);
}
/****************************************************************************
* Name: sx1301_reg_probe
*
* Description:
* Identify the chip on the bus. Some shields come back with an
* unexpected version right after reset but still report the correct chip
* identifier, so both are checked.
*
* Returned Value:
* OK if an SX1301 answered, -ENODEV otherwise.
*
****************************************************************************/
int sx1301_reg_probe(FAR struct sx1301_dev_s *priv)
{
uint8_t version;
uint8_t chipid;
priv->page = 0;
sx1301_reg_read(priv, SX1301_PAGE_ANY, SX1301_REG_VERSION, &version);
if (version == SX1301_CHIP_VERSION)
{
wlinfo("SX1301 detected, version 0x%02x\n", version);
}
else
{
sx1301_reg_read(priv, SX1301_PAGE_ANY, SX1301_REG_CHIP_ID, &chipid);
if (chipid != SX1301_CHIP_ID_VALUE)
{
wlerr("ERROR: no SX1301 found, version 0x%02x chipid 0x%02x\n",
version, chipid);
return -ENODEV;
}
wlwarn("WARNING: SX1301 version 0x%02x, chipid 0x%02x, going on\n",
version, chipid);
}
/* Make sure we start from page 0 */
sx1301_reg_write(priv, SX1301_PAGE_ANY, SX1301_REG_PAGE, 0);
priv->page = 0;
priv->connected = true;
return OK;
}

View file

@ -0,0 +1,384 @@
/****************************************************************************
* drivers/wireless/lpwan/sx1301/sx1301_region.c
*
* 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.
*
****************************************************************************/
/* Channel plans of the supported regions.
*
* Each plan lists the absolute frequency of the eight multi-SF channels and
* of the LoRa standard channel, plus the centre frequency of the two radios.
* The intermediate frequency of every demodulator is the difference between
* the two, and must stay inside about +/-460 kHz for a 125 kHz channel,
* which is what sx1301_region_apply() checks.
*
* The AU915 and US915 bands are split in eight sub-bands of eight 125 kHz
* channels. Networks pick one: The Things Network and the Brazilian
* deployments use the second sub-band (channels 8 to 15 plus the 500 kHz
* channel 65), so that is the default here. The first sub-band (channels 0
* to 7 plus channel 64) is available as "AU915-1" and "US915-1".
*/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <debug.h>
#include <errno.h>
#include <string.h>
#include <sys/param.h>
#include <nuttx/wireless/wireless.h>
#include "sx1301_priv.h"
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
/* Front-end RSSI offset, in 0.1 dBm units, for the SX125x radios of the
* reference designs.
*/
#define SX1301_RSSI_OFFSET_DBM10 (-1660)
/* Largest usable intermediate frequency of a 125 kHz channel */
#define SX1301_IF_LIMIT_HZ 460000
#define MULTI(f, rf) \
{ \
.freq_hz = (f), .rf_chain = (rf), \
.type = LORA_GW_CHAN_MULTI_SF, \
.bandwidth = LORA_GW_BW_125K, .datarate = 0, \
.enable = true \
}
#define STD(f, rf, bw, sf) \
{ \
.freq_hz = (f), .rf_chain = (rf), \
.type = LORA_GW_CHAN_STD, \
.bandwidth = (bw), .datarate = (sf), \
.enable = true \
}
#define CHAN_OFF(t) \
{ \
.freq_hz = 0, .rf_chain = 0, .type = (t), \
.bandwidth = LORA_GW_BW_UNDEFINED, .datarate = 0, \
.enable = false \
}
/****************************************************************************
* Private Data
****************************************************************************/
static const struct lora_gw_regioninfo_s g_sx1301_regions[] =
{
{
.name = "AU915",
.desc = "AU915 sub-band 2, ch 8-15+65 (BR/AU, TTN)",
.radio_freq =
{
917100000, 917900000
},
.channels =
{
MULTI(916800000, 0), MULTI(917000000, 0),
MULTI(917200000, 0), MULTI(917400000, 0),
MULTI(917600000, 1), MULTI(917800000, 1),
MULTI(918000000, 1), MULTI(918200000, 1),
STD(917500000, 0, LORA_GW_BW_500K, 8),
CHAN_OFF(LORA_GW_CHAN_FSK)
}
},
{
.name = "AU915-1",
.desc = "AU915 sub-band 1, ch 0-7+64",
.radio_freq =
{
915500000, 916300000
},
.channels =
{
MULTI(915200000, 0), MULTI(915400000, 0),
MULTI(915600000, 0), MULTI(915800000, 0),
MULTI(916000000, 1), MULTI(916200000, 1),
MULTI(916400000, 1), MULTI(916600000, 1),
STD(915900000, 0, LORA_GW_BW_500K, 8),
CHAN_OFF(LORA_GW_CHAN_FSK)
}
},
{
.name = "US915",
.desc = "US915 sub-band 2, ch 8-15+65 (TTN)",
.radio_freq =
{
904200000, 905000000
},
.channels =
{
MULTI(903900000, 0), MULTI(904100000, 0),
MULTI(904300000, 0), MULTI(904500000, 0),
MULTI(904700000, 1), MULTI(904900000, 1),
MULTI(905100000, 1), MULTI(905300000, 1),
STD(904600000, 0, LORA_GW_BW_500K, 8),
CHAN_OFF(LORA_GW_CHAN_FSK)
}
},
{
.name = "US915-1",
.desc = "US915 sub-band 1, ch 0-7+64",
.radio_freq =
{
903500000, 904300000
},
.channels =
{
MULTI(903200000, 0), MULTI(903400000, 0),
MULTI(903600000, 0), MULTI(903800000, 0),
MULTI(904000000, 1), MULTI(904200000, 1),
MULTI(904400000, 1), MULTI(904600000, 1),
STD(903900000, 0, LORA_GW_BW_500K, 8),
CHAN_OFF(LORA_GW_CHAN_FSK)
}
},
{
.name = "EU868",
.desc = "EU863-870, 8 channels + 868.3 BW250",
.radio_freq =
{
867500000, 868500000
},
.channels =
{
MULTI(867100000, 0), MULTI(867300000, 0),
MULTI(867500000, 0), MULTI(867700000, 0),
MULTI(867900000, 0), MULTI(868100000, 1),
MULTI(868300000, 1), MULTI(868500000, 1),
STD(868300000, 1, LORA_GW_BW_250K, 7),
CHAN_OFF(LORA_GW_CHAN_FSK)
}
},
{
.name = "AS923",
.desc = "AS923-1, 922.2-923.6 MHz",
.radio_freq =
{
922500000, 923300000
},
.channels =
{
MULTI(922200000, 0), MULTI(922400000, 0),
MULTI(922600000, 0), MULTI(922800000, 0),
MULTI(923000000, 1), MULTI(923200000, 1),
MULTI(923400000, 1), MULTI(923600000, 1),
STD(923200000, 1, LORA_GW_BW_250K, 7),
CHAN_OFF(LORA_GW_CHAN_FSK)
}
},
{
.name = "KR920",
.desc = "KR920-923, 7 channels",
.radio_freq =
{
922400000, 923100000
},
.channels =
{
MULTI(922100000, 0), MULTI(922300000, 0),
MULTI(922500000, 0), MULTI(922700000, 0),
MULTI(922900000, 1), MULTI(923100000, 1),
MULTI(923300000, 1), CHAN_OFF(LORA_GW_CHAN_MULTI_SF),
CHAN_OFF(LORA_GW_CHAN_STD),
CHAN_OFF(LORA_GW_CHAN_FSK)
}
},
{
.name = "IN866",
.desc = "IN865-867, 3 mandatory + 3 extra channels",
.radio_freq =
{
865520000, 866300000
},
.channels =
{
MULTI(865062500, 0), MULTI(865402500, 0),
MULTI(865985000, 0), MULTI(866100000, 1),
MULTI(866300000, 1), MULTI(866500000, 1),
CHAN_OFF(LORA_GW_CHAN_MULTI_SF),
CHAN_OFF(LORA_GW_CHAN_MULTI_SF),
CHAN_OFF(LORA_GW_CHAN_STD),
CHAN_OFF(LORA_GW_CHAN_FSK)
}
}
};
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: sx1301_region_count
****************************************************************************/
int sx1301_region_count(void)
{
return nitems(g_sx1301_regions);
}
/****************************************************************************
* Name: sx1301_region_byname
*
* Description:
* Look up a region by name, case insensitively.
*
* Returned Value:
* The region index, or -ENOENT if there is no such region.
*
****************************************************************************/
int sx1301_region_byname(FAR const char *name)
{
size_t i;
if (name == NULL)
{
return -EINVAL;
}
for (i = 0; i < nitems(g_sx1301_regions); i++)
{
if (strcasecmp(name, g_sx1301_regions[i].name) == 0)
{
return i;
}
}
return -ENOENT;
}
/****************************************************************************
* Name: sx1301_region_getinfo
****************************************************************************/
int sx1301_region_getinfo(int region,
FAR struct lora_gw_regioninfo_s *info)
{
if (info == NULL)
{
return -EINVAL;
}
if (region < 0 || region >= sx1301_region_count())
{
return -ENODEV;
}
memcpy(info, &g_sx1301_regions[region], sizeof(*info));
return OK;
}
/****************************************************************************
* Name: sx1301_region_apply
*
* Description:
* Translate a channel plan into the radio and demodulator configuration
* used by sx1301_start(). Only radio A is enabled for transmission, as
* on the reference designs where radio B has no PA path.
*
****************************************************************************/
int sx1301_region_apply(FAR struct sx1301_dev_s *priv, int region)
{
FAR const struct lora_gw_regioninfo_s *ri;
FAR const struct lora_gw_chaninfo_s *ch;
uint32_t centre;
int32_t iffreq;
int i;
if (region < 0 || region >= sx1301_region_count())
{
return -ENODEV;
}
ri = &g_sx1301_regions[region];
/* Radios. Both receive, only radio A transmits. */
for (i = 0; i < LORA_GW_RF_CHAIN_NB; i++)
{
priv->rf[i].enable = true;
priv->rf[i].tx_enable = (i == 0);
priv->rf[i].freq_hz = ri->radio_freq[i];
priv->rf[i].rssi_offset_dbm10 = SX1301_RSSI_OFFSET_DBM10;
}
/* Multi-SF demodulators */
for (i = 0; i < LORA_GW_MULTI_NB; i++)
{
ch = &ri->channels[i];
centre = ri->radio_freq[ch->rf_chain];
memset(&priv->ifc[i], 0, sizeof(priv->ifc[i]));
if (!ch->enable)
{
continue;
}
iffreq = (int32_t)ch->freq_hz - (int32_t)centre;
if (iffreq > SX1301_IF_LIMIT_HZ || iffreq < -SX1301_IF_LIMIT_HZ)
{
wlerr("ERROR: %s channel %d at %" PRIu32 " Hz is %" PRId32
" Hz away from radio %d\n",
ri->name, i, ch->freq_hz, iffreq, ch->rf_chain);
return -ERANGE;
}
priv->ifc[i].enable = true;
priv->ifc[i].rf_chain = ch->rf_chain;
priv->ifc[i].freq_hz = iffreq;
}
/* LoRa standard demodulator (IF8) */
ch = &ri->channels[LORA_GW_MULTI_NB];
memset(&priv->std, 0, sizeof(priv->std));
if (ch->enable)
{
centre = ri->radio_freq[ch->rf_chain];
iffreq = (int32_t)ch->freq_hz - (int32_t)centre;
priv->std.enable = true;
priv->std.rf_chain = ch->rf_chain;
priv->std.freq_hz = iffreq;
priv->std.bandwidth = ch->bandwidth;
priv->std.datarate = ch->datarate;
}
priv->region = region;
wlinfo("Region %s selected (%s)\n", ri->name, ri->desc);
return OK;
}

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