mirror of
https://github.com/mysensors/MySensors.git
synced 2026-02-19 17:11:28 +01:00
STM32 sleep support (#1586)
* adding STM32 HAL sleep implementation * fixed formatting * added STM32F1 sleep support (legacy RTC) * clean up code, no functional change * fixed static analysis warnings
This commit is contained in:
@@ -49,6 +49,23 @@
|
||||
|
||||
#include "MyHwSTM32.h"
|
||||
|
||||
// Sleep mode state variables
|
||||
static volatile uint8_t _wokeUpByInterrupt = INVALID_INTERRUPT_NUM;
|
||||
static volatile uint8_t _wakeUp1Interrupt = INVALID_INTERRUPT_NUM;
|
||||
static volatile uint8_t _wakeUp2Interrupt = INVALID_INTERRUPT_NUM;
|
||||
static uint32_t sleepRemainingMs = 0ul;
|
||||
|
||||
// RTC handle for wake-up timer
|
||||
static RTC_HandleTypeDef hrtc = {0};
|
||||
static bool rtcInitialized = false;
|
||||
|
||||
// Forward declarations for sleep helper functions
|
||||
static bool hwSleepInit(void);
|
||||
static bool hwSleepConfigureTimer(uint32_t ms);
|
||||
static void hwSleepRestoreSystemClock(void);
|
||||
static void wakeUp1ISR(void);
|
||||
static void wakeUp2ISR(void);
|
||||
|
||||
bool hwInit(void)
|
||||
{
|
||||
#if !defined(MY_DISABLED_SERIAL)
|
||||
@@ -254,36 +271,494 @@ uint16_t hwFreeMem(void)
|
||||
#endif
|
||||
}
|
||||
|
||||
// ======================== Sleep Mode Helper Functions ========================
|
||||
|
||||
/**
|
||||
* @brief Initialize RTC for sleep wake-up timer
|
||||
* @return true if successful, false on error
|
||||
*/
|
||||
static bool hwSleepInit(void)
|
||||
{
|
||||
if (rtcInitialized) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Enable PWR clock
|
||||
__HAL_RCC_PWR_CLK_ENABLE();
|
||||
|
||||
// Enable backup domain access
|
||||
HAL_PWR_EnableBkUpAccess();
|
||||
|
||||
// Only reset backup domain if RTC is not already configured
|
||||
// This prevents disrupting other peripherals when MySensors radio is initialized first
|
||||
if ((RCC->BDCR & RCC_BDCR_RTCEN) != 0) {
|
||||
// RTC already enabled - check if it's the right clock source
|
||||
// If already configured, skip reset to avoid disrupting existing setup
|
||||
} else {
|
||||
// RTC not enabled - safe to reset backup domain for clean slate
|
||||
__HAL_RCC_BACKUPRESET_FORCE();
|
||||
HAL_Delay(10);
|
||||
__HAL_RCC_BACKUPRESET_RELEASE();
|
||||
HAL_Delay(10);
|
||||
}
|
||||
|
||||
// Try LSE first (32.768 kHz external crystal - more accurate)
|
||||
// Fall back to LSI if LSE is not available
|
||||
bool useLSE = false;
|
||||
|
||||
// Check if LSE is already running
|
||||
if ((RCC->BDCR & RCC_BDCR_LSERDY) != 0) {
|
||||
// LSE already ready - use it
|
||||
useLSE = true;
|
||||
} else {
|
||||
// Attempt to start LSE
|
||||
RCC->BDCR |= RCC_BDCR_LSEON;
|
||||
uint32_t timeout = 2000000; // LSE takes longer to start
|
||||
while (((RCC->BDCR & RCC_BDCR_LSERDY) == 0) && (timeout > 0)) {
|
||||
timeout--;
|
||||
}
|
||||
|
||||
if (timeout > 0) {
|
||||
// LSE started successfully
|
||||
useLSE = true;
|
||||
} else {
|
||||
// LSE failed - fall back to LSI
|
||||
if ((RCC->CSR & RCC_CSR_LSIRDY) == 0) {
|
||||
// LSI not ready, try to start it
|
||||
RCC->BDCR &= ~RCC_BDCR_LSEON; // Disable failed LSE
|
||||
|
||||
// Enable LSI (internal ~32 kHz oscillator)
|
||||
RCC->CSR |= RCC_CSR_LSION;
|
||||
timeout = 1000000;
|
||||
while (((RCC->CSR & RCC_CSR_LSIRDY) == 0) && (timeout > 0)) {
|
||||
timeout--;
|
||||
}
|
||||
|
||||
if (timeout == 0) {
|
||||
return false; // Both LSE and LSI failed
|
||||
}
|
||||
}
|
||||
// LSI ready (either was already running or just started)
|
||||
}
|
||||
}
|
||||
|
||||
// Configure RTC clock source (only if not already configured correctly)
|
||||
uint32_t currentRtcSel = (RCC->BDCR & RCC_BDCR_RTCSEL);
|
||||
uint32_t desiredRtcSel = useLSE ? RCC_BDCR_RTCSEL_0 : RCC_BDCR_RTCSEL_1;
|
||||
|
||||
if (currentRtcSel != desiredRtcSel) {
|
||||
// Need to change clock source - clear and set
|
||||
RCC->BDCR &= ~RCC_BDCR_RTCSEL; // Clear selection
|
||||
RCC->BDCR |= desiredRtcSel; // Set new selection
|
||||
}
|
||||
RCC->BDCR |= RCC_BDCR_RTCEN; // Ensure RTC clock is enabled
|
||||
|
||||
// Initialize RTC peripheral
|
||||
hrtc.Instance = RTC;
|
||||
|
||||
#if defined(STM32F1xx)
|
||||
// ============================================================
|
||||
// STM32F1: Legacy RTC with counter-based architecture
|
||||
// ============================================================
|
||||
// F1 RTC uses simple 32-bit counter with prescaler
|
||||
// No calendar, no wake-up timer - use RTC Alarm instead
|
||||
|
||||
if (useLSE) {
|
||||
// LSE: 32.768 kHz exact - set prescaler for 1 Hz tick
|
||||
hrtc.Init.AsynchPrediv = 32767; // (32767+1) = 32768 = 1 Hz
|
||||
} else {
|
||||
// LSI: ~40 kHz (STM32F1 LSI is typically 40kHz, not 32kHz)
|
||||
hrtc.Init.AsynchPrediv = 39999; // (39999+1) = 40000 = 1 Hz (approximate)
|
||||
}
|
||||
|
||||
hrtc.Init.OutPut = RTC_OUTPUTSOURCE_NONE;
|
||||
|
||||
// F1 RTC initialization is simpler
|
||||
if (HAL_RTC_Init(&hrtc) != HAL_OK) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// CRITICAL: Enable RTC Alarm interrupt in NVIC (F1 uses Alarm, not WKUP)
|
||||
// Without this, the MCU cannot wake from STOP mode via RTC
|
||||
HAL_NVIC_SetPriority(RTC_Alarm_IRQn, 0, 0);
|
||||
HAL_NVIC_EnableIRQ(RTC_Alarm_IRQn);
|
||||
|
||||
#else
|
||||
// ============================================================
|
||||
// STM32F2/F3/F4/F7/L1/L4/L5/G0/G4/H7: Modern RTC
|
||||
// ============================================================
|
||||
// Modern RTC with BCD calendar and dedicated wake-up timer
|
||||
|
||||
hrtc.Init.HourFormat = RTC_HOURFORMAT_24;
|
||||
|
||||
if (useLSE) {
|
||||
// LSE: 32.768 kHz exact - perfect 1 Hz with these prescalers
|
||||
hrtc.Init.AsynchPrediv = 127; // (127+1) = 128
|
||||
hrtc.Init.SynchPrediv = 255; // (255+1) = 256, total = 32768
|
||||
} else {
|
||||
// LSI: ~32 kHz (variable) - approximate 1 Hz
|
||||
hrtc.Init.AsynchPrediv = 127;
|
||||
hrtc.Init.SynchPrediv = 249; // Adjusted for typical LSI
|
||||
}
|
||||
|
||||
hrtc.Init.OutPut = RTC_OUTPUT_DISABLE;
|
||||
hrtc.Init.OutPutPolarity = RTC_OUTPUT_POLARITY_HIGH;
|
||||
hrtc.Init.OutPutType = RTC_OUTPUT_TYPE_OPENDRAIN;
|
||||
|
||||
// Check if RTC is already initialized (INITS bit in ISR register)
|
||||
// If already initialized, we can skip HAL_RTC_Init which may fail
|
||||
// when called after other peripherals (like SPI) are already running
|
||||
if ((RTC->ISR & RTC_ISR_INITS) == 0) {
|
||||
// RTC not yet initialized - call HAL_RTC_Init
|
||||
if (HAL_RTC_Init(&hrtc) != HAL_OK) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// RTC already initialized - just update the handle
|
||||
// This allows us to use it for sleep even if something else initialized it
|
||||
hrtc.State = HAL_RTC_STATE_READY;
|
||||
}
|
||||
|
||||
// CRITICAL: Enable RTC wakeup interrupt in NVIC
|
||||
// Without this, the MCU cannot wake from STOP mode via RTC
|
||||
HAL_NVIC_SetPriority(RTC_WKUP_IRQn, 0, 0);
|
||||
HAL_NVIC_EnableIRQ(RTC_WKUP_IRQn);
|
||||
|
||||
#endif // STM32F1xx
|
||||
|
||||
rtcInitialized = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Configure RTC wake-up timer for specified duration
|
||||
* @param ms Milliseconds to sleep (0 = disable timer)
|
||||
* @return true if successful, false on error
|
||||
*/
|
||||
static bool hwSleepConfigureTimer(uint32_t ms)
|
||||
{
|
||||
if (!rtcInitialized) {
|
||||
if (!hwSleepInit()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (ms == 0) {
|
||||
#if defined(STM32F1xx)
|
||||
// F1: Disable RTC Alarm
|
||||
HAL_RTC_DeactivateAlarm(&hrtc, RTC_ALARM_A);
|
||||
#else
|
||||
// Modern STM32: Disable wake-up timer
|
||||
HAL_RTCEx_DeactivateWakeUpTimer(&hrtc);
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
#if defined(STM32F1xx)
|
||||
// ============================================================
|
||||
// STM32F1: Use RTC Alarm for wake-up
|
||||
// ============================================================
|
||||
// F1 doesn't have wake-up timer, use alarm instead
|
||||
// RTC counter runs at 1 Hz (configured in hwSleepInit)
|
||||
|
||||
// Read current counter value
|
||||
// Note: On F1, read CNT register directly (HAL doesn't provide a clean way)
|
||||
uint32_t currentCounter = RTC->CNTL | (RTC->CNTH << 16);
|
||||
|
||||
// Calculate alarm value (counter + seconds)
|
||||
// Convert ms to seconds (RTC runs at 1 Hz)
|
||||
uint32_t seconds = ms / 1000;
|
||||
if (seconds == 0) {
|
||||
seconds = 1; // Minimum 1 second
|
||||
}
|
||||
if (seconds > 0xFFFFFFFF - currentCounter) {
|
||||
// Overflow protection
|
||||
seconds = 0xFFFFFFFF - currentCounter;
|
||||
}
|
||||
|
||||
uint32_t alarmValue = currentCounter + seconds;
|
||||
|
||||
// Configure alarm
|
||||
RTC_AlarmTypeDef sAlarm = {0};
|
||||
sAlarm.Alarm = alarmValue;
|
||||
|
||||
if (HAL_RTC_SetAlarm_IT(&hrtc, &sAlarm, RTC_FORMAT_BIN) != HAL_OK) {
|
||||
return false;
|
||||
}
|
||||
|
||||
#else
|
||||
// ============================================================
|
||||
// STM32F2/F3/F4/F7/L1/L4/L5/G0/G4/H7: Use wake-up timer
|
||||
// ============================================================
|
||||
|
||||
uint32_t wakeUpCounter;
|
||||
uint32_t wakeUpClock;
|
||||
|
||||
// Choose appropriate clock and counter value based on sleep duration
|
||||
if (ms <= 32000) {
|
||||
// Up to 32 seconds: use RTCCLK/16 (2048 Hz, 0.488 ms resolution)
|
||||
wakeUpClock = RTC_WAKEUPCLOCK_RTCCLK_DIV16;
|
||||
// Counter = ms * 2048 / 1000 = ms * 2.048
|
||||
// Use bit shift for efficiency: ms * 2048 = ms << 11
|
||||
wakeUpCounter = (ms << 11) / 1000;
|
||||
if (wakeUpCounter < 2) {
|
||||
wakeUpCounter = 2; // Minimum 2 ticks
|
||||
}
|
||||
if (wakeUpCounter > 0xFFFF) {
|
||||
wakeUpCounter = 0xFFFF;
|
||||
}
|
||||
} else {
|
||||
// More than 32 seconds: use CK_SPRE (1 Hz, 1 second resolution)
|
||||
wakeUpClock = RTC_WAKEUPCLOCK_CK_SPRE_16BITS;
|
||||
wakeUpCounter = ms / 1000; // Convert to seconds
|
||||
if (wakeUpCounter == 0) {
|
||||
wakeUpCounter = 1; // Minimum 1 second
|
||||
}
|
||||
if (wakeUpCounter > 0xFFFF) {
|
||||
wakeUpCounter = 0xFFFF; // Max ~18 hours
|
||||
}
|
||||
}
|
||||
|
||||
// Configure wake-up timer with interrupt
|
||||
if (HAL_RTCEx_SetWakeUpTimer_IT(&hrtc, wakeUpCounter, wakeUpClock) != HAL_OK) {
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif // STM32F1xx
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Restore system clock after wake-up from STOP mode
|
||||
* @note After STOP mode, system clock defaults to HSI (16 MHz). We always call
|
||||
* SystemClock_Config() to restore the full clock configuration as the
|
||||
* Arduino core and peripherals expect it.
|
||||
*/
|
||||
static void hwSleepRestoreSystemClock(void)
|
||||
{
|
||||
// After STOP mode, system runs on HSI (16 MHz)
|
||||
// Always restore the system clock configuration to what the Arduino core expects
|
||||
SystemClock_Config();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief ISR for wake-up interrupt 1
|
||||
*/
|
||||
static void wakeUp1ISR(void)
|
||||
{
|
||||
_wokeUpByInterrupt = _wakeUp1Interrupt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief ISR for wake-up interrupt 2
|
||||
*/
|
||||
static void wakeUp2ISR(void)
|
||||
{
|
||||
_wokeUpByInterrupt = _wakeUp2Interrupt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief RTC Wake-up Timer interrupt handler
|
||||
*/
|
||||
#if defined(STM32F1xx)
|
||||
// F1: Use RTC Alarm interrupt
|
||||
extern "C" void RTC_Alarm_IRQHandler(void)
|
||||
{
|
||||
HAL_RTC_AlarmIRQHandler(&hrtc);
|
||||
}
|
||||
#else
|
||||
// Modern STM32: Use dedicated wake-up timer interrupt
|
||||
extern "C" void RTC_WKUP_IRQHandler(void)
|
||||
{
|
||||
HAL_RTCEx_WakeUpTimerIRQHandler(&hrtc);
|
||||
}
|
||||
#endif
|
||||
|
||||
// ======================== Public Sleep Functions ========================
|
||||
|
||||
uint32_t hwGetSleepRemaining(void)
|
||||
{
|
||||
return sleepRemainingMs;
|
||||
}
|
||||
|
||||
int8_t hwSleep(uint32_t ms)
|
||||
{
|
||||
// TODO: Implement low-power sleep mode
|
||||
// For now, use simple delay
|
||||
// Future: Use STM32 STOP or STANDBY mode with RTC wakeup
|
||||
// Initialize RTC if needed
|
||||
if (!rtcInitialized) {
|
||||
if (!hwSleepInit()) {
|
||||
return MY_SLEEP_NOT_POSSIBLE;
|
||||
}
|
||||
}
|
||||
|
||||
(void)ms;
|
||||
return MY_SLEEP_NOT_POSSIBLE;
|
||||
// Configure RTC wake-up timer
|
||||
if (ms > 0) {
|
||||
if (!hwSleepConfigureTimer(ms)) {
|
||||
return MY_SLEEP_NOT_POSSIBLE;
|
||||
}
|
||||
}
|
||||
|
||||
// Reset sleep remaining
|
||||
sleepRemainingMs = 0ul;
|
||||
|
||||
// CRITICAL: Clear wakeup flags before entering sleep
|
||||
// This prevents spurious wakeups from previous events
|
||||
#if defined(STM32F1xx)
|
||||
__HAL_RTC_ALARM_CLEAR_FLAG(&hrtc, RTC_FLAG_ALRAF);
|
||||
#else
|
||||
__HAL_RTC_WAKEUPTIMER_CLEAR_FLAG(&hrtc, RTC_FLAG_WUTF);
|
||||
#endif
|
||||
__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);
|
||||
|
||||
// Suspend SysTick to prevent 1ms interrupts during sleep
|
||||
HAL_SuspendTick();
|
||||
|
||||
// NOTE: USB CDC will disconnect during STOP mode (expected behavior)
|
||||
// USB peripheral requires system clock which is stopped in STOP mode
|
||||
// After wake-up, the host will detect USB disconnect/reconnect
|
||||
// This is normal and unavoidable when using STOP mode sleep
|
||||
|
||||
// Enter STOP mode with low-power regulator
|
||||
// This achieves 10-50 µA sleep current on STM32F4
|
||||
HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);
|
||||
|
||||
// ====================================================================
|
||||
// === MCU is in STOP mode here (10-50 µA), waiting for wake-up ===
|
||||
// ====================================================================
|
||||
|
||||
// After wake-up: restore system clock (defaults to HSI)
|
||||
hwSleepRestoreSystemClock();
|
||||
|
||||
// Resume SysTick
|
||||
HAL_ResumeTick();
|
||||
|
||||
// CRITICAL: Clear wakeup flags after wake-up
|
||||
// This ensures clean state for next sleep cycle
|
||||
#if defined(STM32F1xx)
|
||||
__HAL_RTC_ALARM_CLEAR_FLAG(&hrtc, RTC_FLAG_ALRAF);
|
||||
#else
|
||||
__HAL_RTC_WAKEUPTIMER_CLEAR_FLAG(&hrtc, RTC_FLAG_WUTF);
|
||||
#endif
|
||||
__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);
|
||||
|
||||
// Disable wake-up timer
|
||||
if (ms > 0) {
|
||||
#if defined(STM32F1xx)
|
||||
HAL_RTC_DeactivateAlarm(&hrtc, RTC_ALARM_A);
|
||||
#else
|
||||
HAL_RTCEx_DeactivateWakeUpTimer(&hrtc);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Always timer wake-up for this variant
|
||||
return MY_WAKE_UP_BY_TIMER;
|
||||
}
|
||||
|
||||
int8_t hwSleep(const uint8_t interrupt, const uint8_t mode, uint32_t ms)
|
||||
{
|
||||
// TODO: Implement interrupt-based sleep
|
||||
// Future: Configure EXTI and enter STOP mode
|
||||
|
||||
(void)interrupt;
|
||||
(void)mode;
|
||||
(void)ms;
|
||||
return MY_SLEEP_NOT_POSSIBLE;
|
||||
// Delegate to dual-interrupt variant with INVALID second interrupt
|
||||
return hwSleep(interrupt, mode, INVALID_INTERRUPT_NUM, 0, ms);
|
||||
}
|
||||
|
||||
int8_t hwSleep(const uint8_t interrupt1, const uint8_t mode1,
|
||||
const uint8_t interrupt2, const uint8_t mode2, uint32_t ms)
|
||||
{
|
||||
// TODO: Implement dual-interrupt sleep
|
||||
// Initialize RTC if needed
|
||||
if (!rtcInitialized) {
|
||||
if (!hwSleepInit()) {
|
||||
return MY_SLEEP_NOT_POSSIBLE;
|
||||
}
|
||||
}
|
||||
|
||||
(void)interrupt1;
|
||||
(void)mode1;
|
||||
(void)interrupt2;
|
||||
(void)mode2;
|
||||
(void)ms;
|
||||
return MY_SLEEP_NOT_POSSIBLE;
|
||||
// Configure RTC wake-up timer (if ms > 0)
|
||||
if (ms > 0) {
|
||||
if (!hwSleepConfigureTimer(ms)) {
|
||||
return MY_SLEEP_NOT_POSSIBLE;
|
||||
}
|
||||
}
|
||||
|
||||
// Reset sleep remaining
|
||||
sleepRemainingMs = 0ul;
|
||||
|
||||
// Configure interrupt wake-up sources
|
||||
_wakeUp1Interrupt = interrupt1;
|
||||
_wakeUp2Interrupt = interrupt2;
|
||||
_wokeUpByInterrupt = INVALID_INTERRUPT_NUM;
|
||||
|
||||
// Attach interrupts in critical section (prevent premature wake-up)
|
||||
MY_CRITICAL_SECTION {
|
||||
if (interrupt1 != INVALID_INTERRUPT_NUM)
|
||||
{
|
||||
attachInterrupt(digitalPinToInterrupt(interrupt1), wakeUp1ISR, mode1);
|
||||
}
|
||||
if (interrupt2 != INVALID_INTERRUPT_NUM)
|
||||
{
|
||||
attachInterrupt(digitalPinToInterrupt(interrupt2), wakeUp2ISR, mode2);
|
||||
}
|
||||
}
|
||||
|
||||
// CRITICAL: Clear wakeup flags before entering sleep
|
||||
#if defined(STM32F1xx)
|
||||
__HAL_RTC_ALARM_CLEAR_FLAG(&hrtc, RTC_FLAG_ALRAF);
|
||||
#else
|
||||
__HAL_RTC_WAKEUPTIMER_CLEAR_FLAG(&hrtc, RTC_FLAG_WUTF);
|
||||
#endif
|
||||
__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);
|
||||
|
||||
// Suspend SysTick
|
||||
HAL_SuspendTick();
|
||||
|
||||
// NOTE: USB CDC will disconnect during STOP mode (expected behavior)
|
||||
// See note in timer-only hwSleep() variant above
|
||||
|
||||
// Enter STOP mode with low-power regulator
|
||||
HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);
|
||||
|
||||
// ====================================================================
|
||||
// === MCU is in STOP mode here (10-50 µA), waiting for wake-up ===
|
||||
// ====================================================================
|
||||
|
||||
// After wake-up: restore system clock
|
||||
hwSleepRestoreSystemClock();
|
||||
|
||||
// Resume SysTick
|
||||
HAL_ResumeTick();
|
||||
|
||||
// CRITICAL: Clear wakeup flags after wake-up
|
||||
#if defined(STM32F1xx)
|
||||
__HAL_RTC_ALARM_CLEAR_FLAG(&hrtc, RTC_FLAG_ALRAF);
|
||||
#else
|
||||
__HAL_RTC_WAKEUPTIMER_CLEAR_FLAG(&hrtc, RTC_FLAG_WUTF);
|
||||
#endif
|
||||
__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);
|
||||
|
||||
// Detach interrupts
|
||||
if (interrupt1 != INVALID_INTERRUPT_NUM) {
|
||||
detachInterrupt(digitalPinToInterrupt(interrupt1));
|
||||
}
|
||||
if (interrupt2 != INVALID_INTERRUPT_NUM) {
|
||||
detachInterrupt(digitalPinToInterrupt(interrupt2));
|
||||
}
|
||||
|
||||
// Disable wake-up timer
|
||||
if (ms > 0) {
|
||||
#if defined(STM32F1xx)
|
||||
HAL_RTC_DeactivateAlarm(&hrtc, RTC_ALARM_A);
|
||||
#else
|
||||
HAL_RTCEx_DeactivateWakeUpTimer(&hrtc);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Determine wake-up source
|
||||
int8_t ret = MY_WAKE_UP_BY_TIMER; // Default: timer wake-up
|
||||
if (_wokeUpByInterrupt != INVALID_INTERRUPT_NUM) {
|
||||
ret = (int8_t)_wokeUpByInterrupt; // Interrupt wake-up
|
||||
}
|
||||
|
||||
// Reset interrupt tracking
|
||||
_wokeUpByInterrupt = INVALID_INTERRUPT_NUM;
|
||||
_wakeUp1Interrupt = INVALID_INTERRUPT_NUM;
|
||||
_wakeUp2Interrupt = INVALID_INTERRUPT_NUM;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -90,7 +90,12 @@
|
||||
|
||||
// Timing functions
|
||||
#define hwMillis() millis()
|
||||
#define hwGetSleepRemaining() (0ul)
|
||||
|
||||
/**
|
||||
* @brief Get remaining sleep time
|
||||
* @return Remaining sleep time in milliseconds
|
||||
*/
|
||||
uint32_t hwGetSleepRemaining(void);
|
||||
|
||||
/**
|
||||
* @brief Initialize hardware
|
||||
@@ -177,31 +182,34 @@ uint16_t hwFreeMem(void);
|
||||
|
||||
/**
|
||||
* @brief Sleep for specified milliseconds
|
||||
* @param ms Milliseconds to sleep
|
||||
* @return Actual sleep time or MY_SLEEP_NOT_POSSIBLE
|
||||
* @note Initial implementation returns MY_SLEEP_NOT_POSSIBLE
|
||||
* @param ms Milliseconds to sleep (0 = sleep until interrupt)
|
||||
* @return MY_WAKE_UP_BY_TIMER (-1) if woken by timer, MY_SLEEP_NOT_POSSIBLE (-2) on error
|
||||
* @note Uses STOP mode with low-power regulator (10-50 µA sleep current)
|
||||
* @note Maximum sleep time depends on RTC configuration (~18 hours)
|
||||
*/
|
||||
int8_t hwSleep(uint32_t ms);
|
||||
|
||||
/**
|
||||
* @brief Sleep with interrupt wake
|
||||
* @param interrupt Pin number for interrupt
|
||||
* @param interrupt Arduino pin number for interrupt wake-up
|
||||
* @param mode Interrupt mode (RISING, FALLING, CHANGE)
|
||||
* @param ms Maximum sleep time
|
||||
* @return Actual sleep time or MY_SLEEP_NOT_POSSIBLE
|
||||
* @note Initial implementation returns MY_SLEEP_NOT_POSSIBLE
|
||||
* @param ms Maximum sleep time in milliseconds (0 = no timeout)
|
||||
* @return Interrupt number (0-255) if woken by interrupt, MY_WAKE_UP_BY_TIMER (-1) if timeout,
|
||||
* MY_SLEEP_NOT_POSSIBLE (-2) on error
|
||||
* @note Supports wake-up on any GPIO pin via EXTI (critical for radio IRQ)
|
||||
*/
|
||||
int8_t hwSleep(const uint8_t interrupt, const uint8_t mode, uint32_t ms);
|
||||
|
||||
/**
|
||||
* @brief Sleep with dual interrupt wake
|
||||
* @param interrupt1 First pin number
|
||||
* @param mode1 First interrupt mode
|
||||
* @param interrupt2 Second pin number
|
||||
* @param mode2 Second interrupt mode
|
||||
* @param ms Maximum sleep time
|
||||
* @return Actual sleep time or MY_SLEEP_NOT_POSSIBLE
|
||||
* @note Initial implementation returns MY_SLEEP_NOT_POSSIBLE
|
||||
* @param interrupt1 First Arduino pin number for interrupt wake-up
|
||||
* @param mode1 First interrupt mode (RISING, FALLING, CHANGE)
|
||||
* @param interrupt2 Second Arduino pin number for interrupt wake-up
|
||||
* @param mode2 Second interrupt mode (RISING, FALLING, CHANGE)
|
||||
* @param ms Maximum sleep time in milliseconds (0 = no timeout)
|
||||
* @return Interrupt number that caused wake-up, MY_WAKE_UP_BY_TIMER (-1) if timeout,
|
||||
* MY_SLEEP_NOT_POSSIBLE (-2) on error
|
||||
* @note Useful for hybrid sensors (e.g., button press OR periodic wake-up)
|
||||
*/
|
||||
int8_t hwSleep(const uint8_t interrupt1, const uint8_t mode1,
|
||||
const uint8_t interrupt2, const uint8_t mode2, uint32_t ms);
|
||||
|
||||
@@ -36,12 +36,9 @@ Should work on any STM32 board supported by the STM32duino core.
|
||||
- [x] CPU frequency reporting
|
||||
- [x] Critical section (interrupt disable/restore)
|
||||
- [x] RAM routing table support
|
||||
|
||||
### Planned 🔄
|
||||
- [ ] Low-power sleep modes (STOP, STANDBY)
|
||||
- [ ] RTC-based timekeeping
|
||||
- [ ] Interrupt-based wake from sleep
|
||||
- [ ] Free memory reporting (heap analysis)
|
||||
- [x] Low-power STOP mode sleep (RTC wake-up timer)
|
||||
- [x] RTC-based timed wake-up (F1: 1s resolution, F4+: subsecond)
|
||||
- [x] Interrupt-based wake from sleep (GPIO EXTI)
|
||||
|
||||
## Pin Mapping
|
||||
|
||||
@@ -118,23 +115,13 @@ debug_tool = stlink
|
||||
|
||||
Common `board` values for platformio.ini:
|
||||
- `blackpill_f401cc` - STM32F401CC Black Pill
|
||||
- `blackpill_f411ce` - STM32F411CE Black Pill (recommended)
|
||||
- `blackpill_f411ce` - STM32F411CE Black Pill
|
||||
- `bluepill_f103c8` - STM32F103C8 Blue Pill
|
||||
- `nucleo_f401re` - STM32F401RE Nucleo
|
||||
- `nucleo_f411re` - STM32F411RE Nucleo
|
||||
- `genericSTM32F103C8` - Generic F103C8
|
||||
- See [PlatformIO boards](https://docs.platformio.org/en/latest/boards/index.html#st-stm32) for complete list
|
||||
|
||||
### Upload Methods
|
||||
|
||||
Supported `upload_protocol` options:
|
||||
- `stlink` - ST-Link V2 programmer (recommended)
|
||||
- `dfu` - USB DFU bootloader (requires boot0 jumper)
|
||||
- `serial` - Serial bootloader (requires FTDI adapter)
|
||||
- `jlink` - Segger J-Link
|
||||
- `blackmagic` - Black Magic Probe
|
||||
- `hid` - HID Bootloader 2.0
|
||||
|
||||
## Arduino IDE Configuration
|
||||
|
||||
1. Install STM32duino core:
|
||||
@@ -224,21 +211,29 @@ The STM32 HAL uses the STM32duino EEPROM library, which provides Flash-based EEP
|
||||
|
||||
Configuration is automatic. EEPROM size can be adjusted in the STM32duino menu or via build flags.
|
||||
|
||||
## Low-Power Considerations
|
||||
## Low-Power Sleep Support
|
||||
|
||||
### Current Status
|
||||
Sleep modes are **NOT YET IMPLEMENTED** in this initial release. Calling `sleep()` functions will return `MY_SLEEP_NOT_POSSIBLE`.
|
||||
### Implemented ✅
|
||||
|
||||
### Future Implementation
|
||||
The STM32 supports several low-power modes:
|
||||
- **Sleep mode**: ~10mA (CPU stopped, peripherals running)
|
||||
- **Stop mode**: ~10-100µA (CPU and most peripherals stopped)
|
||||
- **Standby mode**: ~1-10µA (only backup domain active)
|
||||
**STOP mode sleep** with RTC wake-up is fully functional:
|
||||
|
||||
**Supported Families:**
|
||||
- **STM32F1** (Blue Pill) - RTC alarm-based, 1-second resolution
|
||||
- **STM32F2/F3/F4/F7** - RTC wake-up timer, subsecond resolution
|
||||
- **STM32L1/L4/L5** - RTC wake-up timer, ultra-low power
|
||||
- **STM32G0/G4** - RTC wake-up timer
|
||||
- **STM32H7** - RTC wake-up timer
|
||||
|
||||
**Power Consumption:**
|
||||
- **STM32F1**: 5-20 µA (STOP mode)
|
||||
- **STM32F4**: 10-50 µA (STOP mode)
|
||||
|
||||
**Usage:**
|
||||
```cpp
|
||||
sleep(60000); // Sleep for 60 seconds
|
||||
sleep(interrupt, mode, 60000); // Sleep with interrupt wake-up
|
||||
```
|
||||
|
||||
Implementation will use:
|
||||
- RTC for timed wake-up
|
||||
- EXTI for interrupt wake-up
|
||||
- Backup SRAM for state retention
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
@@ -251,9 +246,6 @@ Implementation will use:
|
||||
**Error: `EEPROM.h not found`**
|
||||
- Solution: Update STM32duino core to latest version (2.0.0+)
|
||||
|
||||
**Error: Undefined reference to `__disable_irq`**
|
||||
- Solution: Ensure CMSIS is included (should be automatic with STM32duino)
|
||||
|
||||
### Upload Issues
|
||||
|
||||
**Upload fails with ST-Link**
|
||||
@@ -267,64 +259,9 @@ Implementation will use:
|
||||
- Verify with: `dfu-util -l`
|
||||
- After upload, set BOOT0 back to 0 (GND)
|
||||
|
||||
### Runtime Issues
|
||||
|
||||
**Serial monitor shows garbage**
|
||||
- Check baud rate matches (default 115200)
|
||||
- USB CDC may require driver on Windows
|
||||
- Try hardware UART instead
|
||||
|
||||
**Radio not working**
|
||||
- Verify 3.3V power supply (nRF24 needs clean power)
|
||||
- Check SPI pin connections
|
||||
- Add 10µF capacitor across radio VCC/GND
|
||||
- Verify CE and CS pin definitions
|
||||
|
||||
**EEPROM not persisting**
|
||||
- EEPROM emulation requires Flash write access
|
||||
- Check for debug mode preventing Flash writes
|
||||
- Verify sufficient Flash space for EEPROM pages
|
||||
|
||||
## Performance Characteristics
|
||||
|
||||
### STM32F411CE Black Pill
|
||||
- **CPU**: 100 MHz ARM Cortex-M4F
|
||||
- **Flash**: 512KB
|
||||
- **RAM**: 128KB
|
||||
- **Current**: ~50mA active, <1µA standby (when implemented)
|
||||
- **MySensors overhead**: ~30KB Flash, ~4KB RAM
|
||||
|
||||
### Benchmarks (preliminary)
|
||||
- **Radio message latency**: <10ms (similar to AVR)
|
||||
- **EEPROM read**: ~50µs per byte
|
||||
- **EEPROM write**: ~5ms per byte (Flash write)
|
||||
- **Temperature reading**: ~100µs
|
||||
|
||||
## Contributing
|
||||
|
||||
This STM32 HAL is designed for easy contribution to the main MySensors repository. When contributing:
|
||||
|
||||
1. Follow MySensors coding style
|
||||
2. Test on multiple STM32 variants if possible
|
||||
3. Document any chip-specific quirks
|
||||
4. Update this README with new features
|
||||
|
||||
## References
|
||||
|
||||
- [STM32duino Core](https://github.com/stm32duino/Arduino_Core_STM32)
|
||||
- [STM32duino Wiki](https://github.com/stm32duino/Arduino_Core_STM32/wiki)
|
||||
- [PlatformIO STM32 Platform](https://docs.platformio.org/en/latest/platforms/ststm32.html)
|
||||
- [MySensors Documentation](https://www.mysensors.org/download)
|
||||
- [STM32 Reference Manuals](https://www.st.com/en/microcontrollers-microprocessors/stm32-32-bit-arm-cortex-mcus.html)
|
||||
|
||||
## License
|
||||
|
||||
This code is part of the MySensors project and is licensed under the GNU General Public License v2.0.
|
||||
|
||||
## Version History
|
||||
|
||||
- **v1.0.0** (2025-01-17) - Initial STM32 HAL implementation
|
||||
- Basic functionality (GPIO, SPI, EEPROM, Serial)
|
||||
- Tested on STM32F401/F411 Black Pill
|
||||
- Gateway and sensor node support
|
||||
- No sleep mode yet (planned for v1.1.0)
|
||||
|
||||
Reference in New Issue
Block a user