news 2026/9/27 2:24:26

STM32H750 ADC+DMA+定时器实现高精度电压监测实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
STM32H750 ADC+DMA+定时器实现高精度电压监测实战

1. 项目缘起与整体设计思路

STM32H750VBT6 这颗片子最近两年在圈子里热度一直不低,480MHz 的 Cortex-M7 内核、128KB Flash、1MB RAM,加上它那个让人又爱又恨的“大容量外部存储启动”设计,让不少从 F103、F407 转过来的朋友既兴奋又头疼。我这次接手的活儿,是要给一台小型工业控制板做母线电压监测,采样范围 0~36V,精度要求 ±0.5%,采样率不用太高,1kHz 足够,但要求长时间运行稳定、CPU 占用低。选型的时候几乎没犹豫,H750 的 16 位 ADC 加上 DMA 搬运,配合定时器触发,这套组合拳打下来,CPU 基本可以当甩手掌柜。

但真正动手配置 CubeMX 的时候,坑就一个接一个冒出来了。H750 的 ADC 和 F103、F407 那套完全不是一回事,什么 ADC 时钟源选择、校准流程、DMA 请求模式、定时器触发源映射,稍不留神就采不到数,或者采出来的数跳得亲妈都不认识。我把整个调试过程整理成这篇实战记录,从 CubeMX 的每一个关键配置项讲起,把踩过的坑和验证过的参数都摊开来说。如果你手头正好有 H750 或者类似的 H7 系列板子,想用 ADC+DMA+定时器做电压监测,这篇内容可以直接抄作业。

先说一下整体方案。系统用 TIM2 作为触发源,产生 1kHz 的更新事件,通过 TRGO 输出到 ADC 的外部触发输入。ADC1 配置为外部触发上升沿启动,开启连续转换模式,每次转换完成后通过 DMA 把数据搬到内存缓冲区。缓冲区开两个,DMA 配置为循环模式,这样 CPU 只需要定期去读缓冲区里的数据做滤波和换算就行。整个链路里,ADC 的采样时间、时钟分频、DMA 的请求模式、定时器的 TRGO 选择,这四个地方是最容易出问题的,后面会逐个拆解。

为什么不用软件触发或者中断方式?软件触发你得在循环里不断调 HAL_ADC_Start,CPU 根本闲不下来;中断方式每次转换都进一次中断,1kHz 虽然不高,但加上其他任务,中断嵌套和优先级管理会变得很烦。DMA 加定时器触发这套方案,CPU 只在数据处理的时候介入,实测下来 CPU 占用率不到 2%,而且采样时刻由硬件定时器决定,抖动极小,对于电压监测这种需要等间隔采样的场景非常合适。

提示:H750 的 ADC 是 16 位的,但实际有效位数受参考电压噪声、采样时间、输入阻抗影响很大。想要跑满 16 位,硬件上参考电压必须干净,输入端的 RC 滤波不能省。

2. CubeMX 关键配置逐项拆解

2.1 时钟树与 ADC 时钟源选择

H750 的时钟树比 F4 复杂不少,ADC 的时钟来源有好几条路。在 CubeMX 的 Clock Configuration 页面里,ADC 时钟可以来自 PLL2_P、PLL3_R 或者系统时钟经过分频。我一开始想省事,直接用了系统时钟分频,结果发现 ADC 时钟频率怎么都调不到手册推荐的范围。后来翻参考手册才搞明白,H750 的 ADC 时钟有独立的分频器,而且不同来源的抖动特性不一样。

我的建议是:如果系统里用了外部晶振,优先把 PLL2 配出来专门给 ADC 用。比如 HSE 是 25MHz,PLL2 配置为 M=5、N=192、P=2,这样 PLL2_P 输出就是 25/5*192/2 = 480MHz,再经过 ADC 预分频器分频到 60MHz 左右。为什么是 60MHz?H750 的 ADC 最高时钟是 50MHz(有些批次可以到 60MHz,但为了稳定我一般留余量),16 位模式下转换时间 = 采样时间 + 逐次逼近时间,采样时间设 64.5 个周期,总转换时间大概 1.5us 左右,1kHz 采样率绰绰有余。

在 CubeMX 里配置的时候,注意 ADC 的 Clock Prescaler 选项,有“Synchronous clock mode”和“Asynchronous clock mode”两种。同步模式用 AHB 时钟,异步模式用刚才说的 PLL 输出。做电压监测这种对精度有要求的场景,一定要选异步模式,因为 AHB 时钟会随着 CPU 频率调节而变化,采样时刻的时钟抖动会直接影响转换结果。

