树莓派ADS1115高精度ADC数据采集与实时可视化实战
2026/6/19 16:31:37 网站建设 项目流程

1. 项目概述:从模拟信号到数字洞察

在嵌入式开发和物联网项目中,数据采集是连接物理世界与数字世界的桥梁。我们身边的光照强度、环境温度这些连续变化的物理量,都属于模拟信号。而像树莓派(Raspberry Pi)这样的微处理器,其GPIO口通常只能处理“高”或“低”的数字信号。因此,要读取一个模拟传感器,比如一个随着光线变亮而电阻值线性变化的光敏电阻,或者一个输出电压随温度变化的温度传感器,一个关键的中间件——模数转换器(ADC)——就变得不可或缺。ADS1115就是这样一款高精度、易用的ADC芯片,它通过I2C总线与树莓派通信,将传感器输出的微弱电压信号,精准地转换为树莓派能够理解和处理的数字值。

本教程的目标,就是带你亲手搭建一个能够同时监测光照和温度,并能将数据实时绘制成动态图表的完整系统。这不仅仅是简单的连线与代码复制,我会深入讲解每个环节背后的“为什么”:为什么选择ADS1115而不是其他ADC?为什么电路里需要电位器?代码中的那些除数是怎么来的?通过这个项目,你将掌握传感器数据采集的核心工作流,这套方法可以轻松迁移到土壤湿度、气压、声音强度等任何模拟传感器的应用场景中,是智能家居、环境监测、简易数据记录仪等项目开发的坚实基石。

2. 核心硬件选型与电路设计解析

2.1 为什么是ADS1115?

市面上ADC芯片很多,从廉价但精度低的PCF8591到高速高精度的专用芯片都有。选择ADS1115作为树莓派的模拟前端,是基于几个非常实际的考量:

首先,精度与分辨率。ADS1115是一款16位ADC,这意味着它可以将输入的电压范围(例如0-3.3V)划分为 2^16 = 65536 个不同的数字值。其最小可分辨的电压变化(LSB)为 3.3V / 65536 ≈ 0.05mV。相比之下,树莓派Pico内置的ADC只有12位(4096级),而像PCF8591这类8位I2C ADC只有256级,精度相差甚远。对于光敏电阻、热敏电阻这类变化相对缓慢但需要精细观察的趋势信号,高分辨率能捕捉到更微小的波动。

其次,接口与易用性。ADS1115通过I2C总线通信,只需要连接SDA、SCL两根数据线和电源、地线,极大节省了树莓派宝贵的GPIO资源。其I2C地址可通过引脚配置,方便在同一总线上挂载多个设备。更重要的是,Adafruit等社区为其提供了成熟、稳定的Python驱动库(adafruit-circuitpython-ads1x15),让我们可以用几行代码就完成复杂的ADC配置和数据读取,避免了直接操作寄存器的麻烦。

最后,输入范围与内置增益。ADS1115的输入电压范围是可编程的,可以从±6.144V到±0.256V,通过内置的可编程增益放大器(PGA)实现。这意味着它既能测量较大的信号,也能通过放大来精确测量微弱的传感器信号(如热电偶输出),适应性非常强。在本项目中,我们使用单端输入模式,参考电压为树莓派的3.3V。

注意:ADS1115有ADS1115(16位)和ADS1015(12位)两个版本,购买时请确认型号。对于大多数传感器应用,ADS1115的精度更值得推荐。

2.2 传感器与分压电路原理

我们的核心测量对象是光敏电阻(LDR)和温度传感器(这里假设是NTC热敏电阻)。它们都是电阻式传感器,其电阻值会随外部物理量(光照、温度)变化。树莓派和ADS1115不能直接测量电阻,所以我们需要一个经典电路——分压电路——将电阻值的变化转换为电压值的变化。

电路原理:将一个固定电阻(或电位器)与传感器电阻串联,接在电源(3.3V)和地(GND)之间。测量点位于这两个电阻之间。根据欧姆定律,测量点的电压 V_out = 3.3V * (R_sensor / (R_fixed + R_sensor))。当传感器电阻R_sensor变化时,V_out也随之变化。

