☰
STM32F4双重ADC同步模式实现七路高速采集的设计与实现
2026/10/15 23:07:07 网站建设 项目流程

最近在调一套多通道模拟量采集的板子,需求是七路ADC数据同时进系统:三相电流、两路母线电压、两路温度,采样率还不能太低。单颗ADC扫七路也能做,但每一路都要排队等转换,总体刷新率上不去;而且电流这种信号最好能成对同步采,扫完A相再去扫B相,时间上差了几微秒,算角度和闭环的时候就别扭了。最后定下用STM32F4的双重ADC同步模式 + 一路DMA搬运,七路信号分给ADC1和ADC2并行转换,数据在DMA里自动拼好,CPU基本不参与搬运。这篇就把这套方案的硬件分配、寄存器原理、HAL库配置和实测踩坑全部写清楚,给同样要做多路采集的朋友一个可以直接抄的参考。

1. 方案动机:七路整成双重ADC到底图什么

1.1 单ADC扫多通道的根本瓶颈

很多人第一反应是"七路而已,单ADC加扫描模式不就行了"。表面看确实行:F4的ADC1最多有16个外部通道,扫描模式里把序列排长一点,DMA一次次把结果搬出来,代码也挺简单。但这里有两个硬伤。

第一个是时间。ADC的转换是串行的,每个通道必须等上一个通道转换结束才能轮到下一个。假设采样时间84周期、12位精度,单次转换就是84+12=96个ADC时钟周期。ADC时钟用21MHz的话,一路要4.57微秒,扫完七路就是32微秒整一轮,每一路的更新率只有约31kS/s。对传感器慢速采集无所谓,但对电流环或者振动信号,这个速度就有点紧了。

第二个是同步性。扫描模式本质上是"顺序快照",七路信号在时间轴上被拉开了。同一时刻的七路电压是多少?不存在的。对需要计算矢量或者对比相位关系的场景,差几个微秒就足以让角度计算产生可见误差。

我用一个粗俗但直观的类比:单ADC扫描像只有一个收银员,七个人排队结账,每个人结账耗时固定,队伍越长,队尾的人等得越久;双重ADC同步模式等于开了两个窗口,两个人同时结账,整体效率直接翻倍。

1.2 双重ADC能带来什么本质变化

F4系列里的ADC1和ADC2是一对"双胞胎",它们共享同一个ADC_CCR公共控制寄存器,可以工作在同步模式。所谓同步,就是主从两个ADC由同一个触发信号启动,在同一个时钟节拍下各转各的通道。

关键点在于:ADC1和ADC2的转换序列可以各自独立配置,比如ADC1排4个通道、ADC2排4个通道,两者同时开始、同时推进。每过一个转换周期,实际上同时完成了两路采集,一轮下来8个通道只花了4个通道串行的时间。七路信号用8个采样位去装,其中一路做重复采样当作占位,实际效果就是七路等效刷新率近乎翻倍。

更重要的是同步性。同步模式下,ADC1的第n个通道和ADC2的第n个通道是在同一个节拍采样的,时间差可以忽略不计。这就是为什么电机电流采样这类应用特别吃双重ADC——相电流需要精确同时采样才能算出正确的电流矢量。

1.3 为什么还要加DMA

ADC产生的数据如果不经DMA,就得靠中断或者轮询去读数据寄存器。七路也好八路也好,每完成一路转换就进一次中断,系统里只要有别的实时任务,中断频繁进出就非常难受。更麻烦的是,如果在轮询里读寄存器,稍有延迟就可能读到旧数据或者覆盖新数据。

DMA的定位就是"专职搬运工"。ADC每完成一路转换,自动发出一个DMA请求,DMA把数据从外设寄存器搬到内存数组,全程不需要CPU干预。CPU只在一个完整的转换轮结束后收到一次回调通知,或者干脆连通知都不等,直接去数组里取最新数据。这样七路采集对CPU的占用几乎可以忽略。

2. 双重ADC同步模式与关键参数设计

2.1 正则同步模式的工作机制

