ARTICLE DETAIL

资讯详情

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

S32K144 ADC模块详解:从采样时序、校准到PDB与DMA采集链

S32K144 ADC模块详解:从采样时序、校准到PDB与DMA采集链 一块S32K144的开发板板上做了最简单的旋钮分压接ADC0_SE4我预期读到1.85V对应的数字量结果串口打印出来在3000到3400之间来回跳。用万用表量分压点电压纹丝不动连示波器都看不出明显毛刺。当时我第一反应是电源污染换线、加电容、改滤波折腾了两天最后才发现问题根本不在电源而是我压根没跑ADC校准采样时间也给得太短。这篇就是S32K144系列里ADC模块的完整梳理从模块结构、采样时序、校准流程到PDB触发、DMA搬运、PCB布局和滤波算法全部来自我实际项目里的踩坑记录。写这篇之前我已经在前面的文章里聊过引脚复用、定时器和PDB这些东西而PDB在S32K144上最典型的用法之一就是给ADC做硬件触发源所以这篇算是正好接上。如果你手头也要用S32K144做多通道模拟量采集或者正在被ADC数值漂移、校准不过、采样周期算不明白这些问题折磨这篇应该能把整条链路给你串起来。1. 拆开S32K144的ADC两个SAR内核、参考电压与寄存器清单1.1 ADC0与ADC1不要把两个模块当一组接口S32K144上虽然有ADC0和ADC1两个名字但这两个不是“一组ADC的两种用法”而是两颗完全独立的12位逐次逼近型ADC核。每个核都有自己的时钟分频、触发选择、转换列表、结果寄存器和中断标志唯一共享的只是参考电压和芯片供电。这个区别在实际项目中非常重要。有些人会把ADC0和ADC1当成STM32里面的ADC1/ADC2双重模式来用试图通过主从同步拿到更多通道但S32K144并没有那种复杂的规则通道和注入通道区分。你更常见的做法是让ADC0负责三相电流或几路电压让ADC1负责另一组独立信号两边各自配置、各自触发、各自DMA。它们的相似之处仅在于寄存器结构相同所以SDK驱动API可以复用但初始化结构体、中断处理和校准流程必须各自做一遍。1.2 通道号、引脚复用和那个常被忽略的模拟配置S32K144的ADC通道编号不是按引脚顺序来的芯片手册里有一张“通道号与引脚映射表”ADC0_SE0、ADC0_SE1这些编号和PTA几、PTB几完全是两套逻辑。如果你直接靠感觉猜通道号大概率会把ADC0_SE5接到某个PTA引脚上结果读回来的值要么是0要么是满量程。还要注意引脚复用问题。ADC引脚在芯片出厂复位后通常不是模拟模式你必须在引脚复用寄存器里把该引脚切换到模拟功能同时关闭数字输入缓冲。这一步特别容易被忽略因为代码编译、下载都没问题甚至读寄存器也能读到值但实际上内部多路开关根本没把模拟比较器接进去信号等于悬空。一个比较稳妥的做法是用NXP官方配置工具生成引脚初始化代码然后在生成的模板上看一眼每个ADC通道对应的“Pin Mux”和“模拟使能”是否都被正确勾选。这比在代码里手动改复用寄存器靠谱得多。1.3 VREF怎么接以及“1V输入到底读出多少”ADC模块的转换公式是数字量 (VIN - VREFL) / (VREFH - VREFL) * (2^N - 1)S32K144支持8位、10位、12位三种分辨率默认大部分项目用12位那么满量程就是4095。如果VREFH接3.3VVREFL接GND输入1V时的理论值为(1.0 - 0) / (3.3 - 0) * 4095 ≈ 1241这个1241是理想值实际读数受VREF精度、采样电容稳定时间、内部增益和偏移误差影响上下浮动几个LSB很正常。如果你输入1V却读到1300以上优先怀疑VREF供电质量而不是通道配置。VREFH和VREFL不是可以悬空的引脚。S32K144虽然自带VREF参考电压模块但ADC的参考源仍然需要外部提供稳定的模拟电压。我见过不少开发板直接把VREFH连到3.3V电源轨如果这个3.3V同时给LED、通信芯片、电机驱动供电那ADC读数就是灾难。严格的做法是把VREFH放到单独的LDO或带隙基准源上VREFH和VREFL之间要放至少0.1uF高频去耦电容电容要贴近芯片引脚。VREFH到ADC的走线尽量短粗不要穿过任何开关节点。2. 采样时间、采样周期、转换时间先把时序算明白再加代码2.1 采样阶段和比较阶段SAR ADC为什么需要两个时间逐次逼近型ADC完成一次转换需要两个阶段采样阶段和比较阶段。采样阶段里内部采样电容通过模拟开关接到输入引脚电容充电到输入电压比较阶段里这个电压被逐位拆分比较通过SAR逻辑逼近出数字值。这两段时间性质完全不同。比较阶段基本只跟ADC时钟频率和分辨率有关12位模式下通常需要十几到二十几个ADC时钟周期这部分电路是固定的。采样阶段则可以通过寄存器去加长原因是外部信号源的内阻和内部采样电容构成了一个RC回路如果充电时间不够电容上的电压还没稳定到输入电压后面比较出来的数字量自然偏小或抖动。这也是为什么很多人发现“加长采样时间”之后ADC突然变稳了。2.2 一个可以直接套用的转换时间计算公式S32K144的ADC时钟来自总线时钟或内部RC时钟经过分频后作为ADC内核时钟。你先要确定ADCLK频率然后在配置里选择采样周期数最后按下式估算总转换时间 ≈ (采样周期数 比较周期数 少量额外周期) / ADCLK举个例子ADCLK配置为8MHz也就是每个周期125ns采样周期设成20个ADCLK周期12位比较阶段按16个ADCLK周期估算总周期数大约40个那么一次完整转换约5us。如果满打满算再加上寄存器读写、触发响应之类的开销按6us算一个ADC核每秒能到十几万次采样对绝大多数汽车传感器采集场景完全够用。我习惯在项目初期把“最坏情况”也算出来假设采样周期取到最大ADCLK取低频率看一帧多通道数据能多久更新一次。比如6个通道顺序采集每个按5us算一帧就是30us大约33k帧/秒。如果应用需要至少1kHz更新率余量非常充足。只有当你的采样率达到几百kHz或者要求所有通道同步采样时才需要考虑ADC0/ADC1分拆、甚至外置多路同步ADC。2.3 高阻信号源与“怎么调都不稳”的关系很多ADC“乱跳”问题不是电源不好是信号源内阻太高。典型场景是直接用一个10k甚至100k的电位器分压给ADC电位器内阻和引脚内部采样电容形成低通采样开关一闭合电容上电压就往下掉采样时间不够的话每次采进来的值都不一样。怎么判断是不是这个原因把万用表表笔直接点在ADC引脚上读数如果电压稳定但代码读值跳动大多半是采样保持时间不够。解决思路有两个外部加一个几十nF的电容并联到ADC引脚让电容作为电荷源给内部采样电容快速充电同时把采样周期加长几倍。如果信号本身变化不快我甚至会直接把采样周期设成最大值这种做法的代价只是单次转换时间变长但在低速传感器场景里完全无所谓。3. 上电后先做校准不做校准后续全是玄学3.1 校准到底在修正什么错误S32K144的ADC有固定的增益误差和偏移误差。增益误差会让输入电压满量程处的读数整体偏大或偏小偏移误差会让零点处读数不在0附近。芯片出厂时虽然做过一定程度的trimming但上电后实际误差还受供电电压、温度、时钟分频系数影响所以模块提供了自校准流程通过内部参考通道测量出当前环境下的修正值然后软件把这些修正值写回到校准寄存器后续转换结果才会更接近真实值。跳过校准的后果不会让ADC完全失效但会让你的数据看起来像“玄学”同一块板子同一路信号换一个供电电源读数能偏出几十个LSB。最典型的就是上一章开头说的那种情况分压点电压稳定但ADC读数波动明显实测就是没校准。3.2 校准流程与双ADC校准注意事项S32K144的ADC校准流程在官方参考手册里有明确定义大致是先配置ADC时钟和分辨率确保时钟稳定进入校准模式启动校准转换轮询校准完成标志并检查校准失败标志读取校准结果寄存器和校准偏移值把修正系数写回ADC控制寄存器退出校准模式。用SDK的话就是先调用配置接口再启动校准完成后读取结果并填入驱动结构体里。整体执行时间通常在毫秒级完全可以在系统初始化的早期完成。需要特别提醒的是ADC0和ADC1是两个独立模块两边都要各自跑一遍校准流程。很多人只校准了ADC0然后发现ADC1的数据总是偏大或偏小实际上就是ADC1根本没校准。我在项目里把两个ADC的校准过程封装到了同一个初始化函数里确保每次上电都跑完整。另外校准完成后不要在程序运行途中频繁重新校准。校准本身会打断正在进行的转换频繁校准反而会把正常的采样时序打乱。一般做法是上电初始化时跑一次如果系统对温度漂移有更高要求可以在低负载阶段隔一段时间重新校准一次但要注意在校准期间暂停所有数据采集。3.3 校准完数值还是偏优先检查这三个地方第一参考电压。VREFH和VREFL如果本身就不干净校准只能修正芯片内部误差修正不了外部参考源波动。量一下VREFH对地电压如果和理论值差超过10mV先解决电源问题。第二采样时间依然过短。校准修正的是增益和偏移但采样电容没充满造成的误差属于动态建立误差校准系数覆盖不了。特别是高阻信号源加长采样周期之后再看数据是否变稳定。第三初始化顺序。如果ADC在时钟还没稳定时就跑校准校准结果可能是错的。启动校准前最好等待系统时钟稳定或者加一个小的延时。很多人校准失败标志位被置位却没检查一直用错误系数跑数据自然不对。顺带说一句调试过程中偶尔会遇到把Flash配置字改坏导致芯片进不了调试模式的情况这种时候先别慌用官方烧录工具执行全片擦除芯片能恢复代价是Flash里的代码和配置全部清空。从那以后凡是涉及Flash安全位的烧录配置我都单独存档不跟ADC调试工程混用。4. 多通道连续采集PITPDBADCDMA一条链的正确姿势4.1 轮询方式为什么不适合批量通道采集最简单的读ADC方式是轮询写通道号等待转换结束标志读结果。这个方式在单通道、低频采样时没问题但一旦涉及多通道你就会发现CPU被ADC转换等待完全卡住。假设一个通道5us6个通道轮流转一圈就是30us如果还要做滤波、判断、通信整个主循环调度就很紧张。更关键的是轮询方式很难保证采样时刻的精确性。你在代码里写“等待标志位”但标志位什么时候置1完全取决于之前代码执行到哪一行而之前在跑什么中断、在等哪个外设都是不可控的。对于需要周期性采样的控制类应用这种时间抖动会直接影响系统稳定性。所以多通道连续采集的正确做法是用定时器产生精确的采样节拍经过PDB触发ADC启动ADC转换完成后通过DMA自动把结果搬到内存CPU只需要在DMA双缓冲切换时做数据处理。4.2 触发链的搭建从PIT到PDB再到TRGMUXS32K144的PDB就是用来做ADC触发延时的模块它的作用是把PIT或外部触发信号做一次精准延迟后再送给ADC。为什么要做延迟因为ADC启动瞬间会有采样电容吸流如果你希望在整个系统里让几路信号在特定相位被采集PDB可以精确控制这个采样时刻。我的配置大致是这样的// 伪代码具体函数名以你手中的SDK版本为准 adc_converter_config_t adcCfg; adc_chn_config_t chnCfg; // ADC012位硬件触发使能DMA请求 adcCfg.clockDivide ADC_CLK_DIV8; adcCfg.resolution ADC_RESOLUTION_12BIT; adcCfg.triggerSource ADC_TRIGGER_SRC_HW; adcCfg.dmaRequestEnable true; ADC_DRV_ConfigConverter(ADC0, adcCfg); // 配置多路转换通道 chnCfg.chnIdx ADC0_CHN_0; chnCfg.chnMux ADC_MUX_A; chnCfg.sampleTime ADC_SAMPLE_TIME_LONG; ADC_DRV_ConfigConvChn(ADC0, chnCfg); // 再配置ADC0_CHN_1、ADC0_CHN_2...配置完后把PDB的输出通过TRGMUX接到ADC0的硬件触发输入。PIT产生稳定周期中断PDB收到触发后按设定延时打开ADC采样开关。整个过程不需要CPU干预。这种触发链的最大好处是采样节拍由硬件定时器决定不会因为主循环里有其他代码而抖动。调试的时候用示波器量ADC引脚和PDB输出可以看到每次采样的时刻间隔完全一致这对后面做FFT或者做闭环控制极其重要。4.3 DMA搬运结果与双缓冲处理配置完触发链之后剩下的事情就是让DMA把转换结果搬走。ADC转换完成会产生DMA请求DMA从结果寄存器读取数据写入内存数组。关键点是DMA请求在CPU读取结果寄存器后自动清除如果你在中断里处理数据要确保读取顺序不会丢结果。多通道场景下我习惯把DMA目标地址设计成环形缓冲区或者用双缓冲。双缓冲的意思是DMA先填充缓冲区A满了以后触发中断代码在中断里处理A区数据同时DMA继续填充B区下一次满了再交换。这样做的好处是数据处理和ADC采集并行进行不会因为处理耗时导致某个通道漏采。在实际工程里还有一个容易被忽略的点ADC结果寄存器的对齐方式。12位结果在寄存器里默认是左对齐还是右对齐直接影响你怎么从DMA接收的数据里提取有效值。如果你发现读出来的数字整体偏小或者每隔一个值就出现一次奇怪的跳变先检查对齐方式。这个查寄存器说明就好不要靠猜。5. 数据漂移和噪声滤波算法、输入RC与PCB布局三板斧5.1 先分辨是漂移还是噪声别急着加滤波很多人的第一反应是“ADC乱跳就加滤波”但我建议先分辨一下到底是噪声还是漂移。噪声的特点是短时间内快速波动前后两次采样值之间抖动很大比如从1240跳到1250又跳回1242漂移的特点是缓慢但持续地朝一个方向变化可能几秒内从1240慢慢变成1260中间没有明显抖动脉冲。判断方法很简单把采集到的原始数据以曲线形式打印出来看波动频率。如果是高频抖动优先怀疑电源、参考电压、采样时间或外部干扰如果是低频漂移优先检查参考电压的温度系数、板上是否存在发热源、VREFH是否有缓慢变化的电流负载。先把物理层面的问题解决掉再上数字滤波否则滤波只是把真实信号和噪声一起抹平。5.2 单片机内的数字滤波中值平均滤波器实现滤波算法不要用最朴素的滑动平均那种算法对脉冲干扰很敏感一个毛刺就能把平均值拉偏很多。汽车环境里我常用的是中值平均滤波先取N个样本排序后去掉最大最小再取平均值。这个算法对脉冲噪声和随机抖动都有不错的抑制效果。S32K144这类Cortex-M4F核跑这个算法负担不大N取8或者16都很快。下面是我在项目里常用的一段实现#define ADC_FILTER_LEN 8 uint16_t adc_mid_avg_filter(uint16_t new_sample) { static uint16_t ring[ADC_FILTER_LEN]; static uint8_t idx 0; uint16_t tmp[ADC_FILTER_LEN]; uint16_t sum 0; uint8_t i, j; uint16_t t; // 写入环形缓冲 ring[idx] new_sample; idx (idx 1) (ADC_FILTER_LEN - 1); // 拷贝到临时数组并排序 for (i 0; i ADC_FILTER_LEN; i) { tmp[i] ring[i]; } for (i 1; i ADC_FILTER_LEN; i) { for (j 0; j ADC_FILTER_LEN - i; j) { if (tmp[j] tmp[j 1]) { t tmp[j]; tmp[j] tmp[j 1]; tmp[j 1] t; } } } // 去掉最小和最大取平均 for (i 1; i ADC_FILTER_LEN - 1; i) { sum tmp[i]; } return (uint16_t)(sum / (ADC_FILTER_LEN - 2)); }这段代码的思路是每次来一个新样本就更新环形缓冲然后排序取中间值平均。如果你嫌排序占用时间可以把N降到8在8MHz主频下也只需要几十微秒对大多数应用完全够用。不过要记住滤波是在物理问题解决之后才上的手段。如果硬件上电源噪声已经大到离谱再怎么滤波都是自欺欺人真实信号变化会被延迟甚至掩盖。5.3 模拟前端RC与PCB布局的三个关键点S32K144内置的是SAR ADC不是Σ-Δ型ADC但外部输入前端加RC滤波的思路是通用的。RC有两个作用一个是抗混叠把高于采样频率二分之一的噪声滤掉另一个是为内部采样电容提供一个低阻充电源。很多情况下RC电容还能吸收来自PCB走线的耦合噪声。参数选择上如果信号变化速率不高比如采集电池电压、温度传感器我会用1kΩ电阻加100nF电容截止频率约1.6kHz对几十赫兹以内的信号几乎没有衰减同时能有效滤掉开关电源的高频毛刺。如果信号本身频率较高比如需要采集几百赫兹到几千赫兹的交流信号电容就要相应减小计算公式就是典型的RC低通截止频率f_cutoff 1 / (2 * π * R * C)PCB布局方面有三个最容易踩的坑第一VREFH的去耦电容必须放在芯片引脚旁边。很多板子图省事把电容放在背面走线绕了一大圈高频噪声全进来了。电容引脚到芯片引脚之间走线越短越好最好直接用打过孔的铜皮连接。第二模拟输入走线不要和数字信号长距离平行。尤其是PWM输出、SPI时钟、外部晶振这类高频信号哪怕隔着两层板都可能通过寄生耦合把噪声灌进ADC输入。如果实在绕不开就在模拟输入两边加地线包围。第三ADC时钟和触发源要稳定。PDB触发精度直接决定每次采样的时间间隔是否一致如果你发现转换结果本身稳定但连续采集的波形在时间轴上忽快忽慢去看PDB时钟配置和PIT中断优先级。关于时钟抖动还有一个容易忽略的点如果系统里用了内部RC振荡器给ADC做时钟源频率会随温度变化这不会直接导致采样值跳变但会导致采样时刻的绝对时间不稳定。对要求精密相位关系的应用尽量选外部晶振分频出来的时钟。最后补一个我在实际项目里养成的检查习惯拿到一块新板子先把ADC配置成最原始的单通道软件触发不跑校准、不加滤波用串口把原始值打出来。如果原始值就已很稳再逐步加触发、DMA和滤波如果原始值乱跳就要回到硬件层去找原因。这样做能快速把“代码问题”和“硬件问题”分开而不是在一个复杂的系统里到处猜。S32K144的ADC虽然初始化步骤比一些家用MCU繁琐但一旦把校准、触发链和参考电压这几个关键点都理顺跑出来的数据质量是相当让人放心的。
返回列表