1. 项目概述与核心思路
用一块只有8个引脚的ATtiny85单片机,驱动一个I2C接口的LCD屏幕,同时读取两个不同类型的传感器数据——这听起来像是个“螺蛳壳里做道场”的挑战。我最初的想法很简单:做一个超小型、低功耗的温湿度显示终端,可以随意放在花盆边、鱼缸旁或者书架上。市面上现成的方案要么体积太大,要么功能冗余,而ATtiny85以其极小的封装和低廉的成本,成了我心中的理想核心。
然而,现实很快就给了我一盆冷水。当我尝试将标准的LiquidCrystal_I2C库与ATtiny85搭配时,编译器抛出了一连串的警告。对于有轻微“代码洁癖”的我来说,满屏的警告就像背景噪音一样让人无法专注,这直接让我把这个项目搁置了几个月。直到后来一个具体的需求出现——我需要一个能同时监测室内空气和鱼缸水温的便携设备——这个想法才被重新拾起。解决问题的关键,在于为ATtiny85这个“小个子”找到合适的“瘦身”库,并精心设计引脚分配与通信时序,让I2C LCD和两个传感器能在有限的资源下和谐共处。
2. 硬件选型与电路设计解析
2.1 核心控制器:ATtiny85的潜力与局限
ATtiny85是Atmel(现Microchip)旗下AVR系列的一款8位微控制器,核心优势在于其极致的迷你化。它仅有8个引脚,其中5个可以作为数字I/O使用(PB0-PB4,PB5通常用作复位)。这意味着我们必须精打细算地使用每一个引脚。其内部资源包括8KB的Flash(用于存储程序)、512B的SRAM(用于运行变量)和512B的EEPROM。对于驱动LCD和读取传感器这类任务,内存是主要的瓶颈,尤其是同时使用多个库时,很容易导致内存溢出,从而引发各种难以调试的怪异问题。
注意:选择ATtiny85意味着你必须放弃一些“奢侈”的调试手段,比如串口打印(Serial)。调试主要依靠LED闪烁或者最终输出到LCD上观察,因此代码需要模块化,分步验证。
2.2 显示单元:I2C LCD模块的简化之道
我选用的是常见的1602(16字符x2行)LCD屏,搭配PCF8574 I2C转接板。这个组合至关重要,它原本需要至少6根线(4位数据模式)才能驱动,但通过PCF8574这个I2C IO扩展芯片,我们仅需2根线(SDA, SCL)就能控制LCD,为引脚紧张的ATtiny85节省了宝贵的资源。I2C通信本身是开源协议,但标准Wire库对于ATtiny85来说有些臃肿。幸运的是,有开发者专门为ATtiny优化了I2C实现,例如TinyWireM库,它体积更小,效率更高,是我们这个项目的基石。
2.3 传感器组合:GY-21P与DS18B20
为了同时监测空气和液体(或另一个位置)的温度,我选择了两个传感器:
- GY-21P模块:这个模块实际上集成了Si7021(或兼容的SHT21)温湿度传感器。它通过I2C接口通信,可以同时获取高精度的温度和相对湿度值。这里有一个至关重要的细节:GY-21P模块虽然输出的是3.3V逻辑电平的信号,但其板载的XC6206稳压芯片要求输入电压(VCC)为5V。如果错误地接入3.3V,模块将无法正常工作。ATtiny85工作在5V下,其I/O引脚可以兼容3.3V输入,因此直接连接是安全的。
- DS18B20温度传感器:这是一款经典的“单总线”数字温度传感器,精度高,抗干扰能力强,且支持长距离布线。它只需要一根数据线(加上电源和地)即可通信。我选择它来测量鱼缸水温,因为它有密封探头型号,完全防水。单总线协议虽然节省引脚,但时序要求严格,需要微控制器精确控制。
2.4 电路连接方案
在有限的5个可用I/O口中(假设使用内部时钟,无需外部晶振占用引脚),分配如下:
- PB2 (SCL): 连接LCD和GY-21P的SCL引脚(I2C时钟线)。
- PB0 (SDA): 连接LCD和GY-21P的SDA引脚(I2C数据线)。注意:I2C总线需要上拉电阻,通常PCF8574板和GY-21P模块上已经集成,如果发现通信不稳定,可以额外在SDA和SCL上添加4.7kΩ上拉电阻至VCC。
- PB1: 连接DS18B20的数据线(DQ)。需要接一个4.7kΩ的上拉电阻至VCC,这是单总线协议的必需条件。
- PB3, PB4: 预留,可用于连接按钮切换显示、控制蜂鸣器或LED状态指示。
电源方面,整个系统可由USB口或一块锂电池(通过AMS1117等稳压模块输出5V)供电。ATtiny85、LCD背光(可通过电阻限流或PWM调光)、GY-21P模块和DS18B20均可工作在5V下。
3. 软件库的适配与核心代码实现
3.1 解决编译警告:寻找合适的LCD库
最初使用标准库的失败经历,促使我去寻找专为ATtiny优化的解决方案。我找到了LiquidCrystal_ATtiny这个库,特别是其_revBC版本。这个库针对ATtiny的硬件特性进行了重写,摒弃了不必要的中断和复杂功能,只保留核心的显示驱动,体积小巧,且与TinyWireMI2C库完美兼容。替换库之后,编译警告一扫而空,代码变得干净清爽。
3.2 分步验证:从基础显示到传感器集成
开发过程必须遵循“分步测试,逐步集成”的原则,这是在小资源MCU上开发的关键。
第一步:LCD基础驱动测试首先,我编写了一个最简单的测试程序TEST_I2C_LCD_ATtiny85。它的功能就是让ATtiny85通过I2C控制LCD,显示一个从0累加到1000的数字。这个测试看似简单,却至关重要。它验证了以下几个环节:
- ATtiny85的编程器连接和程序烧录是否正常。
TinyWireM和LiquidCrystal_ATtiny库的安装与初始化是否正确。- LCD的I2C地址是否正确(通常是0x27或0x3F)。
- 硬件连线是否有误。 当数字在屏幕上顺利滚动时,意味着最基础的通信链路已经打通。
第二步:集成GY-21P温湿度传感器在LCD工作正常后,我引入了GY-21P模块,项目升级为TEST_I2C_Si7021_LCD_ATtiny85_v2。这里的主要挑战是Si7021的驱动库。官方库可能包含浮点运算或较大的缓冲区,不适合ATtiny85。我的做法是“裁剪”与“移植”:
- 只保留最核心的读取温度和湿度的函数。
- 将可能的浮点运算转换为整数运算。例如,Si7021返回的湿度原始值需要经过一个公式换算:
RH = (125 * rawData / 65536) - 6。在AVR上,直接计算125 * rawData可能会溢出,需要谨慎处理数据类型(使用unsigned long)。 - 编写简化的I2C读写函数,直接调用
TinyWireM库。 代码中,先初始化I2C,然后发送Si7021的读取命令(如0xF5读湿度,0xF3读温度),等待测量完成(Si7021需要约10-20ms),最后读取两个字节的数据并进行换算。温度和湿度值被格式化为字符串,显示在LCD的不同行上。
第三步:加入DS18B20单总线传感器最后,在TEST_I2C_DS18B20_Si7021_LCD_ATtiny85_v2版本中,我集成了DS18B20。这里使用了OneWire和DallasTemperature库。虽然这两个库相对成熟,但在ATtiny85上仍需注意:
- 确保使用最新版本,它们通常对AVR架构有更好的支持。
OneWire库对时序要求极高,ATtiny85在8MHz内部时钟下运行,必须保证OneWire的延时函数是精确的。有些库版本可能需要针对8MHz进行调整。- 初始化传感器时,务必执行
ds18b20.begin()和ds18b20.setResolution(12)(设置12位精度)等操作。 由于DS18B20转换温度需要时间(12位精度时最多750ms),在代码中不能连续读取,否则会得到前一次的结果。
3.3 系统调度与低功耗考量:看门狗定时器的妙用
让两个传感器和LCD稳定工作的关键,是合理的任务调度。如果使用delay()函数等待传感器转换,会严重阻塞程序,导致系统无法响应其他潜在任务(如按钮扫描),且功耗较高。
我采用了看门狗定时器中断来构建一个简单的软定时器。ATtiny85的看门狗可以设置为在特定时间(如1秒)后产生中断。在中断服务程序中,我设置一个标志位。在主循环中,不断检查这个标志位。当1秒时间到,标志位被置起,主循环便依次执行:读取GY-21P -> 读取DS18B20 -> 更新LCD显示 -> 清除标志位。
这样做的好处是:
- 非阻塞:在等待的1秒内,主循环理论上可以执行其他任务(虽然本项目中没有)。
- 低功耗:在两次测量间隔,如果没有任何任务,可以让MCU进入空闲模式,看门狗中断会将其唤醒,显著降低整体功耗。
- 稳定定时:看门狗定时器相对独立,提供了比循环计数更精确的时间基准。
核心代码结构如下:
#include <TinyWireM.h> #include <LiquidCrystal_Attiny.h> #include <OneWire.h> #include <DallasTemperature.h> // ... 其他库和定义 volatile bool measureFlag = false; // 测量标志位 // 看门狗中断服务程序 ISR(WDT_vect) { measureFlag = true; } void setup() { // 初始化看门狗定时器为1秒中断模式 // ... 看门狗配置代码 // 初始化I2C、LCD、传感器 lcd.begin(); lcd.print("System Ready"); delay(1000); } void loop() { if (measureFlag) { measureFlag = false; // 1. 读取GY-21P温湿度 readSi7021(); // 2. 读取DS18B20温度 ds18b20.requestTemperatures(); float tempWater = ds18b20.getTempCByIndex(0); // 3. 格式化并显示到LCD lcd.clear(); lcd.setCursor(0,0); lcd.print("Air:"); lcd.print(tempAir); lcd.print("C "); lcd.print(humidity); lcd.print("%"); lcd.setCursor(0,1); lcd.print("Water:"); lcd.print(tempWater); lcd.print("C"); } // 此处可以添加其他非实时任务,如检查按钮 }4. 常见问题排查与调试心得
在将想法变为实物的过程中,我遇到了不少坑。这里把典型问题和解决方案记录下来,希望能帮你节省时间。
4.1 I2C通信失败(LCD或GY-21P无响应)
这是最常见的问题,表现为LCD不亮、背光亮但无字符、或者传感器读数为0。
- 检查接线:这是第一步也是最容易出错的一步。确保VCC(5V)、GND连接牢固。重点检查SDA和SCL线是否接反。对于GY-21P,务必确认是5V供电。
- 确认I2C地址:使用一个Arduino Uno和I2C扫描程序,先单独测试LCD模块和GY-21P模块的地址。LCD常见地址是
0x27或0x3F,GY-21P的地址是0x40。 - 检查上拉电阻:I2C总线必须上拉。用万用表测量SDA和SCL线在空闲时的电压,应接近VCC(5V)。如果电压只有1-2V,说明上拉电阻过大或缺失,需要补上4.7kΩ电阻。
- 库冲突:确保只包含了
TinyWireM,并且LiquidCrystal_Attiny库是为I2C优化的版本。不要混用标准Wire库。
4.2 DS18B20读数为-127或85
这通常是单总线通信失败的标志。
- 检查接线和上拉电阻:DS18B20的数据线(DQ)必须通过一个4.7kΩ电阻上拉到VCC(5V)。这个电阻离传感器越近越好。忘记接这个电阻是导致失败的最主要原因。
- 电源问题:尝试使用外部电源为DS18B20供电(将其VCC引脚接至5V,而不是从ATtiny引脚取电),并在代码中使用
parasite power模式。但更推荐使用外部供电模式,稳定性更高。 - 时序问题:在
setup()中,增加一小段延时(如delay(1000)),给DS18B20足够的启动时间。确保OneWire库的延时函数与8MHz时钟匹配。
4.3 程序运行不稳定或偶尔复位
这通常指向内存不足或电源问题。
- 内存溢出:ATtiny85的RAM只有512字节。使用
String类、大的数组或同时初始化多个库对象很容易导致溢出。务必使用F()宏将常量字符串存放到Flash中,例如lcd.print(F(“Hello”));。在编译完成后,查看IDE的输出信息,关注“全局变量使用了xx字节的RAM”的提示,确保它远小于512。 - 电源噪声:当LCD背光开启或传感器工作时,电流突变可能引起电源电压跌落,导致MCU复位。在ATtiny85的VCC和GND之间并联一个100uF的电解电容和一个0.1uF的陶瓷电容,可以很好地平滑电压。
- 看门狗未正确喂狗:如果你启用了看门狗复位功能(而非仅中断),必须在主循环中定期“喂狗”,否则芯片会不断复位。在我的代码中,只使用了看门狗的中断模式,没有启用复位功能,所以不存在这个问题。
4.4 显示内容乱码或闪烁
- 初始化顺序:确保在
lcd.begin()之后,有足够的延时(delay(50)以上),让LCD模块完成内部初始化。 - 对比度调节:调整LCD模块上的电位器(如果有),直到字符清晰显示。电压不合适会导致乱码。
- 代码逻辑:在更新LCD时,避免过于频繁地调用
lcd.clear()。清屏瞬间屏幕会闪烁,可以尝试只更新需要变化的字符位置,而不是全屏刷新。
5. 项目优化与扩展思路
这个基础框架搭建成功后,你可以根据实际需求进行多种优化和扩展,让它变得更实用、更智能。
5.1 低功耗深度优化
如果你希望用电池供电运行数月,需要进行深度低功耗设计:
- 间歇工作:将测量间隔从1秒延长到30秒甚至几分钟。在两次测量之间,让ATtiny85进入
SLEEP_MODE_PWR_DOWN深度睡眠模式,此时功耗可降至1微安以下。依然使用看门狗定时器作为“闹钟”来唤醒MCU。 - 关闭LCD背光:LCD背光是耗电大户。可以将其连接到一个IO口,通过PWM控制亮度,或者仅在需要查看时点亮几秒钟。
- 传感器电源管理:将GY-21P和DS18B20的VCC引脚通过一个MOSFET连接到电源,由ATtiny85的IO口控制。仅在测量前通电,测量后立即断电,可以节省大量功耗。
5.2 功能扩展
- 多路DS18B20:单总线协议支持在同一数据线上挂载多个DS18B20,每个传感器有唯一的64位ROM地址。你可以用一个引脚同时监测鱼缸、室内、室外的温度。
- 添加用户交互:使用预留的PB3、PB4引脚连接轻触开关。实现功能如:切换显示页面(轮流显示空气温湿度、水温、最高最低值等)、调整背光亮度、进入配置模式等。
- 数据记录:虽然ATtiny85的EEPROM只有512字节,但可以紧凑地存储几百条时间戳-温度-湿度数据。例如,每10分钟存储一次,可以记录几天内的数据。通过一个额外的按钮,可以进入“回放”模式,在LCD上滚动查看历史趋势。
- 无线传输:如果引脚和功耗允许,可以尝试连接一个超低功耗的蓝牙模块(如HM-10)或LoRa模块,将数据无线发送到手机或网关,实现远程监控。
5.3 结构设计与封装
为了真正实现“便携”,一个好的外壳必不可少。可以使用3D打印一个小巧的盒子,将ATtiny85(建议使用SOIC或DIP封装以便焊接)、LCD、传感器接口集成在内。为DS18B20的探头引出一条防水线缆。考虑在盒子上开孔,确保GY-21P能接触到空气,同时LCD屏幕清晰可见。一个内置的锂电池充电管理电路(如TP4056)会让它用起来更加方便。
这个项目最让我有成就感的地方,在于用极其有限的硬件资源,实现了一个完整、稳定且实用的功能。它深刻地体现了嵌入式开发的精髓:在约束条件下进行创造性的设计。每一次成功的编译、每一次传感器的正确读数、每一次信息的清晰显示,都是对硬件特性和软件代码深入理解的直接反馈。当你亲手做出这样一个精致的小设备,并把它应用到实际生活中时,那种乐趣是无可替代的。