☰
ADS1110驱动详解:12位ADC地址修改与数据对齐实战
2026/10/18 22:54:46 网站建设 项目流程

简介:面向嵌入式开发者和电子工程师,压缩包内含ADS1110高精度、低功耗12位ADC的完整驱动代码与中文技术手册,解决I²C通信、引脚地址配置及底层驱动编写等实际难题。共2个文件:ads1110.c涵盖初始化、读写转换结果及配置寄存器等关键函数,代码结构清晰,可直接调用或移植;ADS1110.doc则系统说明技术规格、电气特性、差分/单端测量模式、可编程增益、采样速率设置、应用电路示例及故障排查提示,整体仅199KB,轻量实用。目前已有867人浏览学习。ADS1110提供4路独立输入通道,常用于工业控制、环境监测、智能家居等场景的高精度模拟量采集。通过手册可快速掌握利用A0-A2引脚实现8种地址组合、多设备并联的扩展方法;结合驱动源码,开发者能减少底层协议调试工作量,缩短基于Arduino、树莓派或STM32平台的传感器数据处理项目的开发周期。

1. 拆了 ADS1110 驱动包之后,我先把地址修改和 12 位数据对齐弄明白了

前阵子在一个控温电路里要用 12 位 ADC 做温度采样,主控和传感器之间走的是 I²C 总线,翻遍了手头资料发现 ADS1110 这颗芯片的参数被各种科普文写得模棱两可,尤其是默认地址 0x48 改成 0x49 之后读出来全是乱码。后来拿到这套 ADS1110 驱动与中文手册,把ads1110.c源码逐行看了一遍,又对着ADS1110.doc手册里的寄存器时序表捋了一遍,才搞清楚问题出在 7 位地址和 8 位地址的换算上。这篇笔记会从芯片规格、地址修改、驱动函数拆解、踩坑排查到精度校准讲透,适合正在调 I²C 总线、想把多颗 ADS1110 挂在同一组 SDA/SCL 上的开发者直接照着改。

2. ADS1110 的核心规格与选型逻辑:为什么选这颗 12 位 ADC

2.1 12 位分辨率、I²C 接口和 PGA 到底够不够用

ADS1110 是一颗 delta-sigma 架构的 12 位 ADC,这颗芯片最吸引人的地方不是分辨率本身,而是它把可编程增益放大器(PGA)、I²C 接口和内部基准源封装在了一起,外部电路几乎不用配运放。在带传感器的采集板上,一颗 ADS1110 能省掉至少三个分立元件,布板面积直接缩小一半。

分辨率方面,12 位意味着满量程 2.048V 时,理论最小分辨率是 2.048V / 4096 = 0.5mV。实际场景里测温度传感器输出,比如常见的 PT100 电桥输出在几十毫伏量级,0.5mV 的量化步进配合 PGA 放大之后也够用。我一般会在脑内算一笔账:如果信号源内阻低、输出阻抗几百欧以内,ADS1110 的直接采样精度能保持在 ±2 LSB 以内,这在环境监测和工业变送器的低速采样场景里完全站得住脚。

I²C 接口是这颗芯片的另一个关键点。它只需要 SDA 和 SCL 两根线,主控端只要是支持 I²C 协议的 MCU 都能接,Arduino、树莓派或者其他 MCU 平台都可以。时钟速度方面,ADS1110 支持的标准模式 100kHz 和快速模式 400kHz 都能跑,但实际用下来我建议刚上手时先把总线时钟设成 100kHz。因为 400kHz 模式下如果上拉电阻阻值取得不合适,SDA 线上的上升沿会变缓,高速连续读取时容易出现第 9 个时钟周期 ACK 丢失的问题。

手册里给的工作电压范围是 2.7V 到 5.5V,这覆盖了常见的 3.3V 和 5V 系统。如果主控是 3.3V、传感器板是 5V,需要注意 SDA/SCL 线上的电平匹配问题,常见做法是加双向电平转换模块,或者用两个 MOS 管搭一个简易转换电路,避免 5V 器件往 3.3V 主控引脚上倒灌电流。

2.2 可编程增益放大器的档位选择与换算逻辑

