summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/library/stm.rst36
-rw-r--r--ports/stm32/Makefile1
-rw-r--r--ports/stm32/boards/NUCLEO_WL55/mpconfigboard.h2
-rw-r--r--ports/stm32/boards/NUCLEO_WL55/pins.csv6
-rw-r--r--ports/stm32/irq.h2
-rw-r--r--ports/stm32/main.c7
-rw-r--r--ports/stm32/modstm.c7
-rw-r--r--ports/stm32/subghz.c139
-rw-r--r--ports/stm32/subghz.h40
9 files changed, 236 insertions, 4 deletions
diff --git a/docs/library/stm.rst b/docs/library/stm.rst
index a181d6044..970ab1883 100644
--- a/docs/library/stm.rst
+++ b/docs/library/stm.rst
@@ -102,3 +102,39 @@ the second CPU, the RF core.
Execute a HCI command on the SYS channel. The execution is synchronous.
Returns a bytes object with the result of the SYS command.
+
+Functions specific to STM32WLxx MCUs
+------------------------------------
+
+These functions are available on STM32WLxx microcontrollers, and interact with
+the integrated "SUBGHZ" radio modem peripheral.
+
+.. function:: subghz_cs(level)
+
+ Sets the internal SPI CS pin attached to the radio peripheral. The ``level``
+ argument is active-low: a truthy value means "CS pin high" and de-asserts the
+ signal, a falsey value means "CS pin low" and asserts the signal.
+
+ The internal-only SPI bus corresponding to this CS signal can be instantiated
+ using :ref:`machine.SPI()<machine.SPI>` ``id`` value ``"SUBGHZ"``.
+
+.. function:: subghz_irq(handler)
+
+ Sets the internal SUBGHZ radio interrupt handler to the provided
+ function. The handler function is called as a "hard" interrupt in response to
+ radio peripheral interrupts. See :ref:`isr_rules` for more information about
+ interrupt handlers in MicroPython.
+
+ Calling this function with the handler argument set to None disables the IRQ.
+
+ Due to a hardware limitation, each time this IRQ fires MicroPython disables
+ it before calling the handler. In order to receive another interrupt, Python
+ code should call ``subghz_irq()`` to set the handler again. This has the side
+ effect of re-enabling the IRQ.
+
+.. function:: subghz_is_busy()
+
+ Return a ``bool`` corresponding to the internal "RFBUSYS" signal from the
+ radio peripheral. Before sending a new command to the radio over SPI then
+ this function should be polled until it returns ``False``, to confirm the
+ busy signal is de-asserted.
diff --git a/ports/stm32/Makefile b/ports/stm32/Makefile
index 2bfd11157..705139897 100644
--- a/ports/stm32/Makefile
+++ b/ports/stm32/Makefile
@@ -355,6 +355,7 @@ SRC_C += \
dac.c \
adc.c \
sdio.c \
+ subghz.c \
$(wildcard $(BOARD_DIR)/*.c)
SRC_O += \
diff --git a/ports/stm32/boards/NUCLEO_WL55/mpconfigboard.h b/ports/stm32/boards/NUCLEO_WL55/mpconfigboard.h
index ec5904a35..c87fcdea9 100644
--- a/ports/stm32/boards/NUCLEO_WL55/mpconfigboard.h
+++ b/ports/stm32/boards/NUCLEO_WL55/mpconfigboard.h
@@ -14,7 +14,7 @@
#define MICROPY_PY_SOCKET (0)
#define MICROPY_PY_NETWORK (0)
#define MICROPY_PY_ONEWIRE (0)
-#define MICROPY_PY_STM (0)
+#define MICROPY_PY_STM (1)
#define MICROPY_PY_PYB_LEGACY (0)
#define MICROPY_PY_HEAPQ (0)
diff --git a/ports/stm32/boards/NUCLEO_WL55/pins.csv b/ports/stm32/boards/NUCLEO_WL55/pins.csv
index 78a0ef8d0..41e2d725a 100644
--- a/ports/stm32/boards/NUCLEO_WL55/pins.csv
+++ b/ports/stm32/boards/NUCLEO_WL55/pins.csv
@@ -34,9 +34,9 @@
,PC0
,PC1
,PC2
-,PC3
-,PC4
-,PC5
+FE_CTRL3,PC3
+FE_CTRL1,PC4
+FE_CTRL2,PC5
,PC6
SW,PA0
SW1,PA0
diff --git a/ports/stm32/irq.h b/ports/stm32/irq.h
index 8e98b4cc5..7f10d0d00 100644
--- a/ports/stm32/irq.h
+++ b/ports/stm32/irq.h
@@ -155,6 +155,8 @@ static inline void restore_irq_pri(uint32_t state) {
#define IRQ_PRI_CAN NVIC_EncodePriority(NVIC_PRIORITYGROUP_4, 7, 0)
+#define IRQ_PRI_SUBGHZ_RADIO NVIC_EncodePriority(NVIC_PRIORITYGROUP_4, 8, 0)
+
#define IRQ_PRI_SPI NVIC_EncodePriority(NVIC_PRIORITYGROUP_4, 8, 0)
// Interrupt priority for non-special timers.
diff --git a/ports/stm32/main.c b/ports/stm32/main.c
index 2235e4de3..95b74c621 100644
--- a/ports/stm32/main.c
+++ b/ports/stm32/main.c
@@ -86,6 +86,7 @@
#include "servo.h"
#include "dac.h"
#include "can.h"
+#include "subghz.h"
#if MICROPY_PY_THREAD
STATIC pyb_thread_t pyb_thread_main;
@@ -403,6 +404,9 @@ void stm32_main(uint32_t reset_mode) {
#if defined(STM32WB)
rfcore_init();
#endif
+ #if defined(STM32WL)
+ subghz_init();
+ #endif
#if MICROPY_HW_SDRAM_SIZE
sdram_init();
bool sdram_valid = true;
@@ -650,6 +654,9 @@ soft_reset_exit:
#if MICROPY_PY_BLUETOOTH
mp_bluetooth_deinit();
#endif
+ #if defined(STM32WL)
+ subghz_deinit();
+ #endif
#if MICROPY_PY_NETWORK
mod_network_deinit();
#endif
diff --git a/ports/stm32/modstm.c b/ports/stm32/modstm.c
index 94d56d4d0..53bc0db79 100644
--- a/ports/stm32/modstm.c
+++ b/ports/stm32/modstm.c
@@ -32,6 +32,7 @@
#include "extmod/machine_mem.h"
#include "rfcore.h"
#include "portmodules.h"
+#include "subghz.h"
#if MICROPY_PY_STM
@@ -51,6 +52,12 @@ STATIC const mp_rom_map_elem_t stm_module_globals_table[] = {
{ MP_ROM_QSTR(MP_QSTR_rfcore_fw_version), MP_ROM_PTR(&rfcore_fw_version_obj) },
{ MP_ROM_QSTR(MP_QSTR_rfcore_sys_hci), MP_ROM_PTR(&rfcore_sys_hci_obj) },
#endif
+
+ #if defined(STM32WL)
+ { MP_ROM_QSTR(MP_QSTR_subghz_cs), MP_ROM_PTR(&subghz_cs_obj) },
+ { MP_ROM_QSTR(MP_QSTR_subghz_irq), MP_ROM_PTR(&subghz_irq_obj) },
+ { MP_ROM_QSTR(MP_QSTR_subghz_is_busy), MP_ROM_PTR(&subghz_is_busy_obj) },
+ #endif
};
STATIC MP_DEFINE_CONST_DICT(stm_module_globals, stm_module_globals_table);
diff --git a/ports/stm32/subghz.c b/ports/stm32/subghz.c
new file mode 100644
index 000000000..1daed59aa
--- /dev/null
+++ b/ports/stm32/subghz.c
@@ -0,0 +1,139 @@
+/*
+ * This file is part of the MicroPython project, http://micropython.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2023 Angus Gratton
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "py/gc.h"
+#include "py/runtime.h"
+#include "subghz.h"
+#include "irq.h"
+#include "spi.h"
+
+#if defined(STM32WL)
+
+// Interface to the STM32WL series "SUBGHZ Radio" module
+
+STATIC void handle_radio_irq() {
+ // Level-triggered interrupts means the interrupt has to be cleared before
+ // this function returns.
+ //
+ // Rather than writing to SUBGHZ SPI in Interrupt Context to clear the
+ // interrupt, disable the IRQ and rely on Python code to call
+ // subghz_irq(handler) to re-enable when needed.
+ HAL_NVIC_DisableIRQ(SUBGHZ_Radio_IRQn);
+
+ mp_obj_t callback = MP_STATE_PORT(subghz_callback);
+ if (callback != mp_const_none) {
+ mp_sched_lock();
+ gc_lock();
+ // Passing dummy 'pin' argument of None, to keep
+ // compatibility with machine.Pin.isr() handlers
+ mp_call_function_1_protected(callback, mp_const_none);
+ gc_unlock();
+ mp_sched_unlock();
+ }
+}
+
+void SUBGHZ_Radio_IRQHandler(void) {
+ IRQ_ENTER(SUBGHZ_Radio_IRQn);
+ handle_radio_irq();
+ IRQ_EXIT(SUBGHZ_Radio_IRQn);
+}
+
+void subghz_init(void) {
+ __HAL_RCC_SUBGHZ_RADIO_FORCE_RESET();
+
+ #if !MICROPY_HW_CLK_USE_HSE && MICROPY_HW_CLK_USE_BYPASS
+ // SUBGHZ clock source is HSE oscillator.
+ //
+ // If this is not already enabled for the system clock, and we're depending
+ // on the VDDTCXO pin to power the HSE ("bypass mode"), then enable it.
+ __HAL_RCC_HSE_CONFIG(RCC_HSE_BYPASS_PWR);
+ #endif
+
+ NVIC_DisableIRQ(SUBGHZ_Radio_IRQn);
+ NVIC_SetPriority(SUBGHZ_Radio_IRQn, IRQ_PRI_SUBGHZ_RADIO);
+
+ __HAL_RCC_SUBGHZ_RADIO_RELEASE_RESET();
+
+ while (__HAL_RCC_GET_FLAG(RCC_FLAG_RFRST) != 0) {
+ }
+
+ MP_STATE_PORT(subghz_callback) = mp_const_none;
+}
+
+void subghz_deinit(void) {
+ MP_STATE_PORT(subghz_callback) = mp_const_none;
+ NVIC_DisableIRQ(SUBGHZ_Radio_IRQn);
+ __HAL_RCC_SUBGHZ_RADIO_FORCE_RESET();
+ __HAL_RCC_SUBGHZ_RADIO_RELEASE_RESET();
+}
+
+STATIC mp_obj_t subghz_cs(mp_obj_t value) {
+ // Treat the same as normal SPI - truthy is "unselected",
+ // falsey is active low "selected",
+ if (mp_obj_is_true(value)) {
+ LL_PWR_UnselectSUBGHZSPI_NSS();
+ } else {
+ LL_PWR_SelectSUBGHZSPI_NSS();
+ }
+
+ return mp_const_none;
+}
+MP_DEFINE_CONST_FUN_OBJ_1(subghz_cs_obj, subghz_cs);
+
+STATIC mp_obj_t subghz_irq(mp_obj_t handler) {
+ MP_STATE_PORT(subghz_callback) = handler;
+
+ if (mp_obj_is_true(handler)) {
+ HAL_NVIC_ClearPendingIRQ(SUBGHZ_Radio_IRQn);
+ HAL_NVIC_EnableIRQ(SUBGHZ_Radio_IRQn);
+ } else {
+ HAL_NVIC_DisableIRQ(SUBGHZ_Radio_IRQn);
+ }
+
+ return mp_const_none;
+}
+MP_DEFINE_CONST_FUN_OBJ_1(subghz_irq_obj, subghz_irq);
+
+STATIC mp_obj_t subghz_is_busy(void) {
+ // Read the raw unmasked busy signal. This should be checked before driving
+ // CS low to start a command.
+ //
+ // Reads the raw RFBUSYS not the masked RFBUSYMS, in contradiction to EM0453
+ // 6.3 "Radio busy management". This is because the RFBUSYMS signal doesn't
+ // seem to match the reference manual. Observed behaviour matches this bug
+ // report instead: https://community.st.com/s/question/0D53W000014zFx9SAE
+ //
+ // Reading RFBUSYS won't cause any problems here provided a new SPI command
+ // isn't immediately after the previous command, which shouldn't be possible
+ // with MicroPython.
+ return mp_obj_new_bool(LL_PWR_IsActiveFlag_RFBUSYS());
+
+}
+MP_DEFINE_CONST_FUN_OBJ_0(subghz_is_busy_obj, subghz_is_busy);
+
+MP_REGISTER_ROOT_POINTER(mp_obj_t subghz_callback);
+
+#endif // STM32WL
diff --git a/ports/stm32/subghz.h b/ports/stm32/subghz.h
new file mode 100644
index 000000000..f8678cd08
--- /dev/null
+++ b/ports/stm32/subghz.h
@@ -0,0 +1,40 @@
+/*
+ * This file is part of the MicroPython project, http://micropython.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2023 Angus Gratton
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#ifndef MICROPY_INCLUDED_STM32_SUBGHZ_H
+#define MICROPY_INCLUDED_STM32_SUBGHZ_H
+
+#include "py/obj.h"
+
+// Interface to the STM32WL series "SUBGHZ Radio" module
+
+void subghz_init(void);
+void subghz_deinit(void);
+
+MP_DECLARE_CONST_FUN_OBJ_1(subghz_cs_obj);
+MP_DECLARE_CONST_FUN_OBJ_1(subghz_irq_obj);
+MP_DECLARE_CONST_FUN_OBJ_0(subghz_is_busy_obj);
+
+#endif