ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

软件模拟SPI驱动ADS1220:GPIO手搓24位ADC采样方案

软件模拟SPI驱动ADS1220:GPIO手搓24位ADC采样方案

简介:面向STM32F103xx开发者的ADS1220软件模拟驱动包,以纯软件方式模拟SPI时序,完成高精度ADC的初始化、工作模式配置、数据转换与结果读取,解决无硬件SPI或低成本采集场景下的模拟量输入需求。ADS1220为24位低功耗模数转换器,常用于温度、压力、应变等小信号测量,与STM32F103xx配合后适用于工业测量、仪器仪表等嵌入式项目。包内共172个文件,压缩包大小5.22MB,以Keil MDK工程为核心,包含35个C源文件、34个头文件、工程配置(uvprojx/uvoptx)、分散加载描述(sct)以及编译生成的hex、axf、lst文件;其中d、o、crf等属于编译中间产物,不影响源码阅读与重新编译。资源已有794人学习/下载,适合正在调试ADS1220或希望参考软件模拟SPI驱动框架的开发者。通过它可拿到完整工程源码、头文件、Keil工程配置与烧写固件,能直接对照寄存器配置和时序控制代码理解模拟SPI的实现思路;代码基于STM32标准外设库,便于移植到其他型号,也可自行扩展连续转换、单次转换、PGA增益配置等功能,整体结构清晰,是高精度ADC驱动开发的一份实用参考。

1. 软件模拟 ADS1220 驱动:硬件 SPI 不够用时的 24 位采样方案

接手过一个称重采集项目,MCU 的硬件 SPI 被 Flash 和显示屏占满,剩下的引脚只剩几个普通 GPIO,可偏偏要外挂一片 24 位 ADC 做称重传感器的信号采集。当时第一反应是换 MCU,但结构、成本都定死了,只能硬着头皮用 GPIO 手搓 SPI 时序去驱动这颗 ADS1220。做完之后发现,软件模拟 ADS1220 并没有想象中那么玄学:这颗芯片的 SCLK 最高只有 2MHz,GPIO 翻转完全跟得上,真正容易翻车的反而是时序细节、寄存器配置和 DRDY 引脚的状态判断。这篇笔记就是把这条路走通的血泪经验整理出来,给那些遇到硬件 SPI 被占用、引脚复用冲突、或者想省一个 SPI 外设的开发者一个可以直接照着做的方案。新手能跟着把驱动跑起来,熟手可以直接跳过基础部分看参数配置和避坑清单。

2. ADS1220 寄存器与 SPI 时序:写驱动前必须读懂的 4 个配置字节

2.1 寄存器概览:CONFIG0 到 CONFIG3 各管什么

ADS1220 是一颗 24 位、4 通道、带 PGA 增益的低功耗 ADC,控制逻辑比想象中简单:所有功能都浓缩在 4 个 8 位配置寄存器里,地址从 0x00 到 0x03。驱动工作的本质,就是把这 4 个寄存器写对、再按时序把 24 位转换结果读回来,没有复杂的命令状态机。

CONFIG0 寄存器管输入多路开关和增益。高 4 位选择通道和输入模式,比如差分输入 AIN0/AIN1、AIN2/AIN3,或者单端输入;低 4 位配置 PGA 增益,增益值从 1 到 128 共 8 档。用表格看更清晰:

位名称作用
7:4MUX输入通道选择(差分/单端)
3:0PGA增益配置:0000=1、0001=2…0111=128

CONFIG1 寄存器管数据速率、滤波器模式和转换模式。数据速率从 20SPS 到 2000SPS 可调,还能选择 50Hz/60Hz 工频抑制滤波器,这些参数直接影响采样噪声表现。转换模式支持单次转换和连续转换,单次转换适合电池供电的低功耗场景,连续转换则省去每次发送 START 命令的麻烦。

CONFIG2 寄存器管参考源和传感器激励。可以选择内部 2.048V 参考电压,也可以选择外部参考引脚;还能开启两个恒流源用于 PT100、热敏电阻这类需要激励电流的传感器测量。CONFIG3 寄存器则负责空闲输入通道的固定偏置、基准电压检测和复位功能。驱动初始化时,把这 4 个寄存器一次写完,芯片就进入用户配置好的工作状态。

