☰
App Inventor+Arduino+蓝牙,快速搞定手机温湿度计
2026/10/5 1:19:46 网站建设 项目流程

前几天有人问我,用App Inventor做蓝牙温湿度计到底难不难?我说难的部分从来不是蓝牙,而是把Arduino的串口数据变成手机屏幕上能看的温度和湿度。实际走通一次你就知道,这套组合不需要你会Java,不需要碰Android原生开发,更不用去啃蓝牙协议栈的英文文档,核心就是把三件事做好:Arduino采集温湿度并格式化输出、蓝牙模块做无线透传、App Inventor端接收并解析文本。这篇文章就按这个链路展开,从硬件选型到排障一条龙讲清楚,适合那种想快速看到数据上屏、又不想被困在底层代码里的创客朋友。

1. 先搞清楚这个"5分钟"到底能做什么

1.1 为什么选这个组合:App Inventor + Arduino + 蓝牙

严格说,这个项目的"5分钟"指的是把核心链路跑通,而不是把产品打磨到可以拿去卖。你要做的原型是:Arduino每隔两秒读一次DHT11温湿度传感器,通过串口把数据交给蓝牙模块,手机端的App Inventor应用连接蓝牙后,把收到的文本解析出来显示在标签上。整个过程里,App Inventor负责手机端,Arduino负责硬件端,蓝牙模块就是一根看不见的串口线。

选这个组合的理由很直接:成本低、出活快、资料多。Arduino Uno或Nano板子十几块钱,DHT11温湿度传感器几块钱,HC-05蓝牙模块十几块钱,加一起不到五十块就能起步。相比之下,如果用Android原生写蓝牙SPP,你得处理UUID、Socket、输入输出流、线程切换,还要考虑权限适配,没个半天一天搞不定。App Inventor把蓝牙封装成了拖拽块,你只要关心业务逻辑,剩下的系统细节它帮你扛了。

这里先泼一盆冷水:这套方案基本只适合Android手机。无论是HC-05还是HC-06,走的是经典蓝牙SPP协议,iOS对SPP的支持非常差,App Inventor官方也明确说iPhone上无法使用蓝牙客户端组件。如果你手边只有iPhone,别在这个方向上死磕,老老实实换ESP32加BLE方案。Android端则完全没有问题,我实测从Android 6到Android 13都能正常跑。

1.2 材料清单和选型前要做的决定

先列一份最简清单,避免你买一堆用不上的东西:

  • Arduino Uno或Nano,Nano体积小,面包板上用起来方便,我后面示例代码两种板子都能跑。
  • DHT11温湿度传感器,三根线:VCC、GND、DATA。
  • HC-05蓝牙模块,带底板的那种,底板上有稳压和指示灯,适合新手。
  • 面包板一块、杜邦线七八根。
  • Android手机一台,Android 6及以上。

选型的时候有个关键决定:传感器用DHT11还是DHT22?DHT11精度是±2℃、湿度±5%,采样频率1Hz,室内看个大概完全够用,价格便宜。DHT22精度更高,±0.5℃,但价格贵两三倍,而且对供电稳定性更敏感。我建议第一次做从DHT11开始,跑通了再换。另一个决定是蓝牙模块选HC-05、HC-06还是JDY-31。HC-05支持AT指令修改名称和波特率,主从一体,后面做自动连接很方便。HC-06是从机,便宜,但很多版本不能改名字,默认名字就是HC-06。JDY-31是兼容HC-05/06的改进方案,有些版本支持3.3V供电,手册里写着"支持SPP协议、完全兼容HC-05/06",如果你手边是这种模块,代码逻辑完全一样。

1.3 蓝牙模块对比:别被"SPP协议"这四个字吓到

很多人在电商平台搜蓝牙模块,会被"支持SPP协议""经典蓝牙3.0""完全兼容HC-05/06"这些描述搞得头晕。我帮你理一下:

