diff --git a/docs/library/stm.rst b/docs/library/stm.rst index a181d6044c..970ab18834 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()` ``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 2bfd11157e..7051398970 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 ec5904a359..c87fcdea9b 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 78a0ef8d04..41e2d725a2 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 8e98b4cc5e..7f10d0d004 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 2235e4de35..95b74c621a 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 94d56d4d08..53bc0db79c 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 0000000000..1daed59aaf --- /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 0000000000..f8678cd087 --- /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