Linux下生成固定USB转串口设备节点

以USB转串口设备通常的设备节点名为ttyUSBx(x为0~n),Linux内核会根据设备插入的先后顺序进行编号的分配,比如第一个插入的设备编号为ttyUSB0,然后依此加1,变为ttyUSB1,ttyUSB2……

如果仅仅以设备节点ttyUSBx来区别具体是哪个设备,因为末位的编号是随时会变的,所以就会造成混乱。无法保证A设备就是ttyUSB0,B设备就是ttyUSB1。在设备文件/dev目录下并没有提供固定显示ttyUSB的方法,但是,每个USB端口都有唯一的端口号,相当于每个门店的门牌号。只要我们依据端口号来进行设备的区分,那么问题就迎刃而解了。简单点来说就是找到端口号,然后根据端口号找到挂载在这个端口号上面的USB设备是ttyUSB0还是 ttyUSB1即可

端口号

通过以下命令可以查看端口号:

1
2
3
ls -l /sys/class/tty/ttyUSB*
lrwxrwxrwx 1 root root 0 6月 25 16:02 /sys/class/tty/ttyUSB0 -> ../../devices/pci0000:00/0000:00:14.0/usb1/1-1/1-1:1.0/ttyUSB0/tty/ttyUSB0
lrwxrwxrwx 1 root root 0 6月 25 16:03 /sys/class/tty/ttyUSB1 -> ../../devices/pci0000:00/0000:00:14.0/usb1/1-2/1-2:1.0/ttyUSB1/tty/ttyUSB1
  • ttyUSB0所在的端口号为1-1
  • ttyUSB1所在的端口号为1-2

可以看出,这里的1-1端口上比1-2上提前插上USB设备,所以会以这种方式命名。如果插入设备的顺序相反

1
2
3
ls -l /sys/class/tty/ttyUSB*
lrwxrwxrwx 1 root root 0 6月 25 16:10 /sys/class/tty/ttyUSB0 -> ../../devices/pci0000:00/0000:00:14.0/usb1/1-2/1-2:1.0/ttyUSB0/tty/ttyUSB0
lrwxrwxrwx 1 root root 0 6月 25 16:10 /sys/class/tty/ttyUSB1 -> ../../devices/pci0000:00/0000:00:14.0/usb1/1-1/1-1:1.0/ttyUSB1/tty/ttyUSB1
  • ttyUSB0所在的端口号为1-2
  • ttyUSB1所在的端口号为1-1

方法一:bash+python

基本思路:

  • 第一次上电的时候确定哪个端口上的数据是我们所需要的
  • 以后每次上电,我们要找目标端口后面挂载的ttyUSB设备是ttyUSB0还是ttyUSB1,并建立一个软链接(ttyUSBx --> ttydata),以后每次打开/dev/ttydata即可

cmd.sh:利用bash脚本获取/sys/class/tty/ttyUSB*的一些信息保存在device_usb.txt中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#!/bin/bash
declare -i a=0
declare -i b=0
while [[ ! -e "/sys/class/tty/ttyUSB0" ]]
do
sudo sleep 0.01s
a=a+1
if [ $a -eq 300 ];then #等待一段时间没有检测ttyUSB0设备到会自动跳出while
break
fi
done

while [[ ! -e "/sys/class/tty/ttyUSB1" ]]
do
sudo sleep 0.01s
b=b+1
if [[ $b -eq 300||$a -ne 0 ]];then #if USB0 been detected ,also get out of while
break
fi
done


if [[ ! -e /sys/class/tty/ttyUSB0 && ! -e /sys/class/tty/ttyUSB1 ]]; then #如果不存在ttyUSB设备
echo "Not have ttyUSB0 or not have ttyUSB1"
else #如果完美检测到了两个ttyUSB设备,则将信息log到device_usb.txt当中
tty1=$(ls -l /sys/class/tty/ttyUSB0)
tty2=$(ls -l /sys/class/tty/ttyUSB1)

sudo ls -l /sys/class/tty/ttyUSB0 /sys/class/tty/ttyUSB1 > ./device_usb.txt
fi

if [ ! -n "$tty1" ] ;then # "! -n" shows blank var #非空检测
echo "tty1 is empty"
fi
#delay 0.01s to make sure the device_usb.txt complete
sudo sleep 0.01s
#remove the old USB device shortcut

if [ ! -e "/dev/ttydata" ] ;then # 如果/dev/ttydata本身不存在
echo "-------------/dev/ttydata not found"
else #如果存在,则需删除之,然后重新创建之
echo "/dev/ttydata is exist"
sudo rm /dev/ttydata
fi

#exct Python language to get the rignt USB interface
./getUSB.py #调用当前路径下的getUSB.py这个Python语言,获得目标端口

