From 300beb79aa87e037e5017da5693eac9e57aa28d5 Mon Sep 17 00:00:00 2001 From: Alexandre Abadie Date: Thu, 11 Mar 2021 17:29:21 +0100 Subject: [PATCH 1/6] pkg: add driver_sx126x package driver --- pkg/driver_sx126x/Kconfig | 20 ++++ pkg/driver_sx126x/Makefile | 9 ++ pkg/driver_sx126x/Makefile.dep | 7 ++ pkg/driver_sx126x/Makefile.include | 3 + pkg/driver_sx126x/contrib/Makefile | 3 + pkg/driver_sx126x/contrib/driver_sx126x_hal.c | 98 +++++++++++++++++++ pkg/driver_sx126x/doc.txt | 7 ++ pkg/driver_sx126x/driver_sx126x.mk | 3 + .../patches/0001-adapt-to-RIOT.patch | 62 ++++++++++++ 9 files changed, 212 insertions(+) create mode 100644 pkg/driver_sx126x/Kconfig create mode 100644 pkg/driver_sx126x/Makefile create mode 100644 pkg/driver_sx126x/Makefile.dep create mode 100644 pkg/driver_sx126x/Makefile.include create mode 100644 pkg/driver_sx126x/contrib/Makefile create mode 100644 pkg/driver_sx126x/contrib/driver_sx126x_hal.c create mode 100644 pkg/driver_sx126x/doc.txt create mode 100644 pkg/driver_sx126x/driver_sx126x.mk create mode 100644 pkg/driver_sx126x/patches/0001-adapt-to-RIOT.patch diff --git a/pkg/driver_sx126x/Kconfig b/pkg/driver_sx126x/Kconfig new file mode 100644 index 0000000000..8233a61e51 --- /dev/null +++ b/pkg/driver_sx126x/Kconfig @@ -0,0 +1,20 @@ +# Copyright (c) 2021 Inria +# +# This file is subject to the terms and conditions of the GNU Lesser +# General Public License v2.1. See the file LICENSE in the top level +# directory for more details. +# + +config PACKAGE_DRIVER_SX126X + bool "LLCC68 driver package" + depends on TEST_KCONFIG + depends on HAS_PERIPH_SPI + select MODULE_PERIPH_SPI + select MODULE_ZTIMER + select MODULE_ZTIMER_USEC + select MODULE_DRIVER_SX126X_HAL + +config MODULE_DRIVER_SX126X_HAL + bool + help + HAL implementation for the SX126X LoRa radio driver. diff --git a/pkg/driver_sx126x/Makefile b/pkg/driver_sx126x/Makefile new file mode 100644 index 0000000000..4f18518f0c --- /dev/null +++ b/pkg/driver_sx126x/Makefile @@ -0,0 +1,9 @@ +PKG_NAME=driver_sx126x +PKG_URL=https://github.com/Lora-net/sx126x_driver +PKG_VERSION=ba61312213450ae94a4293d75285c1d8f30c04b3 +PKG_LICENSE=BSD + +include $(RIOTBASE)/pkg/pkg.mk + +all: + $(QQ)"$(MAKE)" -C $(PKG_SOURCE_DIR)/src -f $(CURDIR)/$(PKG_NAME).mk diff --git a/pkg/driver_sx126x/Makefile.dep b/pkg/driver_sx126x/Makefile.dep new file mode 100644 index 0000000000..7445a56abe --- /dev/null +++ b/pkg/driver_sx126x/Makefile.dep @@ -0,0 +1,7 @@ +# module dependencies +USEMODULE += driver_sx126x_hal +USEMODULE += ztimer +USEMODULE += ztimer_usec + +# required features +FEATURES_REQUIRED += periph_spi diff --git a/pkg/driver_sx126x/Makefile.include b/pkg/driver_sx126x/Makefile.include new file mode 100644 index 0000000000..1dc9126938 --- /dev/null +++ b/pkg/driver_sx126x/Makefile.include @@ -0,0 +1,3 @@ +INCLUDES += -I$(PKGDIRBASE)/driver_sx126x/src + +DIRS += $(RIOTBASE)/pkg/driver_sx126x/contrib diff --git a/pkg/driver_sx126x/contrib/Makefile b/pkg/driver_sx126x/contrib/Makefile new file mode 100644 index 0000000000..0ee8bc8c92 --- /dev/null +++ b/pkg/driver_sx126x/contrib/Makefile @@ -0,0 +1,3 @@ +MODULE = driver_sx126x_hal + +include $(RIOTBASE)/Makefile.base diff --git a/pkg/driver_sx126x/contrib/driver_sx126x_hal.c b/pkg/driver_sx126x/contrib/driver_sx126x_hal.c new file mode 100644 index 0000000000..4d789469cb --- /dev/null +++ b/pkg/driver_sx126x/contrib/driver_sx126x_hal.c @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2021 Inria + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup pkg_driver_sx126x + * @{ + * + * @file + * @brief HAL implementation for the SX1261/2 LoRa radio driver + * + * @author Alexandre Abadie + * + * @} + */ + +#include "ztimer.h" + +#include "periph/gpio.h" +#include "periph/spi.h" + +#include "sx126x.h" +#include "sx126x_hal.h" + +#define ENABLE_DEBUG 0 +#include "debug.h" + +#define SX126X_SPI_SPEED (SPI_CLK_1MHZ) +#define SX126X_SPI_MODE (SPI_MODE_0) + +sx126x_hal_status_t sx126x_hal_write(const void *context, + const uint8_t *command, const uint16_t command_length, + const uint8_t *data, const uint16_t data_length) +{ + (void)data; + (void)data_length; + sx126x_t *dev = (sx126x_t *)context; + + /* wait for the device to not be busy anymore */ + while (gpio_read(dev->params->busy_pin)) {} + + spi_acquire(dev->params->spi, SPI_CS_UNDEF, SX126X_SPI_MODE, SX126X_SPI_SPEED); + spi_transfer_bytes(dev->params->spi, dev->params->nss_pin, data_length != 0, command, NULL, + command_length); + if (data_length) { + spi_transfer_bytes(dev->params->spi, dev->params->nss_pin, false, data, NULL, data_length); + } + spi_release(dev->params->spi); + return 0; +} + +sx126x_hal_status_t sx126x_hal_read(const void *context, + const uint8_t *command, const uint16_t command_length, + uint8_t *data, const uint16_t data_length) +{ + sx126x_t *dev = (sx126x_t *)context; + + /* wait for the device to not be busy anymore */ + while (gpio_read(dev->params->busy_pin)) {} + + spi_acquire(dev->params->spi, SPI_CS_UNDEF, SX126X_SPI_MODE, SX126X_SPI_SPEED); + spi_transfer_bytes(dev->params->spi, dev->params->nss_pin, true, command, NULL, command_length); + spi_transfer_bytes(dev->params->spi, dev->params->nss_pin, false, NULL, data, data_length); + spi_release(dev->params->spi); + return 0; +} + +sx126x_hal_status_t sx126x_hal_reset(const void *context) +{ + DEBUG("[sx126x_hal] reset\n"); + sx126x_t *dev = (sx126x_t *)context; + + gpio_set(dev->params->reset_pin); + gpio_clear(dev->params->reset_pin); + /* it takes 100us for the radio to be ready after reset */ + ztimer_sleep(ZTIMER_USEC, 100); + gpio_set(dev->params->reset_pin); + return 0; +} + +sx126x_hal_status_t sx126x_hal_wakeup(const void *context) +{ + DEBUG("[sx126x_hal] wakeup\n"); + sx126x_t *dev = (sx126x_t *)context; + + spi_acquire(dev->params->spi, SPI_CS_UNDEF, SX126X_SPI_MODE, SX126X_SPI_SPEED); + gpio_clear(dev->params->nss_pin); + gpio_set(dev->params->nss_pin); + spi_release(dev->params->spi); + + /* it takes 500us for the radio device to be ready after waking up */ + ztimer_sleep(ZTIMER_USEC, 500); + return 0; +} diff --git a/pkg/driver_sx126x/doc.txt b/pkg/driver_sx126x/doc.txt new file mode 100644 index 0000000000..aeaca868d8 --- /dev/null +++ b/pkg/driver_sx126x/doc.txt @@ -0,0 +1,7 @@ +/** + * @defgroup pkg_driver_sx126x SX1261/2 LoRa radio driver + * @ingroup pkg + * @brief This package is an implementation of the SX1261/2 LoRa radio driver. + * + * @see https://github.com/Lora-net/sx126x_driver + */ diff --git a/pkg/driver_sx126x/driver_sx126x.mk b/pkg/driver_sx126x/driver_sx126x.mk new file mode 100644 index 0000000000..0ed96f8ba2 --- /dev/null +++ b/pkg/driver_sx126x/driver_sx126x.mk @@ -0,0 +1,3 @@ +MODULE = driver_sx126x + +include $(RIOTBASE)/Makefile.base diff --git a/pkg/driver_sx126x/patches/0001-adapt-to-RIOT.patch b/pkg/driver_sx126x/patches/0001-adapt-to-RIOT.patch new file mode 100644 index 0000000000..ae478f0b8b --- /dev/null +++ b/pkg/driver_sx126x/patches/0001-adapt-to-RIOT.patch @@ -0,0 +1,62 @@ +From c68e8e74d3de1235c4d818d96573e33e42cbcb74 Mon Sep 17 00:00:00 2001 +From: Alexandre Abadie +Date: Thu, 11 Mar 2021 17:06:43 +0100 +Subject: [PATCH 1/1] adapt to RIOT + +--- + src/sx126x.c | 4 ++-- + src/{sx126x.h => sx126x_driver.h} | 6 +++--- + src/{sx126x_regs.h => sx126x_driver_regs.h} | 0 + 3 files changed, 5 insertions(+), 5 deletions(-) + rename src/{sx126x.h => sx126x_driver.h} (99%) + rename src/{sx126x_regs.h => sx126x_driver_regs.h} (100%) + +diff --git a/src/sx126x.c b/src/sx126x.c +index a61c3ce..2fa7d88 100644 +--- a/src/sx126x.c ++++ b/src/sx126x.c +@@ -35,9 +35,9 @@ + */ + + #include // memcpy +-#include "sx126x.h" ++#include "sx126x_driver.h" + #include "sx126x_hal.h" +-#include "sx126x_regs.h" ++#include "sx126x_driver_regs.h" + + /* + * ----------------------------------------------------------------------------- +diff --git a/src/sx126x.h b/src/sx126x_driver.h +similarity index 99% +rename from src/sx126x.h +rename to src/sx126x_driver.h +index 634ed82..e5ed101 100644 +--- a/src/sx126x.h ++++ b/src/sx126x_driver.h +@@ -29,8 +29,8 @@ + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +-#ifndef SX126X_H +-#define SX126X_H ++#ifndef SX126X_DRIVER_H ++#define SX126X_DRIVER_H + + #ifdef __cplusplus + extern "C" { +@@ -1517,6 +1517,6 @@ sx126x_status_t sx126x_set_trimming_capacitor_values( const void* context, const + } + #endif + +-#endif // SX126X_H ++#endif // SX126X_DRIVER_H + + /* --- EOF ------------------------------------------------------------------ */ +diff --git a/src/sx126x_regs.h b/src/sx126x_driver_regs.h +similarity index 100% +rename from src/sx126x_regs.h +rename to src/sx126x_driver_regs.h +-- +2.27.0 + From 4fc3112995b67235766a64675e7707432f57f6d4 Mon Sep 17 00:00:00 2001 From: Alexandre Abadie Date: Thu, 11 Mar 2021 17:28:56 +0100 Subject: [PATCH 2/6] makefiles: add sx1261/2 pseudo-modules --- makefiles/pseudomodules.inc.mk | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/makefiles/pseudomodules.inc.mk b/makefiles/pseudomodules.inc.mk index 7359f60862..a887731975 100644 --- a/makefiles/pseudomodules.inc.mk +++ b/makefiles/pseudomodules.inc.mk @@ -234,6 +234,12 @@ PSEUDOMODULES += pn532_spi # include variants of sdp3x drivers as pseudo modules PSEUDOMODULES += sdp3x_irq +# include variants of SX126X drivers and LLCC68 driver as pseudo modules +PSEUDOMODULES += sx1261 +PSEUDOMODULES += sx1262 +PSEUDOMODULES += sx1268 +PSEUDOMODULES += llcc68 + # include variants of SX127X drivers as pseudo modules PSEUDOMODULES += sx1272 PSEUDOMODULES += sx1276 From 0b5b9bd80f2e038adad1c1eee959fbd3549f613e Mon Sep 17 00:00:00 2001 From: Alexandre Abadie Date: Thu, 11 Mar 2021 17:28:33 +0100 Subject: [PATCH 3/6] drivers: add implementation for sx126x lora radio --- drivers/Kconfig.net | 1 + drivers/Makefile.dep | 8 + drivers/include/net/netdev.h | 1 + drivers/include/sx126x.h | 236 +++++++++++++ drivers/sx126x/Kconfig | 14 + drivers/sx126x/Makefile | 1 + drivers/sx126x/Makefile.dep | 3 + drivers/sx126x/Makefile.include | 2 + drivers/sx126x/include/sx126x_netdev.h | 37 +++ drivers/sx126x/include/sx126x_params.h | 81 +++++ drivers/sx126x/sx126x.c | 307 +++++++++++++++++ drivers/sx126x/sx126x_netdev.c | 436 +++++++++++++++++++++++++ 12 files changed, 1127 insertions(+) create mode 100644 drivers/include/sx126x.h create mode 100644 drivers/sx126x/Kconfig create mode 100644 drivers/sx126x/Makefile create mode 100644 drivers/sx126x/Makefile.dep create mode 100644 drivers/sx126x/Makefile.include create mode 100644 drivers/sx126x/include/sx126x_netdev.h create mode 100644 drivers/sx126x/include/sx126x_params.h create mode 100644 drivers/sx126x/sx126x.c create mode 100644 drivers/sx126x/sx126x_netdev.c diff --git a/drivers/Kconfig.net b/drivers/Kconfig.net index 04b6b27e16..afe39e97c5 100644 --- a/drivers/Kconfig.net +++ b/drivers/Kconfig.net @@ -16,6 +16,7 @@ rsource "ncv7356/Kconfig" rsource "pn532/Kconfig" rsource "rn2xx3/Kconfig" rsource "slipdev/Kconfig" +rsource "sx126x/Kconfig" rsource "sx127x/Kconfig" rsource "tja1042/Kconfig" source "$(RIOTCPU)/nrf52/radio/nrf802154/Kconfig" diff --git a/drivers/Makefile.dep b/drivers/Makefile.dep index 92e57c4494..2c9c47ab66 100644 --- a/drivers/Makefile.dep +++ b/drivers/Makefile.dep @@ -68,6 +68,10 @@ ifneq (,$(filter lis2dh12%,$(USEMODULE))) USEMODULE += lis2dh12 endif +ifneq (,$(filter llcc68,$(USEMODULE))) + USEMODULE += sx126x +endif + ifneq (,$(filter lps331ap lps2%hb,$(USEMODULE))) USEMODULE += lpsxxx endif @@ -147,6 +151,10 @@ ifneq (,$(filter slipdev_%,$(USEMODULE))) USEMODULE += slipdev endif +ifneq (,$(filter sx126%,$(USEMODULE))) + USEMODULE += sx126x +endif + ifneq (,$(filter sx127%,$(USEMODULE))) USEMODULE += sx127x endif diff --git a/drivers/include/net/netdev.h b/drivers/include/net/netdev.h index 2a1b2b040d..588326c28a 100644 --- a/drivers/include/net/netdev.h +++ b/drivers/include/net/netdev.h @@ -318,6 +318,7 @@ typedef enum { NETDEV_ESP_NOW, NETDEV_NRF24L01P_NG, NETDEV_SOCKET_ZEP, + NETDEV_SX126X, /* add more if needed */ } netdev_type_t; /** @} */ diff --git a/drivers/include/sx126x.h b/drivers/include/sx126x.h new file mode 100644 index 0000000000..2a61318af3 --- /dev/null +++ b/drivers/include/sx126x.h @@ -0,0 +1,236 @@ +/* + * Copyright (C) 2021 Inria + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @defgroup drivers_sx126x SX1261/2/8 and LLCC68 LoRa radio driver + * @ingroup drivers_netdev + * @brief Driver for the SX1261/2/8 and LLCC68 LoRa radio device + * + * @{ + * + * @file + * + * @author Alexandre Abadie + */ + +#ifndef SX126X_H +#define SX126X_H + +#include "sx126x_driver.h" + +#include "net/netdev.h" + +#include "periph/gpio.h" +#include "periph/spi.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Device initialization parameters + */ +typedef struct { + spi_t spi; /**< SPI device */ + gpio_t nss_pin; /**< SPI NSS pin */ + gpio_t reset_pin; /**< Reset pin */ + gpio_t busy_pin; /**< Busy pin */ + gpio_t dio1_pin; /**< Dio1 pin */ + sx126x_reg_mod_t regulator; /**< Power regulator mode */ +} sx126x_params_t; + +/** + * @brief Device descriptor for the driver + */ +typedef struct { + netdev_t netdev; /**< Netdev parent struct */ + sx126x_params_t *params; /**< Initialization parameters */ + sx126x_pkt_params_lora_t pkt_params; /**< Lora packet parameters */ + sx126x_mod_params_lora_t mod_params; /**< Lora modulation parameters */ + uint32_t channel; /**< Current channel frequency (in Hz) */ + uint32_t rx_timeout; /**< RX timeout in ms */ +} sx126x_t; + +/** + * @brief Setup the radio device + * + * @param[in] dev Device descriptor + * @param[in] params Parameters for device initialization + * @param[in] index Index of @p params in a global parameter struct array. + * If initialized manually, pass a unique identifier instead. + */ +void sx126x_setup(sx126x_t *dev, const sx126x_params_t *params, uint8_t index); + +/** + * @brief Initialize the given device + * + * @param[inout] dev Device descriptor of the driver + * + * @return 0 on success + */ +int sx126x_init(sx126x_t *dev); + +/** + * @brief Gets the channel RF frequency. + * + * @param[in] dev Device descriptor of the driver + * + * @return The channel frequency + */ +uint32_t sx126x_get_channel(const sx126x_t *dev); + +/** + * @brief Sets the channel RF frequency. + * + * @param[in] dev Device descriptor of the driver + * @param[in] freq Channel RF frequency + */ +void sx126x_set_channel(sx126x_t *dev, uint32_t freq); + +/** + * @brief Gets the LoRa bandwidth + * + * @param[in] dev Device descriptor of the driver + * + * @return the bandwidth + */ +uint8_t sx126x_get_bandwidth(const sx126x_t *dev); + +/** + * @brief Sets the LoRa bandwidth + * + * @param[in] dev Device descriptor of the driver + * @param[in] bandwidth The new bandwidth + */ +void sx126x_set_bandwidth(sx126x_t *dev, uint8_t bandwidth); + +/** + * @brief Gets the LoRa spreading factor + * + * @param[in] dev Device descriptor of the driver + * + * @return the spreading factor + */ +uint8_t sx126x_get_spreading_factor(const sx126x_t *dev); + +/** + * @brief Sets the LoRa spreading factor + * + * @param[in] dev Device descriptor of the driver + * @param[in] sf The spreading factor + */ +void sx126x_set_spreading_factor(sx126x_t *dev, uint8_t sf); + +/** + * @brief Gets the LoRa coding rate + * + * @param[in] dev Device descriptor of the driver + * + * @return the current LoRa coding rate + */ +uint8_t sx126x_get_coding_rate(const sx126x_t *dev); + +/** + * @brief Sets the LoRa coding rate + * + * @param[in] dev Device descriptor of the driver + * @param[in] cr The LoRa coding rate + */ +void sx126x_set_coding_rate(sx126x_t *dev, uint8_t cr); + +/** + * @brief Gets the payload length + * + * @param[in] dev Device descriptor of the driver + * + * @return the payload length + */ +uint8_t sx126x_get_lora_payload_length(const sx126x_t *dev); + +/** + * @brief Sets the payload length + * + * @param[in] dev Device descriptor of the driver + * @param[in] len The payload len + */ +void sx126x_set_lora_payload_length(sx126x_t *dev, uint8_t len); + +/** + * @brief Checks if CRC verification mode is enabled + * + * @param[in] dev Device descriptor of the driver + * + * @return the LoRa single mode + */ +bool sx126x_get_lora_crc(const sx126x_t *dev); + +/** + * @brief Enable/Disable CRC verification mode + * + * @param[in] dev Device descriptor of the driver + * @param[in] crc The CRC check mode + */ +void sx126x_set_lora_crc(sx126x_t *dev, bool crc); + +/** + * @brief Gets the LoRa implicit header mode + * + * @param[in] dev Device descriptor of the driver + * + * @return the LoRa implicit mode + */ +bool sx126x_get_lora_implicit_header(const sx126x_t *dev); + +/** + * @brief Sets LoRa implicit header mode + * + * @param[in] dev Device descriptor of the driver + * @param[in] mode The header mode + */ +void sx126x_set_lora_implicit_header(sx126x_t *dev, bool mode); + +/** + * @brief Gets the LoRa preamble length + * + * @param[in] dev Device descriptor of the driver + * + * @return the preamble length + */ +uint16_t sx126x_get_lora_preamble_length(const sx126x_t *dev); + +/** + * @brief Sets the LoRa preamble length + * + * @param[in] dev Device descriptor of the driver + * @param[in] preamble The LoRa preamble length + */ +void sx126x_set_lora_preamble_length(sx126x_t *dev, uint16_t preamble); + +/** + * @brief Checks if the LoRa inverted IQ mode is enabled/disabled + * + * @param[in] dev Device descriptor of the driver + * + * @return the LoRa IQ inverted mode + */ +bool sx126x_get_lora_iq_invert(const sx126x_t *dev); + +/** + * @brief Enable/disable the LoRa IQ inverted mode + * + * @param[in] dev Device descriptor of the driver + * @param[in] iq_invert The LoRa IQ inverted mode + */ +void sx126x_set_lora_iq_invert(sx126x_t *dev, bool iq_invert); + +#ifdef __cplusplus +} +#endif + +#endif /* SX126X_H */ +/** @} */ diff --git a/drivers/sx126x/Kconfig b/drivers/sx126x/Kconfig new file mode 100644 index 0000000000..f682c31920 --- /dev/null +++ b/drivers/sx126x/Kconfig @@ -0,0 +1,14 @@ +# Copyright (c) 2021 Inria +# +# This file is subject to the terms and conditions of the GNU Lesser +# General Public License v2.1. See the file LICENSE in the top level +# directory for more details. +# + +config MODULE_SX126X + bool "SX126X LoRa Sub-GHz radio" + depends on HAS_PERIPH_GPIO_IRQ + depends on PACKAGE_DRIVER_SX126X + depends on TEST_KCONFIG + select MODULE_PERIPH_GPIO_IRQ + select MODULE_IOLIST diff --git a/drivers/sx126x/Makefile b/drivers/sx126x/Makefile new file mode 100644 index 0000000000..48422e909a --- /dev/null +++ b/drivers/sx126x/Makefile @@ -0,0 +1 @@ +include $(RIOTBASE)/Makefile.base diff --git a/drivers/sx126x/Makefile.dep b/drivers/sx126x/Makefile.dep new file mode 100644 index 0000000000..c3de4b4ce3 --- /dev/null +++ b/drivers/sx126x/Makefile.dep @@ -0,0 +1,3 @@ +USEMODULE += iolist +USEPKG += driver_sx126x +FEATURES_REQUIRED += periph_gpio_irq diff --git a/drivers/sx126x/Makefile.include b/drivers/sx126x/Makefile.include new file mode 100644 index 0000000000..2d0b8c8b18 --- /dev/null +++ b/drivers/sx126x/Makefile.include @@ -0,0 +1,2 @@ +USEMODULE_INCLUDES_sx126x := $(LAST_MAKEFILEDIR)/include +USEMODULE_INCLUDES += $(USEMODULE_INCLUDES_sx126x) diff --git a/drivers/sx126x/include/sx126x_netdev.h b/drivers/sx126x/include/sx126x_netdev.h new file mode 100644 index 0000000000..c3de0a4987 --- /dev/null +++ b/drivers/sx126x/include/sx126x_netdev.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2021 Inria + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup drivers_sx126x + * @{ + * @file + * @brief Netdev driver definitions for SX1261/2/8 and LLCC68 driver + * + * @author Alexandre Abadie + */ + +#ifndef SX126X_NETDEV_H +#define SX126X_NETDEV_H + +#include "net/netdev.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Reference to the netdev device driver struct + */ +extern const netdev_driver_t sx126x_driver; + +#ifdef __cplusplus +} +#endif + +#endif /* SX126X_NETDEV_H */ +/** @} */ diff --git a/drivers/sx126x/include/sx126x_params.h b/drivers/sx126x/include/sx126x_params.h new file mode 100644 index 0000000000..6c4b95b6b2 --- /dev/null +++ b/drivers/sx126x/include/sx126x_params.h @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2021 Inria + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup drivers_sx126x + * + * @{ + * @file + * @brief Default configuration + * + * @author Alexandre Abadie + */ + +#ifndef SX126X_PARAMS_H +#define SX126X_PARAMS_H + +#include "board.h" +#include "sx126x.h" +#include "sx126x_driver.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @name Set default configuration parameters + * + * Default values are adapted for mbed shield used with to nucleo64 boards + * @{ + */ +#ifndef SX126X_PARAM_SPI +#define SX126X_PARAM_SPI SPI_DEV(0) +#endif + +#ifndef SX126X_PARAM_SPI_NSS +#define SX126X_PARAM_SPI_NSS GPIO_PIN(0, 8) /* D7 */ +#endif + +#ifndef SX126X_PARAM_RESET +#define SX126X_PARAM_RESET GPIO_PIN(0, 0) /* A0 */ +#endif + +#ifndef SX126X_PARAM_BUSY +#define SX126X_PARAM_BUSY GPIO_PIN(1, 3) /* D3 */ +#endif + +#ifndef SX126X_PARAM_DIO1 +#define SX126X_PARAM_DIO1 GPIO_PIN(1, 4) /* D5 */ +#endif + +#ifndef SX126X_PARAM_REGULATOR +#define SX126X_PARAM_REGULATOR SX126X_REG_MODE_DCDC +#endif + +#define SX126X_PARAMS { .spi = SX126X_PARAM_SPI, \ + .nss_pin = SX126X_PARAM_SPI_NSS, \ + .reset_pin = SX126X_PARAM_RESET, \ + .busy_pin = SX126X_PARAM_BUSY, \ + .dio1_pin = SX126X_PARAM_DIO1, \ + .regulator = SX126X_PARAM_REGULATOR } +/**@}*/ + +/** + * @brief Configuration struct + */ +static const sx126x_params_t sx126x_params[] = +{ + SX126X_PARAMS +}; + +#ifdef __cplusplus +} +#endif + +#endif /* SX126X_PARAMS_H */ +/** @} */ diff --git a/drivers/sx126x/sx126x.c b/drivers/sx126x/sx126x.c new file mode 100644 index 0000000000..6567a93061 --- /dev/null +++ b/drivers/sx126x/sx126x.c @@ -0,0 +1,307 @@ +/* + * Copyright (C) 2021 Inria + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup drivers_sx126x + * @{ + * + * @file + * @brief Device driver implementation for the SX1261/2/8 and LLCC68 LoRa radio driver + * + * @author Alexandre Abadie + * + * @} + */ + +#include + +#include "sx126x_netdev.h" + +#include "net/lora.h" +#include "periph/spi.h" + +#include "sx126x_driver.h" +#include "sx126x.h" +#include "sx126x_params.h" + +#define ENABLE_DEBUG 0 +#include "debug.h" + +#ifndef CONFIG_SX126X_PKT_TYPE_DEFAULT +#define CONFIG_SX126X_PKT_TYPE_DEFAULT (SX126X_PKT_TYPE_LORA) +#endif + +#ifndef CONFIG_SX126X_CHANNEL_DEFAULT +#define CONFIG_SX126X_CHANNEL_DEFAULT (868300000UL) /* in Hz */ +#endif + +#ifndef CONFIG_SX126X_TX_POWER_DEFAULT +#define CONFIG_SX126X_TX_POWER_DEFAULT (14U) /* in dBm */ +#endif + +#ifndef CONFIG_SX126X_RAMP_TIME_DEFAULT +#define CONFIG_SX126X_RAMP_TIME_DEFAULT (SX126X_RAMP_10_US) +#endif + +void sx126x_setup(sx126x_t *dev, const sx126x_params_t *params, uint8_t index) +{ + netdev_t *netdev = (netdev_t *)dev; + + netdev->driver = &sx126x_driver; + dev->params = (sx126x_params_t *)params; + netdev_register(&dev->netdev, NETDEV_SX126X, index); +} + +static const uint16_t _bw_khz[3] = { + [LORA_BW_125_KHZ] = 125, + [LORA_BW_250_KHZ] = 250, + [LORA_BW_500_KHZ] = 500, +}; + +static uint8_t _compute_ldro(sx126x_t *dev) +{ + uint32_t symbol_len = (uint32_t)(1 << dev->mod_params.sf) / _bw_khz[dev->mod_params.bw - SX126X_LORA_BW_125]; + if (symbol_len >= 16) { + return 0x01; + } + + return 0x00; +} + +static void sx126x_init_default_config(sx126x_t *dev) +{ + /* packet type must be set first */ + sx126x_set_pkt_type(dev, SX126X_PKT_TYPE_LORA); + sx126x_set_channel(dev, CONFIG_SX126X_CHANNEL_DEFAULT); + + /* Configure PA optimal settings for maximum output power + * Values used here comes from the datasheet, section 13.1.14 SetPaConfig + * and are optimal for a TX output power of 14dBm. + */ + if (IS_USED(MODULE_LLCC68) || IS_USED(MODULE_SX1262)) { + sx126x_pa_cfg_params_t pa_cfg = { + .pa_duty_cycle = 0x02, + .hp_max = 0x02, + .device_sel = 0x00, + .pa_lut = 0x01 + }; + sx126x_set_pa_cfg(dev, &pa_cfg); + } + else if (IS_USED(MODULE_SX1268)) { + sx126x_pa_cfg_params_t pa_cfg = { + .pa_duty_cycle = 0x04, + .hp_max = 0x06, + .device_sel = 0x00, + .pa_lut = 0x01 + }; + sx126x_set_pa_cfg(dev, &pa_cfg); + } + else { /* IS_USED(MODULE_SX1261) */ + sx126x_pa_cfg_params_t pa_cfg = { + .pa_duty_cycle = 0x04, + .hp_max = 0x00, + .device_sel = 0x01, + .pa_lut = 0x01 + }; + sx126x_set_pa_cfg(dev, &pa_cfg); + } + sx126x_set_tx_params(dev, CONFIG_SX126X_TX_POWER_DEFAULT, CONFIG_SX126X_RAMP_TIME_DEFAULT); + + dev->mod_params.bw = (sx126x_lora_bw_t)(CONFIG_LORA_BW_DEFAULT + SX126X_LORA_BW_125); + dev->mod_params.sf = (sx126x_lora_sf_t)CONFIG_LORA_SF_DEFAULT; + dev->mod_params.cr = (sx126x_lora_cr_t)CONFIG_LORA_CR_DEFAULT; + dev->mod_params.ldro = _compute_ldro(dev); + sx126x_set_lora_mod_params(dev, &dev->mod_params); + + dev->pkt_params.pld_len_in_bytes = 0; + dev->pkt_params.crc_is_on = LORA_PAYLOAD_CRC_ON_DEFAULT; + dev->pkt_params.header_type = ( + IS_ACTIVE(CONFIG_LORA_FIXED_HEADER_LEN_MODE_DEFAULT) ? true : false + ); + dev->pkt_params.preamble_len_in_symb = CONFIG_LORA_PREAMBLE_LENGTH_DEFAULT; + dev->pkt_params.invert_iq_is_on = ( + IS_ACTIVE(CONFIG_LORA_IQ_INVERTED_DEFAULT) ? true : false + ); + sx126x_set_lora_pkt_params(dev, &dev->pkt_params); +} + +static void _dio1_isr(void *arg) +{ + netdev_trigger_event_isr((netdev_t *)arg); +} + +int sx126x_init(sx126x_t *dev) +{ + /* Setup SPI for SX126X */ + int res = spi_init_cs(dev->params->spi, dev->params->nss_pin); + + if (res != SPI_OK) { + DEBUG("[sx126x] error: failed to initialize SPI_%i device (code %i)\n", + dev->params->spi, res); + return -1; + } + + DEBUG("[sx126x] init: SPI_%i initialized with success\n", dev->params->spi); + + gpio_init(dev->params->reset_pin, GPIO_OUT); + gpio_init(dev->params->busy_pin, GPIO_IN_PD); + + /* Initialize DIOs */ + if (gpio_is_valid(dev->params->dio1_pin)) { + res = gpio_init_int(dev->params->dio1_pin, GPIO_IN, GPIO_RISING, _dio1_isr, dev); + if (res < 0) { + DEBUG("[sx126x] error: failed to initialize DIO1 pin\n"); + return res; + } + } + else { + DEBUG("[sx126x] error: no DIO1 pin defined\n"); + return -EIO; + } + + /* Reset the device */ + sx126x_reset(dev); + + /* Configure the power regulator mode */ + sx126x_set_reg_mode(dev, dev->params->regulator); + + /* Initialize radio with the default parameters */ + sx126x_init_default_config(dev); + + /* Configure available IRQs */ + const uint16_t irq_mask = ( + SX126X_IRQ_TX_DONE | + SX126X_IRQ_RX_DONE | + SX126X_IRQ_PREAMBLE_DETECTED | + SX126X_IRQ_HEADER_VALID | + SX126X_IRQ_HEADER_ERROR | + SX126X_IRQ_CRC_ERROR | + SX126X_IRQ_CAD_DONE | + SX126X_IRQ_CAD_DETECTED | + SX126X_IRQ_TIMEOUT + ); + + sx126x_set_dio_irq_params(dev, irq_mask, irq_mask, 0, 0); + + if (IS_ACTIVE(ENABLE_DEBUG)) { + sx126x_pkt_type_t pkt_type; + sx126x_get_pkt_type(dev, &pkt_type); + DEBUG("[sx126x] init radio: pkt type: %d\n", pkt_type); + + sx126x_chip_status_t radio_status; + sx126x_get_status(dev, &radio_status); + DEBUG("[sx126x] init: chip mode %d\n", radio_status.chip_mode); + DEBUG("[sx126x] init: cmd status %d\n", radio_status.cmd_status); + } + + return res; +} + +uint32_t sx126x_get_channel(const sx126x_t *dev) +{ + return dev->channel; +} + +void sx126x_set_channel(sx126x_t *dev, uint32_t freq) +{ + dev->channel = freq; + sx126x_set_rf_freq(dev, dev->channel); +} + +uint8_t sx126x_get_bandwidth(const sx126x_t *dev) +{ + return dev->mod_params.bw - SX126X_LORA_BW_125; +} + +void sx126x_set_bandwidth(sx126x_t *dev, uint8_t bandwidth) +{ + dev->mod_params.bw = bandwidth + SX126X_LORA_BW_125; + dev->mod_params.ldro = _compute_ldro(dev); + sx126x_set_lora_mod_params(dev, &dev->mod_params); +} + +uint8_t sx126x_get_spreading_factor(const sx126x_t *dev) +{ + return dev->mod_params.sf; +} + +void sx126x_set_spreading_factor(sx126x_t *dev, uint8_t sf) +{ + dev->mod_params.sf = (sx126x_lora_sf_t)sf; + dev->mod_params.ldro = _compute_ldro(dev); + sx126x_set_lora_mod_params(dev, &dev->mod_params); +} + +uint8_t sx126x_get_coding_rate(const sx126x_t *dev) +{ + return dev->mod_params.cr; +} + +void sx126x_set_coding_rate(sx126x_t *dev, uint8_t cr) +{ + dev->mod_params.cr = (sx126x_lora_cr_t)cr; + sx126x_set_lora_mod_params(dev, &dev->mod_params); +} + +uint8_t sx126x_get_lora_payload_length(const sx126x_t *dev) +{ + sx126x_rx_buffer_status_t rx_buffer_status; + + sx126x_get_rx_buffer_status(dev, &rx_buffer_status); + return rx_buffer_status.pld_len_in_bytes; +} + +void sx126x_set_lora_payload_length(sx126x_t *dev, uint8_t len) +{ + dev->pkt_params.pld_len_in_bytes = len; + sx126x_set_lora_pkt_params(dev, &dev->pkt_params); +} + +bool sx126x_get_lora_crc(const sx126x_t *dev) +{ + return dev->pkt_params.crc_is_on; +} + +void sx126x_set_lora_crc(sx126x_t *dev, bool crc) +{ + dev->pkt_params.crc_is_on = crc; + sx126x_set_lora_pkt_params(dev, &dev->pkt_params); +} + +bool sx126x_get_lora_implicit_header(const sx126x_t *dev) +{ + return dev->pkt_params.header_type == SX126X_LORA_PKT_IMPLICIT; +} + +void sx126x_set_lora_implicit_header(sx126x_t *dev, bool mode) +{ + dev->pkt_params.header_type = (mode ? SX126X_LORA_PKT_IMPLICIT : SX126X_LORA_PKT_EXPLICIT); + sx126x_set_lora_pkt_params(dev, &dev->pkt_params); +} + +uint16_t sx126x_get_lora_preamble_length(const sx126x_t *dev) +{ + return dev->pkt_params.preamble_len_in_symb; +} + +void sx126x_set_lora_preamble_length(sx126x_t *dev, uint16_t preamble) +{ + dev->pkt_params.preamble_len_in_symb = preamble; + sx126x_set_lora_pkt_params(dev, &dev->pkt_params); +} + +bool sx126x_get_lora_iq_invert(const sx126x_t *dev) +{ + return dev->pkt_params.invert_iq_is_on; +} + +void sx126x_set_lora_iq_invert(sx126x_t *dev, bool iq_invert) +{ + dev->pkt_params.invert_iq_is_on = iq_invert; + sx126x_set_lora_pkt_params(dev, &dev->pkt_params); +} diff --git a/drivers/sx126x/sx126x_netdev.c b/drivers/sx126x/sx126x_netdev.c new file mode 100644 index 0000000000..0e93e75ef3 --- /dev/null +++ b/drivers/sx126x/sx126x_netdev.c @@ -0,0 +1,436 @@ +/* + * Copyright (C) 2021 Inria + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup drivers_sx126x + * @{ + * @file + * @brief Netdev adaptation for the SX1261/2/8 and LLCC68 driver + * + * @author Alexandre Abadie + * @} + */ + +#include +#include +#include +#include + +#include "iolist.h" +#include "net/netopt.h" +#include "net/netdev.h" +#include "net/netdev/lora.h" +#include "net/lora.h" + +#include "sx126x.h" +#include "sx126x_netdev.h" + +#define ENABLE_DEBUG 0 +#include "debug.h" + +#if IS_USED(MODULE_LLCC68) +#define SX126X_MAX_SF LORA_SF11 +#else +#define SX126X_MAX_SF LORA_SF12 +#endif + +static int _send(netdev_t *netdev, const iolist_t *iolist) +{ + sx126x_t *dev = (sx126x_t *)netdev; + + netopt_state_t state; + + netdev->driver->get(netdev, NETOPT_STATE, &state, sizeof(uint8_t)); + if (state == NETOPT_STATE_TX) { + DEBUG("[sx126x] netdev: cannot send packet, radio is already transmitting.\n"); + return -ENOTSUP; + } + + uint8_t size = iolist_size(iolist); + + /* Ignore send if packet size is 0 */ + if (!size) { + return 0; + } + + DEBUG("[sx126x] netdev: sending packet now (size: %d).\n", size); + /* Write payload buffer */ + for (const iolist_t *iol = iolist; iol; iol = iol->iol_next) { + if (iol->iol_len > 0) { + sx126x_set_lora_payload_length(dev, iol->iol_len); + sx126x_write_buffer(dev, 0, iol->iol_base, iol->iol_len); + DEBUG("[sx126x] netdev: send: wrote data to payload buffer.\n"); + } + } + + state = NETOPT_STATE_TX; + netdev->driver->set(netdev, NETOPT_STATE, &state, sizeof(uint8_t)); + DEBUG("[sx126x] netdev: send: transmission in progress.\n"); + + return 0; +} + +static int _recv(netdev_t *netdev, void *buf, size_t len, void *info) +{ + DEBUG("[sx126x] netdev: read received data.\n"); + + sx126x_t *dev = (sx126x_t *)netdev; + uint8_t size = 0; + + netdev_lora_rx_info_t *packet_info = (netdev_lora_rx_info_t *)info; + + if (packet_info) { + sx126x_pkt_status_lora_t pkt_status; + sx126x_get_lora_pkt_status(dev, &pkt_status); + packet_info->snr = pkt_status.snr_pkt_in_db; + packet_info->rssi = pkt_status.rssi_pkt_in_dbm; + } + + sx126x_rx_buffer_status_t rx_buffer_status; + + sx126x_get_rx_buffer_status(dev, &rx_buffer_status); + + size = rx_buffer_status.pld_len_in_bytes; + + if (buf == NULL) { + return size; + } + + if (size > len) { + return -ENOBUFS; + } + + sx126x_read_buffer(dev, rx_buffer_status.buffer_start_pointer, buf, size); + + return 0; +} + +static int _init(netdev_t *netdev) +{ + sx126x_t *dev = (sx126x_t *)netdev; + + /* Launch initialization of driver and device */ + DEBUG("[sx126x] netdev: initializing driver...\n"); + if (sx126x_init(dev) != 0) { + DEBUG("[sx126x] netdev: initialization failed\n"); + return -1; + } + + DEBUG("[sx126x] netdev: initialization successful\n"); + return 0; +} + +static void _isr(netdev_t *netdev) +{ + sx126x_t *dev = (sx126x_t *)netdev; + + sx126x_irq_mask_t irq_mask; + + sx126x_get_and_clear_irq_status(dev, &irq_mask); + + if (irq_mask & SX126X_IRQ_TX_DONE) { + DEBUG("[sx126x] netdev: SX126X_IRQ_TX_DONE\n"); + netdev->event_callback(netdev, NETDEV_EVENT_TX_COMPLETE); + } + else if (irq_mask & SX126X_IRQ_RX_DONE) { + DEBUG("[sx126x] netdev: SX126X_IRQ_RX_DONE\n"); + netdev->event_callback(netdev, NETDEV_EVENT_RX_COMPLETE); + } + else if (irq_mask & SX126X_IRQ_PREAMBLE_DETECTED) { + DEBUG("[sx126x] netdev: SX126X_IRQ_PREAMBLE_DETECTED\n"); + } + else if (irq_mask & SX126X_IRQ_SYNC_WORD_VALID) { + DEBUG("[sx126x] netdev: SX126X_IRQ_SYNC_WORD_VALID\n"); + } + else if (irq_mask & SX126X_IRQ_HEADER_VALID) { + DEBUG("[sx126x] netdev: SX126X_IRQ_HEADER_VALID\n"); + netdev->event_callback(netdev, NETDEV_EVENT_RX_STARTED); + } + else if (irq_mask & SX126X_IRQ_HEADER_ERROR) { + DEBUG("[sx126x] netdev: SX126X_IRQ_HEADER_ERROR\n"); + } + else if (irq_mask & SX126X_IRQ_CRC_ERROR) { + DEBUG("[sx126x] netdev: SX126X_IRQ_CRC_ERROR\n"); + netdev->event_callback(netdev, NETDEV_EVENT_CRC_ERROR); + } + else if (irq_mask & SX126X_IRQ_CAD_DONE) { + DEBUG("[sx126x] netdev: SX126X_IRQ_CAD_DONE\n"); + netdev->event_callback(netdev, NETDEV_EVENT_CAD_DONE); + } + else if (irq_mask & SX126X_IRQ_CAD_DETECTED) { + DEBUG("[sx126x] netdev: SX126X_IRQ_CAD_DETECTED\n"); + } + else if (irq_mask & SX126X_IRQ_TIMEOUT) { + DEBUG("[sx126x] netdev: SX126X_IRQ_TIMEOUT\n"); + netdev->event_callback(netdev, NETDEV_EVENT_RX_TIMEOUT); + } + else { + DEBUG("[sx126x] netdev: SX126X_IRQ_NONE\n"); + } +} + +static int _get_state(sx126x_t *dev, void *val) +{ + sx126x_chip_status_t radio_status; + + sx126x_get_status(dev, &radio_status); + netopt_state_t state = NETOPT_STATE_OFF; + + switch (radio_status.chip_mode) { + case SX126X_CHIP_MODE_RFU: + case SX126X_CHIP_MODE_STBY_RC: + case SX126X_CHIP_MODE_STBY_XOSC: + state = NETOPT_STATE_STANDBY; + break; + + case SX126X_CHIP_MODE_TX: + state = NETOPT_STATE_TX; + break; + + case SX126X_CHIP_MODE_RX: + state = NETOPT_STATE_RX; + break; + + default: + break; + } + memcpy(val, &state, sizeof(netopt_state_t)); + return sizeof(netopt_state_t); +} + +static int _get(netdev_t *netdev, netopt_t opt, void *val, size_t max_len) +{ + (void)max_len; /* unused when compiled without debug, assert empty */ + sx126x_t *dev = (sx126x_t *)netdev; + + if (dev == NULL) { + return -ENODEV; + } + + switch (opt) { + case NETOPT_STATE: + assert(max_len >= sizeof(netopt_state_t)); + return _get_state(dev, val); + + case NETOPT_DEVICE_TYPE: + assert(max_len >= sizeof(uint16_t)); + sx126x_pkt_type_t pkt_type; + sx126x_get_pkt_type(dev, &pkt_type); + *((uint16_t *)val) = + (pkt_type == SX126X_PKT_TYPE_LORA) ? NETDEV_TYPE_LORA : NETDEV_TYPE_UNKNOWN; + return sizeof(uint16_t); + + case NETOPT_CHANNEL_FREQUENCY: + assert(max_len >= sizeof(uint32_t)); + *((uint32_t *)val) = sx126x_get_channel(dev); + return sizeof(uint32_t); + + case NETOPT_BANDWIDTH: + assert(max_len >= sizeof(uint8_t)); + *((uint8_t *)val) = sx126x_get_bandwidth(dev); + return sizeof(uint8_t); + + case NETOPT_SPREADING_FACTOR: + assert(max_len >= sizeof(uint8_t)); + *((uint8_t *)val) = sx126x_get_spreading_factor(dev); + return sizeof(uint8_t); + + case NETOPT_CODING_RATE: + assert(max_len >= sizeof(uint8_t)); + *((uint8_t *)val) = sx126x_get_coding_rate(dev); + return sizeof(uint8_t); + + case NETOPT_PDU_SIZE: + assert(max_len >= sizeof(uint8_t)); + *((uint8_t *)val) = sx126x_get_lora_payload_length(dev); + return sizeof(uint8_t); + + case NETOPT_INTEGRITY_CHECK: + assert(max_len >= sizeof(netopt_enable_t)); + *((netopt_enable_t *)val) = sx126x_get_lora_crc(dev) ? NETOPT_ENABLE : NETOPT_DISABLE; + return sizeof(netopt_enable_t); + + case NETOPT_RANDOM: + assert(max_len >= sizeof(uint32_t)); + sx126x_get_random_numbers(dev, val, 1); + return sizeof(uint32_t); + + case NETOPT_IQ_INVERT: + assert(max_len >= sizeof(uint8_t)); + *((netopt_enable_t *)val) = sx126x_get_lora_iq_invert(dev) ? NETOPT_ENABLE : NETOPT_DISABLE; + return sizeof(netopt_enable_t); + + case NETOPT_RSSI: + assert(max_len >= sizeof(int8_t)); + sx126x_get_rssi_inst(dev, ((int16_t *)val)); + return sizeof(int8_t); + + default: + break; + } + + return -ENOTSUP; +} + +static int _set_state(sx126x_t *dev, netopt_state_t state) +{ + switch (state) { + case NETOPT_STATE_STANDBY: + DEBUG("[sx126x] netdev: set NETOPT_STATE_STANDBY state\n"); + sx126x_set_standby(dev, SX126X_CHIP_MODE_STBY_XOSC); + break; + + case NETOPT_STATE_IDLE: + case NETOPT_STATE_RX: + DEBUG("[sx126x] netdev: set NETOPT_STATE_RX state\n"); + sx126x_cfg_rx_boosted(dev, true); + if (dev->rx_timeout != 0) { + sx126x_set_rx(dev, dev->rx_timeout); + } + else { + sx126x_set_rx(dev, SX126X_RX_SINGLE_MODE); + } + break; + + case NETOPT_STATE_TX: + DEBUG("[sx126x] netdev: set NETOPT_STATE_TX state\n"); + sx126x_set_tx(dev, 0); + break; + + case NETOPT_STATE_RESET: + DEBUG("[sx126x] netdev: set NETOPT_STATE_RESET state\n"); + sx126x_reset(dev); + break; + + default: + return -ENOTSUP; + } + return sizeof(netopt_state_t); +} + +static int _set(netdev_t *netdev, netopt_t opt, const void *val, size_t len) +{ + (void)len; /* unused when compiled without debug, assert empty */ + sx126x_t *dev = (sx126x_t *)netdev; + int res = -ENOTSUP; + + if (dev == NULL) { + return -ENODEV; + } + + switch (opt) { + case NETOPT_STATE: + assert(len == sizeof(netopt_state_t)); + return _set_state(dev, *((const netopt_state_t *)val)); + + case NETOPT_DEVICE_TYPE: + assert(len <= sizeof(uint16_t)); + /* Only LoRa modem is supported for the moment */ + if (*(const uint16_t *)val == NETDEV_TYPE_LORA) { + sx126x_set_pkt_type(dev, SX126X_PKT_TYPE_LORA); + return sizeof(uint16_t); + } + else { + return -EINVAL; + } + + case NETOPT_CHANNEL_FREQUENCY: + assert(len <= sizeof(uint32_t)); + sx126x_set_channel(dev, *((const uint32_t *)val)); + return sizeof(uint32_t); + + case NETOPT_BANDWIDTH: + assert(len <= sizeof(uint8_t)); + uint8_t bw = *((const uint8_t *)val); + if (bw > LORA_BW_500_KHZ) { + res = -EINVAL; + break; + } + sx126x_set_bandwidth(dev, bw); + return sizeof(uint8_t); + + case NETOPT_SPREADING_FACTOR: + assert(len <= sizeof(uint8_t)); + uint8_t sf = *((const uint8_t *)val); + if ((sf < LORA_SF6) || (sf > SX126X_MAX_SF)) { + res = -EINVAL; + break; + } + sx126x_set_spreading_factor(dev, sf); + return sizeof(uint8_t); + + case NETOPT_CODING_RATE: + assert(len <= sizeof(uint8_t)); + uint8_t cr = *((const uint8_t *)val); + if ((cr < LORA_CR_4_5) || (cr > LORA_CR_4_8)) { + res = -EINVAL; + break; + } + sx126x_set_coding_rate(dev, cr); + return sizeof(uint8_t); + + case NETOPT_PDU_SIZE: + assert(len <= sizeof(uint8_t)); + sx126x_set_lora_payload_length(dev, *((const uint8_t *)val)); + return sizeof(uint8_t); + + case NETOPT_INTEGRITY_CHECK: + assert(len <= sizeof(netopt_enable_t)); + sx126x_set_lora_crc(dev, *((const netopt_enable_t *)val) ? true : false); + return sizeof(netopt_enable_t); + + case NETOPT_RX_TIMEOUT: + assert(len <= sizeof(uint32_t)); + dev->rx_timeout = *(const uint32_t *)val; + return sizeof(uint32_t); + + case NETOPT_TX_POWER: + assert(len <= sizeof(int16_t)); + int16_t power = *((const int16_t *)val); + if ((power < INT8_MIN) || (power > INT8_MAX)) { + res = -EINVAL; + break; + } + sx126x_set_tx_params(dev, power, SX126X_RAMP_10_US); + return sizeof(int16_t); + + case NETOPT_FIXED_HEADER: + assert(len <= sizeof(netopt_enable_t)); + sx126x_set_lora_implicit_header(dev, *((const netopt_enable_t *)val) ? true : false); + return sizeof(netopt_enable_t); + + case NETOPT_PREAMBLE_LENGTH: + assert(len <= sizeof(uint16_t)); + sx126x_set_lora_preamble_length(dev, *((const uint16_t *)val)); + return sizeof(uint16_t); + + case NETOPT_SYNCWORD: + assert(len <= sizeof(uint8_t)); + sx126x_set_lora_sync_word(dev, *((uint8_t *)val)); + return sizeof(uint8_t); + + case NETOPT_IQ_INVERT: + assert(len <= sizeof(netopt_enable_t)); + sx126x_set_lora_iq_invert(dev, *((const netopt_enable_t *)val) ? true : false); + return sizeof(bool); + + default: + break; + } + + return res; +} + +const netdev_driver_t sx126x_driver = { + .send = _send, + .recv = _recv, + .init = _init, + .isr = _isr, + .get = _get, + .set = _set, +}; From c8e343a59004100ce1f3c83b8643d28f67fd9ad4 Mon Sep 17 00:00:00 2001 From: Alexandre Abadie Date: Thu, 11 Mar 2021 17:29:36 +0100 Subject: [PATCH 4/6] tests/driver_sx126x: add test application --- tests/driver_sx126x/Makefile | 10 + tests/driver_sx126x/Makefile.ci | 11 + tests/driver_sx126x/README.md | 45 ++++ tests/driver_sx126x/app.config.test | 7 + tests/driver_sx126x/main.c | 350 ++++++++++++++++++++++++++++ 5 files changed, 423 insertions(+) create mode 100644 tests/driver_sx126x/Makefile create mode 100644 tests/driver_sx126x/Makefile.ci create mode 100644 tests/driver_sx126x/README.md create mode 100644 tests/driver_sx126x/app.config.test create mode 100644 tests/driver_sx126x/main.c diff --git a/tests/driver_sx126x/Makefile b/tests/driver_sx126x/Makefile new file mode 100644 index 0000000000..68af3a584c --- /dev/null +++ b/tests/driver_sx126x/Makefile @@ -0,0 +1,10 @@ +include ../Makefile.tests_common + +# other values supported are sx1262, sx1268 and llcc68 +LORA_DRIVER ?= sx1261 +USEMODULE += $(LORA_DRIVER) + +USEMODULE += shell +USEMODULE += shell_commands + +include $(RIOTBASE)/Makefile.include diff --git a/tests/driver_sx126x/Makefile.ci b/tests/driver_sx126x/Makefile.ci new file mode 100644 index 0000000000..ecd48b4a74 --- /dev/null +++ b/tests/driver_sx126x/Makefile.ci @@ -0,0 +1,11 @@ +BOARD_INSUFFICIENT_MEMORY := \ + arduino-duemilanove \ + arduino-leonardo \ + arduino-nano \ + arduino-uno \ + atmega328p \ + nucleo-l011k4 \ + stm32f030f4-demo \ + samd10-xmini \ + waspmote-pro \ + # diff --git a/tests/driver_sx126x/README.md b/tests/driver_sx126x/README.md new file mode 100644 index 0000000000..fea2c71319 --- /dev/null +++ b/tests/driver_sx126x/README.md @@ -0,0 +1,45 @@ +sx126x/llcc68 LoRa driver +================== + +This is a manual test application for the SX1261/2/8 and LLCC68 LoRa radio driver. + +Usage +===== + +This application adds a shell command to control basic features radio device: + +``` +main(): This is RIOT! (Version: 2021.04-devel) +Initialization successful - starting the shell now +> help +help +Command Description +--------------------------------------- +sx126x Control the SX126X radio +reboot Reboot the node +version Prints current RIOT_VERSION +pm interact with layered PM subsystem +> sx126x +sx126x +Usage: sx126x +``` + +The `get` and `set` subcommands allows for getting/setting the current +frequency channel (freq) and lora modulation parameters (bw, sf, cr). + +To put the device in listen mode, use the `rx` subcommand: + +``` +> sx126x rx start +sx126x rx start +Listen mode started +``` + +To send a message, use the `tx` subcommand: + +``` +> sx126x tx "This is RIOT!" +sx126x tx "This is RIOT!" +sending "This is RIOT!" payload (14 bytes) +> Transmission completed +``` diff --git a/tests/driver_sx126x/app.config.test b/tests/driver_sx126x/app.config.test new file mode 100644 index 0000000000..f6daeadf2b --- /dev/null +++ b/tests/driver_sx126x/app.config.test @@ -0,0 +1,7 @@ +# this file enables modules defined in Kconfig. Do not use this file for +# application configuration. This is only needed during migration. +CONFIG_MODULE_SX126X=y +CONFIG_PACKAGE_DRIVER_SX126X=y + +CONFIG_MODULE_SHELL=y +CONFIG_MODULE_SHELL_COMMANDS=y diff --git a/tests/driver_sx126x/main.c b/tests/driver_sx126x/main.c new file mode 100644 index 0000000000..313c95b4ed --- /dev/null +++ b/tests/driver_sx126x/main.c @@ -0,0 +1,350 @@ +/* + * Copyright (C) 2021 Inria + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup tests + * @{ + * + * @file + * @brief Test application for the sx126x/llcc68 radio driver + * + * @author Alexandre Abadie + * + * @} + */ + +#include +#include +#include +#include + +#include "msg.h" +#include "thread.h" +#include "shell.h" + +#include "net/lora.h" +#include "net/netdev.h" +#include "net/netdev/lora.h" + +#include "sx126x.h" +#include "sx126x_params.h" +#include "sx126x_netdev.h" + +#define SX126X_MSG_QUEUE (8U) +#define SX126X_STACKSIZE (THREAD_STACKSIZE_DEFAULT) +#define SX126X_MSG_TYPE_ISR (0x3456) +#define SX126X_MAX_PAYLOAD_LEN (128U) + +static char stack[SX126X_STACKSIZE]; +static kernel_pid_t _recv_pid; + +static char message[SX126X_MAX_PAYLOAD_LEN]; + +static sx126x_t sx126x; + +static void _event_cb(netdev_t *dev, netdev_event_t event) +{ + if (event == NETDEV_EVENT_ISR) { + msg_t msg; + msg.type = SX126X_MSG_TYPE_ISR; + if (msg_send(&msg, _recv_pid) <= 0) { + puts("sx126x_netdev: possibly lost interrupt."); + } + } + else { + switch (event) { + case NETDEV_EVENT_RX_STARTED: + puts("Data reception started"); + break; + + case NETDEV_EVENT_RX_COMPLETE: + { + size_t len = dev->driver->recv(dev, NULL, 0, 0); + netdev_lora_rx_info_t packet_info; + dev->driver->recv(dev, message, len, &packet_info); + printf( + "Received: \"%s\" (%d bytes) - [RSSI: %i, SNR: %i, TOA: %" PRIu32 "ms]\n", + message, (int)len, + packet_info.rssi, (int)packet_info.snr, + sx126x_get_lora_time_on_air_in_ms(&sx126x.pkt_params, &sx126x.mod_params) + ); + netopt_state_t state = NETOPT_STATE_RX; + dev->driver->set(dev, NETOPT_STATE, &state, sizeof(state)); + } + break; + + case NETDEV_EVENT_TX_COMPLETE: + puts("Transmission completed"); + break; + + case NETDEV_EVENT_TX_TIMEOUT: + puts("Transmission timeout"); + break; + + default: + printf("Unexpected netdev event received: %d\n", event); + break; + } + } +} + +void *_recv_thread(void *arg) +{ + netdev_t *netdev = (netdev_t *)arg; + + static msg_t _msg_queue[SX126X_MSG_QUEUE]; + + msg_init_queue(_msg_queue, SX126X_MSG_QUEUE); + + while (1) { + msg_t msg; + msg_receive(&msg); + if (msg.type == SX126X_MSG_TYPE_ISR) { + netdev->driver->isr(netdev); + } + else { + puts("Unexpected msg type"); + } + } +} + +static void _get_usage(const char *cmd) +{ + printf("Usage: %s get \n", cmd); +} + +static int sx126x_get_cmd(netdev_t *netdev, int argc, char **argv) +{ + if (argc == 2) { + _get_usage(argv[0]); + return -1; + } + + if (!strcmp("type", argv[2])) { + uint16_t type; + netdev->driver->get(netdev, NETOPT_DEVICE_TYPE, &type, sizeof(uint16_t)); + printf("Device type: %s\n", (type == NETDEV_TYPE_LORA) ? "lora" : "fsk"); + } + else if (!strcmp("freq", argv[2])) { + uint32_t freq; + netdev->driver->get(netdev, NETOPT_CHANNEL_FREQUENCY, &freq, sizeof(uint32_t)); + printf("Frequency: %" PRIu32 "Hz\n", freq); + } + else if (!strcmp("bw", argv[2])) { + uint8_t bw; + netdev->driver->get(netdev, NETOPT_BANDWIDTH, &bw, sizeof(uint8_t)); + uint16_t bw_val = 0; + switch (bw) { + case LORA_BW_125_KHZ: + bw_val = 125; + break; + case LORA_BW_250_KHZ: + bw_val = 250; + break; + case LORA_BW_500_KHZ: + bw_val = 500; + break; + default: + break; + } + printf("Bandwidth: %ukHz\n", bw_val); + } + else if (!strcmp("sf", argv[2])) { + uint8_t sf; + netdev->driver->get(netdev, NETOPT_SPREADING_FACTOR, &sf, sizeof(uint8_t)); + printf("Spreading factor: %d\n", sf); + } + else if (!strcmp("cr", argv[2])) { + uint8_t cr; + netdev->driver->get(netdev, NETOPT_CODING_RATE, &cr, sizeof(uint8_t)); + printf("Coding rate: %d\n", cr); + } + else if (!strcmp("random", argv[2])) { + uint32_t rand; + netdev->driver->get(netdev, NETOPT_RANDOM, &rand, sizeof(uint32_t)); + printf("random number: %" PRIu32 "\n", rand); + } + else { + _get_usage(argv[0]); + return -1; + } + + return 0; +} + +static void _set_usage(const char *cmd) +{ + printf("Usage: %s set \n", cmd); +} + +static int sx126x_set_cmd(netdev_t *netdev, int argc, char **argv) +{ + if (argc != 4) { + _set_usage(argv[0]); + return -1; + } + + int ret = 0; + + if (!strcmp("freq", argv[2])) { + uint32_t freq = atoi(argv[3]); + ret = netdev->driver->set(netdev, NETOPT_CHANNEL_FREQUENCY, &freq, sizeof(uint32_t)); + } + else if (!strcmp("bw", argv[2])) { + uint8_t bw; + if (!strcmp("125", argv[3])) { + bw = LORA_BW_125_KHZ; + } + else if (!strcmp("250", argv[3])) { + bw = LORA_BW_250_KHZ; + } + else if (!strcmp("500", argv[3])) { + bw = LORA_BW_500_KHZ; + } + else { + puts("invalid bandwidth, use 125, 250 or 500"); + return -1; + } + ret = netdev->driver->set(netdev, NETOPT_BANDWIDTH, &bw, sizeof(uint8_t)); + } + else if (!strcmp("sf", argv[2])) { + uint8_t sf = atoi(argv[3]); + ret = netdev->driver->set(netdev, NETOPT_SPREADING_FACTOR, &sf, sizeof(uint8_t)); + } + else if (!strcmp("cr", argv[2])) { + uint8_t cr = atoi(argv[3]); + ret = netdev->driver->set(netdev, NETOPT_CODING_RATE, &cr, sizeof(uint8_t)); + } + else { + _set_usage(argv[0]); + return -1; + } + + if (ret < 0) { + printf("cannot set %s\n", argv[2]); + return ret; + } + + printf("%s set\n", argv[2]); + return 0; +} + +static void _rx_usage(const char *cmd) +{ + printf("Usage: %s rx \n", cmd); +} + +static int sx126x_rx_cmd(netdev_t *netdev, int argc, char **argv) +{ + if (argc == 2) { + _rx_usage(argv[0]); + return -1; + } + + if (!strcmp("start", argv[2])) { + /* Switch to RX state */ + netopt_state_t state = NETOPT_STATE_IDLE; + netdev->driver->set(netdev, NETOPT_STATE, &state, sizeof(state)); + printf("Listen mode started\n"); + } + else if (!strcmp("stop", argv[2])) { + /* Switch to RX state */ + netopt_state_t state = NETOPT_STATE_STANDBY; + netdev->driver->set(netdev, NETOPT_STATE, &state, sizeof(state)); + printf("Listen mode stopped\n"); + } + else { + _rx_usage(argv[0]); + return -1; + } + + return 0; +} + +static int sx126x_tx_cmd(netdev_t *netdev, int argc, char **argv) +{ + if (argc == 2) { + printf("Usage: %s tx \n", argv[0]); + return -1; + } + + printf("sending \"%s\" payload (%u bytes)\n", + argv[2], (unsigned)strlen(argv[2]) + 1); + iolist_t iolist = { + .iol_base = argv[2], + .iol_len = (strlen(argv[2]) + 1) + }; + + if (netdev->driver->send(netdev, &iolist) == -ENOTSUP) { + puts("Cannot send: radio is still transmitting"); + return -1; + } + + return 0; +} + +int sx126x_cmd(int argc, char **argv) +{ + if (argc < 2) { + printf("Usage: %s \n", argv[0]); + return -1; + } + + netdev_t *netdev = (netdev_t *)&sx126x; + + if (!strcmp("get", argv[1])) { + return sx126x_get_cmd(netdev, argc, argv); + } + else if (!strcmp("set", argv[1])) { + return sx126x_set_cmd(netdev, argc, argv); + } + else if (!strcmp("rx", argv[1])) { + return sx126x_rx_cmd(netdev, argc, argv); + } + else if (!strcmp("tx", argv[1])) { + return sx126x_tx_cmd(netdev, argc, argv); + } + + return 0; +} + +static const shell_command_t shell_commands[] = { + { "sx126x", "Control the SX126X radio", sx126x_cmd }, + { NULL, NULL, NULL } +}; + +int main(void) +{ + sx126x_setup(&sx126x, &sx126x_params[0], 0); + netdev_t *netdev = (netdev_t *)&sx126x; + + netdev->driver = &sx126x_driver; + + if (netdev->driver->init(netdev) < 0) { + puts("Failed to initialize SX126X device, exiting"); + return 1; + } + + netdev->event_callback = _event_cb; + + _recv_pid = thread_create(stack, sizeof(stack), THREAD_PRIORITY_MAIN - 1, + THREAD_CREATE_STACKTEST, _recv_thread, netdev, + "recv_thread"); + + if (_recv_pid <= KERNEL_PID_UNDEF) { + puts("Creation of receiver thread failed"); + return 1; + } + + /* start the shell */ + puts("Initialization successful - starting the shell now"); + char line_buf[SHELL_DEFAULT_BUFSIZE]; + + shell_run(shell_commands, line_buf, SHELL_DEFAULT_BUFSIZE); + return 0; +} From bede0cd98ca80927bb2754e9cc6cd98b3d0dd700 Mon Sep 17 00:00:00 2001 From: Alexandre Abadie Date: Sat, 27 Mar 2021 15:23:01 +0100 Subject: [PATCH 5/6] sys/net/auto_init: add sx126x auto_init logic --- .../gnrc/netif/init_devs/auto_init_sx126x.c | 71 +++++++++++++++++++ sys/net/gnrc/netif/init_devs/init.c | 5 ++ 2 files changed, 76 insertions(+) create mode 100644 sys/net/gnrc/netif/init_devs/auto_init_sx126x.c diff --git a/sys/net/gnrc/netif/init_devs/auto_init_sx126x.c b/sys/net/gnrc/netif/init_devs/auto_init_sx126x.c new file mode 100644 index 0000000000..b680a6848d --- /dev/null +++ b/sys/net/gnrc/netif/init_devs/auto_init_sx126x.c @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2021 Inria + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + * + */ + +/** + * @ingroup sys_auto_init_gnrc_netif + * @{ + * + * @file + * @brief Auto initialization for SX1261/2 LoRa interfaces + * + * @author Alexandre Abadie + */ + +#include + +#include "log.h" +#include "board.h" +#include "net/gnrc/netif/lorawan_base.h" +#include "net/gnrc/netif/raw.h" +#include "net/gnrc.h" + +#include "sx126x.h" +#include "sx126x_params.h" + +/** + * @brief Calculate the number of configured SX126X devices + */ +#define SX126X_NUMOF ARRAY_SIZE(sx126x_params) + +/** + * @brief Define stack parameters for the MAC layer thread + */ +#define SX126X_STACKSIZE (THREAD_STACKSIZE_DEFAULT) +#ifndef SX126X_PRIO +#define SX126X_PRIO (GNRC_NETIF_PRIO) +#endif + +/** + * @brief Allocate memory for device descriptors, stacks, and GNRC adaption + */ +static sx126x_t sx126x_devs[SX126X_NUMOF]; +static char sx126x_stacks[SX126X_NUMOF][SX126X_STACKSIZE]; +static gnrc_netif_t _netif[SX126X_NUMOF]; + +void auto_init_sx126x(void) +{ + for (unsigned i = 0; i < SX126X_NUMOF; ++i) { + LOG_DEBUG("[auto_init_netif] initializing sx126x #%u\n", i); + sx126x_setup(&sx126x_devs[i], &sx126x_params[i], i); + if (IS_USED(MODULE_GNRC_NETIF_LORAWAN)) { + /* Currently only one lora device is supported */ + assert(SX126X_NUMOF == 1); + + gnrc_netif_lorawan_create(&_netif[i], sx126x_stacks[i], + SX126X_STACKSIZE, SX126X_PRIO, + "sx126x", (netdev_t *)&sx126x_devs[i]); + } + else { + gnrc_netif_raw_create(&_netif[i], sx126x_stacks[i], + SX126X_STACKSIZE, SX126X_PRIO, + "sx126x", (netdev_t *)&sx126x_devs[i]); + } + } +} +/** @} */ diff --git a/sys/net/gnrc/netif/init_devs/init.c b/sys/net/gnrc/netif/init_devs/init.c index e164d6263c..f567120ac6 100644 --- a/sys/net/gnrc/netif/init_devs/init.c +++ b/sys/net/gnrc/netif/init_devs/init.c @@ -166,4 +166,9 @@ void gnrc_netif_init_devs(void) extern void auto_init_nrf802154(void); auto_init_nrf802154(); } + + if (IS_USED(MODULE_SX126X)) { + extern void auto_init_sx126x(void); + auto_init_sx126x(); + } } From 10ab3c2134390b4e61c68161af1b579500a3b69f Mon Sep 17 00:00:00 2001 From: Alexandre Abadie Date: Sat, 27 Mar 2021 15:26:01 +0100 Subject: [PATCH 6/6] drivers: register sx126x in Kconfig net list --- pkg/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/Kconfig b/pkg/Kconfig index 4eb8695c0f..144ddfcf56 100644 --- a/pkg/Kconfig +++ b/pkg/Kconfig @@ -7,6 +7,7 @@ menu "Packages" rsource "driver_bme680/Kconfig" +rsource "driver_sx126x/Kconfig" rsource "semtech-loramac/Kconfig" rsource "tinydtls/Kconfig" rsource "umorse/Kconfig"