串口调试需分平台处理权限与端口:linux加dialout组,macos选/cu.*,windows用全大写comx;设read超时、检查n/err、匹配波特率与帧格式;启用流控、添加终止符、控制写入间隔;避免goroutine共用端口,及时close并捕获信号。

串口打开失败:Permission denied 或 device not found
Linux/macOS 下常见权限问题,Windows 下多是端口号写错或驱动没装。Go 的 serial.Open 不会自动提权,也不会帮你枚举可用端口。
- Linux 上先用
ls /dev/tty*确认设备名(如/dev/ttyUSB0),再加当前用户到dialout组:sudo usermod -a -G dialout $USER,然后重新登录 - macOS 用
ls /dev/cu.*,注意别选/dev/tty.*开头的(它可能被系统占用) - Windows 要写全路径,比如
COM3,不能只写com3或\.COM3(go-serial库内部已处理前缀) - 别硬编码端口,用
github.com/tarm/serial的GetPortsList()先探测,或让用户传参
读不到数据:Read timeout 一直返回 0 或 EOF
不是串口没发,而是 Go 默认阻塞读 + 没设超时,或者硬件发得慢、帧不完整,导致 Read() 卡住或提前返回。
- 务必在
serial.Config中设置Timeout: 100 * time.Millisecond,否则Read()可能永远等下去 - 不要直接
Read(buf)就完事,要检查返回的n, err:如果n == 0 && err == nil是正常空读;err == io.EOF常见于某些 USB 转串口芯片断连后残留句柄 - 协议有帧头帧尾的,别用固定长度读,改用
bufio.Reader配合ReadBytes()或自己循环拼包 - 波特率必须和硬件严格一致,差一点就全乱码——查清设备文档,别凭印象写
BaudRate: 9600
write 后没响应:数据发出去但设备无反应
大概率是电平、握手信号或终止符不匹配,Go 层面看 Write() 返回成功,不代表硬件真收到了。
- 确认硬件是否需要 RTS/CTS 流控:
serial.Config里RtsCts: true或DtrDsr: true得按设备手册开/关 - 很多设备要求命令结尾带
,而你只写了"AT"——试试[]byte("AT ") - 有些芯片(尤其 CH340)对写入间隔敏感,连续
Write()之间加time.Sleep(10 * time.Millisecond)再试 - 用逻辑分析仪或串口调试助手抓一包对比:Go 发的和手动发的 hex 是否完全一致?少一个字节、多一个 0x00 都可能失败
goroutine 安全与资源泄漏:程序退出后串口还占着
serial.Port 是非线程安全的,且没被 Close() 就退出,下次运行会报 device busy。
立即学习“go语言免费学习笔记(深入)”;
- 绝不在多个 goroutine 里共用一个
serial.Port实例;要并发读写,用sync.Mutex包一层,或每个 goroutine 自己开闭端口(低频场景可行) -
defer port.Close()必须紧跟serial.Open()之后,别写在函数末尾——万一中间 panic 就漏关 - 程序收到
SIGINT(Ctrl+C)时要主动Close(),用os.Signal.Notify()捕获并优雅退出 - Windows 下曾有 bug:进程崩溃后句柄未释放,需手动在设备管理器里禁用再启用 COM 口;Linux/macOS 则依赖内核自动回收,但延迟可能达数秒
串口最麻烦的从来不是 Go 代码写不对,而是硬件状态不可见、协议文档写得模糊、示波器又没带在身边。调不通的时候,先拿别的工具发一遍,确认线和设备没问题,再回来盯 Go 里的 Config 字段和 Read 返回值。










