Skip to content

Commit b192b02

Browse files
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).
1 parent cf79dc6 commit b192b02

File tree

6 files changed

+444
-0
lines changed

6 files changed

+444
-0
lines changed

libraries/SubGhz/README.md

+133
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
## SubGhz library
2+
3+
This library gives some low-level control over the SubGhz radio module
4+
present in the STM32WL series of microcontrollers.
5+
6+
This radio module is integrated into the microcontroller chip. It is
7+
essentially a Semtech SX126x radio chip (a combination of the SX1261 and
8+
SX1262 really), connected to a dedicated internal SPI bus.
9+
10+
This library offers an Arduino-style API to access this module on a low
11+
level, where the user is responsible for composing and sending the right
12+
SPI commands to operate the module. It does *not* offer a high-level
13+
interface for sending or receiving data.
14+
15+
### Using this library
16+
This library defines a single class and predefines an instance of that
17+
class called `SubGhz`. To use this library, call methods on this
18+
instance, e.g.:
19+
20+
```
21+
SubGhz.setNssActive(true);
22+
```
23+
24+
#### SPI access
25+
Access to the dedicated SPI bus happens through the `SubGhz.SPI` object.
26+
This behaves just like the normal Arduino SPI object (it is in fact
27+
a customized subclass of the normal SPIClass, provided by the normal SPI
28+
library).
29+
30+
If you use the `beginTransaction()` method, you can pass the
31+
`SubGhz.spi_settings` variable to get the right settings and run at the
32+
maximum supported speed. If not using `beginTransaction()`, the default
33+
settings are also fine, just a bit slower.
34+
35+
#### "GPIO" signals
36+
Some of the signals which are normally (on an actual external SX126x
37+
radio chip) available externally and controlled by GPIO are now
38+
internally connected to registers in the RCC and PWR modules. These
39+
signals (Nss, Reset and Busy) can be controlled and/or read through this
40+
library.
41+
42+
The Nss signal can be written (and read back) using
43+
`SubGhz.setNssActive(bool)` and `SubGhz.isNssActive()`, the Reset signal
44+
using `SubGhz.setResetActive(bool)` and `SubGhz.isResetActive()` and the
45+
busy signal can only be read using `SubGhz.isBusy()`.
46+
47+
There is no need to configure these signals before using them (i.e.
48+
nothing like `pinMode()` is needed).
49+
50+
Note that there is no way to read the DIO signals directly, since they
51+
are not directly available to the MCU anywhere (only through the
52+
interrupt controller, so with some care the DIO signal status could be
53+
derived from the interrupt pending status if needed).
54+
55+
#### Interrupts
56+
In addition, the DIO signals produced by the radio module are connected
57+
together in an "OR" configuration and drive the radio interrupt. This
58+
interrupt can be enabled and configured using various methods in this
59+
library.
60+
61+
The interrupt can be attached (and enabled) using the
62+
`SubGhz.attachInterrupt(callback)` method. You can pass any callable here that
63+
`std::function` accepts (global function, method bound with `std::bind`,
64+
callable object or a lambda). Attaching an interrupt clears any
65+
previously pending interrupts and enables the interrupt.
66+
67+
The `SubGhz.detachInterrupt()` method can be used to disable the
68+
interrupt and clear the handler and the `SubGhz.hasInterrupt()` method
69+
can be used to query if a handler was attached (regardless of whether it
70+
is currently enabled).
71+
72+
The `SubGhz.disableInterrupt()` and `SubGhz.enableInterrupt()` method
73+
can be used to temporarily disable the interrupt. If the interrupt is
74+
triggered while it is disabled, it will become pending (indicated by the
75+
`SubGhz.isInterruptPending()` method) and the interrupt handler will run
76+
directly when the interrupt is enabled again (unless cleared with
77+
`SubGhz.clearPendingInterrupt()`). Note that there is no method to query
78+
whether the interrupt is currently enabled, as the interrupt controller
79+
hardware does allow reading back this value.
80+
81+
Note that there is there is hardly any point in having multiple DIO
82+
signals, since they are all wired together into a single interrupt and
83+
cannot be individually read, but this is just how the original Semtech
84+
radio was designed.
85+
86+
Also note that the DIO lines are directly connected to the MCU interrupt
87+
controller (NVIC), which is level sensitive. When the ISR is triggered,
88+
it should always either clear the interrupt flag in the radio, or
89+
disable the interrupt in the NVIC (using `SubGhz.disableInterrupt()` in
90+
this library) to prevent the ISR from triggering again (and again and
91+
again etc.) after it completes.
92+
93+
### Example
94+
95+
This is a basic example of initializing the radio and SPI bus and
96+
reading a register through an SPI command. See the examples folder for
97+
a full example sketch.
98+
99+
```C++
100+
// initialize SPI:
101+
SubGhz.SPI.begin();
102+
103+
// clear reset to
104+
SubGhz.setResetActive(false);
105+
106+
// Start SPI transaction and wait for the radio to wake up (it starts
107+
// in sleep mode with its busy signal active).
108+
SubGhz.SPI.beginTransaction(SubGhz.spi_settings);
109+
SubGhz.setNssActive(true);
110+
while (SubGhz.isBusy()) /* wait */;
111+
112+
// Write a command and read the result
113+
SubGhz.SPI.transfer(0x1D); // Write command: Read_Register()
114+
SubGhz.SPI.transfer(0x07); // Write MSB of register address: SUBGHZ_LSYNCRH
115+
SubGhz.SPI.transfer(0x40); // Write LSB of register address: SUBGHZ_LSYNCRH
116+
uint8_t status = SubGhz.SPI.transfer(0x0); // Read status
117+
uint8_t value = SubGhz.SPI.transfer(0x0); // Read register value
118+
119+
// End transaction
120+
SubGhz.setNssActive(false);
121+
SubGhz.SPI.endTransaction();
122+
123+
// value now has the register value read
124+
```
125+
126+
### License
127+
Copyright (c) 2022, STMicroelectronics
128+
All rights reserved.
129+
130+
This software component is licensed by ST under BSD 3-Clause license,
131+
the "License"; You may not use this file except in compliance with the
132+
License. You may obtain a copy of the License at:
133+
opensource.org/licenses/BSD-3-Clause
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
*******************************************************************************
3+
* Copyright (c) 2022, STMicroelectronics
4+
* All rights reserved.
5+
*
6+
* This software component is licensed by ST under BSD 3-Clause license,
7+
* the "License"; You may not use this file except in compliance with the
8+
* License. You may obtain a copy of the License at:
9+
* opensource.org/licenses/BSD-3-Clause
10+
*
11+
*******************************************************************************
12+
*
13+
* This example shows how to use the SubGhz library to start up the
14+
* radio module and read a register through it using an SPI command.
15+
* This reads the sync word MSB register, since that has a specific
16+
* reset value.
17+
*/
18+
#include <SubGhz.h>
19+
20+
21+
void setup() {
22+
Serial.begin(115200);
23+
24+
// initialize SPI:
25+
SubGhz.SPI.begin();
26+
27+
// clear reset to
28+
SubGhz.setResetActive(false);
29+
30+
// Start SPI transaction and wait for the radio to wake up (it starts
31+
// in sleep mode with its busy signal active).
32+
SubGhz.SPI.beginTransaction(SubGhz.spi_settings);
33+
SubGhz.setNssActive(true);
34+
while (SubGhz.isBusy()) /* wait */;
35+
36+
// Write a command and read the result
37+
SubGhz.SPI.transfer(0x1D); // Write command: Read_Register()
38+
SubGhz.SPI.transfer(0x07); // Write MSB of register address: SUBGHZ_LSYNCRH
39+
SubGhz.SPI.transfer(0x40); // Write LSB of register address: SUBGHZ_LSYNCRH
40+
uint8_t status = SubGhz.SPI.transfer(0x0); // Read status
41+
uint8_t value = SubGhz.SPI.transfer(0x0); // Read register value
42+
43+
// End transaction
44+
SubGhz.setNssActive(false);
45+
SubGhz.SPI.endTransaction();
46+
47+
// This should print a register value 0x14 (reset value of the LoRa sync word MSB)
48+
Serial.print("Status: 0x");
49+
Serial.println(status, HEX);
50+
Serial.print("Register value: 0x");
51+
Serial.println(value, HEX);
52+
}
53+
54+
void loop() {
55+
/* Nothing to do */
56+
}

