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 账号登录。