Skip to content

睡眠模式

学习目标

完成本模块后,你将能够: - 理解微控制器的各种睡眠模式 - 配置和使用不同的低功耗模式 - 实现唤醒机制 - 在RTOS环境中使用睡眠模式 - 在医疗器械中实现有效的功耗管理

前置知识

  • 微控制器架构基础
  • 中断处理机制
  • RTOS任务调度
  • 时钟系统

内容

概念介绍

睡眠模式是微控制器降低功耗的主要手段。通过关闭不需要的外设和时钟,MCU可以在保持必要功能的同时大幅降低功耗。这对于电池供电的医疗器械尤为重要。

功耗管理的重要性: - 延长电池寿命:便携式医疗设备的关键指标 - 降低发热:提高可靠性和舒适度 - 环保节能:符合绿色设计理念 - 降低成本:减少电池更换频率

STM32睡眠模式

STM32微控制器提供三种主要的低功耗模式:

1. 睡眠模式(Sleep Mode)

特点: - CPU停止,所有外设继续运行 - 功耗降低最少,唤醒最快 - 典型功耗:几mA

进入条件: - 执行WFI(Wait For Interrupt)或WFE(Wait For Event)指令

唤醒方式: - 任何中断(WFI) - 任何事件(WFE)

// 进入睡眠模式
void Enter_SleepMode(void) {
    // 挂起SysTick中断(可选)
    HAL_SuspendTick();

    // 进入睡眠模式
    HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON, PWR_SLEEPENTRY_WFI);

    // 唤醒后恢复SysTick
    HAL_ResumeTick();
}

2. 停止模式(Stop Mode)

特点: - CPU和大部分外设时钟停止 - SRAM和寄存器内容保持 - RTC、独立看门狗、唤醒单元继续运行 - 典型功耗:几十μA到几百μA

进入条件: - 配置PWR寄存器 - 执行WFI或WFE指令

唤醒方式: - EXTI线中断 - RTC闹钟/唤醒 - 独立看门狗复位

// 进入停止模式
void Enter_StopMode(void) {
    // 挂起SysTick
    HAL_SuspendTick();

    // 配置停止模式
    // PWR_LOWPOWERREGULATOR_ON: 低功耗稳压器
    // PWR_SLEEPENTRY_WFI: 使用WFI指令进入
    HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_SLEEPENTRY_WFI);

    // 唤醒后重新配置系统时钟(HSI被使用)
    SystemClock_Config();

    // 恢复SysTick
    HAL_ResumeTick();
}

3. 待机模式(Standby Mode)

特点: - 功耗最低 - 1.8V域断电,SRAM和寄存器内容丢失 - 只有备份寄存器和RTC保持 - 典型功耗:几μA

进入条件: - 配置PWR寄存器 - 执行WFI或WFE指令

唤醒方式: - WKUP引脚上升沿 - RTC闹钟 - 外部复位 - 独立看门狗复位

// 进入待机模式
void Enter_StandbyMode(void) {
    // 使能WKUP引脚
    HAL_PWR_EnableWakeUpPin(PWR_WAKEUP_PIN1);

    // 清除唤醒标志
    __HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);

    // 进入待机模式
    HAL_PWR_EnterSTANDBYMode();

    // 注意:从待机模式唤醒后,MCU会复位,程序从头开始执行
}

// 检查是否从待机模式唤醒
void CheckWakeupSource(void) {
    if (__HAL_PWR_GET_FLAG(PWR_FLAG_SB) != RESET) {
        // 从待机模式唤醒
        __HAL_PWR_CLEAR_FLAG(PWR_FLAG_SB);

        UART_Printf("Wakeup from Standby Mode\r\n");

        // 恢复应用状态(从备份寄存器或外部存储)
        RestoreApplicationState();
    }
}

代码说明: - 待机模式唤醒后MCU复位,需要保存状态到备份寄存器或外部存储 - 备份寄存器在待机模式下保持,可用于保存关键数据

睡眠模式对比

模式 CPU 外设 SRAM 功耗 唤醒时间 唤醒方式
睡眠 停止 运行 保持 ~mA <1μs 任何中断
停止 停止 停止 保持 ~μA 几μs EXTI, RTC
待机 停止 停止 丢失 <10μA 几十μs WKUP, RTC

