Linux 下串口收发报文踩坑汇总
develop
顺便记录一些在简中互联网上难以找到的信息 :(
开发工具
串口通信软件
minicom
, picocom
均可。笔者使用 minicom
。这软件使用前最好预先配置好。(准确说任何软件使用前都需要配置,就算是网页浏览器你也要先登录才能同步密码和历史记录,这也是一种使用前的配置)
配置文件是 ~/.minirc.dl
:
# Machine-generated file - use setup menu in minicom to change parameters.
pu escape-key ^Q
pu rtscts No
pu port /dev/ttyUSB0
pu logfname /tmp/minicom.log
这配置是机器生成的文件。可以在 minicom
的界面通过用户界面选择合适的选项生成的。这软件设计上的坑在哪里呢(100% 是为了历史兼容才这么设计)
-
minicom XXX
里的XXX
是配置文件而非你要通信的串口名。也就是说直接输入minicom
就是minicom ~/.minirc.dl
。要直接使用哪个串口必须minicom -D serial_name
。这个命令行选项设计反直觉。正常都是program -c config_file XXX
才对 - 默认使用的串口名是
/dev/modem
。这绝对不合理,现在的计算机哪有调制解调器啊?以现在 USB 的普及程度,笔者敢打赌绝大多数的开发者一定是购买一个用 PL 2302 或 CP 2102 之类的芯片做的 USB 转 TTL 适配器再连接开发板上的 TTL 串口,所以默认串口名设计成用适配器对应的/dev/ttyUSB0
才合理呀 - 默认开启硬件流控制,如果是
/dev/modem
这倒没问题,但如果是/dev/ttyUSB0
就需要禁用 RTS CTS 。否则不会有任何输出 - 快捷键一般会用英文首字母,比如偶检验位是 E ,校验位恒为空白电平(表示 1 的电平)用 S 。而
minicom
直接按英文字母顺序排列,很容易让人误以为无校验位是 N ,结果误按下奇校验位的快捷键。
+---------[Comm Parameters]----------+
| |
| Current: 500000 8N1 |
| Speed Parity Data |
| A: <next> L: None S: 5 |
| B: <prev> M: Even T: 6 |
| C: 9600 N: Odd U: 7 |
| D: 38400 O: Mark V: 8 |
| E: 115200 P: Space |
| |
| Stopbits |
| W: 1 Q: 8-N-1 |
| X: 2 R: 7-E-1 |
| |
| |
| Choice, or <Enter> to exit? |
+------------------------------------+
一些有用的功能可以选择开启,比如:
- 串口中换行是
\r\n
,可以让串口通信软件把接收的每一个\n
都先添加\r
。 - 在收到的某一个消息前加上时间戳。
- 回传:将收到的信息再发回去
权限
需要把用户加入 dialout 组:
GNU/Linux:
sudo gpasswd -a wzy dialout
NixOS: /etc/nixos/configuration.nix
:
users.users.wzy.extraGroups = [ "dialout" ];
逃逸键
在串口通信软件中,输入任何字符都会被发送给串口,即使是 Ctrl 和 Alt 也不例外。因为 Ctrl + A ~ Ctrl + Z 会被映射到 A~Z 的 ASCII 编码 + 0x80 再对 0x100 取余的不可见 ASCII 字符上,这从 DEC 公司的 VT 系列电脑就流传下来的设定主打的就是一个能让用户的键盘输入任何 ASCII 字符 :)
,而 Alt 被用来模拟古早计算机上还存在的 Meta 上,输出 Ctrl +\[ 也就是 Esc (\x1B
),简单说就是 Ctrl + Alt + A 等同于以极快的速度依次输入 Esc, Ctrl + A 。因此,需要一个类似转义字符的逃逸键,连续输入 2 次逃逸键才是真正输入逃逸键,输入单次逃逸键再加一个按键可以执行某个指令(例如退出)。此设定在以下软件中亦有体现:
- 远程登录:
ssh
,rsh
,mosh
- 容器:
docker
- 终端分屏:
tmux
,screen
应用
笔者持有一部路由器,大二时希望重新刷入固件支持更高级的功能。当时的操作如下:
- 拆开路由器并找到 RX 和 TX
- 使用 PL 2302 将路由器的 RX 和 TX 连接到 PC 的 USB
- 使用电缆将 PC 的 RJ 45 连接到路由器的 WAN
- 安装 tftp ,并将编译好的 OpenWrt 固件放到 tftp 根目录下
- 使用 minicom 连接路由器
- 根据屏幕提示将固件刷入路由器
- 重启
- (可选)在 PC 上登录 192.168.1.1 进入路由器设置界面,其地址更改为 192.168.1.2,因为笔者还有另一个路由器,其地址被设置 192.168.1.1。 使用网线将该路由器的 LAN 连接到另一个路由器的 LAN 可以让他们共享同一个网络,这意味着:你在客厅,也就是你连接的第一个路由器, 现在你回到你的房间,第一个路由器的信号减弱,第二个路由器的信号减弱路由器信号增强,手机连接第二个路由器。 虽然使用了不同的 AP (路由器),你的 IP 不会改变,因为它们是相同的网络。
顺带一提 OpenWrt 打包了少量开发用的软件。可能是供用户快速修改配置文件的需要?
ssh 192.168.1.2
opkg install vim-tiny
opkg install python
虚拟串口
串口开发的程序最终一定要跑在 2 台通过串口连接的设备上的。在开发阶段,我们可以先在个人电脑上创建 2 个互相连接的虚拟串口进行调试,等调试通过再部署到 2 台通过串口连接的设备。之所以叫虚拟串口,是因为只有串口对应的字符设备,没有真正的物理设备。
笔者使用的虚拟串口软件是 socat
:
socat pty,rawer,link=/tmp/ttyS0 pty,rawer,link=/tmp/ttyS1
然后就可以使用 /tmp/ttyS0
和 /tmp/ttyS1
这 2 个互相连接的串口了。
一些注意事项:
- 先关闭使用虚拟串口的软件,再关闭创建虚拟串口的软件,否则使用虚拟串口的软件会在关闭时读到错误的信息。
串口编程
参考:
即可。
每次只能读取一个字符
当一个串口设备的写速度慢于电脑读的速度时就会出现每次只能读取一个字符的问题: https://stackoverflow.com/questions/32537792/why-i-only-get-one-character-at-one-time-when-i-use-read
最好的解决方法就是提高波特率,这样就不用写 for
循环检测字符到底有没有读完了。
设置不了更高的波特率
termios.h
代表波特率的宏最高也只有 B38400
。因为 POSIX 标准只规定到这个波特率。更高的波特率通过 asm/termios.h
获得。
当 2 个文件同时导入时会有冲突。 https://stackoverflow.com/a/48521433/16027269 给了解决方案:
#define termios asmtermios
#include <asm/termios.h>
#undef termios
#include <termios.h>
标题用的报文。似乎有的开发者管这叫帧、报文?个人感觉按 OSI 模型,UART 在数据链路层,串口通信没有网络层(不存在通过路由器在广域网间接通信的设备)也没有传输层(一对一通信不需要考虑连接),所以开发自己设计的协议应该是在应用层工作,所以个人感觉叫报文可能更合适一点?