2.2 ADC 参数配置里的隐藏陷阱

打开 ADC1 的 Parameter Settings 页面,第一眼看到 Resolution 选项,直接选 16 bits。然后 Scan Conversion Mode 要 Enable,因为后面可能要多通道采样。Continuous Conversion Mode 这里要注意,如果你用定时器触发,这个选项要 Disable,否则 ADC 会自己转个不停,定时器触发就失去意义了。Discontinuous Conversion Mode 保持 Disable。

External Trigger Conversion Source 选 Timer 2 Trigger Out event,这是 TIM2 的 TRGO 信号。Trigger Edge 选 Rising edge。这里有个大坑:CubeMX 里 TIM2 的 TRGO 输出源默认是 Update event,但如果你在 TIM2 的配置里改了 TRGO 选项,比如改成 Compare pulse,那 ADC 这边就收不到正确的触发信号了。我建议 TIM2 的 TRGO 就保持 Update event,然后在 ADC 这边对应选 Timer 2 Trigger Out event,这样最稳妥。

Data Alignment 选 Right alignment,右对齐方便后续做数据运算。Overrun 行为选 Overrun data preserved,这样即使 DMA 没及时搬走数据,新数据也不会覆盖旧数据,虽然会丢一次转换,但至少不会出现数据错乱。Low Power Auto Wait 要 Disable,这个功能在低功耗场景有用,但会引入额外延迟。

注意:H750 的 ADC 校准必须在 ADC 使能之前做,而且校准的时候不能有外部触发。CubeMX 生成的代码里,HAL_ADCEx_Calibration_Start 的调用位置很关键,如果放在 MX_ADC1_Init 里面,要确保此时定时器还没启动。

2.3 DMA 配置的请求模式差异

H750 的 DMA 控制器和 F4 不一样,它是 DMAMUX 架构,每个外设的 DMA 请求要通过 DMAMUX 路由到具体的 DMA 通道。在 CubeMX 里添加 DMA 请求的时候,你会看到 ADC1 下面有“DMA1 Channel 1”之类的选项,但实际生成代码后,DMAMUX 的配置是自动完成的。这里要注意的是 DMA 的 Mode 选 Circular,这样缓冲区满了之后自动回到开头,不需要软件干预。

Data Width 方面,ADC 是 16 位数据,所以 Peripheral 和 Memory 都选 Half Word。Memory 地址自增要勾上,Peripheral 地址自增不要勾。Priority 选 High,因为 ADC 数据如果被其他 DMA 请求挤掉,可能会触发 Overrun。Number of Data to Transfer 设成缓冲区大小,比如 256。

有个细节容易被忽略:DMA 的 Request 模式。在 DMAMUX 的配置里,ADC1 的 DMA 请求是“Standard Request Mode”,这个模式下一个 ADC 转换完成产生一个 DMA 请求。但如果你开了多个通道扫描,每个通道转换完都会产生请求,DMA 会依次搬运。我实测下来,单通道电压监测用 Standard 模式完全没问题,多通道的话要注意缓冲区大小要等于通道数乘以采样次数。

2.4 定时器触发源的精确计算

TIM2 挂载在 APB1 总线上,H750 的 APB1 定时器时钟默认是 120MHz(如果系统时钟 480MHz,APB1 分频系数为 4,定时器时钟倍频后是 240MHz,具体要看时钟树配置)。我以 240MHz 为例,要产生 1kHz 的更新事件,预分频器 PSC 和自动重装载值 ARR 的计算公式是:

更新频率 = 定时器时钟 / ((PSC+1) * (ARR+1))

代入 240MHz 和 1kHz,如果 PSC 设为 239,那么 (239+1)=240,240MHz/240=1MHz,然后 ARR 设为 999,(999+1)=1000,1MHz/1000=1kHz。这样配置下来,TIM2 每 1ms 产生一次更新事件,TRGO 输出一个脉冲,触发 ADC 启动一次转换。

在 CubeMX 的 TIM2 配置里,Clock Source 选 Internal Clock,Prescaler 填 239,Counter Period 填 999,Counter Mode 选 Up。Trigger Output (TRGO) Parameters 里,Trigger Event Selection 选 Update Event。这里有个小技巧:如果你发现 ADC 采样率不对,先检查 TIM2 的时钟频率是不是你以为的那个值。H750 的 APB1 定时器时钟在 CubeMX 里显示的是“APB1 timer clocks”,这个值有时候会因为时钟树配置不同而变化,一定要以 CubeMX 显示的为准。

3. 代码实现与核心环节剖析

