circuitpython/ports/stm32/subghz.c
Angus Gratton 02620c2236 stm32/subghz: Add STM32WL55 subghz radio interface to stm module.
This is the minimum C interface to allow a modem driver to be built in
Python.  Interface is simple, with the intention that the micropython-lib
driver is the main (only) consumer of it.

Signed-off-by: Angus Gratton <angus@redyak.com.au>
2023-08-23 11:58:11 +10:00

140 lines
4.7 KiB
C

/*
* 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