libraries/SubGhz/keywords.txt

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#######################################
2+
# Datatypes (KEYWORD1)
3+
#######################################
4+
callback_function_t KEYWORD1
5+
SubGhzClass KEYWORD1
6+
SubGhz KEYWORD1
7+
8+
#######################################
9+
# Functions (KEYWORD2)
10+
#######################################
11+
attachInterrupt KEYWORD2
12+
clearPendingInterrupt KEYWORD2
13+
detachInterrupt KEYWORD2
14+
disableInterrupt KEYWORD2
15+
enableInterrupt KEYWORD2
16+
hasInterrupt KEYWORD2
17+
isBusy KEYWORD2
18+
isInterruptPending KEYWORD2
19+
isNssActive KEYWORD2
20+
isResetActive KEYWORD2
21+
setNssActive KEYWORD2
22+
setResetActive KEYWORD2
23+
24+
#######################################
25+
# Constants (LITERAL1)
26+
#######################################

libraries/SubGhz/library.properties

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
name=SubGhz
2+
version=1.0.0
3+
author=stm32duino
4+
maintainer=stm32duino
5+
sentence=Allows controlling the SubGhz radio on STM32WL MCUs
6+
paragraph=This library allows access to some internal control signals and predefines the right SPI bus to talk to the radio module.
7+
category=Communication
8+
url=https://github.com/stm32duino/Arduino_Core_STM32
9+
architectures=stm32

