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:
dirkju
2025-12-24 17:50:13 +01:00
committed by GitHub
parent bd94b61bed
commit 9369c67903
3 changed files with 541 additions and 121 deletions

View File

@@ -49,6 +49,23 @@
#include "MyHwSTM32.h" #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) bool hwInit(void)
{ {
#if !defined(MY_DISABLED_SERIAL) #if !defined(MY_DISABLED_SERIAL)
@@ -254,36 +271,494 @@ uint16_t hwFreeMem(void)
#endif #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) int8_t hwSleep(uint32_t ms)
{ {
// TODO: Implement low-power sleep mode // Initialize RTC if needed
// For now, use simple delay if (!rtcInitialized) {
// Future: Use STM32 STOP or STANDBY mode with RTC wakeup if (!hwSleepInit()) {
return MY_SLEEP_NOT_POSSIBLE;
}
}
(void)ms; // Configure RTC wake-up timer
return MY_SLEEP_NOT_POSSIBLE; 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) int8_t hwSleep(const uint8_t interrupt, const uint8_t mode, uint32_t ms)
{ {
// TODO: Implement interrupt-based sleep // Delegate to dual-interrupt variant with INVALID second interrupt
// Future: Configure EXTI and enter STOP mode return hwSleep(interrupt, mode, INVALID_INTERRUPT_NUM, 0, ms);
(void)interrupt;
(void)mode;
(void)ms;
return MY_SLEEP_NOT_POSSIBLE;
} }
int8_t hwSleep(const uint8_t interrupt1, const uint8_t mode1, int8_t hwSleep(const uint8_t interrupt1, const uint8_t mode1,
const uint8_t interrupt2, const uint8_t mode2, uint32_t ms) 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; // Configure RTC wake-up timer (if ms > 0)
(void)mode1; if (ms > 0) {
(void)interrupt2; if (!hwSleepConfigureTimer(ms)) {
(void)mode2; return MY_SLEEP_NOT_POSSIBLE;
(void)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;
} }

View File

@@ -90,7 +90,12 @@
// Timing functions // Timing functions
#define hwMillis() millis() #define hwMillis() millis()
#define hwGetSleepRemaining() (0ul)
/**
* @brief Get remaining sleep time
* @return Remaining sleep time in milliseconds
*/
uint32_t hwGetSleepRemaining(void);
/** /**
* @brief Initialize hardware * @brief Initialize hardware
@@ -177,31 +182,34 @@ uint16_t hwFreeMem(void);
/** /**
* @brief Sleep for specified milliseconds * @brief Sleep for specified milliseconds
* @param ms Milliseconds to sleep * @param ms Milliseconds to sleep (0 = sleep until interrupt)
* @return Actual sleep time or MY_SLEEP_NOT_POSSIBLE * @return MY_WAKE_UP_BY_TIMER (-1) if woken by timer, MY_SLEEP_NOT_POSSIBLE (-2) on error
* @note Initial implementation returns MY_SLEEP_NOT_POSSIBLE * @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); int8_t hwSleep(uint32_t ms);
/** /**
* @brief Sleep with interrupt wake * @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 mode Interrupt mode (RISING, FALLING, CHANGE)
* @param ms Maximum sleep time * @param ms Maximum sleep time in milliseconds (0 = no timeout)
* @return Actual sleep time or MY_SLEEP_NOT_POSSIBLE * @return Interrupt number (0-255) if woken by interrupt, MY_WAKE_UP_BY_TIMER (-1) if timeout,
* @note Initial implementation returns MY_SLEEP_NOT_POSSIBLE * 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); int8_t hwSleep(const uint8_t interrupt, const uint8_t mode, uint32_t ms);
/** /**
* @brief Sleep with dual interrupt wake * @brief Sleep with dual interrupt wake
* @param interrupt1 First pin number * @param interrupt1 First Arduino pin number for interrupt wake-up
* @param mode1 First interrupt mode * @param mode1 First interrupt mode (RISING, FALLING, CHANGE)
* @param interrupt2 Second pin number * @param interrupt2 Second Arduino pin number for interrupt wake-up
* @param mode2 Second interrupt mode * @param mode2 Second interrupt mode (RISING, FALLING, CHANGE)
* @param ms Maximum sleep time * @param ms Maximum sleep time in milliseconds (0 = no timeout)
* @return Actual sleep time or MY_SLEEP_NOT_POSSIBLE * @return Interrupt number that caused wake-up, MY_WAKE_UP_BY_TIMER (-1) if timeout,
* @note Initial implementation returns MY_SLEEP_NOT_POSSIBLE * 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, int8_t hwSleep(const uint8_t interrupt1, const uint8_t mode1,
const uint8_t interrupt2, const uint8_t mode2, uint32_t ms); const uint8_t interrupt2, const uint8_t mode2, uint32_t ms);

View File

@@ -36,12 +36,9 @@ Should work on any STM32 board supported by the STM32duino core.
- [x] CPU frequency reporting - [x] CPU frequency reporting
- [x] Critical section (interrupt disable/restore) - [x] Critical section (interrupt disable/restore)
- [x] RAM routing table support - [x] RAM routing table support
- [x] Low-power STOP mode sleep (RTC wake-up timer)
### Planned 🔄 - [x] RTC-based timed wake-up (F1: 1s resolution, F4+: subsecond)
- [ ] Low-power sleep modes (STOP, STANDBY) - [x] Interrupt-based wake from sleep (GPIO EXTI)
- [ ] RTC-based timekeeping
- [ ] Interrupt-based wake from sleep
- [ ] Free memory reporting (heap analysis)
## Pin Mapping ## Pin Mapping
@@ -118,23 +115,13 @@ debug_tool = stlink
Common `board` values for platformio.ini: Common `board` values for platformio.ini:
- `blackpill_f401cc` - STM32F401CC Black Pill - `blackpill_f401cc` - STM32F401CC Black Pill
- `blackpill_f411ce` - STM32F411CE Black Pill (recommended) - `blackpill_f411ce` - STM32F411CE Black Pill
- `bluepill_f103c8` - STM32F103C8 Blue Pill - `bluepill_f103c8` - STM32F103C8 Blue Pill
- `nucleo_f401re` - STM32F401RE Nucleo - `nucleo_f401re` - STM32F401RE Nucleo
- `nucleo_f411re` - STM32F411RE Nucleo - `nucleo_f411re` - STM32F411RE Nucleo
- `genericSTM32F103C8` - Generic F103C8 - `genericSTM32F103C8` - Generic F103C8
- See [PlatformIO boards](https://docs.platformio.org/en/latest/boards/index.html#st-stm32) for complete list - 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 ## Arduino IDE Configuration
1. Install STM32duino core: 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. 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 ### Implemented ✅
Sleep modes are **NOT YET IMPLEMENTED** in this initial release. Calling `sleep()` functions will return `MY_SLEEP_NOT_POSSIBLE`.
### Future Implementation **STOP mode sleep** with RTC wake-up is fully functional:
The STM32 supports several low-power modes:
- **Sleep mode**: ~10mA (CPU stopped, peripherals running) **Supported Families:**
- **Stop mode**: ~10-100µA (CPU and most peripherals stopped) - **STM32F1** (Blue Pill) - RTC alarm-based, 1-second resolution
- **Standby mode**: ~1-10µA (only backup domain active) - **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 ## Troubleshooting
@@ -251,9 +246,6 @@ Implementation will use:
**Error: `EEPROM.h not found`** **Error: `EEPROM.h not found`**
- Solution: Update STM32duino core to latest version (2.0.0+) - 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 Issues
**Upload fails with ST-Link** **Upload fails with ST-Link**
@@ -267,64 +259,9 @@ Implementation will use:
- Verify with: `dfu-util -l` - Verify with: `dfu-util -l`
- After upload, set BOOT0 back to 0 (GND) - 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 ## References
- [STM32duino Core](https://github.com/stm32duino/Arduino_Core_STM32) - [STM32duino Core](https://github.com/stm32duino/Arduino_Core_STM32)
- [STM32duino Wiki](https://github.com/stm32duino/Arduino_Core_STM32/wiki) - [STM32duino Wiki](https://github.com/stm32duino/Arduino_Core_STM32/wiki)
- [PlatformIO STM32 Platform](https://docs.platformio.org/en/latest/platforms/ststm32.html) - [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) - [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)