唤醒机制

GPIO唤醒(EXTI)

// 配置GPIO唤醒
void Config_GPIO_Wakeup(void) {
    GPIO_InitTypeDef GPIO_InitStruct = {0};

    // 使能时钟
    __HAL_RCC_GPIOC_CLK_ENABLE();

    // 配置唤醒引脚
    GPIO_InitStruct.Pin = GPIO_PIN_13;
    GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;  // 下降沿触发
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);

    // 配置NVIC
    HAL_NVIC_SetPriority(EXTI15_10_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(EXTI15_10_IRQn);
}

// EXTI中断处理
void EXTI15_10_IRQHandler(void) {
    HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_13);
}

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {
    if (GPIO_Pin == GPIO_PIN_13) {
        // 唤醒后的处理
        UART_Printf("Wakeup by GPIO\r\n");
    }
}

RTC唤醒

// RTC句柄
RTC_HandleTypeDef hrtc;

// 配置RTC
void Config_RTC(void) {
    // 使能PWR时钟
    __HAL_RCC_PWR_CLK_ENABLE();

    // 允许访问备份域
    HAL_PWR_EnableBkUpAccess();

    // 配置RTC
    hrtc.Instance = RTC;
    hrtc.Init.HourFormat = RTC_HOURFORMAT_24;
    hrtc.Init.AsynchPrediv = 127;
    hrtc.Init.SynchPrediv = 255;
    hrtc.Init.OutPut = RTC_OUTPUT_DISABLE;
    hrtc.Init.OutPutPolarity = RTC_OUTPUT_POLARITY_HIGH;
    hrtc.Init.OutPutType = RTC_OUTPUT_TYPE_OPENDRAIN;

    if (HAL_RTC_Init(&hrtc) != HAL_OK) {
        Error_Handler();
    }
}

// 设置RTC闹钟唤醒
void Set_RTC_Alarm_Wakeup(uint32_t seconds) {
    RTC_AlarmTypeDef sAlarm = {0};

    // 配置闹钟
    sAlarm.AlarmTime.Hours = 0;
    sAlarm.AlarmTime.Minutes = 0;
    sAlarm.AlarmTime.Seconds = seconds;
    sAlarm.AlarmTime.SubSeconds = 0;
    sAlarm.AlarmMask = RTC_ALARMMASK_DATEWEEKDAY | RTC_ALARMMASK_HOURS | 
                       RTC_ALARMMASK_MINUTES;
    sAlarm.AlarmSubSecondMask = RTC_ALARMSUBSECONDMASK_ALL;
    sAlarm.AlarmDateWeekDaySel = RTC_ALARMDATEWEEKDAYSEL_DATE;
    sAlarm.AlarmDateWeekDay = 1;
    sAlarm.Alarm = RTC_ALARM_A;

    if (HAL_RTC_SetAlarm_IT(&hrtc, &sAlarm, RTC_FORMAT_BIN) != HAL_OK) {
        Error_Handler();
    }
}

// RTC闹钟中断处理
void RTC_Alarm_IRQHandler(void) {
    HAL_RTC_AlarmIRQHandler(&hrtc);
}

void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc) {
    // 唤醒后的处理
    UART_Printf("Wakeup by RTC Alarm\r\n");
}

定时唤醒

// 配置RTC唤醒定时器
void Config_RTC_Wakeup_Timer(uint32_t seconds) {
    // 禁用唤醒定时器
    HAL_RTCEx_DeactivateWakeUpTimer(&hrtc);

    // 配置唤醒定时器
    // RTC_WAKEUPCLOCK_RTCCLK_DIV16: 时钟源
    // seconds * 2048: 计数值(32768Hz / 16 = 2048Hz)
    if (HAL_RTCEx_SetWakeUpTimer_IT(&hrtc, seconds * 2048 - 1, 
                                     RTC_WAKEUPCLOCK_RTCCLK_DIV16) != HAL_OK) {
        Error_Handler();
    }
}