电位器的关键作用:原文中提到要使用电位器,其阻值取传感器阻值变化范围的中间值。这是为了优化测量范围,让V_out在传感器整个工作区间内都能落在ADS1115的有效输入电压范围内(接近0-3.3V),从而充分利用ADC的16位分辨率,获得最佳的测量精度和灵敏度。

  • 计算示例:假设你的光敏电阻在完全黑暗时电阻为1MΩ,在强光下为1kΩ。其阻值中值大约为√(1MΩ * 1kΩ) ≈ 31.6kΩ(几何平均更适合阻值变化范围大的情况)。你可以选择一个50kΩ的可调电位器,通过调节,使在典型光照下V_out大约在1.65V(半量程)左右。这样,无论光线变亮还是变暗,电压都有充足的上下变化空间,不会轻易达到0V或3.3V的极限。

温度传感器:同样原理。如果你使用的是三端模拟温度传感器(如LM35),它直接输出与温度成比例的电压,则可以直接接入ADS1115,无需分压电路。但如果是两端的NTC热敏电阻,其接线方式与光敏电阻完全相同。

2.3 完整电路连接指南

根据原理,我们进行实际连接。请务必在树莓派断电的情况下进行焊接或插线。

  1. ADS1115与树莓派连接

    • VDD-> 树莓派3.3V引脚(例如第1或17号引脚)。
    • GND-> 树莓派GND引脚(例如第6、9、14、20、25等任一引脚)。
    • SCL-> 树莓派GPIO3 (SCL1)引脚(第5号引脚)。
    • SDA-> 树莓派GPIO2 (SDA1)引脚(第3号引脚)。
    • ADDR:此引脚决定I2C地址。悬空或接GND时地址为0x48,这是我们最常用的配置。
  2. 光敏电阻分压电路(接ADS1115的A0通道)

    • 将树莓派3.3V连接到面包板正极总线。
    • 将树莓派GND连接到面包板负极总线。
    • 取一个电位器(例如10kΩ),将其两侧引脚分别接正极总线负极总线,中间引脚(滑片)接一根线,我们称之为“电位器中间信号线”。
    • 将光敏电阻的一端接正极总线,另一端接一根线,我们称之为“光敏电阻信号线”。
    • 将“电位器中间信号线”和“光敏电阻信号线”拧在一起或接在面包板的同一个孔里,然后从这个连接点引出一根线,连接到ADS1115的A0引脚。这就构成了分压电路。
  3. 温度传感器分压电路(接ADS1115的A1通道)

    • 重复上述步骤,使用另一个电位器和你的温度传感器(如NTC),将分压点连接到ADS1115的A1引脚。

实操心得:接线完成后,强烈建议用万用表电压档检查一下A0和A1对GND的电压。用手遮住光敏电阻或握住温度传感器,观察电压是否在合理范围内(如0.5V-2.8V)变化。这能在上电前排除大部分接线错误。

3. 软件环境配置与核心代码剖析

3.1 系统准备与驱动安装

树莓派系统(Raspbian/Raspberry Pi OS)默认已包含Python3和pip,我们需要的是启用I2C接口并安装必要的库。

  1. 启用I2C接口

    • 图形界面:菜单->首选项->Raspberry Pi 配置->接口标签页 -> 启用I2C
    • 命令行(更常用):打开终端,运行sudo raspi-config,选择Interface Options->I2C->Yes启用。重启生效。
  2. 安装Python库

    • 更新软件包列表:sudo apt update
    • 安装ADS1115的Adafruit驱动库和matplotlib。建议使用pip3为当前用户安装,避免系统Python环境冲突。
    pip3 install adafruit-circuitpython-ads1x15 matplotlib numpy
    • 如果提示pip3未找到,先安装pip3:sudo apt install python3-pip

验证I2C设备:安装i2c-tools工具并检测ADS1115是否被正确识别。

sudo apt install i2c-tools -y sudo i2cdetect -y 1

如果一切正常,你应该会在输出表格中看到地址48(十六进制显示为0x48)。如果看不到,请立即检查硬件连接和I2C是否启用。

3.2 数据采集代码深度解读

让我们逐段分析核心代码,理解每一行的意图和可调整的参数。

