尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

ADC与DMA协同工作:高效电压采样方案的原理与实践

ADC与DMA协同工作:高效电压采样方案的原理与实践 做嵌入式这几年ADC采样这件事本身不算难难的是怎么把采样做得既快又不拖累主流程。我最近在调一块基于GD32的电源控制板三相电压采样要求每个工频周期都能拿到足够多的采样点来做有效值计算CPU还得同时跑PID控制算法和通信协议。最开始我用中断逐通道读取ADC结果通道一多、采样频率一高中断服务函数频繁抢占主流程控制波形直接出现抖动实时性完全没法保证。后来把ADC和DMA组合起来干活终于把这块硬骨头啃下来了采样效率上去了CPU占用大幅下降数据也更稳定。这篇博客就把这套基于ADC-DMA协同工作的高效电压采样方案从硬件设计到软件配置再到排坑实录完整梳理一遍。1. 为什么非要把ADC和DMA绑在一起1.1 一次ADC采样的完整过程很多人写ADC程序时只是对着例程抄对内部机制并不清楚。先从最基本的讲起。单片机上最常见的ADC是逐次逼近型SAR核心是一个比较器、一个DAC网络和一个逐次逼近寄存器。可以说它就是在猜输入电压到底是多少每次猜一位用二分法逐步逼近。12位ADC就是猜12次从最高位到最低位每猜一次就把结果寄存器里对应的位置0或置1。到12次猜完结果就是一个和输入电压成正比的数字量。一次完整的ADC转换分为两个阶段采样阶段和转换阶段。采样阶段里内部采样开关闭合采样电容通过引脚外部阻抗充电充电电压要尽可能接近输入电压。转换阶段开关断开片上DAC和比较器开始逐位比较。这两个阶段都需要时间时间长短由ADC时钟和配置的采样周期决定。这里有个关键概念叫采样周期。ADC时钟频率定了之后每次转换需要的时间 采样周期 转换周期/ ADC时钟频率。比如STM32F103的ADC时钟最高14MHz配置采样周期为13.5个周期加上12.5个转换周期那么一次转换要26个ADC时钟周期换算下来大约是1.86微秒。也就是说理论上每秒能采样大约50万次。但这是纯ADC的极限不涉及CPU读数据的时间。还有一点必须注意ADC时钟频率不是越高越好。因为SAR架构里比较器需要一定建立时间时钟太快会导致比较结果不准。所以芯片手册都会给出ADC时钟上限比如F103是14MHzGD32F1系列是12MHz。配置时钟分频时要保证实际ADC时钟不超过上限否则采样精度打折。1.2 没有DMA时CPU都在忙什么中断方式读取ADC结果流程是这样的ADC转换完成硬件置位EOC标志触发中断CPU跳进中断服务函数从ADC数据寄存器把结果读出来存入数组然后清标志、恢复现场。每个通道每次转换都要重复一遍通道越多中断越频繁。问题就出在这个每转换一次就打断一次上。假设我用3通道扫描采样每个通道采样周期配置得比较短整体采样率达到每秒钟20万次。那意味着CPU每5微秒就要被打断一次中断服务函数就算只做读寄存器、存数组、清标志这几件事也要二三十个时钟周期。CPU主频72MHz的话就是每5微秒里有0.4微秒在响应中断大约8%的CPU时间耗在搬数据上。再加上保存现场和恢复现场的开销实际可能超过15%。如果这时候主程序还在跑PID运算、跑Modbus协议、处理按键显示频繁的中断抢占会让所有任务的实时性变差。更难受的是采样频率越高中断越密集CPU越忙最后形成一个为了采样而牺牲控制性能的死循环。这就是我一开始遇到的状况中断一多控制波形都出毛刺了。1.3 DMA介入后带来的变化DMADirect Memory Access直接存储器访问本质是外设和内存之间的搬运工。CPU只需告诉DMA控制器把ADC数据寄存器里的内容每次转换完成后自动搬到这个数组里搬完N次通知我一下剩下的事就完全交给DMA硬件。DMA最大的价值不是快而是不打扰。ADC转换完成信号直接触发DMA搬运请求数据从外设数据寄存器搬到内存缓冲区整个过程不需要CPU参与也不会打断CPU执行指令。CPU只在缓冲区存储到设定长度或指定数量时收到一次中断可以一次性批量处理所有数据。一个很直观的对比中断方式每采样一个点打断CPU一次采样10万个点就要打断10万次用DMA方式我可以设置缓冲区长度为1000采样10万个点只用进中断100次CPU被打断的频次减少了两个数量级。而且DMA支持循环模式缓冲区存满后自动从头开始覆盖写相当于一个硬件实现的环形缓冲区软件永远能拿到最近一段时间的采样数据。我用一个生活化的类比来解释中断方式相当于领导每签一个文件都要秘书跑进办公室汇报一次一天跑几百趟DMA相当于秘书把文件攒成一摞全部签完再拿进去盖章效率完全不一样。2. 动手前的硬件细节采样电路和PCB布局软件写得再漂亮硬件底子不行采样数据照样是一堆废数。ADC-DMA协同只能解决数据搬运效率的问题解决不了采样值本身不准的问题。这一节把硬件上最容易踩坑的点逐个说清楚。2.1 电压采样电路的基本形态最常用的电压采样电路就是电阻分压加RC滤波。被采样电压经过两个电阻分压把幅度缩放到ADC的输入范围内再经过一个RC低通滤波器滤除高频噪声最后送入ADC引脚。分压电阻取值要根据实际电压范围和ADC参考电压计算。举一个最典型的例子被测电压范围0~24VADC参考电压3.3V那么分压比要保证最高24V输入时ADC脚电压不超过3.3V。为了留一点安全余量按最高25V来算分压比就是25V:3.3V也就是大约7.58:1。分压电阻的选择有讲究。阻值太小比如几百欧分压电路自己就会消耗较大电流发热明显还会拉低被测信号阻值太大比如几兆欧配合后级RC和ADC内部采样电容又会让信号建立时间过长。工程上常用几十K到几百K级别。比如我用的是47K和6.8K组合两者串联后约53.8K分压比为47/(476.8)≈0.873等下分压比算反了。输出是下端电阻分压Vout Vin × R2/(R1R2)R2是下端电阻。如果R147K、R26.8K分压比是6.8/53.8≈0.126最高输入24V时输出约3.03V正好符合3.3V系统。分压电路之后必须加RC滤波。这个电容的作用是和分压电阻共同构成低通滤波器截止频率按f1/(2πRC)计算。假设R等效值约6K分压网络从ADC引脚看进去的等效电阻约为R1//R2配一个100nF电容截止频率大约265kHz。如果配1nF电容截止频率约26.5kHz。具体怎么选看被测信号的频率成分。采样工频50Hz的电压截止频率设到1kHz就够了没必要太高太高反而把高频毛刺都放进来。2.2 采样周期、源阻抗与采样电容的匹配ADC内部采样电容在采样阶段必须充电到接近输入电压充电时间由采样周期决定。如果外部源阻抗太大充电时间常数RC很大采样时间又不够电容上的电压还没充到位采样就结束了转换结果必然偏低。这是很多人遇到ADC测电压总是偏低的重要原因。芯片手册一般会给一个最大源阻抗值。以常见MCU为例ADC采样时间配置为1.5个ADC时钟周期时允许的外部源阻抗通常只有1~2K欧姆改到13.5个周期或更长的采样时间允许的源阻抗可以放宽到几十K欧姆。这里有个取舍想提高采样率就要缩短采样时间缩短采样时间就要求外部电路阻抗必须足够低否则误差变大。我的实践经验是在采样率允许的范围内尽量把采样周期配得大一点。比如GD32E230的ADC我配置ADC时钟为8MHz采样周期配到最大换来的是更好的精度和稳定性。毕竟DMA已经把搬运数据的时间省下来了采样周期稍微长那么零点几个微秒对整体吞吐率的影响远小于数据不准带来的麻烦。RC滤波中串联电阻的存在还会加剧充电不足的问题。所以在选择RC参数时不能只看滤波效果还要评估它对ADC采样精度的影响。一个相对保险的做法是分压网络之后接一个小阻值电阻比如100欧姆再接对地电容这样既保持了滤波功能又把ADC引脚看到的源阻抗压低了。这也是很多工业板卡上常见的做法。2.3 PCB布局的3个要点绕开时钟抖动和电源噪声PCB布局直接决定ADC采样数据的实际质量。这一块我交过不少学费总结出3个最重要的布局要点。第一个要点是模拟区域和数字区域必须做到物理隔离。ADC输入通道、参考电压、模拟电源属于模拟信号路径晶振、时钟线、数字总线属于数字信号路径。在空间允许的情况下把ADC输入走线和对地的参考平面放在一起数字信号线尽量远离模拟输入线。如果必须在同一层走模拟线与数字线之间要保持足够间距最好中间插入地线做隔离。这里有个常见误解以为整板铺铜就万事大吉实际上数字信号回流会在地平面上形成噪声如果ADC输入走线横跨数字噪声区域采样数据很容易出现随机跳动。第二个要点是ADC参考电压引脚VREF必须单独去耦。参考电压直接决定ADC的量化基准基准上有纹波量化结果就会跟着波动。VREF引脚附近要放两个去耦电容典型组合是10uF钽电容并联100nF陶瓷电容电容地端用独立的过孔直接打到模拟地层不要和数字电路共用地过孔。很多板的ADC数据漂移查到最后就是VREF去耦电容离引脚太远或者地过孔路径太长参考电压上叠加了来自数字电路的噪声。第三个要点是ADC输入走线要短、直、粗并做好包地处理。ADC输入属于高阻敏感节点走线太长就变成了一根天线容易耦合周围时钟线的噪声。时钟信号跳变沿通过寄生电容和互感耦合到ADC输入线上会导致采样值在特定时间点出现规律性跳变这种问题用软件滤波都很难完全消掉。所以布局时尽可能把采样电路靠近MCU放置输入走线尽量短两侧铺地铜并打上地过孔形成屏蔽相当于给模拟信号做了一件防护服。另外说一个和电源噪声相关的小细节模拟电源AVDD和数字电源DVDD通常会用磁珠或小电感隔离但很多人只在电源入口加了一个磁珠忽略了MCU模拟电源引脚本身也要单独去耦。AVDD引脚附近至少放一个100nF陶瓷电容到模拟地这是保证ADC内部模拟电路工作稳定的底线。3. 配置实操STM32/GD32的ADCDMA完整流程硬件准备好之后软件这一块的配置路径就清晰了。STM32和GD32的ADC/DMA外设框架基本类似我以GD32E230为例这套流程在STM32F1、F4上也通用原理是一样的。3.1 时钟、引脚和基础参数的确定第一步是配置ADC时钟。ADC时钟来自APB2时钟分频不能直接等于系统时钟。以GD32E230跑72MHz系统时钟为例ADC时钟分频系数我一般选6到8得到9到12MHz的ADC时钟既不超过芯片上限又能保证采样速度。第二步配置GPIO引脚为模拟输入模式。这里有一个容易忽略的坑引脚模式必须设为模拟输入而不是浮空输入。普通输入模式下引脚内部有施密特触发器会引入额外电流和噪声影响模拟信号精度。设置成模拟输入后施密特触发器被旁路引脚呈现高阻态适合接收模拟信号。第三步配置ADC工作模式。我需要做三相电压采样三个通道要持续循环采集所以开启扫描模式和连续转换模式。扫描模式下ADC会自动从规则通道组第一个通道依次转换到最后一个连续转换模式让ADC转换完一轮后自动开始下一轮不需要软件反复触发。这里要特别注意连续请求这个配置。GD32的ADC连续转换模式和DMA的Continuous Requests是两个不同层次的配置。ADC侧要开启连续转换DMA侧也要把DMA的循环模式Circular Mode或连续请求模式打开两个配合好才能实现自动转、自动搬、不断流的效果。很多人配置完发现只搬了一次数据就停了多半就是DMA侧没有开循环模式。3.2 多通道扫描的DMA缓冲区设计DMA缓冲区的大小和ADC通道数直接相关这一点必须算清楚。假设我用ADC的通道0、通道1、通道2采样三相电压所有通道都配置为规则通道扫描模式打开。DMA会按照通道顺序把转换结果依次写入缓冲区。也就是说缓冲区第一个单元存通道0的结果第二个存通道1的结果第三个存通道2的结果完成后触发一次DMA传输完成事件如果配置了循环模式下一轮又开始从缓冲区第一个单元覆盖写。缓冲区大小怎么定直观做法是设置成通道数×2字节12位分辨率需要2字节存储。如果开了DMA循环模式还可以把缓冲区设置成更长的倍数比如每个通道对应缓冲区放4个元素相当于用DMA自动做了4轮采样这样软件读一次缓冲区就能拿到4组三相电压数据做均值滤波非常方便。缓冲区用uint16_t数组声明因为12位ADC结果在16位宽度内存储。配置DMA时数据宽度必须和外设寄存器、内存地址匹配。ADC数据寄存器是16位所以我用半字Half Word传输。如果配置成字节传输数据会被截断采样值明显错误。这一点在从8位单片机切换到32位平台时特别容易被坑。3.3 启动顺序与代码骨架完整代码骨架大致如下关键步骤都加了注释。// 缓冲区定义3通道每通道缓存4轮 #define ADC_CH_NUM 3 #define ADC_BUF_DEPTH 4 uint16_t adc_buf[ADC_CH_NUM * ADC_BUF_DEPTH]; volatile uint8_t adc_dma_done 0; void adc_dma_init(void) { // 1. GPIO配置为模拟输入 gpio_mode_set(GPIOA, GPIO_MODE_ANALOG, GPIO_PUPD_NONE, GPIO_PIN_0); gpio_mode_set(GPIOA, GPIO_MODE_ANALOG, GPIO_PUPD_NONE, GPIO_PIN_1); gpio_mode_set(GPIOA, GPIO_MODE_ANALOG, GPIO_PUPD_NONE, GPIO_PIN_2); // 2. ADC配置扫描模式、连续转换、软件触发 adc_clock_config(ADC_ADCCK_PCLK2_DIV_8); adc_conversion_mode_enable(ADC_SCAN_MODE); adc_special_function_config(ADC_CONTINUOUS_MODE, ENABLE); adc_data_alignment_config(ADC_DATAALIGN_RIGHT); adc_channel_length_config(ADC_REGULAR_CHANNEL, ADC_CH_NUM); // 3. 规则通道配置采样周期尽量长一些 adc_regular_channel_config(0, ADC_CHANNEL_0, ADC_SAMPLETIME_55POINT5); adc_regular_channel_config(1, ADC_CHANNEL_1, ADC_SAMPLETIME_55POINT5); adc_regular_channel_config(2, ADC_CHANNEL_2, ADC_SAMPLETIME_55POINT5); // 4. DMA配置 dma_parameter_struct dma_para; dma_deinit(DMA0, DMA_CH0); dma_para.direction DMA_PERIPHERAL_TO_MEMORY; dma_para.periph_addr (uint32_t)(ADC_RDATA); // ADC数据寄存器地址 dma_para.mem_addr (uint32_t)adc_buf; // 内存缓冲区地址 dma_para.periph_width DMA_PERIPHERAL_WIDTH_16BIT; // 半字 dma_para.mem_width DMA_MEMORY_WIDTH_16BIT; dma_para.number ADC_CH_NUM * ADC_BUF_DEPTH; // 传输总长度 dma_para.periph_inc DMA_PERIPH_INCREASE_DISABLE; // 外设地址不递增 dma_para.memory_inc DMA_MEMORY_INCREASE_ENABLE; // 内存地址递增 dma_para.priority DMA_PRIORITY_HIGH; dma_init(DMA0, DMA_CH0, dma_para); // 5. 开启DMA循环模式关键 dma_circulation_enable(DMA0, DMA_CH0); dma_interrupt_enable(DMA0, DMA_CH0, DMA_INT_FTF); dma_channel_enable(DMA0, DMA_CH0); // 6. 开启ADC DMA请求并启动ADC adc_dma_mode_enable(); adc_enable(); // 等ADC稳定后再触发第一次转换 delay_us(10); adc_software_trigger_enable(ADC_REGULAR_CHANNEL); } // DMA传输完成中断处理 void DMA0_Channel0_IRQHandler(void) { if (dma_interrupt_flag_get(DMA0, DMA_CH0, DMA_INT_FLAG_FTF)) { dma_interrupt_flag_clear(DMA0, DMA_CH0, DMA_INT_FLAG_FTF); adc_dma_done 1; } }主循环里拿到adc_dma_done标志后直接批量处理adc_buf数组里的数据。注意DMA是循环覆盖写的所以处理速度必须快过一轮DMA写满缓冲区的时间否则数据会被覆盖掉。这其实也是配置缓冲区深度的意义深度越大软件处理的时间窗口越宽。3.4 双缓冲思路半传输中断的妙用如果系统里ADC采样率很高软件又希望在采样的同时进行其他耗时运算可以用DMA的半传输中断Half Transfer Interrupt做双缓冲。原理很简单DMA传输前半段时软件处理后半段缓冲区DMA传输后半段时软件处理前半段缓冲区。这样缓冲区永远有满半区可以读也有空半区让DMA写。具体实现就是把缓冲区长度设置成2的整数倍开半传输中断和全传输中断两个中断分别处理前一半和后一半。这个做法我常用于高速信号采集场景比如电机驱动器里需要同时采样相电流和母线电压。对于一般的电压采样缓冲区深度调到8或者16配合全传输中断就够了不必强制上双缓冲免得代码复杂度上升。4. 采样值换算、滤波与校准DMA把一堆原始ADC值搬进内存后真正的数据处理才刚开始。原始值和真实电压之间不是简单的比例关系中间还有量纲转换、噪声抑制和校准问题。4.1 ADC原始值怎么换算成真实电压12位ADC的输出范围是0到4095也有的芯片标称0到4096这个差别后面再讲对应输入电压从0到参考电压VREF。换算公式Vmeasured ADC_raw × VREF / 4095以3.3V参考电压为例如果ADC引脚输入1V电压理论转换值是1 × 4095 / 3.3约等于1241。这就是网上常有人问的单片机ADC输入口电压为1V采样得到的值是多少的标准答案。不过要注意如果直接用满量程4096做分母结果是约1288和1241有明显差异。两种算法谁对取决于MCU手册怎么定义满量程。多数12位SAR型ADC的满量程对应值是4095因为最大输出值就是12位全1。但有些高分辨率ADC会采用满量程对应4096FS1的编码方式使用前一定要先看手册。如果是电阻分压采样的电压还需要把分压比乘回来。还是用前面的例子分压比0.126那么实际电压V_real ADC_raw × 3.3 / 4095 / 0.126也可以先算分压后电压再除以分压比。这一步不要用浮点数在每次中断里算嵌入式环境里浮点运算慢更推荐把换算系数预先算成定点数或者用浮点乘一次存好每次采样直接用乘法得出结果。4.2 软件滤波均值、滑动平均、中值的取舍ADC采样数据不可避免有噪声来源可能是电源纹波、参考电压波动、电磁干扰甚至热噪声。硬件滤波能削弱一部分但软件滤波几乎是必需品。不同应用场景选不同的滤波算法不要一上来就上卡尔曼滤波那是过度设计。电压采样这个场景我一般按噪声特性分两类处理。一类是白噪声和高频毛刺适合用滑动平均或算数平均。滑动平均就是在DMA缓冲区里直接对每个通道的N个值求平均N可以设成4、8、16和缓冲深度对应。平均的窗口越大平滑效果越好但响应速度越慢。母线电压采样这种变化缓慢的信号窗口设16完全没问题如果需要快速响应电压跌落窗口就不能太大4到8比较合适。另一类是脉冲性干扰比如电机的换向噪声、继电器吸合瞬间产生的尖峰这类干扰会在某个采样点造成一个极端值滑动平均窗口大了会被污染窗口小了压不住。中值滤波对这种脉冲干扰非常有效思路是把连续的N个值排序取中间值输出。不过中值滤波的计算量比较大嵌入式里很少做全排序一般用去最大最小求平均的替代方案把N个值找出最大值和最小值去掉这两个极值后对剩余的值求平均。这个算法对脉冲干扰的抑制能力接近中值滤波计算量却小得多。我实际项目里用的是冒泡去极值平均的组合先把N个采样值做两趟冒泡找到最大最小值然后累加求平均。把这段代码直接嵌入到DMA中断处理里整个耗时也就几百个时钟周期对主循环几乎无感。4.3 采样数据漂移与校准电压采样最常见的两个精度问题一个是零点偏移一个是增益误差。零点偏移表现为输入接地时ADC还输出几十甚至几百的原始值增益误差表现为输入标准电压时换算结果整体偏大或偏小。零点偏移主要来自ADC自身失调和运放电路失调。消除手段很简单软件上做两点校准。系统上电后先通过模拟开关或继电器把ADC输入端接地读取一组零点原始值记录下来然后输入一个标准电压源读一组标准值根据这两组数据算出增益系数和偏移量后续测量时套用线性修正公式。很多MCU内置ADC偏移校准功能比如STM32的AdcCalibration上电后按手册流程跑一遍校准能显著降低内部失调。GD32系列同样有类似校准寄存器千万别漏掉。还有一类高端MCU会内置温度传感器可以用来做温度补偿但对于一般的电压采样需求做好两点校准已经足够了。还有个小技巧把校准系数存到Flash或EEPROM里上电时自动加载。这样每块板子出厂前标定一次用户使用过程中即使环境温度变化比较大精度也能维持在一定范围内。实践下来经过两点校准后12位ADC的电压采样精度稳定在±0.5%以内是完全可以做到的。5. 常见问题与排查实录这一节整理我在ADC-DMA采样调试过程中真实遇到过的典型问题每一类都给出排查思路和解决措施就当是一份速查表。5.1 典型问题一DMA初次启动后只搬运一次数据现象是采样程序运行后缓冲区里只有第一轮数据是有效的后面就再也不更新了。排查后基本是两大类原因。一类是ADC连续转换模式没有开启ADC转换完一轮规则通道后停止输出转换完成事件DMA自然不再搬运。检查ADC配置里是否开启了连续转换模式或外部定时器触发模式。另一类是DMA循环模式没有使能。DMA传输完设定长度的数据后如果不开启循环模式就会进入传输完成状态并不再响应后续请求需要软件重新使能DMA通道才能继续传输。解决方法是调用DMA循环模式使能接口让DMA搬运完一轮后自动回到起始位置继续等待下一次传输请求。5.2 典型问题二多通道数据错乱现象是通道0的数据出现在通道1的位置上或者某通道数据固定偏移一个位置。最直接的原因是规则通道配置顺序和物理扫描顺序不匹配。ADC扫描模式按规则通道索引顺序扫描不是按寄存器地址顺序。比如通道1配置在规则通道索引0通道0配置在规则通道索引1DMA搬运回来的第一个数据就是通道1的。所以要检查adc_regular_channel_config()函数里索引参数和通道号的对应关系。GD32系列的ADC DMA地址递增方向也有讲究。外设地址固定指向ADC数据寄存器不能递增内存地址必须递增否则所有数据都覆盖在缓冲区第一个元素上。这个配置我见过不止一次写反的改过来就好了。5.3 典型问题三采样值整体偏低或最后一位跳动采样值整体偏低优先检查外部源阻抗和采样周期是否匹配。前面讲过采样电容充电时间不够会导致转换结果偏低。解决思路是把采样周期调到更大档位同时把RC滤波电路的等效源阻抗降低。还有一个容易被忽略的因素是ADC参考电压偏低VREF如果用了普通稳压芯片而没有独立去耦负载波动会让参考电压下降导致同样输入电压下采样值变低。最后一位跳动则是量化精度到极限的正常表现不必追求完全没有波动。12位ADC的理论分辨率是3.3V/4096约0.8mV实际受噪声影响最后1到2个LSB跳动很常见。软件上做均值滤波就能把跳动压下来。如果跳动超过4到5个LSB那就不是量化问题而是参考电压或者输入信号本身有较大噪声需要回到硬件布局上找问题。5.4 典型问题四串口DMA发送和ADC DMA互相干扰系统里同时用了串口DMA发送监控数据和ADC DMA采样有时ADC数据出现中断丢失或者串口发送数据卡住。本质是多个DMA通道竞争总线和中断资源。排查思路是先确认两个DMA通道是否挂在同一个DMA控制器上DMA控制器内部有固定的优先级仲裁规则。把ADC的DMA通道优先级调高串口的调低再配合DMA中断里减小处理时间问题一般就能缓解。使用HAL库时还要注意一个坑串口DMA发送不能连续发送很多人遇到第一次发送正常第二次就卡住。原因是HAL库的DMA发送流程依赖传输完成中断来解除忙标志如果在发送完成回调里没有及时释放或者在上一次发送还没结束时又调用了发送函数就会失败。对策是先查询上一次发送是否完成再启动下一次发送。5.5 问题速查表现象可能原因解决措施DMA只搬运一次就停ADC连续模式未开或DMA循环未使能开启连续转换和循环模式多通道数据对不上号规则通道配置顺序与预期不符核对索引和通道号对应关系采样值整体偏低源阻抗太大、采样周期太短增大采样时间降低源阻抗采样值跳变超过几个LSB参考电压去耦不良或输入噪声过大优化VREF去耦检查PCB布局DMA和其他外设冲突DMA通道优先级配置不当调整优先级减少中断处理耗时串口DMA发送第二次卡住上一次发送未完成就启动下一次检查发送完成标志后再发送首次上电数据异常ADC未校准或未等待稳定上电后执行校准并延时数微秒实际调试中还有一个通用技巧不要一上来就对着代码改来改去先在主循环里把adc_buf原始数据通过串口打印到PC端用串口绘图工具实时绘制数据曲线。波形长什么样问题出在哪里一眼就能看出来。比盯着内存窗口猜要高效得多。6. 这套方案还能怎么延伸ADC-DMA协同不仅适用电压采样。三相电流采样、温度多路采集、音频信号采集只要是多通道、连续采集、不想占用CPU的场景这套框架都可以直接套用。以电机控制为例三相电流采样需要和PWM触发时刻精确同步可以把ADC的外部触发源设为定时器事件每次PWM周期中间点触发一次三通道采样DMA把结果搬到内存后中断里直接做Clark变换和Park变换整个过程CPU开销极小。这里的关键是把触发配置从软件触发改成硬件触发顺序是先配置定时器输出比较或更新事件再去配置ADC外部触发源最后使能DMA。触发源选择不同PWM和采样的对齐精度完全不同。另外如果MCU有多个DMA控制器可以考虑把不同外设的DMA划分到不同的DMA控制器上避免单控制器通道拥堵。比如ADC采样的DMA挂在DMA0串口通信的DMA挂在DMA1数据吞吐大时两者几乎互不影响。这套方案我调通之后基本上成了个人工程里的标准模板。遇到新的采样需求改改通道数、缓冲区尺寸和中断处理逻辑就能复用。踩过不少坑之后最大的体会是ADC采样看着简单但要做到数据准确和系统不卡同时成立硬件、DMA配置、软件滤波每一层都要做到位。尤其是DMA循环模式和ADC连续转换模式的配合这个点想通了整个方案就通了一半。至于剩下的都是些细心活照着上面的套路排查一遍基本都能顺利解决。
返回列表