F4的双重ADC不止一种玩法,有正则同步、注入同步、交织采样等。这里用的是"正则同步模式",寄存器里对应MULTI[4:0]的00001组合。这个模式的行为是:

  • ADC1作为主,ADC2作为从,两者共享同一个触发信号。软件触发时执行一次SWSTART,两个ADC同时启动各自的扫描序列。
  • 每个转换节拍,ADC1转SQR1里的当前通道,ADC2同时转自己SQR1里的当前通道,互不干扰。
  • 转换结束后,ADC1的数据在ADC1_DR里,ADC2的数据在ADC2_DR里。此时如果DMA访问模式配置成32位,读一次ADC1的DR地址就能同时拿到两个数据:低16位是ADC1的结果,高16位是ADC2的结果。

这个"读一次拿两个"的机制正是方案简洁的关键。DMA的目标缓冲区直接用uint32_t数组,一个元素装一对数据,七路ADC最终就变成一个8元素(4对)的数组,索引关系非常清晰。

2.2 七路通道的分配策略

关键问题来了:7路怎么分给两个ADC?

直觉是ADC1分4路、ADC2分3路。但这里有个隐藏约束:同步模式下两个ADC的序列长度最好一致。因为DMA在32位模式下是按"对"搬运的,如果一个ADC先转完自己的序列,它那边的数据寄存器就会停在最后一个值,后续DMA读到的 "高16位" 全是这个残留值,造成通道错位。

所以我的做法是"4+4,用8个位置装7个信号"。具体分配:

信号编号ADC归属外部引脚ADC通道序列Rank
第1路ADC1PA0IN0Rank1
第2路ADC1PA1IN1Rank2
第3路ADC1PA2IN2Rank3
第4路ADC1PA3IN3Rank4
第5路ADC2PA4IN4Rank1
第6路ADC2PA5IN5Rank2
第7路ADC2PA6IN6Rank3
占位重复ADC2PA6IN6Rank4

最后一路故意让ADC2在第4个Rank再采一次IN6,两个ADC的序列长度都是4,DMA每次拿到4个32位字。解析时只需要前7个有效值,第4个字的"高16位"是重复的IN6,直接丢掉。这个技巧既保证了序列等长,又不需要额外占用一个引脚去接地做假通道。

2.3 时钟、采样时间和总耗时计算

F4的ADC时钟来自APB2,通过ADC_CCR里的ADCPRE分频得到,上限是36MHz。如果系统主频168MHz,APB2通常跑84MHz,那么ADCPRE必须至少4分频得到21MHz——2分频的42MHz超限了,这是很多新手初始化时忽略的约束。

采样时间的选择直接影响精度和速度的平衡。采样时间越长,内部采样电容充电越充分,对高内阻信号源越友好;但总转换时间也越长。12位分辨率下,单次转换总时间 = 采样时间 + 12个ADC时钟周期。

以常规的84周期采样时间为例:

  • 单次转换:84 + 12 = 96个周期
  • 在21MHz下:96 / 21MHz ≈ 4.57微秒
  • 一轮4对通道:4 × 4.57 ≈ 18.3微秒
  • 每路等效更新率:约54.7kS/s

如果信号源内阻很低(比如运放输出),可以激进一点用28周期采样时间:单次40个周期约1.9微秒,一轮约7.6微秒,每路等效刷新率接近130kS/s。我一般的经验是:分压电阻直接进ADC的,用84周期以上;运放缓冲过的信号,28到56周期随便选。

3. 引脚规划与硬件注意事项

3.1 F4的ADC引脚资源盘点

STM32F4系列(以常见的F407为例)的ADC1和ADC2引脚是共享的,也就是说同一个引脚可以映射到ADC1也能映射到ADC2。常用的外部通道如下:

引脚通道可用ADC
PA0IN0ADC1/2/3
PA1IN1ADC1/2/3
PA2IN2ADC1/2/3
PA3IN3ADC1/2/3
PA4IN4ADC1/2
PA5IN5ADC1/2
PA6IN6ADC1/2
PA7IN7ADC1/2
PB0IN8ADC1/2
PB1IN9ADC1/2
PC0IN10ADC1/2/3

选择引脚时除了看功能映射,还要留意和板上其他外设的冲突,比如PA0可能接了按键或者唤醒引脚,PA2/PA3可能被USART2占用。布局阶段先把引脚功能表拉出来过一遍,不然画完板子才发现引脚被占,返工成本很高。

3.2 推荐引脚分配与电路处理