模块角色默认波特率主要特点适合场景
HC-05主从一体9600支持AT指令,可改名字、波特率,有状态引脚需要自动连接的手机项目
HC-06从机9600价格低,不能主从切换,部分版本不能改名简单透传、手动选设备也能接受
JDY-31从机/兼容9600兼容HC-05/06,部分带底板和指示灯替代HC-05/06,买得到哪个用哪个

这里说的SPP(Serial Port Profile)就是"蓝牙串口"协议,手机连上模块后,模块的TX/RX引脚会像USB串口一样收发数据。对用户来说,SPP协议的唯一意义就是:你不需要关心蓝牙底层分包、重传、配对加密,只需要在App Inventor里调用连接,然后把模块端当成普通串口对待。

2. Arduino这端:把温湿度变成一串可读的字符

2.1 接线其实就四根线:电源、地、串口和信号

接线是整个项目里物理上最容易错的一环,尤其两个模块同时接的时候。我们先看DHT11,三根线很简单:VCC接Arduino的5V,GND接GND,DATA接数字引脚D2。DHT11的引脚顺序不同厂家不一样,以丝印为准,别只看网上图。

蓝牙模块稍微讲究一点。如果你用HC-05,模块的VCC接Arduino的5V,GND接GND,然后TX接Arduino的RX,RX接Arduino的TX,注意是交叉接。串口通信的基本原则就是发送端接接收端,你的数据由Arduino发出,所以要进蓝牙模块的RX,模块收到后再从TX发出去。很多人第一次做把TX对TX、RX对RX接上去,结果手机能连上但永远收不到数据,原因就在这。

我建议用软件串口,而不是直接接Arduino的硬件串口0/1。原因有两个:第一,Arduino Uno在通过USB下载程序时,板载串口被占用,如果你把蓝牙接在D0/D1上,下载时会互相干扰,甚至导致上传失败;第二,软件串口可以自定义引脚,灵活性高。我的接线方案是:

  • DHT11 DATA → D2
  • HC-05 TX → D3(Arduino的软串口RX)
  • HC-05 RX → D4(Arduino的软串口TX)
  • HC-05 VCC → Arduino 5V
  • HC-05 GND → Arduino GND

注意等级电平的问题。很多HC-05模块是3.3V逻辑电平,但带底板时往往可以用5V供电,板上自带稳压,RX引脚也能容忍Arduino发出的5V TX信号,实测大多数底板没问题。如果你买的模块没有底板,建议TX引脚上串一个1K电阻做分压,避免烧模块。

2.2 写个最简单的DHT11读取程序

先打开Arduino IDE,在库管理器里搜索并安装两个库:DHT sensor library和Adafruit Unified Sensor。前者是驱动DHT系列传感器的,后者是Adafruit库的依赖,缺了编译会报错。

然后在Arduino IDE里新建一个工程,粘贴下面这段代码:

#include <SoftwareSerial.h> #include <DHT.h> #define DHTPIN 2 #define DHTTYPE DHT11 #define BT_RX 3 #define BT_TX 4 DHT dht(DHTPIN, DHTTYPE); SoftwareSerial bt(BT_RX, BT_TX); void setup() { Serial.begin(9600); bt.begin(9600); dht.begin(); } void loop() { float t = dht.readTemperature(); float h = dht.readHumidity(); if (isnan(t) || isnan(h)) { bt.println("T:err,H:err"); } else { bt.print("T:"); bt.print(t, 1); bt.print(",H:"); bt.println(h, 1); } delay(2000); }

代码逻辑很直白:每两秒读一次温度和湿度,拼成一行文本,通过软串口发给蓝牙模块。t, 1是保留一位小数,避免显示太多数字。DHT11的采样周期是1秒,所以我刻意把发送间隔设成2秒,太频繁会读到不稳定的数,手机端也会被刷屏。

如果你用的是DHT22,把上面DHTTYPE改成DHT22就行,其他不用动。如果你的模块实际是JDY-31,代码也照样跑,接线和HC-05一样。

2.3 数据协议为什么选"T:26.5,H:60.2"这种格式

