Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Add SubGhz library to access SubGhz module
This allows accessing some of the signals internally connected to the SubGhz radio (that would have been GPIO signals if the radio was external). This also allocates and exposes the SPI object connected to the SubGhz radio block and handles attaching handlers to the radio interrupt. Note that the DIO signals are *not* exposed, since there is no way to read them directly (and indirectly reading them through the IRQ pending flag does not work in all cases).
- Loading branch information
1 parent
cf79dc6
commit 20fd791
Showing
6 changed files
with
445 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 <SubGhz.h> | ||
|
||
|
||
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 */ | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
####################################### |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 <Arduino.h> | ||
#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()); | ||
} |
Oops, something went wrong.