首页 / 资讯中心 / 文章详情

GD32 ADC配置实战:从STM32迁移到多通道DMA的关键细节

GD32 ADC配置实战:从STM32迁移到多通道DMA的关键细节 ★ FEATURED ARTICLE
拿到GD32的ADC外设第一反应容易觉得“学过STM32就万事大吉”。我最早接触GD32开发板时也这么干过直接把熟悉的ADC配置流程照搬过来结果同一个通道采出来的数值忽高忽低越调越发毛。后来老老实实把GD32的ADC时钟树、采样周期和校准流程单独过了一遍才发现问题基本都出在细节上时钟分频对不对、采样时间够不够、校准有没有做、多通道DMA的缓冲顺序是不是被自己搞反了。这篇笔记打算从最基础的认知讲起然后给一套可直接参考的单通道、多通道DMA配置方案再聊数据换算和片内校准最后把我在实际调试中踩过的坑和滤波经验一并整理出来。适合刚接触GD32、或者正从其他Cortex-M平台迁移过来的开发者照着排查能少走不少弯路。1. 上手前必须搞清的GD32 ADC基础认知1.1 它是一颗什么精度的ADC时钟从哪来GD32内置的ADC是逐次逼近型SAR结构常见精度为12位也就是说转换结果用12个bit来表示输入电压相对于参考电压的比例。内部包含多个模拟输入通道实际数量与封装和芯片型号强相关比如部分型号支持10个左右外部通道加上内部温度和参考电压通道具体以数据手册引脚定义为准。我第一次用的时候没查引脚复用表把ADC引脚配成了普通GPIO复用模式结果读出来永远是零排查了半天才发现方向错了。ADC的时钟源来自APB2总线时钟一般不能直接给到满频需要经过分频后再作为ADC工作时钟常见配置为PCLK2的2/4/6/8分频。GD32手册通常会给出ADC时钟上限不同系列差异很大有的允许到14MHz有的系列可以更高务必以你手上的型号数据手册为准。如果ADC时钟超限转换结果线性度会明显变差看起来就是采样值乱跳且毫无规律这时候先别怀疑传感器回头查分频配置。时钟分频不只是为了“不超过上限”还会直接决定单个转换花费的时间。ADC完成一次转换需要的ADC时钟周期数由采样时间加固定转换周期组成而采样时间由寄存器配置决定。所以选分频系数时要考虑最终能得到的采样率能不能满足你的控制周期或信号频带需求。1.2 一次采样到底经历了什么逐次逼近型ADC的转换过程可以拆成三个阶段。首先是采样阶段内部采样开关闭合采样电容被充电到输入电压这段时间由ADC_SAMPLETIME_x配置一般有几档可选越长的采样时间越有助于电容充分跟踪高阻抗信号源的电压。接着是转换阶段SAR逻辑逐位比较经过固定的转换周期后得到数字结果。最后是数据的锁存和标志位置位应用程序可以读取数据寄存器。对很多人来说采样时间只是“随便选一个能用的”但这里有个关键逻辑如果信号源内阻很高比如某个分压电阻网络阻值达到10千欧以上而采样时间太短采样电容还没充满就开始比较结果会偏低且不稳定。我在一次模拟量采集项目里遇到过类似现象前级传感器输出阻抗高用最短采样时间读出的值比实际电压低了一截把采样时间调长一档后立即恢复正常。所以看到数据偏小且有规律优先检查采样时间配置。采样时间直接决定了ADC的等效输入阻抗可以近似把采样电容、开关电阻和输入源串在一起理解。外部源阻抗越大的场合越需要更长的采样时间。实际项目中如果传感器输出经过了运放跟随器输出阻抗很低那么选短采样时间也没问题。1.3 不同GD32系列之间有哪些差异GD32家族里的芯片很多ADC外设寄存器设计大体沿着Cortex-M MCU的惯例但不同系列之间存在不少细微差异。比如有的系列ADC校准寄存器名称不同有的库函数里写adc_calibration_enable有的则是adc_calibrate_start如果你的代码是用库函数封装的跨系列移植时编译报错是常有的事。另外部分新系列增加了更高精度的选项或差分输入通道这部分能力在旧系列上完全不存在学习时不要拿着一个系列的寄存器硬套另一个系列。DMA与ADC的联动也在不同系列上有区别有些系列需要单独开启DMA请求使能位有些则在ADC使能后自动请求。还有多通道DMA的数据宽度、当前通道寄存器名称都有差异。建议拿到新板子后先跑一个最简单的单通道轮询例程确认采样值和寄存器都能对上再做多通道和DMA扩展。直接上手抄老代码往往是浪费时间最久的方式。另外一个非常实际的差异是参考电压范围。绝大多数GD32芯片的ADC参考电压直接来自VREF引脚或VDDA如果开发板把VREF接到了3.3V那么ADC范围就是0到3.3V。少数芯片内部有参考电压选择位可以在内部参考和外部VREF之间切换使用前务必备注当前板子的连接方式。这块弄错的话所有电压换算结果都会成比例漂移。2. 配置方式选哪种寄存器、标准库还是HAL风格2.1 我的建议顺序我自己的习惯是先用标准库或HAL风格的封装把功能跑通再去读寄存器确认重要配置的最终值。学习阶段不建议一上来就纯寄存器操作因为ADC涉及使能顺序、校准和等待标志代码流程比寄存器名更难记。先用封装好的函数把流程建立起来调试时再通过调试器查看ADC控制寄存器的实际值同时可以加深对位域意义的理解。库函数版本和芯片系列要匹配。工程里用到了adc_channel_length_config这类“通道长度”配置函数说明这套库函数把常规通道和注入通道区分开了配置常规通道数量时也要同时考虑注入通道数量是否被清零。有些开发者在这里漏调用导致常规通道转换触发后不产生正确的结果。个人经验是每配置一次采样通道组就把流程拆成“清长度、配通道、配顺序”三步简单且不容易错。如果你偏好HAL风格也完全可以关键是先确认你使用的代码生成器或库版本包含该系列支持。GD32没有统一标准库版本差异比想象中明显不同版本函数名可能完全不一致。拷贝代码前先看头文件里有没有对应原型这是最基础的检查手段。2.2 常用库函数的一个最小配置骨架以典型的标准外设库风格代码为例单通道轮询方式可以这样写rcu_periph_clock_enable(RCU_GPIOA); rcu_periph_clock_enable(RCU_ADC0); gpio_mode_set(GPIOA, GPIO_MODE_ANALOG, GPIO_OSPEED_50MHZ, GPIO_PIN_1); adc_clock_config(ADC0, ADC_CLK_DIV6); adc_channel_length_config(ADC0, ADC_ROUTINE_CHANNEL, 1); adc_routine_channel_config(ADC0, 0, ADC_CHANNEL_1, ADC_SAMPLETIME_55POINT5); adc_calibration_enable(ADC0); adc_enable(ADC0); adc_software_trigger_enable(ADC0, ADC_ROUTINE_CHANNEL); while (!adc_flag_get(ADC0, ADC_FLAG_END)); uint16_t raw adc_regular_data_read(ADC0);这段代码里我需要特别解释几点。ADC_SAMPLETIME_55POINT5是55.5个ADC时钟周期的采样时间为什么选它而不是最短值因为我的信号源是电阻分压网络输出阻抗不低长一点采样时间能确保结果稳定。如果你接的是低阻抗链路可以换成更短时间提高采样率。ADC_CLK_DIV6则取决于APB2频率和芯片ADC时钟上限假设APB2为72MHz6分频后ADC时钟12MHz在多数允许的范围内。如果芯片允许14MHz8分频只有9MHz6分频能获得更快转换速度。那句adc_calibration_enable很多人会漏。GD32的ADC校准模块在上电后会修正常规通道的偏移误差不做校准也能转换但零输入时的输出可能不是0而是几十到上百个LSB的偏移。项目里对精度有要求时校准基本上必做。注意校准必须在ADC使能之前调用顺序反了的话库函数内部会卡在等待标志的地方。2.3 配置顺序为什么不能乱ADC外设对配置顺序很敏感尤其是“校准必须在使能之前”“使能之后才能触发转换”“触发之后等待结束标志”。如果把校准放在adc_enable之后有些库函数会出现不等待或死锁最终读到的数据始终是老值。我见过一个现象程序跑起来只有一个固定采样结果无论输入电压怎么变结果都不动就是因为触发配置写在了使能后面软件触发根本没有真正生效。正确顺序可以概括为先开时钟再配GPIO模拟模式再配ADC分频与通道做校准使能ADC最后触发并等待。这个顺序背后的原因是校准模块需要在ADC上电稳定后且未开始转换前介入而触发转换需要ADC已经完全进入工作状态提前触发只会把转换请求丢失。遵循这个固定顺序基本可以避免初学者最容易踩的一批坑。还有一个容易忽略的细节常规通道配置函数里第二个参数是“通道在序列中的位置”第三个参数是“物理通道号”最后一个是采样时间。不熟悉的人可能把位置和物理通道搞反导致触发转换后读到的仍然是默认通道的数据。我建议每次配置完都用调试器看一眼当前序列寄存器确保你要采集的通道确实被放在第0个位置上。3. 单通道到多通道DMA的完整配置流程3.1 单通道轮询是理解ADC机制的试金石轮询模式适合采样频率不高、时间不敏感的场景。配置完成后每次需要采集时发起软件触发然后死等ADC_FLAG_END等待时间内的CPU只能空转。以12MHz ADC时钟、55.5周期采样加固定转换周期估算一次转换大约要几十个ADC周期对应时间大概是几微秒。这个量级在慢速传感器采集、按键电压检测等场合完全够用。但这并不意味着轮询代码可以随便写。等待结束标志之前最好先清一次结束标志否则上一次转换遗留的标志可能导致你读到上一个时刻的数据。常见的正确写法是触发前清标志触发后轮询标志标志置位后读取数据并再次清标志。这样即使后续要在中断里读取也不会因为标志残留而拿到陈旧结果。如果你发现轮询读取的数据一直是第一次的值或者偶尔漏采多半是标志没有清干净。另外读取数据寄存器本身有“读后清”的效果但依赖这个特性不够稳妥显式清标志才是可移植性更好的方式。我在实际项目里统一封装了一个函数触发、清标志、等待、读取、再清标志所有ADC读取都走同一个入口排查问题时方便很多。3.2 多通道扫描加DMA让CPU从等待里解放当需要采集多个通道时轮询模式会变得很难受因为你要不断查询“序列是否转换完成”CPU占用率上升且容易漏掉高速信号的变化。更常见的方案是使用扫描模式配合DMA把所有通道的转换结果自动搬运到内存数组中CPU只需要在处理周期内读取数组。以两个通道PA1、PA2为例典型配置如下#define ADC_CH_NUM 2 uint16_t adc_value[ADC_CH_NUM]; rcu_periph_clock_enable(RCU_DMA0); dma_deinit(DMA0, DMA_CH0); dma_parameter_struct dma_param {0}; dma_param.periph_addr (uint32_t)ADC_RDATA(ADC0); dma_param.periph_inc DMA_PERIPH_INCREASE_DISABLE; dma_param.memory_addr (uint32_t)adc_value; dma_param.memory_inc DMA_MEMORY_INCREASE_ENABLE; dma_param.periph_width DMA_PERIPH_WIDTH_16BIT; dma_param.memory_width DMA_MEMORY_WIDTH_16BIT; dma_param.direction DMA_PERIPHERAL_TO_MEMORY; dma_param.number ADC_CH_NUM; dma_param.priority DMA_PRIORITY_HIGH; dma_init(DMA0, DMA_CH0, dma_param); dma_circulation_enable(DMA0, DMA_CH0); dma_channel_enable(DMA0, DMA_CH0);然后把ADC配置成扫描模式并使能DMA请求adc_channel_length_config(ADC0, ADC_ROUTINE_CHANNEL, ADC_CH_NUM); adc_routine_channel_config(ADC0, 0, ADC_CHANNEL_1, ADC_SAMPLETIME_55POINT5); adc_routine_channel_config(ADC0, 1, ADC_CHANNEL_2, ADC_SAMPLETIME_55POINT5); adc_scan_mode_enable(ADC0); adc_dma_mode_enable(ADC0); adc_calibration_enable(ADC0); adc_enable(ADC0); adc_software_trigger_enable(ADC0, ADC_ROUTINE_CHANNEL);这段代码里最值得注意的一点是DMA地址必须是ADC_RDATA(ADC0)也就是ADC常规数据寄存器地址。这个寄存器是16位宽但只有低12位有效所以DMA用16位宽度传输刚好。如果DMA配成8位宽度连续搬运两个通道时会得到两个拆开的字节还原麻烦还容易出错。如果配成32位宽度虽然读取没问题但内存数组要求4字节对齐并且数据排列不如16位直观。dma_circulation_enable在这里很关键。因为扫描模式下一次转换序列会依次产生多个数据完成一轮后DMA传输数量归零。如果不开启循环模式第一轮结束后DMA就停止搬运你需要重新初始化才能继续采集。开启循环模式后DMA会持续把后续结果写入同一数组。这样在循环模式里数组里总是最近一轮的多通道数据。3.3 采样时间、转换周期与最大采样率的估算很多人配置ADC时只看“能用”不关心到底能采多快直到项目需要控制环路或音频采样时才傻眼。这里给一个估算方法一次完整转换总周期数 采样时间周期数 固定转换周期数。固定转换周期通常为12到15个ADC时钟周期具体看芯片。采样时间由寄存器选择档位决定比如55.5周期。那么一次转换大约70个ADC时钟周期ADC时钟12MHz时单次转换时间约5.8微秒对应采样率约170kSPS。这只是单通道极限值多通道扫描时所有通道共享这个速率通道越多单通道等效采样率越低。比如4个通道扫描每个通道的分摊时间就是总转换时间乘以通道数。如果你的应用要求每通道1kSPS以上4通道扫描模式下总采样率要求4kSPS这个需求大多数GD32都能轻松满足。但如果要求每通道100kSPS以上就得认真算清楚分频和采样时间否则数据会失真。我建议在配置表里把APB2频率、ADC分频、采样时间、通道数和理论采样率写清楚方便后续调整。实际项目中我遇到过一次采样率不够导致波形严重畸变的情况问题不在ADC本身而是选了过长的采样时间和过低的分频把理论采样率压到了远低于需求的水平。重新计算后把采样时间调短一档波形立刻正常。4. 读取转换结果是起点数据换算才是真正踩坑区4.1 左右对齐与12位数据的真实含义转换结果是12位的而数据寄存器是16位的这就有对齐方式的问题。右对齐模式下12位有效数据落在寄存器的低12位高4位为0直接读出的uint16_t就是0到4095的原始值。左对齐模式下12位数据被放在高12位低4位为0适合某些需要直接作为16位有符号数或DAC输入的场景。一般情况下我们都用右对齐因为在换算电压时右对齐更直观左对齐反而要移位后才能用。如果库函数配置里有对齐模式的选项请先确认默认值。很多库默认右对齐但个别封装示例可能是左对齐导致电压换算结果总是变成原先的16倍。这种错误非常隐蔽因为数据看起来有规律只是整体偏大。判断方法很简单输入一个已知电压比如1.65V右对齐合理值应该是2048左右如果读出来是32768附近说明对齐方式搞错了。另外要注意的是读取数据寄存器的时机。多通道扫描模式下DMA会逐个把数据搬到内存此时数据寄存器里已经是最后一个通道的值。如果你在DMA搬运过程中手动读数据寄存器结果可能停留在中间状态。所以一旦启用了DMA就不要再手动读取数据寄存器而是读取DMA搬运到内存的数组否则数据时序会乱。4.2 电压换算公式里的参考电压陷阱从原始码值换算成电压最简单的公式是电压 原始值 × 参考电压 / 4096。这里除以4096而不是4095是因为满量程码值对应的是接近参考电压的模拟输入细分度就是4096份。不少资料写4095也能用误差在0.02%级别但做精密采集时建议统一用4096。真正的坑是参考电压本身不一定是3.3V。很多开发板用稳压芯片输出3.3V实际可能只有3.28V甚至3.31V。如果直接用3.3V参与计算所有通道的电压都会系统性偏高或偏低。解决方法是实测一下板上的VREF引脚电压再把实测值代入公式。如果硬件上无法方便测量可以采集内部参考电压通道用已知的内部参考值反向推算实际VREF这是高端MCU常见的自校准思路。电源噪声也会直接进入ADC参考路径导致换算后的电压值出现抖动。因此对精度要求高的场合模拟电源引脚和参考引脚需要加滤波电容尽量减少数字开关噪声耦合。这部分工作虽然属于硬件设计但嵌入式工程师如果不理解就会在软件里花大量时间滤波效果还不尽如人意。软件滤波是必要的但硬件参考设计更值得优先做好。4.3 片内校准和温度传感器通道的使用细节GD32很多型号内置温度传感器可以用于粗略的片上温度监测。使用方式一般是把温度传感器通道加入常规通道序列然后按固定公式计算温度。不同型号的公式斜率、偏移都不同必须要查数据手册不能拿着另一个系列的公式硬套。温度传感器通道的采样值通常变化非常缓慢采样时间可以选长一些以降低噪声影响。片内校准的细节前面提过这里再补充一点校准操作本身需要等待标志位不同系列库函数返回方式不同。有的adc_calibration_enable是阻塞式内部一直在等待校准结束有的则只是发起校准需要你额外等待。用的库不同代码习惯也不同。建议在使用前翻一下库函数源码明确它是不是阻塞实现避免校准还没完成就开始ADC转换导致校准值无效。校准完成后如果要切换参考电压或改变ADC时钟部分系列要求重新校准。特别是从低功耗唤醒后ADC模拟部分可能经历了电源状态变化不重新校准直接读取的偏移会变大。低功耗应用里唤醒后重新初始化ADC并校准是一个稳妥且必要的流程。我见过项目因为省了这一步休眠唤醒后采集值偏差在几十个码值以上。5. 实际调试中最容易踩的五个坑5.1 采样值飘、跳变严重先从参考和采样时间入手跳变是ADC调试图中最高频的问题。原因嘛无外乎信号源内阻高、采样时间不足、参考电压不稳、地线噪声以及GPIO配置错误。我调试时先做三件事第一是确认GPIO确实处于模拟输入模式第二是确认采样时间不是最短档第三是把参考电压引脚上的纹波用示波器看一遍。这三项都正常后跳变才会考虑软件滤波。如果输入引脚悬空或者接了高阻分压网络ADC采到的就是浮空电压读数肯定乱跳。有些开发者抱怨“ADC采样值不稳”结果发现输入引脚根本没接传感器纯属悬空状态。这种问题是硬件连接层面的不是软件能彻底解决的。我的经验是先接一个稳定的直流电压源比如电池或稳压模块输出确认ADC本身稳定后再接真实传感器这样能把问题范围缩小一半。还有一点容易被忽略电源和地平面噪声。ADC对小信号敏感数字外设频繁翻转时地弹会通过模拟输入路径进入ADC。如果条件允许把ADC通道布局在远离高速数字信号线的地方并在电源入口加磁珠和电容隔离。软件上可以开启硬件均值或过采样功能但前提是硬件噪声没有大到完全淹没信号。5.2 多通道数据错位先查DMA传输数量和通道顺序多通道扫描配合DMA时数组里数据位置与通道顺序不一致是常见故障。我遇到过一种现象把PA1配置成通道0PA2配置成通道1结果数组里第一个元素总是PA2的电压。排查后发现是寄存器里通道位置写反了因为扫描序列按“位置号”从0开始转换DMA按完成顺序写入数组。如果配置里把物理通道号和位置搞混数据自然错位。另一种常见错位原因是DMA传输数量与通道数不匹配。假设你要采集3个通道DMA的传输数量配置成2那么第三个通道数据永远不会进入数组第二个通道数据也可能被第三个通道的结果覆盖。这类问题往往不会立刻报错而是表现为数据偶尔缺失。建议把通道数和DMA传输数量都用宏定义写好修改通道时同步修改省得排查时一头雾水。在带缓存区的工程里DMA循环写入同一数组会持续覆盖。如果应用在读数组的同时DMA也写入数组就会读到半旧半新的一组数据。我采取的方案是引入“双缓冲”DMA使用两个数组交替搬运读方拿到完整一组后再处理。虽然增加了内存开销但数据一致性明显改善。GD32的DMA本身不一定支持硬件双缓冲但可以用两个普通数组和中断切换来实现。5.3 DMA和中断同时使用时的优先级问题ADC转换结束可以触发中断DMA传输完成也能触发中断。一旦两者同时使能中断优先级配置不对可能导致DMA传输完成标志被高优先级的ADC中断淹没应用无法及时更新数据。我的建议是只要用了DMA搬运多通道数据就不必再开ADC转换完成中断。DMA传输完成中断已经足够说明这一轮数据全部就位开两次中断纯属增加复杂度。如果确实需要在单通道快速转换时用ADC结束中断那就在中断里直接读取数据寄存器不要在这个中断里访问DMA数组避免时序混乱。不同系列的中断标志位名称也有差异ADC_FLAG_END和ADC_INT_FLAG_END可能是两个不一样的标志前者是状态标志后者是中断标志读取和清除方式都不一样使用前对照库函数头文件查清楚。中断服务程序里还有一个小细节ADC结束中断标志必须在刚进入中断时就清掉否则连续触发下会一直进中断看起来像程序卡死在中断里。清除标志操作本身有延迟要求等标志位真正确认清除后再退出中断比较稳妥。我在一个项目里就因为清标志位置靠后导致中断频繁误触发CPU占用率直线飙升。5.4 校准标志卡死大多是库版本和调用位置问题库函数adc_calibration_enable内部如果采用等待方式一旦校准硬件没正常启动程序就会在调用处死等。常见原因是调用前ADC已经处于使能状态或者APB2时钟没开对。第一次遇到这问题时我先检查rcu_periph_clock_enable(RCU_ADC0)是否执行再确认校准调用前没有其他代码把ADC使能了。排查顺序对了问题基本就解决了。不同库版本的校准函数名和等待方式也有差异。有的库提供的校准函数不会等待完成而是启动后立刻返回这时候你需要在后续操作前再加一个while(!adc_flag_get(...))等待校准结束标志。如果不查源码直接照抄老工程的调用方式很可能会在新库上出现“校准未完成就开始转换”的潜在问题。建议每次换库版本后都花十分钟把用到的那几个函数实现源码快速扫一遍。还有一个隐蔽问题某些系列在低功耗模式唤醒后校准标志可能不会被正确置位。这时候即使调用校准函数也可能直接跳过等待导致后续转换没有真正校准。处理办法是在唤醒后先把ADC模块做一次完整复位或失能再使能再执行校准。虽然麻烦但比数据偏差来得省心。5.5 采样率不够时波形看起来像“台阶”而不是正弦波如果连续采集正弦波信号发现波形呈明显阶梯状说明采样率跟不上信号频率每个周期只采到几个点。这种问题不是ADC坏了而是配置层面的采样率不够。我一般先计算理论采样率再和信号频率做对比至少保证每个周期20个采样点以上才能看出大致波形形状。实际测试中记录几个特征频率下的采样点数就能快速判断瓶颈在哪里。要提高采样率主要途径是提高ADC时钟分频、缩短采样时间、关闭扫描模式中不需要的通道。有些系列还提供快速采样模式能进一步减少固定转换周期数。但要注意缩短采样时间是有代价的输入阻抗稍高时误差会增大。我的原则是先用保守配置确认硬件没问题再逐步缩短采样时间观察数据稳定性找到一个既能满足采样率、又不明显恶化精度的平衡点。6. 从实际项目里沉淀下来的降噪与过采样经验6.1 软件滤波别只会取平均值很多初学者处理ADC抖动就是连续读几十次取平均这在低频缓变信号面前有用但对高频噪声基本无效还会拖慢响应速度。我实际项目中用过的方案是滑动平均滤波适合周期性采样中值滤波适合脉冲噪声以及一阶低通滤波适合控制系统实时性要求高的场景。一阶低通形如y y_prev alpha * (x - y_prev)alpha越小滤波越强但响应越慢需要根据信号带宽来定。滤波参数不能随便拍脑袋。如果信号本身变化比较快把alpha设得太小真实信号也会被拉平表现为系统响应迟钝。我建议先用串口或调试器把原始波形导出来观察噪声频率和幅度再决定用哪种滤波以及滤波深度。用数据说话远比“多采几次平均一下”更可靠。如果ADC带有硬件均值模式或多次采样功能优先使用硬件而不是软件多次采样。硬件均值不会占用CPU而且内部采样节奏更稳定效果通常优于同等次数的软件平均。但是要注意硬件均值相当于降低了有效采样率因为一次结果由多次采样合成换算控制周期时要算清楚。6.2 过采样加求均值如何高效提升有效分辨率过采样技术可以用来提升有效分辨率每多采4倍样本按理论可以多获得1位有效分辨率但前提是噪声满足白噪声特性。实际操作中把同一通道连续采集N次后求和并右移等效于平均增益12位ADC理论上可以通过过采样得到更高的“有效位数”。不过这个提升上限受硬件噪声水平限制不是无脑多采就能无限提高一般做到14到15位有效已经不错。我习惯的做法是对同一通道连续采集16次或32次累加后右移位数得到一组降噪结果。关键是在累加时使用uint32_t变量防止16位数据累加多次后溢出。右移位数根据采集次数决定比如采4次右移2位采16次右移4位。这样的过采样本质上就是平均但你不必手动维护一堆数组累加器写法更省内存。过采样最大的副作用是终端的有效采样率降低。假设ADC实际采样率100kSPS过采样16次后有效输出只有6.25kSPS如果你的控制环路需要10kSPS更新率就满足不了。所以过采样更适合低频高精度测量不适合高速控制。我在一个温度采集项目里对低频信号做过采样效果非常明显原先抖动几个码值过采样后稳定在一两个码值以内。6.3 测量低电压小信号时的硬件配合软件再强硬件前端太差也白搭。对于0到几十毫伏级别的小信号GD32的ADC输入范围通常无法直接覆盖需要先经过放大电路把信号调理到0到3.3V区间。这时候运放的供电、偏置电压和PCB走线都有讲究反馈电阻要选低温漂的走线尽量短模拟地要单点连接主地避免数字地电流干扰。在软件侧可以配合查表法或线性校准来补偿放大电路的增益误差和零点偏移。标准做法是先输入两个已知电压测量对应的ADC码值用两点求斜率得到实际换算关系。把这组参数固化在设备校准流程里生产时每台设备单独校准一次能显著提高批量一致性。ADC本身的线性度在GD32这类MCU里通常足够好真正的误差大头往往来自外部调理电路。还有一个很实用的小技巧如果输入信号在ADC量程的很小一段比如0到0.1V可以直接把参考电压选成1.2V或2.5V档如果芯片支持等效于放大了输入信号的分辨率。这样不需要外部运放也能获得更精细的量化结果。但要注意参考电压降低后参考路径上噪声对结果的影响也会变大。6.4 一个可复用的ADC配置检查清单项目做到后期调ADC时我基本不看资料只对着自己整理的检查清单逐项过一遍。这个清单包括GPIO是否配置为模拟输入、ADC时钟是否超限、采样时间是否和信号源阻抗匹配、校准是否在使能之前完成、使能后是否触发了转换、等待的标志位是否清干净、DMA传输数量是否等于通道数、DMA宽度是否为16位、数据数组是否被其他任务意外改写、参考电压实测值是否参与换算。这套检查顺序看起来简单实际排查时价值很高。遇到过几次同事来求助都是因为“GPIO忘了配成模拟模式”或者“校准放在使能之后”这种低级问题。只要按顺序逐项确认很多谜之现象会立刻显出原形。你也可以照着这个逻辑做一份自己的检查清单后续维护多通道采集代码时能省下不少时间。如果需要更高精度应用还可以考虑环境中电磁干扰的影响比如电机驱动、继电器开关、电源适配器都会对ADC采样造成不小的干扰。这时候采样时机要尽量避开发射源的切换时刻或在硬件上加入RC低通滤波器。RC滤波器的截止频率要选得比信号最高频率高同时要远低于ADC采样率否则信号会被削掉。软件滤波永远替代不了合理的硬件滤波两者是配合关系不是二选一。最后再分享一点个人体会GD32的ADC外设本身并不神秘学习它最有效的方式就是拿一块板子把单通道、多通道DMA、校准和换算全部亲手写一遍踩过几个坑之后就会形成肌肉记忆。我后来移植到其他芯片平台也把这套“先算采样率、再配硬件、最后做校准和滤波”的思路带过去了基本没再因为ADC配置问题翻过车。希望这篇笔记也能帮你少走一点弯路。
阅读完成 · 觉得有帮助?
咨询建站