可靠的串口连接不只是让两端波特率相同。编写应用命令前,应先定义物理连接、帧边界、编码、最大消息长度、超时、确认响应和复位行为。
这份 2026 年维护层把原来五行的笔记扩展为一套完整的 USB 串口流程,采用 Arduino 官方 Serial API 和 pySerial。文末保留完整 2019 年导出作为来源档案。
Table of Contents
1. 选择 USB 串口或 GPIO UART
首次实现时,建议把 Arduino 的普通 USB 数据口连接到 Raspberry Pi USB 口。Arduino 会显示为 USB 串口设备,常见名称是 /dev/ttyACM0 或 /dev/ttyUSB0。这条路线不需要启用 Raspberry Pi 的 GPIO UART,也不用连接 TX/RX 引脚。打开串口可能通过 DTR 让部分 Arduino 开发板复位,因此软件必须容忍短暂重启。
直接 TTL UART 属于另一种电气设计,需要 TX/RX 交叉连接并共地。Raspberry Pi UART 引脚使用 3.3 V 逻辑;官方文档警告,接入 5 V 信号会造成损坏。如果对端不兼容 3.3 V,请使用合适的电平转换器或 USB 转 3.3 V 串口适配器。采用直接 UART 时,应通过 raspi-config 启用 UART 硬件并禁用串口登录控制台,再确认该型号对应的 /dev/serial* 映射。
除非完整电路和开发板行为已经评审,否则不要同时连接 USB 串口和直接 TX/RX 接线。
2. 先定义一个小协议
本例采用刻意收窄的协议:
| 属性 | 约定 |
|---|---|
| 传输 | USB 串口,115200 波特,8 数据位,无校验,1 停止位 |
| 帧边界 | 每行一条 ASCII 命令,以 LF(n)结束;忽略 CR |
| 最大命令 | 结束符之前最多 63 字节 |
| 请求 | PING <十进制请求 ID> |
| 成功 | OK <相同请求 ID> |
| 失败 | ERR bad_command、ERR invalid_byte 或 ERR line_too_long |
| 启动通知 | READY 1;它可能在主机打开串口前发出,因此不能依赖 |
请求 ID 让主机能够匹配响应。PING 没有副作用,所以可以安全重试。移动硬件、收费或写入状态的命令必须单独设计幂等性;盲目重试可能重复执行动作。
协议关键字和数字 ID 使用 ASCII 已经足够。如果后续命令携带人类文本,应明确指定 UTF-8,限制其字节长度,并规定如何拒绝无效输入。不要依赖任一设备当时碰巧启用的编码。
3. 上传有边界的 Arduino 解析器
下面的 sketch 不使用无边界 String,也不会在 loop() 中等待。它缓存一行,拒绝非 ASCII 控制/数据字节,并在帧过长后持续丢弃,直到收到换行。
#include <Arduino.h>
#include <string.h>
constexpr unsigned long BAUD_RATE = 115200;
constexpr size_t MAX_LINE = 64;
enum class DropReason {
none,
invalid_byte,
line_too_long
};
char line_buffer[MAX_LINE];
size_t line_length = 0;
DropReason drop_reason = DropReason::none;
bool is_request_id(const char *text) {
if (*text == '