App Inventor 2 串口通信教程 - USB串口与蓝牙串口

« 返回首页

串口通信基础概念

什么是串口通信?

串口通信(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 内置了 BluetoothClientBluetoothServer 组件,无需安装额外扩展。

所需硬件

  • 蓝牙串口模块: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 设计视图中:

  1. 从左侧组件面板找到 Connectivity(连接) 分类
  2. BluetoothClient 组件拖入屏幕
  3. 设置属性:
    • 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 扩展组件。

前提条件

  1. Android 设备:必须支持 USB Host 模式(Android 3.1+,大部分现代手机均支持)
  2. USB OTG 线缆:将手机 Micro-USB 或 USB-C 接口转换为标准 USB-A 接口
  3. 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 扩展

  1. SerialPort 扩展(社区扩展)
    • 基于 Android USB Host API 封装
    • 支持 CH340、CP210X、FTDI、PL2303 等芯片
    • 提供打开/关闭串口、发送/接收数据等基本功能
  2. USB Serial 扩展(基于 felHR85/UsbSerial 库)

安装扩展

  1. 下载扩展文件(.aix 格式)
  2. 在 App Inventor 2 设计器中,点击左下角 “Extension”“Import extension”
  3. 选择下载的 .aix 文件,点击导入
  4. 导入后,扩展组件将出现在组件面板的 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 原生不支持直接的浮点数字节转换。推荐的替代方案:

  1. 在 Arduino 端直接发送文本Serial.println(temperature, 2); 将浮点数转为文本发送
  2. 使用乘以倍数的方式:发送 intValue = round(floatValue * 100),在 AI2 端除以 100 还原
  3. 使用支持数据转换的扩展组件

推荐方案:文本方式传输浮点数

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 默认配对密码为 12340000
  • 在 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. 简单协议(适合初学者):
    • 发送单个字符命令:'1'=开灯,'0'=关灯
    • 接收单行文本响应
  2. 结构化协议(推荐):
    命令格式:CMD:param1,param2\n
    示例:SET_TEMP:25.5\n
          GET_DATA:TEMP,HUMI\n
    
  3. 二进制协议(高级应用):
    帧结构:[帧头 0xAA][帧头 0x55][长度][命令][数据...][校验和]
    

调试技巧

  1. 先用串口调试工具验证:在写 App 之前,先用电脑端的串口助手(如 SSCOM、Tera Term)验证 Arduino 端的通信逻辑
  2. 逐步调试:先确保能连接,再确保能发送,最后调试接收
  3. 显示原始数据:在 App 界面上同时显示十六进制和文本格式的收发数据,方便排查问题
  4. 添加状态指示:始终显示连接状态和最后的通信时间戳

性能优化

  1. 接收缓冲区:适当增大接收缓冲区,避免数据溢出
  2. 避免频繁发送:在定时器中控制发送频率,建议间隔 ≥ 100ms
  3. 数据处理异步化:将数据解析放在单独的过程中处理,不要阻塞通信
  4. 蓝牙 BLE 替代:如果只需要低频数据传输(如每秒 1-2 次),考虑使用 BLE 方案以降低功耗

参考资源


MIT App Inventor 官方文档采用 CC BY-SA 4.0 授权,本文档由 ai2claw 🐝 整理

文档反馈