ADS1115深度解析:寄存器配置、I2C时序与STM32实战

发布时间:2026/9/9 5:29:10
ADS1115深度解析:寄存器配置、I2C时序与STM32实战 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.5VGND 接地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Ω。反过来如果追求极低功耗、总线速率只有 100kHz10kΩ 也可以。我画板子时习惯先按 4.7kΩ 设计预留 0402 封装的位置出问题再换实测下来这个方法最省事。还有一个容易踩的电平匹配问题。ADS1115 的 VDD 如果接 5VI2C 上拉也会到 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位读地址GND0x480x900x91VDD0x490x920x93SDA0x4A0x940x95SCL0x4B0x960x97这块有一个常见坑很多例程里写的是 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 指针寄存器寻址指针地址寄存器名称功能00hConversion存储最新的转换结果01hConfig配置寄存器控制所有工作参数02hLo_thresh低阈值比较寄存器03hHi_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输入组合000AINP AIN0AINN AIN1差分001AINP AIN0AINN AIN3差分010AINP AIN1AINN AIN3差分011AINP AIN2AINN AIN3差分100AINP AIN0AINN GND单端101AINP AIN1AINN GND单端110AINP AIN2AINN GND单端111AINP AIN3AINN 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.144V187.5μV001±4.096V125μV010±2.048V62.5μV011±1.024V31.25μV100±0.512V15.625μV101±0.256V7.8μV110±0.256V7.8μV111±0.256V7.8μV第 8 位 MODE0 表示连续转换模式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 100PGA 001MODE 0DR 100COMP_QUE 11组合起来就是二进制 0100 0010 0100 0011对应十六进制 0x4243。绝大多数 ADS1115 例程里都能看到这个值现在你明白它的每一位是怎么来的了。2.3 阈值寄存器的默认值陷阱Lo_thresh 和 Hi_thresh 在比较器模式下才会用到。上电默认值时 Lo_thresh 0x8000Hi_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。主控发送写地址字节 0x907 位地址 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.096VLSB 是 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 个 LSB8SPS 下可以做到 ±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 设备都会顺很多。希望这篇拆解能帮你少走一些弯路。