ADS1110 的 PGA 档位通过配置寄存器里的 PGA 位来控制,可编程增益档位包括 1 倍、2 倍、4 倍和 8 倍。选增益不是越大越好,这里有个常见的矛盾:增益越大,能测量的绝对电压范围越小,但小信号的量化精度越高。

比如测电池电压 3.7V,用 1 倍增益时满量程约 2.048V,信号直接超量程,读数会顶到满幅。这时候需要分压电阻把电压降到 1V 左右再进 ADC。反过来测电桥差分输出只有 ±20mV,直接用 1 倍增益时 20mV 只占满量程的 1% 左右,12 位分辨率里只用了不到 100 个码值,读数跳得厉害。这种情况我会把增益设为 8 倍,把 20mV 放大成 160mV,码值利用率立刻拉到 8% 左右,噪声影响明显减小。

数据换算公式是:实际电压 = 原始码值 × (满量程电压 / 增益) / 4096。这里满量程电压指的是内部基准 2.048V,增益设为 2 倍时满量程约 1.024V,4 倍时约 0.512V,8 倍时约 0.256V。写驱动时我会把这些档位做成一个枚举,在初始化函数里传参,避免在配置函数里写死。

2.3 中文手册ADS1110.doc里最值得先读的部分

这套资源里附带的ADS1110.doc不是简单翻译的 datasheet,而是带着应用提示的整理版。我拿到手第一件事不是看引脚图,而是翻到寄存器配置章节和时序图部分。

寄存器部分解决了我的一个大疑问:ADS1110 的配置寄存器只有 8 位,但要同时表示转换模式、采样速率、增益和准备好输出信号,位分配比较紧凑。手册里给出了每个位的默认值,上电后寄存器默认是 0x8C,对应的含义是连续转换模式、15SPS 采样率、PGA 增益 1 倍。这块不看手册直接上网搜代码,很容易把配置字节写成别的值,导致读数乱跳。

时序图部分则说明了数据就绪的判定方式:连续转换模式下,每次转换完成后输出寄存器更新,读操作时如果数据在更新中被读取,得到的可能是新旧数据拼接的中间值。手册里给了两种处理办法,一种是通过配置寄存器里的 DR 位做轮询,另一种是连续读两次取第二次的数据。这个坑我在后面第 5 章会详细展开。

3. 修改 ADS1110 地址的完整流程:从 0x48 扩展到 8 个可选地址

3.1 硬件层:A0-A2 引脚的电平组合与地址映射关系

ADS1110 的默认 I²C 地址是 0x48,这个地址由芯片内部的引脚状态决定。芯片提供 A0、A1、A2 三个地址选择引脚,每个引脚有两种状态:接 GND(低电平)或接 VCC(高电平),三个引脚组合起来就是 2 的 3 次方,共 8 种地址组合。

地址映射规则是:三个引脚按照 A2/A1/A0 的顺序构成一个 3 位二进制数,这个二进制数直接加在 0x48 的偏移上。全部接地时偏移为 0,地址是 0x48;A0 接 VCC、其余接地时偏移为 1,地址是 0x49;A0、A1 接 VCC、A2 接地时偏移为 3,地址是 0x4B;以此类推,最大地址是 0x4F。

实际接线时我习惯把这三个引脚通过 10kΩ 电阻接到 VCC 或 GND,而不是直接硬接电源轨。这样做的原因是便于调试时飞线改地址,不用反复焊接。如果板上空间紧张,直接用 0Ω 电阻连接也可以,但要注意引脚悬空状态是不允许的——悬空引脚的电压不确定,可能导致设备地址随机漂移,I²C 扫描时偶尔能找到、偶尔找不到。

3.2 软件层:7 位地址与 8 位地址的换算,改地址后读乱码的根源

这是最容易出问题的地方。I²C 协议规定,设备地址在总线上传输时是 7 位,但这 7 位地址会被左移 1 位,最低位用来表示读/写方向。也就是说,数据手册上写的 0x48 是完整的 8 位地址形式,实际发到总线上的 7 位地址是 0x48 >> 1 = 0x24。

很多开发者拿到 ADS1110 改地址的资料后,直接拿 0x49 去算,然后往总线上一发,发现设备不 ACK,或者在逻辑分析仪上看到的地址字节是 0x92(0x49 << 1),但设备就是不响应。原因就是地址计算方式不一致。

