mirror of
https://github.com/mysensors/MySensors.git
synced 2026-02-20 01:21:27 +01:00
Fix Sleeping, voltage and temperature readings (#1591)
This commit is contained in:
@@ -22,10 +22,10 @@
|
||||
* @brief Hardware abstraction layer for STM32 microcontrollers using STM32duino core
|
||||
*
|
||||
* This implementation uses the official STM32duino Arduino core which provides
|
||||
* STM32Cube HAL underneath. It supports a wide range of STM32 families including
|
||||
* F0, F1, F4, L0, L4, G0, G4, H7, and more.
|
||||
* STM32Cube HAL underneath. It supports a wide range of STM32 families.
|
||||
*
|
||||
* Tested on:
|
||||
* - STM32F103C8 Blue Pill
|
||||
* - STM32F401CC/CE Black Pill
|
||||
* - STM32F411CE Black Pill
|
||||
*
|
||||
@@ -66,6 +66,18 @@ static void hwSleepRestoreSystemClock(void);
|
||||
static void wakeUp1ISR(void);
|
||||
static void wakeUp2ISR(void);
|
||||
|
||||
// Forward declarations for RTC counter helpers (portable across F1 and modern)
|
||||
static uint32_t hwRtcGetCounter(void);
|
||||
|
||||
/**
|
||||
* @brief Prevent HAL_RTC_Init from re-running clock setup
|
||||
* All clock and backup domain configuration is handled in hwSleepInit()
|
||||
*/
|
||||
extern "C" void HAL_RTC_MspInit(RTC_HandleTypeDef *hrtc_param)
|
||||
{
|
||||
(void)hrtc_param;
|
||||
}
|
||||
|
||||
bool hwInit(void)
|
||||
{
|
||||
#if !defined(MY_DISABLED_SERIAL)
|
||||
@@ -86,7 +98,7 @@ bool hwInit(void)
|
||||
void hwReadConfigBlock(void *buf, void *addr, size_t length)
|
||||
{
|
||||
uint8_t *dst = static_cast<uint8_t *>(buf);
|
||||
int pos = reinterpret_cast<int>(addr);
|
||||
const intptr_t pos = reinterpret_cast<intptr_t>(addr);
|
||||
|
||||
for (size_t i = 0; i < length; i++) {
|
||||
dst[i] = EEPROM.read(pos + i);
|
||||
@@ -96,7 +108,7 @@ void hwReadConfigBlock(void *buf, void *addr, size_t length)
|
||||
void hwWriteConfigBlock(void *buf, void *addr, size_t length)
|
||||
{
|
||||
uint8_t *src = static_cast<uint8_t *>(buf);
|
||||
int pos = reinterpret_cast<int>(addr);
|
||||
const intptr_t pos = reinterpret_cast<intptr_t>(addr);
|
||||
|
||||
for (size_t i = 0; i < length; i++) {
|
||||
EEPROM.update(pos + i, src[i]);
|
||||
@@ -117,8 +129,6 @@ void hwWatchdogReset(void)
|
||||
{
|
||||
#if defined(HAL_IWDG_MODULE_ENABLED) && defined(IWDG)
|
||||
// Reset independent watchdog if enabled
|
||||
// Use direct register write to reload watchdog counter
|
||||
// This works whether IWDG was initialized by HAL or LL drivers
|
||||
IWDG->KR = IWDG_KEY_RELOAD;
|
||||
#endif
|
||||
}
|
||||
@@ -128,145 +138,234 @@ void hwReboot(void)
|
||||
NVIC_SystemReset();
|
||||
}
|
||||
|
||||
int32_t hwReadInternalTemp(void)
|
||||
{
|
||||
#if defined(ATEMP)
|
||||
// STM32duino defines ATEMP — use the easy path
|
||||
analogReadResolution(12);
|
||||
return (int32_t)analogRead(ATEMP);
|
||||
#elif defined(STM32F1xx) && defined(ADC1)
|
||||
// STM32F1: ATEMP not defined in STM32duino, read ADC channel 16 directly
|
||||
// Temperature sensor is on ADC1_IN16, requires TSVREFE bit enabled
|
||||
|
||||
// Enable ADC1 clock
|
||||
__HAL_RCC_ADC1_CLK_ENABLE();
|
||||
|
||||
// Enable temperature sensor and VREFINT (TSVREFE bit in CR2)
|
||||
ADC1->CR2 |= ADC_CR2_TSVREFE;
|
||||
|
||||
// Wait for sensor startup (~10 µs per datasheet)
|
||||
delayMicroseconds(20);
|
||||
|
||||
// Configure channel 16, sample time = 239.5 cycles (longest, for accuracy)
|
||||
// SMP16 is bits [23:21] in SMPR1, value 0b111 = 239.5 cycles
|
||||
ADC1->SMPR1 |= (0x7UL << 18); // SMP16 = 239.5 cycles
|
||||
|
||||
// Set sequence: 1 conversion, channel 16
|
||||
ADC1->SQR3 = 16; // First conversion = channel 16
|
||||
ADC1->SQR1 = 0; // 1 conversion in sequence
|
||||
|
||||
// Ensure ADC is on
|
||||
ADC1->CR2 |= ADC_CR2_ADON;
|
||||
delayMicroseconds(2);
|
||||
|
||||
// Start conversion
|
||||
ADC1->CR2 |= ADC_CR2_ADON; // Second write starts conversion on F1
|
||||
|
||||
// Wait for conversion complete (EOC flag)
|
||||
uint32_t timeout = 100000;
|
||||
while (((ADC1->SR & ADC_SR_EOC) == 0) && (timeout > 0)) {
|
||||
timeout--;
|
||||
}
|
||||
|
||||
if (timeout == 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int32_t raw = (int32_t)(ADC1->DR & 0x0FFF);
|
||||
|
||||
// Disable temperature sensor to save power
|
||||
ADC1->CR2 &= ~ADC_CR2_TSVREFE;
|
||||
|
||||
return raw;
|
||||
#else
|
||||
return -1;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
void hwRandomNumberInit(void)
|
||||
{
|
||||
// Use internal temperature sensor and ADC noise as entropy source
|
||||
// This provides reasonably good random seed values
|
||||
#if defined(HAL_RNG_MODULE_ENABLED) && defined(RNG)
|
||||
// ============================================================
|
||||
// Hardware RNG available (F2, F4x5/F4x7+, F7, L4, G0, G4, H7)
|
||||
// ============================================================
|
||||
__HAL_RCC_RNG_CLK_ENABLE();
|
||||
|
||||
#ifdef ADC1
|
||||
RNG_HandleTypeDef hrng = {0};
|
||||
hrng.Instance = RNG;
|
||||
|
||||
if (HAL_RNG_Init(&hrng) == HAL_OK) {
|
||||
uint32_t seed = 0;
|
||||
if (HAL_RNG_GenerateRandomNumber(&hrng, &seed) == HAL_OK) {
|
||||
randomSeed(seed);
|
||||
// Disable RNG clock to save power after seeding
|
||||
HAL_RNG_DeInit(&hrng);
|
||||
__HAL_RCC_RNG_CLK_DISABLE();
|
||||
return;
|
||||
}
|
||||
HAL_RNG_DeInit(&hrng);
|
||||
}
|
||||
__HAL_RCC_RNG_CLK_DISABLE();
|
||||
// Fall through to software entropy if hardware RNG failed
|
||||
|
||||
#endif // HAL_RNG_MODULE_ENABLED && RNG
|
||||
|
||||
// ============================================================
|
||||
// Software entropy fallback (F0, F1, F3, and chips without RNG)
|
||||
// Uses ADC noise from temp sensor, VREFINT, and timing jitter
|
||||
// ============================================================
|
||||
|
||||
#if defined(ADC1)
|
||||
uint32_t seed = 0;
|
||||
|
||||
// Read multiple samples from different sources for entropy
|
||||
for (uint8_t i = 0; i < 32; i++) {
|
||||
uint32_t value = 0;
|
||||
uint32_t value = hwReadInternalTemp();
|
||||
|
||||
#ifdef TEMP_SENSOR_AVAILABLE
|
||||
// Try to read internal temperature sensor if available
|
||||
value ^= analogRead(ATEMP);
|
||||
#endif
|
||||
|
||||
#ifdef VREF_AVAILABLE
|
||||
// Mix in internal voltage reference reading
|
||||
#if defined(AVREF)
|
||||
value ^= analogRead(AVREF);
|
||||
#endif
|
||||
|
||||
// Mix in current time
|
||||
value ^= hwMillis();
|
||||
|
||||
// Mix in system tick
|
||||
value ^= micros();
|
||||
|
||||
// Accumulate into seed
|
||||
seed ^= (value & 0x7) << (i % 29);
|
||||
// Rotate and XOR for better bit distribution
|
||||
seed ^= (i == 0) ? value : ((value << i) | (value >> (32 - i)));
|
||||
|
||||
// Small delay to ensure values change
|
||||
delayMicroseconds(100);
|
||||
}
|
||||
|
||||
randomSeed(seed);
|
||||
#else
|
||||
// Fallback: use millis as weak entropy source
|
||||
randomSeed(hwMillis());
|
||||
// Last resort: weak entropy from timing only
|
||||
randomSeed(hwMillis() ^ (micros() << 16));
|
||||
#endif // ADC1
|
||||
}
|
||||
|
||||
bool hwUniqueID(unique_id_t *uniqueID)
|
||||
{
|
||||
#ifdef UID_BASE
|
||||
// padding
|
||||
#if defined(UID_BASE)
|
||||
(void)memset(reinterpret_cast<uint8_t *>(uniqueID), MY_HWID_PADDING_BYTE, sizeof(unique_id_t));
|
||||
(void)memcpy(reinterpret_cast<uint8_t *>(uniqueID), (uint32_t *)UID_BASE, 12);
|
||||
(void)memcpy(reinterpret_cast<uint8_t *>(uniqueID), (const uint8_t *)UID_BASE, 12);
|
||||
return true;
|
||||
#else
|
||||
// Unique ID not available on this variant
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
uint16_t hwCPUVoltage(void)
|
||||
{
|
||||
#if defined(VREF_AVAILABLE) && defined(AVREF) && defined(__HAL_RCC_ADC1_CLK_ENABLE)
|
||||
// Read internal voltage reference to calculate VDD
|
||||
// VREFINT is typically 1.2V (varies by STM32 family)
|
||||
|
||||
#if defined(AVREF) && defined(__HAL_RCC_ADC1_CLK_ENABLE)
|
||||
// Force 12-bit resolution for predictable raw values
|
||||
analogReadResolution(12);
|
||||
uint32_t vrefint = analogRead(AVREF);
|
||||
|
||||
if (vrefint > 0) {
|
||||
// Calculate VDD in millivolts
|
||||
// Formula: VDD = 3.3V * 4096 / ADC_reading
|
||||
// Adjusted: VDD = 1200mV * 4096 / vrefint_reading
|
||||
return (uint16_t)((1200UL * 4096UL) / vrefint);
|
||||
#if defined(VREFINT_CAL_ADDR) && defined(VREFINT_CAL_VREF)
|
||||
// Use factory calibration for accurate measurement
|
||||
// VREFINT_CAL was measured at VREFINT_CAL_VREF mV with 12-bit ADC
|
||||
uint16_t vrefint_cal = *((uint16_t *)VREFINT_CAL_ADDR);
|
||||
return (uint16_t)((uint32_t)VREFINT_CAL_VREF * vrefint_cal / vrefint);
|
||||
#else
|
||||
// No factory calibration (e.g., STM32F103)
|
||||
// Assume typical VREFINT = 1.2V, 12-bit ADC
|
||||
return (uint16_t)((1200UL * 4095UL) / vrefint);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
// Return typical 3.3V if measurement not available
|
||||
return 3300;
|
||||
}
|
||||
|
||||
uint16_t hwCPUFrequency(void)
|
||||
{
|
||||
// Return CPU frequency in 0.1 MHz units
|
||||
// F_CPU is defined by the build system (e.g., 84000000 for 84 MHz)
|
||||
return F_CPU / 100000UL;
|
||||
// HAL_RCC_GetSysClockFreq() reads the actual clock configuration registers
|
||||
// and returns the current SYSCLK frequency in Hz
|
||||
return (uint16_t)(HAL_RCC_GetSysClockFreq() / 100000UL);
|
||||
}
|
||||
|
||||
int8_t hwCPUTemperature(void)
|
||||
{
|
||||
#if defined(TEMP_SENSOR_AVAILABLE) && defined(ATEMP) && defined(__HAL_RCC_ADC1_CLK_ENABLE)
|
||||
// Read internal temperature sensor
|
||||
// Note: Requires calibration values for accurate results
|
||||
// cppcheck-suppress knownConditionTrueFalse
|
||||
int32_t temp_raw = hwReadInternalTemp();
|
||||
|
||||
int32_t temp_raw = analogRead(ATEMP);
|
||||
if (temp_raw < 0) { // cppcheck-suppress knownConditionTrueFalse
|
||||
return FUNCTION_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
#ifdef TEMP110_CAL_ADDR
|
||||
// Use factory calibration if available (STM32F4, L4, etc.)
|
||||
uint16_t *temp30_cal = (uint16_t *)TEMP30_CAL_ADDR;
|
||||
uint16_t *temp110_cal = (uint16_t *)TEMP110_CAL_ADDR;
|
||||
#if defined(TEMP110_CAL_ADDR) && defined(TEMP30_CAL_ADDR)
|
||||
// Use factory calibration if available (STM32F4, L4, G4, etc.)
|
||||
uint16_t cal30 = *((uint16_t *)TEMP30_CAL_ADDR);
|
||||
uint16_t cal110 = *((uint16_t *)TEMP110_CAL_ADDR);
|
||||
|
||||
if (temp30_cal && temp110_cal && *temp110_cal != *temp30_cal) {
|
||||
// Calculate temperature using two-point calibration
|
||||
// Formula: T = ((110-30) / (CAL_110 - CAL_30)) * (raw - CAL_30) + 30
|
||||
int32_t temp = 30 + ((110 - 30) * (temp_raw - *temp30_cal)) /
|
||||
(*temp110_cal - *temp30_cal);
|
||||
|
||||
// Apply user calibration
|
||||
temp = (temp - MY_STM32_TEMPERATURE_OFFSET) / MY_STM32_TEMPERATURE_GAIN;
|
||||
if (cal110 != cal30) {
|
||||
int32_t temp = 30 + ((110 - 30) * (temp_raw - (int32_t)cal30)) /
|
||||
((int32_t)cal110 - (int32_t)cal30);
|
||||
|
||||
temp = ((temp - MY_STM32_TEMPERATURE_OFFSET) * 100) / MY_STM32_TEMPERATURE_GAIN;
|
||||
return (int8_t)temp;
|
||||
}
|
||||
#endif // TEMP110_CAL_ADDR
|
||||
#endif
|
||||
|
||||
// Fallback: use typical values (less accurate)
|
||||
// Typical slope: 2.5 mV/°C, V25 = 0.76V for STM32F4
|
||||
// This is a rough approximation
|
||||
// Fallback: use typical datasheet values (no factory calibration)
|
||||
#if defined(STM32F1xx)
|
||||
// STM32F1: V25 = 1.43V, slope = 4.3 mV/°C, slope is NEGATIVE (higher T = lower V)
|
||||
float voltage = (temp_raw * 3.3f) / 4096.0f;
|
||||
int32_t temp = 25 + (int32_t)((1.43f - voltage) / 0.0043f);
|
||||
#else
|
||||
// STM32F4 and others without calibration: V25 = 0.76V, slope = 2.5 mV/°C
|
||||
float voltage = (temp_raw * 3.3f) / 4096.0f;
|
||||
int32_t temp = 25 + (int32_t)((voltage - 0.76f) / 0.0025f);
|
||||
|
||||
return (int8_t)((temp - MY_STM32_TEMPERATURE_OFFSET) / MY_STM32_TEMPERATURE_GAIN);
|
||||
#else
|
||||
// Temperature sensor not available
|
||||
return FUNCTION_NOT_SUPPORTED;
|
||||
#endif
|
||||
|
||||
return (int8_t)(((temp - MY_STM32_TEMPERATURE_OFFSET) * 100) / MY_STM32_TEMPERATURE_GAIN);
|
||||
}
|
||||
|
||||
extern "C" caddr_t _sbrk(int incr);
|
||||
|
||||
uint16_t hwFreeMem(void)
|
||||
{
|
||||
// Calculate free heap memory
|
||||
// This uses newlib's mallinfo if available
|
||||
// Use ARM semihosting / newlib approach
|
||||
// _sbrk(0) returns current program break (top of heap)
|
||||
|
||||
#ifdef STACK_TOP
|
||||
extern char *__brkval;
|
||||
extern char __heap_start;
|
||||
|
||||
char *heap_end = __brkval ? __brkval : &__heap_start;
|
||||
char stack_var;
|
||||
char *heap_end = (char *)_sbrk(0);
|
||||
|
||||
if (heap_end != (char *)-1) {
|
||||
return (uint16_t)(&stack_var - heap_end);
|
||||
}
|
||||
|
||||
// Calculate space between heap and stack
|
||||
return (uint16_t)(&stack_var - heap_end);
|
||||
#else
|
||||
// Alternative method: try to allocate and measure
|
||||
// Not implemented to avoid fragmentation
|
||||
return FUNCTION_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
// ======================== RTC Counter Helpers ========================
|
||||
|
||||
/**
|
||||
* @brief Read current RTC counter value (portable)
|
||||
* @return 32-bit counter on F1, subsecond-approximated tick on modern STM32
|
||||
*/
|
||||
static uint32_t hwRtcGetCounter(void)
|
||||
{
|
||||
#if defined(STM32F1xx)
|
||||
return (uint32_t)(RTC->CNTL | (RTC->CNTH << 16));
|
||||
#else
|
||||
// Modern STM32: use HAL to get time and compute seconds since epoch
|
||||
// For sleep remaining calculation, we only need relative elapsed seconds
|
||||
RTC_TimeTypeDef sTime = {0};
|
||||
HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN);
|
||||
// Must read date after time to unlock shadow registers
|
||||
RTC_DateTypeDef sDate = {0};
|
||||
HAL_RTC_GetDate(&hrtc, &sDate, RTC_FORMAT_BIN);
|
||||
return (uint32_t)(sTime.Hours * 3600 + sTime.Minutes * 60 + sTime.Seconds);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -282,103 +381,107 @@ static bool hwSleepInit(void)
|
||||
return true;
|
||||
}
|
||||
|
||||
// Enable PWR clock
|
||||
// ---- Enable PWR and backup domain access ----
|
||||
#if defined(STM32F1xx)
|
||||
// STM32F1 requires BKP clock enabled BEFORE PWR clock
|
||||
__HAL_RCC_BKP_CLK_ENABLE();
|
||||
__DSB();
|
||||
__HAL_RCC_PWR_CLK_ENABLE();
|
||||
__DSB();
|
||||
|
||||
// Enable backup domain access
|
||||
// Dummy read to ensure clocks are active before accessing PWR registers
|
||||
volatile uint32_t dummy = RCC->APB1ENR;
|
||||
(void)dummy;
|
||||
|
||||
// Direct register write for backup domain access
|
||||
PWR->CR |= PWR_CR_DBP;
|
||||
__DSB();
|
||||
#else
|
||||
// Modern STM32 (F2/F3/F4/F7/L0/L1/L4/L5/G0/G4/H7)
|
||||
__HAL_RCC_PWR_CLK_ENABLE();
|
||||
HAL_PWR_EnableBkUpAccess();
|
||||
#endif
|
||||
|
||||
// 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
|
||||
// ---- Reset backup domain if RTC not already configured ----
|
||||
if ((RCC->BDCR & RCC_BDCR_RTCEN) == 0) {
|
||||
__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
|
||||
// ---- Select RTC clock source: try LSE, fall back to LSI ----
|
||||
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
|
||||
uint32_t timeout = 2000000;
|
||||
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
|
||||
RCC->BDCR &= ~RCC_BDCR_LSEON; // Disable failed LSE
|
||||
|
||||
// Enable LSI (internal ~32 kHz oscillator)
|
||||
if ((RCC->CSR & RCC_CSR_LSIRDY) == 0) {
|
||||
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)
|
||||
// ---- Configure RTC clock source ----
|
||||
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
|
||||
// Changing RTCSEL requires backup domain reset on most STM32 families
|
||||
__HAL_RCC_BACKUPRESET_FORCE();
|
||||
__HAL_RCC_BACKUPRESET_RELEASE();
|
||||
RCC->BDCR |= desiredRtcSel;
|
||||
}
|
||||
RCC->BDCR |= RCC_BDCR_RTCEN; // Ensure RTC clock is enabled
|
||||
RCC->BDCR |= RCC_BDCR_RTCEN;
|
||||
|
||||
// Initialize RTC peripheral
|
||||
// ---- 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
|
||||
hrtc.Init.AsynchPrediv = 32767; // 32768 Hz / 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.AsynchPrediv = 39999; // ~40 kHz / 40000 = 1 Hz (F1 LSI is ~40 kHz)
|
||||
}
|
||||
|
||||
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
|
||||
// Clear any stale alarm flags before enabling interrupts
|
||||
RTC->CRL &= ~RTC_CRL_ALRF;
|
||||
EXTI->PR = EXTI_PR_PR17;
|
||||
NVIC_ClearPendingIRQ(RTC_Alarm_IRQn);
|
||||
|
||||
// Configure EXTI line 17 for RTC Alarm wake-up from STOP mode
|
||||
// Without EXTI, the alarm flag is set but the CPU does not wake
|
||||
EXTI->IMR |= EXTI_IMR_MR17; // Unmask EXTI line 17
|
||||
EXTI->RTSR |= EXTI_RTSR_TR17; // Rising edge trigger
|
||||
|
||||
HAL_NVIC_SetPriority(RTC_Alarm_IRQn, 0, 0);
|
||||
HAL_NVIC_EnableIRQ(RTC_Alarm_IRQn);
|
||||
|
||||
@@ -386,40 +489,32 @@ static bool hwSleepInit(void)
|
||||
// ============================================================
|
||||
// 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
|
||||
hrtc.Init.AsynchPrediv = 127; // 128
|
||||
hrtc.Init.SynchPrediv = 255; // 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.SynchPrediv = 249; // Adjusted for typical ~32 kHz 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
|
||||
// Check if RTC is already initialized (avoid reinit conflicts with SPI etc.)
|
||||
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
|
||||
// Configure EXTI line 22 for RTC wake-up timer (required for STOP mode wake)
|
||||
// HAL_RTCEx_SetWakeUpTimer_IT handles this on most families, but be explicit
|
||||
HAL_NVIC_SetPriority(RTC_WKUP_IRQn, 0, 0);
|
||||
HAL_NVIC_EnableIRQ(RTC_WKUP_IRQn);
|
||||
|
||||
@@ -437,17 +532,13 @@ static bool hwSleepInit(void)
|
||||
static bool hwSleepConfigureTimer(uint32_t ms)
|
||||
{
|
||||
if (!rtcInitialized) {
|
||||
if (!hwSleepInit()) {
|
||||
return false;
|
||||
}
|
||||
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;
|
||||
@@ -455,35 +546,33 @@ static bool hwSleepConfigureTimer(uint32_t ms)
|
||||
|
||||
#if defined(STM32F1xx)
|
||||
// ============================================================
|
||||
// STM32F1: Use RTC Alarm for wake-up
|
||||
// STM32F1: Use RTC Alarm (counter-based)
|
||||
// ============================================================
|
||||
// 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);
|
||||
uint32_t currentCounter = hwRtcGetCounter();
|
||||
|
||||
// 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;
|
||||
// Write alarm registers directly via configuration mode
|
||||
while ((RTC->CRL & RTC_CRL_RTOFF) == 0) { } // Wait for last write
|
||||
RTC->CRL |= RTC_CRL_CNF; // Enter config mode
|
||||
|
||||
if (HAL_RTC_SetAlarm_IT(&hrtc, &sAlarm, RTC_FORMAT_BIN) != HAL_OK) {
|
||||
return false;
|
||||
}
|
||||
RTC->ALRH = (alarmValue >> 16) & 0xFFFF;
|
||||
RTC->ALRL = alarmValue & 0xFFFF;
|
||||
|
||||
RTC->CRL &= ~RTC_CRL_CNF; // Exit config mode
|
||||
while ((RTC->CRL & RTC_CRL_RTOFF) == 0) { } // Wait for write complete
|
||||
|
||||
// Enable alarm interrupt in RTC
|
||||
RTC->CRH |= RTC_CRH_ALRIE;
|
||||
|
||||
#else
|
||||
// ============================================================
|
||||
@@ -493,32 +582,28 @@ static bool hwSleepConfigureTimer(uint32_t ms)
|
||||
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)
|
||||
// Up to 32 seconds: use RTCCLK/16 (2048 Hz, ~0.49 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;
|
||||
wakeUpCounter = (ms << 11) / 1000; // ms * 2048 / 1000
|
||||
if (wakeUpCounter < 2) {
|
||||
wakeUpCounter = 2; // Minimum 2 ticks
|
||||
wakeUpCounter = 2;
|
||||
}
|
||||
if (wakeUpCounter > 0xFFFF) {
|
||||
wakeUpCounter = 0xFFFF;
|
||||
}
|
||||
} else {
|
||||
// More than 32 seconds: use CK_SPRE (1 Hz, 1 second resolution)
|
||||
// Over 32 seconds: use CK_SPRE (1 Hz, 1 second resolution)
|
||||
wakeUpClock = RTC_WAKEUPCLOCK_CK_SPRE_16BITS;
|
||||
wakeUpCounter = ms / 1000; // Convert to seconds
|
||||
wakeUpCounter = ms / 1000;
|
||||
if (wakeUpCounter == 0) {
|
||||
wakeUpCounter = 1; // Minimum 1 second
|
||||
wakeUpCounter = 1;
|
||||
}
|
||||
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;
|
||||
}
|
||||
@@ -530,14 +615,11 @@ static bool hwSleepConfigureTimer(uint32_t ms)
|
||||
|
||||
/**
|
||||
* @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.
|
||||
* @note After STOP mode, system clock defaults to HSI (8 MHz on F1, 16 MHz on F4).
|
||||
* SystemClock_Config() restores the full PLL configuration.
|
||||
*/
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -558,16 +640,16 @@ static void wakeUp2ISR(void)
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief RTC Wake-up Timer interrupt handler
|
||||
* @brief RTC interrupt handlers
|
||||
*/
|
||||
#if defined(STM32F1xx)
|
||||
// F1: Use RTC Alarm interrupt
|
||||
extern "C" void RTC_Alarm_IRQHandler(void)
|
||||
{
|
||||
HAL_RTC_AlarmIRQHandler(&hrtc);
|
||||
// Lean handler: clear flags directly instead of going through HAL
|
||||
EXTI->PR = EXTI_PR_PR17; // Clear EXTI line 17 pending
|
||||
RTC->CRL &= ~RTC_CRL_ALRF; // Clear RTC alarm flag
|
||||
}
|
||||
#else
|
||||
// Modern STM32: Use dedicated wake-up timer interrupt
|
||||
extern "C" void RTC_WKUP_IRQHandler(void)
|
||||
{
|
||||
HAL_RTCEx_WakeUpTimerIRQHandler(&hrtc);
|
||||
@@ -581,87 +663,27 @@ uint32_t hwGetSleepRemaining(void)
|
||||
return sleepRemainingMs;
|
||||
}
|
||||
|
||||
int8_t hwSleep(uint32_t ms)
|
||||
/**
|
||||
* @brief Common sleep implementation
|
||||
* @param interrupt1 First interrupt pin (INVALID_INTERRUPT_NUM = none)
|
||||
* @param mode1 First interrupt mode
|
||||
* @param interrupt2 Second interrupt pin (INVALID_INTERRUPT_NUM = none)
|
||||
* @param mode2 Second interrupt mode
|
||||
* @param ms Sleep duration in ms (0 = interrupt-only, no timer)
|
||||
* @return Wake-up source or error code
|
||||
*/
|
||||
static int8_t hwSleepInternal(const uint8_t interrupt1, const uint8_t mode1,
|
||||
const uint8_t interrupt2, const uint8_t mode2,
|
||||
uint32_t ms)
|
||||
{
|
||||
// Initialize RTC if needed
|
||||
if (!rtcInitialized) {
|
||||
if (!hwSleepInit()) {
|
||||
return MY_SLEEP_NOT_POSSIBLE;
|
||||
}
|
||||
bool hasInterrupt = (interrupt1 != INVALID_INTERRUPT_NUM) ||
|
||||
(interrupt2 != INVALID_INTERRUPT_NUM);
|
||||
|
||||
// Reject timer-only sleep with ms=0 (would sleep forever with no wake source)
|
||||
if (ms == 0 && !hasInterrupt) {
|
||||
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)
|
||||
{
|
||||
// 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)
|
||||
{
|
||||
// Initialize RTC if needed
|
||||
if (!rtcInitialized) {
|
||||
if (!hwSleepInit()) {
|
||||
@@ -676,61 +698,95 @@ int8_t hwSleep(const uint8_t interrupt1, const uint8_t mode1,
|
||||
}
|
||||
}
|
||||
|
||||
// Reset sleep remaining
|
||||
// Reset sleep state
|
||||
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);
|
||||
// Attach interrupt wake-up sources
|
||||
if (hasInterrupt) {
|
||||
_wakeUp1Interrupt = interrupt1;
|
||||
_wakeUp2Interrupt = interrupt2;
|
||||
|
||||
MY_CRITICAL_SECTION {
|
||||
if (interrupt1 != INVALID_INTERRUPT_NUM)
|
||||
{
|
||||
attachInterrupt(digitalPinToInterrupt(interrupt1), wakeUp1ISR, mode1);
|
||||
}
|
||||
if (interrupt2 != INVALID_INTERRUPT_NUM)
|
||||
{
|
||||
attachInterrupt(digitalPinToInterrupt(interrupt2), wakeUp2ISR, mode2);
|
||||
}
|
||||
}
|
||||
if (interrupt2 != INVALID_INTERRUPT_NUM)
|
||||
{
|
||||
attachInterrupt(digitalPinToInterrupt(interrupt2), wakeUp2ISR, mode2);
|
||||
|
||||
// Check if an interrupt already fired between attach and here
|
||||
if (_wokeUpByInterrupt != INVALID_INTERRUPT_NUM) {
|
||||
// Already woken — skip sleep, go to cleanup
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
// CRITICAL: Clear wakeup flags before entering sleep
|
||||
// Record RTC counter before sleep (for sleepRemaining calculation)
|
||||
{
|
||||
const uint32_t sleepStartCounter = hwRtcGetCounter();
|
||||
|
||||
// Clear all wake-up flags before entering STOP mode
|
||||
#if defined(STM32F1xx)
|
||||
__HAL_RTC_ALARM_CLEAR_FLAG(&hrtc, RTC_FLAG_ALRAF);
|
||||
RTC->CRL &= ~RTC_CRL_ALRF;
|
||||
EXTI->PR = EXTI_PR_PR17;
|
||||
NVIC_ClearPendingIRQ(RTC_Alarm_IRQn);
|
||||
#else
|
||||
__HAL_RTC_WAKEUPTIMER_CLEAR_FLAG(&hrtc, RTC_FLAG_WUTF);
|
||||
__HAL_RTC_WAKEUPTIMER_CLEAR_FLAG(&hrtc, RTC_FLAG_WUTF);
|
||||
#endif
|
||||
__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);
|
||||
__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);
|
||||
|
||||
// Suspend SysTick
|
||||
HAL_SuspendTick();
|
||||
// Suspend SysTick to prevent 1ms interrupts during sleep
|
||||
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
|
||||
// NOTE: USB CDC will disconnect (expected; USB needs system clock)
|
||||
HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);
|
||||
|
||||
// Enter STOP mode with low-power regulator
|
||||
HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);
|
||||
// ================================================================
|
||||
// === MCU is in STOP mode here, waiting for wake-up event ===
|
||||
// ================================================================
|
||||
|
||||
// ====================================================================
|
||||
// === MCU is in STOP mode here (10-50 µA), waiting for wake-up ===
|
||||
// ====================================================================
|
||||
// Restore system clock (HSI -> PLL)
|
||||
hwSleepRestoreSystemClock();
|
||||
|
||||
// After wake-up: restore system clock
|
||||
hwSleepRestoreSystemClock();
|
||||
// Resume SysTick
|
||||
HAL_ResumeTick();
|
||||
|
||||
// Resume SysTick
|
||||
HAL_ResumeTick();
|
||||
|
||||
// CRITICAL: Clear wakeup flags after wake-up
|
||||
// Clear wake-up flags after wake
|
||||
#if defined(STM32F1xx)
|
||||
__HAL_RTC_ALARM_CLEAR_FLAG(&hrtc, RTC_FLAG_ALRAF);
|
||||
RTC->CRL &= ~RTC_CRL_ALRF;
|
||||
EXTI->PR = EXTI_PR_PR17;
|
||||
#else
|
||||
__HAL_RTC_WAKEUPTIMER_CLEAR_FLAG(&hrtc, RTC_FLAG_WUTF);
|
||||
__HAL_RTC_WAKEUPTIMER_CLEAR_FLAG(&hrtc, RTC_FLAG_WUTF);
|
||||
#endif
|
||||
__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);
|
||||
__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);
|
||||
|
||||
// Calculate sleep remaining
|
||||
if (ms > 0) {
|
||||
const uint32_t sleepEndCounter = hwRtcGetCounter();
|
||||
uint32_t elapsedSeconds;
|
||||
|
||||
#if defined(STM32F1xx)
|
||||
elapsedSeconds = sleepEndCounter - sleepStartCounter;
|
||||
#else
|
||||
// Handle midnight rollover (86400 seconds in a day)
|
||||
if (sleepEndCounter >= sleepStartCounter) {
|
||||
elapsedSeconds = sleepEndCounter - sleepStartCounter;
|
||||
} else {
|
||||
elapsedSeconds = (86400 - sleepStartCounter) + sleepEndCounter;
|
||||
}
|
||||
#endif
|
||||
|
||||
const uint32_t elapsedMs = elapsedSeconds * 1000;
|
||||
sleepRemainingMs = (elapsedMs < ms) ? (ms - elapsedMs) : 0;
|
||||
}
|
||||
}
|
||||
|
||||
cleanup:
|
||||
// Detach interrupts
|
||||
if (interrupt1 != INVALID_INTERRUPT_NUM) {
|
||||
detachInterrupt(digitalPinToInterrupt(interrupt1));
|
||||
@@ -749,9 +805,9 @@ int8_t hwSleep(const uint8_t interrupt1, const uint8_t mode1,
|
||||
}
|
||||
|
||||
// Determine wake-up source
|
||||
int8_t ret = MY_WAKE_UP_BY_TIMER; // Default: timer wake-up
|
||||
int8_t ret = MY_WAKE_UP_BY_TIMER;
|
||||
if (_wokeUpByInterrupt != INVALID_INTERRUPT_NUM) {
|
||||
ret = (int8_t)_wokeUpByInterrupt; // Interrupt wake-up
|
||||
ret = (int8_t)_wokeUpByInterrupt;
|
||||
}
|
||||
|
||||
// Reset interrupt tracking
|
||||
@@ -761,3 +817,19 @@ int8_t hwSleep(const uint8_t interrupt1, const uint8_t mode1,
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int8_t hwSleep(uint32_t ms)
|
||||
{
|
||||
return hwSleepInternal(INVALID_INTERRUPT_NUM, 0, INVALID_INTERRUPT_NUM, 0, ms);
|
||||
}
|
||||
|
||||
int8_t hwSleep(const uint8_t interrupt, const uint8_t mode, uint32_t ms)
|
||||
{
|
||||
return hwSleepInternal(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)
|
||||
{
|
||||
return hwSleepInternal(interrupt1, mode1, interrupt2, mode2, ms);
|
||||
}
|
||||
|
||||
@@ -20,13 +20,13 @@
|
||||
#ifndef MyHwSTM32_h
|
||||
#define MyHwSTM32_h
|
||||
|
||||
#include <SPI.h>
|
||||
#include <EEPROM.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
#include <Arduino.h>
|
||||
#endif
|
||||
|
||||
#include <SPI.h>
|
||||
#include <EEPROM.h>
|
||||
|
||||
// Crypto endianness
|
||||
#define CRYPTO_LITTLE_ENDIAN
|
||||
|
||||
@@ -46,18 +46,20 @@
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Temperature sensor offset calibration
|
||||
* @brief Temperature sensor offset calibration (integer)
|
||||
* @note Adjust based on your specific STM32 chip calibration
|
||||
* @note Calibration model: T_corrected = (T_raw - offset) / gain
|
||||
*/
|
||||
#ifndef MY_STM32_TEMPERATURE_OFFSET
|
||||
#define MY_STM32_TEMPERATURE_OFFSET (0.0f)
|
||||
#define MY_STM32_TEMPERATURE_OFFSET (0)
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Temperature sensor gain calibration
|
||||
* @brief Temperature sensor gain calibration (integer, scaled x100)
|
||||
* @note Default 100 means gain = 1.0. Use 110 for gain = 1.1, etc.
|
||||
*/
|
||||
#ifndef MY_STM32_TEMPERATURE_GAIN
|
||||
#define MY_STM32_TEMPERATURE_GAIN (1.0f)
|
||||
#define MY_STM32_TEMPERATURE_GAIN (100)
|
||||
#endif
|
||||
|
||||
// Printf format string compatibility
|
||||
@@ -113,6 +115,15 @@ void hwWatchdogReset(void);
|
||||
*/
|
||||
void hwReboot(void);
|
||||
|
||||
/**
|
||||
* @brief Read internal temperature sensor via HAL ADC
|
||||
* @return Raw 12-bit ADC value, or -1 on error
|
||||
* @note Works on all STM32 families. On F1, the temperature sensor is on
|
||||
* ADC1 channel 16 and requires TSVREFE bit to be set in ADC_CR2.
|
||||
* On F4/L4/G4 etc., analogRead(ATEMP) works when ATEMP is defined.
|
||||
*/
|
||||
int32_t hwReadInternalTemp(void);
|
||||
|
||||
/**
|
||||
* @brief Initialize random number generator
|
||||
* @note Uses internal temperature sensor as entropy source
|
||||
@@ -181,11 +192,12 @@ int8_t hwCPUTemperature(void);
|
||||
uint16_t hwFreeMem(void);
|
||||
|
||||
/**
|
||||
* @brief Sleep for specified milliseconds
|
||||
* @param ms Milliseconds to sleep (0 = sleep until interrupt)
|
||||
* @brief Sleep for specified milliseconds (timer wake-up only)
|
||||
* @param ms Milliseconds to sleep (must be > 0)
|
||||
* @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)
|
||||
* @note Uses STOP mode with low-power regulator
|
||||
* @note Maximum sleep time: ~65535 seconds on F1, ~18 hours on modern STM32
|
||||
* @note ms=0 returns MY_SLEEP_NOT_POSSIBLE (use interrupt variant for indefinite sleep)
|
||||
*/
|
||||
int8_t hwSleep(uint32_t ms);
|
||||
|
||||
@@ -193,7 +205,7 @@ int8_t hwSleep(uint32_t ms);
|
||||
* @brief Sleep with interrupt wake
|
||||
* @param interrupt Arduino pin number for interrupt wake-up
|
||||
* @param mode Interrupt mode (RISING, FALLING, CHANGE)
|
||||
* @param ms Maximum sleep time in milliseconds (0 = no timeout)
|
||||
* @param ms Maximum sleep time in milliseconds (0 = no timeout, interrupt only)
|
||||
* @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)
|
||||
@@ -206,7 +218,7 @@ int8_t hwSleep(const uint8_t interrupt, const uint8_t mode, uint32_t ms);
|
||||
* @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)
|
||||
* @param ms Maximum sleep time in milliseconds (0 = no timeout, interrupt only)
|
||||
* @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)
|
||||
|
||||
@@ -6,9 +6,11 @@ This directory contains the Hardware Abstraction Layer (HAL) implementation for
|
||||
|
||||
The STM32 HAL enables MySensors to run on a wide range of STM32 microcontrollers, including:
|
||||
|
||||
- **STM32F0** series (Cortex-M0)
|
||||
- **STM32F1** series (Cortex-M3)
|
||||
- **STM32F4** series (Cortex-M4 with FPU)
|
||||
|
||||
Not tested / implemented:
|
||||
- **STM32F0** series (Cortex-M0)
|
||||
- **STM32L0/L4** series (Low-power Cortex-M0+/M4)
|
||||
- **STM32G0/G4** series (Cortex-M0+/M4)
|
||||
- **STM32H7** series (Cortex-M7)
|
||||
@@ -16,6 +18,7 @@ The STM32 HAL enables MySensors to run on a wide range of STM32 microcontrollers
|
||||
## Supported Boards
|
||||
|
||||
Tested on:
|
||||
- **STM32F103C8 Blue Pill** (72 MHz, 64KB Flash, 20KB RAM)
|
||||
- **STM32F401CC Black Pill** (84 MHz, 256KB Flash, 64KB RAM)
|
||||
- **STM32F411CE Black Pill** (100 MHz, 512KB Flash, 128KB RAM)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user