基于BLE与Arduino的智能环境监测与灯光控制系统全流程开发
1. 项目概述与核心思路
几年前,我给自己房间做了一个基于蓝牙模块的遥控灯,用起来还行,但总感觉差点意思——外观是拿花盆凑合的,线路裸露,活脱脱一个“科学怪人”的试验品。作为一个对《星球大战》有执念的Maker,我决定彻底重做这个项目,目标很明确:它得是一个集环境监测、灯光控制于一体的智能终端,并且,它必须足够酷,从里到外都得有星战的味道。
于是,这个“基于BLE与Arduino的智能环境监测与灯光控制系统”诞生了。它的核心是一个扮演“服务器”角色的Arduino Nano 33 BLE开发板,持续采集房间的温湿度、甲烷和一氧化碳浓度。这些数据不仅会显示在一块护眼的电子墨水屏上,还会通过蓝牙低功耗(BLE)技术,实时广播给我手机上的专属App。反过来,我也可以通过App远程控制两盏灯的开闭、两个小风扇的转向,甚至调节一个内置RGB LED的氛围光颜色。整个系统的“躯体”,是一块我亲自设计的千年隼号飞船造型的PCB,而它的“盔甲”,则是一个3D打印的、能在黑暗中发光的AT-AT步行机风格外壳。
这个项目不仅仅是把几个传感器和模块堆在一起。它涉及了完整的物联网产品开发流程:从硬件选型、电路设计、PCB绘制、固件编程,到3D建模、外壳打印,再到手机App的图形化开发。我想通过这篇分享,不仅展示最终炫酷的效果,更把其中每一个技术决策背后的“为什么”、实操中踩过的“坑”,以及如何让软硬件优雅协作的思考过程,毫无保留地拆解给你听。无论你是想复刻一个放在桌面,还是汲取其中BLE通信、传感器集成或3D设计的思路用于自己的项目,相信都能找到有价值的参考。
2. 硬件架构深度解析与选型考量
一套稳定可靠的硬件是项目的基石。我的选型原则是:在满足功能需求的前提下,优先考虑集成度、功耗以及开发便利性。下面这张表格梳理了整个系统的硬件构成及每个部分的关键选型理由:
| 组件 | 型号/规格 | 核心作用 | 选型理由与注意事项 |
|---|---|---|---|
| 主控 | Arduino Nano 33 BLE | 系统大脑,负责传感器数据采集、BLE通信、外设控制。 | 原生支持BLE,无需外接模块,简化电路和编程。其基于Arm Cortex-M4的处理器性能足以应对多任务。注意:其IO口逻辑电平为3.3V,驱动5V设备需电平转换。 |
| 环境传感器 | DHT22 | 测量环境温湿度。 | 精度和响应速度优于DHT11,单总线通信,占用IO口少。需注意其采样周期较慢(约2秒一次),读取间隔不宜过短。 |
| 气体传感器 | MQ-4, MQ-7 | 分别检测甲烷(CH₄)和二氧化碳(CO)浓度。 | 成本低,对特定气体敏感度高,模拟电压输出便于Arduino读取。重要:这类传感器需要预热稳定(通常通电24小时后数据才可靠),且输出值为电阻比,受环境温湿度影响,适合做定性或趋势监测,而非精确计量。 |
| 显示模块 | Waveshare 2.9英寸电子墨水屏 | 显示环境数据、BLE连接状态、错误信息。 | 核心优势:超低功耗(仅在刷新时耗电)、无闪烁、强光下可视性极佳,彻底解决了LCD/OLED在夜间刺眼的问题。缺点是刷新率慢,不适合动态画面。 |
| 执行器 | 2路继电器模块 | 控制两盏外接LED灯的开闭。 | 利用小电流(来自Arduino IO口)控制大电流(灯饰电源),实现强弱电隔离,安全可靠。选择常开(NO)触点接法。 |
| 执行器 | L9110S电机驱动模块 x2 | 驱动两个5V小风扇正反转及停止。 | 集成H桥电路,一个模块可独立控制一个直流电机,接线和编程比L298N等更简洁。 |
| 指示与报警 | 5mm共阳RGB LED,有源蜂鸣器 | RGB LED用于状态指示(如连接成功、错误),蜂鸣器用于气体超标报警。 | 共阳RGB LED的阳极接VCC,阴极通过限流电阻接IO口,拉低点亮。有源蜂鸣器给高电平即响,编程简单。 |
| 电平转换 | 双向逻辑电平转换器 x2 | 将Arduino Nano 33 BLE的3.3V信号转换为5V,以可靠驱动继电器和电机驱动模块。 | 这是保证系统稳定的关键。直接连接可能导致5V设备无法被3.3V信号触发,或电流倒灌损坏主控。 |
| 供电 | DC电源插孔,外部电池接口 | 为整个系统供电。 | PCB上预留两种供电方式,灵活性强。需确保电源能提供足够的电流(特别是驱动两个风扇和灯时)。 |
注意:关于气体传感器的使用:MQ系列传感器是广谱型,对多种气体都有反应。本项目中的“甲烷”、“一氧化碳”读数更应理解为“对这类气体敏感度的相对指示值”。在实际安全监控应用中,务必使用经过标定的专业传感器,并定期校准。这里的实现主要用于演示和趋势观察。
2.1 为什么选择BLE而非经典蓝牙?
这是项目的通信基石。经典蓝牙(如HC-06模块使用的)功耗高,连接过程复杂(配对),且通常用于持续数据流(如音频)。而蓝牙低功耗(BLE)是为此而生:
- 功耗极低:设备大部分时间处于睡眠模式,仅在广播或连接的瞬间唤醒,非常适合由电池供电或需要长期待机的环境监测设备。
- 连接快速:无需繁琐配对,设备广播自身信息,中心设备(如手机)扫描即可连接,用户体验更接近物联网设备。
- 数据模型清晰:基于GATT(通用属性配置文件),数据被组织为“服务”和“特性”,概念清晰,编程规范。一个“温度读数”就是一个可读的“特性”,一个“灯开关命令”就是一个可写的“特性”。
Arduino Nano 33 BLE内置了BLE支持,通过ArduinoBLE库可以非常方便地将自己配置为一个“外围设备”(Peripheral),向外广播服务和数据。
2.2 电子墨水屏:不止于“护眼”的选择
放弃LCD选择电子墨水屏,最初确实是为了解决夜间观看的视觉疲劳问题。但在深入使用后,我发现它带来了更多好处:
- 零待机功耗:一旦画面刷新完成,即使断电,图像也能持续显示。这意味着显示数据几乎不消耗系统能量。
- 可视角度极佳:接近180度,从任何角度看都清晰,适合作为桌面摆件。
- 独特的“科技感”:其黑白分明的显示效果,与《星球大战》那种略带复古未来主义的风格意外地契合。
当然,缺点也很明显:刷新速度慢(全屏刷新需要2-3秒),且有轻微的残影。这意味着你不能用它来显示快速变化的数据。我的策略是:每分钟刷新一次数据,并且在刷新前先全屏清空(显示白色)再绘制新内容,以减轻残影。
3. 从原理图到千年隼:PCB设计与焊接实战
当所有模块在面包板上测试通过后,为了系统的稳定性和最终颜值,设计一块定制PCB是必经之路。我选择以千年隼号为灵感进行设计,这不仅是情怀,其不规则的形状也能最大化利用PCB面积,并自然形成一种视觉引导。
3.1 设计流程与要点
我使用KiCad进行设计,流程如下:
- 原理图绘制:根据之前的接线图,在KiCad的Eeschema中绘制原理图。为每个元件(包括排针座)创建符号并正确连接。关键一步:为所有需要从PCB引出的接口(如传感器、电机、电源)设计排针座,而不是将元件直接焊死,这样便于调试和更换。
- 元件封装分配:确保每个原理图符号都关联了正确的物理封装(Footprint)。例如,Arduino Nano 33 BLE的排针间距是2.54mm,需要对应的封装。
- PCB布局:在Pcbnew中进行。这是最具艺术性的环节。
- 边框绘制:我导入了千年隼的轮廓图,在“Edge.Cuts”层手动描边,定义了PCB的异形外形。
- 元件摆放:遵循“信号流”原则。电源接口放在角落,主控放中心,传感器接口靠近板边便于接线,功率部分(电机驱动、继电器)与模拟信号部分(传感器)适当远离以减少干扰。
- 布线:先布电源线和地线,确保电源路径足够宽(我用了1mm以上)。信号线使用0.3mm~0.4mm宽度。对于电机这类可能引入噪声的线路,我让其路径尽量短,并远离模拟信号线。
- 设计检查与Gerber输出:使用DRC(设计规则检查)功能排查短路、间距过小等问题。确认无误后,导出Gerber文件集,这是PCB工厂的生产图纸。
实操心得:与制造商沟通:我选择了灰色阻焊漆来模仿千年隼的船体颜色。在提交Gerber文件前,一定要仔细阅读PCB打样厂家的工艺要求,比如最小线宽/线距、孔径、阻焊桥等,确保你的设计符合其生产能力。
3.2 焊接与组装注意事项
收到PCB后,焊接是让设计变成实物的关键一步。
- 焊接顺序:建议“先低后高,先内后外”。先焊接贴片电阻、电平转换芯片等矮小元件,再焊接排针座、电源插座,最后安装高大的模块(如气体传感器)。这样电烙铁不会被遮挡。
- 温度与手法:使用可调温烙铁,温度设置在320°C-350°C之间。对于多引脚排针,可以先固定对角两个引脚确保位置正确,再焊接其余引脚。助焊剂能极大提升焊接质量,特别是对于GND这类大焊盘。
- 逻辑电平转换器的连接:这是最容易出错的地方。转换器有高压侧(HV)和低压侧(LV)。务必确认:HV侧(5V)连接继电器、电机驱动等5V外设的信号线及5V电源;LV侧(3.3V)连接Arduino的IO口及3.3V电源。方向接反可能导致电平转换失效甚至损坏设备。
- 上电前检查:焊接完成后,务必用万用表蜂鸣档进行以下检查:
- 电源短路:测量5V与GND、3.3V与GND之间是否短路。
- 关键信号线:检查Arduino的A0-A6、D2-D13等关键IO口到对应传感器、模块的线路是否连通,且没有与其他网络短路。
完成焊接并插入所有模块后,一个功能完整的“千年隼号”核心板就准备好了。接下来,就是为它打造一个炫酷的机身。
4. 赋予灵魂:3D建模与打印实战
硬件电路是项目的“内脏”,而3D打印的外壳则是它的“骨骼”和“皮肤”。我设计了一个AT-AT步行机风格的支架结构,分为主体、灯罩和插头三部分,并使用夜光PLA材料,让它在黑暗中能幽幽发光。
4.1 建模思路与结构设计
使用Autodesk Fusion 360进行建模,我的核心设计原则是“功能导向”和“易于组装”。
- 主体结构:设计了一个中空的方柱体作为主壳体,内部预留了精确匹配千年隼PCB形状和大小的卡槽。PCB可以像插卡一样从侧面滑入,并通过壳体上的螺丝孔位固定。这种设计避免了使用胶水,方便后期维护升级。
- 散热与走线:在壳体靠近风扇和继电器的位置开了通风栅格。内部设计了线缆通道,让杜邦线可以整齐排布,避免杂乱。
- 模块化设计:灯罩和插头部分通过卡扣或螺丝与主体连接。插头顶部预留了尤达大师半身像的底座。所有连接处都设计了加强筋,确保整体牢固。
- 星战元素融入:不仅在插头表面浮雕了帝国标志、反抗军标志等图标,整个AT-AT的机械结构风格也与星战宇宙观一致。
4.2 切片与打印参数详解
将STL文件导入Ultimaker Cura进行切片。由于我的Creality CR-200B构建体积是200x200x200mm,一些大部件需要拆分打印。
- 材料:夜光PLA。这种材料在吸收光线后能在黑暗中发光,但比普通PLA更脆,打印时回缩要设置得稍高一些,以减少拉丝。
- 层高:0.2mm。在细节(如浮雕文字)和打印时间之间取得平衡。
- 填充密度:20%。对于这种装饰性为主的结构,20%的填充提供了足够的强度,又节省材料和时间。
- 支撑:对于悬空部分(如AT-AT的“腿部”下方),必须生成支撑。我选择“树状支撑”,它接触面积小,更容易拆除且更省材料。
- 打印速度:50 mm/s。对于FDM打印机,这是一个可靠的通用速度,能保证不错的打印质量。
- 床温/喷嘴温度:PLA通常床温60°C,喷嘴205-215°C。但夜光PLA可能需要稍高的喷嘴温度(如215-220°C)以确保流动性。
踩坑记录:打印失败排查:第一次打印主体时,在某个高度发生了层移。检查发现,是Y轴皮带有些松动。在调整皮带张力并重新校准打印床后,问题解决。经验:定期维护打印机(检查皮带、导轨润滑、清理喷嘴)是成功打印的前提。
打印完成后,小心去除支撑,用砂纸打磨掉毛刺和接缝痕迹。然后,将风扇、灯座等部件用热熔胶固定在壳体内部预留的位置上。最后,将组装好的PCB插入主体卡槽,连接好所有线缆,盖上盖板。当接通电源,电子墨水屏亮起,RGB LED发出预设颜色的光时,这个项目的硬件部分就完整地“活”了过来。
5. 固件编程:Arduino端的逻辑与通信实现
硬件准备就绪后,我们需要赋予它“智能”。Arduino端的固件是整个系统的中枢神经,负责数据采集、处理、显示以及最重要的——通过BLE与手机App通信。
5.1 核心库与初始化
首先,在Arduino IDE中安装必要的库:
ArduinoBLE:用于BLE通信的核心库。GxEPD及相关依赖:用于驱动Waveshare电子墨水屏。DHT sensor library:用于读取DHT22传感器数据。
初始化部分的关键是建立BLE服务架构。在BLE中,一个设备可以提供多个“服务”,每个服务包含多个“特性”。特性才是实际承载数据(如温度值)或接收命令(如开关灯)的单元。
#include <ArduinoBLE.h> #include <GxEPD.h> #include <GxIO/GxIO_SPI/GxIO_SPI.h> #include "DHT.h" // 1. 定义BLE服务UUID(需唯一,可使用在线生成器) BLEService environmentService("19B10000-E8F2-537E-4F6C-D104768A1214"); // 2. 定义数据特性 // 用于广播的传感器数据(只读、带通知) BLEFloatCharacteristic tempChar("19B10001-E8F2-537E-4F6C-D104768A1214", BLERead | BLENotify); BLEFloatCharacteristic humidityChar("19B10002-E8F2-537E-4F6C-D104768A1214", BLERead | BLENotify); BLEFloatCharacteristic mq4Char("19B10003-E8F2-537E-4F6C-D104768A1214", BLERead | BLENotify); BLEFloatCharacteristic mq7Char("19B10004-E8F2-537E-4F6C-D104768A1214", BLERead | BLENotify); // 用于接收命令的控制特性(可读、可写) BLEByteCharacteristic lamp1Char("19B10005-E8F2-537E-4F6C-D104768A1214", BLERead | BLEWrite); BLEByteCharacteristic lamp2Char("19B10006-E8F2-537E-4F6C-D104768A1214", BLERead | BLEWrite); BLEByteCharacteristic fanLChar("19B10007-E8F2-537E-4F6C-D104768A1214", BLERead | BLEWrite); BLEByteCharacteristic fanRChar("19B10008-E8F2-537E-4F6C-D104768A1214", BLERead | BLEWrite); BLEByteCharacteristic rgbChar("19B10009-E8F2-537E-4F6C-D104768A1214", BLERead | BLEWrite); void setup() { Serial.begin(9600); // 初始化传感器、屏幕、引脚模式... // 3. 初始化BLE if (!BLE.begin()) { Serial.println("BLE初始化失败!"); showErrorOnDisplay(); // 在墨水屏显示错误 while (1); } // 4. 设置设备名称和广播的服务 BLE.setLocalName("BLE Remote Lamp"); BLE.setAdvertisedService(environmentService); // 5. 将特性添加到服务中 environmentService.addCharacteristic(tempChar); // ... 添加其他所有特性 // 6. 将服务添加到BLE设备 BLE.addService(environmentService); // 7. 为可写特性设置事件处理器(当手机App写入命令时触发) lamp1Char.setEventHandler(BLEWritten, handleCommand); // ... 为其他可写特性设置 // 8. 开始广播 BLE.advertise(); Serial.println("BLE设备已激活,等待连接..."); }5.2 主循环逻辑与数据流
在loop()函数中,程序需要高效地处理多任务:维护BLE连接、定时采集数据、更新显示、响应命令。
unsigned long lastUpdateTime = 0; const long updateInterval = 60000; // 数据更新间隔:60秒 void loop() { // 1. 持续监听BLE事件(连接、断开、数据写入) BLE.poll(); // 2. 定时任务:每分钟采集并更新一次数据 if (millis() - lastUpdateTime >= updateInterval) { readSensorData(); // 从DHT22和MQ传感器读取数据 updateDisplay(); // 刷新电子墨水屏 updateBLECharacteristics(); // 更新BLE特性值(触发通知) lastUpdateTime = millis(); } // 其他后台任务... }updateBLECharacteristics()函数是数据上传的关键。它不仅仅是将新值写入特性,更重要的是,对于设置了BLENotify权限的特性,当值改变时,外围设备可以主动向已连接的中心设备(手机)发送通知,而无需手机反复查询。这是一种高效的、由服务器推动的数据更新模式。
void updateBLECharacteristics() { tempChar.writeValue(currentTemperature); humidityChar.writeValue(currentHumidity); mq4Char.writeValue(mq4Value); mq7Char.writeValue(mq7Value); // 写入操作会自动触发通知发送给已连接的客户端 }5.3 命令处理与执行
当手机App通过写入lamp1Char等特性发送命令时,会触发我们之前绑定的handleCommand函数。
void handleCommand(BLEDevice central, BLECharacteristic characteristic) { // 判断是哪个特性被写入了 if (characteristic.uuid() == lamp1Char.uuid()) { byte cmd = lamp1Char.value(); digitalWrite(LAMP1_PIN, cmd == 1 ? HIGH : LOW); // 假设1开灯,0关灯 Serial.print("灯1命令: "); Serial.println(cmd); } else if (characteristic.uuid() == rgbChar.uuid()) { byte colorCode = rgbChar.value(); setRGBColor(colorCode); // 根据编码设置RGB LED颜色 } // ... 处理其他命令 }这种基于特性UUID进行判断的方式,使得一个事件处理函数可以清晰地区分和处理来自不同控制界面的命令。
5.4 电子墨水屏的驱动与优化
驱动墨水屏的核心在于理解其“局部刷新”和“全局刷新”模式。为了获得最好的显示效果并减少残影,我采用以下策略:
- 全屏清空:在每次更新数据前,调用
display.fillScreen(GxEPD_WHITE)和display.update()进行全屏刷新(耗时约2-3秒),以清除旧图像的所有痕迹。 - 局部绘制:清屏后,再调用
drawBitmap,drawRect,print等函数绘制新的边框、图标和文本。 - 最终更新:所有元素绘制完成后,再调用一次
display.update()将缓冲区内容更新到屏幕。 - 图像处理:墨水屏只能显示黑白二值图。需要提前用工具(如Image2Lcd)将PNG/JPG图标转换为单色位图数组,并注意扫描方向和颜色反转的设置,否则显示出来可能是反的。
通过以上固件设计,Arduino端就成为了一个既能主动上报数据,又能即时响应远程命令的智能节点。
6. 手机控制端:MIT App Inventor开发全指南
为了让用户能方便地交互,我开发了一个Android应用“BLE Remote Lamp”。考虑到快速开发和原型验证,我选择了MIT App Inventor这款图形化编程工具。它让不熟悉Java/Kotlin的开发者也能通过积木块拼接的方式创建功能完整的App。
6.1 项目创建与BLE扩展导入
- 新建项目:访问MIT App Inventor官网,创建一个新项目,命名为“BLE_Remote_Lamp”。
- 导入BLE扩展:MIT App Inventor默认不支持BLE。需要从扩展库中导入
BluetoothLE组件。这是整个App能与Arduino通信的基础。 - 界面设计:使用设计器拖拽组件。主要界面元素包括:
ListPicker:用于显示扫描到的BLE设备列表。Label:多个,用于显示设备状态、传感器数据(温度、湿度、气体值)。Button:扫描、停止、连接、断开、发送命令等按钮。ListPicker或Spinner:用于选择风扇方向、RGB颜色等命令。HorizontalArrangement/VerticalArrangement:用于布局,使界面整洁。
6.2 核心逻辑积木块解析
App的核心逻辑集中在“块”编辑器中。关键流程如下:
A. 扫描与连接
- 点击“扫描”按钮:调用
BluetoothLE1.StartScanning。扫描到的设备会触发BluetoothLE1.DeviceFound事件,我们将设备名称添加到ListPicker的列表中。 - 用户选择设备后:在
ListPicker.AfterPicking事件中,获取选中设备的地址,调用BluetoothLE1.Connect方法进行连接。 - 连接成功:触发
BluetoothLE1.Connected事件,在这里我们可以开始发现设备服务。
B. 发现服务与特性连接成功后,必须发现设备提供的服务及其包含的特性,才能进行读写。
(伪代码表示逻辑) 当 BluetoothLE1.Connected 事件发生 调用 BluetoothLE1.DiscoverServices 当 BluetoothLE1.ServicesDiscovered 事件发生 对于 BluetoothLE1.Services 中的每一个服务 如果 服务.UUID 等于 “19B10000-E8F2...” (我们的环境服务UUID) 调用 BluetoothLE1.DiscoverCharacteristics,传入该服务UUID 当 BluetoothLE1.CharacteristicsDiscovered 事件发生 对于 返回的特性列表 中的每一个特性 如果 特性.UUID 等于温度特性UUID 将全局变量“温度特性”设为该特性 调用 BluetoothLE1.SetCharacteristicNotification,启用该特性的通知 ...(同理设置湿度、气体等通知特性) 如果 特性.UUID 等于灯1控制特性UUID 将全局变量“灯1控制特性”设为该特性 ...(同理设置其他控制特性)启用通知后,当Arduino端更新了温度值,手机App会自动收到BluetoothLE1.CharacteristicChanged事件,我们可以在其中更新UI显示。
C. 发送控制命令发送命令的本质是向一个具有“写”权限的特性写入值。
(伪代码表示逻辑) 当 “开灯1”按钮被点击 如果 “灯1控制特性”不为空 调用 BluetoothLE1.WriteByteCharacteristicValue 服务UUID: “19B10000...” 特性UUID: “19B10005...” 值: 1 (代表打开)对于风扇控制(停止、左转、右转)和RGB颜色选择,原理相同,只是写入的字节值不同,需要与Arduino端的handleCommand函数约定好编码规则(例如,0=停止,1=左转,2=右转)。
6.3 界面优化与用户体验
- 状态反馈:在连接、发现服务、读写数据等关键步骤,通过
Notifier组件或改变Label的文本颜色,给用户明确的进度提示。 - 数据自动更新:一旦成功订阅了传感器特性的通知,数据就会自动刷新,无需用户手动点击。
- 异常处理:监听
BluetoothLE1.Disconnected事件,在意外断开时清空数据并提示用户。 - 图标与主题:可以导入星战相关的图标和背景,让App与硬件主题保持一致。
完成开发后,可以直接通过AI伴侣在手机上测试,或者打包成APK文件安装。通过这个图形化流程,我们无需编写一行传统代码,就实现了一个功能完备的BLE控制中心。
7. 系统集成、调试与问题排查实录
当硬件、固件、App都分别完成后,将它们集成在一起并稳定运行,是最后也是最考验耐心的一步。这个过程我遇到了不少问题,也总结了一套调试方法。
7.1 上电与基础功能测试
- 供电检查:首先确保电源电压和电流足够。特别是当两个风扇同时启动时,瞬时电流较大。我用万用表测量了5V电源轨的电压,在风扇启动瞬间,电压跌落不能超过0.2V,否则可能导致Arduino复位。
- 串口监控:打开Arduino IDE的串口监视器,设置正确的波特率(如9600)。查看启动日志,确认所有初始化(BLE、传感器、屏幕)是否成功。这是最直接的诊断窗口。
- 传感器读数:在代码中先屏蔽BLE和屏幕部分,仅测试传感器。将DHT22和MQ传感器的原始读数打印到串口,观察数值是否在合理范围内(例如,室内温度通常在15-30°C之间)。用手握住DHT22,看温度是否缓慢上升;向MQ传感器附近呼一口气(含CO2),看读数是否有变化。
7.2 BLE连接与通信调试
这是问题高发区。
- 手机搜不到设备:
- 检查广播:确保Arduino代码中
BLE.advertise()被成功执行。查看串口是否打印了“等待连接”的信息。 - 检查设备名:确认手机蓝牙设置里没有连接过同名设备,有时旧连接会缓存导致冲突。可以尝试修改
BLE.setLocalName中的名字。 - 重启蓝牙:关闭再打开手机的蓝牙功能。
- 检查广播:确保Arduino代码中
- App能连接但收不到数据:
- 检查UUID:这是最常见的问题。务必确保Arduino代码中定义的服务UUID和特性UUID,与App中用于发现和订阅的UUID完全一致,包括大小写和连字符。
- 检查通知是否启用:在App端,连接并发现服务后,必须对每个需要接收数据的特性调用
SetCharacteristicNotification方法,否则不会收到更新。 - 检查Arduino端更新逻辑:确认
updateBLECharacteristics()函数被定期调用,并且writeValue成功执行。
- 发送命令无反应:
- 检查特性权限:在Arduino端,控制命令对应的特性(如
lamp1Char)必须包含BLEWrite权限。 - 检查写入值:在App端,使用
WriteByteCharacteristicValue时,写入的字节值必须与Arduino端handleCommand函数中switch-case判断的值匹配。 - 逻辑电平验证:如果命令发送成功但硬件没反应(如灯不亮),用万用表测量对应Arduino引脚的电平是否随命令变化。如果没有变化,检查代码;如果有变化但外设不工作,检查电平转换器接线和5V供电。
- 检查特性权限:在Arduino端,控制命令对应的特性(如
7.3 电子墨水屏显示异常
- 屏幕全白或全黑:首先检查接线,尤其是
BUSY引脚,必须正确连接。BUSY引脚用于告知主控屏幕是否处于刷新状态,忽略它会导致通信时序混乱。 - 图像错位或反相:这是图像数据数组处理不当。回顾“Image2Lcd”转换时的设置:扫描模式(通常为水平)、颜色反转、左右镜像。不同的屏幕驱动芯片可能需要不同的组合,需要根据实际效果调整并重新生成数组。
- 残影严重:确保在每次完整更新前都进行了一次全屏清空刷新(
display.fillScreen(GxEPD_WHITE)+display.update())。局部刷新虽然快,但只适用于连续变化的文本,对于图标切换,全刷更干净。
7.4 稳定性优化
- 看门狗定时器:在Arduino代码中启用硬件看门狗(
wdt_enable),并在loop()中定期喂狗(wdt_reset)。这样万一程序跑飞,系统会自动复位,而不是死机。 - 传感器读取容错:DHT22读取可能偶尔失败。在
readSensorData()函数中,加入重试机制和超时判断,如果连续几次读取失败,则使用上一次的有效值,并记录错误。 - BLE连接保持:在
loop()中持续调用BLE.poll()是维持连接和处理事件的关键。确保没有长时间的delay()阻塞它。对于需要等待的操作(如屏幕刷新),尽量使用非阻塞的时间判断(millis()差值)。 - 电源去耦:在PCB的Arduino和电机驱动模块的电源入口处,并联一个100uF的电解电容和一个0.1uF的瓷片电容,可以有效滤除因电机启停引起的电源噪声,防止系统复位。
经过以上系统的调试和优化,整个项目从一堆独立的模块,最终融合为一个稳定、可靠、交互流畅的智能环境监测与控制系统。当你在手机上轻轻一点,远处的AT-AT双眼(RGB LED)随之变换颜色,风扇开始转动,环境数据静静地在电子墨水屏上更新时,那种创造和掌控的成就感,正是Maker精神的终极乐趣。
