
先说结论如果你刚从F103或者F407的老工程迁移到STM32H743上做ADC多通道采样大概率会在CubeMX配置阶段就踩坑。这芯片的ADC模块和F4完全不是一个思路多了一个“快速通道/慢速通道”的概念DMA请求逻辑也更拧巴再加上网上教程大多停留在“能跑通”的层面很少有人讲清楚采样周期、扫描顺序、DMA缓冲区排列之间的连带关系。我最近在调一块8路模拟量采集板把踩过的坑、验证过的配置和最终能稳定运行的代码整理成这篇给准备上手H743的兄弟一个参考。这套内容适合谁正在用STM32H743做数据采集、工业变送器、FOC电流采样、电池监测反正只要是“多通道ADCDMA搬运数据”这种标准玩法的人都可以直接照抄配置思路。我会把CubeMX里那些默认参数是怎么回事、为什么这么配、实际跑起来会遇到什么问题都讲透。1. 整体方案与设计思路为什么H743 多通道 DMA是绕不开的组合1.1 H743的ADC资源比F4“豪横”在哪里先看一眼H743的ADC硬件资源这决定了我们后面所有配置的上限。H743内部有3个独立的12位逐次逼近型ADC编号ADC1/2/3最典型的特点是支持可编程过采样可以输出最高16位转换结果。也就是说你用H743做采集时如果对速度要求不高、对精度和分辨率有要求可以不外挂高精度ADC芯片靠内部过采样把有效分辨率抬上去这在天平、传感器变送器这类应用里很吃亏——不对是很占便宜。另一个是主频带来的优势。H743的ADC时钟最大可以跑到50MHz左右12位分辨率下加上最短采样周期2.5个时钟周期单个ADC的理论转换速率能到3Msps以上。F4系列最高2.4MspsF1更是只有1Msps级别所以在采集高速信号时H743具备直接采中频信号的能力不需要上外部高速ADC。H743的ADC还有一个F4没有的重要特性通道分为快速通道和慢速通道。快速通道的输入等效电容和采样开关阻抗更小可以用更短的采样时间慢速通道则需要留出更长的采样时间否则采样值会偏、会跳。这个细节稍后重点讲很多人在配置阶段不看通道类型直接把所有通道设成最低采样周期结果数据一团糟。此外H743内部有一个可选的参考电压缓冲器VREFBUF可以产生2.5V或2.9V的稳定内部参考省掉外部基准芯片。ADC的VREF引脚上甚至可以悬空直接使能内部缓冲就行这个在精密采集场景里非常实用。1.2 多通道采样为什么必须用DMA这里先说一个基础问题多通道扫描模式采集DMA到底帮我们干了什么当你在ADC1上开启Scan Mode并配置了8个转换通道之后ADC硬件会按你设定的顺序一个个转换。不开启DMA时你只能在每次转换完成后在中断里读取ADCx-DR寄存器。8个通道就是8次中断CPU全程被打断而且每次中断响应到读取寄存器的延迟都不确定这会导致每次采样点之间的时间间隔不均匀直接影响采样波形的质量尤其是在分析相位差、计算有功功率这类场景下中断延迟就是灾难。使用DMA后ADC每次转换完成会自动发出DMA请求DMA控制器把ADCx-DR里的数据搬到内存缓冲区整个搬运过程不需要CPU干预。CPU只在DMA搬运完一整轮也就是所有通道都采完一遍或者搬运到一半时收到一次中断处理一次数据即可。对于多通道周期采样来说这是效率最高的方案。DMA的选择也很简单H743的ADC1有独立的DMA请求信号可以接到DMA1或者DMA2的任意空闲数据流上。我习惯用DMA1 Stream0没有特殊原因只是工程里统一约定。如果你用了DMA2做串口收发ADC就接DMA1避免两条数据流互相抢占带宽。不过DMA也不是万能的。真正坑人的不是DMA本身而是多通道扫描模式下DMA缓冲区里的数据排列顺序和连续转换时Buffer数据反复覆盖这两个问题下面我会专门用两个章节来展开。2. CubeMX里的关键配置项每个参数都用在哪里2.1 时钟树ADC时钟的分频逻辑与上限H743的ADC时钟源和F4完全不同F4的ADC挂载在APB2总线上时钟上限36MHzH743的ADC时钟独立了可以由PLL2P、PLL3P或者系统时钟AHB分频得到最高支持到50MHz左右。在CubeMX的Clock Configuration页面里默认工程可能给ADC1/2/3分配了50MHz时钟这个值能用但我建议根据实际采样率需求来确定不用一味追高。为什么要专门说ADC时钟因为采样时间和转换时间都是按ADC时钟周期计算的。假设你采样周期配成16.5个周期12位分辨率固定转换周期12.5个周期那么一次完整转换需要29个ADC时钟周期。ADC时钟50MHz时这个转换时间约0.58微秒3个ADC开起来每秒能采一大片数据。但如果工程里系统时钟只有64MHzADC时钟被分得更低同样的配置下转换时间会成倍拉长。实际项目里如果采样率要求不高把ADC时钟调到25MHz左右更省功耗、更稳。配置ADC时钟的方法在CubeMX的Clock Configuration里找到ADC12和ADC3的时钟源选项可选PLL2P、PLL3P、per_ck等。一般选PLL2P然后直接输入50MHz。如果你想偷懒也可以直接在CubeMX里双击“ADC12”时钟线改成AHB分频或者PLL分频界面会告诉你当前值是否超出上限。2.2 ADC参数面板逐项解读Scan、Continuous、DMA请求的关系CubeMX里打开ADC1参数面板一堆选项很多人看得眼晕。实际上最重要的就那几项。Resolution分辨率。通常选12位如果对速度有极致要求可以选8位或6位转换时间会相应缩短。注意14位和16位是通过过采样实现的不是硬件原生后面我会单独说过采样配置。Scan Conversion Mode扫描模式。多通道采集必须开启。开启后ADC会按Rank顺序依次转换所有通道每转完一轮产生一次序列转换结束事件。注意扫描模式开启后中断标志位的位置会变EOC是每转一个通道都置位而SEQ序列结束标志才是整轮结束用DMA时不用关心中断标志DMA传输完成就代表一轮结束。Continuous Conversion Mode连续转换模式。如果只采集一轮选Disable配合外部触发或软件触发如果希望ADC自己不停地从头到尾循环转换就选Enable。配合DMA Circular模式可以实现“后台无限采集”CPU完全不参与。这个模式的缺点是功耗高、数据一直更新个人建议优先用ContinuousCircular然后在DMA回调里做数据处理这是最省事的结构。Discontinuous Conversion Mode不连续模式。这个模式容易让人迷惑。通俗讲它会把Rank序列切成一小段一小段靠外部触发信号一段段推进。常规采集项目用不到如果选了反而会导致DMA始终收不齐所有通道的数据建议保持Disable。DMA Continuous Requests。这个选项决定ADC每转换完一个通道就发一次DMA请求还是只在转换完一个序列后发一次请求。如果开了扫描模式DMA建议选Enable这样数据能连续搬运到内存配合DMA Circular模式形成流水线。如果选Disable一轮转换只发一次DMA请求数据量小、传输不连续实际使用中我基本不会这么配。Number Of Conversion就是总通道数比如8个通道就填8。下面的Rank列表里每个Rank对应一个通道顺序就是DMA缓冲区里数据的排列顺序这个顺序后面要拿来跟硬件原理图对一遍非常重要。2.3 采样周期的选择快速通道与慢速通道采样周期是ADC参数里最容易被忽略、但影响实际数据质量最大的选项。H743的ADC通道分两组快速通道输入充电时间需求短采样周期最低可以配到2.5个ADC时钟周期。慢速通道输入电容较大充电时间需求长采样周期最低8.5个ADC时钟周期。更准确地说H743的ADC采样时间配置项一般是2.5 / 8.5 / 16.5 / 32.5 / 64.5 / 387.5 / 810.5单位是ADC时钟周期。如果你选择了2.5而它实际是一个慢速通道那么采样电容可能还没充满采到的电压会比实际值偏低特别是一些高阻抗信号源会偏低得离谱。怎么判断通道是快速还是慢速CubeMX里鼠标停在通道旁边或查看数据手册更直接的方法是看引脚号。一般来说直接连在ADC输入引脚上的大部分是快速通道部分多路复用脚位会是慢速通道。稳妥的做法除非你非常确定信号源阻抗极低否则采样周期至少选16.5以上。温度传感器、分压电阻网络这类常见的模拟信号建议32.5起步。采样时间的计算公式如下总转换时间 采样周期 固定转换周期与分辨率相关其中12位分辨率时固定转换周期为12.5个ADC时钟周期10位是10.5个8位是8.5个。举个例子ADC时钟50MHz12位分辨率采样周期16.5个周期。那么单个通道转换时间 (16.5 12.5) / 50MHz 0.58微秒8个通道一轮就是4.64微秒最大扫描频率约215kHz。如果你打算采集50Hz交流信号做FFT这个速率绰绰有余。如果你要采集100kHz以上的信号就得把采样周期压到2.5或8.5并且用快速通道。3. 实操过程从CubeMX到代码的完整落地3.1 第一步CubeMX工程配置实例我用ADC1做8路采集依次是PA0、PA1、PA2、PA3、PA4、PA5、PB0、PB1。这几个引脚在H743上全部对应ADC1的通道。打开CubeMX先把这8个引脚在Pinout视图中设为ADC1_INP0到ADC1_INP7。然后进入Analog → ADC1按下表配置参数项配置值说明Mode: INP0~INP7Enabled8个通道全部打开Resolution12 bits默认即可Scan Conversion ModeEnabled必须开启Continuous Conversion ModeEnabled配合DMA循环Discontinuous Conversion ModeDisabled常规项目用不到DMA Continuous RequestsEnabled连续搬运Number of Conversion8通道数量Rank 1~8对应INP0~INP7顺序按硬件定义Sampling Time32.5 Cycles保守起见DMA Settings标签页Add DMAMode Circular关键DMA配置里要特别注意三个点Mode设为Circular这样DMA搬运完一轮后自动重新从头开始配合ADC的Continuous模式形成无限循环。Data Width外设和内存都设为Half Word16位因为ADC的DR寄存器是16位宽度ADC分辨率最大也只用到16位。Memory Increment设为Enable否则每轮数据都会写到缓冲区同一个位置后一个数据覆盖前一个数据。3.2 第二步DMA缓冲区的排列顺序问题这是我觉得整个CubeMX配置里最值得单独拎出来讲的地方。你开启8通道扫描后DMA缓冲区里会怎么排列答案按Rank的顺序排列不是按引脚号排列。Rank 1对应INP0Rank 2对应INP1依此类推。也就是说一次完整扫描转换结束后DMA缓冲区从首地址开始依次存放CH0、CH1、CH2……CH7的数据。这个顺序是由CubeMX生成的ADC_ChannelConfTypeDef中Rank字段顺序决定的你在代码里改动Rank顺序DMA数据顺序也跟着变。所以代码里取通道数据的逻辑非常简单#define ADC_CH_NUM 8 uint16_t adc_buf[ADC_CH_NUM]; // 假设在DMA传输完成回调中取数据 uint16_t ch0_val adc_buf[0]; uint16_t ch1_val adc_buf[1]; // ... 依此类推如果你在CubeMX里调整了通道顺序比如把INP5放到Rank 1那么adc_buf[0]就是INP5的数据而不是INP0。因此强烈建议配置Rank顺序时直接让硬件原理图上的物理通路与DMA buffer索引一一对应并且把每个通道的物理意义写在代码注释里防止后续接线的人换了一路信号数据全部错位排查半天还找不到原因。3.3 第三步代码层的正确启动顺序CubeMX生成工程后默认已经生成了MX_ADC1_Init()和MX_DMA_Init()函数但这些函数只是初始化寄存器不会启动转换。你需要在主程序里按顺序做三件事校准、启动DMA转换、开始处理数据。H743的ADC内部有个校准机制上电后如果不校准就开始转换结果会有几十个LSB的偏差。校准函数需要在整个ADC处于非转换状态时调用一次HAL_ADC_ExCalibration_Start(hadc1);然后启动DMA传输HAL_ADC_Start_DMA(hadc1, (uint32_t *)adc_buf, ADC_CH_NUM);adc_buf是缓冲区长度是8个uint16_t。ADC_CH_NUM是8意思是每次采样多少个点由DMA搬运到缓冲区。如果CubeMX里把Continuous Conversion Mode设成了Enable并且DMA是Circular模式那么HAL_ADC_Start_DMA调用一次之后ADC就会一直在后台转换DMA会不断把数据刷新到adc_buf。取电压值的换算公式#define VREF_VALUE 3.3f float voltage (float)adc_buf[0] * VREF_VALUE / 4095.0f;如果使用了内部VREFBUF产生了2.5V参考那这里的VREF_VALUE就换乘2.5。在DMA传输完成中断里系统每次一轮数据搬完会调用一次HAL_ADC_ConvCpltCallbackvolatile uint8_t adc_data_ready 0; void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc) { if (hadc-Instance ADC1) { for (int i 0; i ADC_CH_NUM; i) processed_buff[i] adc_buf[i]; // 拷贝一份防止下次搬运覆盖 adc_data_ready 1; } }很多人的误区是直接在DMA传输完成中断里做换算或者FFT、滤波。其实这样极不推荐因为中断里占用时间越长对实时采集的影响越大。正确做法是在回调里做轻量级拷贝把数据复制到处理缓冲区然后置一个标志位在主循环里根据标志位去处理数据。实测下来这个模式在H743上可以很稳定地跑。4. 常见问题与排查技巧实录4.1 数据全是0xFFF或者0x000我见过最多的情况是第一版工程跑起来串口打印adc_buf八个通道全是0xFFF或者全为0x000。全0xFFF说明每个通道的输入引脚实际悬空或者被拉到高电平ADC本身没工作因为H743的ADC引脚没有内部下拉悬空时读数接近满量程。全0则是引脚被拉到地或者外部驱动能力不够。但还有一种情况是没有调用校准函数并且ADC时钟配置错误导致转换结果异常。先确认CubeMX里ADC时钟是不是有值之前有个兄弟把ADC1/2时钟源配成了Disabled结果所有ADC寄存器读出来全是复位值。检查方法很简单在MX_ADC1_Init里hadc1.Init.ClockPrescaler如果被设成了0对应禁止就说明时钟配置没生效。4.2 DMA收到的数据顺序和物理通道对不上这个问题几乎每个做多通道采集的人都会经历一次。现象是明明配置的是PA0、PA1、PA2……结果串口打出来的数据第0个通道显示的是PA4的电压。造成这个问题的两个原因一是CubeMX生成的Rank顺序和你预期不一致。比如你重新拖拽了引脚映射CubeMX会自动重新排列Rank列表如果你在界面看不清直接去代码里看MX_ADC1_Init中的通道配置顺序。二是你在代码里手动改了ADC_ChannelConfTypeDef的Channel字段却没有同步改Rank。记住sConfig_ch1.Channel ADC_CHANNEL_5; sConfig_ch1.Rank 1;上面的配置虽然把Channel设置成了CH5但放在Rank 1的位置意味着DMA第一个数据就是CH5。Rank序号决定DMA排列顺序Channel字段决定读哪个引脚。很多人只改Channel不改Rank最后通道对不上这不是H743的锅是配置逻辑理解错了。4.3 DMA数据一直在刷新我怎么判断读到的数据是“新”的还是“旧”的这个在使用Continuous Circular模式时非常典型。特别是主循环处理速度慢、采样速率又高时DMA搬运可能在你还没处理完上一轮数据时就已经覆盖了缓冲区。解决思路有三种用DMA半传输中断和传输完成中断把缓冲区拆成前后两半。前半轮的数据只在半传输中断里处理后半轮的数据在传输完成中断里处理处理时间只要小于半轮转换时间就不会丢。在DMA回调里做内存拷贝拷贝完立刻置标志。主循环检测标志后处理拷贝缓冲区。如果采样速率远超CPU处理能力说明方案设计有问题应该在DMA之前做硬件抽取或降采样别让CPU硬扛。至于判断“新旧”工程上通常不追求精确到哪一次的样本而是用HAL_ADC_ConvCpltCallback的频率作为数据刷新率依据。真正高精度的信号采集系统会使用双缓冲模式这里先不展开后文会简单提一下。4.4 采样数值波动大、上下几百个LSB的跳动排除外部信号本身噪底大的情况最常见的原因是参考电压不稳和采样时间不够。H743内部虽然有过采样功能但它不能消除参考电压的噪声。如果板上的3.3V是从DC-DC直接出来、纹波几百毫伏ADC结果跟着一起跳加再多的软件滤波都是治标不治本。我的做法是模拟供电和数字供电之间加磁珠330欧级别的隔离VREF引脚接上10uF100nF的滤波电容并且尽可能靠近MCU引脚。有条件的话直接启用H743内部VREFBUF用内部基准。配置起来很简单在CubeMX里把VREFBUF设为Enabled模式选2.5V还是2.9VADC的VREF引脚就不需要外部提供参考了。采样时间不足的判断方法把采样周期从32.5改成387.5如果数据跳动幅度明显变小说明之前确实是采样时间不够应该优先提高信号源输出阻抗或增加前级运放缓冲而不是依赖长采样周期。4.5 连续转换模式下怎么把某一路从扫描序列中临时剔除有次需求是8个通道里某一路传感器偶尔失效不想每次都采它但又不想重新生成工程。这时候可以通过HAL_ADC_ConfigChannel动态改Rank列表但要注意修改通道前必须先调用HAL_ADC_Stop_DMA停止转换否则参数写入时可能和正在进行的转换冲突导致偶发死机或数据混乱。示例代码HAL_ADC_Stop_DMA(hadc1); sConfig.Channel ADC_CHANNEL_5; sConfig.Rank 1; sConfig.SamplingTime ADC_SAMPLETIME_32CYCLES_5; HAL_ADC_ConfigChannel(hadc1, sConfig); HAL_ADC_Start_DMA(hadc1, (uint32_t *)adc_buf, ADC_CH_NUM);改完之后新配置会在下一轮扫描时生效不用复位。5. 进阶玩法与实测心得5.1 双ADC交替采样提升等效采样率如果单ADC在12位分辨率下满足不了采样率H743可以把ADC1和ADC2配成双模式采样同一组信号。CubeMX里在ADC1的Mode中可以直接选择Dual regular simultaneous mode配置好后两块ADC同时转换同一路信号DMA请求由主ADC统一发出缓冲区里会交替存放ADC1和ADC2的结果。实际采样率可以翻倍代价是占用两个ADC不能同时采独立的多通道。这个玩法在FOC电流环采样里用得比较多H743的3个ADC恰好可以分配成两路电流采样加一路母线电压采样延时可以低到1微秒以内很适合跑电机控制。5.2 过采样滤波的妙用H743的ADC自带硬件过采样CubeMX里Oversampling开启后可以把14位、16位档位打开。硬件过采样本质上是对转换结果做移位平均等效于低通滤波能明显降低随机噪声而且不占用CPU。不过要理解过采样的代价过采样率每增加4倍有效分辨率才提升1位而转换速率降低为原来的四分之一。比如16位过采样需要64倍采样那么你实际等效12位采样率直接被除以64。所以过采样只适合低速高精度场景比如电池电压监测、温度采集不适合电流波形分析。我的建议如果噪声主要来自数字开关导致的毛刺更推荐在PCB布局上做文章而非完全依赖软件过采样。软件滤波永远只能补救不能从根源解决。5.3 一个让我反复排查了两天的低级坑DMA中断优先级还有一个非常隐蔽的坑在H743上容易出现DMA的中断优先级设置不当导致回调函数从不执行或者执行频率异常。H743的中断优先级分组默认是抢占优先级子优先级。如果把DMA流的抢占优先级设得比某个高频定时器中断低而定时器中断里又有一个长时间任务DMA的中断就会被反复延迟。当时我遇到的现象是adc_buf数据是正常的但HAL_ADC_ConvCpltCallback里面的计数变量增长频率不对仔细查才发现DMA中断被串口中断阻塞了很久。解决办法在CubeMX的NVIC设置里把DMA1_Stream0_IRQn的抢占优先级调成最低比如0最高优先级或者至少高于长时间运行的中断。ADC数据搬运是实时性要求很高的操作给它最高的中断优先级不丢人。5.4 数据校验的小技巧用正弦波信号驱动输入通道自检说一个我自认为很实用的调试思路在多通道ADC工程跑通之后别急着接真实传感器先拿信号发生器给每个输入通道灌一个1kHz、1Vpp的正弦波然后用串口上位机或者Python脚本把adc_buf数据打出来画波形。这一步能非常直观地验证三件事每个通道的数据顺序是否正确——波形出现的先后必须与Rank顺序一致。采样时间是否足够——如果正弦波幅度看起来明显偏小或者波形有阶梯感说明采样时间不够。相位差是否正常——同一时刻采多路正弦波理论上各路之间应该几乎没有相位差取决于采样循环时间如果某一路相位明显偏移检查那个通道的前级电路。当时我在一个积分为高阻抗输出的传感器上发现了波形钝化的问题就是靠这个正弦波自检抓出来的。回头想省下的排查时间远超调那根信号线占用的十分钟。5.5 关于“相位差”这个搜索热词多说一句最近看到一些搜“相位差 stm32h743”的人多半是想用ADC同时采集电压和电流两路信号然后通过反正切计算相位差做功率因数计算。用多通道ADC做这事有个天然限制扫描模式下多路信号不是真正同步采样的而是有一个微小的转换时间差。如果两路信号频率是50Hz这个时间差造成的相位误差通常可以忽略但如果信号频率到了kHz级别就必须考虑这个误差。要精准测相位差方案有两个一是用ADC1和ADC2的双同步模式让两路信号严格同拍采样二是把两个采样点之间的时间差固定下来在算法里补偿这个固定偏移。第一个方案硬件配置简单软件补偿的工作量也小是我推荐的方案。最后分享一个调H743采集时的小心得不要一上来就把采样率跑满。先把CubeMX里的采样周期和连续模式调到一个保守值确认DMA数据通路没问题再逐步压缩采样周期、提升速率。每次只改一个变量用示波器或信号发生器验证这样的调试节奏看起来慢实际上是踩坑最少的路子尤其是在H743这种外设丰富、配置自由度高的芯片上。