usbdev=$(cat ./usbdev) #获取到这个设备
echo "the device is : "
echo $usbdev

sudo ln -s /dev/$usbdev /dev/ttydata #将这个设备软连接到/dev/ttydata以后每次打开这个ttydata即可

getUSB.py:通过device_usb.txt中的信息,获取到当前挂着在目标端口上的是ttyUSB0还是ttyUSB1并保存在usbdev 中

1
2
3
4
5
6
7
8
9
10
11
12
#!/usr/bin/python
#coding:utf-8
import re #正在表达式
sss = open("./device_usb.txt","rb") #打开device_usb.txt设备,并读取内容
www = open("./usbdev","wb") #当前路径下创建usbdev文件,后续会写入内容
s_read = sss.read()
r = r"usb1/1-1/1-1.+?(ttyUSB[0-9])"
#这个规则是找到usb1/1-1/1-1/这个字符串后面紧跟的是此次上电生成的ttyUSB0或者ttyUSB1
output = re.findall(r,s_read)
www.write(output[0]) #将结果写到usbdev中
www.close()
sss.close()

完成之后设置开机即可

ubuntu下开机启动脚本

/etc/rc.local 文件

Ubuntu 会在启动时自动执行 /etc/rc.local 文件中的脚本,默认该文件中有效的脚本代码为空,把需要执行的脚本添加到该文件的 exit 0 之前即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/bin/sh -e
#
# rc.local
#
# This script is executed at the end of each multiuser runlevel.
# Make sure that the script will "exit 0" on success or any other
# value on error.
#
# In order to enable or disable this script just change the execution
# bits.
#
# By default this script does nothing.
cd /home/ubuntu
echo 'hello,world' >> rc.local.log
exit 0
update-rc.d 命令

Ubuntu 服务器在启动时会自动执行/etc/init.d目录下的脚本,所以我们可以将需要执行的脚本放到/etc/init.d目录下,或者在该目录下创建一个软件链接指向其他位置的脚本路径,然后通过update-rc.d将脚本添加到开机自启动。启动脚本必须以 #!/bin/bash 开头

例子:新建开机启动脚本 start_when_boot,放置到 /etc/init.d 目录:

1
2
3
4
#!/bin/bash
cd /home/ubuntu
date >> boot.log
echo 'hello, world' >> boot.log

执行 update-rc.d start_when_boot defaults 将上述脚本添加为开机启动;
执行 update-rc.d -f start_when_boot remove 将上述开机启动脚本移除;

方法二:udev

linux中设备号一般按先后顺序一次向后增大,udev规则文件可以解决这个问题。udev是一种Linux2.6内核采用的/dev目录的管理系统(windows中的设备管理器)。通过从sysfs获得的信息,可以提供对特定设备的固定设备名。sysfs是Linux 2.6定的设备名, 根据Wirting udev rules的详细介绍, udev有如下功能:

  • 将设备节点从默认名称重命名为其他名称
  • 通过创建指向默认设备节点的符号链接,为设备节点提供备用/永久名称
  • 根据程序输出命名设备节点
  • 更改设备节点的权限和所有权
  • 在创建或删除设备节点时启动脚本(通常在连接或拔出设备时)
  • 重命名网络接口

创建文件/etc/udev/rules.d/10-local.rule(默认的规则配置文件存放在/etc/udev/rules.d/中,默认生效顺序为从小数字到大数字), 内容如下:

1
2
KERNEL=="ttyUSB*", ATTRS{idVendor}=="067b", ATTRS{idProduct}=="2303", MODE:="0777", SYMLINK+="user_uart"
KERNEL=="ttyUSB*", ATTRS{idVendor}=="1a86", ATTRS{idProduct}=="7523", MODE:="0777", SYMLINK+="mcu_uart"
  • 匹配sys中内核名为ttyUSB*的设备,属性匹配依据生产商编号idVendor和产品号idProduct, 设定读写权限为0777, 符号链接名为user_uart(PL2303串口转USB)和mcu_uart(CH340串口转USB)
  • idVendoridProduct可以由lsusblsusb -vvv命令查看

如果两个串口用同一厂家的则会无效。如都是PL2303或者都是CH340/CP2102

保存退出后udev规则就生效了,重新拔插两个串口设备,就可以看到/dev/user_uart指向/dev/ttyUSB0, /dev/mcu_uart指向/dev/ttyUSB1

udev常用的匹配类型:

  • BUS:匹配总线类型,比如PCI USB等

  • KERNEL:匹配Kernel设备名,比如hda hdb

  • DRIVER:匹配Kernel的驱动程序名

  • SUBSYSTEM:匹配子系统名

  • ID:匹配总线系统的ID (e.g. PCI bus ID)

  • PLACE:匹配物理位置 (对USB很有用)