C++在STM32中做多路ADC连续DMA采集,本质是封装HAL调用,核心依赖ADC扫描+连续模式与DMA循环传输;不能用std::vector因需静态对齐内存,HAL_ADC_Start_DMA()仅接受裸指针,且通道顺序、采样时间、缓冲区对齐等硬件配置决定成败。

直接上结论:C++在单片机(如STM32)内部做多路ADC连续DMA循环扫描转换,本质不是“用C++语法特性”,而是用C++封装HAL库调用,核心仍靠硬件配置——ADC扫描+连续模式 + DMA循环传输。C++只负责组织逻辑、封装状态、避免裸指针误用,不改变底层时序和寄存器行为。
为什么不能直接写 std::vector<int> 接ADC数据
嵌入式环境里,std::vector 依赖动态内存分配(malloc),而DMA目标地址必须是静态、连续、已知的物理内存块,且需满足对齐要求(通常4字节对齐)。HAL库的HAL_ADC_Start_DMA()只接受uint32_t*或uint16_t*这类裸指针。
- DMA控制器无法跟踪堆内存碎片,运行中分配失败会静默导致数据写入非法地址
- 多数MCU(尤其F1/F4)没启用
new重载或heap初始化,std::vector::push_back()直接HardFault - 实时性要求下,避免任何不可预测的分配延迟
HAL_ADC_Start_DMA() 的关键参数怎么设
这个函数是整个链路的启动开关,参数错一个,DMA就传不起来或数据错位:
-
&hadc1:必须是已MX_ADC1_Init()完成的句柄,且hadc1.Init.ScanConvMode = ENABLE -
(uint32_t*)&adc_buffer[0]:缓冲区首地址,类型必须匹配hadc1.Init.DataAlign(右对齐用uint16_t*,左对齐用uint32_t*) -
3:缓冲区长度,单位是“转换次数”,不是字节数;若扫描3个通道,填3即可(非3×sizeof(uint16_t)) -
HAL_ADC_MODE_CONTINUOUS:必须选这个,否则DMA传完一轮就停 -
DMA_CIRCULAR:DMA模式必须为循环,否则传满一次后触发中断但不再续传
常见错误:adc_buffer定义成局部变量(栈上),函数返回后地址失效;或定义成static uint16_t adc_buffer[3]但没加__attribute__((aligned(4))),DMA写入时触发总线错误。
立即学习“C++免费学习笔记(深入)”;
通道顺序错乱?检查 SQRx 序列和采样时间
HAL库把通道顺序藏在hadc1.Instance->SQR3等寄存器里,CubeMX里拖拽顺序只是生成了对应配置代码。但容易被忽略的是:每个通道的采样时间必须单独设置,且不能全设成ADC_SAMPLETIME_1CYCLE_5。
- 高阻抗信号源(如电位器、热敏电阻分压)需要更长采样时间(≥13.5周期),否则读数偏低甚至全零
- 不同通道混用采样时间会导致序列错位:ADC按SQR顺序扫,但某通道因采样不足而转换超时,后续通道全部延后
- CubeMX里每个通道的
Sampling Time要手动展开设置,不能依赖“全部相同”按钮
实测现象:PA0/PA1/PA2三通道扫描,若PA1接100kΩ电位器却只设1.5周期采样,adc_buffer[1]长期为0,而adc_buffer[0]和[2]数值正常——这不是DMA问题,是ADC硬件没采够电荷。
DMA传输完成中断里该做什么
别在HAL_ADC_ConvCpltCallback()里做耗时操作。这个回调在DMA传输完“一轮”(即所有扫描通道各转一次)后触发,是CPU唯一安全介入时机:
- 只做三件事:标记新数据就绪(如置
data_ready_flag = 1)、复制缓冲区(memcpy(local_copy, adc_buffer, sizeof(adc_buffer)))、清标志(供主循环检测) - 绝对不要在其中调用
printf、HAL_UART_Transmit或浮点运算——UART发送会阻塞,浮点可能触发未使能的FPU异常 - 如果要用双缓冲避免覆盖,得手动配两个buffer+切换逻辑,HAL库的
HAL_ADC_Start_DMA()不自动支持
最容易被忽略的一点:DMA循环模式下,HAL_ADC_ConvCpltCallback()每轮都触发,但hadc1.State始终是HAL_ADC_STATE_BUSY_REG,不能靠它判断“是否第一次启动”。初始化后首次触发才是真实数据,后续都是循环刷新。


