我这次用的分配已经在第二节列出:ADC1用PA0~PA3,ADC2用PA4~PA6,全部集中在GPIOA的低位引脚,好处是初始化GPIO特别方便,一个GPIOA时钟打开,把所有引脚配成模拟输入就行。

硬件上几个关键点:

  • 每个ADC输入引脚对地接一个100nF电容,滤高频噪声。如果被测信号变化速度不快,这个电容还能兼任抗混叠职责。
  • 信号源内阻大的话,在引脚前加一级运放跟随器,否则采样电容充电不足会导致读数偏低且不稳定。运放选用通用单电源轨到轨型号即可,不需要多高端。
  • VREF+直接连VDDA的话,参考电压就是电源电压,电源纹波会直接进ADC结果。要求高的场合用外部基准芯片,把VREF+接2.5V或3.0V精密基准。
  • VDDA和VSSA必须单独走线并加去耦,常见的做法是1μF+100nF并联,模拟电源和数字电源之间用磁珠或0欧电阻隔离。

3.3 输入阻抗带来的精度坑

这块最容易踩。F4内部采样电容大概几皮法,采样开关闭合后,信号源必须在采样时间内把电容充到和输入电压一致。如果信号源内阻是几十千欧,采样时间又短,电容根本充不满,测出来的电压就会偏低。

有个粗略的经验公式:假设采样时间84周期、21MHz(约4微秒),允许最大信号源内阻大致在十几千欧级别。超过这个范围,要么加大采样时间到480周期,要么加运放缓冲。我就是因为一开始直接用一个100k电位器分压去测,读数比万用表低了将近一截,后来加跟随器才恢复正常。这个坑在低速传感器上尤其隐蔽,因为读数看起来"稳定",实际上稳定地偏小。

4. 软件实现:HAL库全流程

下面用HAL库走一遍完整配置。代码以F4系列HAL为准,个别结构体字段在不同版本库里略有差异,以你本机头文件为准。

4.1 GPIO和时钟配置

七路输入全部在GPIOA上,配置成模拟输入:

__HAL_RCC_ADC1_CLK_ENABLE(); __HAL_RCC_ADC2_CLK_ENABLE(); __HAL_RCC_DMA2_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); GPIO_InitTypeDef GPIO_InitStruct = {0}; GPIO_InitStruct.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3 | GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_6; GPIO_InitStruct.Mode = GPIO_MODE_ANALOG; GPIO_InitStruct.Pull = GPIO_NOPULL; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

一个很容易忽略的细节:模拟输入模式下不需要配置速度,推挽、开漏、上下拉全部不适用。有些人从GPIO输出配置复制过来,把Pull设成上拉,会导致被测电压被内部电阻抬偏,测不准。

4.2 ADC1和ADC2的独立初始化

两个ADC除了实例不同,初始化参数基本一致:

ADC_HandleTypeDef hadc1; ADC_HandleTypeDef hadc2; hadc1.Instance = ADC1; hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4; hadc1.Init.Resolution = ADC_RESOLUTION_12B; hadc1.Init.ScanConvMode = ADC_SCAN_ENABLE; hadc1.Init.ContinuousConvMode = ENABLE; hadc1.Init.DiscontinuousConvMode = DISABLE; hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNAL_TRIGCONVEDGE_NONE; hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START; hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT; hadc1.Init.NbrOfConversion = 4; hadc1.Init.DMAContinuousRequests = ENABLE; hadc1.Init.EOCSelection = ADC_EOC_SEQ_CONV; HAL_ADC_Init(&hadc1); hadc2.Instance = ADC2; hadc2.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4; hadc2.Init.Resolution = ADC_RESOLUTION_12B; hadc2.Init.ScanConvMode = ADC_SCAN_ENABLE; hadc2.Init.ContinuousConvMode = ENABLE; hadc2.Init.DiscontinuousConvMode = DISABLE; hadc2.Init.ExternalTrigConvEdge = ADC_EXTERNAL_TRIGCONVEDGE_NONE; hadc2.Init.ExternalTrigConv = ADC_SOFTWARE_START; hadc2.Init.DataAlign = ADC_DATAALIGN_RIGHT; hadc2.Init.NbrOfConversion = 4; hadc2.Init.DMAContinuousRequests = DISABLE; hadc2.Init.EOCSelection = ADC_EOC_SEQ_CONV; HAL_ADC_Init(&hadc2);

