自定义串口通信协议

原题叙述

有若干个温度采集器,每个温度采集器可实现8路温度的测量。试设计一个通信协议,用于温度采集器与上位计算机的串行通信协议,可实现温度采集数据上传、上位机控制每路温度测量通的开启功能。
作业提交方式:文档,详细说明设计思路及具体协议格式。

题目分析

根据原题内容,可以按照如下步骤来实现:

  1. 选择硬件层通信协议
  2. 设计相应的通信协议,注意有多个传感器且有多路温度采集
  3. 编写相关的下位机程序
  4. 编写相关的上位机程序
  5. 测试通信协议收发机制
    按照上述的步骤内容,以下将分为四个章节来叙述。

通信协议设计

选择串口通信作为底层通信协议,故而需要设计基于串口的通信协议。

数据帧格式定义

表 3 1协议数据帧格式

起始标志协议版本数据长度控制字符真实数据CRC32校验结束标志
1Byte1Byte1Byte1Byte不定4Byte1Byte

起始标志 协议版本 数据长度 控制字符 真实数据 CRC32校验 结束标志
1Byte 1Byte 1Byte 1Byte 不定 4Byte 1Byte

如上表 3 1所示即为数据帧的格式,现结合温度采样问题对具体数据叙述如下:

  1. 起始标志。协议数据帧开始的标志,保留字为0xFE。
  2. 协议版本。同其他设备通信时首要的一致性保证,此次为0x01,即第一版。
  3. 数据长度。表示当前数据包的大小。
  4. 控制字符。可以自定义,对于温度采样问题,该为温度采集器序号。
  5. 真实数据。发送的数据内容,对于温度采样问题,该为温度采样序号(1Byte)与温度真实数据,关于温度真实数据的换算如下式所示:
    T r = ( D a t a [ 1 ] ∗ 256 + D a t a [ 2 ] ) / 100 T_r=(Data[1]*256+Data[2])/100 Tr=(Data[1]256+Data[2])/100
    式中, Tr为实际温度值,为一浮点型数据
    Data为采集到的数据,在未进行转义时,应为3位,其中第一位表示温度采样序号,后两位为实际数据,计算如上式(从0开始)
  6. CRC32校验。根据前述数据所得的CRC32校验码。
  7. 结束标志。协议数据结束的标志,保留字为0xFF。

保留字与转义问题

数据打包时只有包头为0xFE,只有包尾为0xFF,对于其中的数据段若出现上述保留字,则应进行相关的转义处理:
表 3 2 字符转义

保留字0xFE START0xFF END0xFD ESC
保留字符0xFDDE0xFDDF0xFDDD

如上表所示,即为将相应的数据转义之后得到的数据,实际上是相关的数据与0x20作异或之后的结果。

下位机程序

基本简介

使用STM32芯片作为MCU,并采用FreeRTOS嵌入式系统,编写了相关的协议收发函数,并新建了一个线程做相关的测试,测试内容如下:
只有一个温度采集器,有三路温度测量数据。第一路为固定的11.11℃,第二路一开始为0℃,每隔1s增加0.01℃,第三路一开始为0℃,每隔1s减少0.01℃,即成负数。

协议C文件

/* include files *************************************************/
#include "usart_protocol.h"


/* System defines ***********************************************/
#define Protocol_Version 0x01
#define Start_Fram 0xFE
#define End_Fram   0xFF
static uint8_t start_Identi = Start_Fram;
static uint8_t end_Identi   = End_Fram;
uint8_t test = 0xfe;

/* Protocol command value ***************************************/


/* System global variable ***************************************/
handle_uart_proto huart_proto; // Send status machine
extern CRC_HandleTypeDef hcrc;


/**
 * @brief init for the usart protocol
 * @param hproto handle of protocol
 */
void usart_proto_init(handle_uart_proto* hproto)
{
    hproto->Current = -1;
    hproto->Len = 0;
}