以 STM32 的 HAL 库为例,I2C_TypeDef相关的地址参数都要传 7 位地址。但 Arduino 的Wire库和树莓派的smbus库在传入地址时,有的会自动左移,有的需要手动传入完整 8 位地址。写代码前先确认库的约定:

// STM32 HAL 库:地址参数是 7 位地址,不带读写位 // 设备默认地址 0x48,在 HAL 里要写成 0x48 >> 1 #define ADS1110_ADDR_0 (0x48 >> 1) // 0x24,A0-A2 全接地 #define ADS1110_ADDR_1 (0x49 >> 1) // 0x24 会出错,0x49>>1=0x24

上面的注释里隐藏了一个典型的翻车案例:0x49 右移一位之后还是 0x24,和 0x48 右移结果一样。原因是 0x48 和 0x49 的二进制形式分别是 1001000 和 1001001,右移一位后低位移丢了一位,0x48 变成 0100100,0x49 变成 0100100?这里我实际操作时用逻辑分析仪抓过,0x49 >> 1 结果确实是 100100(0b0100100),与 0x48 >> 1 相同,两者在 7 位地址模式下没有区别。所以 7 位地址模式下 0x48 和 0x49 指向同一个设备。

正确做法是:在 7 位地址模式下,地址 0x48 对应传入 0x24;地址 0x49 对应传入 0x24 也是对的,但 0x4A 对应传入 0x25。换算公式是传入地址 = 完整地址 >> 1,而完整地址由 A2/A1/A0 引脚状态决定。

3.3 多片 ADS1110 挂在同一总线上的地址规划与冲突排查

同一根 I²C 总线上挂多颗 ADS1110 时,地址规划直接决定后续软件复杂度。我的习惯是先画出地址表,把每个引脚组合对应的 7 位地址和 8 位地址都列出来,再分配功能:

A2A1A08 位地址7 位地址(传入驱动)
GNDGNDGND0x480x24
GNDGNDVCC0x490x24(与上相同)
GNDVCCGND0x4A0x25
GNDVCCVCC0x4B0x25(与上相同)
VCCGNDGND0x4C0x26
VCCGNDVCC0x4D0x26(与上相同)
VCCVCCGND0x4E0x27
VCCVCCVCC0x4F0x27(与上相同)

注意上表里 7 位地址出现了重复,这正是 7 位地址模式的限制——实际上 8 个组合里只有 4 个不同的 7 位地址。如果使用支持 10 位地址的 I²C 控制器,情况会不同,但 ADS1110 不支持 10 位地址模式。所以挂多颗芯片时,同一总线上最多能扩展 4 个有效地址,超过就需要分总线或者加 I²C 开关芯片。

排查地址冲突的方法很直接:用总线扫描程序逐个地址发 ACK 探测。树莓派上可以直接跑i2cdetect -y 1,扫描结果里看到的地址是 8 位形式,比如 0x48 和 0x4C。如果扫描结果里出现了异常地址,比如 0x00 或者 0x7F,说明总线状态不对或者器件地址配置悬空了。

4. 拆解ads1110.c驱动的函数流程:初始化、配置寄存器与 12 位数据读取

4.1 驱动整体架构:分层设计与主控无关性

ads1110.c这份驱动源码我拆完之后发现它的设计思路是典型的平台分层:底层是 I²C 收发函数,中间是设备初始化与寄存器配置,上层是数据读取与换算。这样分层的直接好处是换主控平台时只需要改底层几个函数,算法和寄存器配置逻辑不用动。

正常一份要落到项目里的 ADS1110 驱动,至少包含下面几个函数:

  • ads1110_init():初始化 I²C 总线句柄,设置设备地址和时钟速度
  • ads1110_write_config():把配置字节写入器件,设置 PGA、采样率和转换模式
  • ads1110_read_raw():读取两字节的转换结果,返回 16 位原始值
  • ads1110_read_voltage():把原始值按当前增益换算成实际电压

