
1. 项目缘起从点亮屏幕到读取电压一次完整的I2C实战最近在折腾树莓派想给它加点“感官”和“嘴巴”。感官指的是能感知模拟世界比如测量一下电池电压、光照强度嘴巴指的是能显示点信息不用每次都SSH登录看日志。这两个需求恰好能通过两个非常经典的I2C外设来实现I2C接口的LCD1602液晶屏和ADS1115模数转换器ADC。这听起来像是两个独立的任务但把它们放在一起恰恰是理解I2C总线“一主多从”精髓的绝佳实践。很多人学I2C可能只接一个设备跑通例程就结束了但实际项目中总线上挂多个设备才是常态。这次我们就来一次“全家桶”式的实战用树莓派作为主机Master同时驱动一块LCD屏幕和一个ADC芯片完成从读取模拟电压到显示结果的完整链路。你会发现理解了地址和协议操作多个I2C设备就像在同一个文件夹里操作不同的文件一样简单。2. 战前准备硬件连接与软件环境搭建在写代码之前确保硬件和软件基础打得牢能避免后面99%的“玄学”问题。2.1 硬件清单与接线图你需要准备以下硬件树莓派任何型号均可本文以树莓派4B为例。I2C LCD1602模块注意是“I2C接口”的它通常是一个蓝色PCB小板上面集成了LCD屏和一个PCF8574或类似的I2C转接芯片。背面一般有地址选择焊盘如A0, A1, A2。ADS1115模块一个16位高精度ADC模块同样通过I2C通信。它也有地址选择引脚ADDR。杜邦线若干用于连接。核心接线原则所有I2C设备共享两条总线——SDA数据线和SCL时钟线。此外还需要共地GND电源VCC则根据模块需求连接3.3V或5V。接线步骤连接总线与电源将树莓派的GPIO2 (SDA)引脚连接到LCD模块的SDA引脚和ADS1115模块的SDA引脚。将树莓派的GPIO3 (SCL)引脚连接到LCD模块的SCL引脚和ADS1115模块的SCL引脚。将树莓派的GND引脚连接到两个模块的GND。电源注意树莓派GPIO的电源引脚是3.3V。务必确认你的LCD模块和ADS1115模块是否支持3.3V逻辑电平。大多数模块兼容3.3V/5V但为稳妥起见建议先用3.3V供电。将树莓派的3.3V引脚连接到两个模块的VCC。设置设备地址这是避免冲突的关键LCD模块查看背面的地址选择焊盘。如果全部断开默认其I2C地址通常是0x27PCF8574或0x3F某些兼容芯片。如果你需要接多个同款LCD才需要焊接这些焊盘来改变地址。ADS1115模块模块上有一个ADDR引脚通过连接到GND、VCC或SDA、SCL来设置4个不同地址0x48, 0x49, 0x4A, 0x4B。默认悬空或接GND通常是0x48。确保它和LCD的地址不同。注意接线时最好断电操作。接好后务必仔细检查尤其是电源不要接反SDA/SCL不要接错。一次错误的接线可能损坏树莓派或模块。2.2 软件环境配置启用I2C与安装库树莓派系统默认可能未启用I2C接口我们需要先打开它。启用I2C接口在终端运行sudo raspi-config。选择Interface Options-I2C-Yes来启用ARM I2C接口。退出并重启树莓派sudo reboot。检查I2C设备是否被识别重启后运行sudo i2cdetect -y 1树莓派40针GPIO排针使用I2C-1总线。你会看到一个表格如果接线和地址设置正确应该在对应的地址位置显示数字如0x27和0x48而不是“--”。这证明树莓派已经“看到”了这两个设备。安装必要的Python库更新包列表sudo apt update安装Python3的I2C工具和库sudo apt install python3-smbus i2c-tools安装针对LCD和ADC的专用Python库它们封装了底层操作让我们更省心ADS1115库pip3 install adafruit-circuitpython-ads1x15一个常用的LCD库RPLCD可能对I2C支持更友好pip3 install RPLCD备选你也可以使用Adafruit的CharLCD库pip3 install adafruit-circuitpython-charlcd环境准备好后我们就可以分步攻克这两个设备了。3. 初探I2C LCD让树莓派“开口说话”LCD1602是最常见的字符型液晶能显示2行每行16个字符。直接驱动它需要7-10根线而I2C模块将其简化为2根数据线。3.1 I2C LCD驱动原理浅析I2C LCD模块的核心是一个“翻译官”芯片比如PCF8574。这是一个8位I/O扩展芯片通过I2C接收树莓派发来的命令和数据然后用自己的8个引脚模拟出驱动原生LCD1602所需的时序和数据。树莓派只需要关心向特定I2C地址发送正确的字节序列剩下的复杂时序都由这个“翻译官”搞定。我们使用的RPLCD或Adafruit_CharLCD库就是把这些字节序列的生成规则封装成了简单的函数比如write_string()、clear()。3.2 使用RPLCD库驱动LCD这里以RPLCD库为例因为它对I2C的支持比较直接。首先我们需要确定模块的I2C地址和背板芯片类型。# lcd_test.py import time from RPLCD.i2c import CharLCD # 初始化LCD # 参数背板芯片类型‘PCF8574’ I2C地址0x27 列数行数 lcd CharLCD(i2c_expanderPCF8574, address0x27, port1, cols16, rows2, dotsize8) # 注意port1 对应树莓派上的 /dev/i2c-1 # 清屏 lcd.clear() # 在第一行显示文字 lcd.write_string(Hello, Pi!) # 将光标移动到第二行开头 lcd.cursor_pos (1, 0) # 在第二行显示文字 lcd.write_string(I2C LCD Ready) time.sleep(5) lcd.clear()运行这个脚本python3 lcd_test.py如果你的LCD地址正确应该能看到两行显示。如果屏幕只亮背光无字符可能是对比度问题。很多I2C模块上有一个蓝色的电位器用螺丝刀旋转它可以调节对比度直到字符清晰出现。实操心得地址不对怎么办如果i2cdetect看到了设备但LCD不工作最常见的原因是地址不对。尝试0x3F、0x20、0x38等常见地址。i2cdetect显示的数字是十六进制在代码里要写成0x27这种形式。库的兼容性如果RPLCD不工作可以换用Adafruit_CharLCD库初始化方式略有不同需要指定SDA和SCL的GPIO引脚编号。显示乱码或错位确保初始化时cols和rows参数设置正确。写入字符串前可以用lcd.home()将光标归位。4. 深入ADC模块为树莓派添加“模拟感官”树莓派的GPIO只能读取数字信号高/低电平但真实世界很多信号是模拟的如电压连续变化。ADS1115就是一个高精度的“模拟转数字”翻译官它能将0-3.3V或更高取决于配置的电压转换成一个16位的数字值0-65535。4.1 ADS1115的核心特性与配置ADS1115有4个输入通道A0-A3可以单端输入测量对GND电压或差分输入测量两通道间的电压差。它内部有一个可编程增益放大器PGA允许你测量更小的电压变化提高分辨率。所有这些功能都通过配置其内部的几个寄存器来实现。幸运的是Adafruit的库帮我们处理了这些细节。关键配置包括增益GAIN决定了ADC的量程。例如GAIN_TWOTHIRDS对应 ±6.144VGAIN_ONE对应 ±4.096V。量程越小对相同电压变化的数字分辨率越高。如果你测量的是3.3V系统内的信号选择GAIN_TWOTHIRDS或GAIN_ONE都比较安全。采样速率DATA_RATE从每秒8次到860次不等。速率越高数据刷新越快但可能引入更多噪声。对于读取缓慢变化的传感器如温度、光照低速即可。工作模式连续转换模式或单次转换模式。单次模式更省电适合电池供电场景。4.2 编写ADC数据读取代码我们将使用Adafruit的ADS1x15库来读取A0通道的电压。# adc_test.py import time 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对象传入I2C总线和地址 ads ADS.ADS1115(i2c, address0x48) # 地址根据你的模块设置修改 # 设置增益量程这里选择±4.096V ads.gain 1 # 对应GAIN_ONE # 创建模拟输入对象指定使用ADS1115的哪个通道A0 chan AnalogIn(ads, ADS.P0) # ADS.P0 对应 A0 通道 print(ADC Value, Voltage) try: while True: # 读取原始值和计算出的电压 print(f{chan.value}, {chan.voltage:.3f}V) time.sleep(1) except KeyboardInterrupt: print(\nProgram stopped.)运行此代码你会看到终端不断打印A0引脚上的原始ADC值0-32767因为这是16位有符号数我们单端输入只用正值部分和换算后的电压值。你可以用杜邦线将A0连接到3.3V、GND或中间电压比如通过一个电位器分压来测试。避坑指南读数跳动这是正常现象模拟信号总有噪声。可以通过软件滤波来平滑比如连续读取10次取平均值。Adafruit库的value属性本身可能已经包含了一些内部滤波。电压值不对首先检查ads.gain的设置是否与你的输入电压匹配。如果你输入5V但量程设置为±4.096V读数会溢出接近最大值。确保输入电压不超过VCC 0.3V。多个通道读取只需创建多个AnalogIn对象即可例如chan1 AnalogIn(ads, ADS.P0)chan2 AnalogIn(ads, ADS.P1)。5. 系统集成读取电压并实时显示单独控制LCD和ADC已经完成现在我们把它们结合起来做一个简单的电压表在LCD上实时显示A0通道的电压值。5.1 整合代码与逻辑设计思路很简单在一个主循环里每秒读取一次ADC电压然后将电压值格式化成字符串发送到LCD显示。需要注意LCD显示刷新和字符串格式化。# voltage_meter.py import time import board import busio import adafruit_ads1x15.ads1115 as ADS from adafruit_ads1x15.analog_in import AnalogIn from RPLCD.i2c import CharLCD # --- 初始化 I2C LCD --- lcd CharLCD(i2c_expanderPCF8574, address0x27, port1, cols16, rows2) lcd.clear() lcd.write_string(Voltage Meter) time.sleep(2) lcd.clear() # --- 初始化 ADS1115 ADC --- i2c_bus busio.I2C(board.SCL, board.SDA) # 注意这里复用I2C总线对象 ads ADS.ADS1115(i2c_bus, address0x48) ads.gain 1 # ±4.096V chan AnalogIn(ads, ADS.P0) print(Starting Voltage Display...) try: while True: # 1. 读取电压 voltage chan.voltage # 2. 格式化显示字符串 # 保留3位小数占8个字符宽度例如“ 3.287V” voltage_str f{voltage:6.3f}V # 第一行显示提示 display_line1 ADC A0 Voltage: # 第二行居中显示电压值 display_line2 voltage_str.center(16) # 3. 更新LCD显示 lcd.cursor_pos (0, 0) lcd.write_string(display_line1) lcd.cursor_pos (1, 0) lcd.write_string(display_line2) # 4. 终端也打印方便调试 print(fVoltage: {voltage_str}) # 5. 等待1秒 time.sleep(1) except KeyboardInterrupt: lcd.clear() lcd.write_string(Bye!) time.sleep(1) lcd.clear() print(\nProgram exited cleanly.)5.2 运行效果与优化思考运行这个脚本LCD第一行会固定显示“ADC A0 Voltage:”第二行则会动态更新如“ 3.287V”这样的电压读数。一个简易的树莓派电压表就做好了。进阶优化方向多通道巡检可以扩展循环依次读取A0、A1、A2、A3四个通道的电压并在LCD上轮播显示或分两行显示两个通道。软件去抖滤波在读取chan.voltage后可以加入一个简单的移动平均滤波减少显示数值的跳动。# 简单的移动平均滤波示例 readings [] def read_filtered_voltage(channel, num_samples10): total 0 for _ in range(num_samples): total channel.voltage time.sleep(0.001) # 微小延迟 return total / num_samples阈值报警可以设置一个电压阈值比如低于3.0V当电压低于此值时让LCD背光闪烁或显示警告信息。Web界面结合Flask等Web框架将实时电压数据发布到网页上实现远程监控。6. I2C项目中的常见问题与深度排查即使按照教程操作你也可能会遇到一些奇怪的问题。下面是一个完整的排查思路帮你定位I2C通信的故障点。6.1 设备未找到i2cdetect无显示这是最典型的问题。sudo i2cdetect -y 1命令后对应的地址位置是“--”。排查步骤物理连接断电重新检查所有接线。确保SDA、SCL、GND、VCC四根线都牢固连接且没有接错。特别注意树莓派GPIO的3.3V引脚输出电流有限约50mA如果同时驱动多个模块可能导致供电不足。尝试单独给一个模块供电测试。地址冲突确保两个设备的I2C地址不同。如果地址相同i2cdetect可能只显示一个或者行为不可预测。上拉电阻I2C总线需要上拉电阻通常4.7kΩ将SDA和SCL线拉到高电平。树莓派内部已有约1.8kΩ的上拉电阻对于短距离、少量设备通常足够。但如果连接线较长或设备较多内部上拉可能不够会导致信号质量差。此时需要在外部SDA和SCL线上各加一个4.7kΩ电阻上拉到3.3V。模块损坏尝试单独连接其中一个模块到树莓派排除模块本身故障。6.2 设备地址识别但库无法驱动i2cdetect能看到设备例如0x27和0x48但Python脚本报错或没反应。排查步骤权限问题运行Python脚本时是否使用了sudo直接操作I2C设备需要root权限。或者将用户加入i2c用户组sudo usermod -aG i2c $USER然后注销重新登录生效。库版本或初始化参数错误确认安装的库名正确。pip3 list查看是否安装了adafruit-circuitpython-ads1x15和RPLCD。仔细核对初始化代码中的地址0x27vs0x3F、端口port1、背板芯片类型‘PCF8574’是否正确。一个字符都不能错。总线冲突确保代码中没有创建多个冲突的I2C总线对象。在集成代码中我们使用了busio.I2C()创建了一个总线对象然后分别传递给ADS1115和LCD库RPLCD自己内部管理。如果两个库都试图独立创建总线对象可能会冲突。最佳实践是创建一个共享的I2C总线对象。6.3 数据读取不稳定或错误LCD显示乱码或ADC读数明显不准、跳动极大。排查步骤电源噪声这是ADC读数跳动的首要原因。确保使用稳定的电源为树莓派和模块供电。尝试用高质量的电源适配器并在VCC和GND之间并联一个100uF的电解电容和一个0.1uF的陶瓷电容以滤除电源噪声。信号干扰将I2C信号线SDA/SCL远离电机、继电器、开关电源等强干扰源。如果线较长使用双绞线。参考电压ADS1115的测量精度依赖于其参考电压的稳定性。模块上的VCC就是它的参考电压。因此为ADS1115提供干净、稳定的3.3V电源至关重要。LCD对比度显示乱码或方块首先调节模块上的电位器改变对比度。6.4 深入理解用逻辑分析仪抓取I2C波形如果以上软件排查都无效可以考虑使用硬件工具。一个USB逻辑分析仪如Saleae Logic系列或国产平价版本是极佳的调试助手。将探针连接到SDA和SCL线设置触发条件可以清晰地看到树莓派发出的Start信号、设备地址、读写位、ACK/NACK、寄存器地址、数据等。通过对比波形和I2C协议标准可以精准定位是主机发送的命令不对还是从机没有响应。例如你可以看到树莓派是否正确地发送了LCD或ADC的地址对方是否回复了ACK。这对于驱动不常见的I2C设备或调试底层协议问题几乎是终极手段。经过这次从单个设备驱动到多设备集成再到深度排查的完整流程你应该对树莓派上的I2C应用有了比较扎实的理解。I2C的魅力就在于它的简洁和标准化一旦掌握了地址寻址和寄存器读写的模式你就可以轻松地让树莓派与成百上千种传感器、执行器、屏幕对话。下次当你遇到一个新的I2C设备时翻出它的数据手册找到设备地址和关键寄存器用今天学到的框架去尝试驱动它这个过程本身就是最大的乐趣所在。