引言

在工业控制、医疗仪器和测试测量领域,高速数据采集系统是核心模块。传统的轮询或中断方式采集 ADC 数据,CPU 占用率高且实时性差。使用 DMA(直接内存访问)可以实现零 CPU 干预的高速数据采集。

本文将详细介绍基于 DMA 的 ADC 采集系统的设计方法,包括硬件配置、软件实现和性能优化。

系统架构

1.1 系统架构

DMA ADC 数据采集系统架构图

架构说明

  1. 传感器层:输出模拟信号(温度/压力/光电等)
  2. 信号调理:放大、滤波,调理到 0-3.3V 范围
  3. ADC:12/14/16-bit 精度,最高 1MSPS 采样率
  4. DMA 控制器:循环缓冲模式,自动回绕,零 CPU 干预
  5. 内存缓冲区:双缓冲策略,Buffer[0] 和 Buffer[1] 交替使用
  6. DSP 处理:FFT、滤波、特征提取等实时算法

触发机制:定时器提供精确采样率(100Hz - 1MHz)

关键优势:DMA 实现零 CPU 占用的高速数据采集

1.2 关键指标

参数典型值说明
采样率100kSPS - 10MSPS根据应用需求选择
分辨率12/14/16 bitADC 精度
通道数1-16多通道同步采集
缓冲大小1KB - 1MB根据处理延迟确定

DMA 配置详解

2.1 DMA 控制器选择

以 STM32H7 为例:

  • DMA1/DMA2:通用 DMA,支持外设到内存传输
  • BDMA:基本 DMA,用于低功耗外设
// DMA 配置结构体
DMA_HandleTypeDef hdma_adc1;

void MX_DMA_Init(void)
{
    // 启用 DMA 时钟
    __HAL_RCC_DMA1_CLK_ENABLE();
    
    // 配置 DMA 通道
    hdma_adc1.Instance = DMA1_Stream0;
    hdma_adc1.Init.Request = DMA_REQUEST_ADC1;
    hdma_adc1.Init.Direction = DMA_PERIPH_TO_MEMORY;
    hdma_adc1.Init.PeriphInc = DMA_PINC_DISABLE;      // 外设地址不递增
    hdma_adc1.Init.MemInc = DMA_MINC_ENABLE;          // 内存地址递增
    hdma_adc1.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;  // 16 位数据
    hdma_adc1.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
    hdma_adc1.Init.Mode = DMA_CIRCULAR;               // 循环模式
    hdma_adc1.Init.Priority = DMA_PRIORITY_HIGH;
    hdma_adc1.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
    
    HAL_DMA_Init(&hdma_adc1);
    
    // 关联 DMA 到 ADC
    __HAL_LINKDMA(&hadc1, DMA_Handle, hdma_adc1);
}

2.2 双缓冲策略

工作流程

  1. DMA 半传输完成 → 触发 HAL_ADC_ConvHalfCpltCallback
  2. DMA 全传输完成 → 触发 HAL_ADC_ConvCpltCallback
  3. 回调函数 → 设置 buffer_ready 标志
  4. 主循环 → 检测标志位 → 处理数据

循环缓冲(推荐)

  • DMA 自动回绕到缓冲区起始位置
  • 适合连续采集场景
  • 需要双缓冲或半传输中断处理

普通缓冲

  • 传输完成后停止

  • 适合单次采集

  • 需要手动重启 DMA

  • DMA 自动回绕到缓冲区起始位置

  • 适合连续采集场景

  • 需要双缓冲或半传输中断处理

#define ADC_BUFFER_SIZE 1024
uint16_t adc_buffer[ADC_BUFFER_SIZE];

// 启动 DMA 循环采集
HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_buffer, ADC_BUFFER_SIZE);

普通缓冲

  • 传输完成后停止
  • 适合单次采集
  • 需要手动重启 DMA

2.3 定时器触发配置

使用定时器触发 ADC 转换,实现精确采样:

// 配置定时器(100kHz 触发)
TIM_HandleTypeDef htim_trigger;