这是很多人忽略但很重要的设计。Arduino端往串口里塞什么格式的数据,直接决定了App Inventor端解析的难度。

我从一开始就推荐发送带标签、逗号分隔、换行结尾的纯文本,例如:

T:26.5,H:60.2

为什么不用二进制浮点数?因为App Inventor处理二进制要拼接字节、判断结束位,非常痛苦。纯文本你可以直接用文本处理积木按逗号切一次、按冒号再切一次,非常简单。为什么带T:和H:前缀?因为万一某次数据解析出错,你至少能知道哪一段是温度、哪一段是湿度,排查问题时方便很多。为什么每行结尾要println而不是print?因为换行符就是一帧数据的结束标志,App Inventor端可以利用这个分隔符判断"一行数据收完了"。

波特率方面,代码里bt.begin(9600)必须和蓝牙模块的默认波特率一致。HC-05/06出厂默认都是9600,如果你用AT指令改过,记得同步改代码。这个数字不是随便定的,9600在2米左右的距离内完全够用,而且低波特率容错性更好,不容易出乱码。

3. App Inventor这端:从"配对"到"上屏"的完整链路

3.1 界面组件:别放一堆按钮,够用就行

打开App Inventor后,新建一个项目,命名随意。手机端的界面我设计得非常精简:

  • ListPicker1:用来显示已配对的蓝牙设备列表,用户手动点选,也可以用来做自动连接的来源。
  • Button1:连接/断开按钮,文本初始为"连接设备"。
  • Label1:显示温度,比如"温度:26.5 ℃"。
  • Label2:显示湿度,比如"湿度:60.2 %"。
  • Clock1:定时器组件,间隔1000毫秒,用来周期性读蓝牙缓冲。
  • BluetoothClient1:在组件面板的"连接"分类下面,这是整个App的核心,负责蓝牙连接和数据收发。

不要一上来就堆一堆图表、按钮、颜色设置。先把最简单的链路跑通,界面美观是后面的事。

3.2 核心块逻辑:连接、读取、解析

打开“块”视图,真正需要拖的块其实不多。我按功能拆给你看。

连接逻辑:

when Screen1.Initialize if BluetoothClient1.BluetoothEnabled = false call BluetoothClient1.EnableBluetooth

这个块是在应用启动时检查蓝牙是否开启,如果没开就弹出系统配对请求。但注意,EnableBluetooth只是跳转到系统设置页面,不是强制开启,Android不允许普通应用直接开蓝牙。

按钮点击逻辑:

when Button1.Click if BluetoothClient1.IsConnected = true call BluetoothClient1.Disconnect set Button1.Text to "连接设备" else if ListPicker1.Selection != "" call BluetoothClient1.Connect(ListPicker1.Selection) if BluetoothClient1.IsConnected = true set Button1.Text to "断开连接" else call Notifier1.ShowAlert("连接失败")

这里有个细节:ListPicker1.Selection在选中后看起来是设备名,但里面实际保存的是列表项的完整内容。我建议初始化时就给ListPicker1设置Display项和Value项,Display给人看,Value存蓝牙地址。如果你图省事,直接把BluetoothClient1.BluetoothAddressesAndNames扔给ListPicker1作为一个简单列表,Selection里就会包含名称和地址的混合文本,Connect时可能会因为格式不对连接失败。实际上App Inventor的BluetoothAddressesAndNames返回的是每个元素形如"名称 地址"的列表,Connect的时候需要的是地址,所以不能直接拿整个元素去连。比较稳的做法是写一个解析函数,从元素里把最后一段数字地址提取出来。

读取逻辑稍微复杂一点。蓝牙数据是流式的,一次Clock触发不一定能收到完整的一行,可能收到半个数据帧,也可能一次收到好几帧。我在项目里习惯用一个全局变量buffer暂存文本,每次把新读到的内容追加进去,再从中截取一行行的完整数据。

核心块逻辑是这样:

