diff --git a/libraries/SubGhz/README.md b/libraries/SubGhz/README.md new file mode 100644 index 0000000000..1b5af42233 --- /dev/null +++ b/libraries/SubGhz/README.md @@ -0,0 +1,134 @@ +## SubGhz library + +This library gives some low-level control over the SubGhz radio module +present in the STM32WL series of microcontrollers. + +This radio module is integrated into the microcontroller chip. It is +essentially a Semtech SX126x radio chip (a combination of the SX1261 and +SX1262 really), connected to a dedicated internal SPI bus. + +This library offers an Arduino-style API to access this module on a low +level, where the user is responsible for composing and sending the right +SPI commands to operate the module. It does *not* offer a high-level +interface for sending or receiving data. + +### Using this library +This library defines a single class and predefines an instance of that +class called `SubGhz`. To use this library, call methods on this +instance, e.g.: + +``` +SubGhz.setNssActive(true); +``` + +#### SPI access +Access to the dedicated SPI bus happens through the `SubGhz.SPI` object. +This behaves just like the normal Arduino SPI object (it is in fact +a customized subclass of the normal SPIClass, provided by the normal SPI +library). + +If you use the `beginTransaction()` method, you can pass the +`SubGhz.spi_settings` variable to get the right settings and run at the +maximum supported speed. If not using `beginTransaction()`, the default +settings are also fine, just a bit slower. + +#### "GPIO" signals +Some of the signals which are normally (on an actual external SX126x +radio chip) available externally and controlled by GPIO are now +internally connected to registers in the RCC and PWR modules. These +signals (Nss, Reset and Busy) can be controlled and/or read through this +library. + +The Nss signal can be written (and read back) using +`SubGhz.setNssActive(bool)` and `SubGhz.isNssActive()`, the Reset signal +using `SubGhz.setResetActive(bool) and `SubGhz.isResetActive()` and the +busy signal can only be read using `SubGhz.isBusy()`. + +There is no need to configure these signals before using them (i.e. +nothing like `pinMode()` is needed). + +Note that there is no way to read the DIO signals directly, since they +are not directly available to the MCU anywhere (only through the +interrupt controller, so with some care the DIO signal status could be +derived from the interrupt pending status if needed). + +#### Interrupts +In addition, the DIO signals produced by the radio module are connected +together in an "OR" configuration and drive the radio interrupt. This +interrupt can be enabled and configured using various methods in this +library. + +The interrupt can be attached (and enabled) using the +`SubGhz.attachInterrupt(callback)` method. You can pass any callable here that +`std::function` accepts (global function, method bound with `std::bind`, +callable object or a lambda). Attaching an interrupt clears any +previously pending interrupts and enables the interrupt. + +The `SubGhz.detachInterrupt()` method can be used to disable the +interrupt and clear the handler and the `SubGhz.hasInterrupt()` method +can be used to query if a handler was attached (regardless of whether it +is currently enabled). + +The `SubGhz.disableInterrupt()` and `SubGhz.enableInterrupt()` method +can be used to temporarily disable the interrupt. If the interrupt is +triggered while it is disabled, it will become pending (indicated by the +`SubGhz.isInterruptPending()` method) and the interrupt handler will run +directly when the interrupt is enabled again (unless cleared with +`SubGhz.clearPendingInterrupt()`). Note that there is no method to query +whether the interrupt is currently enabled, as the interrupt controller +hardware does allow reading back this value. + +Note that since these DIO signals are always connected to the interrupt +controller, drive a single interrupt and are not available on any +physical pins or anywhere else, there is really little point in having +multiple DIO signals, but this is just how the original Semtech radio +was designed. + +Also note that the DIO lines are directly connected to the MCU interrupt +controller (NVIC), which is level sensitive. When the ISR is triggered, +it should always either clear the interrupt flag in the radio, or +disable the interrupt in the NVIC (using `SubGhz.disableInterrupt()` in +this library) to prevent the ISR from triggering again (and again and +again etc.) after it completes. + +### Example + +This is a basic example of initializing the radio and SPI bus and +reading a register through an SPI command. See the examples folder for +a full example sketch. + +```C++ + // initialize SPI: + SubGhz.SPI.begin(); + + // clear reset to + SubGhz.setResetActive(false); + + // Start SPI transaction and wait for the radio to wake up (it starts + // in sleep mode with its busy signal active). + SubGhz.SPI.beginTransaction(SubGhz.spi_settings); + SubGhz.setNssActive(true); + while (SubGhz.isBusy()) /* wait */; + + // Write a command and read the result + SubGhz.SPI.transfer(0x1D); // Write command: Read_Register() + SubGhz.SPI.transfer(0x07); // Write MSB of register address: SUBGHZ_LSYNCRH + SubGhz.SPI.transfer(0x40); // Write LSB of register address: SUBGHZ_LSYNCRH + uint8_t status = SubGhz.SPI.transfer(0x0); // Read status + uint8_t value = SubGhz.SPI.transfer(0x0); // Read register value + + // End transaction + SubGhz.setNssActive(false); + SubGhz.SPI.endTransaction(); + + // value now has the register value read +``` + +### License +Copyright (c) 2022, STMicroelectronics +All rights reserved. + +This software component is licensed by ST under BSD 3-Clause license, +the "License"; You may not use this file except in compliance with the +License. You may obtain a copy of the License at: + opensource.org/licenses/BSD-3-Clause diff --git a/libraries/SubGhz/examples/ReadRegister/ReadRegister.ino b/libraries/SubGhz/examples/ReadRegister/ReadRegister.ino new file mode 100644 index 0000000000..100a1a88e3 --- /dev/null +++ b/libraries/SubGhz/examples/ReadRegister/ReadRegister.ino @@ -0,0 +1,56 @@ +/* + ******************************************************************************* + * Copyright (c) 2022, STMicroelectronics + * All rights reserved. + * + * This software component is licensed by ST under BSD 3-Clause license, + * the "License"; You may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * opensource.org/licenses/BSD-3-Clause + * + ******************************************************************************* + * + * This example shows how to use the SubGhz library to start up the + * radio module and read a register through it using an SPI command. + * This reads the sync word MSB register, since that has a specific + * reset value. + */ +#include + + +void setup() { + Serial.begin(115200); + + // initialize SPI: + SubGhz.SPI.begin(); + + // clear reset to + SubGhz.setResetActive(false); + + // Start SPI transaction and wait for the radio to wake up (it starts + // in sleep mode with its busy signal active). + SubGhz.SPI.beginTransaction(SubGhz.spi_settings); + SubGhz.setNssActive(true); + while (SubGhz.isBusy()) /* wait */; + + // Write a command and read the result + SubGhz.SPI.transfer(0x1D); // Write command: Read_Register() + SubGhz.SPI.transfer(0x07); // Write MSB of register address: SUBGHZ_LSYNCRH + SubGhz.SPI.transfer(0x40); // Write LSB of register address: SUBGHZ_LSYNCRH + uint8_t status = SubGhz.SPI.transfer(0x0); // Read status + uint8_t value = SubGhz.SPI.transfer(0x0); // Read register value + + // End transaction + SubGhz.setNssActive(false); + SubGhz.SPI.endTransaction(); + + // This should print a register value 0x14 (reset value of the sync word MSB) + Serial.print("Status: 0x"); + Serial.println(status, HEX); + Serial.print("Register value: 0x"); + Serial.println(value, HEX); +} + +void loop() { + /* Nothing to do */ +} diff --git a/libraries/SubGhz/keywords.txt b/libraries/SubGhz/keywords.txt new file mode 100644 index 0000000000..ef4bff18c8 --- /dev/null +++ b/libraries/SubGhz/keywords.txt @@ -0,0 +1,26 @@ +####################################### +# Datatypes (KEYWORD1) +####################################### +callback_function_t KEYWORD1 +SubGhzClass KEYWORD1 +SubGhz KEYWORD1 + +####################################### +# Functions (KEYWORD2) +####################################### +attachInterrupt KEYWORD2 +clearPendingInterrupt KEYWORD2 +detachInterrupt KEYWORD2 +disableInterrupt KEYWORD2 +enableInterrupt KEYWORD2 +hasInterrupt KEYWORD2 +isBusy KEYWORD2 +isInterruptPending KEYWORD2 +isNssActive KEYWORD2 +isResetActive KEYWORD2 +setNssActive KEYWORD2 +setResetActive KEYWORD2 + +####################################### +# Constants (LITERAL1) +####################################### diff --git a/libraries/SubGhz/library.properties b/libraries/SubGhz/library.properties new file mode 100644 index 0000000000..f57bbbc98f --- /dev/null +++ b/libraries/SubGhz/library.properties @@ -0,0 +1,9 @@ +name=SubGhz +version=1.0.0 +author=stm32duino +maintainer=stm32duino +sentence=Allows controlling the SubGhz radio on STM32WL MCUs +paragraph=This library allows access to some internal control signals and predefines the right SPI bus to talk to the radio module. +category=Communication +url=https://github.com/stm32duino/Arduino_Core_STM32 +architectures=stm32 diff --git a/libraries/SubGhz/src/SubGhz.cpp b/libraries/SubGhz/src/SubGhz.cpp new file mode 100644 index 0000000000..b5394fd55f --- /dev/null +++ b/libraries/SubGhz/src/SubGhz.cpp @@ -0,0 +1,102 @@ +/* + ******************************************************************************* + * Copyright (c) 2022, STMicroelectronics + * All rights reserved. + * + * This software component is licensed by ST under BSD 3-Clause license, + * the "License"; You may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * opensource.org/licenses/BSD-3-Clause + * + ******************************************************************************* + */ + +#include +#include "SubGhz.h" + +SubGhzClass SubGhz; + +extern "C" void SUBGHZ_Radio_IRQHandler(void) +{ + SubGhz.handleIrq(); +} + +constexpr SPISettings SubGhzClass::spi_settings; + +void SubGhzClass::handleIrq() +{ + if (callback) { + callback(); + } +} + +void SubGhzClass::attachInterrupt(callback_function_t callback) +{ + this->callback = callback; + HAL_NVIC_ClearPendingIRQ(SUBGHZ_Radio_IRQn); + enableInterrupt(); +} + +void SubGhzClass::detachInterrupt() +{ + disableInterrupt(); + this->callback = nullptr; +} + +bool SubGhzClass::hasInterrupt() +{ + return (bool)this->callback; +} + +void SubGhzClass::enableInterrupt() +{ + HAL_NVIC_EnableIRQ(SUBGHZ_Radio_IRQn); +} + +void SubGhzClass::disableInterrupt() +{ + HAL_NVIC_DisableIRQ(SUBGHZ_Radio_IRQn); +} + +void SubGhzClass::clearPendingInterrupt() +{ + HAL_NVIC_ClearPendingIRQ(SUBGHZ_Radio_IRQn); +} + +bool SubGhzClass::isInterruptPending() +{ + return HAL_NVIC_GetPendingIRQ(SUBGHZ_Radio_IRQn); +} + +void SubGhzClass::setNssActive(bool value) +{ + if (value) { + LL_PWR_SelectSUBGHZSPI_NSS(); + } else { + LL_PWR_UnselectSUBGHZSPI_NSS(); + } +} + +bool SubGhzClass::isNssActive() +{ + return LL_PWR_IsSUBGHZSPI_NSS_Selected(); +} + +void SubGhzClass::setResetActive(bool value) +{ + if (value) { + LL_RCC_RF_EnableReset(); + } else { + LL_RCC_RF_DisableReset(); + } +} + +bool SubGhzClass::isResetActive() +{ + return LL_RCC_RF_IsEnabledReset(); +} + +bool SubGhzClass::isBusy() +{ + return (LL_PWR_IsActiveFlag_RFBUSYS()); +} diff --git a/libraries/SubGhz/src/SubGhz.h b/libraries/SubGhz/src/SubGhz.h new file mode 100644 index 0000000000..178998ad9f --- /dev/null +++ b/libraries/SubGhz/src/SubGhz.h @@ -0,0 +1,118 @@ +/* + ******************************************************************************* + * Copyright (c) 2022, STMicroelectronics + * All rights reserved. + * + * This software component is licensed by ST under BSD 3-Clause license, + * the "License"; You may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * opensource.org/licenses/BSD-3-Clause + * + ******************************************************************************* + */ + +#ifndef __SUBGHZ_H +#define __SUBGHZ_H + +#include +#include +#include + +#if !defined(SUBGHZSPI_BASE) + #error "This board does not seem to have a SubGhz radio module, cannot use this library." +#endif + +extern "C" void SUBGHZ_Radio_IRQHandler(); + +/** + * Singleton class to manage the SubGHZ ISR and internal signals + */ +class SubGhzClass { + public: + using callback_function_t = std::function; + + // Attach the passed handler to be called (from ISR context) when + // the radio IRQ triggers, then clear any pending IRQs and enable + // it. + // + // Note that the radio IRQ is *level* triggered, so the callback + // should either disable directly process the IRQ and clear the IRQ + // flags in the radio (so the radio module clears its DIO lines + // before the ISR returns), or the callback should disable the IRQ + // to prevent it from being triggered over and over again. + void attachInterrupt(callback_function_t callback); + + // Detach the interrupt handler, disabling the IRQ too. + void detachInterrupt(); + + // Return whether an interrupt callback is currently attached + // (regardless of whether it is enabled or not). + bool hasInterrupt(); + + // Enable the interrupt again if it was previously disabled using + // disableInterrupt(). This does *not* clear any pending interrupts, + // so if the IRQ was triggered while it was disabled, this will + // cause the callback (previously registered with attachInterrupt) + // to fire directly. + void enableInterrupt(); + + // Temporarily disable interrupt processing by disabling the IRQ in + // the NVIC. + void disableInterrupt(); + + // Clear the interrupt pending flag (to suppress an IRQs that + // happened while the interrupt was disabled). + void clearPendingInterrupt(); + + // Returns whether the interrupt is currently pending (only really + // useful when the interrupt is disabled, or while some higher + // priority interrupt is currently running). + bool isInterruptPending(); + + // Update the NSS signal that is internally connected to the radio + // SPI block. + // This signal is active-low, so passing LOW activates the block, + // HIGH deactivates it. + void setNssActive(bool value); + + // Read back the value written to NSS + bool isNssActive(); + + // Update the RESET signal that is internally connected to the + // radio. Pass true to put the module in reset, or false to enable + // the module. + void setResetActive(bool value); + + // Read back the value written to RESET + bool isResetActive(); + + // Return the state of busy signal exported by the radio module. + // Note that this uses the RFBUSYS flag that returns the radio busy + // signal directly, not the RFBUSYMS flag that also returns an extra + // busy period after every SPI transaction. + bool isBusy(); + + // The SPI object that can be used to talk to the radio. Works + // like regular SPI objects, except that writeNSS() above must be + // used to control the (internal) NSS "pin". + SUBGHZSPIClass SPI; + + // SPI settings to use for the radio. This uses the maximum speed + // supported by the radio, which should always work (no chance of + // bad wiring that requires reducing the speed). + // This value should be passed to `SubGhz.SPI.beginTransaction()`. + static constexpr SPISettings spi_settings = {16000000, MSBFIRST, SPI_MODE_0}; + + protected: + // To access handleIrq() + friend void SUBGHZ_Radio_IRQHandler(); + void handleIrq(); + + private: + callback_function_t callback; +}; + +// Singleton instance, no other instances should be created +extern SubGhzClass SubGhz; + +#endif // __SUBGHZ_H