
1. 项目概述为什么树莓派需要外置ADC玩树莓派的朋友尤其是搞物联网、数据采集或者智能家居的肯定都遇到过一个问题树莓派本身没有模拟输入接口。是的你没看错这个功能强大的小电脑它的GPIO引脚全是数字的。这意味着你没法直接用它来读取像电位器旋钮的角度、光照传感器的亮度值、土壤的湿度或者电池的电压这些连续变化的模拟信号。这对于一个标榜“创客神器”的设备来说确实是个不小的遗憾。这时候外置的模数转换器ADC就成了必备的扩展件。而在众多ADC芯片中德州仪器TI的ADS1115以其高精度、易用性和适中的价格成为了树莓派社区里最受欢迎的选择之一。它通过I2C总线与树莓派通信提供了4个单端或2个差分输入通道分辨率高达16位。简单来说它能把外部世界连续变化的电压“翻译”成树莓派能理解的数字让我们可以编程处理这些数据。我最近在一个环境监测项目里深度使用了ADS1115用它来采集温湿度、光照和土壤湿度传感器的数据。整个过程从选型、电路连接、驱动安装到Python编程踩过一些坑也总结了不少心得。这篇文章我就来手把手带你玩转树莓派上的这颗16位ADC芯片从原理到代码从接线到避坑让你也能轻松把模拟世界接入你的数字项目。2. 核心硬件解析ADS1115为何是树莓派的好搭档2.1 ADS1115芯片深度拆解在决定使用ADS1115之前我对比过好几款ADC比如精度较低的PCF85918位、MCP300810位以及更高速的ADS101512位。最终选择ADS1115主要是基于以下几个硬核考量首先16位分辨率是它的王牌。分辨率决定了ADC能把模拟电压分成多少级。对于5V的参考电压12位ADC如ADS1015的最小电压变化是5V / 4096 ≈ 1.22mV而16位的ADS1115能达到5V / 65536 ≈ 0.076mV。这意味着它能感知到更微小的电压变化。对于测量传感器输出比如PT100热电阻经过放大后的毫伏级信号这个精度优势非常明显。其次可编程增益放大器PGA让它适应性极强。ADS1115内置的PGA可以提供从±6.144V到±0.256V多个量程。比如当你测量一个最大只有3.3V的传感器信号时可以将PGA设置为±4.096V量程这样有效分辨率会更高测量结果更精确。这个功能是很多廉价ADC模块所不具备的。再者I2C接口与树莓派是天作之合。树莓派原生支持I2C只需两根线SDA SCL就能连接多个设备节省宝贵的GPIO资源。ADS1115的I2C地址可以通过ADDR引脚配置理论上一条总线上可以挂载4个ADS1115实现最多16路模拟输入扩展。最后内部基准和比较器是锦上添花。芯片内置了稳定的2.048V基准电压源保证了测量的准确性无需外接基准。而比较器功能可以设置阈值当输入电压超过设定值时ALRT引脚会输出警报这非常适合用于电池低压报警等无需CPU持续轮询的应用场景能有效降低系统功耗。注意市面上常见的蓝色ADS1115模块通常已经集成了必要的电平转换和滤波电路将芯片的IO电压从2.0-5.5V转换到与树莓派GPIO兼容的3.3V并且提供了便于接线的排针。购买时认准这种“树莓派兼容”模块会省事很多。2.2 硬件连接与电路要点连接ADS1115模块到树莓派非常简单但细节决定成败。以下是标准的四线连接法VCC- 树莓派3.3V(引脚1)。强烈建议使用3.3V而不是5V。虽然模块支持5V但树莓派的I2C总线电平是3.3V的用5V供电可能存在电平不匹配的风险长期使用可能损坏树莓派GPIO。GND- 树莓派GND(引脚6/9/14/20等任意一个)。SDA- 树莓派GPIO2 (SDA)(引脚3)。SCL- 树莓派GPIO3 (SCL)(引脚5)。如果你的模块有ADDR地址选择引脚通常通过焊接焊盘到GND或VCC来选择地址。默认不焊接时地址通常是0x48。ALRT警报引脚在基础数据采集应用中可以不接。这里有一个非常重要的实操心得务必在树莓派的I2C总线上加上拉电阻。I2C协议是开漏输出需要上拉电阻将总线拉到高电平。虽然树莓派内部GPIO有约1.8kΩ的弱上拉电阻但对于总线电容较大或设备较多的情况可能驱动力不足导致通信不稳定。很多ADS1115模块已经板载了4.7kΩ或10kΩ的上拉电阻通常位于模块背面如果你的模块没有或者你连接了多个I2C设备最好在SDA和SCL线上各外接一个4.7kΩ的电阻到3.3V。连接完成后一个常见的检查方法是使用i2cdetect命令。在终端输入sudo i2cdetect -y 1如果一切正常你应该能看到地址0x48或其他你设置的地址出现在输出列表中。如果看不到首先检查接线然后确认树莓派的I2C接口是否已启用可通过sudo raspi-config-Interface Options-I2C启用。3. 软件环境搭建与基础驱动3.1 启用I2C与安装Python库树莓派系统默认可能未开启I2C。启用方法如下sudo raspi-config # 选择 Interface Options - I2C - Yes 启用。重启后安装必要的工具和Python库sudo apt update sudo apt install -y i2c-tools python3-pip sudo pip3 install adafruit-circuitpython-ads1x15这里我选择的是Adafruit维护的ads1x15库。这个库封装得很好同时支持ADS1015和ADS1115API简洁明了社区支持也活跃。当然你也可以使用smbus2等底层库自己封装但对于快速上手和稳定应用Adafruit库是首选。安装后可以运行一个简单的测试脚本确认硬件和软件通信正常import board import busio import adafruit_ads1x15.ads1115 as ADS from adafruit_ads1x15.analog_in import AnalogIn # 创建I2C总线对象 i2c busio.I2C(board.SCL, board.SDA) # 创建ADS1115对象默认地址0x48 ads ADS.ADS1115(i2c) # 创建模拟输入对象使用通道0 chan AnalogIn(ads, ADS.P0) print(ADC值: {:5}\t电压: {:.3f}V.format(chan.value, chan.voltage))如果能看到打印出的ADC原始值和换算后的电压恭喜你最基础的环境已经打通了。3.2 库函数核心参数解读使用Adafruit库时有几个关键对象和参数需要理解ADS1115(i2c, address0x48, gain1): 初始化ADC对象。address是I2C地址。gain对应PGA增益设置是关键参数gain1- ±4.096V (默认)gain2- ±2.048Vgain4- ±1.024Vgain8- ±0.512Vgain16- ±0.256V选择原则让你的输入信号最大电压值尽可能接近但不超过所选量程。例如传感器最大输出3.0V用±4.096V量程gain1可以但用±2.048V量程gain2会获得更高的有效分辨率因为相同的16位分辨率被用在了更小的电压范围上。AnalogIn(ads, positive_pin, negative_pinADS.P2): 创建通道对象。positive_pin是正输入端如ADS.P0negative_pin是负输入端。默认负端是ADS.P2这实际上将通道0和1配置为了差分输入模式A0-A1。如果要使用单端模式以GND为参考需要将negative_pin设置为ADS.P3内部连接到GND即AnalogIn(ads, ADS.P0, ADS.P3)。chan.value和chan.voltage:value是原始的16位ADC读数-32768 到 32767因为它是带符号的用于差分测量。voltage是库根据你设置的gain自动换算出的电压值单位是伏特。这是最常用的属性。4. 高级应用与数据采集实战4.1 单端与差分模式的选择与配置这是使用ADS1115时第一个需要明确的概念。单端模式测量的是输入引脚相对于GND的电压。接线简单适用于大多数传感器如光敏电阻分压、电位器。差分模式测量的是两个输入引脚之间的电压差优点是能抑制共模噪声适合测量传感器桥路如电子秤的称重传感器或微小信号。使用Adafruit库配置单端模式测量A0引脚电压# 单端模式测量A0对GND的电压 chan_single AnalogIn(ads, ADS.P0, ADS.P3) # 负端指定为P3 (GND) print(fA0单端电压: {chan_single.voltage:.3f}V)使用差分模式测量A0和A1之间的电压差# 差分模式测量A0和A1之间的电压差 chan_diff AnalogIn(ads, ADS.P0, ADS.P1) # 正端A0负端A1 print(fA0-A1差分电压: {chan_diff.voltage:.3f}V) # 注意差分模式下voltage可能为负值如果A1电压高于A0。一个重要技巧ADS1115的四个模拟输入A0-A3是复用的。当你在差分模式下使用A0和A1作为一对时A2和A3会自动成为另一对差分输入。你不能同时将A0用于一个差分对又将A1用于另一个单端测量。硬件上通道是共享的。在编程时你需要规划好通道的使用方式。4.2 采样速率与数据读取策略ADS1115的采样速率是可编程的从8SPS每秒采样数到860SPS。更高的速率能捕捉更快速的信号变化但可能会引入更多的噪声并且需要更频繁的I2C读取占用CPU。对于温度、湿度这种变化缓慢的信号8SPS或16SPS完全足够还能降低功耗。在Adafruit库中采样速率通过data_rate参数设置ads ADS.ADS1115(i2c, data_rate128) # 设置采样率为128SPS可选的data_rate值有8, 16, 32, 64, 128, 250, 475, 860。读取数据有两种常见方式轮询模式在循环中不断读取chan.value或chan.voltage。这是最简单的方式但效率低且读取速度受Python循环和I2C通信延迟限制远达不到芯片的最高采样率。连续转换模式将芯片设置为连续转换模式然后以固定间隔读取结果。Adafruit库默认工作在单次转换模式读取一次才转换一次。要使用连续模式需要稍微深入底层配置# 切换到连续转换模式 ads.mode ADS.Mode.CONTINUOUS ads.data_rate 250 # 250SPS while True: # 在连续模式下读取的是最新一次的转换结果 print(chan.voltage) time.sleep(0.004) # 睡眠时间略小于1/250秒实测心得对于大多数树莓派应用单次转换模式配合轮询完全够用。连续模式在需要极高数据吞吐率时才有优势但要注意I2C总线速度和树莓派处理能力可能成为瓶颈。我曾在尝试用860SPS连续采集时因为I2C读取跟不上导致数据缓冲区溢出和程序卡顿。对于高于250SPS的应用建议评估使用更快的接口如SPI的ADC或性能更强的微控制器。4.3 多通道轮询采集实例一个典型的应用是轮流采集多个传感器的数据。下面是一个完整的例子使用单端模式采集4路传感器电压并计算对应的物理量假设是连接了4个电位器import time import board import busio import adafruit_ads1x15.ads1115 as ADS from adafruit_ads1x15.analog_in import AnalogIn # 初始化 i2c busio.I2C(board.SCL, board.SDA) ads ADS.ADS1115(i2c, gain1) # ±4.096V量程 # 创建4个单端输入通道 channels [ AnalogIn(ads, ADS.P0, ADS.P3), # A0 AnalogIn(ads, ADS.P1, ADS.P3), # A1 AnalogIn(ads, ADS.P2, ADS.P3), # A2 AnalogIn(ads, ADS.P3, ADS.P3), # A3 (注意P3作为负端这里测量的是A3对内部GND实际上A3不能以P3为负做单端测量) ] # 更正对于A3的单端测量负端需要连接到GND但ADS1115内部没有将P3连接到GND的单独通道。 # 更通用的做法是使用差分模式将负端固定在一个参考电压如通过一个通道接地上。 # 或者使用一个外部GND连接到负端。这里为了示例简化实际应用需注意。 # 一个可行的多通道单端测量方案假设A3接GND作为公共参考 # 通道0: A0 - A3 (差分A3接地) # 通道1: A1 - A3 # 通道2: A2 - A3 # 这样实现了以A3为地的“伪”单端测量。 channels [ AnalogIn(ads, ADS.P0, ADS.P3), # A0 相对于 A3 (GND) AnalogIn(ads, ADS.P1, ADS.P3), # A1 相对于 A3 (GND) AnalogIn(ads, ADS.P2, ADS.P3), # A2 相对于 A3 (GND) ] # 注意此时A3引脚需要硬件连接到GND。 print(按下 CtrlC 停止) try: while True: for i, ch in enumerate(channels): voltage ch.voltage # 示例将电压转换为角度假设电位器接3.3V电压范围0-3.3V对应0-300度 angle (voltage / 3.3) * 300 print(f通道{i}: {voltage:.3f} V - {angle:.1f} 度, end | ) print() # 换行 time.sleep(1) # 每秒采集一轮 except KeyboardInterrupt: print(\n采集结束。)这个例子展示了基本的循环采集结构。在实际项目中你可能需要将数据存入文件、数据库或者通过MQTT发送到服务器。5. 精度提升与噪声处理实战技巧16位ADC听起来精度很高但实际使用中读数跳变、噪声干扰是常见问题。以下是我在实践中总结的几条提升测量质量的技巧1. 硬件滤波是根本在传感器信号进入ADC输入端之前加入RC低通滤波电路例如一个1kΩ电阻串联和一个0.1uF电容对地可以极大地抑制高频噪声。对于ADS1115模块如果信号线较长这个滤波尤其重要。2. 软件滤波平滑数据对于缓慢变化的信号在软件中对连续多次采样值进行平均可以显著减少随机噪声的影响。常用的有滑动平均滤波和中值滤波。import collections class MovingAverageFilter: def __init__(self, window_size10): self.window collections.deque(maxlenwindow_size) def update(self, new_value): self.window.append(new_value) return sum(self.window) / len(self.window) # 使用示例 filter_a0 MovingAverageFilter(window_size5) smoothed_voltage filter_a0.update(chan0.voltage)3. 注意参考电压和增益的匹配ADS1115使用内部2.048V基准。当PGA增益设置大于1时量程变小但基准不变所以最小分辨率LSB会变化。例如增益1±4.096V时LSB 4.096V / 32768 0.125mV增益2±2.048V时LSB 2.048V / 32768 0.0625mV。理解这一点有助于你评估系统的理论精度。4. 避免I2C总线干扰尽量让I2C走线远离树莓派的高频噪声源如SD卡槽、HDMI接口。如果条件允许使用带屏蔽的线缆。确保电源稳定可以在VCC和GND之间并联一个10uF的电解电容和一个0.1uF的陶瓷电容用于退耦。5. 校准与补偿对于需要高绝对精度的应用比如电压表可以对ADC进行校准。用一个已知精密的电压源如基准电压芯片输入一个固定电压读取ADC值计算出一个校准系数。公式大致是实际电压 ADC读数 * 系数 偏移。偏移量可以通过测量短路输入0V时的读数来确定。6. 典型问题排查与解决方案实录即使按照教程操作你也可能会遇到一些奇怪的问题。下面是我和社区里常见的一些坑及其解决方法问题1i2cdetect能看到地址但Python读取全是0或固定值。可能原因A增益Gain设置不当。如果输入电压很小比如几毫伏而增益设置得太低如gain1量程±4.096VADC读数可能始终在最低位徘徊看起来像0。尝试增大增益如设为gain16。可能原因BI2C总线速度不匹配或冲突。树莓派I2C默认速度是100kHz。尝试在初始化时降低速度Adafruit库似乎不支持直接设置但可以尝试使用busio.I2C(board.SCL, board.SDA, frequency10000)创建低速总线对象但需注意库兼容性。更常见的是总线上有其他设备冲突尝试只连接ADS1115模块进行测试。可能原因C电源问题。确保模块供电稳定。用万用表测量VCC和GND之间的电压是否稳定在3.3V。问题2读数不稳定跳动很大。排查步骤首先将输入引脚短路到GND测量0V观察读数。理想情况应该是0V附近非常小的波动几个LSB以内。如果此时跳动依然很大问题出在ADC本身或电源上。如果短路GND时稳定接上传感器后跳动问题出在传感器信号或引线上。检查传感器供电是否稳定信号线是否过长且未屏蔽尝试在输入端增加滤波电容。检查接地。确保树莓派、ADS1115模块和传感器共地良好。糟糕的接地是噪声的主要来源。问题3测量负电压解释在单端模式下以GND为参考ADS1115不能测量负电压。如果输入电压低于GND读数将是未定义的可能损坏芯片。在差分模式下可以测量负的电压差即positive_pin电压低于negative_pin电压此时chan.voltage会输出负值。绝对禁止将任何引脚电压施加到低于GND 0.3V或高于VCC0.3V否则可能永久损坏芯片。问题4如何测量高于3.3V或5V的电压解决方案使用电阻分压电路。例如要测量0-10V的电压可以使用一个9kΩ和1kΩ的电阻串联。10V电压在1kΩ电阻上分得1V将这个1V接入ADS1115需确保在量程内。计算关系为V_in V_adc * (R1R2)/R2。注意分压电阻的精度和温漂会影响测量精度。问题5同时使用多个ADS1115模块。操作利用ADDR引脚设置不同的I2C地址。模块上通常有A0/A1焊盘将对应焊盘短接到GND或VCC可以改变地址。地址映射如下ADDR引脚连接I2C地址GND0x48VCC0x49SDA0x4ASCL0x4B在代码中初始化时指定对应的地址即可ads1 ADS.ADS1115(i2c, address0x48)ads2 ADS.ADS1115(i2c, address0x49)。最后分享一个我项目中的小技巧为了长时间稳定运行我写了一个简单的看门狗脚本。它会定期检查ADC是否还能正常读取数据如果连续多次读取失败就尝试重新初始化I2C总线和ADC对象。这对于需要7x24小时运行的数据记录站非常有用可以应对偶尔的I2C总线锁死问题虽然不常见但确实会发生。