From 5e47d944188642ceba765a733db4dead681bf0a9 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Thu, 3 Nov 2022 10:45:07 +0100 Subject: [PATCH] [STM32WLx] Add module for STM32WL MCUs with integrated radio (#588) This is a nearly complete implementation, except that the Dio1 interrupt is not yet supported (this will be added in a subsequent commit to simplify review). This fixes #588. --- src/BuildOpt.h | 1 + src/Module.h | 7 +- src/RadioLib.h | 1 + src/modules/SX126x/STM32WLx.cpp | 76 +++++++++++++++ src/modules/SX126x/STM32WLx.h | 122 +++++++++++++++++++++++++ src/modules/SX126x/STM32WLx_Module.cpp | 99 ++++++++++++++++++++ src/modules/SX126x/STM32WLx_Module.h | 49 ++++++++++ 7 files changed, 354 insertions(+), 1 deletion(-) create mode 100644 src/modules/SX126x/STM32WLx.cpp create mode 100644 src/modules/SX126x/STM32WLx.h create mode 100644 src/modules/SX126x/STM32WLx_Module.cpp create mode 100644 src/modules/SX126x/STM32WLx_Module.h diff --git a/src/BuildOpt.h b/src/BuildOpt.h index e28e7a5c..2e2e8835 100644 --- a/src/BuildOpt.h +++ b/src/BuildOpt.h @@ -96,6 +96,7 @@ //#define RADIOLIB_EXCLUDE_SX127X //#define RADIOLIB_EXCLUDE_RFM9X // dependent on RADIOLIB_EXCLUDE_SX127X //#define RADIOLIB_EXCLUDE_SX126X + //#define RADIOLIB_EXCLUDE_STM32WLX // dependent on RADIOLIB_EXCLUDE_SX126X //#define RADIOLIB_EXCLUDE_SX128X //#define RADIOLIB_EXCLUDE_AFSK //#define RADIOLIB_EXCLUDE_AX25 diff --git a/src/Module.h b/src/Module.h index 9958a30f..78793d0a 100644 --- a/src/Module.h +++ b/src/Module.h @@ -44,10 +44,15 @@ class Module { * See setRfSwitchTable() for details. */ enum OpMode_t { - // Table end marker is zero to ensure zero-initialized mode ends the table + /*! End of table marker, use \ref END_OF_MODE_TABLE constant + * instead. Value is zero to ensure zero-initialized mode ends the + * table */ MODE_END_OF_TABLE = 0, + /*! Idle mode */ MODE_IDLE, + /*! Receive mode */ MODE_RX, + /*! Transmission mode */ MODE_TX, }; diff --git a/src/RadioLib.h b/src/RadioLib.h index be496dd6..419e0bea 100644 --- a/src/RadioLib.h +++ b/src/RadioLib.h @@ -78,6 +78,7 @@ #include "modules/SX126x/SX1261.h" #include "modules/SX126x/SX1262.h" #include "modules/SX126x/SX1268.h" +#include "modules/SX126x/STM32WLx.h" #include "modules/SX127x/SX1272.h" #include "modules/SX127x/SX1273.h" #include "modules/SX127x/SX1276.h" diff --git a/src/modules/SX126x/STM32WLx.cpp b/src/modules/SX126x/STM32WLx.cpp new file mode 100644 index 00000000..a1ec8a58 --- /dev/null +++ b/src/modules/SX126x/STM32WLx.cpp @@ -0,0 +1,76 @@ +/* + +Copyright (c) 2018 Jan Gromeš +Copyright (c) 2022 STMicroelectronics + +This file is licensed under the MIT License: https://opensource.org/licenses/MIT +*/ + +#include "STM32WLx.h" +#if !defined(RADIOLIB_EXCLUDE_STM32WLX) + + +STM32WLx::STM32WLx(STM32WLx_Module* mod) : SX1262(mod) { +} + +int16_t STM32WLx::begin(float freq, float bw, uint8_t sf, uint8_t cr, uint8_t syncWord, int8_t power, uint16_t preambleLength, float tcxoVoltage, bool useRegulatorLDO) { + // Execute common part + int16_t state = SX1262::begin(freq, bw, sf, cr, syncWord, power, preambleLength, tcxoVoltage, useRegulatorLDO); + RADIOLIB_ASSERT(state); + + // This overrides the value in SX126x::begin() + // On STM32WL, DIO2 is hardwired to the radio IRQ on the MCU, so it + // should really not be used as RfSwitch control output. + state = setDio2AsRfSwitch(false); + RADIOLIB_ASSERT(state); + + return(state); +} + +int16_t STM32WLx::beginFSK(float freq, float br, float freqDev, float rxBw, int8_t power, uint16_t preambleLength, float tcxoVoltage, bool useRegulatorLDO) { + // Execute common part + int16_t state = SX1262::beginFSK(freq, br, freqDev, rxBw, power, preambleLength, tcxoVoltage, useRegulatorLDO); + RADIOLIB_ASSERT(state); + + // This overrides the value in SX126x::beginFSK() + // On STM32WL, DIO2 is hardwired to the radio IRQ on the MCU, so it + // should really not be used as RfSwitch control output. + state = setDio2AsRfSwitch(false); + RADIOLIB_ASSERT(state); + + return(state); +} + +int16_t STM32WLx::setOutputPower(int8_t power) { + // get current OCP configuration + uint8_t ocp = 0; + int16_t state = readRegister(RADIOLIB_SX126X_REG_OCP_CONFIGURATION, &ocp, 1); + RADIOLIB_ASSERT(state); + + // Use HP only if available and needed for the requested power + bool hp_supported = _mod->findRfSwitchMode(MODE_TX_HP); + bool use_hp = power > 14 && hp_supported; + + // set PA config. + if(use_hp) { + RADIOLIB_CHECK_RANGE(power, -9, 22, RADIOLIB_ERR_INVALID_OUTPUT_POWER); + state = SX126x::setPaConfig(0x04, 0x00, 0x07); // HP output up to 22dBm + _tx_mode = MODE_TX_HP; + } else { + RADIOLIB_CHECK_RANGE(power, -17, 14, RADIOLIB_ERR_INVALID_OUTPUT_POWER); + state = SX126x::setPaConfig(0x04, 0x01, 0x00); // LP output up to 14dBm + _tx_mode = MODE_TX_LP; + } + RADIOLIB_ASSERT(state); + + // set output power + /// \todo power ramp time configuration + state = SX126x::setTxParams(power); + RADIOLIB_ASSERT(state); + + // restore OCP configuration + return(writeRegister(RADIOLIB_SX126X_REG_OCP_CONFIGURATION, &ocp, 1)); +} + + +#endif // !defined(RADIOLIB_EXCLUDE_STM32WLX) diff --git a/src/modules/SX126x/STM32WLx.h b/src/modules/SX126x/STM32WLx.h new file mode 100644 index 00000000..d90f1450 --- /dev/null +++ b/src/modules/SX126x/STM32WLx.h @@ -0,0 +1,122 @@ +/* + +Copyright (c) 2018 Jan Gromeš +Copyright (c) 2022 STMicroelectronics + +This file is licensed under the MIT License: https://opensource.org/licenses/MIT +*/ + +#if !defined(_RADIOLIB_STM32WLx_H) +#define _RADIOLIB_STM32WLx_H + +#include "../../TypeDef.h" + +#if !defined(RADIOLIB_EXCLUDE_STM32WLX) + +#include "../../Module.h" +#include "SX1262.h" +#include "STM32WLx_Module.h" + +/*! + \class STM32WLx + + \brief Derived class for STM32WL modules. + + The radio integrated into these modules is essentially the same as the + Semtech %SX126x external radio chips, so most of the documentation for + those also applies here. + + One notable difference with the %SX126x radios is that this radio + essentially combines the %SX1261 and %SX1262 by integrating both the + low-power (LP) and high-power (HP) amplifier. See setOutputPower() and + setRfSwitchTable() for details on how this is handled. +*/ +class STM32WLx : public SX1262 { + // NOTE: This class could not be named STM32WL (or STM32WLxx), since + // those are macros defined by + // system/Drivers/CMSIS/Device/ST/STM32WLxxx/Include/stm32wlxx.h + public: + /*! + \brief Default constructor. + + \param mod Instance of STM32WLx_Module that will be used to communicate with the radio. + */ + STM32WLx(STM32WLx_Module* mod); + + /*! + * \brief Custom operation modes for STMWLx. + * + * This splits the TX mode into two modes: Low-power and high-power. + * These constants can be used with the setRfSwitchTable() method, + * instead of the Module::OpMode_t constants. + */ + enum OpMode_t { + /*! End of table marker, use \ref END_OF_MODE_TABLE constant instead */ + MODE_END_OF_TABLE = Module::MODE_END_OF_TABLE, + /*! Idle mode */ + MODE_IDLE = Module::MODE_IDLE, + /*! Receive mode */ + MODE_RX = Module::MODE_RX, + /*! Low power transmission mode */ + MODE_TX_LP = Module::MODE_TX, + /*! High power transmission mode */ + MODE_TX_HP, + }; + /*! \copydoc Module::END_OF_MODE_TABLE */ + static constexpr auto END_OF_MODE_TABLE = Module::END_OF_MODE_TABLE; + + // basic methods + + /*! + \copydoc SX1262::begin + */ + int16_t begin(float freq = 434.0, float bw = 125.0, uint8_t sf = 9, uint8_t cr = 7, uint8_t syncWord = RADIOLIB_SX126X_SYNC_WORD_PRIVATE, int8_t power = 10, uint16_t preambleLength = 8, float tcxoVoltage = 1.6, bool useRegulatorLDO = false); + + /*! + \copydoc SX1262::beginFSK + */ + int16_t beginFSK(float freq = 434.0, float br = 4.8, float freqDev = 5.0, float rxBw = 156.2, int8_t power = 10, uint16_t preambleLength = 16, float tcxoVoltage = 1.6, bool useRegulatorLDO = false); + + // configuration methods + + /*! + \brief Sets output power. Allowed values are in range from -17 to 22 dBm. + + This automatically switches between the low-power (LP) and high-power (HP) amplifier. + + LP is preferred and supports -17 to +14dBm. When a higher power is + requested (or the LP amplifier is marked as unvailable using + setRfSwitchTable()), HP is used, which supports -9 to +22dBm. + + \param power Output power to be set in dBm. + + \returns \ref status_codes + */ + virtual int16_t setOutputPower(int8_t power) override; + + /*! + \copybrief Module::setRfSwitchTable + + This method works like Module::setRfSwitchTable(), except that you + should use STM32WLx::OpMode_t constants for modes, which + distinguishes between a low-power (LP) and high-power (HP) TX mode. + + For boards that do not support both modes, just omit the + unsupported mode from the table and it will not be used (and the + valid power range is adjusted by setOutputPower() accordingly). + + Note that the setRfSwitchTable() method should be called *before* the + begin() method, to ensure the radio knows which modes are supported + during initialization. + */ + // Note: This explicitly inherits this method only to override docs + using SX126x::setRfSwitchTable; + +#if !defined(RADIOLIB_GODMODE) + private: +#endif +}; + +#endif // !defined(RADIOLIB_EXCLUDE_SX126X) + +#endif // _RADIOLIB_STM32WLX_MODULE_H diff --git a/src/modules/SX126x/STM32WLx_Module.cpp b/src/modules/SX126x/STM32WLx_Module.cpp new file mode 100644 index 00000000..1fa4725a --- /dev/null +++ b/src/modules/SX126x/STM32WLx_Module.cpp @@ -0,0 +1,99 @@ +/* + +Copyright (c) 2022 STMicroelectronics + +This file is licensed under the MIT License: https://opensource.org/licenses/MIT +*/ + +#include "STM32WLx_Module.h" + +#if !defined(RADIOLIB_EXCLUDE_STM32WLX) + +#include + +// This defines some dummy pin numbers (starting at NUM_DIGITAL_PINS to +// guarantee these are not valid regular pin numbers) that can be passed +// to the parent Module class, to be stored here and then passed back to +// the overridden callbacks when these are used. +enum { + RADIOLIB_STM32WLx_VIRTUAL_PIN_NSS = NUM_DIGITAL_PINS, + RADIOLIB_STM32WLx_VIRTUAL_PIN_BUSY, + RADIOLIB_STM32WLx_VIRTUAL_PIN_IRQ, + RADIOLIB_STM32WLx_VIRTUAL_PIN_RESET, +}; + + +STM32WLx_Module::STM32WLx_Module(): + Module( + RADIOLIB_STM32WLx_VIRTUAL_PIN_NSS, + RADIOLIB_STM32WLx_VIRTUAL_PIN_IRQ, + RADIOLIB_STM32WLx_VIRTUAL_PIN_RESET, + RADIOLIB_STM32WLx_VIRTUAL_PIN_BUSY, + SubGhz.SPI, + SubGhz.spi_settings + ) +{ + setCb_pinMode(virtualPinMode); + setCb_digitalWrite(virtualDigitalWrite); + setCb_digitalRead(virtualDigitalRead); +} + +void STM32WLx_Module::virtualPinMode(uint32_t dwPin, uint32_t dwMode) { + switch(dwPin) { + case RADIOLIB_STM32WLx_VIRTUAL_PIN_NSS: + case RADIOLIB_STM32WLx_VIRTUAL_PIN_BUSY: + case RADIOLIB_STM32WLx_VIRTUAL_PIN_IRQ: + case RADIOLIB_STM32WLx_VIRTUAL_PIN_RESET: + // Nothing to do + break; + default: + ::pinMode(dwPin, dwMode); + break; + } +} + +void STM32WLx_Module::virtualDigitalWrite(uint32_t dwPin, uint32_t dwVal) { + switch (dwPin) { + case RADIOLIB_STM32WLx_VIRTUAL_PIN_NSS: + SubGhz.setNssActive(dwVal == LOW); + break; + + case RADIOLIB_STM32WLx_VIRTUAL_PIN_RESET: + SubGhz.setResetActive(dwVal == LOW); + break; + + case RADIOLIB_STM32WLx_VIRTUAL_PIN_BUSY: + case RADIOLIB_STM32WLx_VIRTUAL_PIN_IRQ: + // Should not (and cannot) be written, just ignore + break; + + default: + ::digitalWrite(dwPin, dwVal); + break; + } +} + +int STM32WLx_Module::virtualDigitalRead(uint32_t ulPin) { + switch (ulPin) { + case RADIOLIB_STM32WLx_VIRTUAL_PIN_BUSY: + return(SubGhz.isBusy() ? HIGH : LOW); + + case RADIOLIB_STM32WLx_VIRTUAL_PIN_IRQ: + // If the IRQ is disabled, we can read the value by clearing and + // seeing if it immediately becomes pending again. + // TODO: This seems a bit fragile, but it does work. + SubGhz.clearPendingInterrupt(); + return(SubGhz.isInterruptPending() ? HIGH : LOW); + + case RADIOLIB_STM32WLx_VIRTUAL_PIN_NSS: + return(SubGhz.isNssActive() ? LOW : HIGH); + + case RADIOLIB_STM32WLx_VIRTUAL_PIN_RESET: + return(SubGhz.isResetActive() ? LOW : HIGH); + + default: + return(::digitalRead(ulPin)); + } +} + +#endif // !defined(RADIOLIB_EXCLUDE_STM32WLX) diff --git a/src/modules/SX126x/STM32WLx_Module.h b/src/modules/SX126x/STM32WLx_Module.h new file mode 100644 index 00000000..05f2b2fe --- /dev/null +++ b/src/modules/SX126x/STM32WLx_Module.h @@ -0,0 +1,49 @@ +/* + +Copyright (c) 2022 STMicroelectronics + +This file is licensed under the MIT License: https://opensource.org/licenses/MIT +*/ + +#if !defined(_RADIOLIB_STM32WLX_MODULE_H) +#define _RADIOLIB_STM32WLX_MODULE_H + +#include "../../TypeDef.h" + +#if !defined(RADIOLIB_EXCLUDE_STM32WLX) + +#include "../../Module.h" + +/*! + * \class STM32WLx_Module + * + * This is a subclass of Module to be used with the STM32WLx driver. + * + * It is used to override some callbacks, allowing access to some of the + * radio control signals that are wired to internal registers instead of + * actual GPIO pins. + */ +class STM32WLx_Module : public Module { + // Note: We cannot easily override any methods here, since most calls + // are non-virtual and made through a Module*, so they would not be + // calling any overridden methods. This means this class works by + // overriding some of the callbacks in its constructor. + + public: + STM32WLx_Module(); + +#if !defined(RADIOLIB_GODMODE) + private: +#endif + + // Replacement callbacks to handle virtual pins. These are static, + // since they replace global functions that cannot take any this + // pointer for context. + static void virtualPinMode(uint32_t dwPin, uint32_t dwMode); + static void virtualDigitalWrite(uint32_t dwPin, uint32_t dwVal); + static int virtualDigitalRead(uint32_t ulPin); +}; + +#endif // !defined(RADIOLIB_EXCLUDE_STM32WLX) + +#endif // _RADIOLIB_STM32WLX_MODULE_H