import matplotlib.pyplot as plt import numpy as np # 虽然本例未直接使用np函数,但matplotlib依赖它,导入是好习惯 import board import busio import time import adafruit_ads1x15.ads1115 as ADS from adafruit_ads1x15.analog_in import AnalogIn # 初始化I2C总线,指定使用树莓派默认的I2C引脚 i2c = busio.I2C(board.SCL, board.SDA) # 创建ADS1115对象,默认地址0x48,默认增益1(±4.096V范围) ads = ADS.ADS1115(i2c) # 如果你需要不同的增益,可以在这里设置,例如: # ads.gain = 2/3 # ±6.144V # ads.gain = 1 # ±4.096V (默认) # ads.gain = 2 # ±2.048V # ads.gain = 4 # ±1.024V # ads.gain = 8 # ±0.512V # ads.gain = 16 # ±0.256V # 增益越大,能分辨的微小电压变化越精细,但量程越小。 # 创建两个模拟输入通道对象,分别对应ADS1115的A0和A1引脚 light_channel = AnalogIn(ads, ADS.P0) # 光敏电阻 temp_channel = AnalogIn(ads, ADS.P1) # 温度传感器 # AnalogIn对象有两个常用属性: # .value: 获取原始的16位ADC读数 (0-65535) # .voltage: 获取计算后的电压值 (单位:伏特) # 在本项目中,我们使用.value进行后续计算,因为它更直接。

数据存储与初始化

# 初始化时间轴(X轴)和数据轴(Y轴)的列表 time_points = [] # 存储时间点 light_values = [] # 存储处理后的光照值 temp_values = [] # 存储处理后的温度值 # 设置图表初始参数 plt.ion() # 开启交互模式,这是实现动态更新的关键! fig, ax = plt.subplots() ax.set_ylim(-50, 1000) # 根据你的传感器和除数预估Y轴范围,可调整 ax.set_xlabel('Time (seconds)') ax.set_ylabel('Processed Sensor Value') ax.set_title('Real-time Light & Temperature Monitoring') # 创建两条线的对象,并获取其引用 light_line, = ax.plot([], [], label='Light Intensity', color='#0069af', lw=2) temp_line, = ax.plot([], [], label='Temperature', color='#ff8000', lw=2) ax.legend()

核心采集与动态绘图循环

start_time = time.time() # 记录程序开始时间 try: while True: # 1. 计算当前时间(相对于开始时间的秒数) current_time = time.time() - start_time time_points.append(current_time) # 2. 读取原始ADC值并进行缩放处理 raw_light = light_channel.value raw_temp = temp_channel.value # 这里是关键!除数(30和3)需要根据你的实际电路校准。 # 目的:将原始的0-65535的值,缩放到一个便于在图表上观察的范围。 processed_light = raw_light / 30 # 假设除数30 processed_temp = raw_temp / 3 # 假设除数3 light_values.append(processed_light) temp_values.append(processed_temp) # 3. 更新图表数据 light_line.set_data(time_points, light_values) temp_line.set_data(time_points, temp_values) # 4. 自动调整X轴范围,显示最近一段时间的数据(例如最近60秒) ax.set_xlim(max(0, current_time - 60), current_time + 5) # 5. 重绘图表并短暂暂停 fig.canvas.draw() fig.canvas.flush_events() time.sleep(5) # 每5秒采集一次数据。可根据需要调整,太频繁可能影响性能。 except KeyboardInterrupt: # 当用户按下Ctrl+C时,优雅退出 print("\nProgram terminated by user.") plt.ioff() # 关闭交互模式 plt.show() # 显示最终的静态图表

3.3 校准与除数调整实战

代码中的raw_light / 30raw_temp / 3校准的关键步骤。除数没有魔法,需要你根据实际测量来调整。

校准流程

  1. 先不设置除数,或者将除数设为1,在循环中打印出raw_lightraw_temp的值。
    print(f"Raw Light: {raw_light}, Raw Temp: {raw_temp}")
  2. 手动改变环境条件。对于光敏电阻,用手完全遮盖,然后用手电筒近距离照射。对于温度传感器,用手握住加热,或者用冰块冷却。
  3. 观察打印出的原始ADC值的最大变化范围。例如,光照变化下,raw_light从 8000 变到 45000。
  4. 设定一个你希望图表显示的Y轴范围,比如0-1000。那么,你的除数就是原始值变化范围 / 期望的显示范围
    • 例如:光敏电阻原始变化范围约 45000 - 8000 = 37000。希望显示在0-1000内,除数可初步设为 37000 / 1000 = 37。你可以取整为30或40进行微调。
    • 除数越大,最终显示的值越小,曲线在图表上就越“平缓”;除数越小,显示值越大,曲线就越“陡峭”。目标是让曲线在典型变化下能占据图表Y轴的大部分区域,既不顶到天花板,也不贴在地板上。