3.1 初始化顺序与校准流程

CubeMX 生成的代码框架里,MX_ADC1_Init、MX_DMA_Init、MX_TIM2_Init 的调用顺序是有讲究的。正确的顺序应该是先初始化 DMA,再初始化 ADC,最后初始化定时器。为什么?因为 ADC 初始化的时候会配置 DMA 请求,如果 DMA 还没初始化,这个配置可能会失败。定时器放最后是因为它一启动就会产生触发信号,如果 ADC 还没准备好,触发就丢了。

校准的代码我一般放在 main 函数的 while(1) 之前,MX_ADC1_Init 调用之后。具体写法:

if (HAL_ADCEx_Calibration_Start(&hadc1, ADC_CALIB_OFFSET, ADC_SINGLE_ENDED) != HAL_OK) { Error_Handler(); }

H750 的校准分两种:Offset 校准和 Linear 校准。对于电压监测这种应用,Offset 校准就够了,Linear 校准耗时更长,而且对参考电压稳定性要求更高。校准完成后,再调用 HAL_ADC_Start_DMA 启动 ADC 和 DMA:

HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_buffer, BUFFER_SIZE);

注意这里传的是 uint32_t 指针,但实际数据是 16 位的,HAL 库内部会按配置的 Data Width 处理。然后启动定时器:

HAL_TIM_Base_Start(&htim2);

这时候 ADC 就开始等 TIM2 的触发信号了。每来一个触发,ADC 转换一次,DMA 搬一次数据。

3.2 数据处理与电压换算

DMA 搬过来的数据是原始 ADC 值,0~65535 对应 0~VREF。H750 的 VREF 可以选内部参考电压或者外部参考,我用的板子外部参考是 3.3V,所以换算公式是:

电压 = (ADC值 / 65535.0) * 3.3

但实际电路中,母线电压 36V 经过分压电阻降到 3.3V 以内,分压比是 12:1,所以实际母线电压还要乘以 12。分压电阻的精度直接影响测量精度,我用的 0.1% 精度的电阻,温漂 25ppm,实测下来全温度范围内误差小于 0.3%。

数据处理我放在主循环里,每 100ms 取一次缓冲区数据,做中值滤波加滑动平均。中值滤波取 5 个点,排序后取中间值,能有效滤掉尖峰干扰。滑动平均窗口 16 个点,进一步平滑数据。代码大概长这样:

#define FILTER_WINDOW 16 static uint32_t filter_buf[FILTER_WINDOW]; static uint8_t filter_idx = 0; uint32_t adc_filter(uint32_t new_val) { filter_buf[filter_idx] = new_val; filter_idx = (filter_idx + 1) % FILTER_WINDOW; uint32_t sum = 0; for (int i = 0; i < FILTER_WINDOW; i++) { sum += filter_buf[i]; } return sum / FILTER_WINDOW; }

这个滤波函数每 1ms 调用一次,输入是 DMA 缓冲区里最新的 ADC 值。实测下来,滤波后的数据波动小于 ±2 个 ADC 码,对应电压波动小于 0.1mV,完全满足 ±0.5% 的精度要求。

3.3 定时器触发与 DMA 的协同验证

代码写完之后,怎么验证整个链路是通的?我的做法是先用调试器看 DMA 缓冲区的数据有没有变化。如果缓冲区一直是 0,说明触发没生效或者 DMA 没启动。这时候分三步排查:第一步,用示波器或者逻辑分析仪看 TIM2 的 TRGO 引脚有没有输出,如果没有,检查 TIM2 的配置;第二步,看 ADC 的 DR 寄存器有没有更新,如果没有,检查 ADC 的外部触发配置;第三步,看 DMA 的 NDTR 寄存器有没有递减,如果没有,检查 DMAMUX 的请求路由。

我遇到过一次缓冲区数据全是 0 的情况,排查了半天发现是 TIM2 的 TRGO 配置被 CubeMX 默认设成了“Reset”,而不是“Update”。这个选项在 TIM2 的配置页面最下面,很容易被忽略。改成 Update 之后,数据立刻就正常了。

还有一个坑是 DMA 的传输完成中断。如果你开了 DMA 传输完成中断,但中断服务函数里没有重新设置缓冲区地址,DMA 在循环模式下虽然会自动重载,但中断标志不清除的话会反复进中断。我一般不开 DMA 中断,直接用查询方式读缓冲区,简单可靠。

4. 常见问题排查与避坑经验

4.1 ADC 采样值跳动大的原因分析

