问题描述
今天在设计python串口代码时遇见一个问题,接收到的数据打印出来,一直都是两三组连在一起,即我的目标数据是:
7e307d02087d01557e
但是我把接收到的数据经过处理打印出来后是:7e307d02087d01557e7e307d02087d01557e
或者打印结果是:7e307d02087d01557e7e307d02087d01557e7e307d02087d01557e
额。。。这显然不是我需要的结果了。
解决方式
先说下我的思路,首先引入getopt库,实现串口的串口号和波特率可以由命令行参数改变,这点说实话有些鸡肋。
test_param = {"com": "com1", "baudrate": "3000000"}
# 尝试获取命令行参数,c->指定的com口,b->指定的波特率,s->secret_file,t->test_file
try:
options, argv = getopt.getopt(sys.argv[1:], "c:b:")
except getopt.GetoptError:
sys.exit()
# 根据命令行的参数内容,调整字典中键值对应的value
for option, value in options:
if option in ("-c", "--comname"):
test_param['com'] = value
print("com: {0}".format(test_param['com']))
if option in ("-b", "--baudrate"):
test_param['baudrate'] = value
print("baudate : {0}".format(test_param['baudrate']))
uartmanage = TUartManage(test_param["com"], test_param['baudrate'])
其次是构建python的pyserial串口功能类TUartManage,主要是有两个参数,串口号和波特率。
在进行类的初始化时,主要是建立了一个**bytearray()**数组,因为这个串口程序最终是要服务于协议栈使用,所以根据需求决定的,但是影响说实话不大,因为python想要做一些类型、格式等等的改变真的是太容易了。
class TUartManage(object):
def __init__(self, com, baudrate):
self.__com = com
self.__baudrate = baudrate
self.__read_flag = False
self.data_bytes = bytearray()
self.__com_uart = serial.Serial(port=self.__com, baudrate=self.__baudrate, timeout=0.1)
if self.__com_uart.is_open:
print('open ' + self.__com + ' success !!!')
此外,self.__com_uart是建立的串口对象,在这里是打开串口一,并给定了一个波特率,波特率最后说给了多少。
看下下面的实现程序:
def __read_data(self):
'''
在线程内循环接收数据,数据发送端选用的正点原子的串口调试工具,最大支持3000K的波特率,所以
本程序实时性实测在波特率为3000ke时接收正常。
:return: None
:note 数据缓存在self.data_bytes中,可由外界直接调用获取
'''
while self.__com_uart.isOpen() and self.__read_flag:
if self.__com_uart.in_waiting:
self.data_bytes = self.__com_uart.read(self.__com_uart.in_waiting)
if self.data_bytes[0] == 0x7e:
data_end = self.data_bytes.find(0x7e, 1)
self.data_bytes = self.data_bytes[0:data_end+1].hex()
self.__com_uart.reset_input_buffer()
def com_start_read(self, rx_size=1*1024*1024):
"""
设置接收buf的size,并创建一个线程,专门用来实时接收数据
:param rx_size: 默认设置1M的接收缓存
:return: None
"""
self.__com_uart.set_buffer_size(rx_size=rx_size)
self.__read_flag = True
read_thread = threading.Thread(target=self.__read_data)
read_thread.setDaemon(True) #守护进程,自动运行结束退出,或者主进程结束时,自动结束
read_thread.start()
先来说下额外的知识点:
如果在一个类中,想要将一些参数、函数设置为私有属性,即类似于C语言的static变量、函数这样子的,只可以在本文件内使用,外界无法查看与调用,这时可以采用在首字母前加两个下划线的形式,在本文程序内均有涉及,关于私有属性的知识点,最近会整理下,感兴趣可以翻一下博客。
继续来说下上面的程序,如果想读取数据时可以调用com_start_read函数,可以通过此函数设置serial的接收缓存大小,默认为1M。此函数建立一个带守护属性的线程,即此线程退出方式有两种,一是函数运行完成,自然退出,但是考虑到串口读取函数一定是需要一直读取的,所以退出时一般是利用他的守护属性,即当主线程退出时,也会结束守护线程。
真正的串口读取函数其实是:__read_data函数,但是你也可以发现,他是一个具有私有属性的函数,所以外界调用时是看不见的。此函数是以:
while self.__com_uart.isOpen() and self.__read_flag:
来作为循环判断依据的,所以此函数正常退出时,会有两种方式,一是串口被关闭,条件不成立,会立即结束循环,二是将允许读串口标志位self.__read_flag变为False,这部分贴上来的代码上没有,因为我还没写,其实就是再建立一个com_stop_read函数,在里面将允许读串口标志位self.__read_flag变为False。
至于这么做的目的当时是为了适应协议在不同时刻需要设置不同的缓存size问题,还有就是移植性会比较好的。
最后再来说一下最初的问题,怎么提高串口的实时性,其实我当时犯的错误是,每次都从接收缓存内读取数据,但是一直没有对其进行清除,所以会带着上次的数据,造成实时性不好的假象,所以使用:
self.__com_uart.read(self.__com_uart.in_waiting)
来读串口数据时,每次读完一定要在最后对其进行清零,可别在最开始清零,不然你容易得到一个空的数据。
还有就是测试时,使用的是正点原子的XCOM串口工具,最高支持3000K波特率,所以本程序测试时给定的波特率为3000K,这实时性还不够好吗。
以前在校园时最常用的是9600和115200,目前看见的实用的也不过250K和500K,所以3000K够快啦。
代码还没有完善,没做com_stop_read函数,不过也是非常简单的,我把代码贴在最后,感兴趣可以自己看下,有好的建议也可以沟通交流。
额外的:
安装serial时,经常理所当然的使用:
pip install serial
但是运行时会发现提示serial中没有Serial,这是因为按的不对,真正的该执行的应该是:
pip install pyserial
祝君好运,python串口玩得愉快
代码如下:
# coding=utf-8
# @Time : 2020.12.19
# @Author : QiQiuyang
# @File : uart_manage.py
# @note : 实现python端串口多线程实时收发
import serial #pip install pyserial
import sys
import getopt
import threading
class TUartManage(object):
def __init__(self, com, baudrate):
self.__com = com
self.__baudrate = baudrate
self.__read_flag = False
self.data_bytes = bytearray()
self.__com_uart = serial.Serial(port=self.__com, baudrate=self.__baudrate, timeout=0.1)
if self.__com_uart.is_open:
print('open ' + self.__com + ' success !!!')
def __read_data(self):
'''
在线程内循环接收数据,数据发送端选用的正点原子的串口调试工具XCOM,最大支持3000K的波特率,所以
本程序实时性实测在波特率为3000ke时接收正常。
:return: None
:note 数据缓存在self.data_bytes中,可由外界直接调用获取
'''
while self.__com_uart.isOpen() and self.__read_flag:
if self.__com_uart.in_waiting:
self.data_bytes = self.__com_uart.read(self.__com_uart.in_waiting)
if self.data_bytes[0] == 0x7e:
data_end = self.data_bytes.find(0x7e, 1)
self.data_bytes = self.data_bytes[0:data_end+1].hex()
self.__com_uart.reset_input_buffer()
def com_start_read(self, rx_size=1*1024*1024):
"""
设置接收buf的size,并创建一个线程,专门用来实时接收数据
:param rx_size: 默认设置1M的接收缓存
:return: None
"""
self.__com_uart.set_buffer_size(rx_size=rx_size)
self.__read_flag = True
read_thread = threading.Thread(target=self.__read_data)
read_thread.setDaemon(True) #守护进程,自动运行结束退出,或者主进程结束时,自动结束
read_thread.start()
def com_stop_read(self):
'''
将允许读串口的标志位置False,这个操作会使read_data函数,自然结束读取数据,退出线程
:return: None
'''
self.__read_flag = False
if __name__ == "__main__":
# 字典
test_param = {"com": "com1", "baudrate": "3000000"}
# 尝试获取命令行参数,c->指定的com口,b->指定的波特率,s->secret_file,t->test_file
try:
options, argv = getopt.getopt(sys.argv[1:], "c:b:")
except getopt.GetoptError:
sys.exit()
# 根据命令行的参数内容,调整字典中键值对应的value
for option, value in options:
if option in ("-c", "--comname"):
test_param['com'] = value
print("com: {0}".format(test_param['com']))
if option in ("-b", "--baudrate"):
test_param['baudrate'] = value
print("baudate : {0}".format(test_param['baudrate']))
uartmanage = TUartManage(test_param["com"], test_param['baudrate'])
uartmanage.com_start_read()
while True:
print(uartmanage.data_bytes)