2.2 SPI 时序与命令字节:为什么软件模拟完全可行

ADS1220 的 SPI 时序对于 GPIO 模拟来说非常友好。它的 SCLK 最高 2MHz,这个频率下普通 MCU 的 GPIO 翻转延时完全可以满足;命令格式也清晰,无非是读寄存器、写寄存器、读转换数据、复位、同步这几种。通信时 CS 拉低,SCLK 空闲为低电平,数据在时钟边沿被采样,常见的实现配置是 SPI 模式 1,也就是 CPOL=0、CPHA=1。我一般会先在逻辑分析仪上确认一下芯片手册里的时序图,再写代码,毕竟不同批次的手册细节可能有差异,但绝大多数情况模式 1 是正确的。

命令字节的定义是驱动里最容易写错的地方。查询数据手册的命令定义部分,几个关键命令如下:复位命令发送 0x06,启动转换发送 0x04,读数据命令发送 0x10 然后读取 24 位;寄存器读命令 RREG 的基础字节是 0x20,寄存器写命令 WREG 的基础字节是 0x40。读或写多字节寄存器时,需要用「起始地址」和「寄存器个数减一」两个字节来组织命令。

#define ADS1220_CMD_RESET 0x06 #define ADS1220_CMD_START 0x04 #define ADS1220_CMD_RDATA 0x10 #define ADS1220_CMD_RREG 0x20 #define ADS1220_CMD_WREG 0x40 #define ADS1220_REG_CONFIG0 0x00 #define ADS1220_REG_CONFIG1 0x01 #define ADS1220_REG_CONFIG2 0x02 #define ADS1220_REG_CONFIG3 0x03

上面的宏定义里,命令基础字节和寄存器地址分开定义,组命令时用按位或合成最终字节。比如写 CONFIG0 就发送ADS1220_CMD_WREG | 0x00,即 0x40;读 CONFIG2 就发送ADS1220_CMD_RREG | 0x02,即 0x22。有些驱动会把 WREG 写成固定的 0x40 然后第二个字节放寄存器数减一,这种写法与宏定义不冲突,只是可读性差一些。建议把命令定义和寄存器地址分开,方便后续维护。

还有一点容易被忽略的是寄存器写入个数字节。ADS1220 的 WREG 命令序列是:第一个字节写命令加起始地址,第二个字节写「要写的寄存器数量减一」,之后才是实际数据。如果只写一个寄存器,第二字节就是 0x00。读命令同理,发送完命令和数量字节后,依次读取数据。这个「数量减一」的格式和很多其他 SPI 芯片不同,容易踩坑。

3. GPIO 手搓 SPI 读写 ADS1220:核心驱动函数的逐段实现

3.1 底层字节收发:GPIO 初始化与位翻转

软件模拟 SPI 的第一步是把 SCLK、MOSI、MISO、CS 四根线对应的 GPIO 配置好。SCLK、MOSI、CS 是输出模式,MISO 是输入模式。需要注意的是 GPIO 输出速度不能配得太低,否则边沿太缓,ADS1220 在 2MHz 下可能采样出错;一般配到最高速度档位,同时开启内部上拉或下拉保持默认电平。