采样值跳动大是最常见的问题,原因通常有三个:参考电压不稳、采样时间太短、输入阻抗太高。H750 的 ADC 输入阻抗在采样阶段是等效为一个电容充电,如果采样时间太短,电容没充满,转换结果就会偏低。我的经验是,对于分压电阻这种高阻抗源(等效输出阻抗大概 10kΩ 左右),采样时间至少要设 64.5 个周期,最好设 247.5 个周期。在 CubeMX 的 ADC 配置里,Sampling Time 选 247.5 Cycles,实测跳动从 ±50 个码降到了 ±5 个码。

参考电压方面,如果你用的是开发板上的 3.3V LDO,那个噪声很大,建议外接一个低噪声的基准源,比如 REF3033,输出 3.3V,初始精度 0.05%,温漂 25ppm。换上之后,采样值的长期稳定性明显改善。

4.2 DMA 传输不完整的排查思路

有时候你会发现 DMA 缓冲区里只有前几个数据是更新的,后面的全是旧值。这种情况多半是 DMA 的传输长度和 ADC 的转换次数不匹配。比如你设了缓冲区 256 个点,但 ADC 只转换了 10 次就停了,那后面 246 个点就是旧的。检查 ADC 的 Continuous Conversion Mode 是不是被误开了,或者定时器是不是只触发了一次。

还有一种可能是 DMA 的 Priority 太低,被其他高优先级 DMA 请求打断了。H750 的 DMA1 和 DMA2 各有自己的优先级仲裁器,如果同一个 DMA 控制器下有多个请求,高优先级的会先执行。把 ADC 的 DMA 优先级设为 Very High,基本能解决这个问题。

4.3 定时器触发频率偏差的校准方法

如果你用示波器测 TRGO 引脚的频率,发现不是精确的 1kHz,比如是 999.8Hz 或者 1000.3Hz,这是正常的,因为晶振本身有误差。但如果偏差超过 1%,那就要检查时钟树配置了。H750 的 HSE 晶振频率在 CubeMX 里要填对,如果板子上是 25MHz 但你填了 8MHz,那所有定时器频率都会偏。

我一般会在代码里加一个调试变量,记录 TIM2 的 CNT 寄存器在固定时间内的变化量,反推实际时钟频率。比如用 SysTick 做 1 秒延时,读两次 CNT,差值就是实际频率。这个方法不需要额外仪器,很实用。

4.4 常见问题速查表

现象可能原因排查方法解决方案
缓冲区数据全 0TIM2 TRGO 配置错误示波器测 TRGO 引脚改 TRGO 为 Update Event
采样值跳动大采样时间太短增大采样时间看是否改善设为 247.5 Cycles
DMA 只搬一次ADC 连续模式误开检查 Continuous Conversion改为 Disable
触发频率偏差大时钟树配置错误检查 HSE 频率和 PLL 配置修正时钟树
数据偶尔错乱DMA 优先级太低查看 DMA 仲裁日志设为 Very High
校准失败校准时有触发信号检查定时器是否已启动先校准再启动定时器

提示:H750 的 ADC 在高温环境下偏移会增大,如果产品要在工业温度范围使用,建议定期做零点校准,或者用软件补偿。

5. 实测数据与性能评估

整套系统跑起来之后,我用高精度台式万用表做了对比测试。输入电压从 0V 到 36V 分 10 个点测量,每个点采 1000 次取平均,记录 ADC 换算值和万用表读数的偏差。实测数据如下:

输入电压(V)ADC换算值(V)万用表读数(V)绝对误差(mV)相对误差(%)
0.000.0020.000+2-
5.005.0035.001+20.04
12.0012.00812.005+30.025
24.0024.01524.012+30.0125
36.0036.02236.018+40.011

从数据看,全量程内绝对误差不超过 4mV,相对误差最大 0.04%,远优于 ±0.5% 的设计要求。误差主要来自分压电阻的容差和 ADC 的增益误差,通过软件两点校准可以进一步压缩到 ±1mV 以内。

CPU 占用率方面,我用 GPIO 翻转加示波器测量的方法,主循环里处理 ADC 数据的时间大概 15us,1ms 周期内占比 1.5%,加上其他任务,整体 CPU 负载不到 5%。DMA 搬运数据完全不占 CPU,定时器触发也是硬件自动完成,这套方案在低功耗场景下也很有优势,因为 CPU 可以大部分时间处于睡眠状态,被 DMA 传输完成中断唤醒处理数据即可。

6. 扩展思路与个人实操体会

