python串口发送十六进制数(python串口通信的接收与发送)

我在调试工作中经常用到串口,为了实现自动化做了一些尝试,

下文总结了,操作串口涉及的动作如何用python自动化地执行的方法

主要有这几个话题:

1、创建串口对象,链接串口

2、经过串口收发字符串

3、串口打印解码(读取中英文打印)

4、等待串口出现某个字符串,计算等待时间

5、以十六进制形式向串口发送字符

6、以十六进制形式向串口发送某个文件

7、自动打开串口UI窗口,方便手动操作

8、自动启动 secureCRT 记录log

我将实操的过程一一总结如下

一、创建串口对象

如下的几个部分,是依赖 pyserial 这个第三方库来实现

import serials_obj=serial.Serial(comPort, baudrate=115200,bytesize=8,parity=’N’,stopbits=1,timeout=1)

这里面 comPort 是串口的标志字符串,比如在windows下 是 “com1” , 在linux 下是类似这样的字符串 “/dev/ttyUSB1”

windows 下启动设备管理器查看 端口可以查看字符串

python串口发送十六进制数(python串口通信的接收与发送)

linux 下查看可用的串口,使用如下命令查询

ls -l /dev/*

一般情况下,linux如果使用的是默认的串口,串口名称是dev下的ttyS* ,一般ttyS0对应com1,ttyS1对应com2

按照上面的写法,可以创建一个串口通信的对象,然后再对这个串口对象操作,即可实现收发字符串的目的。

其余参数不再赘述,详见我之前的一篇文章。

timeout参数非常有用,下面会说到。

二、串口收发字符串

2.1 发送字符串

为了经过串口发送字符串,可以使用下面的写法

text = “pwd” s_obj.write(text.encode())

用write(data)方法发送字符串命令,需要注意的是,pyserial的文档写明了,发送的输入元素必须是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 = “我是目标打印” find_time_out = 8 start_time = time.time() ser_text_pool = ” s_obj = serial.Serial(comPort, baudrate=115200, bytesize=8, parity=’N’, stopbits=1, timeout=2) while 1: if s_obj.in_waiting > 0: try: data = s_obj.read(4) ser_text_pool = data.decode(‘utf-8′,’replace’) except: continue check1 = time.time() time_delta = round(check1 – start_time,2) if target_text in ser_text_pool: try: data = s_obj.read(1000) ser_text_pool = data.decode(‘utf-8′,’replace’) except: pass print(“==================== 很好,找到[%s] 耗时[%s]秒===” % (target_text,time_delta)) break else: check_time = time.time() time_cost = round(check_time – start_time,2) if time_cost > find_time_out: print(“== 糟糕!%s秒内,没有找到[%s]” % (find_time_out,target_text)) print(“============== 收到串口打印如下 ==============”) print(ser_text_pool) print(“============== 串口打印 END ==============”) sys.exit() else: check_time = time.time() time_cost = check_time – start_time if time_cost > find_time_out: print(“== 糟糕!%s秒内,没有找到[%s]” % (find_time_out,target_text)) print(“============== 收到串口打印如下 ==============”) print(ser_text_pool) print(“============== 串口打印 END ==============”) sys.exit()

在这个例子里,使用了 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 osimport timeimport serial.tools.list_ports as lpsdef change_ini_and_open_sscom(number, comport): print([comport]) ui_width = 373 with open(r’D:\tools\sscom\sscom51.ini’,”r”) as f: lines = f.readlines() with open(r’D:\tools\sscom\sscom51.ini’,”w”) as f_w: for line in lines: # 程序启动自动打开串口 if ‘N1063=’ in line: line = line.replace(line,’N1063=,Y\n’) # 修改串口号 if ‘N1080=’ in line: line = line.replace(line,’N1080=,{0}\n’.format(comport)) #设置窗口高度 if ‘N1083=’ in line: line = line.replace(line,’N1083=,826\n’) #设置窗口最高点的坐标 if ‘N1085=’ in line: line = line.replace(line,’N1085=,3\n’) #设置窗口宽度 if ‘N1082=’ in line: line = line.replace(line,’N1082=,%s\n’ % ui_width) #窗口左边坐标 if ‘N1084=’ in line: line = line.replace(line,’N1084=,%s\n’ % (ui_width*number) ) f_w.write(line) p = os.popen(r’D:\tools\sscom\sscom5.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,35baudrate = 115200secureCRTpath = “C:\SecureCRT\SecureCRT.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里带时间戳

这样设置

如上图勾选,上start log upon connect ,然后如图写上

on each line : [%h:%m:%s.%t]—

这样设置后,再运行上面的python脚本启动它,就可以自动保存带时间戳的log了。

发表评论

登录后才能评论