ADS1220驱动详解:24位ADC寄存器配置与STM32工程实践
简介面向 STM32 嵌入式开发者的 ADS1220 驱动代码资源为 24 位高精度 ADC 的 SPI 通信与数据采集提供可直接移植的 C 语言实现。代码基于 STM32F407 平台涵盖 GPIO 与 SPI 初始化、命令发送、24 位转换结果读取等核心函数并梳理了 ADS1220 的 PGA 增益设置、连续/单次转换模式及中断处理逻辑适合传感器接口、精密测量等工业场景。资源包为 4KB 的 zip 压缩包内含 2 个源文件分别是头文件与 C 文件结构精简、便于集成到现有工程并快速验证。已有 507 人学习浏览适用于正在调试 ADS1220 或希望理解其驱动框架的嵌入式工程师。通过阅读源码可快速掌握 STM32 SPI 主模式配置与 ADS1220 寄存器操作要点为后续功能扩展和性能优化打下基础。 做传感器采集项目这几年ADS1220这颗24位ADC我用得算是比较多的一颗。热电偶测温、称重传感器毫伏信号、电流环采样这类小信号场景它比ADS1115要合适得多内置可编程增益放大器PGA、内部参考电压、两个可配置激励电流源一颗芯片能把前端调理电路省掉大半。但驱动代码不像ADS1115那样满网都是搜出来的例程很多是Arduino的寄存器配置、DRDY引脚处理、24位补码换算这些细节几乎都要自己踩一遍。这篇就把我实际工程里验证过的ADS1220驱动代码、寄存器配置思路和排坑实录整理出来给做仪器仪表、工业采集、传感器信号处理的朋友做个参考。1. 项目概述与整体设计思路1.1 ADS1220这颗芯片到底能干什么ADS1220是TI出的一颗低功耗、24位分辨率、最高2kSPS采样率的四通道ADC。这里说的四通道实际是支持两路差分输入或者四路单端输入内部还有PGA增益范围1到128倍。你可以想象它就是一个“高精度电压表头”把传感器输出的微小电压信号放大、数字化然后通过SPI接口送给单片机。这颗芯片最常见的应用就是热电偶冷端补偿测温、称重传感器电桥信号采集、应变片应力测量以及需要高分辨率小信号采集的工业变送器。和普通的16位ADC比起来24位分辨率配合128倍增益理论上能分辨到纳伏级别的信号变化当然实际会受到噪声、PCB布局和参考电压稳定度的限制。我当初选它主要是看重三个点内部参考电压2.048V不需要额外买基准芯片内置两个IDAC电流源可以直接给RTD铂电阻或者电桥提供激励休眠电流很低适合电池供电的便携采集设备。整体设计思路就是让外部硬件尽量简化软件上把寄存器初始化、数据读取、工程量换算做成独立模块方便在不同项目里复用。1.2 驱动架构HAL库分层与模块边界驱动代码基于STM32 HAL库实现但核心架构并不绑定具体MCU只要改一下底层SPI读写函数就能平移到其他平台。整体分了三层底层硬件抽象层HAL负责SPI外设初始化、GPIO控制、延时等。设备驱动层ADS1220负责寄存器配置、命令发送、转换数据读取对上层屏蔽时序细节。应用层负责把原始码值换算成电压、温度等工程量以及后续显示、存储。为什么要这样分层因为SPI时序是所有ADC操作的基础寄存器配置是保证芯片在正确状态下工作的关键而换算逻辑则是工程师真正关心的东西。三层分离后你调试的时候可以在任意一层打断点逻辑清晰得多。注意ADS1220虽然内部有24位分辨率但实际有效分辨率受噪声和PGA增益影响。如果你需要做到20位以上的稳定输出硬件布局和电源滤波比软件更重要后面会专门讲。2. 寄存器配置与关键参数解析2.1 四个配置寄存器逐个拆解ADS1220有4个8位配置寄存器所有工作模式都由这几个寄存器决定。很多人在网上找例程发现读出来的数据不对多半是寄存器配置和实际应用场景不匹配。下面是我梳理的寄存器位定义。配置寄存器0地址00h输入多路复用器MUX、PGA增益、PGA旁路。Bit功能说明7:4MUX[3:0]输入通道选择0000表示AIN0-AIN1差分0001表示AIN2-AIN3差分0100表示AIN0单端等3:1GAIN[2:0]PGA增益0001倍0012倍0104倍0118倍10016倍10132倍11064倍111128倍0PGA_BYPASS旁路PGA置1时PGA不参与放大配置寄存器1地址01h数据速率、工作模式、温度传感器。Bit功能说明7:5DR[2:0]数据速率0001.8SPS00110SPS01020SPS01145SPS10090SPS101175SPS110330SPS1111000SPS4:3MODE[1:0]工作模式00正常模式01涡轮模式10/11省电模式2TS内部温度传感器使能1BURN传感器断路检测电流源使能0保留必须写0配置寄存器2地址02hIDAC电流源大小、内部参考电压、50/60Hz滤波。Bit功能说明7:6IDAC[1:0]IDAC电流值设置00关闭0150uA10100uA11250uA5PSW内部上电开关控制4:3VREF[1:0]参考电压选择00内部2.048V01外部参考输入10模拟电源250/60工频滤波模式050Hz抑制160Hz抑制1:0保留必须写0配置寄存器3地址03hIDAC电流源输出引脚选择。如果不用激励电流源全部写0即可。2.2 增益、数据速率与参考电压的选择逻辑配置寄存器的核心就是做三个选择信号范围、采样速度、参考来源。先看信号范围。以热电偶K型为例0到1000摄氏度对应的毫伏信号大约是0到41mV如果选择内部参考电压2.048V满量程就是正负2.048V/Gain倍。为了让41mV信号尽量占满ADC的输入范围选择增益16倍后满量程是正负128mV41mV信号能用到约三分之一量程信噪比比较理想。再看数据速率。ADS1220的DR位决定内部数字滤波器的输出速率。速率越高带宽越高但噪声也越大。从实际数据看10SPS和90SPS两种速率下有效分辨率能差出两三位。采集热电偶这类变化缓慢的信号90SPS已经足够采集动态应变信号才需要用到330甚至1000SPS。参考电压的选择更关键。内部2.048V参考电压精度高温漂小但没有外部基准那么灵活。如果传感器是比例式输出比如桥式称重传感器它的输出信号正比于电桥激励电压这时用模拟电源供电作为参考电压VREF选10理论上可以消除电源波动带来的误差这就是所谓的比例式测量。注意PGA增益太大时输入信号稍微有点直流偏置就容易把ADC打饱和。我的经验是先用最小增益量一下信号幅度确认量级后再加大增益别一上来就追求128倍。3. 基于HAL库的驱动代码实现3.1 硬件接线与SPI初始化ADS1220的通信接口是标准的4线SPISCLK、DINMOSI、DOUTMISO、CS外加一个数据就绪引脚DOUT/DRDY。注意ADS1220的DOUT引脚身兼两职既用于SPI数据输出又在转换完成时输出低电平脉冲表示数据就绪。所以驱动中需要把这个引脚配置成输入模式通过读取它的电平变化判断数据是否准备好。以STM32 HAL库为例SPI的初始化配置要注意几点SPI时钟极性CPOL0相位CPHA1即空闲时SCLK为低电平第二个边沿采样数据。最大SPI时钟频率4MHz实际使用时建议设在1MHz以下线材过长或PCB走线不佳时高速率很容易读回错位数据。DRDY引脚建议配置为外部中断下降沿触发或者直接在轮询循环中查询。我常用查询方式代码简单且不容易漏事件。hi2c2.Instance SPI2; // 用CubeMX生成时参数按下面设置 // Mode: Master // Direction: 2 Lines Full Duplex // Data Size: 8 Bits // Clock Polarity: Low // Clock Phase: 2 Edge // Prescaler: 32对应约1.125MHz 36MHz总线初始化GPIO时CS引脚推挽输出默认置高SCLK和MOSI复用为SPI功能MISO复用为SPI功能DRDY配置为开漏上拉输入。DRDY是开漏输出必须加上拉电阻否则读不到下降沿。3.2 寄存器读写底层函数ADS1220读写寄存器使用命令字节读寄存器命令是0x20加上寄存器起始地址第二个字节是要读的寄存器数量减1写寄存器命令是0x40加上寄存器起始地址第二个字节是要写的寄存器数量减1。写配置寄存器0到3本质是一次写入4个字节配置数据。下面这段代码是我实际使用的寄存器写配置函数void ADS1220_WriteReg(uint8_t startAddr, uint8_t *data, uint8_t len) { HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_RESET); uint8_t cmd 0x40 | (startAddr 0x03); HAL_SPI_Transmit(hspi2, cmd, 1, 100); uint8_t countByte len - 1; HAL_SPI_Transmit(hspi2, countByte, 1, 100); for (uint8_t i 0; i len; i) { HAL_SPI_Transmit(hspi2, data[i], 1, 100); } HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_SET); }对应的读寄存器函数结构相同只是命令换成0x20发送完命令和数量字节后再发0x00字节同时接收返回数据。3.3 连续转换与单次转换的数据读取ADS1220支持连续转换和单次转换两种模式。连续转换模式下芯片会持续采样每次转换完成后DRDY引脚拉低主机读取数据后DRDY自动恢复高电平。单次转换模式则适合低功耗应用发送START命令后芯片完成一次转换读取数据后自动进入空闲状态。我的驱动函数里这样处理uint8_t ADS1220_ReadData(int32_t *rawCode) { // 等待DRDY变低即等待转换完成 uint32_t timeout 0xFFFF; while (HAL_GPIO_ReadPin(DRDY_GPIO_Port, DRDY_Pin) ! GPIO_PIN_RESET) { if (--timeout 0) return 1; } uint8_t buf[3] {0, 0, 0}; uint8_t rxBuf[3] {0, 0, 0}; HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_RESET); // 发送三个NOP字节同时接收24位转换结果 for (uint8_t i 0; i 3; i) { HAL_SPI_TransmitReceive(hspi2, buf[i], rxBuf[i], 1, 100); } HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_SET); // 将三个字节合成24位补码并扩展到32位有符号数 uint32_t code ((uint32_t)rxBuf[0] 16) | ((uint32_t)rxBuf[1] 8) | rxBuf[2]; *rawCode (int32_t)(code 8) 8; return 0; }这里最关键的一步是24位补码转32位有符号数。(code 8) 8这个操作看起来简单但能正确处理最高位符号位的扩展。如果漏掉这一步负电压的原始码值会被当成一个很大的正数后续换算出来的工程量就会完全错乱。3.4 电压值、温度值与工程量的换算拿到原始码值后需要知道最低有效位LSB对应的电压是多少。公式如下LSB 2 × VREF / (Gain × 2^23)当内部参考电压VREF2.048VPGA增益Gain16时LSB 4.096 / (16 × 8388608) ≈ 0.0000305V也就是说原始码值每变化1对应的输入电压变化约30.5nV。#define VREF_V 2.048f #define PGA_GAIN 16.0f float ADS1220_RawToVoltage(int32_t rawCode) { float lsb (2.0f * VREF_V) / (PGA_GAIN * 8388608.0f); return rawCode * lsb; }如果是差分输入比如AIN0为信号正端、AIN1为信号负端那么计算出的电压就是两者之差。换算成温度时热电偶还需要查分度表加上冷端温度补偿称重传感器则需要进行标定通过两点或三点校准把电压映射到重量值。注意ADS1220内部的24位数据在读取时如果芯片检测到输入超量程或转换结果饱和状态字节会给出相应标志。在精度要求高的场合最好把第四个状态字节一并读出来做溢出判断。4. 常见问题与排查技巧实录4.1 SPI读回全0或者全FF多半是接线和时钟极性问题这是我遇到过最多的问题蓝牙串口打印出来要么全是0要么全是0xFF。排查思路很简单先用逻辑分析仪或者示波器看SCLK、MOSI、MISO三根线的时序。第一看接线SDO、SCLK、DIN是不是和MCU的MISO、SCK、MOSI一一对应。很多新手会把DIN和DOUT接反ADS1220的DIN是芯片的串行输入对应主机的MOSIDOUT是芯片的串行输出对应主机的MISO。第二看时钟极性相位。ADS1220要求CPOL0、CPHA1也就是第二个边沿采样。如果在CubeMX里默认配置成了模式0CPHA0读回来的数据会整体错位一位结果完全不对。第三看CS拉低时SCLK是否持续发送。ADS1220每次通信以CS拉低开始CS拉高结束通信期间CS必须保持低电平。有些人把CS当成普通GPIO每次传输都重新拉上拉下导致一条命令被拆成多次传输数据自然出错。4.2 数据跳动大、稳定不下来怎么办如果你的转换结果最后几位一直在跳先别急着怀疑芯片。ADC对电源噪声和参考电压噪声非常敏感ADS1220的AVDD和DVDD建议分别用10uF和100nF电容贴近引脚放置。数字电路和模拟电路尽量分区布线SPI信号线不要和模拟输入线平行走线。其次检查参考电压。如果选择内部参考确认REF引脚有没有按要求接上电容。内部参考需要外部去耦电容一般接一个1uF到10uF的电容到地具体容值看数据手册。软件滤波也是一个手段。实测下来对90SPS的原始数据做16次滑动平均有效分辨率能提高2到3位。但注意滤波会降低响应速度动态信号场景要控制平均次数。4.3 转换结果和万用表对不上检查这里最常见的错误是增益配置不对。你输入一个100mV的信号结果读出200mV多半是PGA增益设置为1倍但你以为开了16倍。也就是说寄存器0的GAIN位写错或者根本没写进去。建议写完配置后回读一下寄存器内容确认。还有就是参考电压理解问题。满量程不是2.048V而是2.048V×2因为差分输入支持正负两个方向。计算LSB时一定要用2×VREF而不是VREF。很多例程在这个地方吃了亏换算出来的电压偏小一半。另外一个容易忽略的点是PGA旁路位。当PGA_BYPASS1时PGA被旁路增益设置无效输入阻抗也下降。在驱动代码里这个位一般保持0除非你确定信号幅度足够大不需要放大。4.4 DRDY引脚不拉低、读不到新数据的处理如果DRDY一直保持高电平转换结果永远不更新重点看START命令有没有发。ADS1220上电后默认是空闲状态没有收到START命令不会主动开始转换。还有一个常见坑读数据时SPI时钟不够导致通信超时或DRDY一直被占用。有些实现会一直拉低CS不放而正确做法是每次读取完成后要把CS拉高让芯片恢复时序状态。检查这一点问题往往就解决了。5. 扩展应用显示、存储与其他器件驱动5.1 OLED与数码管显示转换结果ADS1220转换出来的数据最终要让人看得见最常见的搭配就是SPI接口的OLED屏比如SSD1306驱动的0.96寸屏。两者可以挂在同一条SPI总线上片选分开就行。注意OLED通常用模式0CPOL0, CPHA0而ADS1220要求模式1CPOL0, CPHA1两个设备挂在同一条SPI总线上时要小心切换时钟相位或者给两者分配不同的SPI外设。如果项目用的是0.36寸三位半数码管一般用TM1621这类LCD驱动芯片或者TM1628这种LED驱动芯片。驱动逻辑不复杂先把ADS1220的工程量转成浮点小数再拆出个位、十位、百位和正负号送显即可。显示刷新率不用太高10Hz左右就很好看。5.2 与LIS2DW12、SST25VF080B等器件共享SPI总线的注意事项做整机产品时一块主板上往往不止一个SPI设备。比如LIS2DW12加速度计是SPI接口SST25VF080B是SPI接口NOR Flash再加上ADS1220三个设备挂一条总线的情况很常见。这时候每个设备需要独立的CS引脚并且驱动中要保证任何时刻只有一个CS被拉低。共享SPI总线要注意两点第一各个器件的SPI模式可能不同通信前要把SPI配置切换成对应模式第二一些器件在CS拉高后还会继续驱动MISO引脚一小段时间如果下一个设备立刻开始通信会收到干扰字节。稳妥的做法是在每次片选切换之间加一个小的延时或者确保器件都支持三态输出。我对SST25VF080B写Flash时会格外留意SPI模式它是模式0而ADS1220是模式1所以在驱动里面单独封装一个SPI模式切换函数每操作一个器件前先配置好对应参数实测很稳。5.3 移植到其他MCU平台的要点除了STM32的HAL库现在很多项目也会用中微SC8F096BUZ、GD32、华大等MCU。移植ADS1220驱动代码核心就是替换三件事SPI收发函数、GPIO读写函数、延时函数。寄存器配置逻辑和24位补码换算逻辑都可以原样保留。以中微SC8F096BUZ这类国产8位或32位MCU为例需要注意SPI外设寄存器配置是否支持CPHA1部分低端MCU的SPI外设只有模式0和模式3这时可以通过软件模拟SPI时序解决。软件模拟SPI虽然速度慢但对于ADS1220这种最大才4MHz的芯片来说完全够用。我的经验是在硬件SPI配置不便的平台上直接用GPIO翻转模拟SPI反而更容易调通。另外一个移植坑是数据类型长度。ADS1220的转换结果是24位在32位MCU上int32_t没问题但在一些8位MCU上要注意编译器对unsigned int的处理。统一用int32_t和uint8_t声明变量可以避免跨平台时出现长度不一致的问题。最后说几句ADS1220这颗芯片硬件设计上坑不多真正的门槛都在驱动代码和调试上。寄存器配置看起来繁琐但只要理解了每个位的作用配置值其实可以从应用场景一步步推出来。我在实际项目里最深的体会是拿到一片新ADC先不要急着写代码先把输入信号范围和参考电压定下来再倒推增益和速率这样寄存器配置几乎是水到渠成的事。如果后面有空我打算把冷端补偿算法和标定流程也整理出来那两个部分才是热电偶应用里真正耗时间的环节。本文还有配套的精品资源点击获取