重要提示plt.ylim(-50, 1000)中的范围需要与你调整后的除数和传感器实际输出匹配。如果调整除数后曲线仍然超出范围,记得同步修改这里的ylim

4. 系统优化与高级应用拓展

4.1 提升数据质量与系统稳定性

基础的采集循环可以工作,但在长期运行或复杂应用中,以下几点优化至关重要:

  1. 软件滤波:ADC读数可能存在随机噪声。简单的软件滤波可以平滑曲线,让趋势更明显。

    • 移动平均滤波:存储最近N个读数,取平均值作为当前值。
    window_size = 5 light_history = [] def filtered_reading(new_value, history_list, window): history_list.append(new_value) if len(history_list) > window: history_list.pop(0) return sum(history_list) / len(history_list) # 在循环中使用 smoothed_light = filtered_reading(raw_light, light_history, window_size)
  2. 异常值处理:偶尔的读数跳变(例如由于电源干扰)可能会破坏图表。可以设置一个合理的物理变化阈值,如果本次读数与上次读数差值超过阈值,则视为异常并忽略或使用上次值。

  3. 数据记录与持久化:实时图表很棒,但历史数据需要保存。可以在每次循环中将时间戳和处理后的数据写入CSV文件。

    import csv # 在程序开始时 with open('sensor_data.csv', 'w', newline='') as f: writer = csv.writer(f) writer.writerow(['Timestamp', 'Light', 'Temperature']) # 在循环中 with open('sensor_data.csv', 'a', newline='') as f: writer = csv.writer(f) writer.writerow([current_time, processed_light, processed_temp])
  4. 使用线程或异步:如果采集间隔很短(如<0.1秒),绘图操作可能会阻塞采集循环,导致时间戳不准。可以考虑使用threading模块将数据采集和绘图更新放在不同线程,或者使用asyncio进行异步处理。

4.2 从ADC值到实际物理量

目前我们得到的是经过缩放的“相对值”。对于许多监控应用,这已经足够。但如果你需要知道精确的“勒克斯(Lux)”或“摄氏度(°C)”,就需要进行标定和转换。

  • 光敏电阻:其电阻-照度关系通常是非线性的,且不同型号差异很大。制造商的数据手册会提供电阻-照度曲线或公式。你需要通过实验,测量几个已知照度下(可用手机光强计APP粗略参考)对应的ADC值或电压值,然后通过插值或拟合(如指数拟合)来建立转换函数。
  • NTC热敏电阻:其电阻-温度关系可以用Steinhart-Hart方程来描述:1/T = A + B*ln(R) + C*[ln(R)]^3,其中A, B, C是常数,通常数据手册会提供。你需要先根据分压电路公式和测得的电压反算出热敏电阻的当前阻值R,再代入公式计算温度T(开尔文),最后转换为摄氏度。
  • 模拟温度传感器(如LM35/TMP36):这类传感器输出与温度成线性比例的电压(如LM35为10mV/°C)。转换非常简单:温度(°C) = (电压值 - 零点偏移) * 缩放系数。例如,从.voltage属性获得电压V,对于TMP36,温度 = (V - 0.5) * 100

示例:将ADS1115电压转换为实际值

# 假设使用TMP36,接在ADS1115的A2通道 temp_sensor = AnalogIn(ads, ADS.P2) # 直接读取电压并转换 voltage = temp_sensor.voltage temperature_c = (voltage - 0.5) * 100 print(f"Voltage: {voltage:.3f}V, Temperature: {temperature_c:.2f}°C")

4.3 项目扩展思路

这个基础框架可以像乐高一样扩展:

  1. 多传感器网络:一个ADS1115有4个单端输入通道。你可以同时接入光、温、湿、声四种传感器。I2C总线可以挂载多个ADS1115(通过设置不同的ADDR引脚电平),轻松扩展至8、12甚至更多通道。
  2. 远程监控与Web界面:使用Flask或FastAPI框架,将树莓派变成一个Web服务器。在后台运行数据采集脚本,将数据存入数据库(如SQLite),前端通过HTTP请求获取数据,并用ECharts或Chart.js在浏览器中绘制更美观的实时图表。这样你就可以在任何设备的浏览器上查看监控数据。
  3. 阈值报警与自动化:在代码中加入逻辑判断,当光照低于某值自动打开LED灯,当温度高于某值自动启动风扇或发送邮件/短信提醒(可集成SMTP或Telegram Bot API)。
  4. 低功耗与太阳能供电:如果用于野外长期监测,可以考虑使用树莓派Zero W(功耗更低),并配置定时唤醒采集、数据打包上传后深度睡眠的策略,配合太阳能电池板和充电管理模块,实现能源自给。