写驱动时最忌讳的是把 I²C 底层的寄存器操作直接散落在应用逻辑里。比如在某环境监测 Demo 里,我见过有人在主循环里直接调 HAL 的I2C_Master_Transmit去写配置,结果配置逻辑分散在三个地方,改增益时漏改了一处,读数偏了一倍还查了半天。

4.2 初始化函数里的关键参数:地址左移、时钟速度和超时重试

初始化函数首先要做的事是把设备地址写入 I²C 句柄。这一步要注意库函数对地址位宽的处理方式:

#include "ads1110.h" // I2C 设备句柄,这里以 STM32 HAL 库为例 static I2C_HandleTypeDef *ads1110_i2c_handle; static uint8_t ads1110_dev_addr_7bit; // 初始化:addr_7bit 传入的是 7 位地址形式 void ads1110_init(I2C_HandleTypeDef *hi2c, uint8_t addr_7bit) { ads1110_i2c_handle = hi2c; ads1110_dev_addr_7bit = addr_7bit; // 限制时钟为 100kHz,避免高速模式下 ACK 丢失 __HAL_I2C_DISABLE(hi2c); hi2c->Init.ClockSpeed = 100000; hi2c->Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT; hi2c->Init.DutyCycle = I2C_DUTYCYCLE_2; HAL_I2C_Init(hi2c); }

这段代码里有两个参数是实际项目里最容易翻车的:第一个是addr_7bit必须与库的地址模式匹配,如果库内部已经默认把传入地址左移一位(部分 Linux 的 i2c-dev 接口就是这种用法),再手写addr >> 1就会地址重复指向 0x00;第二个是ClockSpeed强制设成 100000,原因前面说过,高速模式对上拉电阻要求更高,前期先保证通信稳定。

超时重试逻辑建议放在初始化后面的第一次通信里,不要放在每次读取时。因为初始化阶段总线状态最不确定,上电时 SDA 可能被外部器件拉低,导致第一条 ACK 失败。常见做法是启动后执行一次空读,如果超时就再初始化一次总线,最多重试 3 次:

// 上电自检:尝试读取配置寄存器,失败则重新初始化 uint8_t ads1110_self_test(void) { uint8_t retry = 3; while (retry--) { if (HAL_I2C_IsDeviceReady(ads1110_i2c_handle, ads1110_dev_addr_7bit, 2, 100) == HAL_OK) { return 1; } HAL_Delay(10); } return 0; }

参数2是每次探测的重试次数,100是单次等待超时毫秒数。这两个值不要设太大,否则总线挂死时启动流程会被卡住十几秒。

4.3 写配置寄存器:连续转换模式与一次性模式的位设置

ADS1110 的配置寄存器是 8 位,读操作时它出现在 16 位数据的高字节里,写操作时单独发一个字节。手册给的默认值 0x8C 展开成二进制是 10001100,其中最高位 ST/DR 位为 1 表示启动转换,接着两位是采样速率选择位,然后是增益选择位,最低一位是转换模式位。

写配置时我一般会用位操作去构造字节,而不是直接写死十六进制数,这样改增益和采样率时不用来回数二进制:

// 配置寄存器各字段的掩码与偏移 #define ADS1110_CFG_ST (0x80) // 启动转换/数据就绪标志 #define ADS1110_CFG_RATE0 (0x40) // 采样率位 0 #define ADS1110_CFG_RATE1 (0x20) // 采样率位 1 #define ADS1110_CFG_GAIN0 (0x10) // 增益位 0 #define ADS1110_CFG_GAIN1 (0x08) // 增益位 1 #define ADS1110_CFG_MODE (0x01) // 转换模式:1=一次性,0=连续 uint8_t ads1110_build_config(uint8_t rate_sel, uint8_t gain_sel, uint8_t one_shot) { uint8_t cfg = ADS1110_CFG_ST; // 默认启动转换 if (rate_sel == 0) cfg |= ADS1110_CFG_RATE0; if (rate_sel == 1) cfg |= ADS1110_CFG_RATE1; if (gain_sel == 0) cfg |= ADS1110_CFG_GAIN0; if (gain_sel == 1) cfg |= ADS1110_CFG_GAIN1; if (one_shot) cfg |= ADS1110_CFG_MODE; return cfg; }