// RTC唤醒中断处理
void RTC_WKUP_IRQHandler(void) {
    HAL_RTCEx_WakeUpTimerIRQHandler(&hrtc);
}

void HAL_RTCEx_WakeUpTimerEventCallback(RTC_HandleTypeDef *hrtc) {
    // 定时唤醒处理
    UART_Printf("Wakeup by RTC Timer\r\n");
}

RTOS中的睡眠模式

FreeRTOS Tickless Idle

FreeRTOS提供Tickless Idle模式,在空闲时自动进入低功耗模式。

// FreeRTOSConfig.h 配置
#define configUSE_TICKLESS_IDLE  1

// 实现低功耗回调
void vApplicationSleep(TickType_t xExpectedIdleTime) {
    // 计算睡眠时间(毫秒)
    uint32_t sleepTimeMs = xExpectedIdleTime * portTICK_PERIOD_MS;

    // 配置RTC唤醒定时器
    Config_RTC_Wakeup_Timer(sleepTimeMs / 1000);

    // 进入停止模式
    HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_SLEEPENTRY_WFI);

    // 唤醒后重新配置时钟
    SystemClock_Config();
}

// 低功耗任务示例
void LowPowerTask(void *pvParameters) {
    while(1) {
        // 执行任务
        ProcessSensorData();

        // 进入阻塞状态,允许系统进入低功耗模式
        vTaskDelay(pdMS_TO_TICKS(1000));  // 1秒
    }
}

代码说明: - Tickless Idle在所有任务阻塞时自动进入低功耗模式 - 需要实现vApplicationSleep回调函数 - 唤醒后需要补偿系统时钟

实际应用示例

便携式血压计

// 血压计低功耗状态机
typedef enum {
    STATE_IDLE,           // 空闲状态(待机模式)
    STATE_MEASURING,      // 测量状态(正常运行)
    STATE_DISPLAYING,     // 显示结果(正常运行)
    STATE_TRANSMITTING    // 数据传输(正常运行)
} DeviceState_t;

DeviceState_t deviceState = STATE_IDLE;

void BloodPressureMonitor_Task(void *pvParameters) {
    while(1) {
        switch (deviceState) {
            case STATE_IDLE:
                // 显示"按键开始测量"
                Display_ShowMessage("Press to start");

                // 保存状态到备份寄存器
                SaveStateToBackupRegister(STATE_IDLE);

                // 进入待机模式,等待按键唤醒
                Enter_StandbyMode();
                break;

            case STATE_MEASURING:
                // 执行血压测量
                MeasureBloodPressure();

                // 测量完成,进入显示状态
                deviceState = STATE_DISPLAYING;
                break;

            case STATE_DISPLAYING:
                // 显示测量结果
                Display_ShowResults();

                // 显示30秒后进入空闲
                vTaskDelay(pdMS_TO_TICKS(30000));
                deviceState = STATE_IDLE;
                break;

            case STATE_TRANSMITTING:
                // 通过蓝牙传输数据
                TransmitData();

                // 传输完成,进入空闲
                deviceState = STATE_IDLE;
                break;
        }
    }
}

// 从待机模式唤醒后的初始化
void main(void) {
    HAL_Init();
    SystemClock_Config();

    // 检查唤醒源
    if (__HAL_PWR_GET_FLAG(PWR_FLAG_SB) != RESET) {
        // 从待机模式唤醒
        __HAL_PWR_CLEAR_FLAG(PWR_FLAG_SB);

        // 恢复状态
        deviceState = RestoreStateFromBackupRegister();

        // 按键唤醒,开始测量
        if (deviceState == STATE_IDLE) {
            deviceState = STATE_MEASURING;
        }
    }

    // 初始化外设
    Init_Peripherals();

    // 创建任务
    xTaskCreate(BloodPressureMonitor_Task, "BPM", 512, NULL, 1, NULL);

    // 启动调度器
    vTaskStartScheduler();
}

代码说明: - 空闲时进入待机模式,功耗<10μA - 按键唤醒后开始测量 - 使用备份寄存器保存状态 - 测量和显示时正常运行

最佳实践