void ads1220_gpio_init(void) { GPIO_InitTypeDef gpio; gpio.Mode = GPIO_MODE_OUTPUT_PP; // SCLK/MOSI/CS 推挽输出 gpio.Pull = GPIO_PULLDOWN; // 默认拉低,SCLK 空闲低电平 gpio.Speed = GPIO_SPEED_FREQ_HIGH; // 高速翻转,保证时序边沿 gpio.Pin = ADS1220_PIN_SCLK; HAL_GPIO_Init(ADS1220_SCLK_PORT, &gpio); gpio.Pin = ADS1220_PIN_MOSI; HAL_GPIO_Init(ADS1220_MOSI_PORT, &gpio); gpio.Pin = ADS1220_PIN_CS; HAL_GPIO_Init(ADS1220_CS_PORT, &gpio); gpio.Mode = GPIO_MODE_INPUT; // MISO 输入 gpio.Pull = GPIO_PULLUP; gpio.Pin = ADS1220_PIN_MISO; HAL_GPIO_Init(ADS1220_MISO_PORT, &gpio); }

上述代码里,SCLK 默认拉低,这是配合模式 1 时序的必要条件。GPIO 速度配 HIGH 级别,是为了让 SCLK 在 2MHz 时边沿足够陡。如果你用的 MCU 不支持 HAL 库,换成寄存器操作也是同样的思路:方向寄存器设输出,速度寄存器设最高档。

基础的字节收发是整个软件模拟 SPI 的心脏。发送一个字节时,从最高位开始逐位输出;接收一个字节时,从最高位开始逐位读取 MISO。注意 ADS1220 是 24 位 ADC,所以数据传输经常是 3 字节,但底层字节函数必须保证每一位都在正确的时钟边沿被采样。

void ads1220_spi_write_byte(uint8_t data) { for (int i = 7; i >= 0; i--) { ADS1220_SCLK_LOW(); if (data & (1 << i)) ADS1220_MOSI_HIGH(); else ADS1220_MOSI_LOW(); ADS1220_SCLK_HIGH(); // 上升沿,主机发送数据 } ADS1220_SCLK_LOW(); } uint8_t ads1220_spi_read_byte(void) { uint8_t value = 0; for (int i = 7; i >= 0; i--) { ADS1220_SCLK_LOW(); ADS1220_SCLK_HIGH(); // 下降沿,主机采样 MISO if (ADS1220_MISO_READ()) value |= (1 << i); } ADS1220_SCLK_LOW(); return value; }

这里最关键的是时钟边沿的方向。写字节时,先把 SCLK 拉低,设置 MOSI 电平,再拉高 SCLK,这样数据在上升沿被芯片采样;读字节时,拉低 SCLK,然后拉高,芯片在下降沿把数据放到 MISO 上,主机在 SCLK 低电平阶段读取。两个函数的节奏配合必须一致,否则数据会整体错位。另外有些驱动会在每发完一字节后加几个空循环延时,对于 2MHz 以内的速率,现代 MCU 的 GPIO 翻转速度足够,不需要额外延时;但如果你移植到很慢的 MCU 或者用 GPIO 模拟时钟频率超过 1MHz,适当插入空循环会更稳。

CS 的控制比较简单,通信开始前拉低,通信结束后拉高。实际项目中,连续读寄存器或读转换数据时,CS 可以一直保持低电平,不需要每字节都重新拉一次。

void ads1220_cs_low(void) { ADS1220_CS_LOW(); } void ads1220_cs_high(void) { ADS1220_CS_HIGH(); }

3.2 寄存器读写命令封装:地址、数量、数据三段式

有了底层字节函数,寄存器读写命令就顺理成章了。写寄存器时,先拉低 CS,发送命令字节和数量字节,再发送配置数据,最后拉高 CS。这个过程的时序要求是 CS 低电平期间 SCLK 连续翻转,中间不能有太长的停顿,否则芯片可能判定通信结束。

void ads1220_write_reg(uint8_t reg_addr, uint8_t data) { ads1220_cs_low(); ads1220_spi_write_byte(ADS1220_CMD_WREG | reg_addr); ads1220_spi_write_byte(0x00); // 写 1 个寄存器:数量减一等于 0 ads1220_spi_write_byte(data); ads1220_cs_high(); }

第二个字节固定写 0x00,表示只写一个寄存器。如果要连续写多个寄存器,就需要把数量减一的值写在这里,然后依次发送各个寄存器数据。写完后可以立刻进入下一步操作,但如果是复位之后第一次配置,建议等一下 DRDY 引脚状态或者加一个小延时,让芯片内部完成初始化。

读寄存器是对称的操作:发送读命令和数量字节,然后读取一个或连续多个字节。注意读的时候,MOSI 上不需要继续输出数据,但 SCLK 必须继续翻转,否则芯片不会输出。

uint8_t ads1220_read_reg(uint8_t reg_addr) { uint8_t value = 0; ads1220_cs_low(); ads1220_spi_write_byte(ADS1220_CMD_RREG | reg_addr); ads1220_spi_write_byte(0x00); // 读 1 个寄存器 value = ads1220_spi_read_byte(); ads1220_cs_high(); return value; }

这段代码在调试阶段非常重要。写完配置寄存器后,建议立刻把它们读回来比对,确认写入值和读回值一致。很多时候驱动「看起来在跑」但采样数据完全不对,其实是寄存器没写进去,原因可能是 CS 时序不对、命令字节拼错或者 GPIO 方向配置错误。读回校验是最快的排错手段。

3.3 读转换结果:DRDY 轮询与 24 位数据拼接

ADS1220 完成一次转换后,会把 DRDY 引脚拉低。软件模拟驱动最常用的方式是轮询 DRDY:检测到低电平后,通过 SPI 读取 24 位数据,DRDY 会自动恢复高电平。单次转换模式下,每次读完数据后芯片进入空闲状态;要再次启动转换,需要发送 START 命令。

int32_t ads1220_read_data(uint32_t timeout_ms) { uint32_t start_tick = HAL_GetTick(); // 等待 DRDY 拉低,表示转换完成 while (ADS1220_DRDY_READ() != 0) { if (HAL_GetTick() - start_tick > timeout_ms) return ADS1220_TIMEOUT; // 超时保护,返回错误码 } uint32_t raw = 0; ads1220_cs_low(); ads1220_spi_write_byte(ADS1220_CMD_RDATA); // 发送读数据命令 raw = (uint32_t)ads1220_spi_read_byte() << 16; raw |= (uint32_t)ads1220_spi_read_byte() << 8; raw |= (uint32_t)ads1220_spi_read_byte(); ads1220_cs_high(); // 24 位有符号数转 int32 int32_t value = (int32_t)(raw << 8) >> 8; return value; }

这里最容易翻车的是 24 位有符号数的类型转换。raw是 uint32_t,如果直接强转 int32_t,符号位不会自动扩展,正的 0x7FFFFF 没问题,但负的 0x800000 会被当成 8388608,而不是 -8388608。正确做法是先把 raw 左移 8 位,把符号位顶到 int32 的最高位,再算术右移 8 位恢复原始数值。这一行写错,整个 ADC 的读数就会在零附近来回乱跳,而且数值越大误差越离谱。

DRDY 轮询的超时值也要合理设置。假设数据速率配置为 20SPS,一次转换最长需要约 50ms,超时设 200ms 是比较安全的;如果配置为 2000SPS,转换只要 0.5ms,超时设 100ms 足够。超时返回值建议用一个明显异常的数,比如0x7FFFFFFF或自定义的宏,避免和正常 ADC 数值混淆。

4. 采样参数配置实战:增益、速率与滤波器怎么搭配

4.1 PGA 增益与满量程:选错档位会让有效位数白白丢失

ADS1220 内置 PGA 可编程增益放大器,增益范围为 1 到 128,共 8 档。增益的作用是把小幅度的传感器信号放大到接近满量程,从而充分利用 24 位分辨率。以内部 2.048V 参考电压为例,不同增益下对应的满量程输入范围如下:

PGA 增益满量程输入范围1 LSB 对应电压
1±2.048V0.244µV
8±0.256V0.031µV
16±0.128V0.015µV
64±0.032V0.004µV
128±0.016V0.002µV

选增益的核心原则是让目标信号尽量占满量程的 50% 到 90%。比如称重传感器的满量程输出通常是 2mV/V,5V 激励下满量程 10mV,选增益 128 后信号被放大到 1.28V,接近但不超过满量程,这样 24 位分辨率发挥得最充分。如果信号太小却选了低增益,读数会在低位大幅跳动,有效位数可能只有 12 位,白白浪费了这颗 ADC 的精度。

我用表格来说明一个常见选型场景:PT100 在 0℃ 时电阻 100Ω,1mA 激励下电压 0.1V;温度到 100℃ 时电阻约 138.5Ω,电压 0.1385V。差分信号变化只有约 38.5mV,选增益 32 或 64 比较合适。反过来说,如果你直接测一个 0 到 5V 的信号,就必须用外部参考或分压,增益只能选 1。

4.2 数据速率与滤波器:20SPS 到 2000SPS 怎么权衡

数据速率配置在 CONFIG1 寄存器中,直接影响噪声表现和 50Hz/60Hz 工频抑制能力。ADS1220 的可选速率从 20SPS 到 2000SPS,配合内置数字滤波器,不同速率下的噪声指标差异很大。速率越低,数字滤波器平均的点数越多,噪声越小;速率越高,响应越快,但噪声也会明显增大。

数据速率典型有效分辨率工频抑制
20SPS接近 24 位同时抑制 50Hz 和 60Hz
45SPS约 22 位抑制 50Hz 较好
90SPS约 21 位抑制 60Hz 较好
340SPS 以上约 19 位以下基本无工频抑制能力

实际项目中,采集称重、温度这类缓变信号,我一般选 20SPS 或 45SPS,噪声表现最好,而且 50Hz 工频干扰能被有效滤除。如果你要采集的是振动信号或者需要快速响应的场景,那就得被迫接受高速率下的噪声,这时候可以配合外部硬件滤波来弥补。这个取舍必须在项目需求阶段就确认清楚,否则等硬件做好了再改速率配置,往往发现噪声压不下去。

滤波器的选择还涉及一个容易忽略的点:ADS1220 的数据速率改变后,第一次转换结果需要等待滤波器稳定,通常要丢几个点。驱动初始化后不要立刻采数据,先空读几次或丢弃前几个转换值,否则初始数据会偏大。

4.3 参考电压选择:内部参考省事,外部参考更稳

CONFIG2 寄存器里可以选择内部 2.048V 参考或外部参考输入。内部参考的好处是省外围器件、上电即可用,适合精度要求不特别高的场景;缺点是温漂和初始精度有限,而且它是从 AVDD 分压得到的,如果 AVDD 纹波大,参考电压也会跟着抖。外部参考一般用专用的参考芯片,温漂可以做到很低,适合称重、高精度温度测量这类需要长期稳定性的场景。

驱动代码里配置参考源的位域就在 CONFIG2 寄存器的 bit7 和 bit6。内部参考对应二进制01,外部参考对应10或11。选择外部参考时,REF 引脚上的电压必须稳定,去耦电容要靠近引脚放置,至少 10µF 电解电容并联 100nF 陶瓷电容。我用过一个项目,用了内部参考后数据在 1 小时内漂了十几个码,换成外部参考后漂移立刻小了一个数量级,这个代价是值得的。

4.4 一次完整的 4 字节初始化配置

把以上三个小节综合成一个实际配置:单次转换模式、差分输入 AIN0/AIN1、增益 32、数据速率 20SPS、内部参考。对应 CONFIG0 到 CONFIG3 的字节如下:

uint8_t config0 = 0x00; // MUX=0000, AIN0/AIN1 差分输入 config0 |= (4 << 0); // PGA=0100, 增益 32 uint8_t config1 = 0x04; // DR=010, 20SPS, 单次转换模式 config1 |= (0 << 4); // 不使能 50/60Hz 同时抑制吗? 见下方说明 uint8_t config2 = 0x10; // 内部参考 2.048V, 关闭电流源 uint8_t config3 = 0x00; // 默认, 空闲通道接 AVDD // 写入寄存器, 注意从 CONFIG0 开始连续写 4 个 ads1220_cs_low(); ads1220_spi_write_byte(ADS1220_CMD_WREG | 0x00); ads1220_spi_write_byte(0x03); // 连续写 4 个寄存器: 4-1=3 ads1220_spi_write_byte(config0); ads1220_spi_write_byte(config1); ads1220_spi_write_byte(config2); ads1220_spi_write_byte(config3); ads1220_cs_high();

上面代码里的config1位域实际含义要翻手册确认:数据速率位通常占 bit7:5,增益位占 CONFIG0 的 bit3:0。不同的参考手册可能把位域顺序画得不一样,写代码前必须对着数据手册的寄存器表逐位核对,不要凭记忆填值。这里给的字节值是常见配置,但如果你手里的芯片是 B 版本或更晚的硅片,个别位域定义可能有增补,务必以手册为准。写完后,立刻把 CONFIG0 到 CONFIG3 读回来,和写入值逐位比对,确认无误后再启动转换。

5. ADS1220 驱动避坑指南:DRDY 异常与数据漂移的 5 个排查点

5.1 DRDY 引脚永不拉低:转换根本没启动

现象:程序烧进去后,轮询 DRDY 一直超时,要么卡死,要么反复返回超时错误码。用示波器量 DRDY 引脚,发现始终保持高电平,没有任何脉冲。

原因:最常见的是复位后没有发送 START 命令。ADS1220 上电后处于空闲状态,不会自动开始转换;即使配置了寄存器,也必须发送 START 命令(0x04)才能触发第一次转换。另一个原因是复位命令或配置命令本身没被正确接收,芯片仍在默认状态。

解决:上电后先延时 100ms 等待芯片内部振荡器稳定,然后发送复位命令 0x06,再等待 100ms,写入配置寄存器,最后发送 START 命令。每次写完寄存器后建议把 DRDY 引脚重新确认一下,看是否有下降沿。如果还是没有,用逻辑分析仪抓 SPI 波形,重点看 CS 低电平期间命令字节是否发送完整。

5.2 读回来的 24 位数据整体乱跳:符号位没处理

现象:输入悬空或接零信号时,读回的数据在正负大数之间随机跳动,比如 0x7FFFFF 和 0x800000 之间来回切换,完全没有稳定数值。

原因:这是把 24 位无符号拼接结果直接强转 int32 导致的。24 位 ADC 的转换结果最高位是符号位,但uint32_t类型里它只是普通数据位,直接赋值后,CPU 不会自动把它扩展成 int32 的符号位。结果就是正的极限值 0x7FFFFF 和负的极限值 0x800000 在无符号视角下只差 1,但转成有符号后一个巨大正值一个巨大负值。

解决:使用 3.3 小节提到的左移 8 位再右移 8 位的方法,先让符号位占据 int32 的最高位,再算术右移恢复。代码如下:

int32_t value = (int32_t)(raw << 8) >> 8;

这一行代码写错的人不少,排查最快的方法是打印 raw 原始值和转换后的 value,对比零输入时是否都接近 0。如果原始值接近 0x800000 而转换值接近 -8388608,那基本就是符号扩展的锅,修复后立刻恢复正常。

5.3 低增益下读数稳定,高增益下低位狂跳

现象:配置为增益 1 时读数平稳,但切到增益 64 或 128 后,数据低 4 位甚至低 8 位持续无规律跳动,完全无法用。

原因:高增益会把噪声同步放大。电源纹波、参考电压噪声、PCB 走线耦合进来的干扰,在增益 128 下会被放大一百多倍,24 位 ADC 的低位自然被噪声淹没。另一个常见原因是传感器的激励电流源配置不合理,导致传感器信号叠加了共模噪声。

解决:先检查模拟电源和参考电压的纹波,AVDD 上用磁珠加 10µF 电容滤波,参考电压引脚并 100nF 和 1µF 电容。PCB 布局上,模拟信号走线尽量短且远离数字信号线,MISO/MOSI/SCLK 这类 SPI 线不要与模拟输入平行走线。软件上,降低数据速率到 20SPS,开启内置滤波器,能明显改善低位跳动。如果还不行,检查传感器激励电流是否匹配传感器阻值范围,电流过大会导致传感器自热和噪声放大。

5.4 软件模拟数据偶尔错位:SCLK 边沿采反了

现象:大部分时间读数正常,但偶尔读回来的值是上一次的旧值,或者数据整体像被移动了 8 位。用逻辑分析仪抓波形,发现 SCLK 和数据关系看起来是对的,但芯片手册和实际代码对不上。

原因:SPI 模式配置错误。ADS1220 常见的用法是模式 1,也就是 CPOL=0、CPHA=1,数据在 SCLK 下降沿变化、上升沿被采样。如果你的代码写成模式 0 或模式 3,大部分位还能猜对,但边界处的移位就会偶发错位。还有一个隐藏坑:GPIO 模拟时,读字节函数里 SCLK 拉高后立刻读 MISO,有些 MCU 的 GPIO 输入在边沿附近采样不稳定,需要加 1-2 个 NOP 延时再读。

解决:用逻辑分析仪对比芯片手册的时序图,确认 SCLK 空闲电平是低还是高、数据在哪个边沿有效。读字节函数里,在 SCLK 拉高后加两个空指令再读取 MISO,给 GPIO 输入电路一点稳定时间。如果改用硬件 SPI 做对照测试,确认驱动逻辑本身没问题,再回头优化软件模拟的时序。

5.5 多次重启后配置丢失:复位时序与寄存器写入顺序不对

现象:程序下载后第一次运行正常,但按复位键后读数全乱或 ADC 不工作;有时是写入的配置要反复写两遍才生效。

原因:复位后 ADS1220 还没准备好就收到了配置命令。芯片上电或复位后需要一个短暂的内部初始化时间,通常要几毫秒到几十毫秒;如果在这期间发送 SPI 命令,芯片会忽略或部分接收。另一个可能是 CS 信号在复位期间被噪声拉低,芯片误收到随机数据进入异常状态。

解决:驱动初始化代码里,上电后先延时 100ms,再发送复位命令,再延时 50ms,之后才写配置寄存器。如果复位引脚由 MCU 控制,确保复位释放后也延时。日常运行时如果不需要复位芯片,建议只在初始化时发一次复位命令;每次写寄存器前,可以先发一个 NOP 或读一次寄存器确认芯片在线,避免因为通信异常导致配置静默失败。最后检查 CS 引脚的默认电平,不要在芯片未选中时被干扰信号拉低。

6. 把软件模拟驱动跑得更稳:校验、回调与性能边界

驱动基本跑通后,下一步就是围绕稳定性和响应速度做工程化收尾。我自己的习惯是:任何寄存器的写入之后,紧跟着读回并比对,发现不一致就重试三次,三次都失败就上报错误,绝不带着错误配置继续采样。这个习惯救过我很多次,尤其是在现场调试时,硬件连接松动导致 SPI 通信间歇性异常,如果配置静默失败,后面所有的数据都是垃圾,而你却以为系统在正常工作。

中断设计上,软件模拟 SPI 和硬件 SPI 的差别在于:模拟方式不能进入中断回调去读寄存器,因为 GPIO 翻转时序不能被打断。常见做法是 DRDY 引脚接 MCU 的外部中断,中断里只置一个标志位,主循环检测到标志后执行完整的 SPI 读取流程。这样既保证了时序不被中断破坏,又避免了主循环空转等待 DRDY 浪费 CPU。对于 20SPS 的低速率场景,CPU 占用率不到 1% 完全够用。

还有一个实际项目中踩过的坑:软件模拟 SPI 的时钟频率完全由 MCU 主频和代码效率决定,不同编译优化等级下同一份代码的 GPIO 翻转速度可能差 30% 以上。所以在性能边界上,不要只看 2MHz 的标称值,实际 SCLK 频率要在示波器上测一下,确保低于芯片上限。如果移植到更低主频的 MCU,适当在每字节后加延时,把 SCLK 压到 500kHz 到 1MHz,稳定性和兼容性反而更好。

最后建议保留一个「配置快照」功能:把 4 个配置寄存器当前值保存到非易失存储里,每次上电读取并和实际写入值比对。ADS1220 的寄存器是易失的,掉电即失,但通过比对可以发现 MCU 启动时序差异、外部干扰导致的寄存器意外改写。某次排查一个数据随机跳变的问题时,就是通过这个快照功能发现稳定的问题——不是 ADC 本身的问题,而是某个 GPIO 在这组配置下被复用成了片上外设功能,导致 CS 信号被周期性干扰。排查最后靠的是一根逻辑分析仪,结论很简单,但过程让我养成了新板子调试先读回校验、再跑数据流的习惯。希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进