[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.
This commit is contained in:
Matthijs Kooijman 2022-11-03 10:45:07 +01:00
parent da6c3f6a6b
commit 5e47d94418
7 changed files with 354 additions and 1 deletions

View file

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

View file

@ -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,
};

View file

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

View file

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

View file

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

View file

@ -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 <SubGhz.h>
// 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)

View file

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