RadioLibSmol/src/modules/SX126x/STM32WLx.cpp
Matthijs Kooijman 4c712c1f2c [MOD] Remove constexpr usage
This was introduced when STM32WL support was added. Using constexpr for
the END_OF_MODE_TABLE constant allows it to be initialized in the class
declaration, but this needs C++11. This moves the initialization out of
the class declaration to the .cpp file, which does not need constexpr.
It also lets STM32WLx::END_OF_MODE_TABLE define its value directly
(instead of aliasing Module::END_OF_MODE_TABLE) to prevent reduce
runtime overhead (see below).

The downside of this change is that the value of the END_OF_MODE_TABLE
is no longer visible in other compilation units and thus cannot be
inlined into the rfswitch_table (if used).

For example, on STM32, this means that instead of having a pre-cooked
rfswitch_table that lives in the .rodata section (so can be read
directly from flash), the table lives in RAM and is initialized at
runtime (the actual modes and pins are copied from flash to RAM by the
standard startup loop that copies all of the .data section, and the
END_OF_MODE_TABLE value is copied by a bit of new generated code). This
means a little more runtime overhead, but the cost is mostly in RAM size
(80 bytes for the SMT32WL sketches, 16 per mode plus 16 for the
END_OF_MODE_TABLE).

In a first attempt at this commit, STM32WLx::END_OF_MODE_TABLE was still
initialized using the Module::END_OF_MODE_TABLE value, but since the
latter is also not available at compiletime, this meant initialization
of the former also needed to happen at runtime, adding even more code
overhead (and possibly leading to ordering issues as well). To avoid
this, the STM32WLx::END_OF_MODE_TABLE initialization now just duplicates
that of Module::END_OF_MODE_TABLE.

On AVR, the impact is not so much: Since AVR cannot address flash
directly, the table was already copied from flash to RAM at startup, so
the extra RAM usage is just 4 bytes because END_OF_MODE_TABLE is now
also present in RAM, to be copied into rfswitch_table at startup.

Options for avoiding this overhead (not implemented in this commit)
could be (in no particular order):

1. Use a macro instead of a constant. Downside is that these cannot be
   scoped inside the Module/STM32WLx classes like now, so this requires
   changes to sketches that use a rfswitch_table (and reduced scoping
   and using macros adds more opportunity for conflicts and weird
   errors).
2. Apply the change in this commit only when C++11 is not available.
   Downside is that the initialization value of these constants must be
   duplicated in the .h and .cpp file for C++ and older versions
   respectively.
3. Let sketches just use `{Module::MODE_END_OF_TABLE, {}}` explicitly
   instead of `Module::END_OF_MODE_TABLE`. Downside of this is that this
   requires sketches to be modified and that it lets the sketch encode
   more of the table structure, potentially making future API changes
   harder (but it probably does not really matter in practice).
4. Turn END_OF_MODE_TABLE into a static method, which *can* then be
   defined in the class declaration and inlined. The method can then be
   conditionally marked as constexpr, which allows C++11 compilers to
   completely resolve the rfswitch_table value at compiletime, producing
   a binary identical to before this commit. When constexpr is omitted
   (e.g. on older compilers), some runtime overhead is added (pretty
   much the same as the result from this commit).  Downside is that
   sketches must be modified, and the `END_OF_MODE_TABLE` "constant"
   must now be called, e.g.  `END_OF_MODE_TABLE()` which might be a bit
   unexpected syntax.
2023-02-03 12:42:49 +01:00

108 lines
3.6 KiB
C++

/*
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)
#include <SubGhz.h>
const Module::RfSwitchMode_t STM32WLx::END_OF_MODE_TABLE = {Module::MODE_END_OF_TABLE, {}};
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);
// Apply workaround for HP only
state = SX126x::fixPaClamping(use_hp);
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));
}
int16_t STM32WLx::clearIrqStatus(uint16_t clearIrqParams) {
int16_t res = SX126x::clearIrqStatus(clearIrqParams);
// The NVIC interrupt is level-sensitive, so clear away any pending
// flag that is only set because the radio IRQ status was not cleared
// in the interrupt (to prevent each IRQ triggering twice and allow
// reading the irq status through the pending flag).
SubGhz.clearPendingInterrupt();
if(SubGhz.hasInterrupt())
SubGhz.enableInterrupt();
return(res);
}
void STM32WLx::setDio1Action(void (*func)(void)) {
SubGhz.attachInterrupt([func]() {
// Because the interrupt is level-triggered, we disable it in the
// NVIC (otherwise we would need an SPI command to clear the IRQ in
// the radio, or it would trigger over and over again).
SubGhz.disableInterrupt();
func();
});
}
void STM32WLx::clearDio1Action() {
SubGhz.detachInterrupt();
}
#endif // !defined(RADIOLIB_EXCLUDE_STM32WLX)