串口通信器使用指导
设备发文本时,用“读取串口 / 写入串口”。设备发二进制帧时,用“读取字节 / 写入字节”,直接交给二进制组件,不经过文本转换。00、80、FF 都是普通数据,不是乱码。
通信模型与接线
通过“串口通信器”组件,App 可以通过手机串口进行通信;如果只需无线传输,使用蓝牙/BLE 通信可能是目前更主流的方案(详见 Iot 专题)。
手机串口通信模型如下:

可以看到,手机串口连接时,没有像 PC 那样指定“COM3”等端口,而是直接使用默认的 USB 设备:

先跑通原始字节回显
下载 SerialBinaryLab.aia。文件的自动检查结果见验证记录;USB 硬件回显需要按本节实测。
本例是 USB 串口诊断 App,用来确认连接、原样收发、半包处理和校验;不会控制继电器或擅自配置设备。Android 手机需要支持 USB 主机/OTG,连接驱动支持的 USB 转串口模块。将模块自己的 TX 与 RX 接在一起形成回显,再连接手机;已有设备固件实现原样回显时可直接用该设备。串口通信器不会使手机自动兼容所有 USB 芯片。
导入工程 → 连接模块 → 打开 → 允许 USB 权限,必要时再点打开 → 发送。预期发送和回复都为 A5 01 05 DC 87,显示“回显一致,原始字节收发通过”;断开后停止轮询。手机端尚未实测,不用 APK 编译结果代替这个操作。
默认波特率 9600、缓冲区大小 256。波特率在串口组件的界面属性中设置,两端相同;本实现按驱动默认串口格式工作,不能把此示例当成所有工业设备的通用配置。
| 界面组件 | 名称/设置 |
|---|---|
| 串口通信器 | 串口通信器1;波特率 9600,缓冲区大小 256 |
| 二进制 | 二进制1;高位优先:真 |
| 计时器 | 计时器1;计时间隔 100 毫秒,计时启用:假,始终计时:假 |
| 按钮 | 按钮_打开、按钮_发送、按钮_关闭 |
| 标签 | 标签_状态、标签_发送、标签_接收 |
完整代码块
帧为 A5 + 01 + 两字节数值 1500 + SUM8,共 5 字节。发送前显示原始帧;收到完整、校验正确且与发送一致的帧,才算通过。一次只发一个请求,约 3 秒无回复则超时。下面的缓存只是这个固定 5 字节诊断协议;接真实设备时按它的帧格式改,不能把任意 5 字节当成一帧。
global 缓冲 = []
global 等待回复 = false
global 等待次数 = 0
global 发送帧 = []
when 按钮_打开.Click() {
if (not 串口通信器1.IsOpen) {
串口通信器1.InitializeSerial()
if (串口通信器1.OpenSerial()) {
缓冲 = []
等待回复 = false
计时器1.TimerEnabled = true
标签_状态.Text = "已打开,可以发送"
} else {
标签_状态.Text = "未打开;允许 USB 权限并检查设备后重试"
}
}
}
when 按钮_发送.Click() {
if (串口通信器1.IsOpen) {
if (not 等待回复) {
发送帧 = 二进制1.AppendChecksum(二进制1.Build([165, 1, 二进制1.NumberToBytes(1500, 2)]), "SUM8")
等待回复 = true
等待次数 = 0
标签_发送.Text = 二进制1.ToHex(发送帧)
标签_状态.Text = "已发送,等待回显确认"
串口通信器1.WriteBytes(发送帧)
} else {
标签_状态.Text = "上一条请求还在等待回复"
}
} else {
标签_状态.Text = "请先打开串口"
}
}
when 计时器1.Timer() {
if (串口通信器1.IsOpen) {
var 新字节 = 串口通信器1.ReadBytes()
if (length(新字节) > 0) {
标签_接收.Text = 二进制1.ToHex(新字节)
缓冲 = 二进制1.Build([缓冲, 新字节])
if (length(缓冲) > 1024) {
缓冲 = []
标签_状态.Text = "数据过多,已清空缓存;请检查设备协议"
}
while (length(缓冲) >= 5) {
var 帧头 = 二进制1.IndexOf(缓冲, 165, 1)
if (帧头 == 0) {
缓冲 = []
} else {
if (帧头 > 1) {
缓冲 = 二进制1.SubBytes(缓冲, 帧头, length(缓冲) - 帧头 + 1)
}
if (length(缓冲) >= 5) {
var 帧 = 二进制1.SubBytes(缓冲, 1, 5)
if (二进制1.VerifyChecksum(帧, "SUM8")) {
if (等待回复 and 二进制1.ToHex(帧) == 二进制1.ToHex(发送帧)) {
等待回复 = false
标签_状态.Text = "回显一致,原始字节收发通过"
}
缓冲 = 二进制1.SubBytes(缓冲, 6, length(缓冲) - 5)
} else {
缓冲 = 二进制1.SubBytes(缓冲, 2, length(缓冲) - 1)
}
}
}
}
}
if (等待回复) {
等待次数 = 等待次数 + 1
if (等待次数 >= 30) {
等待回复 = false
缓冲 = []
标签_状态.Text = "回复超时;检查回显接线、波特率和设备协议"
}
}
} else {
清理会话()
标签_状态.Text = "设备已断开,请重新打开"
}
}
when 按钮_关闭.Click() {
清理会话()
if (串口通信器1.IsOpen) {
var 已关闭 = 串口通信器1.CloseSerial()
if (已关闭) {
标签_状态.Text = "已关闭"
} else {
标签_状态.Text = "关闭失败,请检查设备"
}
}
}
procedure 清理会话() {
计时器1.TimerEnabled = false
等待回复 = false
缓冲 = []
}
when Screen1.ErrorOccurred(组件, 函数名称, 错误编号, 消息) {
等待回复 = false
缓冲 = []
if (not 串口通信器1.IsOpen) {
清理会话()
}
标签_状态.Text = join("串口错误:", 函数名称, " / ", 消息)
}
换成你的硬件
只改三类内容:串口参数、发送帧、收到完整帧后的解析。先把收到的字节显示成十六进制,对照设备文档;协议匹配后再更新界面。
- 开关/继电器:发送命令并等待确认;不要在发送按钮中直接显示“设备已打开”。状态字节使用“取位 / 设置位”。
- 传感器:16 位整数用“字节转整数”,有负值时选有符号;比例为 0.1 时除以 10。IEEE754 float 用“字节转小数”,字节数 4;字节顺序对照设备文档。
- 文本串口屏/打印机:使用文本读写;需要中文时先确认编码,GBK 字节可由二进制组件转换后直接“写入字节”。打印机命令必须使用对应型号的协议。
- 长度可变的帧:复用帧头和长度拆帧,把新数据来源换成
串口通信器1.ReadBytes();不要复制本例的固定 5 字节规则。
同一串口不要同时调用文本读取和字节读取,否则一次读取会取走另一次要处理的数据。缓冲区大小只限制单次读取量,不是设备帧长;没有数据时返回空列表,计时器继续等待即可。
视频教程
视频介绍了 App Inventor 2 串口组件的使用方法,以及如何利用手机串口与 Arduino 硬件进行文本通信。串口通信波特率:9600。
方法与错误
| 入口 | 作用 |
|---|---|
InitializeSerial() / OpenSerial() |
初始化后打开;只有打开返回真才启动轮询 |
WriteBytes(byteList) |
原样发送列表,不加换行;空列表不发送 |
ReadBytes() |
返回目前到达的字节,数量可能少于缓冲区大小;没有数据返回空列表 |
CloseSerial() |
主动关闭;离开/重置 Android 工程时组件也会释放串口 |
3901 表示尚未初始化,3903 表示尚未打开,3902 表示写入失败/不完整,3904 表示字节或缓冲区参数不合法,3905 表示读取失败。错误在屏幕“出现错误时”显示;缓冲区大小允许 1~8192,默认 256。
Android 使用已有 USB 串口驱动。鸿蒙沿用原有串口管理实现,原始字节接口已对齐,但其系统 API 从 API 19 引入且依赖设备串口能力,不能把 Android 测试结果当作鸿蒙设备实测。参见 OpenHarmony 串口 API 声明。
扫码添加客服咨询