libraries/SubGhz/src/SubGhz.cpp

+102
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/*
2+
*******************************************************************************
3+
* Copyright (c) 2022, STMicroelectronics
4+
* All rights reserved.
5+
*
6+
* This software component is licensed by ST under BSD 3-Clause license,
7+
* the "License"; You may not use this file except in compliance with the
8+
* License. You may obtain a copy of the License at:
9+
* opensource.org/licenses/BSD-3-Clause
10+
*
11+
*******************************************************************************
12+
*/
13+
14+
#include <Arduino.h>
15+
#include "SubGhz.h"
16+
17+
SubGhzClass SubGhz;
18+
19+
extern "C" void SUBGHZ_Radio_IRQHandler(void)
20+
{
21+
SubGhz.handleIrq();
22+
}
23+
24+
constexpr SPISettings SubGhzClass::spi_settings;
25+
26+
void SubGhzClass::handleIrq()
27+
{
28+
if (callback) {
29+
callback();
30+
}
31+
}
32+
33+
void SubGhzClass::attachInterrupt(callback_function_t callback)
34+
{
35+
this->callback = callback;
36+
HAL_NVIC_ClearPendingIRQ(SUBGHZ_Radio_IRQn);
37+
enableInterrupt();
38+
}
39+
40+
void SubGhzClass::detachInterrupt()
41+
{
42+
disableInterrupt();
43+
this->callback = nullptr;
44+
}
45+
46+
bool SubGhzClass::hasInterrupt()
47+
{
48+
return (bool)this->callback;
49+
}
50+
51+
void SubGhzClass::enableInterrupt()
52+
{
53+
HAL_NVIC_EnableIRQ(SUBGHZ_Radio_IRQn);
54+
}
55+
56+
void SubGhzClass::disableInterrupt()
57+
{
58+
HAL_NVIC_DisableIRQ(SUBGHZ_Radio_IRQn);
59+
}
60+
61+
void SubGhzClass::clearPendingInterrupt()
62+
{
63+
HAL_NVIC_ClearPendingIRQ(SUBGHZ_Radio_IRQn);
64+
}
65+
66+
bool SubGhzClass::isInterruptPending()
67+
{
68+
return HAL_NVIC_GetPendingIRQ(SUBGHZ_Radio_IRQn);
69+
}
70+
71+
void SubGhzClass::setNssActive(bool value)
72+
{
73+
if (value) {
74+
LL_PWR_SelectSUBGHZSPI_NSS();
75+
} else {
76+
LL_PWR_UnselectSUBGHZSPI_NSS();
77+
}
78+
}
79+
80+
bool SubGhzClass::isNssActive()
81+
{
82+
return LL_PWR_IsSUBGHZSPI_NSS_Selected();
83+
}
84+
85+
void SubGhzClass::setResetActive(bool value)
86+
{
87+
if (value) {
88+
LL_RCC_RF_EnableReset();
89+
} else {
90+
LL_RCC_RF_DisableReset();
91+
}
92+
}
93+
94+
bool SubGhzClass::isResetActive()
95+
{
96+
return LL_RCC_RF_IsEnabledReset();
97+
}
98+
99+
bool SubGhzClass::isBusy()
100+
{
101+
return (LL_PWR_IsActiveFlag_RFBUSYS());
102+
}

0 commit comments

Comments
 (0)