# 引言:低功耗与数据完整性的博弈 在嵌入式开发中,STOP 模式能大幅降低功耗(典型电流降至 µA 级),但唤醒延迟和通信中断常令人头疼。STM32F4 的 LPTIM 支持在 STOP 模式下运行,可提供微秒级定时中断,配合精心设计的串口接收策略,能完美解决“省电”与“不丢数据”的矛盾。 # 原理剖析:LPTIM 如何在 STOP 下“活”着 ## 1. LPTIM 的时钟源与工作条件 - LPTIM 在 STOP 模式下依赖 **LSI(约 32 kHz)** 或 **LSE(32.768 kHz)** 时钟,这些低速时钟在 STOP 时保持运行。 - 通过配置 `RCC->CSR` 使能 LSI,并设置 `RCC->APB1ENR` 中的 LPTIM1 时钟使能。 - LPTIM 的计数频率 = 时钟源 / 预分频值(1~128),可实现从 30 µs 到数秒的定时范围。 ## 2. 唤醒机制 - LPTIM 可配置为 **比较匹配中断**,当计数值达到预设的 ARR(自动重载值)时,产生事件并唤醒 CPU。 - 关键:在进入 STOP 前,需将 LPTIM 的中断使能(`LPTIM_IER_ARRMIE`),并确保 NVIC 中对应中断优先级高于临界区屏蔽级别。 ## 3. 串口数据保全策略 - 使用 **DMA 接收 + 环形缓冲区**:在 STOP 前,配置 UART 的 DMA 接收模式,数据直接写入内存,不依赖 CPU。 - 唤醒后,CPU 从环形缓冲区读取数据,避免因 STOP 期间 CPU 暂停导致的溢出丢失。 # 配置步骤:从零到实战 ## 步骤 1:时钟与 GPIO 初始化 ```c void SystemClock_Config_STOP(void) { // 使能 LSI RCC->CSR |= RCC_CSR_LSION; while (!(RCC->CSR & RCC_CSR_LSIRDY)); // 配置 LPTIM 时钟(LSI 作为时钟源) RCC->APB1ENR |= RCC_APB1ENR_LPTIM1EN; RCC->DCKCFGR2 |= RCC_DCKCFGR2_LPTIM1SEL_0; // LSI 选择 } ``` ## 步骤 2:LPTIM 配置(定时 100 µs) ```c void LPTIM_Init(uint32_t us) { // 预分频:LSI 32kHz,分频 32 -> 1kHz,即 1ms 计数周期 LPTIM1->CFGR = LPTIM_CFGR_PRESC_2; // 分频 32 // 计算 ARR 值:100us = 0.1ms -> 计数 100 次 uint32_t arr = (us * 32) / 1000; // 粗略计算,实际需校准 LPTIM1->ARR = arr - 1; // 使能比较匹配中断 LPTIM1->IER |= LPTIM_IER_ARRMIE; NVIC_EnableIRQ(LPTIM1_IRQn); NVIC_SetPriority(LPTIM1_IRQn, 2); // 高于临界区屏蔽级别 } ``` ## 步骤 3:串口 DMA 接收配置 ```c #define RX_BUF_SIZE 256 uint8_t rx_buf[RX_BUF_SIZE]; volatile uint16_t rx_index = 0; void UART_DMA_Init(void) { // 使能 DMA1 时钟 RCC->AHB1ENR |= RCC_AHB1ENR_DMA1EN; // 配置 DMA 流(以 USART2_RX 为例) DMA1_Stream5->CR = 0; // 先复位 DMA1_Stream5->PAR = (uint32_t)&USART2->RDR; DMA1_Stream5->M0AR = (uint32_t)rx_buf; DMA1_Stream5->NDTR = RX_BUF_SIZE; DMA1_Stream5->CR = DMA_SxCR_CHSEL_1 | // 通道4(USART2_RX) DMA_SxCR_PL_1 | // 高优先级 DMA_SxCR_MSIZE_0 | // 8位 DMA_SxCR_PSIZE_0 | DMA_SxCR_MINC | // 内存递增 DMA_SxCR_CIRC; // 循环模式 // 使能 USART2 的 DMA 接收 USART2->CR3 |= USART_CR3_DMAR; USART2->CR1 |= USART_CR1_RE | USART_CR1_UE; DMA1_Stream5->CR |= DMA_SxCR_EN; } ``` ## 步骤 4:进入 STOP 模式 ```c void EnterStopMode(void) { // 确保 DMA 已接收数据,但 CPU 暂停 __disable_irq(); // 临界区,防止中断干扰 // 设置 SLEEPDEEP 位 PWR->CR |= PWR_CR_PDDS; // 选择 STOP 模式 SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk; // 执行 WFI 指令 __WFI(); // 唤醒后恢复 __enable_irq(); // 重新配置系统时钟(因为 STOP 会关闭 HSI/HSE) SystemClock_Config(); } ``` ## 步骤 5:LPTIM 中断处理(唤醒后执行) ```c void LPTIM1_IRQHandler(void) { if (LPTIM1->ISR & LPTIM_ISR_ARRM) { LPTIM1->ICR |= LPTIM_ICR_ARRMCF; // 清除标志 // 从环形缓冲区读取数据(示例:简单打印) while (rx_index != (RX_BUF_SIZE - DMA1_Stream5->NDTR)) { uint8_t data = rx_buf[rx_index++]; // 处理数据,如通过另一串口发送或存储 if (rx_index >= RX_BUF_SIZE) rx_index = 0; } } } ``` # 完整代码示例(整合) ```c // main.c 核心部分 int main(void) { HAL_Init(); SystemClock_Config(); // 正常模式时钟 GPIO_Init(); // 配置 LED 等 UART_DMA_Init(); LPTIM_Init(100); // 100 µs 定时 while (1) { // 主循环处理其他任务 // 当需要进入低功耗时: EnterStopMode(); // 唤醒后继续 HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin); HAL_Delay(100); } } ``` # 注意事项与调试陷阱 - **时钟校准**:LSI 精度较差(±5%),若需精确微秒级,建议使用 LSE 或增加校准逻辑。 - **中断优先级**:LPTIM 中断优先级必须高于 `__disable_irq()` 的屏蔽级别(如高于 `BASEPRI`),否则无法唤醒。 - **DMA 缓冲区大小**:确保环形缓冲区足够大,避免在 STOP 期间溢出。 - **唤醒后时钟恢复**:STOP 模式会关闭 HSI/HSE,唤醒后必须重新配置系统时钟,否则外设工作异常。 - **调试技巧**:使用逻辑分析仪观察 UART 波形,确认数据无丢失;在 LPTIM 中断中置 GPIO 翻转,测量实际唤醒时间。 # 总结 通过 LPTIM 与 DMA 的巧妙结合,STM32F4 在 STOP 模式下实现了微秒级定时唤醒,同时保证了串口数据完整性。此方案适用于需要周期性采集且对功耗敏感的 IoT 节点。掌握这一技术,你的嵌入式设计将更上一层楼。