ADC/DMA多通道采集例程 - 基于STM32F411
1. 为什么要用 ADC + DMA?
核心原理:解放 CPU!
正常情况下,CPU 需要死等 ADC 转换完成,再把数据读出到内存。如果 ADC 通道多、采样频率高,CPU 就会被耗死在“等待-读取”的枯燥操作中。
引入 DMA(直接内存访问,硬件搬运工) 后,ADC 每转换完一个通道的数据,会直接触发 DMA 硬件,由 DMA 自动将外设数据搬运到我们指定的内存数组里,整个过程 CPU 完全不需要插手。
2. STM32CubeMX 配置指南 (基于 F411)
假设我们需要连续采集两个模拟传感器的数据(例如接入 PA0 和 PA1)。
第一步:配置 ADC 引脚
- 侧边栏打开
Analog -> ADC1 - 勾选
IN0和IN1,对应的PA0和PA1引脚会自动配置为模拟模式。
第二步:配置 ADC 参数
打开 Parameter Settings 面板:
- Scan Conversion Mode (扫描模式):
Enable(多通道采集必须开启)。 - Continuous Conversion Mode (连续转换):
Enable(开启后 ADC 会自动一直采,不需要 CPU 每次去触发)。 - Number of Conversion (转换数量):改为
2(因为我们用了两个输入通道)。 - 在下方的 Rank 中,将 Rank 1 和 Rank 2 分别绑定为 Channel 0 和 Channel 1(这决定了数据存进数组的先后顺序)。
第三步:配置 DMA
打开 DMA Settings 面板,点击 Add 添加 ADC1 的 DMA 请求:
- Mode (模式):改为
Circular(循环模式,搬运满一轮后自动回到数组开头继续搬,实现自动刷新)。 - Data Width (数据宽度):
- Peripheral (外设) 选择
Half Word(16位,因为 STM32F411 的 ADC 结果是12位)。 - Memory (内存) 也选择
Half Word。
- Peripheral (外设) 选择
- Increment Address (地址自增):
- Peripheral 保留不勾选 (ADC永远只有那一个数据寄存器,地址固定)。
- Memory 必须 勾选 (每次搬运自动放到数组的下一个位置,否则后采的数据会覆盖前面)。
3. 代码实战 (HAL 库)
CubeMX 配置并生成代码后,我们只需要手动添加不超过 10 行代码就能让它跑起来。
1. 定义存储数组
在 main.c 的全局变量定义区域(/* USER CODE BEGIN PV */),预留出接收数据的数组:
1 | |
2. 启动 ADC 与 DMA
在 main() 函数中,各种外设初始化完成后(/* USER CODE BEGIN 2 */),开启自动采集:
1 | |
注:代码执行到此处后,adc_values[0] 和 adc_values[1] 里的值就会被硬件在后台悄悄且源源不断地更新,主循环里的代码直接当成普通变量读取即可,再也不需要去管底层的调用。
3. (进阶) 处理转换回调
如果想在 DMA 每完整搬运完一轮(集齐2个数据)时立即做点什么运算,可以在 main.c 的最底下写回调函数:
1 | |
4. 核心函数剖析
HAL_ADC_Start_DMA(ADC_HandleTypeDef* hadc, uint32_t* pData, uint32_t Length)
hadc:ADC 句柄指针,传入&hadc1即可。pData:目标内存的首地址。底层参数类型设定的是32位指针,为了不报警告,用(uint32_t *)adc_values强转一下。Length:传输的数据长度。DMA 搬运多少次判定为“完成一整轮任务”。我们有 2 个通道,所以填2。此数值通常等同于你的数组大小和开启的通道数。
避坑指南
在多通道 ADC 配合 DMA 时,初学者最容易遇到的情况是读取的数据错位(通道乱套)。如果你发现 adc_values[0] 里的数值似乎是 PA1 的,那一定是因为在 CubeMX 排列 Rank 顺序时排反了,记住:数组索引永远严格对应 Rank 序列配置的先后顺序。
1 | |