通常来说,许多传感器是通过串口进行数据传输的。串口通信(Serial Communications)的概念非常简单,串口按位(bit)发送和接收字节。尽管比按字节(byte)的并行通信慢,但是串口可以在使用一根线发送数据的同时用另一根线接收数据。它很简单并且能够实现远距离通信。串口通信使用3根线完成,分别是地线、发送、接收。由于串口通信是异步的,端口能够在一根线上发送数据同时在另一根线上接收数据。其他线用于握手,但不是必须的。串口通信最重要的参数是波特率、数据位、停止位和奇偶校验。对于两个进行通信的端口,这些参数必须匹配的[1]。
串口通信虽然简单,但是在处理如GNSS/惯导紧组合场景下,数据传输不能发生掉数、误码等现象。所以如何书写可靠的串口读数程序是必须的。对于工科本科生和工程师而言更是必备技能。本篇文章将详尽分析串口读数的工作流程,并给出基于Python的串口读数伪代码。
需要下载pyserial。若使用conda环境则直接使用:
conda install pyserial
命令即可。
首先应当定义几个超参数:
len_data: 单次读入的字节数;
REC_Length: 当前接收到数据总长度;
str_RS: 开辟的缓冲区大小,用来存放数组;
dealData_RS: 用来存放接受值的数组;
DealData_RS: 用来处理接收值的二维数组;
REC_Length_RS: 接收到的数据长度
Temp_Pos_RS: 用来记录定义串口传输协议的帧头在哪一个字节
RECPOS_RS: 接受线程处理标志位;
DealPos_RS: 处理线程标志位;
incorrect_RS: CRC校验未通过的标志位。
#%% All Hyper-parameters
len_data_RS = 20
REC_Length_RS = 0
str_RS = [0 for x in range(0, 2048)]
dealData_RS = [0 for x in range(0, len_data_RS)]
Dealdata_RS = [[0 for x in range(len_data_RS)] for y in range(10)]
Temp_Pos_RS = 0
RECPOS_RS = 0
DealPOS_RS = 0
incorrect_RS = 0
right_RS = 0
# RS测量数据接收线程
def run_rec_RS():
global REC_Length_RS, Temp_Pos_RS, RECPOS_RS, incorrect_RS, right_RS, str_RS, Dealdata_RS
while True:
if ser_RS.in_waiting:
data = (bytes)(ser_RS.read(len_data_RS)) # 读进来之后,赋予的类型是Bytes
len_rec = len(data)
if data != "": # 如果读到的数据不为空
for i in range(len_rec):
str_RS[i + REC_Length_RS] = data[i] # 将读取数据放到数组中
REC_Length_RS = REC_Length_RS + len_rec
# 如果接收到的数据长度比定义的数据长度还短,那肯定没有一包信息了,返回
if REC_Length_RS<len_data_RS:
return
# 如果不是的话,去掉4个字节的CRC,找帧头先
for i in range(REC_Length_RS-3):
# 如果帧头找到了,记录下帧头在缓冲区中的位置。在这里我定义的帧头转换为十进制就是170和193
if (str_RS[i]==170) & (str_RS[i+1]==193):
Temp_Pos_RS = i
break
# 如果已经寻到了倒数第四个字节还没寻到,那么从倒数第四个字节接着往下寻。
if i == REC_Length_RS-3:
Temp_Pos_RS = REC_Length_RS-3
# 如果当前总的字节长度减去帧头索引大于数据长度
if REC_Length_RS-Temp_Pos_RS >= (len_data_RS-1):
# 那么就把数据放到二维数组中来
for j in range(len_data_RS):
Dealdata_RS[RECPOS_RS][j] = str_RS[j + Temp_Pos_RS]
# 计算CRC异或和校验
crc_data = 0
for i in range(len_data_RS-1):
crc_data = Dealdata_RS[RECPOS_RS][i] ^ crc_data
i = i+1
if crc_data != Dealdata_RS[RECPOS_RS][i]:
incorrect_RS = incorrect_RS + 1
print("Incorrect RS Flag is",incorrect_RS)
else:
RECPOS_RS = (RECPOS_RS + 1)%10
# 如果通过CRC校验和,则将数据存至数组恰当位置;否则仅抛弃帧头段
for j in range(REC_Length_RS - Temp_Pos_RS - len_data_RS):
str_RS[j] = str_RS[j + Temp_Pos_RS + len_data_RS]
REC_Length_RS = REC_Length_RS - Temp_Pos_RS - len_data_RS
else:
for j in range(REC_Length_RS - Temp_Pos_RS):
str_RS[j] = str_RS[j + Temp_Pos_RS]
REC_Length_RS = REC_Length_RS - Temp_Pos_RS
# 解析数据线程
def run_deal_RS():
global RECPOS_RS, DealPOS_RS, Dealdata_RS
while True:
dealdd = [0 for x in range(0, len_data_RS)]
# 如果处理标志位和接受标志位不同,则说明可以处理的过来。如果相同则说明有可能会发生处理处理完了但是接收还没有接收的情况,时序可能造成混乱。
if RECPOS_RS != DealPOS_RS:
if (Dealdata_RS[DealPOS_RS][0] == 170) & (Dealdata_RS[DealPOS_RS][1] == 193):
# 按照帧格式解析你的数据
if __name__ == '__main__':
try:
ser_RS = serial.Serial(
# port='/dev/ttyUSB0',
port='/COM4',
baudrate=9600,
parity='N', # 校验位
bytesize=8, # 字节大小
timeout=1
)
except SerialException:
ser_RS.close()
ser_RS = serial.Serial(
# port='/dev/ttyUSB0', # /dev/ttys0
port='/COM4',
baudrate=9600,
parity='N', # 校验位
bytesize=8, # 字节大小
timeout=1,
)
整个的串口读数程序基本就是这样了。在运算过程中,有几个要注意的点:
Ref:
[1]https://baike.baidu.com/item/%E4%B8%B2%E5%8F%A3%E9%80%9A%E4%BF%A1/3775296?fr=aladdin