void MX_TIM2_Init(void)
{
    htim_trigger.Instance = TIM2;
    htim_trigger.Init.Prescaler = 83;           // 84MHz / 84 = 1MHz
    htim_trigger.Init.CounterMode = TIM_COUNTERMODE_UP;
    htim_trigger.Init.Period = 9;               // 1MHz / 10 = 100kHz
    htim_trigger.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    
    // 配置触发输出
    TIM_MasterConfigTypeDef sMasterConfig;
    sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE;
    sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
    HAL_TIMEx_MasterConfigSynchronization(&htim_trigger, &sMasterConfig);
    
    HAL_TIM_Base_Start(&htim_trigger);
}

// ADC 配置为外部触发
hadc1.Init.ExternalTrigConv = ADC_EXTERNALTRIG_T2_TRGO;
hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_RISING;

数据处理与同步

3.1 双缓冲策略

#define BUFFER_SIZE 512
uint16_t buffer[2][BUFFER_SIZE];  // 双缓冲
volatile uint8_t buffer_index = 0;
volatile uint8_t buffer_ready = 0;

// DMA 半传输完成回调
void HAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef* hadc)
{
    buffer_index = 0;
    buffer_ready = 1;
}

// DMA 传输完成回调
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
    buffer_index = 1;
    buffer_ready = 1;
}

// 主循环处理
while (1) {
    if (buffer_ready) {
        ProcessData(buffer[buffer_index], BUFFER_SIZE);
        buffer_ready = 0;
    }
}

3.2 数据对齐处理

DMA 传输时注意数据对齐:

// 确保缓冲区 32 字节对齐(Cortex-M7 缓存行大小)
uint16_t adc_buffer[1024] __attribute__((aligned(32)));

// 或者使用编译器指令
#pragma data_alignment = 32
uint16_t adc_buffer[1024];

3.3 缓存维护

对于 Cortex-M7,DMA 传输后需要维护 D-Cache:

void DMA_InvalidateBuffer(void *buffer, uint32_t size)
{
    SCB_InvalidateDCache_by_Addr(buffer, size);
}

// 在半传输/全传输回调中调用
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
    DMA_InvalidateBuffer(adc_buffer, sizeof(adc_buffer));
    data_ready = 1;
}

性能优化

4.1 提高采样率

  • 使用高速 ADC 内核(如 STM32H7 的 16-bit 5MSPS)
  • 降低 ADC 分辨率换取速度(16bit → 12bit)
  • 使用多 ADC interleaved 模式

4.2 降低 CPU 占用

  • 启用 DMA 中断,仅在缓冲区满时处理
  • 使用 DSP 库进行 SIMD 加速
  • 将数据处理放在 DTCM 中执行

4.3 降低延迟

  • 减小缓冲区大小(但增加中断频率)
  • 使用高优先级 DMA 通道
  • 启用数据缓存(D-Cache)

常见问题

Q1: DMA 传输数据丢失

原因:缓冲区太小或处理太慢。

解决:增大缓冲区,或使用双缓冲 + 中断处理。

Q2: 采样率不稳定

原因:定时器配置错误或时钟源不稳定。

解决:使用外部晶振,检查定时器预分频器。

Q3: ADC 数据跳变

原因:参考电压波动或接地不良。

解决:使用独立模拟电源,优化 PCB 布局。

总结

基于 DMA 的 ADC 采集系统设计要点:

  1. 选择合适 DMA 通道:支持外设到内存,循环模式
  2. 配置定时器触发:实现精确采样间隔
  3. 使用双缓冲策略:避免数据丢失
  4. 注意数据对齐:提升缓存效率
  5. 维护 D-Cache:确保数据一致性

掌握这些技巧,你可以轻松实现 1MSPS 以上的零 CPU 占用数据采集!


本文基于 ST 官方技术文档和实际项目经验整理,结合 2026 年最新技术趋势编写。

参考资料

  • STM32H7 Reference Manual
  • ARM Cortex-M7 Technical Reference Manual
  • STM32CubeH7 HAL 库文档