这里rate_sel和gain_sel的具体取值要对照手册里的采样速率表和增益表。ADS1110 的采样速率有两种可选,15SPS 和 30SPS,对应配置位组合分别是 11 和 10 之类的映射关系,不同版本手册略有出入。增益档位对应的是 1/2/4/8 倍,二进制编码也是两 bit。用位拼接方式的好处是将来换芯片型号时,只需要改掩码定义,函数逻辑不用动。

写配置的 I²C 时序是发两个字节:第一个字节是设备地址加写方向,第二个字节就是配置字节。注意在连续转换模式下,每次转换结束后配置寄存器会被清零,需要在每次读数据之前重新写入。这个问题手册里写得很隐晦,中文版里也只是在应用提示部分带了一句,很容易被忽略。

4.4 读取转换结果:两字节合并、12 位左对齐与符号扩展处理

读数据时从器件连续读取两个字节。ADS1110 的输出寄存器是 16 位,但有效数据只有 12 位,而且是左对齐的,也就是说转换结果占据高 12 位,低 4 位是零。把两字节合并成 16 位整数后,要右移 4 位才能得到真实码值。

如果是差分输入,结果可能为负值。12 位数据在 16 位寄存器里左对齐时,最高位就是符号位。合并之后直接当有符号数处理,需要先判断最高位再进行符号扩展。看下面这段代码:

// 读取原始转换值,返回 16 位有符号数 int16_t ads1110_read_raw(void) { uint8_t buf[2]; HAL_StatusTypeDef ret; // 重新写入配置,确保连续转换模式下寄存器正确 uint8_t cfg = ads1110_build_config(0, 0, 0); // 默认 15SPS, 增益 1 HAL_I2C_Mem_Write(ads1110_i2c_handle, ads1110_dev_addr_7bit, 0xFF, I2C_MEMADD_SIZE_8BIT, &cfg, 1, 100); // 连续读取两个数据字节 ret = HAL_I2C_Master_Receive(ads1110_i2c_handle, ads1110_dev_addr_7bit, buf, 2, 100); if (ret != HAL_OK) { return 0x8000; // 返回特殊错误码,调用方需要检查 } // 合并两字节,12 位数据左对齐 uint16_t reg = ((uint16_t)buf[0] << 8) | buf[1]; // 右移 4 位取 12 位有效数据,最高位是符号位 return (int16_t)(reg >> 4); }

这段代码里有两个容易被忽略的细节:第一个是HAL_I2C_Mem_Write的第三个参数0xFF是寄存器地址,但 ADS1110 只有一个寄存器,这个参数实际上被忽略了,换成任意值都不影响通信,我习惯写成 0xFF 以便一眼认出是伪地址;第二个是错误码0x8000的选择,这个值换算成 12 位数据是 -2048,在正常差分测量里代表负满量程,调用方必须检查这个值再做换算,否则会把通信错误当成真实采样值。

4.5 把原始码值换算成实际电压:增益、基准与码值的关系

拿到原始码值之后,换算成电压的公式是:

实际电压 = 原始码值 × 满量程基准 / (增益倍数 × 4096)

满量程基准在 ADS1110 内部是 2.048V。增益为 1 时,1 个 LSB 对应 0.5mV;增益为 8 时,1 个 LSB 对应 62.5μV。换算函数里要保留浮点数精度,但在没有 FPU 的低端 MCU 上,我一般会先用定点数算成毫伏整数,避免浮点运算拖慢主循环:

// 把原始码值换算成实际电压,返回微伏(uV)单位的有符号整数 int32_t ads1110_raw_to_uv(int16_t raw, uint8_t gain_sel) { int32_t uv = (int32_t)raw * 2048000L / 4096; // gain_sel 是枚举值:0=1倍, 1=2倍, 2=4倍, 3=8倍 if (gain_sel == 1) uv /= 2; else if (gain_sel == 2) uv /= 4; else if (gain_sel == 3) uv /= 8; return uv; }

这里固定用 2048000μV 做分子,是因为 2.048V 内部基准换算成微伏是整数,避免浮点误差。放大处理是在除法时直接按增益档位缩放的,逻辑上等效于先乘后除,但不溢出。这个函数实测在从 -2048 到 +2047 的满码值范围内都不会溢出,32 位整数足够。

