简介:最近需要做一个上下位机通信的项目,在和公司对接的时候,需要事先明确通信协议,以便后续的测试。所以我自定义了基于串口的通信协议。
1. 通信帧是什么?为什么要使用通信帧?
通信帧是一种在通信系统中用于传输数据的数据包格式。通信帧是一组按照特定格式组织的二进制数据,其中包含了必要的信息,以便发送方和接收方能够正确地解释和处理数据。通信帧通常包含以下几个重要的部分:
-
帧头(Frame Header): 帧头标识了一个通信帧的开始,通常由几个字节组成,用于标记帧的起始位置。
-
帧类型/命令码(Frame Type/Command Code): 这个部分指示了帧中包含的数据的类型或者命令的类型。接收方根据这个信息来决定如何解析和处理帧中的数据。
-
数据长度(Data Length): 这个部分指示了帧中数据的长度,使接收方能够正确地截取和处理数据。
-
数据域(Data Field): 数据域包含了实际传输的数据,其格式和含义由帧类型或者命令码来决定。
-
校验(Checksum/CRC): 校验部分用于验证帧中数据的完整性,防止在传输过程中发生错误。常见的校验算法包括CRC(循环冗余校验)和校验和等。
使用通信帧的目的是为了在通信过程中实现可靠、有序、高效的数据传输。通过定义一种标准的帧格式,通信的各个参与方能够按照同样的规则来构造和解析帧,从而保证数据的正确性和一致性。通信帧的使用也使得系统更容易扩展和维护,因为通过修改帧格式,可以相对容易地引入新的功能或者改变通信协议。
2. 自定义通信帧格式
3. 代码实现(基础示例)
上位机创建自定义通信帧,通过UART传输至下位机。
import serial
import struct
import crcmod.predefined
# 打开串口
ser = serial.Serial('COM1', 9600, timeout=1)
# 创建CRC校验对象
crc16 = crcmod.predefined.Crc('crc-16')
def send_custom_frame(command, data):
# 帧头
frame_header = b'\xA5\x5A'
# 命令码
command_bytes = struct.pack('<H', command) # 使用'<H'表示小端字节顺序
# 数据长度
data_length = len(data)
length_bytes = struct.pack('<H', data_length)
# 计算CRC校验值
crc16.update(frame_header + command_bytes + length_bytes + data)
crc_value = crc16.crcValue
crc_bytes = struct.pack('<H', crc_value)
# 构造自定义通信帧
frame = frame_header + command_bytes + length_bytes + data + crc_bytes
ser.write(frame)
# 例子:发送一个带有命令码为0x1234和数据为b'Hello'的帧
send_custom_frame(0x1234, b'Hello')
# 关闭串口
ser.close()
下位机接收上位机传输的通信帧数据,并且进行解析进行指令对应功能。
import serial
import struct
import crcmod.predefined
# 打开串口
ser = serial.Serial('COM1', 9600, timeout=1)
# 创建CRC校验对象
crc16 = crcmod.predefined.Crc('crc-16')
def receive_custom_frame():
# 读取帧头
frame_header = ser.read(2)
if frame_header != b'\xA5\x5A':
return None # 帧头不匹配
# 读取命令码
command_bytes = ser.read(2)
command = struct.unpack('<H', command_bytes)[0] # 使用'<H'表示小端字节顺序
# 读取数据长度
length_bytes = ser.read(2)
data_length = struct.unpack('<H', length_bytes)[0]
# 读取数据
data = ser.read(data_length)
# 读取CRC校验值
crc_bytes = ser.read(2)
crc_value = struct.unpack('<H', crc_bytes)[0]
# 验证CRC校验
crc16.update(frame_header + command_bytes + length_bytes + data)
if crc16.crcValue != crc_value:
return None # CRC校验失败
return command, data
# 例子:接收并解析帧
received_frame = receive_custom_frame()
if received_frame:
command, data = received_frame
print("Received Command:", hex(command))
print("Received Data:", data)
# 关闭串口
ser.close()