串口通信基础概念
什么是串口通信?
串口通信(Serial Communication)是一种逐位(bit by bit)传输数据的通信方式。在嵌入式开发、物联网、传感器数据采集等领域,串口是最常用的通信手段之一。常见的串口参数包括:
| 参数 | 说明 | 常见值 |
|---|---|---|
| 波特率(Baud Rate) | 数据传输速率 | 9600、115200 |
| 数据位(Data Bits) | 每帧数据的有效位数 | 7、8 |
| 停止位(Stop Bits) | 标志一帧数据结束 | 1、2 |
| 校验位(Parity) | 错误检测方式 | None、Odd、Even |
为什么在 App Inventor 2 中使用串口通信?
在以下典型场景中,你需要在 App Inventor 2(简称 AI2)应用中使用串口通信:
- Arduino 通信:手机与 Arduino 开发板进行数据交互
- 传感器数据采集:读取温度、湿度、GPS 等传感器模块的数据
- 硬件控制:通过手机控制电机、舵机、LED 等外设
- 串口调试:开发一个手机端的串口调试助手工具
- 物联网项目:将硬件设备与手机 App 对接
方案对比
在 Android 平台上通过 App Inventor 2 实现串口通信,主要有以下三种方案:
| 特性 | USB 串口(OTG) | 蓝牙经典串口 | 蓝牙 BLE |
|---|---|---|---|
| 连接方式 | USB OTG 线缆 | 蓝牙配对 | 蓝牙低功耗 |
| 传输距离 | 约 1-2 米(线缆长度) | 约 10 米 | 约 10-30 米 |
| 传输速率 | 最高 12Mbps(USB 2.0) | 约 1-3 Mbps | 约 1 Mbps |
| 是否需要扩展 | 需要 USB Serial 扩展 | 内置 BluetoothClient 组件 | 需要 BLE 扩展 |
| 硬件要求 | OTG 线 + USB 转串口模块 | 蓝牙模块(HC-05/HC-06) | BLE 模块(HM-10/ESP32) |
| 功耗 | 较高 | 中等 | 低 |
| 适用场景 | 有线调试、高速数据传输 | 短距离无线控制 | 低功耗物联网设备 |
| Android 版本 | 3.1+(USB Host 支持) | 2.0+ | 4.3+ |
选择建议:
- 如果你的项目需要有线直连(如使用 USB OTG 线连接 Arduino),选择 USB 串口方案
- 如果你需要无线控制且硬件成本较低,选择 蓝牙经典串口方案(HC-05/HC-06 模块)
- 如果你需要低功耗、较远距离通信,选择 蓝牙 BLE方案
方案一:蓝牙串口通信(内置组件)
蓝牙串口是 App Inventor 2 中最成熟、最容易实现的串口通信方式。AI2 内置了 BluetoothClient 和 BluetoothServer 组件,无需安装额外扩展。
所需硬件
- 蓝牙串口模块:HC-05、HC-06、JDY-31 等
- Arduino 开发板(可选,用于实际项目)
- 杜邦线等连接线材
HC-05/HC-06 蓝牙模块与 Arduino 连接
HC-05/HC-06 Arduino Uno
─────────── ───────────
VCC ───→ 5V
GND ───→ GND
TXD ───→ RX (Pin 0)
RXD ───→ TX (Pin 1)
⚠️ 注意:HC-05/HC-06 的工作电压为 3.3V(RX 引脚),而 Arduino Uno 的 TX 输出为 5V。建议在 Arduino TX 与蓝牙模块 RXD 之间串联一个分压电路(两个电阻),以保护蓝牙模块。
App Inventor 2 蓝牙串口组件配置
1. 添加 BluetoothClient 组件
在 App Inventor 2 设计视图中:
- 从左侧组件面板找到 Connectivity(连接) 分类
- 将 BluetoothClient 组件拖入屏幕
- 设置属性:
- BluetoothClient1 是不可见组件,自动出现在组件列表中
2. BluetoothClient 关键属性
| 属性 | 说明 | 默认值 |
|---|---|---|
Secure |
是否使用安全连接 | true |
DelimiterByte |
消息分隔字节值 | 0 |
3. BluetoothClient 关键方法
| 方法 | 说明 |
|---|---|
Connect(address) |
连接到指定 MAC 地址的蓝牙设备 |
ConnectWithUUID(address, uuid) |
使用 UUID 连接(SPP 串口协议) |
Disconnect() |
断开蓝牙连接 |
SendText(text) |
发送文本数据 |
SendBytes(byteArray) |
发送字节数组 |
ReceiveText(numberOfBytes) |
接收指定长度的文本 |
ReceiveSignedBytes(numberOfBytes) |
接收有符号字节数组 |
ReceiveUnsignedBytes(numberOfBytes) |
接收无符号字节数组 |
Available() |
返回可读取的字节数 |
IsConnected |
是否已连接 |
蓝牙串口通信积木块实现
界面设计
建议在界面上放置以下组件:
- ListPicker(蓝牙设备选择器):用于选择并连接蓝牙设备
- Button(发送按钮):发送数据
- TextBox(输入框):输入要发送的数据
- Label(显示标签):显示接收到的数据
- BluetoothClient1(不可见组件):蓝牙客户端
蓝牙连接积木
当 ListPicker1.BeforePicking 执行:
设置 ListPicker1.Elements 为 BluetoothClient1.AddressesAndNames
当 ListPicker1.AfterPicking 执行:
如果 BluetoothClient1.Connect(ListPicker1.Selection):
设置 Label_Status.文本 为 "已连接:" + ListPicker1.Selection
否则:
设置 Label_Status.文本 为 "连接失败!"
断开连接积木
当 Button_Disconnect.点击 执行:
调用 BluetoothClient1.Disconnect()
设置 Label_Status.文本 为 "已断开连接"
发送文本数据积木
当 Button_Send.点击 执行:
如果 BluetoothClient1.IsConnected:
调用 BluetoothClient1.SendText(TextBox_Send.文本)
否则:
设置 Label_Status.文本 为 "未连接!"
发送字节数据积木
当 Button_SendByte.点击 执行:
如果 BluetoothClient1.IsConnected:
调用 BluetoothClient1.SendBytes(字节数组)
否则:
设置 Label_Status.文本 为 "未连接!"
💡 提示:发送字节数据时,可以使用 AI2 的
make a list和类型转换积木来构造字节数组。
接收数据积木(定时轮询方式)
当 Clock1.Timer 执行(间隔 100 毫秒):
如果 BluetoothClient1.IsConnected:
如果 BluetoothClient1.Available() > 0:
设置 Label_Receive.文本 为 BluetoothClient1.ReceiveText(BluetoothClient1.Available())
接收数据积木(分隔符方式)
使用分隔符方式可以按行接收数据(适用于 Arduino 的 Serial.println() 输出):
当 BluetoothClient1.AfterReceiving 执行:
// 当收到以 DelimiterByte 结尾的数据时触发
设置 Label_Receive.文本 为 BluetoothClient1.ReceiveText(-1)
⚠️ 注意:使用分隔符接收方式前,需要先调用
BluetoothClient1.ReceiveText(-1)(参数为 -1 表示读取到分隔符为止)。分隔符默认为换行符(ASCII 10)。
Arduino 端代码示例(蓝牙串口)
// Arduino 蓝牙串口通信示例
// 使用 HardwareSerial 与蓝牙模块通信
void setup() {
Serial.begin(9600); // 与蓝牙模块通信的波特率
}
void loop() {
// 从蓝牙模块接收数据(来自手机 App)
if (Serial.available() > 0) {
char command = Serial.read();
switch (command) {
case '1':
// 打开 LED
digitalWrite(13, HIGH);
Serial.println("LED ON");
break;
case '0':
// 关闭 LED
digitalWrite(13, LOW);
Serial.println("LED OFF");
break;
case 'T':
// 返回温度传感器数据
int temp = analogRead(A0);
Serial.println(temp);
break;
}
}
}
💡 提示:如果你的 Arduino 使用了
Serial与电脑通信,同时需要与蓝牙模块通信,可以考虑使用 SoftwareSerial 库,将蓝牙模块连接到其他数字引脚。
方案二:USB 串口通信(扩展组件)
USB 串口通信通过 Android 的 USB Host API 实现手机与 USB 设备的直接通信。这需要使用 OTG(On-The-Go)线缆和 USB Serial 扩展组件。
前提条件
- Android 设备:必须支持 USB Host 模式(Android 3.1+,大部分现代手机均支持)
- USB OTG 线缆:将手机 Micro-USB 或 USB-C 接口转换为标准 USB-A 接口
- USB 转串口芯片:常见的有 CH340、CP2102、FT232、PL2303 等
支持的 USB 转串口芯片
| 芯片型号 | 说明 | 默认波特率 | 常见设备 |
|---|---|---|---|
| CH340 / CH341 | 国产芯片,性价比高 | 9600 | Arduino Nano 兼容板 |
| CP210X | Silicon Labs 芯片 | 9600 | ESP8266 开发板 |
| FT232 | FTDI 芯片,稳定可靠 | 9600 | 工业级转换器 |
| PL2303 | Prolific 芯片 | 9600 | 早期串口线 |
| CDC/ACM | USB 通信设备类 | 115200 | Arduino Uno/Mega(原生USB) |
USB Serial 扩展组件
由于 App Inventor 2 内置组件不直接支持 USB 串口通信,需要使用第三方扩展(Extension)。
常用的 USB Serial 扩展
- SerialPort 扩展(社区扩展)
- 基于 Android USB Host API 封装
- 支持 CH340、CP210X、FTDI、PL2303 等芯片
- 提供打开/关闭串口、发送/接收数据等基本功能
- USB Serial 扩展(基于 felHR85/UsbSerial 库)
- 开源库 UsbSerial 的 AI2 封装
- 支持同步和异步数据读写
- 支持 CDC、CP210X、FTDI、PL2303、CH34x 等芯片
- GitHub 仓库:https://github.com/felHR85/UsbSerial
安装扩展
- 下载扩展文件(
.aix格式) - 在 App Inventor 2 设计器中,点击左下角 “Extension” → “Import extension”
- 选择下载的
.aix文件,点击导入 - 导入后,扩展组件将出现在组件面板的 Extension 分类中
USB Serial 扩展的关键方法
⚠️ 注意:以下为典型 USB Serial 扩展的方法描述,具体方法名称和参数可能因不同扩展而异,请参考你所使用的扩展的文档。
| 方法/事件 | 说明 |
|---|---|
Open(baudRate) |
以指定波特率打开 USB 串口设备 |
Close() |
关闭串口连接 |
WriteText(text) |
发送文本数据 |
WriteBytes(byteList) |
发送字节数组 |
AfterReceiving(text) |
收到数据时触发的事件 |
IsOpened |
返回串口是否已打开 |
SetBaudRate(rate) |
设置波特率 |
SetDataBits(bits) |
设置数据位(7/8) |
SetParity(parity) |
设置校验位 |
SetStopBits(bits) |
设置停止位 |
USB 串口通信积木块实现
界面设计
- Button(连接按钮):打开 USB 串口
- Button(断开按钮):关闭 USB 串口
- TextBox(波特率输入):输入波特率,默认 9600
- TextBox(发送输入框):输入要发送的数据
- Button(发送按钮):发送数据
- Label(接收显示):显示接收到的数据
- SerialPort1(扩展组件):USB 串口组件
打开串口积木
当 Button_Connect.点击 执行:
调用 SerialPort1.Open(TextBox_BaudRate.文本)
如果 SerialPort1.IsOpened:
设置 Label_Status.文本 为 "串口已打开,波特率:" + TextBox_BaudRate.文本
否则:
设置 Label_Status.文本 为 "串口打开失败,请检查设备连接"
关闭串口积木
当 Button_Disconnect.点击 执行:
调用 SerialPort1.Close()
设置 Label_Status.文本 为 "串口已关闭"
发送数据积木
当 Button_Send.点击 执行:
如果 SerialPort1.IsOpened:
调用 SerialPort1.WriteText(TextBox_Send.文本)
否则:
设置 Label_Status.文本 为 "串口未打开!"
接收数据积木
当 SerialPort1.AfterReceiving(receivedData) 执行:
设置 Label_Receive.文本 为 receivedData
数据格式与解析
串口通信中,数据的编码和解析是关键环节。以下是常见的数据格式处理方法。
文本(ASCII)模式
最简单的通信方式,直接发送和接收文本字符串。
发送端积木:
调用 BluetoothClient1.SendText("HELLO")
接收端积木:
设置 receivedText 为 BluetoothClient1.ReceiveText(-1)
十六进制模式
在工业控制和嵌入式通信中,经常需要发送十六进制(Hex)格式的原始字节数据。
十六进制字符串转字节数组
将十六进制字符串(如 "FF 01 A3")转换为字节数组:
过程 HexToBytes(hexString):
// 将十六进制字符串拆分为字节列表
设置 hexList 为 split hexString at " "
设置 byteList 为 空列表
对于 hexList 中的每个 hexByte:
// 将两位十六进制字符串转为数字
设置 byteValue 为 十六进制转十进制(hexByte)
将 byteValue 添加到 byteList
返回 byteList
💡 提示:AI2 中没有内置的十六进制转换函数,可以使用数学运算来实现。十六进制字符 ‘0’-‘9’ 对应值 0-9,’A’-‘F’ 对应值 10-15。
字节数组转十六进制字符串
将接收到的字节数组转换为可读的十六进制字符串:
过程 BytesToHex(byteList):
设置 hexString 为 ""
对于 byteList 中的每个 byteValue:
设置 hexString 为 hexString + 格式化为两位十六进制(byteValue) + " "
返回 hexString
浮点数转字节
在传感器数据传输中,经常需要将浮点数转换为字节进行传输。
Arduino 端(发送浮点数)
// Arduino 端:将浮点数通过串口发送
void sendFloat(float value) {
byte* bytePtr = (byte*)&value;
Serial.write(bytePtr, sizeof(float)); // 发送 4 个字节
}
App Inventor 2 端(接收并还原浮点数)
在 AI2 中接收 4 个字节后,需要将它们还原为浮点数:
过程 BytesToFloat(b1, b2, b3, b4):
// IEEE 754 单精度浮点数(32位)
// 小端模式(Little-Endian):低位字节在前
设置 intValue 为 b1 + b2 * 256 + b3 * 65536 + b4 * 16777216
// 使用类型转换还原为浮点数
// 注意:AI2 中需要通过扩展或特殊处理来实现浮点数字节转换
返回 intValue 对应的浮点数
⚠️ 重要说明:App Inventor 2 原生不支持直接的浮点数字节转换。推荐的替代方案:
- 在 Arduino 端直接发送文本:
Serial.println(temperature, 2);将浮点数转为文本发送- 使用乘以倍数的方式:发送
intValue = round(floatValue * 100),在 AI2 端除以 100 还原- 使用支持数据转换的扩展组件
推荐方案:文本方式传输浮点数
Arduino 端代码:
float temperature = 25.67;
Serial.println(temperature, 2); // 发送 "25.67\n"
AI2 端积木:
当 Clock1.Timer 执行:
如果 BluetoothClient1.Available() > 0:
设置 receivedText 为 BluetoothClient1.ReceiveText(-1)
设置 temperature 为 转换为数字(receivedText)
设置 Label_Temp.文本 为 "温度:" + temperature + "°C"
数据帧格式设计
在复杂的通信场景中,建议设计统一的数据帧格式:
帧头 + 数据长度 + 数据体 + 校验和 + 帧尾
示例:
0xAA 0x55 0x04 0x01 0x19 0x00 0x00 0x1E 0x0D 0x0A
帧头 长度 数据体(4字节) 校验 帧尾
过程 ParseDataFrame(receivedBytes):
// 查找帧头 0xAA55
如果 receivedBytes[1] = 0xAA 且 receivedBytes[2] = 0x55:
设置 dataLength 为 receivedBytes[3]
// 提取数据体
设置 dataStart 为 4
设置 checksum 为 0
对于 i = dataStart 到 dataStart + dataLength - 1:
checksum = checksum + receivedBytes[i]
// 校验
如果 checksum mod 256 = receivedBytes[dataStart + dataLength]:
返回 "数据校验通过"
否则:
返回 "数据校验失败"
实战案例
案例一:Arduino LED 蓝牙控制
通过手机蓝牙远程控制 Arduino 上的 LED 开关。
Arduino 代码
// Arduino 蓝牙 LED 控制示例
const int LED_PIN = 13;
void setup() {
pinMode(LED_PIN, OUTPUT);
Serial.begin(9600); // HC-05 默认波特率
}
void loop() {
if (Serial.available() > 0) {
char cmd = Serial.read();
if (cmd == '1') {
digitalWrite(LED_PIN, HIGH);
Serial.println("LED:ON");
} else if (cmd == '0') {
digitalWrite(LED_PIN, LOW);
Serial.println("LED:OFF");
}
}
}
App Inventor 2 积木
连接蓝牙设备:
当 ListPicker_BT.BeforePicking 执行:
设置 ListPicker_BT.Elements 为 BluetoothClient1.AddressesAndNames
当 ListPicker_BT.AfterPicking 执行:
BluetoothClient1.Connect(ListPicker_BT.Selection)
开灯:
当 Button_On.点击 执行:
如果 BluetoothClient1.IsConnected:
调用 BluetoothClient1.SendText("1")
关灯:
当 Button_Off.点击 执行:
如果 BluetoothClient1.IsConnected:
调用 BluetoothClient1.SendText("0")
接收反馈:
当 Clock1.Timer 执行(间隔 200ms):
如果 BluetoothClient1.IsConnected:
如果 BluetoothClient1.Available() > 0:
设置 Label_Feedback.文本 为 BluetoothClient1.ReceiveText(BluetoothClient1.Available())
案例二:DHT11 温湿度传感器数据读取
通过蓝牙实时读取 Arduino 连接的 DHT11 温湿度传感器数据。
Arduino 代码
#include <DHT.h>
#define DHTPIN 2
#define DHTTYPE DHT11
DHT dht(DHTPIN, DHTTYPE);
void setup() {
Serial.begin(9600);
dht.begin();
}
void loop() {
if (Serial.available() > 0) {
char cmd = Serial.read();
if (cmd == 'R') { // 收到读取命令
float h = dht.readHumidity();
float t = dht.readTemperature();
if (!isnan(h) && !isnan(t)) {
// 以文本格式发送,逗号分隔
Serial.print(t, 1);
Serial.print(",");
Serial.println(h, 1);
} else {
Serial.println("ERROR");
}
}
}
}
App Inventor 2 积木
请求传感器数据:
当 Button_Read.点击 执行:
如果 BluetoothClient1.IsConnected:
调用 BluetoothClient1.SendText("R")
自动定时读取:
当 Clock_Auto.Timer 执行(间隔 2000ms):
如果 Checkbox_Auto.勾选 且 BluetoothClient1.IsConnected:
调用 BluetoothClient1.SendText("R")
解析接收数据:
当 Clock_Receive.Timer 执行(间隔 100ms):
如果 BluetoothClient1.IsConnected:
如果 BluetoothClient1.Available() > 0:
设置 rawText 为 BluetoothClient1.ReceiveText(-1)
设置 dataList 为 split rawText at ","
如果 length of dataList ≥ 2:
设置 temp 为 select list item list=dataList index=1
设置 humi 为 select list item list=dataList index=2
设置 Label_Temp.文本 为 "温度:" + temp + "°C"
设置 Label_Humi.文本 为 "湿度:" + humi + "%"
案例三:USB OTG 串口调试助手
开发一个简单的 USB 串口调试工具,支持发送和接收十六进制/文本数据。
界面设计
| 组件 | 名称 | 用途 |
|---|---|---|
| Button | Button_Connect | 连接 USB 设备 |
| Button | Button_Disconnect | 断开连接 |
| TextBox | TextBox_BaudRate | 波特率(默认 9600) |
| CheckBox | CheckBox_HexMode | 十六进制模式 |
| TextBox | TextBox_Send | 发送数据输入框 |
| Button | Button_Send | 发送按钮 |
| Label | Label_Receive | 接收数据显示 |
| Button | Button_Clear | 清空接收区 |
| SerialPort1(扩展) | SerialPort1 | USB 串口组件 |
| Clock | Clock_Receive | 定时接收 |
打开串口积木
当 Button_Connect.点击 执行:
设置 baudRate 为 转换为数字(TextBox_BaudRate.文本)
调用 SerialPort1.Open(baudRate)
发送数据积木(支持文本/十六进制模式切换)
当 Button_Send.点击 执行:
如果 SerialPort1.IsOpened:
如果 CheckBox_HexMode.勾选:
// 十六进制模式:将十六进制字符串转为字节
设置 hexString 为 TextBox_Send.文本
设置 byteList 为 call HexToBytes(hexString)
调用 SerialPort1.WriteBytes(byteList)
否则:
// 文本模式:直接发送文本
调用 SerialPort1.WriteText(TextBox_Send.文本)
接收数据显示积木
当 SerialPort1.AfterReceiving(data) 执行:
如果 CheckBox_HexMode.勾选:
// 十六进制模式显示
设置 Label_Receive.文本 为 Label_Receive.文本 + call BytesToHex(data)
否则:
// 文本模式显示
设置 Label_Receive.文本 为 Label_Receive.文本 + data
常见问题与解决方案
1. 蓝牙连接失败
问题描述:点击蓝牙设备后显示连接失败。
解决方案:
- 确保手机蓝牙已开启,并且已与 HC-05/HC-06 配对
- HC-05 默认配对密码为
1234或0000 - 在 Android 系统设置中手动配对蓝牙设备后,再在 App 中连接
- 确保没有其他应用占用蓝牙连接
2. 串口打开失败(USB 模式)
问题描述:USB 串口扩展无法打开设备。
解决方案:
- 确认手机支持 USB OTG 功能
- 确认 OTG 线缆工作正常
- 确认 USB 转串口芯片型号在扩展的支持列表中
- 部分 Android 设备需要用户授权 USB 设备访问权限(弹窗确认)
3. 接收数据乱码
问题描述:接收到的数据显示为乱码。
解决方案:
- 检查波特率是否匹配:App 端和 Arduino/设备端必须一致
- 检查数据格式:文本模式和十六进制模式不要混用
- 检查编码方式:确保两端使用相同的字符编码(ASCII / UTF-8)
- 如果发送中文,确保使用 UTF-8 编码
4. 数据接收不完整
问题描述:接收到的数据被截断或分多次到达。
解决方案:
- 使用分隔符方式接收(如换行符
\n) - 设计数据帧格式,包含帧头、长度、校验等信息
- 在 Arduino 端使用
Serial.println()而非Serial.print() - 适当增大定时器的接收间隔
5. 浮点数传输精度丢失
问题描述:传输浮点数时精度丢失。
解决方案:
- 推荐:在 Arduino 端直接以文本形式发送浮点数:
Serial.println(value, 2); - 如果必须使用二进制方式传输,注意字节序(大小端)问题
- Arduino 使用小端序(Little-Endian),部分扩展可能需要手动处理字节序
6. 多条数据粘包
问题描述:快速连续发送数据时,接收端出现粘包现象。
解决方案:
- 使用行缓冲:每条数据后加
\r\n或\n - 使用固定长度数据帧
- 使用起始/结束标记(如
<>包裹每条数据)
最佳实践
通信协议设计
- 简单协议(适合初学者):
- 发送单个字符命令:
'1'=开灯,'0'=关灯 - 接收单行文本响应
- 发送单个字符命令:
- 结构化协议(推荐):
命令格式:CMD:param1,param2\n 示例:SET_TEMP:25.5\n GET_DATA:TEMP,HUMI\n - 二进制协议(高级应用):
帧结构:[帧头 0xAA][帧头 0x55][长度][命令][数据...][校验和]
调试技巧
- 先用串口调试工具验证:在写 App 之前,先用电脑端的串口助手(如 SSCOM、Tera Term)验证 Arduino 端的通信逻辑
- 逐步调试:先确保能连接,再确保能发送,最后调试接收
- 显示原始数据:在 App 界面上同时显示十六进制和文本格式的收发数据,方便排查问题
- 添加状态指示:始终显示连接状态和最后的通信时间戳
性能优化
- 接收缓冲区:适当增大接收缓冲区,避免数据溢出
- 避免频繁发送:在定时器中控制发送频率,建议间隔 ≥ 100ms
- 数据处理异步化:将数据解析放在单独的过程中处理,不要阻塞通信
- 蓝牙 BLE 替代:如果只需要低频数据传输(如每秒 1-2 次),考虑使用 BLE 方案以降低功耗
参考资源
- MIT App Inventor 官方文档
- MIT App Inventor 社区论坛
- felHR85/UsbSerial - Android USB 串口库
- Android USB Host API 官方文档
- Arduino Bluetooth Serial 教程
MIT App Inventor 官方文档采用 CC BY-SA 4.0 授权,本文档由 ai2claw 🐝 整理
扫码添加客服咨询