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

资讯详情

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

ADS1220高精度ADC驱动实战:SPI时序、寄存器配置与滤波标定

ADS1220高精度ADC驱动实战:SPI时序、寄存器配置与滤波标定 干这行年头久了你会发现一个规律凡是涉及精密测量的项目最后较劲的基本都在ADC这一环上。前阵子给一台多点温控仪表做采集板传感器输出信号只有几个毫伏普通12位ADC根本没法用翻了半天选型表最后锁定了TI的ADS1220。这是一颗24位Δ-Σ型ADC内置PGA可编程增益放大器、激励电流源还支持热电偶和RTD的直连方案最吸引人的是SPI接口驱动起来不算复杂但想要真正把精度做出来里面的门道其实不少。这篇就把我这次的驱动开发实战过程完整拆出来从硬件接口到寄存器配置再到滤波和标定把验证过的方案和踩过的坑一并写清楚。如果你正要搞高精度采集或者嵌入式驱动开发这篇应该能让你少走几天弯路。1. 方案选型与驱动架构设计1.1 为什么最后选了ADS1220做温控仪表之前我其实先看了几颗别的芯片。最早想用ADS111516位精度I2C接口还带PGA看起来挺省事。但一算账就发现不对实际需要分辨到0.01°C而K型热电偶输出的热电动势本身就很小典型只有40多µV/°C那么0.01°C就对应0.4µV左右。ADS1115在这个量级上噪声太大16位下理论LSB在PGA16时大约7.8µV单看量化误差就不够用。后来又看了24位的ADS1232精度够了但它是双通道专用方案通道数量偏少而且没有内置温度传感器做冷端补偿还得另接芯片。ADS1220比较均衡。24位分辨率内部PGA最高支持128倍增益输出数据速率最低可以压到20SPS这个速率下内部数字滤波器对50Hz和60Hz工频干扰有很强的抑制能力恰好适合低频缓变信号。更关键的是它有两路差分输入支持四线制RTD和热电偶测温内部还集成了两个可编程激励电流源IDAC可以直接给RTD提供激励电流硬件设计上能省掉一堆外围。SPI接口最高可以跑到几MHz驱动逻辑做简单了后面如果想加更多通道还能级联扩展灵活性比I2C接口好得多。芯片选完之后驱动开发的思路也定了一定要把最常用的操作封装成一套稳定的API初始化、读写数据、配置通道这些底层逻辑不能反复改动。我的做法是驱动层分两层底层管SPI收发和GPIO时序上层管寄存器和数据状态解析。上层不直接碰硬件下层不掺业务逻辑这样后期平台从STM32换到其他MCU底层的SPI适配函数一换上层代码就不用动。1.2 驱动框架拆解底层通信与应用层解耦当时画驱动框架的时候我把它拆成了三个模块。第一个是平台适配层就是那个hal_ads1220_spi_rw之类的函数里面就是SPI收发、CS引脚控制、延时这些基础操作第二个是芯片驱动层包括寄存器读写、复位、初始化配置、启动转换、读取转换结果第三个是应用层比如把原始码值转换成温度、电压或者进一步做冷端补偿、线性校正。每一层都只暴露必要接口内部实现细节不带出去。这样拆分之后的收益很明显调试的时候哪一层出错一目了然不需要把整块代码翻个遍。比如采集到的数据全是0xFF那基本就是SPI时序或者CS控制的问题根本不用去查应用层。另外复用性也好之前上一版项目的驱动代码把硬件相关部分抽出来改一改GPIO和SPI句柄就能用在新的板子上省了大量时间。/* 芯片驱动层对外接口示例 */ int32_t ads1220_init(ads1220_cfg_t *cfg); int32_t ads1220_set_channel(uint8_t ch, ads1220_mux_t mux); int32_t ads1220_read_raw(int32_t *raw); int32_t ads1220_read_voltage(float *voltage_mv);这里有个细节当时踩过AD转换结果不是简单的补码格式24位里面有几位需要处理符号扩展。如果直接拿int32_t接收不把高字节做符号位扩展负温度下数据就会完全错乱。这个我在后面第4节里专门展开。2. SPI通讯时序与控制要点2.1 引脚连接与SPI模式选择ADS1220的引脚不多但连接时必须仔细。核心是SPI四根线SCLK、DIN、DOUT、CS外加电源AVDD、DVDD以及基准电压REFPREFM。我这边MCU用STM32F407供电方案是AVDD用3.3V模拟电源单独走DVDD同样3.3V但注意DVDD的最高电压上限手册上写的是5.5V如果直接用5V逻辑单片机也问题不大不过为了减少数字噪声耦合我是统一3.3V不需要电平转换。SPI模式这里有个常见的坑。ADS1220手册上写的时序同时支持CPOL0、CPHA0模式0和CPOL1、CPHA1模式3但实际用下来我更推荐模式0。原因很简单STM32的SPI硬件外设配置模式0最常见而且和绝大多数SPI存储芯片、传感器兼容万一以后板子上挂了别的SPI设备统一的模式能少调一处是一处。CS引脚不能用SPI外设的硬件NSS一定要用普通GPIO手动拉低拉高这样才能精确控制每次通信的帧边界尤其是读数据时CS必须保持一整帧的低电平中间不能有任何抖动。当时我的引脚分配是这样的SPI1_SCLKPA5、SPI1_MISOPA6、SPI1_MOSIPA7、CS用PB6DRDY数据就绪指示用PB7配置成输入下降沿中断。DRDY这根线很重要后面我会详细说它在数据准备就绪时会输出一个低脉冲用它可以触发中断通知MCU来读比盲目延时等数据靠谱得多。2.2 SPI时序验证方法和故障定位调试SPI最容易翻车的地方往往是时序细节。比如SCLK空闲电平不对、数据在错误的边沿被采样、CS释放太早导致最后一个字节没发完。我调试时用逻辑分析仪直接抓波形一眼就能看出问题。你手头有示波器的话重点看几个位置SCLK的极性和相位、DIN上的数据是否在SCLK上升沿保持稳定、CS低电平期间是否完整覆盖了所有SCLK周期、DOUT上读取的每比特是否和手册时序图对应上。还有一个很多人会忽略的问题上电后不要急着发配置命令。ADS1220内部有一个上电复位过程DVDD稳定之后至少要等2ms左右才能正常响应SPI命令。我第一次调试时上电后立刻发寄存器写命令结果寄存器写入根本没生效数据全部返回0xFF就是因为缺少这个延时。后面我把初始化序列固定为上电延时3ms再发送复位命令再延时3ms然后才开始写配置寄存器之后再也没出过这种奇奇怪怪的问题。另外如果发现读回来的寄存器内容一直不对别急着怀疑芯片坏了先确认线序。SPI的MISO和MOSI接反是最常见的低级错误因为这个芯片DOUT是数据输出接到MCU的MISO上DIN是数据输入接到MCU的MOSI上接反的后果就是写入总是失败读回来全是0xFF。查这类问题时用逻辑分析仪看波形是最快的。/* SPI 读写封装所有命令最终都走这个函数 */ static void ads1220_transfer(uint8_t *txbuf, uint8_t *rxbuf, uint16_t len) { HAL_GPIO_WritePin(ADS1220_CS_GPIO_Port, ADS1220_CS_Pin, GPIO_PIN_RESET); HAL_SPI_TransmitReceive(hspi1, txbuf, rxbuf, len, 100); while (HAL_SPI_GetState(hspi1) ! HAL_SPI_STATE_READY) {} HAL_GPIO_WritePin(ADS1220_CS_GPIO_Port, ADS1220_CS_Pin, GPIO_PIN_SET); }3. 寄存器配置与初始化流程3.1 四个配置寄存器的功能拆解ADS1220一共有4个配置寄存器每个8位地址从0x00到0x03。上电后这些寄存器的默认值是0x00也就是说内部MUX选择的是AIN0/AIN1差分输入PGA增益为1这种默认配置用来测毫伏级信号基本没用必须重新配置。CONFIG寄存器0地址0x00负责输入多路复用器和多路选择器位[7:4]选择输入通道位[3:1]设置PGA增益。比如测热电偶时我把MUX配置为AIN0/AIN1差分、PGA增益设为32或64。这里有个细节必须注意PGA增益倍数决定了可测量的满量程范围增益64时满量程只有32mV左右如果接入信号超过这个范围输出会饱和钳位表现在数据上就是一直处于某个极限值。所以选增益之前一定要先估算信号范围别贪图高增益过高的增益会把噪声一起放大分辨率并不完全等同于精度。CONFIG寄存器1地址0x01主要设置数据速率和转换模式。数据速率位[6:4]从20SPS到2000SPS可选。我强烈建议需要工频抑制的场景直接选20SPS因为这个速率下内置FIR滤波器在50Hz和60Hz频点有约-100dB的衰减能够很好抑制工频干扰。CONFIG寄存器2地址0x02比较冷门但RTD测温时特别有用里面包含激励电流源IDAC的设置位[2:0]。例如给PT100提供250µA的激励电流需要将IDAC设置为0x06对应250µA。这个电流源必须和CONFIG3寄存器中的IDAC输出引脚分配配合使用分配错了电流就出不来了。CONFIG寄存器3地址0x03的高位控制IDAC输出的引脚选择低位控制基准电压源的选择。做比率式电阻测量时基准电压可以选择REFP/REFM引脚输入这样可以消除激励电流源波动带来的误差。我整理了一份配置参考表你可以直接抄作业使用场景输入MUXPGA增益数据速率基准源IDAC设置K型热电偶AIN0/AIN13220SPS内部4.096V关闭PT100三线制AIN1/AIN2820SPSREFP/REFM250µA称重传感器AIN0/AIN112820SPSREFP/REFM关闭通用毫伏信号AIN0/AIN11645SPS内部4.096V关闭3.2 初始化代码和写寄存器命令写寄存器命令格式为0x40加上寄存器地址后面跟着要写入的数据。可以一次连续写多个寄存器只要在数据字节后面接上下一寄存器的值即可CS一帧拉低就能完成多字节写入。我下面是初始化代码用的是STM32的HAL库其他平台换一下底层收发函数就能直接移植。/* 写单个寄存器 */ static void ads1220_write_reg(uint8_t reg_addr, uint8_t value) { uint8_t tx[2]; tx[0] 0x40 | (reg_addr 0x0F); /* 写命令 寄存器地址 */ tx[1] value; ads1220_transfer(tx, NULL, 2); } /* 连续写多个寄存器 */ static void ads1220_write_regs(uint8_t start_addr, uint8_t *values, uint8_t len) { uint8_t tx[8]; tx[0] 0x40 | (start_addr 0x0F); for (uint8_t i 0; i len; i) { tx[i 1] values[i]; } ads1220_transfer(tx, NULL, len 1); } /* 读取三个通道转换结果的配置 */ void ads1220_setup(void) { uint8_t cfg[4]; /* CONFIG0: AIN0/AIN1差分, PGA32, 默认 */ cfg[0] 0x00 | (ADS1220_PGA_32 1); /* CONFIG1: 20SPS数据速率, 连续转换模式 */ cfg[1] (ADS1220_DR_20SPS 4); /* CONFIG2: 关闭温度传感器, 关闭IDAC */ cfg[2] 0x00; /* CONFIG3: 内部参考电压4.096V */ cfg[3] 0x00; ads1220_write_regs(0x00, cfg, 4); }注意第3个寄存器写完后必须等到DRDY信号拉低再读数据。如果你用的是延时方式代替轮询DRDY时间一定要给足。20SPS对应每50ms产生一个新样本理论上隔60ms再读是安全的。但这里有个坑转换周期是从上一次读取操作结束之后才开始计数的如果你读得太频繁反而可能读到旧数据而且状态位容易错乱这一点后面我单独避开。4. 数据采集、符号扩展与滤波实战4.1 正确读取24位转换结果读取转换结果有几种不同方式最稳的是用命令0x10触发一次单次读取。启动一次转换后等待DRDY拉低然后发送0x10命令芯片会把24位数据按从高到低的顺序从DOUT引脚逐位输出。这时顺序非常关键第一字节是最高位bit23第二字节是bit16~23第三字节是低8位。为什么特别强调符号扩展因为ADS1220的输出默认是二进制补码格式当输入信号为负电压时转换结果是负数高字节的最高位是1。如果你只是简单地把三个字节拼起来存在uint32_t变量里负数就变成很大的正数了。比如-12mV在小增益情况下输出0xFFFFF000直接无脑左移拼起来就是4294966272显然不对。正确的做法是拼接成32位后先判断符号位如果bit23为1需要把高32位全部置1再进行解释。int32_t ads1220_read_raw(void) { uint8_t rx[3]; uint8_t cmd 0x10; int32_t raw; /* 等待DRDY低电平表示数据就绪 */ while (HAL_GPIO_ReadPin(ADS1220_DRDY_GPIO_Port, ADS1220_DRDY_Pin) ! GPIO_PIN_RESET) {} ads1220_transfer(cmd, rx, 3); raw ((int32_t)rx[0] 16) | ((int32_t)rx[1] 8) | rx[2]; if (raw 0x800000) { raw | ~0xFFFFFF; /* 符号扩展为32位负数 */ } return raw; }读取之后原始LSB值要换成实际电压。公式是电压 原始值 × (基准电压 / 增益 / 2^23)。千万别把基准电压值代错了ADS1220内部参考电压是4.096V但必须确认配置的是内部基准还是外部基准否则算出来的电压值差得离谱。我调试时有一半时间都在查这种低级代错量程的锅。4.2 滑动窗口滤波和中值平均算法高精度ADC不等于高稳定数据。ADS1220在20SPS下虽然噪声很低但信号仍然会叠加随机噪声和微小的漂移如果不做数字滤波显示的温度值会不停地跳个0.02°C。对于仪表应用这个跳动用户是接受不了的。常用的做法有几种一是简单去极值平均每5次采集去掉一个最大值一个最小值剩下3次取平均二是滑动中值滤波适合抑制尖峰脉冲干扰三是一阶滞后滤波适合温度这类缓慢变化信号用来让显示值平滑变化。我最后采用的是去极值滑动平均窗口大小取7。这样做的好处是既保留了数据曲线的实时性又能滤掉偶发的毛刺。#define FILTER_WINDOW 7 uint32_t filter_remove_avg(uint32_t *buf, uint8_t len) { uint32_t sum 0; uint32_t max 0, min 0xFFFFFFFF; uint8_t i; for (i 0; i len; i) { sum buf[i]; if (buf[i] max) max buf[i]; if (buf[i] min) min buf[i]; } sum - max; sum - min; return sum / (len - 2); }这里还要说一个和ADC本身无关但实际影响体验的点数据采集线程的调度不能太随意。如果你在RTOS里建了一个任务任务优先级太低或者被别的任务抢占那么DRDY中断来了之后可能不能及时读取数据导致下一次转换开始后旧数据还没被取走。尤其是20SPS模式下50ms的周期看起来宽裕但如果中断处理不及时数据一样会丢失或错位。我的建议是把读取数据逻辑放在中断回调中或者给采集任务一个足够高的优先级并确保读取过程没有阻塞等待的操作。经过滤波后的数据温度显示就能稳定到0.01°C级别了。实际上在标准实验室环境下我的板子配合K型热电偶在恒温槽里测50°C温度波动范围控制在±0.02°C以内这已经是这只芯片在这个配置下的正常水平了。5. 调试实录常见问题与排查速查表5.1 从波形到数据分门别类地定位异常驱动开发过程中有一类问题最让人头疼硬件看起来都正常SPI时序也对但转换结果就是不对。我整理了自己调试中遇到的高频问题和对应的排查方向做成一个速查表后面项目再遇到类似问题可以顺着这张表一个一个过。现象可能原因排查方向读寄存器全部是0xFFSPI模式不对、MISO/MOSI接反、CS控制异常逻辑分析仪抓SPI时序检查CPOL/CPHA、接线、CS电平写寄存器后读回值不变上电后未等待复位完成就操作延长上电延时至少3ms以上检查写命令格式数据输出始终为0xFFFFFF输入信号超过满量程、PGA配置错误降低增益用万用表测量输入引脚电压数据跳动大电源纹波干扰、参考源不稳定、信号线屏蔽差检查模拟电源滤波电容信号用屏蔽双绞线负温度显示错误百出符号扩展没做、补码处理错误检查原始数据拼接和符号位扩展逻辑连续转换模式下DRDY异常频繁读取时序不合规、读取频率过高改为单次转换模式确认每次读取数据周期第一条SPI模式的问题最典型。ADS1220虽然支持模式0和模式3但如果你用STM32的SPI外设配置成模式3又恰好在上升沿采数据读出来的字节经常错位反之模式0则很少出事。另外CS控制用的GPIO初始化成推挽输出时速度等级也要注意如果GPIO速度配置过低CS电平的翻转边沿太慢也会偶发通信错误我当时是直接配置为高速模式。第五条符号扩展的问题前面已经讲过了这里再补充一个实际案例。我测热电偶的时候室温下热端和冷端温差很小输出接近0V转换结果在0附近正负摆动。当时代码里没有符号扩展采集值一会儿是几万一会儿是几百万当时一度以为是电路虚焊了。后来单步调试打印出三字节数据才发现高字节一直有bit23在跳才意识到补码问题。解决之后数据立马正常。5.2 模拟部分的避坑经验除了纯数字驱动问题高精度ADC还有一个绕不开的主题模拟电路布局和电源质量。ADS1220对电源噪声非常敏感。如果AVDD上的纹波超过几毫伏转换结果里就会出现明显的周期波动频率往往和开关电源的开关频率一致。我第一次在测试板上用DC-DC直接供电20SPS下采集到的数据就带有规律性跳动后来换成线性稳压器LDO单独给模拟部分供电再在AVDD引脚旁边并联一个10µF钽电容加0.1µF陶瓷电容噪声立刻就降下来了。还有一点就是地线的处理。ADS1220的模拟地和数字地在小信号应用里原则上应该单点连接不要让数字信号的回流路径穿过模拟地平面。PCB布线时SPI信号线要尽量短而且不要和模拟输入线并行走线太长不然SPI翻转的串扰会直接耦合进ADC输入端。如果板子空间实在有限也可以用铺地隔离的方式减少串扰但效果不如空间分隔明显。关于参考电压如果你的应用只是测个大概温度内部4.096V基准完全够用。但如果你要做高精度计量或者产品需要一致性很好建议用外部精密基准源比如REF5025或者TI的REF3440把它们放在靠近REFP和REFM引脚的位置。做个简单算术你就明白为什么了内部基准的温度系数通常在几个ppm/°C外部精密基准能做到1ppm/°C以下对于全温度范围精度指标要求0.1%的产品这个差异就是过不过检的区别。5.3 关于校准和量化的另一个细节还有一个容易被忽略的点就是实际有效分辨率并不等于24位。ADS1220在20SPS、PGA32时峰峰值噪声大约在0.6µV左右折算到有效分辨率大概就是20位多一点。也就是说你后面再做多少软件滤波也不可能突破这个原始噪声底限系统能稳定显示的位数是由硬件噪声决定的。因此不要一味追求数字上的高分辨率真正决定测量精度的瓶颈往往是传感器本身的精度和稳定性。我自己的项目里做了两次标定一次是电气零点校准输入短路时记录原始偏移值一次是两点温度校准用精密电阻箱或标准温度源标定实际温度对应的原始LSB。然后把这两组系数存到MCU的Flash里测量时用线性插值做修正。一个非常实用的技巧是把校准系数和滤波后的原始值在应用层分开保存不要混在一起处理这样每次重新校准也不用动驱动代码。6. 实操心得与项目总结驱动写完之后我回头审视整个开发过程最有价值的心得有三条。第一条是高精度ADC驱动的难点永远不只是驱动本身而是对噪声、参考源、信号链路的整体理解。单纯会写寄存器没有用采集出来的数据不好看最终还是得从硬件上找原因。第二条是调试工具一定要用好逻辑分析仪和示波器几乎是必备的不要靠猜一帧SPI波形截图往往就能定位出问题省下几个小时的电报级排查。第三条是给项目预留一个校準接口别把校准系数写死在代码里做成可以动态修改的参数产线调试时会感谢你当初这个决定。ADS1220这颗芯片整体驱动开发难度不算大但每个细节都做对了才能发挥出应有性能。我最后的建议是先别急着上RTOS用一个裸机状态机把单通道数据采集跑通确认数据稳定可靠后再做多通道扩展和系统集成。这样每一步都有明确的验证点出了问题也容易回溯不会出现满屏都是问题却不知道从哪里下手的局面。希望这篇实战记录能帮你缩短开发周期项目顺利落地。
返回列表