这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » 嵌入式开发 » STM32 » 【原创】DMA进阶之链式模式、双缓冲与实战应用----from婉

共1条 1/1 1 跳转至页

【原创】DMA进阶之链式模式、双缓冲与实战应用----from婉

工程师
2026-10-19 18:09:38     打赏

之前写过一篇DMA+ADC的文章(链接就不贴了),评论区好多朋友说基础用法懂了但进阶用法还是懵。今天聊聊DMA的链式模式和双缓冲,这两个东西在实际项目里非常好用。

一、先回顾一下DMA的基本流程

STM32的DMA控制器负责在内存和外设之间搬运数据,整个过程不占用CPU。基本流程就是:

1. 配置DMA通道、方向、数据宽度、地址递增等

2. 配置外设数据地址和内存数据地址

3. 配置传输数量

4. 使能DMA通道

5. 数据开始传输,传输完成后产生中断

这个大家应该都熟,不废话了,直接进进阶内容。

二、DMA循环模式(Circular Mode)

STM32F1的DMA支持循环模式(Circular Mode),配置后传输完成会自动重新加载计数值继续传,不需要CPU干预。这个功能在ADC连续采样、串口不定长接收等场景非常有用。

配置方法(以DMA1 Channel 1为例):

// 配置DMA为循环模式
DMA_InitTypeDef DMA_InitStructure;
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)adc_buffer;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
DMA_InitStructure.DMA_BufferSize = ADC_BUFFER_SIZE;
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_M2M = DMA_M2M_Disable;
DMA_Init(DMA1_Channel1, &DMA_InitStructure);

// 使能DMA通道
DMA_Cmd(DMA1_Channel1, Enable);

这样ADC每转换完一个数据,DMA就自动搬到buffer里,转完最后一个元素后自动回到第一个元素继续搬,永远不停。

三、双缓冲技术(Double Buffering)

链式模式配合双缓冲就是王炸了。思路是这样的:

准备两块buffer:BufferA和BufferB。DMA一轮搬数据到BufferA,搬完产生中断,然后DMA切换到BufferB继续搬。CPU有空的时候处理BufferA里的数据,这样DMA和CPU可以同时工作,不丢数据。

实现方式:

#define DMA_BUFFER_SIZE  256
uint16_t adc_buffer[2][DMA_BUFFER_SIZE];  // 双缓冲
volatile uint8_t buffer_index = 0;        // 当前DMA写入的buffer索引

void DMA1_Channel1_IRQHandler(void)
{
    if(DMA_GetITStatus(DMA1_IT_TC1))
    {
        buffer_index = !buffer_index;  // 切换buffer索引
        DMA_ClearITPendingBit(DMA1_IT_TC1);

        // 这里可以设置一个标志,告诉主循环数据已就绪
        data_ready = buffer_index ^ 1;  // 上一个完成的buffer索引
    }
}

注意:DMA切换buffer需要软件干预,STM32F1的DMA没有硬件自动切换buffer的功能。所以需要在传输完成中断里重新配置DMA的内存地址指向另一个buffer。

四、实战案例:ADC+DMA+串口实现实时数据采集

这是我在项目里实际用的方案,采集4路模拟量通过串口发出去:

// ADC初始化(4通道扫描模式)
void ADC_Init_Config(void)
{
    ADC_InitTypeDef ADC_InitStructure;

    ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
    ADC_InitStructure.ADC_ScanMode = ENABLE;
    ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
    ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
    ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
    ADC_InitStructure.ADC_NbrOfChannel = 4;
    ADC_Init(ADC1, &ADC_InitStructure);

    // 配置4个通道
    ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);
    ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_55Cycles5);
    ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 3, ADC_SampleTime_55Cycles5);
    ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 4, ADC_SampleTime_55Cycles5);

    ADC_DMACmd(ADC1, ENABLE);  // 使能ADC的DMA请求
    ADC_Cmd(ADC1, ENABLE);
    ADC_Cmd(ADC1, Enable);
    ADC_SoftwareStartConvCmd(ADC1, Enable);
}

// DMA初始化
void DMA_ADC_Init(void)
{
    // ... 配置DMA1 Channel1为循环模式,从ADC1->DR搬到adc_buffer[4][N]
}

主循环里只需要检查标志位,拿到数据做处理就行,CPU几乎没开销。

五、几个注意事项

1. DMA传输完成中断的优先级不要设太高,不然会抢CPU太多时间。一般设成中等优先级就行。

2. 循环模式下DMA不会自动停止,如果你需要停止采集,记得先关DMA通道再关ADC,不然可能会有残留数据。

3. 双缓冲的buffer大小要2的幂次方,这样可以用位运算做索引(buffer_index & (size-1)),比取模快。

4. 如果用FreeRTOS,DMA中断里不要做耗时的数据处理,设个标志位让任务去处理就行。

5. STM32H7的DMA比F1强大很多,支持硬件自动双缓冲和链式传输配置,如果项目用H7可以省不少事。



共1条 1/1 1 跳转至页

回复

匿名不能发帖!请先 [ 登陆 注册 ]