5. 常见问题与故障排查实录

即使按照教程操作,你也可能会遇到一些坑。下面是我在多次项目中总结出的问题清单和解决方法。

问题现象可能原因排查步骤与解决方案
运行脚本时报错ModuleNotFoundError: No module named 'board''adafruit_ads1x15'Python库未正确安装,或在不同Python环境中运行。1. 确认安装命令:pip3 install adafruit-circuitpython-ads1x15
2. 确认运行命令:python3 your_script.py。如果系统有多个Python版本,使用which python3which pip3确认路径一致。
3. 尝试使用sudo pip3安装,或使用虚拟环境。
sudo i2cdetect -y 1检测不到地址0x48I2C未启用、硬件连接错误、电源问题或设备损坏。1.检查I2C启用sudo raspi-config-> Interface Options -> I2C -> Enable。
2.检查物理连接:重点检查SDA、SCL、3.3V、GND这四根线是否接错、虚接。用万用表通断档检查。
3.检查电源:测量ADS1115的VDD引脚对GND是否为稳定的3.3V。
4.检查地址:确认ADS1115的ADDR引脚是悬空或接地(地址0x48)。如果接了VDD,地址会是0x49,此时应用i2cdetect -y 1查找0x49。
5.尝试降低I2C速率:在代码初始化I2C时尝试i2c = busio.I2C(board.SCL, board.SDA, frequency=100000)
图表没有数据或是一条直线代码逻辑错误、传感器接线错误、除数设置极端不合理。1.打印原始值:在循环内打印light_channel.valuetemp_channel.value,看它们是否随环境变化。如果不变化,是硬件或通道配置问题。
2.检查通道绑定:确认AnalogIn(ads, ADS.P0)中的P0P1与实际传感器连接的A0、A1一致。
3.检查分压电路:用万用表测量ADS1115的A0/A1引脚对GND的电压,手动改变传感器状态(遮光、加热),看电压是否变化。如果没有变化,检查分压电路的接线。
4.检查除数:如果原始值变化正常,但图表是直线,可能是除数太大或太小,导致处理后的值变化微乎其微或超出Y轴范围。调整除数并同步调整plt.ylim()
图表更新卡顿或程序无响应matplotlib交互模式更新开销大,或循环间隔太短。1.增加采集间隔:将time.sleep()的时间从5秒增加到10秒或更长。
2.优化绘图:不要在循环中反复创建新的plot,而是像优化后的代码那样,只更新已有线条的数据set_data()和坐标轴范围set_xlim()
3.减少历史数据点:只保留最近100个点用于绘图if len(time_points) > 100: time_points.pop(0); light_values.pop(0)
4.考虑其他绘图库:对于极高频率的实时数据,可以考虑使用pyqtgraph,它专为高性能实时绘图设计。
读数不稳定,曲线毛刺多电源噪声、传感器噪声、接线过长引入干扰。1.硬件滤波:在ADS1115的输入引脚(A0, A1)与GND之间并联一个0.1uF的陶瓷电容,可以滤除高频噪声。
2.软件滤波:实现如前所述的移动平均滤波。
3.检查电源:使用线性稳压电源为树莓派供电,避免使用劣质或功率不足的USB电源。在树莓派电源引脚和ADS1115的VDD附近并联一个10uF以上的电解电容。
4.缩短接线:使用较短的杜邦线,并尽量使连线整齐,避免形成天线引入干扰。

调试这类项目,一个核心的原则是分段隔离。先确保硬件连接和I2C通信正常(i2cdetect),再确保能读到变化的原始值(打印value),最后才是处理数据和绘图。耐心地一步步走,每一个环节都验证通过,最终的系统就会稳定可靠。这个从模拟信号感知,到数字转换,再到可视化呈现的完整链条,是物联网时代最基础的技能之一,希望你能通过这个项目真正掌握它。

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

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

立即咨询