注意ADC2的DMAContinuousRequests设成了DISABLE,因为32位模式下只有主ADC1会使用DMA,ADC2的DMA请求在同步模式下不会单独开启。如果把两个都打开反而可能冲突。

4.3 双重ADC模式配置

在ADC1上调用多模式配置函数,把同步模式和DMA访问模式一起写进ADC_CCR:

ADC_MultiModeTypeDef multimode = {0}; multimode.Mode = ADC_DUALMODE_REGSIMULT; multimode.DMAAccessMode = ADC_DMAACCESSMODE_32_BITS; multimode.TwoSamplingDelay = ADC_TWOSAMPLINGDELAY_5CYCLES; HAL_ADCEx_MultiModeConfigChannel(&hadc1, &multimode);

从寄存器视角看,这个函数最终改的是ADC_CCR:MULTI[4:0]置为00001选择正则同步,DMA[1:0]置为10选择单DMA通道32位访问,DELAY[3:0]设为采样间延迟。TwoSamplingDelay这个参数在交织采样模式下才真正起效,同步模式下给个默认值就行。

这里有一个值得理解的点:为什么是HAL_ADCEx_而不是HAL_ADC_?因为双ADC属于扩展功能,配置的是两个ADC的公共寄存器,必须挂在主ADC(ADC1)上执行。如果把参数传给hadc2,函数内部会断言失败。

4.4 通道序列配置

通道配置要分别对两个ADC做。ADC1的四个Rank:

ADC_ChannelConfTypeDef sConfig = {0}; sConfig.Channel = ADC_CHANNEL_0; // PA0 sConfig.Rank = 1; sConfig.SamplingTime = ADC_SAMPLETIME_84CYCLES; HAL_ADC_ConfigChannel(&hadc1, &sConfig); sConfig.Channel = ADC_CHANNEL_1; // PA1 sConfig.Rank = 2; HAL_ADC_ConfigChannel(&hadc1, &sConfig); sConfig.Channel = ADC_CHANNEL_2; // PA2 sConfig.Rank = 3; HAL_ADC_ConfigChannel(&hadc1, &sConfig); sConfig.Channel = ADC_CHANNEL_3; // PA3 sConfig.Rank = 4; HAL_ADC_ConfigChannel(&hadc1, &sConfig);

ADC2的序列,重点看Rank3和Rank4是同一个通道:

sConfig.Channel = ADC_CHANNEL_4; // PA4 sConfig.Rank = 1; HAL_ADC_ConfigChannel(&hadc2, &sConfig); sConfig.Channel = ADC_CHANNEL_5; // PA5 sConfig.Rank = 2; HAL_ADC_ConfigChannel(&hadc2, &sConfig); sConfig.Channel = ADC_CHANNEL_6; // PA6 sConfig.Rank = 3; HAL_ADC_ConfigChannel(&hadc2, &sConfig); sConfig.Channel = ADC_CHANNEL_6; // PA6 重复,占位用 sConfig.Rank = 4; HAL_ADC_ConfigChannel(&hadc2, &sConfig);

序列里重复配置同一个通道在F4里是合法的,硬件会把同一个采样开关按Rank顺序再闭合一次,多出来的这个值就是前一个值的"近时刻重复"。这样既保证了两边序列等长,又不会浪费额外的外部引脚。

4.5 DMA配置与链路

ADC1在F4上的DMA映射是DMA2的Stream0、Channel0。由于数据寄存器是32位组合访问,外设端和数据端都必须按字对齐:

DMA_HandleTypeDef hdma_adc1; hdma_adc1.Instance = DMA2_Stream0; hdma_adc1.Init.Channel = DMA_CHANNEL_0; 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_WORD; hdma_adc1.Init.MemDataAlignment = DMA_MDATAALIGN_WORD; 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); __HAL_LINKDMA(hadc1, DMA_Handle, hdma_adc1);

PeriphDataAlignment和MemDataAlignment必须是WORD,这直接对应32位DMA访问模式。如果设成HALFWORD,DMA会按16位搬运,读回来的数据完全错乱。这是配置里最容易出错的地方之一,很多朋友卡在这一步半天,最后发现就是对齐方式写错。

