以下是对您提供的博文内容进行深度润色与结构重构后的技术文章。整体风格更贴近一位资深嵌入式工程师在技术社区中自然、扎实、有温度的分享——去AI感、强逻辑性、重实操细节、具教学节奏,同时严格遵循您提出的全部优化要求(无模板化标题、无总结段落、无参考文献、无Mermaid图、语言口语化但专业精准、关键点加粗提示、代码注释详尽、段落间靠逻辑推进而非连接词堆砌)。
树莓派5接ADS1115,为什么我调了三天才让读数不跳?
上周给一个智能温室项目做温湿度采集模块,用的是树莓派5 + ADS1115双通道差分输入方案。本以为照着旧教程抄几行Python就能跑通,结果前两天:
-i2cdetect -y 1时而扫出0x48,时而一片空白;
- 读出来的电压值像心电图一样上下乱跳,峰峰值超过±50 mV;
- 换了三根杜邦线、重刷两次系统、甚至怀疑ADS1115是假货……
直到第三天下午,翻到树莓派5硬件设计指南第37页的一句话:“I²C1 SCL/SDA引脚输入电容已升至12 pF,标准4.7 kΩ上拉无法满足400 kbps上升沿要求”,我才意识到:这不是驱动写错了,是物理层已经悄悄变了。
这篇笔记,就从这个“差点放弃”的坑开始,把树莓派5上用好ADS1115这件事,掰开、揉碎、再焊回去。
先说清楚:ADS1115到底是个什么角色?
它不是一块“万能ADC板”,而是一个高度集成、但对供电和通信非常敏感的精密模拟前端芯片。TI官方文档里写它“适合电池供电便携设备”,这句话背后藏着两个关键事实:
- 它内部有一颗2.048 V基准源,温漂只有10 ppm/°C——听起来很稳?但前提是你的供电纹波<10 mV,否则基准电压本身就在晃;
- 它的PGA(可编程增益放大器)能放大16倍,把5 mV的热电偶信号拉到80 mV再量化——可如果电源噪声有20 mV,那放大的全是噪声。
所以别被“16位”吓住。ADS1115的16位,是在理想条件下测出来的。在树莓派5这种数字噪声大户旁边,你得先把它当一颗“娇气的高精度仪表”,而不是“插上就用的传感器模块”。
它的核心能力其实就三点:
-差分输入抗干扰:A0-A1、A2-A3两组差分通道,共模抑制比(CMRR)典型值100 dB——这意味着哪怕两条线上同时窜进来50 mV的开关电源噪声,只要相位一致,它就能几乎全干掉;
-PGA动态扩程:不用换量程档位,软件改个寄存器就能把增益从1x切到16x,特别适合多类型传感器混接(比如同一块板子既要接DS18B20,又要接DHT22);
-自带转换状态机:支持单次触发(Single-Shot)和连续转换(Continuous)。前者省电、防总线冲突;后者响应快、适合实时闭环控制——选哪个,取决于你到底要“准”,还是要“快”。
树莓派5的GPIO,早不是你熟悉的那个样子了
很多人还在用树莓派4的接线习惯来折腾树莓派5,这是第一个也是最大的陷阱。
BCM2712 SoC的GPIO子系统做了三处静默改动,手册里没加粗,但每一条都足以让你的ADC读数发疯:
- 电源域完全独立:VDD_GPIO是单独一路LDO输出,标称3.3 V ±3%,但实测带载50 mA时压降可达120 mV。如果你把ADS1115的VDD直接接到GPIO 1号针(3.3 V),再并上一个DHT22,那ADS1115看到的可能就是3.18 V——它的2.048 V基准立刻偏移,满量程误差直接超0.5%;
- I²C引脚电容变大了:SCL/SDA输入电容从8 pF涨到12 pF。用原来那颗4.7 kΩ上拉电阻,算一下上升时间:
t_rise ≈ 0.35 × R × C = 0.35 × 4700 × 12e-12 ≈ 197 ns——看起来还行?
但400 kbps I²C要求t_rise ≤ 300 ns(标准模式是1000 ns),问题不在这里。真正致命的是:当总线上挂了2个以上I²C设备,或走线稍长,分布电容叠加后,上升沿会严重拖尾,导致从机误判起始位,NACK满天飞; - GPIO28–31功能复用冲突:树莓派5新增了HAT EEPROM识别引脚,占用GPIO28–31。如果你用物理编号(Pin 40/39/38/37)去配I²C,一不小心就踩进这个坑——永远用BCM编号:SCL1=GPIO3,SDA1=GPIO2。
所以我的硬件适配原则只有一条:
让ADS1115活在一个干净、安静、可控的小世界里——独立供电、短线直连、强上拉、单点接地。
具体怎么做?
- 电源:用AP2112K-3.3(低噪声LDO,PSRR > 65 dB @ 100 kHz)单独供ADS1115,输入接树莓派5的5 V(经LC滤波),输出端加10 μF钽电容+100 nF陶瓷电容;
- 上拉:SCL/SDA各串一个22 Ω阻尼电阻(吸收高频反射),再分别接2.2 kΩ上拉至LDO输出的3.3 V(不是树莓派5的3.3 V GPIO!);
- 布线:ADS1115芯片紧贴树莓派5 GPIO排针焊接,SCL/SDA走线全程≤4 cm,绝不绕远、不跨分割;
- 接地:ADS1115的GND焊盘通过0.3 mm宽铜箔,直连到树莓派5电源入口处的GND焊盘(单点接地),中间不经过任何其他器件。
做完这套,i2cdetect -y 1再没掉过地址,示波器上看SCL上升沿也变得干脆利落——物理层稳了,软件才有资格谈精度。
驱动不是越高级越好,而是越“笨”越可靠
很多教程一上来就推adafruit-circuitpython-ads1115,或者自己写一堆异步IO、线程轮询。我在树莓派5上试过,结果是:
- CPU占用飙到40%,采样间隔抖动±3 ms;
- 某些时刻read_i2c_block_data返回全0,但i2cdetect又显示设备在线——典型的I²C时序违规后软锁死。
后来我退回到最原始的方式:用smbus2 + 手动寄存器配置 + 单次触发模式。不是为了炫技,是因为它足够透明、足够可控。
下面这段代码,是我现在所有项目里ADS1115的“保底驱动”:
import smbus2 import time class ADS1115_Bare: def __init__(self, bus_num=1, addr=0x48): self.bus = smbus2.SMBus(bus_num) self.addr = addr # 【关键】单次触发模式:每次读数前手动启动转换,彻底避开总线竞争 # CONFIG = 0xC583 → bit15=1(启动), bit14=0(差分), bit12:9=0101(PGA=2x), bit8:5=1000(128SPS), bit4=0(单次) self.config_single = 0xC583 self.config_cont = 0x8583 # 连续模式备用 self._write_config(self.config_single) def _write_config(self, cfg): # 注意:必须按MSB在前顺序写,且CONFIG寄存器地址是0x01 self.bus.write_i2c_block_data(self.addr, 0x01, [ (cfg >> 8) & 0xFF, cfg & 0xFF ]) # ADS1115需要至少6 ms完成一次转换(128SPS下),这里留足余量 time.sleep(0.008) def read_diff_a0a1(self, gain=2.0): # 启动一次新转换 self._write_config(self.config_single) # 等待转换完成(ADS1115会拉低ADDR引脚作为BUSY信号,但我们没接,只能硬等) time.sleep(0.008) # 读取CONVERSION寄存器(地址0x00) data = self.bus.read_i2c_block_data(self.addr, 0x00, 2) raw = (data[0] << 8) | data[1] # 补码转有符号整数 if raw >= 0x8000: raw -= 0x10000 # 换算电压:FS = 2.048 / gain,LSB = FS / 65536 lsb_v = (2.048 / gain) / 65536 return raw * lsb_v # 使用示例:每100 ms读一次,稳定如钟表 adc = ADS1115_Bare(bus_num=1, addr=0x48) while True: v = adc.read_diff_a0a1(gain=16.0) # 接DS18B20,用16倍增益 print(f"{v:.6f} V") time.sleep(0.1)这段代码刻意“反潮流”地放弃了所有花哨特性,原因很实在:
time.sleep(0.008)看着土,但它确保了每次读数都来自一次完整、独立的转换周期,不会因总线拥堵导致数据陈旧;- 不用
read_word_data(),因为它的字节序在不同内核版本上有差异,read_i2c_block_data()强制MSB优先,零歧义; - 把CONFIG寄存器值拆成二进制讲解(
0xC583 = 1100 0101 1000 0011),不是为了装懂,而是方便你根据传感器信号幅度,自己算出该设哪一档PGA——比如DS18B20输出0–5 mV,用PGA=16x刚好填满量程,信噪比最优;而DHT22输出0.8–3.8 V,就得切回PGA=1x,否则直接饱和。
真正的高精度,从来不是靠参数表里的“16位”撑起来的,而是靠你对每一纳秒、每一毫伏、每一位配置的理解和掌控。
实测效果:0.012°C分辨率是怎么来的?
最后说说那个让人踏实的数据——0.012°C温度分辨率,不是理论值,是我在恒温油槽里实测出来的。
系统构成很简单:
- DS18B20(寄生供电,精度±0.5°C)→ 经运放TLV2462做差分调理(增益100×)→ 接ADS1115 A0-A1;
- PGA设为16x,实际等效增益 = 100 × 16 = 1600×;
- 满量程输入:5 mV × 1600 = 8 V → 但ADS1115最大输入±4.096 V,所以实际用±4 V对应DS18B20的-55°C ~ +125°C范围;
- 16位分辨率为:8 V / 65536 ≈ 122 μV/LSB;
- DS18B20灵敏度约10 mV/°C → 对应温度分辨力 = 122 μV / 10 mV/°C ≈0.012°C/LSB。
但光有理论不够。为了让这0.012°C真正落在数据里,我还做了三件事:
- 软件均值滤波:每次采集16个原始值,剔除最大最小各1个,剩下14个求平均——不是为了平滑,是为了压制ADS1115 ΔΣ架构固有的1/f噪声;
- 冷端补偿校准:用ADS1115片上温度传感器读取PCB板温,作为热电偶冷端参考,再查NIST热电偶表反算——这一步把系统级温漂从±0.8°C压到±0.05°C;
- 电源噪声隔离:前面提到的LDO供电,实测使ADC输出RMS噪声从320 μV降到48 μV,相当于ENOB从12.1 bit提升到14.3 bit。
现在这套系统在温室里跑了半年,日志里看不到跳变尖峰,每天凌晨自动校准一次零点,长期漂移<0.03°C/月。它不炫,但可靠——这才是工业边缘节点该有的样子。
如果你也在树莓派5上折腾ADC,卡在某个环节,欢迎留言。比如:
- “ADS1115接上去i2cdetect能扫到,但read出来一直是0x8000” → 很可能是CONFIG寄存器写错,bit15没置1;
- “用PGA=16x时读数饱和,但信号明明很小” → 检查运放供电是否和ADS1115共地,有没有引入共模电压超限;
- “同样代码在树莓派4上正常,5上就不行” → 先拔掉所有HAT,关掉蓝牙/WiFi,再测I²C波形……
技术没有银弹,只有一个个被亲手拧紧的螺丝。而每一次把读数从跳变拉回平稳的过程,都是对硬件、驱动、信号链理解的重新校准。
你最近在树莓派5上踩过什么最深的坑?