5. ADS1110 驱动开发中的高频问题排查:地址、数据对齐与时钟三个重灾区

5.1 现象:总线扫描能发现设备,但读写返回垃圾值

有次在某数据采集项目里,扫描函数能看到 0x48 地址,但读回来的原始码值一直在 0xFFFF 和 0x0000 之间疯跳,偶尔还冒出来 0x7FFF。

原因分析后发现是数据对齐处理出了问题。ADS1110 的 12 位数据在 16 位寄存器里左对齐,低 4 位恒为 0。如果程序直接把buf[0] << 8 | buf[1]当作 16 位结果,把低 4 位零也算进有效位,再除以 16 会得到正确结果;如果某段代码里漏了右移 4 位,看起来范围是 0 到 65520,但这其实不对,因为低 4 位不是有效数字。

解决方式统一在驱动入口处处理,所有读取函数都返回右移 4 位后的有符号 12 位值,上层只要调ads1110_raw_to_uv()即可。从那以后我在新项目中都要求在头文件里写清楚返回值的单位是码值还是电压,避免不同模块各写一套换算。

5.2 现象:改地址之后设备返回 NACK,逻辑分析仪显示地址字节是 0x92

某个模拟项目里想把两片 ADS1110 挂在一起,按照手册把第二片芯片的 A0 引脚接 VCC,地址理论是 0x49。代码里传入0x49 << 1作为 HAL 库地址参数,结果设备完全不响应。

定位这个问题时用逻辑分析仪抓了总线波形,发现主机发出的地址字节是 0x92,正好是 0x49 左移一位。HAL 库的 7 位地址模式要求传入的是不带读写位的 7 位地址,即0x49 >> 1,而不是0x49 << 1。0x49 左移一位等于 0x92,这个值作为 7 位地址超出范围了,而且与 0x48 的映射也乱了。

解决方式是查阅你所用的 I²C 库的地址位宽约定,HAL 库用 7 位地址,树莓派的smbus库用 8 位地址。统一做法是在驱动初始化函数里增加注释,写明传入的是 7 位还是 8 位形式。

5.3 现象:差分输入时读数始终为负半幅或零

接 PT100 电桥时发现读数稳定在负满量程附近,但用万用表量差分电压明明是正的小电压。

原因是 IN- 和 IN+ 接反了。ADS1110 的差分测量约定是 IN+ 减去 IN- 输出码值,信号从电桥出来时如果公共地接法不对,正负关系就反了。这时候软件上可以做一个交换逻辑,但最稳的解法是硬件上把电桥的两根输出线对调。如果信号源是浮空的传感器,还要注意共模电压不能超出芯片的输入范围,否则即使差分电压正常,共模部分也会把输入级顶到饱和。

另一个容易忽略的点是上电时输入引脚悬空。差分输入端悬空时,内部偏置电流会把输入电压拉到不确定电位,读数表现为无规律跳动。解决方法是把 IN- 和 IN+ 各自通过 10kΩ 电阻拉到 GND,让输入级有一个确定的偏置点。

5.4 现象:连续多次读取时数据偶尔跳变一个很大的值

在监控面板上看到电压值偶尔从 1.234V 跳到 0.987V,过几秒又跳回来,幅度大约 20%。用示波器量输入电压是稳定的,问题出在读取时序上。

查了手册里的应用说明,ADS1110 在连续转换模式下,每次数据更新时输出寄存器会先写高字节再写低字节。如果主机正好在更新过程中发起读取,可能读到的组合是上一次的高字节和下一次的低字节,拼接出来的数据就变成了一个不存在的中间值。

解决方法是判断数据就绪标志,或者连续读两次丢弃第一次结果。连续读两次的方式更省事,因为第一次读操作会把寄存器锁存到一个缓冲,第二次读到的必定是同一帧数据的完整两个字节。我一般会在驱动里加一个ads1110_read_raw_stable()函数,内部调用两次ads1110_read_raw(),返回第二次的结果。

5.5 现象:I²C 总线 SDA 线被拉死,所有 I²C 设备都失联

一根总线上挂了 ADS1110、温度传感器和 EEPROM,某次程序跑飞后 SDA 线一直保持低电平,扫描和通信全部失败。

