
Arduino加蓝牙模块再加一块LCD屏这组合听起来像是个入门玩具但真上手之后你会发现它是理解“数据链路怎么运转”最直观的一课。手机发一条消息蓝牙模块收到后通过串口送给ArduinoArduino再把它转成LCD屏上的字符整个过程不复杂却把串口通信、电平匹配、字符解析、I2C显示这些嵌入式基础点全串起来了。很多人做完这个小东西后面做遥控小车状态屏、环境监测显示端、甚至智能家居控制面板用的都是同一套思路。我自己也踩过不少坑从乱码到收不到数据一步步理顺之后今天把完整方案写出来给打算做这个项目的朋友当参考。Arduino 蓝牙 LCD屏显示接收信息从零到跑通的完整实战这个项目特别适合三类人刚学完Arduino基础、想从点灯进阶到通信项目的玩家做课程设计需要“手机控制/显示”功能的开发者以及单纯想把闲置的蓝牙模块和LCD屏利用起来的手工爱好者。它不需要射频知识也不需要复杂的协议解析只要理解“数据从哪来、中间怎么转、最终去哪显示”三个环节就能稳定运行。接下来我按自己的实现顺序逐步拆解。1. 项目核心拆解这条“信息链路”是怎么转起来的1.1 一条完整的数据流这个项目的本质是一条单向数据链路手机蓝牙发字符串 → 蓝牙模块接收 → 模块通过串口把数据送给Arduino → Arduino对字符串做处理和判断 → 最后通过I2C总线写入LCD屏幕显示出来。人话版就是手机相当于一个发件人蓝牙模块是邮局分拣员它只负责把收到的无线信号变成串口TTL电平信号Arduino是收件人和翻译它读取串口来的字节按字符拼成一句完整的话LCD屏是黑板Arduino把翻译完的内容写上去。要注意的是蓝牙模块和Arduino之间的通信本质上就是串口通信。它和电脑通过USB连接Arduino用的那套串口协议没有任何区别。所以搞懂了UART收发就搞懂了这个项目的一半。我见过很多人一上来就问“蓝牙模块要不要装驱动”“需不需要WiFi配网”这类问题其实根源在于没把蓝牙模块当成一根“无线串口线”来看——它就是一根看不见的杜邦线只是传输介质从铜线变成了2.4GHz无线电波。1.2 为什么必须用软串口SoftwareSerial这是这个项目里最容易让新手“卡死”的地方。Arduino的Uno和Nano板子上硬件串口只有一组对应D0RX和D1TX两个引脚。这组串口被USB转串口芯片占用也就是说你插着USB线烧录代码、打开串口监视器时用的就是D0/D1。如果蓝牙模块也接到D0/D1上会出什么问题两件事共用同一个口乱套。烧录程序时蓝牙还在错误地收发数据打开串口监视器时蓝牙数据会直接经过电脑串口干扰调试信息。所以正确做法是蓝牙模块别碰D0/D1用软件模拟出来的串口放到D2、D3这些普通数字引脚上。Arduino的SoftwareSerial库就是干这个的。它用代码模拟串口时序普通数字引脚就能吞吐9600波特率对于传字符串显示到LCD这种应用场景性能完全够用。我实测在9600波特率下连续发送一百多字符也基本不丢数据。当然它也有局限比如同时只能监听一个软串口、接收缓冲区只有64字节但对于本项目来说足够宽裕了。核心接线逻辑就是蓝牙模块的TX接到Arduino的软串口RX引脚D2蓝牙模块的RX接到Arduino的软串口TX引脚D3。注意是交叉连接发送接接收接收接发送跟两个串口设备互连的规则一样。1.3 蓝牙选型HC-05还是HC-06很多人第一次买蓝牙模块都纠结过这个问题。市面上最常见的两种经典蓝牙模块是HC-05和HC-06长相几乎一样但性格不同。HC-05支持主从一体可以配置成主机主动去连接别的从机设备也能作为从机被手机连接还能通过AT指令改名称、改配对码、改波特率。HC-06则是纯从机它的任务就是乖乖等着手机来连不能主动连接别人。对本项目而言我们只需要手机主动搜索、连接、发送消息所以两者都可以选。我的建议是看后续玩法。如果你以后打算把Arduino和另一个蓝牙模块做双机通信比如一个做遥控端、一个做接收端那就直接上HC-05配置灵活如果只是手机单向发消息给LCD显示HC-06物美价廉还省去主从配置的精力。无论选哪个默认通信波特率通常都是9600只是HC-05在AT指令模式下可能使用38400不同厂商固件有差异购买时让卖家确认清楚。2. 硬件准备与接线把每一根线都讲明白2.1 物料清单这个项目的物料非常便宜也非常常见基本都是从入门套件里就能翻出来的东西硬件推荐型号作用主控板Arduino Uno或Nano项目主脑读取串口数据并驱动LCD蓝牙模块HC-05或HC-06接收手机蓝牙信号转为串口TTL数据显示屏LCD1602 I2C转接板2行16字符液晶显示模块烧录I2C固件的转接板可以省掉疯狂接线I2C转接板PCF8574将I2C信号转成LCD所需的并行信号面包板与杜邦线若干连接电路方便手工调整这里每个硬件都值得聊两句。LCD1602是经典款显示两行16个字符对显示一条短信、一组数据绰绰有余。I2C转接板是关键它在背后默默替我们完成了LCD需要的那堆并行信号控制让原本要接6根以上引脚的LCD屏简化为4根线的I2C总线设备。最初我做这个项目时用的是不带I2C的裸屏先不说接线数量光是调对比度那个电位器就够折腾的了后来换I2C转接板后体验直线上升。强烈建议直接买带I2C转接板的版本省事也更容易成功。2.2 完整接线表接线时按照下面的表格来就不会乱模块引脚接Arduino引脚说明蓝牙 VCC5V蓝牙模块供电蓝牙 GNDGND共地必须连接蓝牙 TXD2模块发送数据到Arduino软串口RX蓝牙 RXD3经分压如果模块是3.3V逻辑此处必须分压LCD VCC5V背光及IC供电LCD GNDGND共地LCD SDAA4I2C数据线LCD SCLA5I2C时钟线注意Arduino Uno和Nano的A4、A5是硬件I2C引脚接LCD的SDA、SCL时需要确认自己用的主控板。如果是其他型号要查一下对应的I2C引脚定义。另外所有模块的GND必须和Arduino的GND连在一起这就是常说的“共地”。不共地串口信号没有统一参考电平数据大概率乱码或者直接收不到。这一点新手经常忽略结果排查半天都找不到问题。2.3 电平转换HC-05的RX引脚为什么不能直连这个坑我印象太深了。HC-05的电气特性是3.3V逻辑它的RXD引脚并不耐5V。而Arduino的D3引脚输出的是5V TTL电平信号如果直接接HC-05的RX长时间运行轻则接收异常重则烧毁模块的接收电路。HC-06的多数模块板载了电平兼容电路能直连5V但HC-05不行。解决方案是给RX线路做个简单的分压在Arduino的D3引脚和HC-05的RXD之间串联一个1k电阻然后从HC-05的RXD这端再接一个2k电阻到GND两个电阻的交接点电压刚好被分到3.3V左右。原理很简单5V通过1k电阻后再经2k电阻到地形成分压交接点电压 5V x 2kΩ / (1kΩ 2kΩ) ≈ 3.3V。这样逻辑电平从5V降到3.3V既满足HC-05识别高电平的门槛又不会过压。我在实际项目里为了省事直接用了模块上自带的电平转换电路有些商家做的模块板载了RX分压电阻这时候就无需外置分压。但为了稳妥还是建议自己按上面说的方法做成本只有两个电阻换来的是安全。3. 软件准备与代码实现把数据“接住”再“上屏”3.1 开发环境与库的安装软件环境其实很简单Arduino IDE是主要工具版本新一点就行。库方面需要两个SoftwareSerial这是内置库不需要额外安装和LiquidCrystal_I2C驱动I2C转接板并控制LCD。打开IDE的库管理器菜单栏点“工具 → 管理库”搜索LiquidCrystal_I2C选择由知名作者发布的版本安装即可我安装的是稳定版直接用没问题。顺便提一句LiquidCrystal_I2C这个库基本上成了I2C LCD的推荐标准但不同作者发布的库云端地址可能不同个别库可能存在初始化参数不一致的问题。如果发现显示屏有现象但是显示不正常优先确认库是否正确安装再看代码里的构造函数参数和转接板上的实际I2C地址是否吻合。3.2 完整代码及讲解直接上代码我用的版本是目前验证过稳定运转的#include Wire.h #include LiquidCrystal_I2C.h #include SoftwareSerial.h // 创建软串口对象RX为D2TX为D3 SoftwareSerial btSerial(2, 3); // 创建LCD对象0x27是常见的I2C设备地址如果你扫描出来是别的地址就要改 LiquidCrystal_I2C lcd(0x27, 16, 2); // 字符接收缓冲区 char receivedBuffer[64]; int bufferIndex 0; void setup() { // 初始化LCD打开背光打印启动信息 lcd.init(); lcd.backlight(); lcd.setCursor(0, 0); lcd.print(Ready...); // 初始化软串口波特率9600和蓝牙模块保持一致 btSerial.begin(9600); // 硬件串口也初始化方便调试打印日志 Serial.begin(9600); Serial.println(Bluetooth LCD started); } void loop() { // 持续监听软串口读取每一个到达的字节 if (btSerial.available()) { char c btSerial.read(); // 遇到行结束符认为一条完整的消息已经到达 if (c \n || c \r) { if (bufferIndex 0) { receivedBuffer[bufferIndex] \0; // 加上字符串结束符 displayMessage(receivedBuffer); // 调用显示函数 bufferIndex 0; // 清空缓冲准备接收下一条 } } else { // 防止缓冲区溢出最大保留63个字符 if (bufferIndex sizeof(receivedBuffer) - 1) { receivedBuffer[bufferIndex] c; } } } } // 将消息显示到LCD上分两行显示每行16字符 void displayMessage(char* msg) { lcd.clear(); lcd.setCursor(0, 0); // 先把消息按长度拆成两行 String text String(msg); String firstLine text.substring(0, 16); // 第一行最多16个字符 String secondLine ; if (text.length() 16) { secondLine text.substring(16, 32); // 第二行最多16个字符 } lcd.print(firstLine); if (secondLine.length() 0) { lcd.setCursor(0, 1); lcd.print(secondLine); } }这个代码的核心逻辑在loop函数里。btSerial.available()检查软串口缓冲区是否有字节到达如果有就逐个读取。我规定一条消息的结束标志是换行符或回车符这是绝大多数串口助手软件发送字符串时的默认行为一般都会自动附加。接收部分用了一个字符数组而不是String原因后面会说。收到完整一行后调用displayMessage函数先清屏再把数据拆成两行第一行最多16字符第二行最多16字符正好适配LCD1602的两行显示范围。如果你希望立刻看到效果烧录的时候注意烧录完成后LCD上应该显示“Ready...”。然后打开手机上的蓝牙串口软件连接模块发送一条测试消息看看屏幕是不是出现了对应的字符串。3.3 动手前必须搞懂的三个设计选择第一为什么用字符数组而不用String。String是Arduino封装好的类写代码确实快几行就能拼好一个字符串。但它在底层大量使用动态内存分配长时间运行会导致内存碎片最终程序可能莫名其妙崩溃。字符数组是固定内存虽然写起来多几行但稳定、可控符合嵌入式开发习惯。尤其当你以后在这个项目上加传感器、加状态机时稳定性的差异会越来越明显。第二为什么要有结束符判断。如果你只是傻傻地接收每个字符然后立刻显示会发现LCD上会随机蹦出半截字符或者一条消息被切成好几屏。原因很简单串口是流式的数据是一字节一字节送过来的没有“我发完一句了”的天然标记。所以发送端要在消息末尾加\n接收端判断到这个字符才把前面的字符当成一个完整句子处理。这是串口通信的通用套路我现在写任何串口程序都会在协议里带上帧头和换行标志。第三软串口引脚为什么选D2和D3。SoftwareSerial库建议使用支持引脚电平变化中断的引脚来作为软串口RX引脚。在Uno/Nano上D2、D3是支持外部中断的引脚最适合做软件串口接收。如果你把RX放在不支持中断的引脚上接收会极其不稳定丢数据是常事。另外D2、D3默认没有其他功能冲突非常清净。3.4 手机端配对与数据发送手机端需要装一款“蓝牙串口助手”类的App市面上的选择很多原理都一样扫描附近经典蓝牙设备连接之后进入一个类似串口调试面板的界面底部输入框可以发字符串通常还带发送换行选项。我第一次测试时犯了个低级错误手机蓝牙配对时要输PIN码经典蓝牙模组的默认PIN一般是“1234”或“0000”我连续试了三次都没连上后来才想起要把手机蓝牙设置页里旧设备的配对信息删掉重连。如果你遇到配不上模块大概率是之前配对过一次、但模块重置导致认证信息不一致删掉重新配对或者按住模块按键重新复位就好。配对成功后在串口助手界面发送“Hello Arduino”或者中文有些App支持发送UTF-8格式LCD显示中文会乱码所以尽量发英文字符即可看到LCD上刷新出头一行内容。如果来不及看LCD会一直保留最后一条消息。4. 实操过程与核心环节完整跑通一遍顺便记录每个坑4.1 上电顺序与第一次连接这个项目有一个很容易被忽略的细节上电顺序。我一般推荐先给Arduino供上电等LCD初始化和串口准备好再打开手机蓝牙连接模块。因为蓝牙模块上电会有一个初始化和广播过程如果手机在模块自动进入AT模式的时候尝试连接可能出现配好但连不上的假死状态。对于HC-05来说还有一个特殊状态如果按住模块上的按键再上电模块会进入AT指令模式此时它不再响应普通配对连接。如果你之前测试时不小心把它弄到了AT模式手机就搜不到或连不上。解决办法就是拔掉电源重新按正常方式上电。另外HC-05的AT模式下默认波特率可能是38400但普通连接模式是9600两者不同调试时要区分清楚。我第一次验证项目时用的调试链路是先不接手机直接把HC-05模块通过USB转TTL模块接到电脑用电脑串口助手发AT指令确认模块工作正常。这样可以先把蓝牙模块的问题和Arduino程序问题隔离开来极大的缩小排查范围。4.2 看着数据一步步变成屏幕上的字符现在来一次完整的实操视角。假设我发送的消息是“Hello LCD Project”。字符串在手机App文本框里输入后点击发送。如果App配置了发送时附加换行实际通过蓝牙空中发出的是“Hello LCD Project\r\n”这一串字节。HC-05接收到无线数据后在它的串口输出口TX上按9600的波特率把这串字节轮流通过电平变化送出去每个字符对应8个数据位。Arduino的D2引脚检测到电平变化SoftwareSerial库在后台开始按9600的波特率把一个个位组合回字节然后把它放入自己的接收缓冲区。loop函数每循环一次就去看缓冲区里有没有新字节有就取出来判断。当它依次读到H、e、l、l、o每个字符时都会存入receivedBuffer数组。直到读到\r回车或\n换行Arduino知道一条消息结束了就把之前存的字符串交给displayMessage函数。函数计算字符串长度长于16个字符的部分截出来放到第二行显示。这个过程中LCD屏先执行lcd.clear()屏幕上的旧内容被抹掉然后光标设置到(0,0)位置逐字打印第一行再根据剩余内容在第二行继续打印。如果消息是中文情况就会复杂得多因为中文在UTF-8编码下通常是3个字节一个汉字而且LCD1602的字符库不直接支持汉字需要字库或字模所以这个项目里建议以英文和数字测试为主中文会显示成乱码或者空的方块。4.3 两个我亲测有效的显示花样扩展基础功能跑通之后加一些小改动就能让项目实用性翻倍。第一个是滚动显示长消息。当消息超过32个字符时超过两行容量我的代码会截断后面的内容只显示前32个字符这不完美。可以做滚动显示每隔几百毫秒把显示窗口向后挪一个字符让超长消息在屏幕上像跑马灯一样滚动。实现起来也不难设置一个滚动偏移变量在显示函数里用substring(offset, offset16)实时切片再通过millis()定时刷新即可。第二个是给消息加上前缀标志。比如手机发送“M:Hello”代码里判断前缀M就知道这是一条需要立即显示的消息发送“C”就知道要清屏。协议化之后再接DHT11温湿度传感器可以把“T:25.5”这种带前缀的指令解析出来在屏幕上显示温度和湿度读数。我有段时间就把它改成了宿舍环境监控屏蓝牙模块改成WiFi模块数据源从手机变成远端服务器迁移起来非常顺。5. 常见问题与排查技巧实录踩过的坑一次说清楚5.1 问题速查表症状可能原因解决办法LCD只亮背光显示两行方块I2C地址错误运行I2C地址扫描代码确认设备真实地址后修改构造参数0x27LCD有字符但全是乱码波特率不匹配 / 软件串口干扰 / 接线太长统一波特率到9600缩短杜邦线检查共地手机搜不到蓝牙模块模块处于AT模式 / 模块停止广播重新上电退出AT模式确认模块供电正常手机能搜到但连接失败配对PIN码不对或旧配对信息残留删除手机上旧设备记录重新输入PIN码默认1234或0000发一条消息LCD上显示了好几屏残缺内容缺少消息结束判断代码里判断换行符发送端确认加换行每次显示的内容都会丢几个字符软串口接收缓冲区溢出或数据太快降低发送频率避免长消息一次性涌入或者改用硬串口接收LCD显示中文成乱码LCD1602不支持中文字库发英文或数字或换用带中文字库的屏幕模块手机上显示已连接但屏幕无反应蓝牙模块的TX/RX接反或未共地检查TX接D2、RX接D3并确认GND连接在一起这张表是我排查过的真实情况的汇总几乎覆盖了新人能遇到的绝大多数坑。如果你是第一次遇到某个现象优先把表里的对应原因逐条过一遍很多时候问题在“共地”和“接反”这么简单的地方。5.2 硬件层面的避坑经验电源是这个项目里最隐蔽的杀手。Arduino板载稳压芯片给两个模块同时供电时LCD背光要几十毫安蓝牙模块发射瞬间电流可能冲到几十毫安到一百毫安5V电压会有明显波动。症状就是LCD背光忽明忽暗或者正常显示几十秒后开始出乱码。我碰到过两次一次是蓝牙模块发射瞬间把洛阳的5V拉低导致LCD复位一次是杜邦线接触不良接触电阻太大导致启动就黑屏。解决这种问题的方法就是给项目单独供电如果用的是Arduino Uno用外置9V直流电源从DC口供电如果用的Nano在用USB供电的基础上把LCD和蓝牙模块的5V改成从外部电源接入GND和系统共地。此外模块拿到手之后别急着接线。先仔细看一遍针脚丝印很多卖家会在电路板上标注RX、TX、VCC、GND的位置但有的模块TX/RX方向和常规相反有的会多出STATE和EN脚。如果分不清用万用表蜂鸣档测一遍模块电源和地之间有没有短路再看有没有印刷的丝印别靠颜色盲猜。5.3 软件层面的排查顺序如果项目起不来我会严格按照下面这个顺序排查而不是东敲一下西碰一下第一步先绕过蓝牙模块直接验证LCD显示部分。把代码里所有btSerial相关的读取注释掉改用SerialUSB串口接收数据在电脑串口监视器里输入测试字符串。如果LCD能正确显示电脑发的内容那么LCD硬件、I2C、代码的显示逻辑都正常问题锁定在蓝牙链路。第二步单独测试蓝牙模块。把HC-05或HC-06通过USB转TTL模块接到电脑用串口助手发AT指令确认模块能正常回复、波特率是否匹配、能否广播。这个阶段最好在裸模块状态下测试不接其他外设排除干扰。第三步把蓝牙模块接到软串口先不发数据打开Arduino IDE的串口监视器看btSerial接到的原始字节流。如果这里看到的是乱码大概率是波特率不一致或共地不良如果看到正常字符再考虑是不是发送端的换行缺失。这样一步步收窄问题范围比同时猜测好几个变量要高效得多。第四步如果以上都正常才轮到考虑是不是程序逻辑的问题。比如LCD清屏速度跟不上接收速度缓冲越界或者滚屏函数写得有bug。这部分的调试方法就是在displayMessage函数里加Serial.print打印信息告诉你在哪一行卡住了。5.4 提升稳定的两个小技巧第一个技巧是使用硬件串口替代软件串口的终局方案。如果你手头有Arduino Mega这种带多个硬件串口的板子可以把蓝牙模块接到Serial1、Serial2这些硬件串口上这样既占用几个硬件UART又能获得硬件级别的接收可靠性和更大的数据吞吐。UNO和Nano受限于只有一组硬件串口换来换去都一样但Mega及很多ESP32开发板都可以直接用这个方案。第二个技巧是给蓝牙模块的供电加一点缓冲。如果空间允许在蓝牙模块的VCC和GND之间并联一个100微法拉电容注意极性能明显改善模块在发送瞬间的电压跌落。我做过对比实验加了电容之后长时间满载传输时乱码率下降非常明显这在用面包板供电不稳的场景下尤其有效。6. 从显示消息到小型信息控制枢纽的扩展方向这个项目它只是个开始但对数据结构、串口流控、外设驱动方式的积累非常扎实。你可以把这个东西往三个方向扩展。第一个方向换个显示载体。把LCD1602换成OLED显示屏I2C接口通常兼容类似驱动方式只是库不同数据链路完全不变但显示内容可以做得更精致比如加菜单、加图标曲线。OLED的字体和对比度也更清晰还省电。第二个方向接传感器形成“信息中心”。比如加一个DHT11温湿度传感器每隔几秒采集一次在上位机手机/电脑串口发一条“READ”指令Arduino就把“T:25.5 C, H:60%”拼成字符串回传或者显示在LCD上。若想做成主动上报loop里通过millis实现定时采集显示不需要手机每次来问。这套“轮询/定时上报”机制在以后做监控类项目几乎必用。第三个方向把蓝牙换成WiFi模块或直接用ESP32。当你想让LCD显示远程的数据比如服务器推送的通知、天气API返回的信息蓝牙就少显身手了。此时换成ESP8266或ESP32代码逻辑照样是接收串口数据、解析字符串、输出到LCD只是协议从蓝牙串口变成TCP/IP这正好是往物联网方向过渡的跨步。我从这个项目转到ESP32环境屏大概只花了半天时间就迁移完毕因为那个“接收-解析-呈现”的框架早就定型了。我给刚入手的朋友的建议是别停留在“把Hello发到屏幕上”这个阶段。拿到这个项目的代码和接线图然后尝试改动一点点东西比如把消息变成倒序显示、让屏幕收到消息后自动闪烁几次、加一个蜂鸣器提醒、或者自己写一个简单的两字节协议来验证校验位。改坏了大不了重新烧录但每次改动都会让你对数据链路和字符串处理更熟悉。我自己的体会是这种“手机发指令、硬件做反应”的模式一旦玩熟你会发现它几乎可以套用到任何事情上。后续你做的智能小车、桌面时钟、门禁显示、宿舍环境监控本质上都是这个模型换了外设和传输层。所以这个项目真的不只是“Arduino蓝牙LCD屏显示接收到的信息”这么简单它是你手里那套可重复用的信息处理逻辑的起点。遇到乱码别焦虑按上面的排查顺序一步步来这块屏总有乖乖显示你想看到的内容的那一天。