when Clock1.Timer if BluetoothClient1.IsConnected = true and BluetoothClient1.BytesAvailableToRead > 0 set global buffer to join(global buffer, BluetoothClient1.ReceiveText(-1)) while global buffer contains "\n" set global line to segment of global buffer before first "\n" set global buffer to text after first "\n" of global buffer call ProcessLine(global line)

ProcessLine就是你自建的一个函数,参数是一行字符串。里面先用split text at comma把"T:26.5,H:60.2"拆成两个元素,再对每个元素用冒号分割,取第二部分转成数字,最后更新标签文本。如果某一行里包含"err",就直接显示"传感器异常"。这种用缓冲加换行判断的方式,能同时解决半包和粘包问题,是串口解析里的通用套路,不只是App Inventor适用。

3.3 自动连接的两种实现思路

标题里写了"手机自动连Arduino",但App Inventor没有现成的"一键自动连接"组件,需要你写一点逻辑。我试过两种思路,各有优劣。

第一种是启动后自动扫描已配对设备,通过设备名匹配。在Screen1.Initialize里加一个延时,等蓝牙栈就绪后遍历BluetoothClient1.BluetoothAddressesAndNames,找到文本中包含"HC-05"(或者你AT指令改的名字)的那个元素,提取地址并调用Connect。这种方案的好处是用户零操作,打开App就自动连,体验好。坏处是对设备名敏感,你如果用HC-06且没改名,默认名字可能撞车,最好在模块AT指令里改成一个难重复的名字,比如"BT_TEMP_01"。

第二种是定时器轮询式连接。专门用一个Clock或复用读数Clock,在连接失败后每隔几秒尝试连接一次。逻辑是:如果没连接,就遍历设备列表,匹配到目标设备后连接。这样即使手机蓝牙启动慢、第一次没连上,后面也能自动补上。我实际用的就是第二种,因为有些手机蓝牙在上电唤醒后有几百毫秒延迟,第一次连接失败的概率不低,轮询能补齐这个空缺。

但无论哪种方式,有个前提必须在代码外完成:手机必须先和蓝牙模块配对一次。App Inventor只能连接已经配对的设备,它没有提供发起配对并输入PIN码的界面。所以第一次用的时候,先去手机系统设置里的蓝牙页面搜索HC-05,输入PIN码(HC-05默认1234或0000)完成配对,然后再打开你的App。这一点文章后面排障部分会详细说。

3.4 权限和Android版本:很多人在这里翻车

蓝牙应用在Android上有个很反直觉的权限要求:Android 6.0及以上,扫描蓝牙设备或者读取蓝牙相关信息,需要"位置信息"权限。很多人写完App一点ListPicker发现列表是空的,第一反应是蓝牙模块坏了,其实是系统把权限拦了。解决方法是到手机设置里把App的位置权限打开,或者至少在蓝牙扫描时允许"仅使用期间"授权。

Android 12及以上又进一步,把附近设备扫描权限单独拆出来,叫"附近的设备"。App Inventor在打包时会自动在清单里声明这些权限,但首次运行到相关功能时,系统会弹窗索取授权,你需要在弹窗时点允许。国内一些定制ROM还会把权限默认设为拒绝,甚至需要额外开启"后台定位"才能扫描蓝牙。我的建议是:真机测试前,先到应用权限页面把所有和蓝牙、位置相关的权限都打开,排除掉这个变量再排查别的。

4. 实测排障:HC-05连不上、数据乱码、自动连接失效

4.1 手机搜不到HC-05,先按这个顺序排查

HC-05蓝牙模块连接不上,是我在相关搜索词里看到频率最高的问题,可以说十个做蓝牙串口项目的人里面,至少有五个栽在"搜不到模块"上。按下面顺序排查,通常几分钟就能定位。

第一,看模块指示灯。HC-05带底板的话,上电后未连接时指示灯通常是慢闪,连接成功后变成快闪或常亮。如果灯完全不亮,先查电源和接线,VCC和GND是否接反,杜邦线是否松动。很多底板上的LED在3.3V供电时会变暗,不是坏了。

