go.bug.st/serial.v1是最省心的串口库:纯Go实现、无cgo依赖、跨平台开箱即用;需分平台排查设备名与权限,Read/Write必须设超时和终止符,并发读写须加锁或分goroutine。

go.bug.st/serial.v1 是目前最省心的选择:纯 Go 实现、无 CGO 依赖、跨平台开箱即用,不用为 Windows 的 msvcrt、Linux 的 dialout 权限或 macOS 的 /dev/tty.* 占用问题反复折腾。
串口打不开:Permission denied 或 device not found 怎么查
这不是代码写错了,是系统级路径或权限没对上。
- Linux:先运行
ls /dev/tty*确认真实设备名(如/dev/ttyUSB0),再确认用户是否在dialout组:sudo usermod -a -G dialout $USER,改完必须重新登录终端 - macOS:只认
/dev/cu.*开头的设备(如/dev/cu.usbserial-1420),/dev/tty.*很可能被系统后台占着 - Windows:端口名必须全大写且无前缀,
COM3可以,com3或\.COM3在部分库下会失败;go.bug.st/serial内部做了标准化,相对宽容 - 别硬编码端口。用
serial.GetPortsList()先探测可用端口,或让用户通过命令行参数传入
Read() 一直卡住或返回 0 字节
Read() 默认阻塞,不设超时就是等一辈子 —— 尤其在硬件发得慢、帧不完整或线空闲时。
- 必须显式设置读超时:
serial.WithReadTimeout(100 * time.Millisecond) - 每次调用后务必检查
n, err:n == 0 && err == nil是正常空读;err == io.EOF往往是 USB 转串口芯片断连后残留句柄 - 协议带帧头(如
0x7E)或换行符结尾?别用固定长度Read(buf),改用bufio.NewReader(port).ReadBytes('\n')或自己拼包
Write() 返回成功但设备没响应
Go 层面 “写成功” 只代表数据进了内核缓冲区,不代表硬件真收到了。
立即学习“go语言免费学习笔记(深入)”;
- 确认终止符:很多设备要求命令结尾是
\r\n,你只写了[]byte("AT"),得改成[]byte("AT\r\n") - 查硬件手册:是否要 RTS/CTS 流控?
go.bug.st/serial中用RTSCTS: true开启,CH340/CP2102 类芯片通常需关掉 - 连续写多条命令?加
time.Sleep(10 * time.Millisecond)—— 部分低端 USB 转串口芯片无法承受高频写入 - 用串口调试助手抓一包,对比 Go 发出的 hex 和手动发送是否完全一致:少一个
0x0D、多一个0x00都可能失败
goroutine 并发读写串口时 panic 或丢数据
serial.Port 实例不是线程安全的,共用一个句柄并发读写必出问题。
- 绝对不要在多个 goroutine 里共享同一个
*serial.Port - 高频读写场景:用
sync.Mutex包一层,读写前mu.Lock(),完后mu.Unlock() - 低频场景更简单可靠:每个 goroutine 自己
Open()→ 操作 →Close(),只要注意别频繁开关 - 退出前必须
defer port.Close(),否则下次运行报device busy
真正容易被忽略的是:超时不是可选项,而是默认行为。不设 ReadTimeout 的串口程序,在真实硬件交互中大概率会卡死;而流控、终止符、写入节奏这些细节,往往比语法本身更能决定通信成败。


