/**
 * @brief store a new data to the send status machine
 * @param hproto handle of the send status machine
 * @param Command the command data
 * @param Data the pointer to the real data
 * @param length the length of the real data
 * @return ok, error, full, overlength
 */
UART_PROTO_StatusTypeDef usart_proto_store(handle_uart_proto* hproto,
    uint8_t Command, uint8_t* Data, uint8_t length)
{
    uint8_t position;
    uint8_t DataPosition = 0;
    uint32_t crc_checkCode;

    // if thData[16]de status machine is full, return error
    if(hproto->Len >= MAX_LEN) return uart_proto_FULL;
    
    // if the data length is overlength, return error
    if(length > MAX_Data_LEN) return uart_proto_OVERLEN;
    
    /* Update the data status machine ***************************************/
    // Update the length and position of status machine
    hproto->Len ++;
    if(hproto->Current == (MAX_LEN-1))
        hproto->Current = 0;
    else
    {
        hproto->Current++;
        position = hproto->Current;
    }
    // Update the command data
    hproto->Command[position] = Command;
    // check for the escape character and store the position if any
    for(position=0; position<length; position++)
        if(Data[position]==0xFF || Data[position]==0xFE || Data[position] == 0xFD)
        {
            hproto->Data[hproto->Current][DataPosition] = 0xFD;
            DataPosition++;
            hproto->Data[hproto->Current][DataPosition] = Data[position]^0x20;
            DataPosition++;
        }
        else
        {
            hproto->Data[hproto->Current][DataPosition] = Data[position];
            DataPosition++;
        }
    // Calculate the crc check code
    crc_checkCode = HAL_CRC_Calculate(&hcrc, (uint32_t*)hproto->Data[hproto->Current],
        position/4+1);
    hproto->Data[hproto->Current][DataPosition] = crc_checkCode/0x1000000;
    DataPosition++;
    hproto->Data[hproto->Current][DataPosition] = crc_checkCode%0x1000000/0x10000;
    DataPosition++;
    hproto->Data[hproto->Current][DataPosition] = crc_checkCode%0x10000/0x100;
    DataPosition++;
    hproto->Data[hproto->Current][DataPosition] = crc_checkCode%0x100;
    DataPosition++;
    // Update the Data lenth;
    hproto->DataLength[hproto->Current] = DataPosition;
    
    return uart_proto_OK;
}


/**
 * @brief Send one data data package from the send status machine
 * @param hproto handle of protocol
 * @param huart handle of usart which will send the data
 * @return ok, error
 */
UART_PROTO_StatusTypeDef usart_proto_sendOneData(handle_uart_proto* hproto,
    UART_HandleTypeDef* huart)
{
    // Calculate the data package length. Include:
    // 1.Start Fram; 2.protocol version; 3.package length; 4.command data; 
    // 5.Real data; 6.crc data; 7.End Fram
    uint8_t length = 1 + 1 + 1 + hproto->DataLength[hproto->Current] + 1;
    uint8_t protocol_version = Protocol_Version;
    
    // if there is no data in the machine, return error
    if(hproto->Current == -1) return uart_proto_EMPTY;

    // 1.Send the start fram
    if(HAL_UART_Transmit(huart, &test, 1, 100)!=HAL_OK)
        return uart_proto_ERROR;

    // 2.Send the protocol version
    if(HAL_UART_Transmit(huart, &protocol_version, 1, 100)!=HAL_OK)
        return uart_proto_ERROR;
    
    // 3.Send the data length
    if(HAL_UART_Transmit(huart, &length, 1, 100)!=HAL_OK)
        return uart_proto_ERROR;
    
    // 4.Send the command data
    if(HAL_UART_Transmit(huart, &hproto->Command[hproto->Current], 1, 100)!=HAL_OK)
        return uart_proto_ERROR;

    // 5&6.Send the data AND CRC check code
    if(HAL_UART_Transmit(huart, hproto->Data[hproto->Current],
        hproto->DataLength[hproto->Current],100)!=HAL_OK)
        return uart_proto_ERROR;

    // 7.Send the end indentifiler
    if(HAL_UART_Transmit(huart, &end_Identi, 1, 100)!=HAL_OK)
        return uart_proto_ERROR;
    
    // Update the current position.
    hproto->Len--;
    if(hproto->Len == 0) hproto->Current = -1;
    else if (hproto->Current == 0) hproto->Current = MAX_LEN-1;
    else hproto->Current--;

    return uart_proto_OK;
}