第二,确认模块是否误入了AT模式。HC-05的底板上有按键,如果上电时按住这个按键再通电,模块会进入AT指令模式,此时它不广播蓝牙信号,手机自然搜不到。解决方法是断电后重新上电,不要碰按键。如果你前面试过AT指令,很可能把它留在指令模式下忘记了。

第三,在手机蓝牙设置里找到已经配对的旧设备,删除后重新搜索。有些手机只要设备在配对列表里,就不会在搜索列表里重复显示,你以为搜不到,其实早就配对过。

第四,排除距离和干扰。模块离手机太远、中间隔着金属物体,或者旁边一堆2.4G设备,都可能导致搜索不到。把模块放到手机旁边再试一次。

如果以上都试过还是搜不到,拿另一台手机试一下。如果第二台手机能搜到,大概率是第一台手机的蓝牙扫描缓存或者权限问题;如果第二台也搜不到,模块本身可能是坏的,或者一直被什么东西占用了配对名额。

4.2 连上但永远收不到一行完整数据

手机能连上蓝牙,说明链路已经通了一半,但收不到数据,问题多半出在Arduino到蓝牙模块这一段。

最经典的原因是TX/RX接反。前面我已经强调过交叉接线,但这里还是得再重复一次:模块的TX要接Arduino的RX,模块的RX要接Arduino的TX。你可以在Arduino IDE的串口监视器里先看一眼,如果串口监视器里能正常打印T:26.5,H:60.2,说明传感器和代码没问题,这时问题基本就锁定在蓝牙接线或者波特率上了。

第二个原因是波特率不匹配。如果模块被改成38400或者9600以外的速率,而你代码里仍是9600,收到的就是一堆乱码或者根本没有数据。你可以把蓝牙模块重新进入AT模式,用USB转TTL接电脑发AT+UART?查询当前配置,确认模块波特率再同步代码。没有USB转TTL的话,直接换一个出厂设置的模块也是排查思路。

第三个原因是软串口的引脚选择有讲究。并不是任意两个数字引脚都能做软串口RX/TX。Arduino Uno上,软串口RX引脚不能同时被设置为输入中断用途,虽然DHT11接D2不影响D3,但如果你后续加了其他库占用了引脚,就可能互相干扰。最简单的验证方法是临时把DHT11的数据打印到硬件串口,再在软串口发固定字符串,确认软件串口本身能通。

4.3 每次都要手动选设备?自动连接的坑

如果你已经实现了自动连接逻辑,但发现实际还是要手动点,可能不是逻辑写错,而是手机蓝牙栈没准备好。

我遇到过的情况是:应用启动时就立刻执行连接,但那时安卓蓝牙服务还没有完全就绪,BluetoothClient1.BluetoothAddressesAndNames返回的是空列表,自动连接当然扑空。解决办法是在定时器里做轮询,每隔两秒检查一次连接状态,如果未连接就重新获取设备列表并尝试。等蓝牙栈稳定后,列表自然会出现HC-05,连接就能成功。

另一个坑是设备名称匹配太严格。如果你用contains "HC-05"去匹配,但模块被改名为"BT_TEMP_01",那肯定匹配不上。如果你用= "HC-05"精确匹配,某些模块名字里带了空格或者不可见字符,也会失败。我建议匹配的时候把前后空格去掉,并且用contains而不是精确相等。如果怎么都匹配不上,在ListPicker里手动选一次,连接成功后把那个设备地址存到TinyDB里,下次直接按地址连,比按名字匹配更稳。

4.4 一个完整的排查链路示例

我在一次实际测试中,遇到"手机能连、串口监视器有数据、但手机端始终没显示"的问题,排查过程是这样的:

第一步,确认Arduino串口监视器正常。我打开IDE的串口监视器,看到每两秒一行T:26.5,H:60.2,说明DHT11和代码没问题。

