STM32 DMA 实战指南:从原理到高效数据搬运
1. 初识DMA:为什么你的STM32需要这个"搬运工"
如果你正在用STM32做项目,大概率遇到过这样的场景:串口要收发大量数据,ADC需要连续采样,或者需要把数据从内存搬到外设。如果这些活都让CPU来干,就像让一个工程师去搬砖,不仅大材小用,还会拖慢整个系统的运行速度。
我这里有个真实的例子:去年我做了一个智能家居控制器,需要同时处理串口通信、ADC采样和SPI Flash读写。最初没用DMA,CPU占用率直接飙到70%以上,系统响应慢得让人抓狂。后来上了DMA,同样的工作量,CPU占用降到20%以下,整个系统流畅得不像同一块芯片。
DMA的全称是Direct Memory Access(直接存储器访问),说白了就是芯片内部的一个专职"搬运工"。它能在不需要CPU参与的情况下,自动完成数据搬运工作。想象一下,CPU是公司里的项目经理,DMA就是专职的行政助理——项目经理只需要说"把这份文件从A部门送到B部门",剩下的具体工作就完全交给助理了。
在STM32中,DMA可以处理多种数据传输方向:
- 从外设到内存(比如ADC采样数据存到数组)
- 从内存到外设(比如把数组数据通过串口发送)
- 内存到内存(部分型号支持,比如大数据块拷贝)
2. DMA工作原理深度解析
2.1 DMA的核心工作机制
DMA的工作原理其实很直观。当外设需要传输数据时,它会向DMA控制器发出请求。DMA控制器接收到请求后,就会接管系统总线,直接在外设和内存之间搬运数据。整个过程CPU完全不用参与,可以继续执行其他任务。
我特别喜欢用快递站的比喻来解释DMA:CPU就像快递站老板,数据包裹就像需要派送的快递。如果没有DMA,每个包裹都需要老板亲自处理——接收、登记、分拣、派送,忙得不可开交。有了DMA之后,老板只需要雇佣几个快递员(DMA通道),告诉它们从哪里取件、送到哪里、送多少件,剩下的工作就完全不用操心了。
2.2 关键寄存器详解
DMA的配置主要通过一组寄存器完成,每个通道都有自己的一套寄存器。最重要的包括:
DMA_CPARx:存放外设地址。比如你要从ADC的数据寄存器取数据,就把ADC_DR的地址写在这里。
DMA_CMARx:存放内存地址。这就是数据要搬运到的目的地,通常是我们定义的数组或缓冲区。
DMA_CNDTRx:设置要传输的数据量。这是个递减计数器,每传输一个数据就自动减1,减到0就表示传输完成。
DMA_CCRx:这个寄存器最复杂,包含了通道的各种配置选项:
- 数据传输方向(内存到外设还是外设到内存)
- 循环模式还是单次模式
- 地址是否自动递增
- 数据宽度(8位、16位还是32位)
- 通道优先级
// 示例:配置DMA通道的基本寄存器设置
DMA1_Channel1->CPAR = (uint32_t)&ADC1->DR; // 外设地址:ADC数据寄存器
DMA1_Channel1->CMAR = (uint32_t)adc_buffer; // 内存地址:自定义数组
DMA1_Channel1->CNDTR = 100; // 传输100个数据
DMA1_Channel1->CCR |= DMA_CCR_EN; // 使能DMA通道
3. DMA vs CPU传输:性能对决
3.1 传统CPU传输的局限性
在没有DMA的情况下,所有数据搬运都得CPU亲力亲为。比如从ADC读取数据,CPU需要:
- 等待ADC转换完成
- 从ADC数据寄存器读取值
- 把这个值写入内存中的数组
- 更新数组索引
- 检查是否采集了足够的数据
这个过程每个数据都要消耗多个CPU周期。如果采样率是10kHz,意味着CPU每100微秒就要被中断一次,根本干不了其他正事。
3.2 DMA传输的优势
同样的ADC采样任务,用DMA来实现:
- CPU初始化DMA,设置好源地址(ADC数据寄存器)、目标地址(内存数组)、数据量
- 启动DMA和ADC
- DMA自动把每个ADC转换结果搬运到数组
- 传输完成后产生中断通知CPU
在这个过程中,CPU只在开始和结束时参与,中间可以完全专注于其他任务。实测下来,使用DMA后系统效率提升非常明显,特别是在需要高速数据处理的场合。
3.3 实际性能对比
为了直观展示差异,我做了个实验:用STM32F103同时处理串口通信和ADC采样,比较使用DMA前后的性能表现。
| 任务类型 | CPU占用率(无DMA) | CPU占用率(有DMA) | 性能提升 |
|---|---|---|---|
| 串口发送1KB数据 | 35% | 5% | 7倍 |
| ADC连续采样1000点 | 45% | 8% | 5.6倍 |
| SPI传输2KB数据 | 50% | 10% | 5倍 |
从数据可以看出,DMA带来的性能提升是实实在在的。特别是在多任务系统中,这种提升会更加明显。
4. DMA配置实战:一步步教你设置
4.1 寄存器级配置详解
虽然现在很多开发者喜欢用HAL库,但了解寄存器级的配置仍然很重要,这能帮你真正理解DMA的工作原理。
首先需要开启DMA时钟,这个很多人会忘记:
RCC->AHBENR |= RCC_AHBENR_DMA1EN; // 使能DMA1时钟
然后配置DMA通道的各个寄存器。以内存到外设的串口发送为例:
void dma_uart_tx_init(void)
{
// 设置外设地址(串口数据寄存器)
DMA1_Channel4->CPAR = (uint32_t)&USART1->DR;
// 设置内存地址(发送缓冲区)
DMA1_Channel4->CMAR = (uint32_t)tx_buffer;
// 设置传输数据量
DMA1_Channel4->CNDTR = TX_BUFFER_SIZE;
// 配置通道参数
DMA1_Channel4->CCR =
DMA_CCR_DIR | // 内存到外设
DMA_CCR_MINC | // 内存地址递增
DMA_CCR_PSIZE_0 | // 外设数据宽度8位
DMA_CCR_MSIZE_0 | // 内存数据宽度8位
DMA_CCR_PL_0; // 中等优先级
// 使能通道
DMA1_Channel4->CCR |= DMA_CCR_EN;
}
4.2 使用标准库配置
STM32标准库让DMA配置变得简单很多。首先定义初始化结构体,然后调用库函数:
void dma_config_using_lib(void)
{
DMA_InitTypeDef dma_init;
// 使能DMA时钟
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
// 配置DMA初始化结构体
dma_init.DMA_PeripheralBaseAddr = (uint32_t)&USART1->DR;
dma_init.DMA_MemoryBaseAddr = (uint32_t)tx_buffer;
dma_init.DMA_DIR = DMA_DIR_PeripheralDST; // 内存到外设
dma_init.DMA_BufferSize = TX_BUFFER_SIZE;
dma_init.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // 外设地址不递增
dma_init.DMA_MemoryInc = DMA_MemoryInc_Enable; // 内存地址递增
dma_init.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
dma_init.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
dma_init.DMA_Mode = DMA_Mode_Normal; // 单次模式
dma_init.DMA_Priority = DMA_Priority_Medium;
dma_init.DMA_M2M = DMA_M2M_Disable; // 非内存到内存模式
// 初始化DMA通道
DMA_Init(DMA1_Channel4, &dma_init);
// 使能DMA通道
DMA_Cmd(DMA1_Channel4, ENABLE);
}
4.3 使用HAL库配置
HAL库进一步简化了配置过程,特别适合快速开发:
void dma_config_using_hal(void)
{
// 声明DMA句柄
DMA_HandleTypeDef hdma_uart_tx;
// 配置DMA句柄
hdma_uart_tx.Instance = DMA1_Channel4;
hdma_uart_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
hdma_uart_tx.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_uart_tx.Init.MemInc = DMA_MINC_ENABLE;
hdma_uart_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
hdma_uart_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
hdma_uart_tx.Init.Mode = DMA_NORMAL;
hdma_uart_tx.Init.Priority = DMA_PRIORITY_MEDIUM;
// 初始化DMA
HAL_DMA_Init(&hdma_uart_tx);
// 关联DMA到串口
__HAL_LINKDMA(&huart1, hdmatx, hdma_uart_tx);
}
5. 串口DMA实战应用
5.1 串口DMA发送配置
串口DMA发送是最常见的应用场景之一。传统方式下,每个字节都需要CPU参与发送,效率极低。使用DMA后,只需要设置好发送缓冲区和数据量,DMA就会自动完成全部发送工作。
这是我实际项目中用的串口DMA发送配置:
void uart_dma_tx_config(void)
{
// 使能DMA和USART时钟
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
// DMA配置
DMA_DeInit(DMA1_Channel4);
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&USART1->DR;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)tx_buffer;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
DMA_InitStructure.DMA_BufferSize = TX_BUFFER_SIZE;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA1_Channel4, &DMA_InitStructure);
// 使能USART的DMA发送请求
USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE);
// 使能DMA通道
DMA_Cmd(DMA1_Channel4, ENABLE);
}
5.2 串口DMA接收配置
DMA接收更加实用,特别是处理不定长数据。我们可以设置DMA为循环模式,这样接收缓冲区就像一个环形缓冲区,新数据会自动覆盖旧数据。
void uart_dma_rx_config(void)
{
// 初始化DMA接收
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&USART1->DR;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)rx_buffer;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
DMA_InitStructure.DMA_BufferSize = RX_BUFFER_SIZE;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; // 循环模式
DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA1_Channel5, &DMA_InitStructure);
// 使能USART DMA接收
USART_DMACmd(USART1, USART_DMAReq_Rx, ENABLE);
// 使能DMA通道
DMA_Cmd(DMA1_Channel5, ENABLE);
}
5.3 实用技巧:处理不定长数据
DMA接收固定长度数据很简单,但实际项目中经常需要处理不定长数据。我这里有个很实用的方法:
// 获取当前DMA接收的数据量
uint16_t get_uart_rx_count(void)
{
// 总缓冲区大小减去剩余未传输的数据量
return RX_BUFFER_SIZE - DMA_GetCurrDataCounter(DMA1_Channel5);
}
// 处理接收到的数据
void process_rx_data(void)
{
uint16_t rx_count = get_uart_rx_count();
if (rx_count != last_rx_count) {
// 有新数据到达
uint16_t new_data_len = (rx_count > last_rx_count) ?
(rx_count - last_rx_count) :
(RX_BUFFER_SIZE - last_rx_count + rx_count);
// 处理新数据
process_new_data(new_data_len);
last_rx_count = rx_count;
}
}
这种方法利用DMA的循环缓冲特性,实现了高效的串口数据接收。我在多个项目中都采用这种方案,稳定性和效率都很不错。
6. ADC与DMA的完美组合
6.1 ADC连续采样配置
ADC连续采样是DMA的另一个典型应用场景。传统方式需要CPU频繁中断来读取采样值,使用DMA后可以实现真正的"设置后不管"。
以下是一个ADC多通道连续采样的配置示例:
void adc_dma_config(void)
{
ADC_InitTypeDef adc_init;
DMA_InitTypeDef dma_init;
// 使能时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
// 配置DMA
dma_init.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;
dma_init.DMA_MemoryBaseAddr = (uint32_t)adc_values;
dma_init.DMA_DIR = DMA_DIR_PeripheralSRC;
dma_init.DMA_BufferSize = ADC_CHANNEL_COUNT;
dma_init.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
dma_init.DMA_MemoryInc = DMA_MemoryInc_Enable;
dma_init.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
dma_init.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
dma_init.DMA_Mode = DMA_Mode_Circular;
dma_init.DMA_Priority = DMA_Priority_High;
dma_init.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA1_Channel1, &dma_init);
// 配置ADC
adc_init.ADC_Mode = ADC_Mode_Independent;
adc_init.ADC_ScanConvMode = ENABLE; // 扫描模式
adc_init.ADC_ContinuousConvMode = ENABLE; // 连续转换
adc_init.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
adc_init.ADC_DataAlign = ADC_DataAlign_Right;
adc_init.ADC_NbrOfChannel = ADC_CHANNEL_COUNT;
ADC_Init(ADC1, &adc_init);
// 配置ADC通道
ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_239Cycles5);
ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_239Cycles5);
// 使能ADC DMA
ADC_DMACmd(ADC1, ENABLE);
// 使能DMA和ADC
DMA_Cmd(DMA1_Channel1, ENABLE);
ADC_Cmd(ADC1, ENABLE);
// 开始转换
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
}
6.2 多通道采样数据处理
配置好多通道采样后,ADC会按顺序转换每个通道,DMA自动将结果存放到指定数组。比如配置了3个通道,那么adc_values[0]存放通道1的值,adc_values[1]存放通道2的值,以此类推。
这种方式的优势非常明显:无论采样速率多高,CPU都不需要参与数据搬运,只需要在需要时直接读取数组中的最新值即可。
7. SPI通信中的DMA应用
7.1 SPI DMA发送接收配置
SPI通信通常速率较高,使用DMA可以显著提升效率。特别是在需要传输大量数据时,比如读写SPI Flash、与显示屏通信等。
以下是一个SPI DMA传输的配置示例:
void spi_dma_config(void)
{
// 使能时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
// 配置DMA发送通道
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&SPI1->DR;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)tx_data;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
DMA_InitStructure.DMA_BufferSize = DATA_SIZE;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA1_Channel3, &DMA_InitStructure);
// 配置DMA接收通道
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&SPI1->DR;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)rx_data;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
DMA_InitStructure.DMA_BufferSize = DATA_SIZE;
DMA_Init(DMA1_Channel2, &DMA_InitStructure);
// 使能SPI DMA请求
SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Tx, ENABLE);
SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Rx, ENABLE);
// 使能DMA通道
DMA_Cmd(DMA1_Channel2, ENABLE);
DMA_Cmd(DMA1_Channel3, ENABLE);
}
7.2 SPI DMA传输注意事项
SPI DMA传输有些细节需要特别注意。首先是传输长度的对齐问题,SPI通常是8位或16位传输,要确保缓冲区地址和数据长度都正确对齐。
另一个常见问题是传输完成判断。由于发送和接收是同时进行的,但速度可能略有差异,需要妥善处理传输完成中断:
void DMA1_Channel2_IRQHandler(void) // SPI接收完成中断
{
if (DMA_GetITStatus(DMA1_IT_TC2)) {
DMA_ClearITPendingBit(DMA1_IT_TC2);
// 处理接收完成
spi_rx_complete = 1;
}
}
void DMA1_Channel3_IRQHandler(void) // SPI发送完成中断
{
if (DMA_GetITStatus(DMA1_IT_TC3)) {
DMA_ClearITPendingBit(DMA1_IT_TC3);
// 处理发送完成
spi_tx_complete = 1;
}
}
在实际使用中,我通常等待发送完成和接收完成都触发后再处理数据,确保数据传输的完整性。
8. DMA使用中的常见问题与解决方案
8.1 内存对齐问题
DMA对内存对齐有严格要求,如果地址没有正确对齐,可能会导致传输失败或数据错误。比如设置16位传输时,内存地址必须是2的倍数;32位传输时,地址必须是4的倍数。
解决方案是使用编译器指令确保缓冲区正确对齐:
// 确保缓冲区按4字节对齐
__align(4) uint8_t dma_buffer[BUFFER_SIZE];
// 或者使用C11标准后的方式
_Alignas(4) uint8_t dma_buffer[BUFFER_SIZE];
8.2 缓冲区溢出问题
在使用循环模式时,如果数据处理速度跟不上数据产生速度,会导致缓冲区溢出,新数据覆盖未处理的数据。
我的解决方案是使用双缓冲区或环形缓冲区:
// 双缓冲区实现
uint8_t dma_buffer[2][BUFFER_SIZE];
uint8_t current_buffer = 0;
void DMA1_Channel1_IRQHandler(void)
{
if (DMA_GetITStatus(DMA1_IT_HT1)) {
// 半传输完成,处理前半部分数据
process_data(dma_buffer[current_buffer], BUFFER_SIZE/2);
} else if (DMA_GetITStatus(DMA1_IT_TC1)) {
// 传输完成,处理后半部分数据
process_data(dma_buffer[current_buffer] + BUFFER_SIZE/2, BUFFER_SIZE/2);
// 切换缓冲区
current_buffer = 1 - current_buffer;
DMA_SetMemoryAddress(DMA1_Channel1, (uint32_t)dma_buffer[current_buffer]);
}
DMA_ClearITPendingBit(DMA1_IT_GL1);
}
8.3 优先级配置问题
多个DMA通道同时工作时,优先级配置不当可能导致高优先级任务被阻塞。我曾经遇到过ADC采样因为SPI传输优先级太高而丢失数据的情况。
解决方案是根据任务实时性要求合理设置优先级:
- 高实时性任务(如ADC采样):最高优先级
- 中等实时性任务(如串口通信):中等优先级
- 低实时性任务(如内存拷贝):低优先级
// 设置通道优先级
DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh; // ADC采样
DMA_Init(DMA1_Channel1, &DMA_InitStructure);
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; // 串口传输
DMA_Init(DMA1_Channel4, &DMA_InitStructure);
DMA_InitStructure.DMA_Priority = DMA_Priority_Low; // 内存拷贝
DMA_Init(DMA1_Channel6, &DMA_InitStructure);
9. 高级技巧与性能优化
9.1 使用内存到内存传输
部分STM32型号支持内存到内存的DMA传输,这在需要大数据块拷贝时非常有用。不过要注意,内存到内存传输通常只能使用DMA2控制器。
void memory_copy_dma(uint32_t *src, uint32_t *dst, uint32_t size)
{
// 检查DMA2是否可用
if (!RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA2, ENABLE)) {
// 不支持内存到内存传输,使用软件拷贝
memcpy(dst, src, size * 4);
return;
}
// 配置内存到内存传输
DMA_InitTypeDef dma_init;
dma_init.DMA_PeripheralBaseAddr = (uint32_t)src;
dma_init.DMA_MemoryBaseAddr = (uint32_t)dst;
dma_init.DMA_DIR = DMA_DIR_PeripheralSRC;
dma_init.DMA_BufferSize = size;
dma_init.DMA_PeripheralInc = DMA_PeripheralInc_Enable;
dma_init.DMA_MemoryInc = DMA_MemoryInc_Enable;
dma_init.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
dma_init.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;
dma_init.DMA_Mode = DMA_Mode_Normal;
dma_init.DMA_Priority = DMA_Priority_High;
dma_init.DMA_M2M = DMA_M2M_Enable; // 启用内存到内存模式
DMA_Init(DMA2_Channel1, &dma_init);
DMA_Cmd(DMA2_Channel1, ENABLE);
// 等待传输完成
while (DMA_GetFlagStatus(DMA2_FLAG_TC1) == RESET);
DMA_ClearFlag(DMA2_FLAG_TC1);
}
9.2 优化传输性能
为了获得最佳的DMA传输性能,有几个关键点需要注意:
数据宽度匹配:尽量让源和目的的数据宽度一致,避免DMA需要做数据宽度转换。
地址对齐:确保地址按照数据宽度正确对齐,32位传输时地址要是4的倍数。
缓冲区大小:合理设置缓冲区大小,太小的缓冲区会增加中断频率,太大的缓冲区会增加内存占用和延迟。
总线带宽:注意DMA和CPU可能共享总线带宽,高优先级的DMA传输可能会暂时阻塞CPU对总线的访问。
我在一个音频处理项目中就遇到过因为DMA带宽占用过高导致CPU性能下降的问题。后来通过优化传输策略,将大块传输拆分成多个小块交错进行,解决了这个问题。
9.3 低功耗应用中的DMA
在低功耗应用中,DMA可以帮大忙。因为CPU可以在DMA工作时进入低功耗模式,等到DMA传输完成时再被唤醒。
void low_power_adc_sample(void)
{
// 配置ADC和DMA
adc_dma_config();
// 使能DMA传输完成中断
DMA_ITConfig(DMA1_Channel1, DMA_IT_TC, ENABLE);
// 启动ADC转换
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
// 进入低功耗模式,等待DMA中断唤醒
__WFI();
}
// DMA中断处理函数
void DMA1_Channel1_IRQHandler(void)
{
if (DMA_GetITStatus(DMA1_IT_TC1)) {
DMA_ClearITPendingBit(DMA1_IT_TC1);
// 处理采样数据
process_adc_data();
}
}
这种模式下,CPU大部分时间处于睡眠状态,只有在需要处理数据时才被唤醒,可以显著降低系统功耗。
我在一个电池供电的传感器项目中使用了这种技术,系统功耗降低了60%以上,电池寿命从3个月延长到了8个月,效果非常显著。
更多推荐


所有评论(0)