协议头文件

#ifndef __USART_PROTOCOL_H
#define __USART_PROTOCOL_H

#ifdef __cplusplus
 extern "C" {
#endif


/* include files *****************************************************/
#include "main.h"

/* System Define *****************************************************/
#define MAX_LEN 10 // maximum length the send data length.
#define MAX_Data_LEN 16 // maximum data length
#define MAX_Data_Send_LEN 20 // contain the crc check code

/* Command Data */
#define Protocol_CMD_TEMP 0x01
#define Protocol_CMD_TIME 0X02


/* specified structure ***********************************************/
/**
 * @brief status machine real status
 */
typedef enum
{
    uart_proto_OK      = 0x00U,
    uart_proto_ERROR   = 0x01U,
    uart_proto_FULL    = 0x02U,
    uart_proto_EMPTY   = 0x03U,
    uart_proto_OVERLEN = 0x04U
} UART_PROTO_StatusTypeDef;

/* This is the send status machine which store the data will be sent */
typedef struct{
    uint8_t Len; // status machine data length
    int8_t Current; // current position
    uint8_t Command[MAX_LEN]; // store the command data
    uint8_t DataLength[MAX_LEN]; // store the data length
    uint8_t Data[MAX_LEN][MAX_Data_Send_LEN]; // store the pointer of real data
}handle_uart_proto;


/* function define ********************************************************/
void usart_proto_init(handle_uart_proto* hproto);

UART_PROTO_StatusTypeDef usart_proto_store(handle_uart_proto* hproto,
    uint8_t Command, uint8_t* Data, uint8_t length);

UART_PROTO_StatusTypeDef usart_proto_sendOneData(handle_uart_proto* hproto,
    UART_HandleTypeDef* huart);


#ifdef __cplusplus
}
#endif

#endif

发送程序

/* USER CODE BEGIN Header_StartTask_Test02 */
/**
* @brief Function implementing the myTask_Test02 thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartTask_Test02 */
void StartTask_Test02(void *argument)
{
  /* USER CODE BEGIN StartTask_Test02 */
  float temp[3] = {11.11, 0, 0};
  uint8_t TempData[3] = {0x01, 0x00, 0x00};
  uint8_t i;
  uint16_t temp_swap;
  /* Infinite loop */
  for(;;)
  {
    for(i=0;i<3;i++)
    {
      if(temp[i]>0) temp_swap = temp[i]*100;
      else temp_swap = temp[i]*100 + 0x10000;
      TempData[0] = i+1;
      TempData[1] = temp_swap/0x100;
      TempData[2] = temp_swap%0x100;
      usart_proto_store(&huart_proto, Protocol_CMD_TEMP, TempData, 3);
    }
    temp[1] += 0.1;
    temp[2] -= 0.1;
    osDelay(1000);
  }
  /* USER CODE END StartTask_Test02 */
}

上位机程序

基本叙述

采用自制的串口通信上位机,并新增了相关的协议处理功能。

协议处理函数