睡眠模式使用最佳实践

  • 选择合适的模式:根据功耗要求和唤醒时间选择
  • 外设管理:进入睡眠前关闭不需要的外设
  • 时钟配置:停止模式唤醒后需要重新配置时钟
  • 状态保存:待机模式前保存关键数据到备份寄存器或外部存储
  • 唤醒源配置:确保至少有一个唤醒源
  • 测试验证:实际测量功耗,验证低功耗效果
  • RTOS集成:使用Tickless Idle自动管理低功耗
  • 电池监控:实时监控电池电量,低电量时进入超低功耗模式

常见陷阱

注意事项

  • 忘记配置唤醒源:导致无法唤醒,系统"死机"
  • 外设未关闭:外设继续运行,功耗降低不明显
  • 时钟未恢复:停止模式唤醒后使用HSI,性能下降
  • 调试困难:睡眠模式下调试器断开,难以调试
  • SRAM丢失:待机模式下SRAM内容丢失,未保存状态
  • RTC未配置:定时唤醒失败
  • 中断优先级:唤醒中断优先级过低,可能被抢占
  • 功耗测量不准:未断开调试器、LED等额外负载

实践练习

  1. 基础睡眠:实现睡眠模式和唤醒
  2. 定时唤醒:使用RTC定时唤醒
  3. 功耗测量:测量不同模式下的实际功耗
  4. 状态保存:实现待机模式的状态保存和恢复
  5. RTOS集成:在FreeRTOS中实现Tickless Idle

自测问题

睡眠、停止和待机模式有什么区别?如何选择?

三种模式在功耗、唤醒时间和状态保持上有不同的权衡。

答案

主要区别

特性 睡眠模式 停止模式 待机模式
CPU 停止 停止 停止
外设 运行 停止 停止
SRAM 保持 保持 丢失
功耗 ~mA ~μA <10μA
唤醒时间 <1μs 几μs 几十μs
唤醒后 继续执行 继续执行 复位

选择建议

  1. 睡眠模式
  2. 短时间空闲(<1ms)
  3. 需要快速响应
  4. 外设需要继续运行
  5. 例:等待UART接收

  6. 停止模式

  7. 中等时间空闲(几ms到几秒)
  8. 需要保持SRAM内容
  9. 可以接受几μs的唤醒延迟
  10. 例:传感器定时采样

  11. 待机模式

  12. 长时间空闲(几秒到几小时)
  13. 功耗要求极低
  14. 可以接受复位和状态丢失
  15. 例:便携设备待机

医疗器械应用: - 连续监测设备:停止模式 - 按需测量设备:待机模式 - 实时报警设备:睡眠模式

从停止模式唤醒后为什么需要重新配置时钟?

停止模式会改变系统时钟源。

答案

原因

  1. 时钟切换
  2. 进入停止模式时,HSE和PLL被关闭
  3. 唤醒时使用HSI(内部RC振荡器)
  4. HSI频率较低(通常16MHz)

  5. 性能影响

  6. HSI精度较低(±1%)
  7. 频率低于正常工作频率
  8. 外设时钟也受影响

  9. 需要恢复

  10. 重新启动HSE
  11. 重新配置PLL
  12. 切换系统时钟到PLL

恢复代码

void SystemClock_Config(void) {
    RCC_OscInitTypeDef RCC_OscInitStruct = {0};
    RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

    // 配置HSE
    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
    RCC_OscInitStruct.HSEState = RCC_HSE_ON;
    RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
    RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
    RCC_OscInitStruct.PLL.PLLM = 8;
    RCC_OscInitStruct.PLL.PLLN = 336;
    RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
    HAL_RCC_OscConfig(&RCC_OscInitStruct);

    // 配置系统时钟
    RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_SYSCLK;
    RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
    HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5);
}

注意:睡眠模式不需要重新配置时钟。

如何在待机模式下保存和恢复应用状态?

待机模式会丢失SRAM内容,需要特殊方法保存状态。

答案

保存方法

  1. 备份寄存器
  2. STM32提供20个32位备份寄存器
  3. 在待机模式下保持
  4. 访问需要使能PWR时钟和备份域
// 保存状态
void SaveState(uint32_t state) {
    HAL_PWR_EnableBkUpAccess();
    HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR0, state);
}