I²C 协议里如果主机在通信中途异常复位,从机可能还在等待后续时钟,把 SDA 保持在发送状态。解决方式是让主机产生 9 个以上的时钟脉冲,把从机状态机复位。很多 HAL 库的HAL_I2C_Init()不做这个总线恢复操作,需要自己用 GPIO 模拟:

// 软件复位 I2C 总线,产生 9 个时钟脉冲 void i2c_bus_recovery(GPIO_TypeDef *port_scl, uint16_t pin_scl, GPIO_TypeDef *port_sda, uint16_t pin_sda) { HAL_GPIO_WritePin(port_scl, pin_scl, GPIO_PIN_RESET); for (int i = 0; i < 9; i++) { HAL_GPIO_WritePin(port_scl, pin_scl, GPIO_PIN_SET); HAL_Delay(1); HAL_GPIO_WritePin(port_scl, pin_scl, GPIO_PIN_RESET); HAL_Delay(1); } // 最后产生一个 STOP 条件 HAL_GPIO_WritePin(port_sda, pin_sda, GPIO_PIN_SET); HAL_GPIO_WritePin(port_scl, pin_scl, GPIO_PIN_SET); }

这段恢复代码的本质是让总线上的从机接收到足够的时钟边沿,强制它们退出正在传输的状态。注意在走恢复流程时要先把 SDA 释放为高电平,否则后面产生的 STOP 条件不成立。

6. 精度验证与差分测量技巧:用零点校准和跨零点数据把 12 位用出 14 位的效果

如果你已经能稳定读码值了,下一步就是验证精度边界。我拿到新板子第一件事不是上传感器,而是把 IN+ 和 IN- 短接在一起,读 100 次原始码值。这个操作能暴露三个问题:失调电压有多大、噪声分布范围是多少、有没有周期性干扰。短接读数的平均值如果偏离 0 超过 ±3 个 LSB,说明芯片本身有可观失调;如果 100 次读数跳动的范围超过 ±4 个 LSB,说明布局或电源去耦有问题。这时候在 VDD 引脚旁边补一个 0.1μF 陶瓷电容,通常能把跳动范围压缩一半。

零点校准是精度提升最划算的手段。把短接读数存成offset_raw,之后每次实际测量都先用原始码值减去这个偏移再换算。这样能把芯片本身的失调误差从 ±2 LSB 压低到接近 0。在控温项目里,我用这个方法把温度采样误差从 ±0.5℃ 降到了 ±0.1℃ 以内。

差分测量还有个容易忽略的技巧:信号源如果参考地和 ADC 的 GND 有电位差,差分输入也救不了。ADS1110 的 IN+ 和 IN- 虽然算的是差值,但两个引脚对 GND 的电压必须落在电源范围内。测量浮空电池电压时,要把电池负极与 ADS1110 的 GND 相连,形成共地参考。

对传感器信号来说,12 位分辨率有一个隐形的使用边界:信号幅度必须尽量占满 PGA 量程。同样一个 0-100mV 的信号,用 8 倍增益时码值分布在 ±1600 附近,量化分辨率等效出来约等于 14 位 ADC 在 0-100mV 满量程下的表现。这个逻辑反过来也成立——如果信号本身只有 2mV,8 倍增益下也只有 16mV 量程的 1/8,这时候该考虑外置放大或者换更高位数的 ADC。

最后一个技巧是采样率的取舍。ADS1110 支持的最高速率是 15SPS 和 30SPS 两档,但实际噪声水平会随速率上升而变差。我习惯在信号本身变化慢的场景里用 15SPS,然后在软件里做 4 次平均,等效采样率变成约 3.75SPS,但噪声峰值能压低一半左右。这套组合在测温度和压力这类缓变信号上表现相当稳。

以前我每次拿到新 ADC 第一反应都是先写驱动再对着手册查寄存器位,后来发现顺序要反过来,先拿万用表确认参考电压的稳定性,再确认地址映射关系,最后动代码,可以少走不少弯路。从那以后我每次焊完板子都会强制走一遍短接读数、零点校准、地址扫描这个流程。希望这些经验能在你调 ADS1110 时派上用场。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询