![63e8a51d41376272bcde114f399abf1d.png](https://i-blog.csdnimg.cn/blog_migrate/c1f8ab58aae0b0dea6bb353bcf720b44.jpeg)
我在调试工作中经常用到串口,为了实现自动化做了一些尝试,
下文总结了,操作串口涉及的动作如何自动化地执行的方法
主要由这以下几个话题:
1、创建串口对象,链接串口
2、经过串口收发字符串
3、串口打印解码(读取中英文打印)
4、等待串口出现某个字符串,计算等待时间
5、以十六进制形式向串口发送字符
6、以十六进制形式向串口发送某个文件
7、自动打开串口UI窗口,方便手动操作
8、自动启动 secureCRT 记录log
下面将实操的过程记录如下
一、创建串口对象
import serial
s_obj = serial.Serial(comPort, baudrate=115200, bytesize=8, parity='N', stopbits=1, timeout=1)
这里面 comPort 是串口的标志字符串,
比如在windows下是类似这样的文本"com1" , 在linux 下是类似这样的字符串 "/dev/ttyUSB1"
windows 下启动设备管理器查看 端口可以查看字符串
![b37a44dee19c00714a726a29a5b021a9.png](https://i-blog.csdnimg.cn/blog_migrate/1eac9806ad5139a1bf66f4dc1de70656.png)
linux 下查看可用的串口,使用如下命令查询
ls -l /dev/*
一般情况下,linux如果使用的是默认的串口,串口名称是dev下的ttyS* ,一般ttyS0对应com1,ttyS1对应com2
按照上面的写法,可以创建一个串口通信的对象,下面讲讲如何使用它
二、串口收发字符串
2.1 发送字符串
为了经过串口发送字符串,可以使用下面的写法
text = "pwd"
s_obj.write(text.encode())
这里需要用 write(data) 方法发送字符串命令,需要注意的是,pyserial的文档注明了,write的输入参数必须是bytes 格式的(也就是二进制数据),
这里说明一下,python3里对字符串和二进制数据流有明确的区分,文本总是unicode编码储存的,由str类型表示。二进制数据则由bytes类型表示
所以字符串数据需要encode()函数将其编码为二进制数据,然后才可以顺利发送。(encode方法默认的编码是UTF-8)
这里发送的字符一般是英文短语。后面会说到如何发送十六进制的数据
2.2 接收打印,然后解码
为了从串口接收字符串,可以使用下面的写法
s_obj = serial.Serial(comPort, baudrate=115200, bytesize=8, parity='N', stopbits=1, timeout=2)
data = s_obj.read(150*60)
print_out=data.decode('utf-8','replace')
print(print_out)
print_out=data.decode('gb18030','replace')
print(print_out)
这里先创建了一个serial对象,紧接着,读取数据data(是二进制数据)。
关于读取这里,说明一下,这里read函数的输入参数是 bytes 的数量 ,如果是UTF-8编码,1个数量可以表示一个英文字母,这里我写的非常大的一个值 150*60。原因有2:
- 第一个原因是,serial模块的官方文档里是这样写的,如果创建 serial 对象的时候,设置了timeout时间,则读取一个非常大数量的行为会变成,在timeout时间内,能读到多少就读取多少打印。相反,如果没有设置timeout,读取非常大的数量的行为就会让程序block住,一直等着串口打印出足数的字符才罢休。上面我设置了2秒的timeout,所以不会卡住,而是2秒内能收多少字符就收多少(2秒的时间是很长的),到了2秒就停止读取串口打印。
- 第二个原因是,这里讨论的场景是串口一次性打印很多数据,可以稍等几秒(比如等待3秒后再来读取数据,并不关心耗费的时间),一次读取大量字符符合这个场景的需求。
接下来对收到的 data 解码,
如果接收到的 data 全是英文,就可以用 utf-8 编码将二进制数据解码为unicode字符串。如果data里包含中文,则最好以 gb18030 编码将二进制数据解码为 unicode 字符串。
需要说明一点的是,
有时候由于带中文,但是强制用 utf-8 解码,或者由于串口的传输线缆出现接触不良等原因,会产生错误或者乱码,如果直接解码,就会报错,为了能够顺畅的解码串口打印,避免这种情况发送,decode的参数里加上 “replace” 即可。它实现的作用是,如果解码的过程中遇到错误,会自动以问号 ? 代替解码失败的字符。
三、计算打印耗时
有时候在接收串口打印的同时,还希望能知道等待了多久才收到某个打印
可以使用下面的写法
target_text
在这个例子里,使用了 while 循环来不停的尝试,尝试的语句是
if s_obj.in_waiting > 0
serial 对象的in_waiting 属性返回的是串口的接收buffer 收到的字节数,如果大于0表示有东西打印出来,然后就可以继续下面的动作:
开始一个子过程:使用 read(4)方法接收了4个字节,然后解码成 unicode 明文字符,再追加 ser_text_pool 这个字符串里,
最后判断目标字符串在不在 ser_text_pool 里面,子过程结束。
这里子过程里仅接收4个字节,有2个目的:
- 第一个目的是,设置读取量非常小,可以让一次读取时间尽量的小,这样的话,多次执行子过程就会“一点点”地累加收到串口的打印,每次都会判断有没有收到目标字符串,一旦发现目标字符串,就会计算耗时,这样得到的等待时间比较精确(实操证实了)。
- 如果设置为2个字符,又有点过短,小概率会出错(解码后累加后出现乱码导致有打印但是匹配不到),所以用4个字节最为合适
下面就是基本操作了,用逻辑语句分开找到和没有找到两种情况,如果需要将每种情况遇到的串口都可以打印出来。
四、以十六进制形式发送
4.1 以十六进制发送字符串
有部分时候,经过串口发送字符串的时候,希望以十六进制的形式发送,
可以用下面的写法实现
cmd = "ef aa 21 00 00 00 00 21"
ser_obj.write(bytes.fromhex(cmd))
可以看出来,这里面核心的过程是 将十六进制的str类型字符串转换为二进制的bytes类型数据,然后就可以发送了
4.2 以十六进制发送文件
如果发送的内容由简单的字符串,改为二进制文件,情况则稍有不同
可以使用下面的写法实现
def sendbin(ser_obj, ser_baudrate, file_obj,read_step_size=8, sleep_dividend=512, sleep_time=0.0005):
print("开始写入hex")
ser_obj.baudrate = ser_baudrate
i = 0
while 1:
c = file_obj.read(read_step_size)
ssss = str(binascii.b2a_hex(c))[2:-1]
if not c:
break
ser_obj.write(bytes().fromhex(ssss)) # 将16进制转换为字节
if i % sleep_dividend == 0:
time.sleep(sleep_time)
i += 1
print("写入完成")
这个代码的主要内容是:从一个文件里每次读取一点点数据,然后以16进制重新编码后写入串口,重复这个过程直到文件发完。
这其中,文件的读取 file.read(byte size) 方法的工作原理是,每次读取完指针就停留在末尾,第二次读取接着上次的指针的位置继续读。所以可以循环执行。
其中信息的传递的主要过程是:
- c = file.read() 从二进制数据的文件里,读取到变量c里,数据格式是bytes,表示方式是二进制
- binascii.b2a_hex(c) 这一步操作是将 bytes类型的二进制表示数据,转换为同为bytes类型的16进制表示数据。 输入数据的每个字节都被转换成相应的2位十六进制表示。因此,返回的bytes对象是输入数据长度的两倍。
- 之后的str(newC)[2:-1] 将16进制的表示,剥离掉了引号,提取出十六进制的明文字符串,此时输出类型为str
- 最后一步写入的时候是这样写的 bytes().fromhex(ssss) 这个动作是将一个十六进制表示的str 转换为二进制的bytes
以上就是信息传递和写入的一次过程,重复这个过程就可以实现持续的写入文件到串口。
实际上还有最后一个问题,真实的串口是有buffer的,不可能一直持续地写入,如果硬来,就会卡死。
所以需要串口每写一会儿要等待一会儿时间,给串口一个发送的消化时间,这里我写了等待了0.5毫秒,已经足够了,实际过程是每发送512次后停0.5毫秒,接着继续发送,直到读文件结束。
五、自动打开串口UI工具
调试工作中,有时候需要半自动化的操作串口,为人工介入操作提供方便。
一个实现的思路如下:
# -*- coding: utf-8 -*-
import os
import time
import serial.tools.list_ports as lps
def change_ini_and_open_sscom(number, comport):
print([comport])
ui_width = 373
with open(r'D:toolssscomsscom51.ini',"r") as f:
lines = f.readlines()
with open(r'D:toolssscomsscom51.ini',"w") as f_w:
for line in lines:
# 程序启动自动打开串口
if 'N1063=' in line:
line = line.replace(line,'N1063=,Yn')
# 修改串口号
if 'N1080=' in line:
line = line.replace(line,'N1080=,{0}n'.format(comport))
#设置窗口高度
if 'N1083=' in line:
line = line.replace(line,'N1083=,826n')
#设置窗口最高点的坐标
if 'N1085=' in line:
line = line.replace(line,'N1085=,3n')
#设置窗口宽度
if 'N1082=' in line:
line = line.replace(line,'N1082=,%sn' % ui_width)
#窗口左边坐标
if 'N1084=' in line:
line = line.replace(line,'N1084=,%sn' % (ui_width*number) )
f_w.write(line)
p = os.popen(r'D:toolssscomsscom5.13.1.exe')
time.sleep(1)
def find_all_port_and_open_them():
port_list = list(lps.comports())
if len(port_list) == 0:
print('找不到串口')
else:
for i in range(0,len(port_list)):
port_str = str(port_list[i]).split(' -')[0]
if port_str != "COM1":
change_ini_and_open_sscom(i,port_str)
if __name__ == '__main__':
find_all_port_and_open_them ()
该代码是用于驱动 sscom 串口工具,可以方便的一键打开多个串口UI窗口,方便快速打开多个UI窗口,辅助手动调试。
六、自动启动 secureCRT 记录log
如果希望长时间的监控串口,虽然可以使用python的pyserial包来编写一个程序实现,但是最稳妥的方法还是调用成熟的第三方工具来做,至少不容易崩。
如果还希望实现某种自动化,还是否可行呢?答案是肯定的
一个实现的思路如下(类似sscom的启动方式,但是附带了log):
comman_log_name = '%s_print.log'
def setupSecureCRT(baudrate,comport,secureCRTpath,logpath,tabName,windowsName='wrong-wake-test'):
cmd = f"{secureCRTpath} /POS 50 100 /LOG {logpath} /N {tabName} /TITLEBAR {windowsName} /SERIAL {comport} /BAUD {baudrate}"
mprocess = subprocess.Popen(cmd)
portpool = 33,34,35
baudrate = 115200
secureCRTpath = "C:SecureCRTSecureCRT.exe"
for i in range(len(portpool)):
comport = 'com' + portpool[i]
tabName = tagpool[i]
logpath = comman_log_name % comport
tabname = "tab_%s" % comport
time.sleep(4)
p = Process(target=setupSecureCRT,args=(baudrate,comport,secureCRTpath,logpath,tabName))
p.start()
以上code是运行于windows平台的,能够自动启动多线程驱动打开串口UI(通过命令行启动 vandyke公司的secureCRT 软件),可以实现同时记录多个串口,并指定log路径的目的,可用于自动化长时间监控操作。
如果希望记录的log里带时间戳
这样设置
![f7444f37b27d89199be58297f02398bc.png](https://i-blog.csdnimg.cn/blog_migrate/077e56d8feb9cb75ebfcffd458fbd0d5.png)
选择 编辑默认session
![8c2ad457202daf12ae66556f6c27b387.png](https://i-blog.csdnimg.cn/blog_migrate/6fc5e2be271a9b156eae521f6794ff33.jpeg)
如上图勾选,上start log upon connect ,然后如图写上
on each line : [%h:%m:%s.%t]---
这样设置后,再运行上面的python脚本启动它,就可以自动保存带时间戳的log了。
欢迎任何人或组织转载本专栏文章,转载请注明出处【知乎-GooD白-测试脚本】