// 恢复状态
uint32_t RestoreState(void) {
    HAL_PWR_EnableBkUpAccess();
    return HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR0);
}
  1. 外部EEPROM/Flash
  2. 容量大,可保存更多数据
  3. 需要I2C/SPI通信
  4. 写入速度较慢

  5. RTC备份SRAM(部分型号):

  6. 4KB备份SRAM
  7. 在待机模式下保持
  8. 访问速度快

保存内容建议: - 设备状态(空闲/测量/显示等) - 测量数据(最近的测量结果) - 配置参数 - 时间戳 - CRC校验值(验证数据完整性)

恢复流程

void main(void) {
    HAL_Init();

    if (__HAL_PWR_GET_FLAG(PWR_FLAG_SB)) {
        // 从待机唤醒
        __HAL_PWR_CLEAR_FLAG(PWR_FLAG_SB);

        // 恢复状态
        uint32_t savedState = RestoreState();

        // 验证CRC
        if (ValidateCRC(savedState)) {
            // 恢复应用状态
            RestoreApplicationState(savedState);
        } else {
            // CRC错误,使用默认状态
            UseDefaultState();
        }
    }

    // 正常初始化
    Init_Application();
}

FreeRTOS的Tickless Idle是如何工作的?

Tickless Idle是FreeRTOS的低功耗特性。

答案

工作原理

  1. 检测空闲
  2. 所有任务都处于阻塞状态
  3. 没有就绪任务需要运行

  4. 计算睡眠时间

  5. 找到最近的任务唤醒时间
  6. 计算可以睡眠的tick数

  7. 进入低功耗

  8. 停止SysTick
  9. 配置RTC唤醒定时器
  10. 进入停止模式

  11. 唤醒和补偿

  12. RTC或其他中断唤醒
  13. 计算实际睡眠时间
  14. 补偿系统tick计数

配置

// FreeRTOSConfig.h
#define configUSE_TICKLESS_IDLE  1
#define configEXPECTED_IDLE_TIME_BEFORE_SLEEP  2  // 最小空闲tick数

// 实现低功耗回调
void vApplicationSleep(TickType_t xExpectedIdleTime) {
    // 进入低功耗模式
    // 配置唤醒定时器
    // 唤醒后补偿tick
}

优势: - 自动管理,无需手动控制 - 与任务调度无缝集成 - 显著降低功耗

注意事项: - 需要精确的RTC - 唤醒时间补偿要准确 - 短时间睡眠可能不值得(开销大于收益)

在医疗器械中使用睡眠模式需要注意什么?

医疗器械对可靠性和响应时间有严格要求。

答案

医疗器械低功耗设计要点

  1. 安全优先
  2. 关键监测功能不能因低功耗而失效
  3. 报警功能必须可靠唤醒
  4. 实现看门狗监控

  5. 响应时间

  6. 紧急情况下的唤醒时间要求
  7. 选择合适的睡眠模式
  8. 测试最坏情况下的响应时间

  9. 数据完整性

  10. 进入低功耗前保存关键数据
  11. 使用CRC验证数据
  12. 记录低功耗事件日志

  13. 电池管理

  14. 实时监控电池电量
  15. 低电量时进入超低功耗模式
  16. 提供低电量警告

  17. 用户体验

  18. 唤醒后快速响应
  19. 显示设备状态
  20. 提供电池电量指示

  21. 测试验证

  22. 长期功耗测试
  23. 唤醒可靠性测试
  24. 极端温度下的测试
  25. 符合IEC 60601-1要求

  26. 文档化

  27. 记录功耗预算
  28. 说明低功耗策略
  29. 提供电池寿命估算

相关资源

参考文献

  1. STM32F4xx Reference Manual - STMicroelectronics
  2. AN4365: Using STM32F4 MCU power modes with best dynamic efficiency - STMicroelectronics
  3. FreeRTOS Low Power Support - FreeRTOS.org
  4. IEC 60601-1:2005+AMD1:2012 - Medical electrical equipment
  5. "Embedded Systems Architecture" - Tammy Noergaard

💬 讨论区

欢迎在这里分享您的想法、提出问题或参与讨论。需要 GitHub 账号登录。