ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

GD32F407 ADC多通道DMA采集实战:从配置到滤波的避坑指南

GD32F407 ADC多通道DMA采集实战:从配置到滤波的避坑指南 简介这是一份基于GD32F407微控制器的ADC与DMA应用开发资源包面向嵌入式开发者和初学者重点演示库函数环境下从ADC初始化、通道配置到DMA自动传输的完整实现思路。包内共256个文件以h头文件与c源码文件为主另有s启动文件、hex/bin固件、uvproj工程文件及bat辅助脚本等整体约1.67MB能直接用于Keil工程参考与编译烧录。已有2018人学习下载。资源覆盖单次转换、连续转换、扫描模式以及中断与DMA结合等典型场景并包含多通道采样与高速数据采集相关代码结构说明便于对照芯片手册快速上手也可为后续调参和功能扩展提供基础模板。 GD32F407的ADC开发我断断续续踩了一周坑才把多通道DMA采集这条路彻底走通。手头这个项目要同时采集电池电压、两路霍尔电流和一路NTC温度四路模拟量全部走GD32F407片内ADC数据要持续进入DMA通道不能漏也不能错位。前后折腾下来从ADC时钟配置到DMA缓冲布局再到滤波算法选型每一环都有不少值得记录的地方。这篇就当作一次完整的经验复盘如果你也在用GD32F407或者同系列的其他Cortex-M4片子做多通道模拟量采集应该能帮你少走很多弯路。1. 项目整体认知与方案选型逻辑1.1 GD32F407的ADC资源到底够不够用先说结论在绝大多数工业控制和板级监测场景里GD32F407片内ADC的资源是够用的而且是相当够用。这颗片子内部集成3个独立的SAR ADC也就是逐次逼近寄存器型ADC。每个ADC支持多个外部输入通道12位分辨率下理论采样率能达到Msps级别供电范围2.4V到3.6V整体规格对标的是同类Cortex-M4平台。与传统的“单ADC外设”策略不同3个ADC可以独立工作也可以组合成同步采样模式像电机控制里需要同时抓取相电压和相电流的场景这种多ADC结构就很实用。我这次的项目没有用到三ADC同步模式场景是连续监测四路缓变信号所以只用了ADC0、规则通道组扫描模式加DMA传输。这里要注意的是GD32F407的ADC0、ADC1、ADC2虽然都叫ADC但对应的引脚复用和DMA请求通道并不完全一样选型阶段就要把引脚分配和DMA映射提前确认好不然后期改板子代价很大。很多人习惯性觉得12位分辨率不够用实际上要看怎么定义“不够用”。对于电池电压监测、温度采样、霍尔电流反馈这类信号12位配合合理的硬件设计和软件滤波完全能扛住。真正需要上外部高精度ADC的时候往往是称重、精密仪表、微弱信号检测这些场景后面会专门展开说。1.2 为什么我坚持用DMA而不是轮询读取多通道ADC采集中轮询方式不是不能用是性价比太低。如果不用DMA每次转换完成后CPU都得去读ADC数据寄存器四通道扫一轮要读四次每次还要检查对应的标志位。当采样频率要求不高比如每秒几十次轮询当然无所谓但一旦把采样率提上去或者CPU还要同时处理通信、控制算法、显示刷新轮询就会变成一场灾难——哪个任务先执行、中断什么时候来、数据会不会覆盖都是麻烦事。DMA的玩法是ADC每转换完一个通道数据经过硬件自动搬到内存里整个过程不用CPU参与。你要做的只是初始化时配好外设地址、内存地址、数据宽度和传输数量之后CPU可以专心干别的采集完了再去看内存里的数据。这样既降低了CPU占用又保证了数据连续性和时序稳定性。在GD32F407上ADC0的DMA请求对应DMA0的特定通道具体映射可以在参考手册的DMA请求映射表里查到。配置时要注意打开ADC的DMA模式使能位这一步很多人容易漏——ADC本身不发DMA请求DMA通道配得再对也没用。2. 核心配置流程与原理拆解2.1 ADC时钟采样速度上限从哪来ADC的时钟配置是整个采样系统的基础。GD32F407的ADC时钟来源于APB2也就是PCLK2需要经过RCU里的分频器后才能给ADC使用。手册推荐ADC时钟不要超过40MHz这个上限不是拍脑袋定的而是SAR ADC内部比较器、电容网络在过高工作频率下会变得不稳定转换误码率上升。假设你的系统里PCLK2配的是100MHz想要把ADC时钟压到25MHz那就选4分频。计算公式很简单f_ADC f_PCLK2 / 分频系数再往后推一步单通道完成一次完整转换需要的时间是t_CONV (采样周期数 12) / f_ADC这个“12”对应12位分辨率下逐次逼近所需的12个ADC时钟周期。如果是10位分辨率就换成108位就换8。降低分辨率确实能提升转换速度但代价是精度下降实际项目中要衡量好。我这次在项目里用的是ADC_SAMPLETIME_28POINT5这样一档采样时间配合25MHz ADC时钟单个通道一次转换约1.62微秒。四通道扫描一轮总耗时约6.48微秒换算下来差不多154kSPS。对电池电压、温度这种缓变信号来说这个速度已经非常宽裕了。需要注意如果你把系统主频改高APB2对应变快ADC时钟分频也要重新配否则ADC会跑超标。改完时钟记得回头确认一下这个数。2.2 采样时间不是越大越好源阻抗与建立时间的博弈采样时间这个参数新手容易走极端。一种人全程用最小采样时间觉得越快越好另一种人不管通道差异一律拉到最大觉得越稳越好。实际上正确做法是根据信号源阻抗来选。SAR ADC在采样阶段会把内部采样电容接到输入引脚上信号源需要在采样时间内把采样电容充到和输入电压足够接近的程度。如果信号源输出阻抗太高比如几万欧姆而采样时间又特别短电容根本来不及充满最后的转换结果就会偏低而且误差随信号源阻抗变化不是固定偏移很难校准掉。这个道理和生活里的接水有点类似。水龙头开得再大如果管子前面有根很细的水管限制流量短时间里杯子就是接不满。采样时间就是“接水时间”信号源内阻就是那根细水管。GD32F407的采样时间有几档可选从几个时钟周期到两百多个时钟周期。项目里四路通道共用一个采样时间配置扫描模式下规则组只能统一配置所以要以阻抗最高的那一路为标准。我这块的NTC分压网络源阻抗在十几千欧级别所以统一选了28.5个时钟周期保证温度的采样精度同时其他三路也完全够用。如果你的系统里某一路信号源阻抗特别高更稳妥的办法是在前端加一级运放跟随器把阻抗降下来这样才能用更短的采样时间跑出更高采样率。2.3 配置顺序和几个容易漏的寄存器位ADC初始化的代码逻辑不算复杂但配置顺序和细节位很容易出错。我自己的习惯顺序是先配置GPIO为模拟模式再配DMA最后才是ADC外设本身最后启动DMA通道。GPIO配置时要把引脚设为模拟模式并且关闭上下拉。曾经遇到过用默认的GPIO配置去初始化ADC引脚结果采样值一路偏高的怪事最后查到是内部下拉电阻把分压网络拉偏了。ADC引脚的上下拉状态对测量结果的影响远比想象中大。ADC使能之后、正式启动转换之前务必做一次校准。GD32F407的库函数里有对应的校准接口校准完成的标志位要检查。另外建议在启动规则转换之前做一次“伪转换”把第一次转换结果丢弃。这个第一次结果在很多芯片上都会偏可能是内部电路尚未完全稳定也可能是电容初始状态引起的项目里直接丢弃最省心。DMA那边还有几个关键位外设地址必须指向ADC数据寄存器内存地址指向数组首地址传输方向是外设到内存数据宽度是16位12位数据在16位寄存器里右对齐内存地址自增但外设地址不自增。最后别忘了开循环模式——多通道扫描加DMA的真正意义是让数据源源不断地进内存而不是传完一轮就停。3. 完整实操代码配置、采集与滤波3.1 基础初始化代码代码是基于GD32F4xx标准外设库写的不同版本的库函数名可能有差异重点看逻辑。四路通道规划为PA0到PA3对应ADC0的通道0到通道3。#include gd32f4xx.h #define ADC_CH_NUM 4 #define ADC_SAMPLE_NUM 8 uint16_t adc_raw[ADC_CH_NUM * ADC_SAMPLE_NUM]; uint16_t adc_result[ADC_CH_NUM]; static void adc_gpio_config(void) { rcu_periph_clock_enable(RCU_GPIOA); // 四路模拟输入注意关闭上下拉 gpio_mode_set(GPIOA, GPIO_MODE_ANALOG, GPIO_PUPD_NONE, GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3); }GPIO这一层没有太多花样关键就是模拟模式加无上下拉。有些项目里GPIO引脚还承担其他复用功能要先查手册确认ADC输入和复用功能不冲突。DMA初始化这部分是整个链路的骨架。DMA0的通道映射取决于ADC外设ADC0在GD32F407上对应DMA0的某个通道实际以参考手册为准。static void adc_dma_config(void) { dma_parameter_struct dma_init_struct; rcu_periph_clock_enable(RCU_DMA0); dma_deinit(DMA0, DMA_CH0); dma_struct_para_init(dma_init_struct); dma_init_struct.direction DMA_PERIPHERAL_TO_MEMORY; dma_init_struct.memory_addr (uint32_t)adc_raw; dma_init_struct.memory_inc DMA_MEMORY_INCREASE_ENABLE; dma_init_struct.memory_width DMA_MEMORY_WIDTH_16BIT; dma_init_struct.number ADC_CH_NUM * ADC_SAMPLE_NUM; dma_init_struct.periph_addr (uint32_t)(ADC_RDATA(ADC0)); dma_init_struct.periph_inc DMA_PERIPH_INCREASE_DISABLE; dma_init_struct.periph_width DMA_PERIPHERAL_WIDTH_16BIT; dma_init_struct.priority DMA_PRIORITY_HIGH; dma_init(DMA0, DMA_CH0, dma_init_struct); dma_circulation_enable(DMA0, DMA_CH0); dma_channel_enable(DMA0, DMA_CH0); }DMA缓冲区大小我设成了通道数乘以每通道采样次数。如果你想每路只保留最新的一个数据number就等于通道数想对每路做多次采样再滤波number就按倍数扩展。循环模式下DMA传输完一批会自动从头开始缓冲区会被周期性刷新这是后续滤波策略的基础。3.2 ADC初始化与DMA联动ADC部分代码不长却最容易在细节上翻车。我把它拆开逐段说。static void adc_config(void) { uint8_t i; rcu_periph_clock_enable(RCU_ADC0); rcu_adc_clock_config(RCU_ADCCK_APB2_DIV4); adc_clock_enable(ADC0); adc_resolution_config(ADC0, ADC_RESOLUTION_12B); adc_data_alignment_config(ADC0, ADC_DATAALIGN_RIGHT); adc_channel_length_config(ADC0, ADC_ROUTINE_CHANNEL, ADC_CH_NUM); for (i 0; i ADC_CH_NUM; i) { adc_routine_channel_config(ADC0, i, i 1, ADC_SAMPLETIME_28POINT5); } adc_external_trigger_source_config(ADC0, ADC_EXT_TRIGGER_SOURCE_NONE); adc_external_trigger_config(ADC0, ADC_EXT_TRIGGER_SOURCE_NONE, DISABLE); adc_dma_mode_enable(ADC0); adc_enable(ADC0); // 伪转换丢弃第一次结果 adc_software_trigger_enable(ADC0); while (SET ! adc_flag_get(ADC0, ADC_FLAG_EOC)); (void)adc_routine_data_read(ADC0); adc_calibration_enable(ADC0); adc_software_trigger_enable(ADC0); }先说ADC时钟。RCU_ADCCK_APB2_DIV4的含义是ADC时钟取APB2的4分频如果PCLK2配置为100MHz那么ADC时钟就是25MHz处于安全范围内。我见过有人在这里选了2分频ADC时钟直接顶到接近50MHz采样值倒是没崩但整体精度明显下降后来降到4分频才恢复正常。如果怀疑系统里ADC精度欠佳第一步就该检查这个分频系数。然后是触发方式。这里用的软件触发也就是调用软件触发函数后ADC立即开始按规则组顺序扫描。如果项目里需要精准定时采样可以改用定时器触发配合PWM事件实现电机控制里的电流同步采样。需要同步采样的场景我建议去研究一下双ADC同步模式这属于另一个话题但原理和当前这套配置有很多共通之处。在校准之前先做一次伪转换是为了让ADC内部状态稳定下来。这个经验是实际调出来的当时跳过这一步系统上电后第一次DMA搬运的数据里第一路的值明显偏离正常范围其余路都正常后来加上伪转换就消失了。3.3 数据读取布局与滤波算法DMA数据到达内存后读取布局是个容易胡乱操作的地方。我的缓冲区是adc_raw[ADC_CH_NUM * ADC_SAMPLE_NUM]每路采8次那么内存布局是前四个数据对应通道0到3的第一次采样接下来四个对应通道0到3的第二次采样依此类推。所以要提取第i路的所有采样值应该按下面这种方式跳着取void adc_process(void) { uint8_t i, j; uint32_t sum[ADC_CH_NUM] {0}; for (i 0; i ADC_CH_NUM; i) { for (j 0; j ADC_SAMPLE_NUM; j) { sum[i] adc_raw[j * ADC_CH_NUM i]; } adc_result[i] (uint16_t)(sum[i] / ADC_SAMPLE_NUM); } }这种“隔行取数”的方式如果没留意会很容易错位。我最初写成了adc_raw[i * ADC_SAMPLE_NUM j]这种连续取法结果通道0的数据混进了通道1花了半天才发现是缓冲区索引错了。简单求平均对随机噪声有一定抑制效果但ADC数据里偶尔会有明显毛刺比如开关电源开关瞬间耦合进来的尖峰。这时候平均值法就会被一个毛刺拉偏不少。更稳的做法是中值加均值组合滤波先取中值滤掉毛刺再做均值抑制随机噪声。uint16_t adc_median_filter(uint16_t *buf, uint8_t len) { uint8_t i, j; uint16_t tmp; // 冒泡排序只取中间值 for (i 0; i len - 1; i) { for (j 0; j len - 1 - i; j) { if (buf[j] buf[j 1]) { tmp buf[j]; buf[j] buf[j 1]; buf[j 1] tmp; } } } return buf[len / 2]; }如果信号本身变化较快需要平滑但不希望延迟太大一阶低通滤波是更常用的方案。代码只有一行核心逻辑float adc_smooth 0.0f; #define ALPHA 0.3f adc_smooth ALPHA * (float)adc_result[0] (1.0f - ALPHA) * adc_smooth;ALPHA越小越平滑但响应也越迟钝。我自己调试时常用0.2起步如果发现波形太毛糙就把ALPHA继续调小如果发现信号跟不上真实变化就适当加大。这个值没有标准答案跟你的采样速率和信号变化速度都有关系只能现场试。4. 常见问题排查以及和外部ADC芯片的选型边界4.1 采样值波动的排查思路ADC采集值波动是这类项目里最常见的返工原因。排查顺序很重要我一般是按照“硬件干扰、参考电压、采样时间、软件滤波”这个顺序来的。先看硬件。ADC引脚的走线是否过长、有没有靠近开关管或电感、模拟地和数字地是否干净。很多ADC噪声不是来自ADC本身而是来自系统里其他高频开关信号通过地平面和引脚耦合进了模拟通道。如果VDDA上的纹波大参考电压就不稳ADC结果自然波动。给VDDA加磁珠加去耦电容是成本最低也最有效的做法。再看软件配置。采样时间设得太短信号源阻抗又高结果就会偏低而且抖动。逐一检查源阻抗和采样时间的匹配关系很多“波动大”的问题出在这里。还有一种情况是DMA一直写内存主循环一直读内存两边没有同步机制读者读到一半的数据被写者覆盖看起来就像随机跳动。这种问题可以在主循环里加临界区保护或者通过DMA半满中断来同步处理时机。4.2 DMA多通道错位的经典坑多通道DMA数据错位基本是两个原因一个是缓冲区索引布局写错另一个是伪转换没做。前者靠仔细核对代码解决后者靠在上电后丢弃第一次转换结果解决。还有一个容易踩的坑是DMA传输数量配置错误。如果number设置成通道数而不是通道数乘以每通道采样次数数组后面的数据全是垃圾。这个地方建议写一个月级或者宏定义不要直接裸写数字后期维护能少掉不少头发。DMA传输完成中断里处理数据也是一种思路。不过要注意如果采样频率高、中断处理逻辑重CPU时间会被吃掉不少反而失去了DMA释放CPU的初衷。我倾向于让DMA循环跑着主循环定时去消费数据这样各干各的思路清晰。4.3 什么时候该换外部ADC从HX711到MCP3561内部ADC虽然配置简单、开发效率高但精度上限摆在那里。遇到三种情况我就会考虑外接ADC芯片一是分辨率需要超过12位比如称重、精密应变片测量二是需要一个独立的、稳定的参考电压源三是信号很微弱需要ADC本身具备较高的信噪比和可编程增益。称重和应变片应用里HX711和ADS1232用得最多都是24位Delta-Sigma ADC带有内置增益放大器专为桥式传感器设计。HX711的软件驱动并不复杂而且价格低很多电子秤方案都用它ADS1232比HX711的噪声特性更稳一些适合追求更低漂移的场景。MCP3561属于更高性能的系列同样是24位但可配置性更强各种滤波模式和采样率设置更灵活不过驱动复杂度也上去了。另外GD32F407这类芯片的参考电压通常是直接接 VDDA。如果你的VDDA噪声大又没有独立参考源那么即便内部ADC本身性能不差最终指标也会被参考电压拖累。这种情况下要么在电源设计上多下功夫要么直接上一颗带内部参考或者外部参考输入的高精度外部ADC芯片。4.4 三点校准与精度标定的实际经验最后提一下标定。如果要做绝对精度测量12位ADC的出厂增益误差和偏移误差需要软件校准。最常见的做法是两点校准采集两个标准电压算出增益和偏移然后用线性映射修正。这种方案适合误差基本线性的系统。但在较高精度要求下我会推荐三点校准。两点校准假设误差是一条直线但实际ADC在低端、中段、高端的线性度往往不完全一致。用三个标准点把量程分成两段分别做分段线性映射能明显改善中段的测量误差。校准流程也不复杂准备好三个标准电压源分别采集对应的ADC原始值记录三组 (标准电压, ADC码值) 数据运行时先判断当前ADC码值落在哪段区间再用对应区间的斜率截距做换算。校准这件事在很多项目里被忽视但当你把系统联调后发现采样误差超差回头看最有效的往往是校准流程没做。片内ADC不会像精密仪器那样自己特别准标定是量产阶段绕不开的环节。5. 写在后面的调试手记做这个项目的过程中最大的感受是ADC看似简单真正把它吃透却要把时钟树、DMA映射、扫描时序、滤波策略这些完全不同的模块串起来。每一个环节单独看都不难组合在一起就容易出各种意想不到的问题。我自己的习惯是每改一个配置就固定验证一个指标不要一次性改三四处再去看结果否则出了问题根本不知道是哪一步导致的。最后再分享一个细节调试初期建议先接一路信号用串口把原始ADC值和滤波后的值都打出来确认单通道完全稳定后再加其他通道。多通道问题本质上是单通道问题的叠加单通道没调好之前直接上多通道只会让排查难度成倍增长。这套流程虽然看起来慢但实际是最快到位的路径。本文还有配套的精品资源点击获取
返回列表