ADS1115是我在项目里用得最多的16位 ADC,也是让不少初学者卡壳最多的地方。芯片本身不复杂,但寄存器配置、工作模式、I2C 通信时序这三件事如果没理顺,很容易出现“设备扫描不到”“读数全 0”或者“数据跳得像心电图”这类问题。这篇文章我从底层把 ADS1115 的寄存器、模式和 I2C 时序完整拆开讲,配合 STM32 的 HAL 库和模拟 I2C 代码,适合正在调嵌入式采集、或者想深入理解 I2C 协议的人参考。没有废话,全部是实操干货。
1. 项目概述与整体设计思路
1.1 为什么选型会落到 ADS1115 这颗芯片上
ADS1115 是 TI 推出的一款 16 位、低功耗、带 I2C 接口的模数转换器,集成了多路输入选择器、可编程增益放大器、内部基准源和振荡器,外围只需要接几颗电阻电容就能工作。在 24 位 ADC 满天飞的今天,它依然大量出现在电压采集、电池电量监测、电流采样、传感器信号读取等场景里,原因很直接:16 位精度对于大多数工业信号足够了,内部 PGA 可以把小信号放大后再采样,I2C 接口只占两根线,而且芯片封装极小。
我做过一个 8 通道电池组监测板,主控是 STM32F407,板子上需要同时采集每一串电芯电压。如果全用 MCU 内置 ADC,输入通道不够,而且 F407 内置 ADC 在 12 位分辨率下做多通道轮流扫,通道间的串扰和采保时间都是麻烦事。后来改成两片 ADS1115,每片 4 个单端输入,I2C 地址不同并联在同一条总线上,代码逻辑瞬间简单了。这就是这颗芯片的核心价值:用简单可靠的方式做多路模拟量采集,不用跟 ADC 的时序较劲。
1.2 硬件连接与总线上拉设计
ADS1115 的引脚不复杂:VDD 是电源,接 2.0V 到 5.5V;GND 接地;SCL 和 SDA 是 I2C 引脚;ADDR 用来配置 I2C 地址;ALERT/RDY 在比较器模式或就绪模式下给主控发信号。很多人第一次画板子只接了 VDD、GND、SCL、SDA,把 ALERT 空着,这没问题,但调试时少了一个非常有用的状态指示手段。我的习惯是预留 ALERT 到 MCU 的任意一个 GPIO,哪怕最终固件里不用,调试时用示波器或者逻辑分析仪挂在上面看转换完成信号,能省很多猜时间的工作。
I2C 总线必须要有上拉电阻,这点几乎人人知道,但阻值选多少经常被忽略。SCL 和 SDA 各接一颗上拉电阻到 VDD,常见取 4.7kΩ,适用于 100kHz 和 400kHz 总线。如果你的总线上挂的设备多、走线长,总线电容会变大,4.7kΩ 时上升沿可能太慢,建议换 2.2kΩ 甚至 1kΩ。反过来,如果追求极低功耗、总线速率只有 100kHz,10kΩ 也可以。我画板子时习惯先按 4.7kΩ 设计,预留 0402 封装的位置,出问题再换,实测下来这个方法最省事。
还有一个容易踩的电平匹配问题。ADS1115 的 VDD 如果接 5V,I2C 上拉也会到 5V,而 MCU 侧是 3.3V 电平,这时候直接连 SDA/SCL 可能超 IO 耐压,或者导致高电平判别异常。稳妥做法是 VDD 和 MCU 同电源,或者用电平转换芯片/模块。ADS1115 本身在 3.3V 供电下完全正常工作,我大多数设计直接统一用 3.3V。
1.3 ADDR 引脚决定设备地址,必须提前规划
ADS1115 的 I2C 7 位地址由 ADDR 引脚的电平状态决定,接法有四种:接地、接 VDD、接 SDA、接 SCL。对应地址如下:
| ADDR 接法 | 7位地址 | 8位写地址 | 8位读地址 |
|---|---|---|---|
| GND | 0x48 | 0x90 | 0x91 |
| VDD | 0x49 | 0x92 | 0x93 |
| SDA | 0x4A | 0x94 | 0x95 |
| SCL | 0x4B | 0x96 | 0x97 |
这块有一个常见坑:很多例程里写的是 0x90 或 0x91,有人会问为什么 I2C 地址不是 0x48。原因很简单,I2C 总线传输地址时是 7 位地址左移一位,最低位表示读写方向,所以 7 位地址 0x48 对应写地址 0x90、读地址 0x91。如果你用 STM32 HAL 库的HAL_I2C_Mem_Write这类接口,传入的 DevAddress 参数填 7 位地址 0x48 即可,库内部会做移位处理;如果是自己模拟 I2C,就必须在发送地址字节时手动拼上读写位。
多片 ADS1115 并联时,四个地址不够用还可以考虑 TCA9548A 这类 I2C 多路开关,但绝大多数项目两片并联四个地址足够,先把 ADDR 引脚规划好,别等到画完板子发现地址冲突。
2. 寄存器配置逐位拆解
2.1 四个寄存器的地图与访问方式
ADS1115 内部有四个寄存器,都是 16 位宽,通过 I2C 指针寄存器寻址:
| 指针地址 | 寄存器名称 | 功能 |
|---|---|---|
| 00h | Conversion | 存储最新的转换结果 |
| 01h | Config | 配置寄存器,控制所有工作参数 |
| 02h | Lo_thresh | 低阈值比较寄存器 |
| 03h | Hi_thresh | 高阈值比较寄存器 |
对 ADS1115 做任何操作,第一步往往是先写指针寄存器,告诉芯片接下来要访问哪个寄存器。比如读转换结果,必须先通过 I2C 发送指针地址 00h,然后再发起数据读取,否则芯片不知道你要读哪里。这个“先指后读”的机制是多数 I2C 从设备的标准做法,理解它之后,操作任何带寄存器映射的传感器(比如 BH1750、BMP280)都能举一反三。
我在调试时认识的一个典型错误是:有人直接把读地址发出去然后开始读数据,忘发指针地址,结果读回来的是芯片内部默认指针指向的内容,通常是上一次操作遗留的寄存器,数据自然不对。所以“先写指针再读写数据”这两步一定不能省。
2.2 Config 寄存器每一位到底控制什么
Config 寄存器是 ADS1115 最核心的部分,16 位里每一位都有明确含义。下面按位段拆开看。
第 15 位 OS:在写模式下,写入 1 表示启动一次单次转换;在单次模式读回时,该位为 1 表示转换已经完成。连续模式下读回通常为 0。这个位是单次触发模式的开关,也是轮询转换状态的标志位。
第 14~12 位 MUX[2:0]:输入多路选择器配置。000~011 是四种差分输入组合,100~111 分别对应 AIN0 到 AIN3 对 GND 的单端输入。单端模式下输入电压不能为负,否则读数为负值,配置时要留意。
| MUX | 输入组合 |
|---|---|
| 000 | AINP = AIN0,AINN = AIN1(差分) |
| 001 | AINP = AIN0,AINN = AIN3(差分) |
| 010 | AINP = AIN1,AINN = AIN3(差分) |
| 011 | AINP = AIN2,AINN = AIN3(差分) |
| 100 | AINP = AIN0,AINN = GND(单端) |
| 101 | AINP = AIN1,AINN = GND(单端) |
| 110 | AINP = AIN2,AINN = GND(单端) |
| 111 | AINP = AIN3,AINN = GND(单端) |
- 第 11~9 位 PGA[2:0]:可编程增益放大器的满量程范围。增益越大,能测量的最大输入电压越小,但对应的 LSB 精度越高。满量程 ±4.096V 时 LSB 约 125μV,±0.256V 时 LSB 约 7.8μV。选 PGA 的原则是让信号尽量占满量程,同时又不能超量程,否则读数饱和在 0x7FFF 或 0x8000 附近。
| PGA | 满量程范围 | LSB 大小 |
|---|---|---|
| 000 | ±6.144V | 187.5μV |
| 001 | ±4.096V | 125μV |
| 010 | ±2.048V | 62.5μV |
| 011 | ±1.024V | 31.25μV |
| 100 | ±0.512V | 15.625μV |
| 101 | ±0.256V | 7.8μV |
| 110 | ±0.256V | 7.8μV |
| 111 | ±0.256V | 7.8μV |
第 8 位 MODE:0 表示连续转换模式,1 表示单次模式。我下面会单独讲两者的行为差异和选型。
第 7~5 位 DR[2:0]:数据速率,从 8SPS 到 860SPS 共 8 档。速率越低,噪声越低,精度越高。采样电池电压这种缓变信号,128SPS 已经非常充裕,我一般直接拉到 128SPS 或更低,既稳定又省电。
第 4 位 COMP_MODE:比较器模式,0 表示传统比较器(窗口比较或阈值比较),1 表示窗口比较器。不用的项目通常忽略。
第 3 位 COMP_POL:比较器输出极性,0 低电平有效,1 高电平有效。这个会影响 ALERT 引脚的判读逻辑。
第 2 位 COMP_LAT:比较器锁存使能,0 表示非锁存,1 表示锁存。锁存模式下需要读 Config 寄存器才能清除。
第 1~0 位 COMP_QUE[1:0]:比较器触发后需要的连续转换次数,11 表示禁用比较器并让 ALERT 引脚处于高阻状态。如果不使用 ALERT 功能,这两位置 11 最省心。
我常用的配置举例:采集 AIN0 对 GND 的电压,满量程 ±4.096V,连续转换,128SPS,禁用比较器。MUX = 100,PGA = 001,MODE = 0,DR = 100,COMP_QUE = 11,组合起来就是二进制 0100 0010 0100 0011,对应十六进制 0x4243。绝大多数 ADS1115 例程里都能看到这个值,现在你明白它的每一位是怎么来的了。
2.3 阈值寄存器的默认值陷阱
Lo_thresh 和 Hi_thresh 在比较器模式下才会用到。上电默认值时 Lo_thresh = 0x8000,Hi_thresh = 0x7FFF,如果开启了比较器功能但不好好设置阈值,ALERT 引脚可能一上电就保持触发状态,导致主控误判。我的建议是凡是不用比较器的项目,Config 寄存器里直接关闭比较器(COMP_QUE = 11),阈值寄存器保持默认值不用管。
但如果你用到了 ALERT/RDY 的转换就绪功能,注意它的配置方式和比较器是同一个引脚,要通过 Config 里的比较器相关位把引脚行为配成“转换完成后拉低”而不是“超过阈值拉低”。这个功能我建议初期先不用,等基本采集逻辑跑通了再开,否则排查问题时容易把两个信号混在一起,分不清到底是数据就绪还是报警。
3. 工作模式深度对比与选型
3.1 连续转换模式的行为逻辑与潜在风险
连续转换模式下,ADS1115 上电后或者写入 Config 后,会按照 DR 设定的速率不间断地执行模数转换,每次转换完成后自动把结果写入 Conversion 寄存器,接着继续下一次转换,不需要主控干预。
连续模式看起来省事,但有一个隐患:如果主控读取频率低于转换频率,Conversion 寄存器的旧数据会被新数据覆盖,读出来的永远是“最近一次”转换结果,你无法判断两次读数之间到底隔了几次转换。在某些实时性要求高的场景这不算问题,但如果你想做多通道同步采集或者需要判断数据有效性,就会心里没底。ADC 的转换结果就绪本身不会通过 I2C 线报告,除非你用了 ALERT 引脚。
从 I2C 时序角度看,连续模式下主控可以在任意时刻发起读数请求,芯片会立刻返回当前 Conversion 寄存器的内容。这里要注意的是,如果读高字节和低字节之间恰好奇偶发生了一次新转换,理论上存在读到的新旧数据拼接风险。ADS1115 的数据手册其实没有明确给出多字节读的锁存机制,我实测时极少遇到这个问题,但在高数据速率(475SPS、860SPS)下如果发现数值偶发跳变,可以考虑把速率降下来,或者改用下面说的单次模式。
3.2 单次触发模式的流程与省电优势
单次模式的触发方式很简单:写 Config 寄存器时把 OS 位置 1,芯片执行一次转换,完成后自动进入断电状态,功耗几乎可以忽略。读取时用轮询 OS 位或者延时等待,然后读 Conversion 寄存器即可。
我做一个两节 18650 电池电压监测模块时,主控每秒只读一次电压,如果全程用连续模式,ADS1115 一直以 128SPS 跑着,光 ADC 的电流就白白耗掉不少。改成单次模式后,每次测量从触发到出结果不到 10ms,其余时间芯片都在亚微安级的断电状态,整机待机电流降到非常低。对于电池供电的采集设备,单次模式几乎是必须的选择。
单次模式下的时间预算要算清楚。比如 DR 设置为 128SPS,对应转换时间约 1/128 ≈ 7.8ms;如果 DR 设置为 8SPS,一次转换约 125ms。读数据前等待时间最好取转换周期再加一点余量,别刚发完触发就读数据,这时候转换还没完成,读到的可能是上一次残留值。
3.3 数据速率怎么选才合适
数据速率的选择本质上是精度和响应速度的权衡。ADS1115 的噪声指标随数据速率升高而变差,低速下有效分辨率更接近 16 位,高速下有效位数会掉。我的经验是:采集电池电压、温度这类缓变量,8SPS 到 64SPS 足够;如果需要监测 50Hz 市电波形,250SPS 以上才有意义;如果是音频或者振动信号这种高频动态,ADS1115 本身不太适合,860SPS 也偏慢,应该换内置 ADC 或者 SPI 接口的高速 ADC。
还有一个细节:单次模式下实际等待时间不只是 1/DR。启动转换后,芯片内部还有一小段初始化时间,实测下来总延迟会比理论值略高一点。所以我写等待逻辑时习惯在 1/DR 基础上多加 20%~30% 的余量,比如 128SPS 我就延时 10ms,确保万无一失。
4. I2C 通信时序逐字节拆解
4.1 I2C 协议基础:起始、地址、应答、数据、停止
如果不把 I2C 底层时序搞懂,就很难真正定位为什么 ADS1115 通信失败。I2C 总线核心概念就五个:起始条件、地址字节、应答位、数据字节、停止条件。
起始条件(START)的定义是:在 SCL 保持高电平期间,SDA 从高电平跳变到低电平。停止条件(STOP)反之,在 SCL 高电平期间,SDA 从低电平跳变到高电平。这个时序是必须严格满足的,很多自己模拟 I2C 的人写代码时在电平切换顺序上出错,最常见的错误就是 SDA 还没拉低就先拉高了 SCL,或者结束前 SCL 已经拉低。用软件模拟时,每次电平切换之间稍微加一点延时,时序反而更宽容。
地址字节由 7 位设备地址加 1 位读写标志组成,一共 8 位。发送完地址字节后,从设备会在第 9 个时钟周期拉低 SDA 表示应答(ACK),否则保持高电平就是非应答(NACK)。读数据阶段,主控每读完一个字节需要主动发送应答或非应答:还想继续读就回 ACK,读完最后一个字节必须回 NACK,然后发 STOP,否则从设备会一直输出数据。这个主控回 NACK 的动作是模拟 I2C 里最容易漏的,漏了会导致总线时序错乱。
4.2 写配置寄存器的完整时序
写 Config 寄存器(指针地址 01h)的完整时序如下:
- 主控发 START。
- 主控发送写地址字节 0x90(7 位地址 0x48 左移一位,最低位为 0 表示写)。
- 等待 ADS1115 返回 ACK。
- 主控发送指针地址 0x01,指定要访问 Config 寄存器。
- 等待 ACK。
- 主控发送 Config 寄存器高字节(例如 0x42)。
- 等待 ACK。
- 主控发送 Config 寄存器低字节(例如 0x43)。
- 等待 ACK。
- 主控发 STOP。
这里有个细节:ADS1115 的 16 位寄存器通过 I2C 传输时永远是高字节在前。有人会习惯性地先发低字节,结果寄存器配置完全错位,读回来的数据也是乱的。
用 STM32 HAL 库写这个配置过程非常简单,HAL_I2C_Mem_Write会把“发指针地址 + 发数据”两步合并成一个函数调用:
uint8_t config[2] = {0x42, 0x43}; // 高字节在前 HAL_StatusTypeDef status; status = HAL_I2C_Mem_Write(&hi2c1, 0x48, 0x01, I2C_MEMADD_SIZE_8BIT, config, 2, 100); if (status != HAL_OK) { // 处理通信错误 }如果你用的是模拟 I2C,时序就要自己抠了。下面这段是我常用的模拟写时序,GPIO 配置成开漏输出,确保能正确释放总线:
void i2c_start(void) { SDA_HIGH(); SCL_HIGH(); delay_us(5); SDA_LOW(); delay_us(5); SCL_LOW(); } void i2c_stop(void) { SCL_LOW(); SDA_LOW(); delay_us(5); SCL_HIGH(); delay_us(5); SDA_HIGH(); delay_us(5); } void i2c_write_byte(uint8_t dat) { for (int i = 7; i >= 0; i--) { if (dat & (1 << i)) SDA_HIGH(); else SDA_LOW(); delay_us(2); SCL_HIGH(); delay_us(5); SCL_LOW(); } SDA_HIGH(); // 释放 SDA,等待从设备 ACK delay_us(2); SCL_HIGH(); delay_us(5); SCL_LOW(); }配置过程就是把上面的函数按顺序串起来。特别注意写指针地址和写数据之间不要多插 STOP,除非你想用“先写指针后停止,再启动写数据”的分离式操作,那种写法在部分从设备上不认,统一用一次连续写最稳。
4.3 读转换结果的完整时序
读转换结果(指针地址 00h)的标准做法分两步:先写指针地址 00h,然后重新发起读操作。完整时序如下:
- 主控发 START。
- 主控发送写地址 0x90,等待 ACK。
- 主控发送指针地址 0x00,等待 ACK。
- 主控发重复起始条件(REPEATED START)。
- 主控发送读地址 0x91,等待 ACK。
- 主控读取高字节,并向从设备回 ACK。
- 主控读取低字节,并向从设备回 NACK。
- 主控发 STOP。
为什么中间要一个重复起始条件?因为一次 I2C 事务里方向不能直接反转,主机要先让从机进入“读模式”,必须重新发一次带读标志的地址。重复起始和 STOP 的区别是总线没有被释放,中间不会有其他主机插进来,读操作是原子的。
HAL 库的读法依然简单:
uint8_t buf[2]; HAL_StatusTypeDef status; status = HAL_I2C_Mem_Read(&hi2c1, 0x48, 0x00, I2C_MEMADD_SIZE_8BIT, buf, 2, 100); if (status == HAL_OK) { int16_t raw = (buf[0] << 8) | buf[1]; }注意buf[0]是高字节。转换成实际电压时,先把两个字节拼成 int16_t,然后乘上 LSB 电压。比如 PGA 是 ±4.096V,LSB 是 125μV,实际电压就是 raw × 0.000125,单位 V。
如果是模拟 I2C 读多字节,重点在于读完高字节后主控要回 ACK,读完低字节后必须回 NACK 再 STOP。看下面这段:
uint8_t i2c_read_byte(uint8_t ack) { uint8_t dat = 0; for (int i = 7; i >= 0; i--) { SCL_HIGH(); delay_us(5); dat <<= 1; if (SDA_READ()) dat |= 1; SCL_LOW(); delay_us(5); } if (ack) SDA_LOW(); // ACK: 拉低 SDA else SDA_HIGH(); // NACK: 释放 SDA delay_us(2); SCL_HIGH(); delay_us(5); SCL_LOW(); SDA_HIGH(); return dat; }读两个字节时:
i2c_start(); i2c_write_byte(0x90); // 写地址 i2c_write_byte(0x00); // 指针指向 Conversion i2c_start(); // REPEATED START i2c_write_byte(0x91); // 读地址 uint8_t msb = i2c_read_byte(1); // 读高字节,回 ACK uint8_t lsb = i2c_read_byte(0); // 读低字节,回 NACK i2c_stop();这个流程我建议你在逻辑分析仪上完整抓一遍,亲眼看看时序和手册是否一致。我在自己项目里就靠这个操作找回了至少两个“看起来没问题但就是不出数据”的 bug。
4.4 SCL 速率对通信稳定性的影响
ADS1115 的数据手册标称支持最高 3.4Mbps 的高速模式,但实际工程里我几乎不会跑到这么高。MCU 的硬件 I2C 外设在 400kHz 快速模式下工作良好,100kHz 标准模式兼容性最好,模拟 I2C 则多用在低速场合,延时时长决定实际频率。
在 STM32F407 上用硬件 I2C 时,我曾经把速度配到 400kHz,配合 4.7kΩ 上拉电阻和短走线,通信稳定。但如果板子上有其他容性负载,或者用了长飞线连接外部模块,总线边沿会变缓,400kHz 下可能出现数据错位。飞到 100kHz 基本能解决 90% 的偶发通信问题。ADS1115 这种低速传感器根本不需要高带宽,100kHz 足够满足任何实际采样需求,我现在的习惯是能用 100kHz 就不用 400kHz,稳定压倒一切。
另外一点,ADS1115 本身不会做时钟拉伸,也就是说它在 I2C 通信过程中不会主动拉低 SCL 让主机等待。它的转换过程和 I2C 通信过程是独立的,转换没完成时你去读寄存器,只会读到旧值,而不是让总线卡住。这一点和某些传感器不同,理解后你就知道“读数据前先判断转换完成”是主控的责任,而不是从设备会帮你阻塞。
5. 实操中的常见问题与排查技巧
5.1 I2C 地址扫描:调试第一步就该做
凡是 I2C 设备、ADS1115 也好、其他芯片也好,第一步一定是扫描总线,确认设备地址能应答。很多“为什么读不到数据”的问题,在地址扫描阶段就能暴露出来。
地址扫描的原理很朴素:对总线上的每个可能 7 位地址发送一个写地址字节,看有没有设备回 ACK。收到 ACK 就说明该地址有设备在应答。我调试 STM32 时写过一个非常简单的扫描函数,用 HAL 库的HAL_I2C_Master_Transmit逐个探测地址:
for (uint8_t addr = 0x03; addr < 0x78; addr++) { HAL_StatusTypeDef status; status = HAL_I2C_Master_Transmit(&hi2c1, addr, NULL, 0, 50); if (status == HAL_OK) { printf("Found device at 0x%02X\r\n", addr); } }扫描不到设备,按优先级排查:ADDR 引脚是否接对;VDD 是否上电;SDA/SCL 是否接反;上拉电阻是否虚焊;总线是否被拉死。总线被拉死是常见现象,表现是 SDA 一直为低,原因可能是有设备把总线占用未释放。这时候可以对 SCL 手动敲几个脉冲,然后再发 STOP,很多时候总线就恢复了。
5.2 读回数据全 0xFFFF 或 0x0000 的原因
读回高字节和低字节都是 0xFF,通常是总线读到了无应答或者悬空电平,常见于模拟 I2C 读时序里没有正确释放 SDA,或者上拉电阻没接好。读回全是 0x00,可能是地址不对、读到了不存在的寄存器,或者上拉电阻短路到地。
还有一种情况是输入电压超过 PGA 设定范围,导致转换结果饱和。如果配置的是 ±2.048V 量程却输入了 3.3V,读数会固定在 0x7FFF(正饱和)附近,并非通信问题。排查时先拿万用表量一下 AIN 输入电压,再对照 PGA 量程判断是不是输入超范围。
5.3 读数跳动严重时如何降噪
ADS1115 标称 16 位,但实际跳动程度很大程度取决于硬件设计和数据速率。如果读数不稳定,先检查电源。我给 ADS1115 供电时习惯在 VDD 引脚旁边放一颗 0.1μF 陶瓷电容加一颗 1μF~10μF 的钽电容或铝电解电容,模拟输入走线尽量短粗,模拟地和数字地在 ADC 附近单点汇接。这些是模拟电路的常规操作,但能解决大量“软件滤波压不住”的噪声问题。
数据速率越低,噪声越小。128SPS 下我读固定电压时,跳动范围大约在 ±2~±4 个 LSB,8SPS 下可以做到 ±1 LSB。如果 128SPS 的跳动在可接受范围,没必要强求更低速率。软件上再做一次滑动平均或者中值滤波,基本能达到稳定读数。
我实测发现一个有意思的现象:用单次模式逐次读取时,相邻两次读数之间的跳动反而比连续模式略大,因为每次转换的采样时刻是独立的,混入的噪声不相关。但单次模式在低功耗场景下收益太大,数据跳动就靠软件平均处理,两者并不冲突。
5.4 用逻辑分析仪抓取时序的实操心得
逻辑分析仪是调 I2C 的神器,几十块钱的 8 通道 24MHz 采样率设备就够用。把通道 0 接 SDA,通道 1 接 SCL,触发电平设成 1.65V 左右,触发条件选“下降沿”,然后跑一次读操作,波形里就能看到完整的 START、地址、ACK、数据、STOP。
抓波形时关注三点:一是地址字节是不是 0x90/0x91 而不是 0x48/0x49;二是在读数据阶段最后一个字节后主控有没有回 NACK;三是 STOP 条件里 SDA 和 SCL 的先后顺序是否正确。这三处是我见过最容易出错的地方。软件模拟 I2C 时,输出电平顺序和时间可以在波形里非常清楚地看到,比看代码猜靠谱得多。
我印象最深的一次调试经历就是模拟 I2C 读 ADS1115 死活读不对,代码翻来覆去检查都没问题,最后接上逻辑分析仪,发现读高字节和低字节之间少了一次 ACK 时钟,从设备输出的低字节根本没被正确采样。这个错误用眼睛盯代码很难发现,但在波形里一眼就能看出来。
6. 基于个人经验的几条补充建议
我用 ADS1115 做过好几轮产品,从最初的手工飞线调试到后来的批量贴片,积累了一些文档里不太会写的体会,在这里一并分享。
第一,先把地址扫描跑通再谈配置。我见过太多人上来就写配置、读数据,一旦失败就开始怀疑芯片坏了,其实大概率是地址没对上。先扫描、再写配置、再读转换结果,这个顺序能帮你把问题范围切得很干净。第二,所有寄存器操作都用“先写指针再读写数据”的方式,不要依赖芯片上电后的默认指针状态,因为我遇到过不同批次芯片默认指针行为不完全一致的情况。第三,ALERT/RDY 引脚从硬件上预留出来,即使初期不用,后续如果要判断转换完成状态,起码不用重新改板。
ADS1115 真正难的不是寄存器配置本身,而是 I2C 通信底层的那点“手感”。只要把时序图彻底看懂,把模拟 I2C 的 ACK、NACK、START、STOP 都亲手调一遍,以后再碰任何 I2C 设备都会顺很多。希望这篇拆解能帮你少走一些弯路。