def Protocol_Receive(self, data):
    for c in data:
        if c == 0xfe:
            # if it is receiving now, it means that there is no end identifier, so return error!
            if self.protocol_status == Proto_Sta.Receiving:
                self.protocol_status = Proto_Sta.Waiting
                return -1  # error
            else:
                self.protocol_status = Proto_Sta.Receiving
                self.protocol_receiveLen = 1
        elif c == 0xff:
            # same error: get a end identifier when the machine is waiting
            if self.protocol_status == Proto_Sta.Waiting:
                return -1
            else:
                self.protocol_status = Proto_Sta.Waiting
                print('version:', self.protocol_version)
                print('length:', self.protocol_length)
                print('command:', self.protocol_cmd)
                print('data:', self.protocol_data)
                print('crc:', self.protocol_crc)
                self.Protocol_Deploy()
                self.protocol_version = 0xff
                self.protocol_receiveLen = 0
                self.protocol_data = []
                self.protocol_crc = []
                self.protocol_cmd = 0xff
                self.protocol_length = 0xff
        else:
            if self.protocol_status == Proto_Sta.Receiving:
                if self.protocol_receiveLen == 1:
                    self.protocol_version = c
                    self.protocol_receiveLen += 1
                elif self.protocol_receiveLen == 2:
                    self.protocol_length = c
                    self.protocol_receiveLen += 1
                elif self.protocol_receiveLen == 3:
                    self.protocol_cmd = c
                    self.protocol_receiveLen += 1
                elif 4 <= self.protocol_receiveLen <= self.protocol_length-5:
                    self.protocol_data.append(c)
                    self.protocol_receiveLen += 1
                elif self.protocol_receiveLen > self.protocol_length-5:
                    self.protocol_crc.append(c)
                    self.protocol_receiveLen += 1

def Protocol_Deploy(self):
    if self.protocol_version != 0x01:
        return -1
    # should add the crc check program here
    if self.protocol_cmd == 0x01:  # temperature
        number = self.protocol_data[0]
        temp = self.protocol_data[1]*256 + self.protocol_data[2]
        if temp >= 32768:
            temp = (temp - 65536)/100
        else:
            temp = temp/100
        if number == 1:
            self.lineEdit_Temp_1.setText(str(temp))
        elif number == 2:
            self.lineEdit_Temp_2.setText(str(temp))
        elif number == 3:
            self.lineEdit_Temp_3.setText(str(temp))
    if self.protocol_cmd == 0x02:  # date and time
        year = self.protocol_data[0]
        month = self.protocol_data[1]
        day = self.protocol_data[2]
        hour = self.protocol_data[3]
        minute = self.protocol_data[4]
        second = self.protocol_data[5]

实验测试

如下图所示,图6-1位下位机通信图,其中为自制的STM32通信板。图6-2为上位机通信图,为通过PYQT及PYSERIAL模块编写的上位机程序。
附件中有相关的演示视频。
在这里插入图片描述
图 6 1 下位机通信实验
在这里插入图片描述
图 6 2上位机通信

实验演示

  • 13
    点赞
  • 176
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
实现自定义串口通信协议可以通过以下几个步骤来完成。 第一步是确定协议的基本要素,包括帧格式、数据字段和控制字段等。帧格式可选择起始位、数据位、校验位和停止位等组成,数据字段用于传输需要通信的数据,控制字段用于控制通信过程。 第二步是确定通信双方的通信流程和通信规则。通信流程包括建立连接、数据传输和断开连接等步骤,通信规则包括数据发送方和接收方之间的协作方式、错误处理方式等。 第三步是实现软件端和硬件端的通信功能。在软件端,可以使用编程语言如Python、C++等来实现自定义协议的编码和解码功能,将要发送的数据按照协议格式编码后发送,接收到的数据按照协议格式解码后进行处理。在硬件端,可以使用控制芯片如8051、Arduino等来实现串口通信的物理层功能。 第四步是进行通信测试和调试。在通信过程中,可以通过单元测试和集成测试来验证协议的正确性和可靠性,通过日志记录和错误处理来定位和解决通信中出现的问题。 最后一步是对协议进行优化和改进。根据实际需求,可以对协议的性能、安全性和扩展性等进行改进,提高通信速度、保护数据安全和支持更多的功能扩展。 总之,实现自定义串口通信协议需要考虑协议的基本要素、通信流程和通信规则,并在软硬件端进行功能实现和测试调试,最后对协议进行优化和改进。
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值