# 引言 在物联网和便携设备中,低功耗设计是核心需求。STM32F4 系列提供多种低功耗模式,其中 STOP 模式在保留 SRAM 和寄存器内容的同时,将系统时钟关闭,功耗可降至微安级。然而,许多开发者期望在 STOP 模式下通过 DMA 继续传输数据(如从 ADC 或外部传感器),却常遇到 DMA 静默失败或系统无法唤醒的问题。本文将揭示背后的硬件限制,并给出可落地的配置方案。 # STOP 模式下的时钟与 DMA 行为 ## 硬件限制分析 - STM32F4 的 STOP 模式会关闭除 RTC、IWDG 和部分外设(如 PWR)外的所有时钟,包括 DMA 所依赖的 AHB/APB 总线时钟。 - DMA 控制器本身需要时钟才能工作,因此在 STOP 模式下,DMA 无法自主搬运数据,除非使用支持“DMA 保持”的特定外设(如 LPTIM、RTC 等),但这些外设通常仅支持有限的数据传输。 - 关键陷阱:许多开发者误以为配置 DMA 为循环模式后,STOP 模式下仍能继续工作,但实际上 DMA 会因时钟停止而挂起,导致数据丢失或外设 FIFO 溢出。 ## 解决方案思路 - 方案一:使用 RTC 周期性唤醒 MCU,在短暂唤醒期间执行 DMA 传输,然后重新进入 STOP。 - 方案二:利用支持低功耗 DMA 的外设(如 LPTIM),但 STM32F4 的 LPTIM 不支持 DMA,因此方案一更通用。 - 方案三:若必须连续采集,可考虑使用外部 ADC 或传感器自带 FIFO,MCU 仅定期唤醒读取。 # 配置步骤与代码示例 以下以 STM32F407 为例,演示如何通过 RTC 唤醒实现周期性 DMA 传输(从 ADC1 采集数据到内存)。 ## 1. 初始化 RTC 唤醒 ```c // 启用 PWR 和 RTC 时钟 RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_RTC, ENABLE); // 配置 RTC 时钟源为 LSI(约 32kHz) RCC_RTCCLKConfig(RCC_RTCCLKSource_LSI); RCC_RTCCLKCmd(ENABLE); // 等待 RTC 同步 RTC_WaitForSynchro(); // 设置唤醒周期为 1 秒(LSI 频率可能略有偏差,需校准) RTC_SetWakeUpCounter(32000); // 1秒 RTC_WakeUpCmd(ENABLE); // 使能 RTC 唤醒中断 EXTI_InitTypeDef EXTI_InitStructure; EXTI_InitStructure.EXTI_Line = EXTI_Line22; // RTC 唤醒事件 EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising; EXTI_InitStructure.EXTI_LineCmd = ENABLE; EXTI_Init(&EXTI_InitStructure); NVIC_InitTypeDef NVIC_InitStructure; NVIC_InitStructure.NVIC_IRQChannel = RTC_WKUP_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); ``` ## 2. 配置 ADC 和 DMA ```c // 启用 ADC1 和 DMA2 时钟 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); // ADC 配置(单通道,软件触发) ADC_InitTypeDef ADC_InitStructure; ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b; ADC_InitStructure.ADC_ScanConvMode = DISABLE; ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None; ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; ADC_InitStructure.ADC_NbrOfConversion = 1; ADC_Init(ADC1, &ADC_InitStructure); ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_84Cycles); // DMA 配置(循环模式,内存到内存) DMA_InitTypeDef DMA_InitStructure; DMA_InitStructure.DMA_Channel = DMA_Channel_0; DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR; DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)adc_buffer; DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory; DMA_InitStructure.DMA_BufferSize = 100; DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; DMA_InitStructure.DMA_Priority = DMA_Priority_High; DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable; DMA_Init(DMA2_Stream0, &DMA_InitStructure); DMA_Cmd(DMA2_Stream0, ENABLE); ADC_DMACmd(ADC1, ENABLE); ``` ## 3. 进入 STOP 模式并处理唤醒 ```c // 进入 STOP 模式前,确保 DMA 已停止(避免挂起) DMA_Cmd(DMA2_Stream0, DISABLE); // 进入 STOP 模式 PWR_EnterSTOPMode(PWR_Regulator_LowPower, PWR_STOPEntry_WFI); // 系统唤醒后,重新配置系统时钟(因为 STOP 会关闭 HSI/HSE) SystemInit(); // 重新启用 DMA 并启动 ADC 转换 DMA_Cmd(DMA2_Stream0, ENABLE); ADC_SoftwareStartConv(ADC1); // 等待 DMA 传输完成(或使用中断) while(DMA_GetFlagStatus(DMA2_Stream0, DMA_FLAG_TCIF0) == RESET); // 处理数据... ``` ## 4. RTC 唤醒中断服务函数 ```c void RTC_WKUP_IRQHandler(void) { if (RTC_GetITStatus(RTC_IT_WUT) != RESET) { RTC_ClearITPendingBit(RTC_IT_WUT); // 清除 EXTI 挂起位 EXTI_ClearITPendingBit(EXTI_Line22); // 可在此设置标志,主循环中处理 } } ``` # 注意事项与陷阱规避 - **时钟恢复**:STOP 模式唤醒后,系统时钟默认使用 HSI,必须重新调用 `SystemInit()` 恢复为 HSE/PLL,否则外设时钟频率错误。 - **DMA 挂起**:在进入 STOP 前,务必禁用 DMA 流,否则唤醒后 DMA 状态可能异常,导致传输错误。 - **RTC 校准**:LSI 精度较差(±5%),若需要精确周期,建议使用外部 32.768kHz 晶振或校准 LSI。 - **唤醒时间**:从 STOP 唤醒到时钟稳定通常需要几十微秒,若对时序敏感,需在唤醒后加入延时。 - **低功耗模式选择**:若需保留 DMA 连续传输,可考虑使用 STM32L4 系列,其支持 DMA 在低功耗模式下运行(如 LPTIM 触发),但 STM32F4 不支持,需调整设计。 # 总结 STM32F4 的 STOP 模式因时钟关闭导致 DMA 无法工作,这是硬件限制,无法绕过。通过 RTC 周期性唤醒,在唤醒窗口内执行 DMA 传输,是一种简单有效的方案。开发者需注意时钟恢复、DMA 状态管理等细节,避免数据丢失。对于更高要求的低功耗场景,建议评估支持低功耗 DMA 的 MCU 系列。