第二步,重新检查蓝牙模块接线。果然发现HC-05的TX接在了Arduino的D4上,也就是TX对TX,把两根线对调后,手机端立刻有数据了。那次之后我总结出一个习惯:无论多自信,接线完成后一定要拿手机串口调试助手先测一发,不要直接上App,这样能把问题分成硬件链路和App逻辑两段,定位快很多。

第三步,如果手机串口调试助手能收到数据但App没显示,重点检查App里的解析逻辑。我这里曾因为ReceiveText(-1)读到的是空字符串导致buffer一直不长,后来改成while BytesAvailableToRead > 0循环读,才稳定下来。所以排障时一定要先确定问题在哪一层,不要从头瞎猜。

5. 跑通之后还能怎么玩:扩展功能和避坑建议

5.1 在手机上画出温湿度变化曲线

当你能稳定收到数据之后,下一步自然是想看趋势。App Inventor新版自带Chart和Trendline组件,你可以在Clock事件里把每次解析出的温度、湿度数值添加到图表的数据源里,图上就会一点点画出曲线。

需要注意的是,App Inventor的图表组件属于相对新的功能,老版本教程里很少讲。如果你用的App Inventor页面里找不到Chart,可以在组件面板的“图表”分类下找。添加数据时用Chart1.DataSource相关的块,把数值追加进去。但蓝牙传输不适合秒级持续刷曲线,持续的2秒一帧对蓝牙和手机屏幕压力都不小,建议把采样间隔改成5秒甚至10秒,或者先在Arduino端累计几组取平均值再发。

5.2 加一个风扇或舵机,让"看数据"变成"用数据"

数据上屏只是第一步,真正好玩的是基于数据做动作。很多人做完温湿度计之后,会接着用Arduino控制风扇、加湿器或者舵机。比如温度超过30℃时,手机App里按一个按钮发送"FAN_ON",Arduino端用软件串口收到这个字符串后,解析出来控制接在D9引脚上的继电器,从而开关风扇。

Arduino端接收指令的代码可以在现有基础上扩展:

if (bt.available() > 0) { String cmd = bt.readStringUntil('\n'); cmd.trim(); if (cmd == "FAN_ON") { digitalWrite(9, HIGH); } else if (cmd == "FAN_OFF") { digitalWrite(9, LOW); } }

App Inventor端发送指令更简单,调用BluetoothClient1.SendText("FAN_ON")就行。这个玩法也是很多"Arduino智能小车""Arduino控制舵机"项目的基础,本质是双向的串口指令交互。不过要注意,App Inventor的SendText是直接发字符串,Arduino端如果同时收字符串又收其他数据,要处理好缓冲区分隔,避免指令和数据混在一起。

5.3 把数据传到云端的轻量方案

蓝牙的天然限制是手机必须靠近才能看到数据。如果你想让温湿度计在无人值守的时候记录数据,或者远程查看,建议换思路:把ESP8266或ESP32接上DHT11,用Wi-Fi直接上报到MQTT服务器。这套方案的好处是彻底摆脱蓝牙配对、手机权限这类问题,数据随时在线,坏处是代码量比Arduino加蓝牙多一些,而且需要配置Wi-Fi和MQTT。

如果你还是想保留现有蓝牙硬件,也可以通过手机App把数据转发到云端。App Inventor里可以用TinyWebDB组件或Web组件调用一个HTTP接口,把每次读到的温湿度POST到你的服务器或云平台。不过手机不在旁边时,数据链路就断了,所以这只能算临时方案,长期看还是ESP32方案靠谱。

我做这个项目的最大体会是,App Inventor最大的价值不是让你避免写代码,而是让你把注意力放在数据链路上:传感器怎么变成数字,数字怎么变成串口字节,串口怎么穿过蓝牙,手机怎么还原成可读信息。想清楚这条链路,后面换任何硬件和平台都能很快上手。你把这个温湿度计跑通之后,下一步哪怕换成PM2.5传感器、土壤湿度传感器,也只是换引脚和解析规则的事。先动手,别怕接线,烧了板子大不了换一块Uno,成本不高,但经验是自己的。

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

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

立即咨询