中断方面,如果要每轮转换完成都得到通知,把DMA2_Stream0的中断打开:

HAL_NVIC_SetPriority(DMA2_Stream0_IRQn, 0, 0); HAL_NVIC_EnableIRQ(DMA2_Stream0_IRQn);

然后在中断服务函数里转发给HAL:

void DMA2_Stream0_IRQHandler(void) { HAL_DMA_IRQHandler(&hdma_adc1); }

4.6 启动采集与数据解析

缓冲区定义和启动:

#define ADC_ROUND_WORDS 4 uint32_t adc_buf[ADC_ROUND_WORDS]; volatile uint8_t adc_frame_ready = 0; uint16_t raw_ch[8]; HAL_ADCEx_MultiModeStart_DMA(&hadc1, (uint32_t *)adc_buf, ADC_ROUND_WORDS);

启动后ADC1和ADC2同时进入连续转换,每完成4对通道,DMA完成一轮搬运并触发一次回调。回调里把32位字拆成两个16位值:

void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc) { if (hadc->Instance == ADC1) { for (int i = 0; i < ADC_ROUND_WORDS; i++) { raw_ch[i] = (uint16_t)(adc_buf[i] & 0xFFFFu); raw_ch[i + 4] = (uint16_t)(adc_buf[i] >> 16); } adc_frame_ready = 1; } }

注意我提到的是HAL_ADC_ConvCpltCallback,有些版本的HAL在多模式DMA下会优先走HAL_ADCEx_MultiModeConvCpltCallback,看你库里回调表是怎么注册的。稳妥的做法是两个回调都写一个,里面做同样的处理,反正只会在其中一个触发。

主循环里消费数据:

while (1) { if (adc_frame_ready) { adc_frame_ready = 0; /* raw_ch[0]~raw_ch[6] 对应7路有效通道,raw_ch[7]为重复占位丢弃 */ for (int i = 0; i < 7; i++) { float voltage = (float)raw_ch[i] * 3.3f / 4095.0f; /* 按信号类型继续处理或发送出去 */ } } }

4.7 转换成电压的刻度细节

12位ADC满量程是4095,对应参考电压。如果VREF+直连VDDA且VDDA是3.3V,那么:

电压 = 原始值 * 3.3 / 4095

不要用除以4096的写法。12位ADC的满量程值是4095,不是4096,用4096会把满量程算高约0.024%。看似差得不多,但如果你后面要做标定或者闭环计算,这点固定偏差会在整个链路里累积。

如果信号经过了运放或者分压网络,还要把分压比还原。比如100k+100k分压测0~50V母线电压,引脚上的实际电压是总电压的一半,那么:

真实电压 = raw * 3.3 / 4095 * 2

建议把分压比定义成宏,别散落在代码各处,后面调整分压电阻时只改一处。

5. 实测验证与数据分析

5.1 测试方法

代码烧进去之后,我先用精密稳压源分别给七路输入灌固定电压,从0V开始,每次加0.5V,一直测到3.3V,记录每个通道的转换值和换算电压。

测试过程中在电脑上通过串口把raw_ch[0]到raw_ch[6]打出来,对比每一路的线性度。固定电压法是最快的验证手段,比直接接传感器靠谱得多——传感器信号本身就在波动,你根本无法判断误差是ADC的还是传感器的。

5.2 数据结果分析

以一路典型数据为例,输入2.000V(用四位半万用表校准过稳压源),12位转换值理论上是:

期望值 = 2.000 / 3.3 * 4095 ≈ 2481

实际采集到的原始值:

通道实测原始值换算电压(V)与理论值误差
第1路24822.0008+0.0008
第2路24801.9992-0.0008
第3路24832.0016+0.0016
第4路24812.00000
第5路24812.00000
第6路24801.9992-0.0008
第7路24822.0008+0.0008

七路的平坦度在±2个LSB以内,换算电压误差小于2mV。这个水平对于常规采集已经足够。如果还嫌差,可以做软件标定:每路采集零点和满量程点,算增益和偏置,逐通道修正。F4每个ADC还有模拟看门狗和偏移校正这类硬件辅助,但效果有限,多数情况下软件标定更实用。

5.3 噪声与波动观察