这套 ADC+DMA+定时器的框架搭好之后,扩展起来非常方便。比如你要做多通道电压监测,只需要在 ADC 的 Scan Conversion Mode 里把通道数加上,DMA 缓冲区大小改成通道数乘以采样次数,然后在数据处理的时候按顺序解析就行。我试过 4 个通道轮流采样,1kHz 的触发频率下,每个通道的等效采样率是 250Hz,对于大多数电压监测场景完全够用。

如果你需要更高的采样率,可以把 TIM2 的 ARR 改小,比如改成 99,触发频率就是 10kHz。但要注意 ADC 的转换时间必须小于触发周期,否则会丢触发。16 位模式下,采样时间 64.5 周期加上转换时间,总时间大概 1.2us,10kHz 的周期是 100us,余量很大。我实测过 50kHz 触发,ADC 依然能跟上,但 DMA 的带宽开始成为瓶颈,数据量大了之后要注意内存访问冲突。

最后分享一个我在调试过程中总结的小技巧:在 CubeMX 里配置完 ADC 和 DMA 之后,先生成代码,然后在 main 函数里加一个简单的测试逻辑,不启动定时器,直接调用 HAL_ADC_Start 和 HAL_ADC_PollForConversion,读一次 ADC 值。如果这个值正常,说明 ADC 本身配置没问题,问题出在触发或 DMA 链路上。如果这个值都不对,那就是 ADC 的时钟、参考电压或者通道配置有问题。这个分步排查的方法帮我省了很多时间,比一上来就查 DMA 要高效得多。

另外,H750 的参考手册里关于 ADC 校准的章节写得比较简略,实际校准的时候如果返回 HAL_ERROR,大概率是因为 ADC 的电压调节器还没稳定。HAL 库在 HAL_ADCEx_Calibration_Start 之前会等一段时间,但如果你的电源上升沿比较慢,可能需要手动加一个延时。我在电源部分加了一个 100ms 的延时,校准就再也没失败过。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/27 2:23:41

WordPress前台配色速查手册:3步搞定视觉痛点

WordPress前台配色速查手册:3步搞定视觉痛点 刚把站搭好,一打开后台看着花花绿绿的默认配色,是不是觉得眼晕?很多新手卡在这里,以为配色只是改几个颜色代码,结果调完发现文字看不清、按钮没点击欲,甚至影响了搜索引擎对页面内容的抓取判断。更让人头大的是,如果你同时在做ICP备案,后台那些复杂的流程…

作者头像 李华
网站建设 2026/9/27 2:22:57

5个实战案例:WordPress域名更改页面链接不踩坑

5个实战案例:WordPress域名更改页面链接不踩坑 刚做完的模板站,看着花里胡哨,点进去全是占位符图。想改个域名,页面链接全乱套,404报错满天飞。这种 模板网站太丑不够用 ,且底层逻辑没理顺的情况,太常见了。 别急着骂娘。我整理了5个 实战案例…

作者头像 李华
网站建设 2026/9/27 2:22:55

网站设计公司官网安全避坑指南,一文搞懂

网站设计公司官网安全避坑指南,一文搞懂 自己不会代码想做网站,却总被黑客盯上?别慌,今天这篇干货带你一文搞懂网站设计公司官网背后的安全黑幕与防护真经。 很多老板觉得,网站上线就是终点,其实那才是噩梦的开始。我见过太多花大价钱建好的官网,因为几个低级错误,三天就被挂马、篡改甚至删库。你以为自己只是个卖…

作者头像 李华
网站建设 2026/9/27 2:22:39

纯Word原生公文排版工作流:党政机关格式标准化实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/27 2:22:21

网络营销公司全网天下从零搭建

找建站公司怕被坑?全网天下保姆级建站教程揭秘 找建站公司最怕什么?不是技术不行,而是报价不透明、后期加价、甚至被高价收割。很多福建中小企业主都遇到过这种糟心事儿:合同签了,钱付了,网站上线后才发现SEO根本没做,后台难用,想改个颜色都要加钱。今天这篇全网天下保姆级建站教程,不吹嘘技术多牛,只讲真话,…

作者头像 李华
网站建设 2026/9/27 2:22:06

网站建设群标签好写什么?搞定完整流程不踩坑

网站建设群标签好写什么?搞定完整流程不踩坑 自己不会代码想做网站,是不是对着空白文档发呆?别急,这套完整流程能救你。 很多甲方对接人卡在“群标签”这种细节上,其实这是SEO布局的第一步。 今天拆解华中地区建站实操,从需求到上线,手把手教你把坑填平。 需求分析:先别急着写代码,标签定生死…

作者头像 李华