原题叙述
有若干个温度采集器,每个温度采集器可实现8路温度的测量。试设计一个通信协议,用于温度采集器与上位计算机的串行通信协议,可实现温度采集数据上传、上位机控制每路温度测量通的开启功能。
作业提交方式:文档,详细说明设计思路及具体协议格式。
题目分析
根据原题内容,可以按照如下步骤来实现:
- 选择硬件层通信协议
- 设计相应的通信协议,注意有多个传感器且有多路温度采集
- 编写相关的下位机程序
- 编写相关的上位机程序
- 测试通信协议收发机制
按照上述的步骤内容,以下将分为四个章节来叙述。
通信协议设计
选择串口通信作为底层通信协议,故而需要设计基于串口的通信协议。
数据帧格式定义
表 3 1协议数据帧格式
起始标志 | 协议版本 | 数据长度 | 控制字符 | 真实数据 | CRC32校验 | 结束标志 |
---|---|---|---|---|---|---|
1Byte | 1Byte | 1Byte | 1Byte | 不定 | 4Byte | 1Byte |
起始标志 协议版本 数据长度 控制字符 真实数据 CRC32校验 结束标志
1Byte 1Byte 1Byte 1Byte 不定 4Byte 1Byte
如上表 3 1所示即为数据帧的格式,现结合温度采样问题对具体数据叙述如下:
- 起始标志。协议数据帧开始的标志,保留字为0xFE。
- 协议版本。同其他设备通信时首要的一致性保证,此次为0x01,即第一版。
- 数据长度。表示当前数据包的大小。
- 控制字符。可以自定义,对于温度采样问题,该为温度采集器序号。
- 真实数据。发送的数据内容,对于温度采样问题,该为温度采样序号(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开始) - CRC32校验。根据前述数据所得的CRC32校验码。
- 结束标志。协议数据结束的标志,保留字为0xFF。
保留字与转义问题
数据打包时只有包头为0xFE,只有包尾为0xFF,对于其中的数据段若出现上述保留字,则应进行相关的转义处理:
表 3 2 字符转义
保留字 | 0xFE START | 0xFF END | 0xFD ESC |
---|---|---|---|
保留字符 | 0xFDDE | 0xFDDF | 0xFDDD |
如上表所示,即为将相应的数据转义之后得到的数据,实际上是相关的数据与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上位机通信
实验演示