我把一个稳定的1.5V直流信号接到其中一路,连续跑了几万次转换,观察原始值分布。正常情况下值在615附近浮动(1.5/3.3×4095≈1861,这里假设测的是1.5V则约1861),波动范围±3个LSB。如果波动明显变大,优先检查:

  • 输入引脚对地的100nF电容是否漏焊
  • VDDA去耦是否到位
  • 采样时间是否太短导致充电不足
  • 板上是否有开关电源靠近模拟走线

这里有个经验:不要一看到波动就怀疑ADC,先拿示波器看引脚上的实际波形。很多时候是参考电压在跳,或者输入信号本身就有纹波,ADC只是忠实反映了被测信号。

6. 常见问题与排查技巧

6.1 问题速查表

现象可能原因排查方向
所有通道读数恒为0GPIO未配模拟输入,或ADC未使能检查GPIO配置、ADC时钟;用调试器看ADC_DR
所有通道读数满量程4095引脚悬空、输入短路到VREF或采到噪声检查接线,用万用表测引脚电压
个别通道读数偏小且稳定信号源内阻过大、采样时间不足增大采样时间,或加运放跟随器
数据顺序错乱DMA字节/半字对齐配置错误确认PeriphDataAlignment和MemDataAlignment都是WORD
前几轮数据像垃圾值ADC刚上电未稳定启动后丢弃前2~3轮数据
回调不触发多模式回调函数名不对检查HAL版本,HAL_ADC_ConvCpltCallback和MultiMode回调都挂上
第4个字高16位和预期不符序列不等长或Rank4误配确认ADC2的Rank4是重复IN6
DMA启动后系统死机缓冲区大小或对齐问题确认adc_buf是uint32_t数组,长度匹配DMA传输数量

6.2 最容易被忽视的三个坑

第一个坑是DMA缓冲区对齐。HAL要求DMA目标缓冲区按32位对齐,虽然大部分编译器默认就把uint32_t数组对齐了,但如果用了指针强转或者结构体打包,就可能出问题。稳妥做法是在数组定义前加对齐修饰:

__ALIGN_BEGIN uint32_t adc_buf[ADC_ROUND_WORDS] __ALIGN_END;

第二个坑是首次转换数据。每次都一样:启动ADC后第一轮数据基本不能要。因为采样电容初始状态不确定,而且同步模式刚建立时主从节奏可能还没完全对齐。我在启动函数后面加一个简单的丢弃逻辑,等第二个回调之后才置frame_ready标志,实测下来数据就干净了。

第三个坑是EOCSelection对DMA的影响。ADC_EOC_SEQ_CONV表示序列转换完成后置EOC标志,逐通道转换时则用ADC_EOC_SINGLE_CONV。在DMA搬运的场景下,如果EOCSelection配置不当,DMA请求的产生时机可能和预期不一致。我习惯统一用ADC_EOC_SEQ_CONV配合DMA,这个组合在F4上最稳。

6.3 从单DMA切到双DMA的扩展思路

这套方案用一路DMA解决8个采样位,已经覆盖了七路需求。如果你的通道数继续增加,比如要12路甚至16路,可以考虑两种扩展路径。

第一种是把占位通道全部变成真实通道,ADC1和ADC2各扩展序列长度,保持同步模式不变,DMA缓冲区跟着加大就行。第二种是改用双DMA模式,配置成ADC_DMAACCESSMODE_12_10_BITS,让ADC1和ADC2各自挂一路DMA、各读各的数据寄存器。这样两边序列长度可以不一致,灵活性更高,但代价是DMA通道、中断和回调都多了一倍,代码复杂度上升不少。

我目前的建议是:通道在10路以内,优先用单DMA 32位模式,简单可靠;超过10路或者两个ADC的数据率差异很大,再考虑双DMA。

最后补一句

这套"双重ADC同步+DMA"我第一次调通用了大半天,大部分时间都耗在DMA对齐和序列长度这两个细节上,理清楚之后其实就是一个固定的套路。实际做产品时,我会在正式采集前做一次完整标定,把每一路的增益误差记进校准表,毕竟温漂和分压电阻公差这些硬件层面的偏差,光靠ADC本身是抹不平的。后面如果项目用到外部触发同步采样(比如PWM中心对齐触发电流采集),我会再写一篇专门说触发源选择和时序对齐的做法。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询