ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

STM32H533CET6 ADC从单通道到多通道DMA与定时器触发实战指南

2026/8/30 11:46:49 拓冰建站 浏览量
STM32H533CET6 ADC从单通道到多通道DMA与定时器触发实战指南 实际项目里很多人的第一块板子都是从F1或者F4开始的ADC那一套代码基本是拷来拷去。但当我第一次拿到STM32H533CET6去写ADC读取时发现老一套根本不灵CubeMX里多了一堆没见过的选项HAL库函数也跟以前不一样而且同样一套代码放在这颗芯片上读出来的数据总是不对劲。这篇文章就从STM32H533CET6这颗料的实际使用出发把ADC从单通道软触发到多通道扫描加DMA、定时器触发、滤波、标定、排错整个链路都过一遍给正在用H5系列做模拟量采集的人一个可以直接参考的路线。1. STM32H533CET6的ADC资源先别急着照搬老代码1.1 这颗芯片的ADC到底变在哪也许有人问同样是ADC能有多大差别实际上差别很大。STM32H533CET6的内核是Cortex-M33外设总线结构和老款M3/M4不太一样ADC时钟不是简单挂在APB2上而是由内核时钟树的一个分支提供。你在CubeMX里能看到ADC kernel clock的分频设置如果不先把它理清楚后面配置采样时间、计算采样率都是空中楼阁。H5系列的ADC虽然名义上还是12位逐次逼近型SAR ADC但内部带了硬件过采样、可配置的采样保持时间、多通道扫描、规则组和注入组、更灵活的触发源以及一套校准流程。对于从F1/F4切过来的人最不适应的就是时钟树。老款ADC通常直接在APB2上分频配置完就完事H5的ADC时钟要从PLL、HSI或者HSE里选一条路径再经过ADC分频器输出。CubeMX的Clock Configuration页面能看到这条链路但很多人没注意直接在预设时钟树上改了某个分频结果ADC时钟超限CubeMX就报红。所以用STM32H533CET6的ADC之前建议先打开参考手册找到RCC和ADC章节把时钟路径过一遍。不然你算出来的采样周期和实际行为对不上排查起来非常痛苦。另外H5的校准和旧系列也不同HAL库里有独立的HAL_ADCEx_Calibration_Start函数不在初始化里调用的话前几次转换数据可能不准尤其是换挡或变温度之后。1.2 先想清楚需求再决定用哪种模式应用场景推荐接口方式只采一路周期不用太准软件触发加Polling读取多路轮流采集扫描模式加DMA需要精确固定间隔采样定时器触发加DMA多个信号要求时间同步双ADC同步或注入组不要一上来就上多通道DMA。我自己的习惯是先用单通道轮询验证硬件通路确认引脚、基准和信号源都没问题再切多通道。这里有个容易忽略的点如果多路信号只是“差不多同时采集”就可以扫描模式足够了但如果做电机电流环或者电源环路要求每次采样都发生在固定的PWM时刻就必须用定时器触发。STM32H533CET6的定时器触发源不止一个具体用哪个定时器事件要参考CubeMX里的下拉列表和芯片参考手册的触发映射表。选择模式之前还要想清楚采样率要求。ADC是单次转换还是连续转换DMA是普通模式还是循环模式这些都直接决定CubeMX里的参数。一开始选错后面再改配置尤其在板上已经焊好料的时候会非常浪费时间。1.3 引脚、封装与参考电压STM32H533CET6的“CE”后缀代表LQFP48封装引脚数量不算多ADC输入脚和PWM输出、UART、I2C复用在一起。如果你把ADC输入布在PWM输出旁边读数跳变会非常明显。所以画板阶段就要注意ADC输入走线尽量短避免与数字IO长距离平行有条件在引脚附近加一个RC低通。还有一个常被忽略的点是参考电压。LQFP48封装的VREF引脚需要单独接一个低噪声电源不能直接把3.3V数字电源接上去。如果参考电压有纹波ADC的有效位数会明显下降。H5内部还有VREFINT通道可以用来做内部基准校准后面标定部分会用到。另外引脚模式一定要设成Analog不要保留默认的GPIO输入模式否则内部上下拉或数字输入缓冲会干扰模拟信号。2. 用CubeMX配置STM32H533CET6的ADC参数怎么选2.1 ADC时钟树与采样时间计算配置建议进入ADC1的Parameter Settings之前先在Clock Configuration里看ADC kernel clock。以我用的工程为例PLL1Q给到ADC kernel clock再经过一个可配置分频器得到ADC时钟。CubeMX会告诉你当前值是否超范围如果报红就降低PLL1Q频率或增加分频系数。采样时间设置界面里有一个Sampling Time下拉常见选项有1.5、2.5、8.5、16.5、32.5、64.5、387.5等周期数。采样时间越长采样保持电容充电时间越充足但总转换时间也越长。转换时间计算公式可以写成Tconv (采样时间周期数 固定转换周期数) / ADC时钟频率对12位分辨率固定转换周期数通常是12.5。比如ADC时钟50MHz采样时间2.5周期Tconv (2.5 12.5) / 50MHz 0.3us单通道连续采样理论约3.33Msps。如果采样时间设成32.5周期Tconv (32.5 12.5) / 50MHz 0.9us约1.11Msps。多通道扫描时每轮总时间等于所有通道Tconv之和。这段计算虽然简单但很关键。尤其在用定时器触发时定时器溢出周期不能小于每轮扫描的Tconv否则触发事件来了但ADC还在忙转换结果会被延后甚至丢失。我习惯把最大值留出20%余量再反推采样时间能设多少。2.2 单通道软件触发配置步骤单通道轮询方式适合调试阶段快速验证。CubeMX里的操作路径大致是这样的在左侧Pinout面板找到ADC1把对应的输入引脚拉到Analog模式。在ADC1 Mode里使能需要的通道比如IN0。在Parameter Settings里设置Resolution选12-bit。Scan Conversion Mode设为Disable。Continuous Conversion Mode设为Disable。Discontinuous Conversion Mode设为Disable。DMA Continuous Requests设为Disable。Sampling Time先用16.5周期等数据正常后再缩短。生成代码后先调用HAL_ADCEx_Calibration_Start再调用HAL_ADC_Start、HAL_ADC_PollForConversion、HAL_ADC_GetValue。这里有个细节如果信号源内阻比较大采样时间设太短采样电容充不满读数会偏低且随温度变化。把采样时间拉长到64.5周期甚至387.5周期如果读数马上变准就说明前级驱动能力不足。硬件上可以加运放跟随器不能只靠软件硬扛。2.3 多通道扫描加DMA最容易出错的几个开关多通道扫描加DMA才是实际项目里最常用的组合。步骤是在ADC1的Rank区域添加多个通道每个Rank对应转换顺序。打开Scan Conversion Mode和Continuous Conversion Mode。给每个通道设置采样时间不同通道可以不一样。在DMA Settings里添加ADC1的DMA请求模式设为Circular数据宽度设为Half Word。回到ADC Parameter Settings确认DMA Continuous Requests为Enabled。为什么DMA Continuous Requests这么重要因为ADC转换完一轮如果这个选项没开DMA搬完N个数据后就停在那里不再响应后续的转换结果。很多人只在DMA设置里选了Circular以为就够了实际跑起来发现数组只更新一次就是这个原因。还有坑HAL_ADC_Start_DMA的第三个参数是buffer长度单位是转换次数不是字节数。比如4个通道传4就行。如果用uint32_t数组去装16位转换结果DMA按Half Word搬运时数据会连续存放在低16位或者高16位取决于地址对齐。最省心的做法是直接定义uint16_t数组。2.4 定时器触发按固定节奏干活对于需要精确采样间隔的应用比如电机控制或电源环路可以用定时器触发ADC。CubeMX里配置方法并不复杂选一个定时器比如TIM1配置成产生内部触发事件。不需要真的输出PWM到引脚只要让内部事件周期性地发出。在ADC1的Trigger Source里选择Timer 1 Trigger Out event之类。关闭Continuous Conversion Mode因为转换节奏由定时器决定不需要自动连续。启动顺序建议先启动DMA再启动定时器。也就是先HAL_ADC_Start_DMA再HAL_TIM_Base_Start确保触发事件来临时DMA已经准备好。注意即便用了定时器触发DMA Continuous Requests仍然建议打开否则每轮扫描后DMA不搬新数据。触发频率不能高于ADC转换速度。比如前面算的Tconv是0.3us定时器触发周期就不能小于0.3us多通道扫描则要大于所有通道Tconv之和。如果用了很大采样时间触发频率又很高数据会出现周期性错位很难查。3. HAL库代码实现软触发、DMA循环、滤波一次说清3.1 单通道轮询读取调试阶段最省事调试阶段我会先写一个简单的单通道读取函数用来确认硬件通路。代码大致如下uint16_t h533_adc_read_single(ADC_HandleTypeDef *hadc) { HAL_ADC_Start(hadc); if (HAL_ADC_PollForConversion(hadc, 10) HAL_OK) { return (uint16_t)HAL_ADC_GetValue(hadc); } return 0xFFFF; }实际使用时校准函数只调用一次放在初始化的最后。每次读取都校准没有必要而且会拖慢速度。如果通道切换了要重新配置rank并重新启动否则读到的是上一次配置的结果。这个函数的优点是直观缺点是会阻塞等待。如果只用于调试完全够用。我经常用它读零点电压和标准电压源确认参考电压和硬件通路没问题后再切到DMA多通道模式。如果这一步数据就不对那就不要急着调DMA先查硬件。3.2 DMA多通道循环采样标准工程姿势多通道循环采样的代码结构大概是这样的#define ADC_CH_NUM 4 volatile uint16_t adc_raw[ADC_CH_NUM]; volatile uint8_t adc_dma_ready 0; void adc_dma_start(void) { HAL_ADCEx_Calibration_Start(hadc1, ADC_CALIB_OFFSET, ADC_SINGLE_ENDED); __HAL_ADC_CLEAR_FLAG(hadc1, ADC_FLAG_DMAEOC); HAL_ADC_Start_DMA(hadc1, (uint32_t *)adc_raw, ADC_CH_NUM); } void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc) { if (hadc-Instance ADC1) { adc_dma_ready 1; } }主循环里可以这样消费数据uint16_t local_buf[ADC_CH_NUM]; if (adc_dma_ready) { adc_dma_ready 0; for (int i 0; i ADC_CH_NUM; i) { local_buf[i] adc_raw[i]; } }为什么要先拷贝到local_buf因为DMA是后台搬运主循环读取的时候DMA可能正在更新数组。如果直接处理adc_raw可能前半段是新数据、后半段是旧数据。先整体拷贝一份至少保证这一帧数据是同一个时刻的一致性快照。当然这只是简易方法更严格的做法是用DMA半传输完成中断加传输完成中断做双缓冲但小工程不一定需要。adc_raw和adc_dma_ready必须加volatile否则编译器优化后可能一直读到旧值。这个坑很经典尤其在开启O2优化之后。3.3 软件滤波别被原始值误导原始ADC值跳动很正常要区分是硬件噪声还是信号本身。我在项目中加了一个滑动平均滤波代码很简单#define FILTER_DEPTH 16 static uint16_t filter_queue[FILTER_DEPTH]; static uint8_t filter_head 0; static uint8_t filter_full 0; uint16_t adc_slide_average(uint16_t input) { uint32_t sum 0; filter_queue[filter_head] input; filter_head; if (filter_head FILTER_DEPTH) { filter_head 0; filter_full 1; } uint16_t count filter_full ? FILTER_DEPTH : filter_head; for (uint16_t i 0; i count; i) { sum filter_queue[i]; } return (uint16_t)(sum / count); }这个实现会在前16次未填满时输出部分平均有一点平移但可接受。更稳的办法是初始化数组时全部填满第一个采样值再开始平均。如果信号本身有突发毛刺光滑动平均不够可以先做中值滤波去毛刺再做滑动平均。滤波的代价是延迟。如果做PID控制或电流环不要做过重的滤波一个简单限幅就够了。软件滤波应该配合硬件设计而不是替代硬件设计。我见过有人用滑动平均把纹波压得很好但阶跃响应慢到没法用那就要考虑是不是采样率太低了。3.4 把ADC值换算成电压和物理量ADC原始码值只是比例关系最终要换算成电压。最简单的公式#define ADC_VREF_MV 3300.0f #define ADC_RESOLUTION 4096.0f float adc_raw_to_mv(uint16_t raw) { return (float)raw * ADC_VREF_MV / ADC_RESOLUTION; }如果使用外部基准就用实际基准电压。很多板上VREF实际不是准确的3.3V可能是3.28V或者3.31V。可以先用万用表量一次再把ADC_VREF_MV改掉。更自动的做法是读取内部基准通道VREFINT利用校准值反推实际VREF。具体计算方式在参考手册“VREFINT”章节有说明基本思路是VREFINT是一个已知的标称值读码值之后反推当前参考电压。如果测量的是电流或温度还需要线性标定。比如一个量程5A的电流传感器输出电压0到3.3V那电流等于电压乘5再除3.3。但大多数传感器零点有偏移需要两点标定读零点对应的ADC码值再读满量程点用线性插值。标定参数建议存在Flash或外部EEPROM里不要写死在代码里否则每次换板都要重新编译。3.5 采样率估算与数据吞吐硬件的瓶颈在ADC总转换时间不在DMA搬运。举个例子12位分辨率ADC时钟50MHz采样时间2.5周期单通道Tconv是0.3us理论约3.33Msps。4通道扫描时每轮0.3us总吞吐率约3.33M次每秒但每个通道每秒钟只轮到约0.83M次。如果应用要求每个通道都是1Msps这个配置就不够。要提高吞吐