目录
前沿
这段时间在写一个自动化测试平台的下位机软件,借此机会给大家分享一下自定义的串口通信协议。
1 命令格式
帧格式:[数据头码] [类型码] [长度码] [ID 码] [参数码1] [参数码2]... [参数码n] [校验码] [数据尾码] 注意:参数码最少为1
-
数据头码:‘$’ '#' [0x24 0x23](主到从) ,‘@’ '#' [0x40 0x23](从到主)
-
类型码:用于定于具体操作的类型,可以是读写寄存器、控制电机,也可以是控制摄像头等
-
数据尾码:'#' '@'[0x23 0x40](主到从),‘#’ ‘$’[0x23 0x24](从到主)长度码:1byte[类型码] +1byte [ID 码] + nbyte[参数码] (参数码最少为1)+1byte[校验码]
-
ID码:ID码根据类型码的不同含义有所不同
-
参数码:参数码可表示地址、数据、子类型等
-
校验码:等于和校验^0xFF,计算公式: [类型码] + [长度码] + [ID 码] + [参数码1] + [参数码2] +... + [参数码n](参数码最少为1)
2 应用举例
这里举一个寄存器的读写操作命令格式。寄存器数据位为8位。
类型码:0x01
ID码:读(0x00),写(0x01)
2.1 读寄存器
通信方向 | 数据头码 | 长度码 | 类型码 | ID码 | 参数码1 | 参数码2 | 参数码3 | 参数码n | 校验码 | 数据尾码 |
---|---|---|---|---|---|---|---|---|---|---|
主-->从 | "$#" | 0x05 | 0x01 | 0x00 | addr | len | 无 | 无 | xx | "#@" |
从-->主 | "@#" | xx | 0x01 | 0x00 | len | data | data | data | xx | "#$" |
addr:读寄存器的地址
-
0x00:寄存器1地址
-
0x01:寄存器2地址
-
0x02:寄存器3地址
len:读取的长度
data:读取的数据,地址依次递增
举例:
主-->从:0x24 0x23 0x05 0x01 0x00 0x01 0x02 0xxx 0x23 0x40
表示从0x01地址开始,读取2个连续的地址的值
从-->主:0x40 0x23 0x06 0x01 0x00 0x02 0xxx 0xxx 0xxx 0x23 0x24
返回0x01和0x02地址的值
2.2 写寄存器
通信方向 | 数据头码 | 长度码 | 类型码 | ID码 | 参数码1 | 参数码2 | 参数码3 | 参数码n | 校验码 | 数据尾码 |
---|---|---|---|---|---|---|---|---|---|---|
主-->从 | "$#" | xx | 0x01 | 0x01 | addr | len | data | data | xx | "#@" |
从-->主 | "@#" | xx | 0x01 | 0x01 | addr | len | data | data | xx | "#$" |
addr:写寄存器的地址
-
0x00:寄存器1地址
-
0x01:寄存器2地址
-
0x02:寄存器3地址
len:写的长度,字节为单位
data:写的数据,地址依次递增
举例:
主-->从:0x24 0x23 0x07 0x01 0x01 0x01 0x02 0x01 0x02 0xxx 0x23 0x40
表示向0x01地址写入0x01值,0x02地址写入0x02值
从-->主:0x24 0x23 0x07 0x01 0x01 0x01 0x02 0x01 0x02 0xxx 0x23 0x40
返回状态
3 编写代码
3.1 main函数代码
main函数主要实现协议的解析,根据不同的类型码进行具体的操作。cmdSet是一个函数指针数组,里面存放不同类型的操作函数。
typedef void (*p_cmdType)(CommDataWithHost_Typedef *commdata_struct);
/* 不同类型码执行不同的函数 */
p_cmdType cmdSet[MAX_CMD_SUPPORT]=
{
Function1, /* 其他功能函数 */
RW_RegFunction /* 读写寄存器函数 */
};
/* 参数码的大小 */
#define PARA_CODE_LEN 32
/* 帧结构 */
typedef struct
{
__IO uint8_t framehead1;
__IO uint8_t framehead2;
__IO uint8_t length;
__IO uint8_t type;
__IO uint8_t id;
__IO uint8_t function[PARA_CODE_LEN];
__IO uint8_t check;
__IO uint8_t frameend1;
__IO uint8_t frameend2;
}CommDataWithHost_Typedef;
CommDataWithHost_Typedef RxDataWithHost_Structure;
__IO uint8_t ReceiveData_InformFlag = RXDATA_FROMHOST_FRAMEHEAD1_FLAG;
extern p_cmdType cmdSet[MAX_CMD_SUPPORT];
/*******************************************************************************
* Function Name : main.
* Description : Main routine.
* Input : None.
* Output : None.
* Return : None.
*******************************************************************************/
int main(void)
{
void (*pfun)(CommDataWithHost_Typedef *);
USART_Config();
/* Infinite loop */
while (1)
{
if(ReceiveData_InformFlag == RXDATA_FROMHOST_COMPLETED_FLAG)
{
ReceiveData_InformFlag = RXDATA_FROMHOST_FRAMEHEAD1_FLAG;
/* 防止数组越界 */
if (RxDataWithHost_Structure.type < MAX_CMD_SUPPORT)
{
//p_cmdType pfun = cmdSet[RxDataWithHost_Structure.type];
pfun = cmdSet[RxDataWithHost_Structure.type];
if (pfun != 0)
{
pfun(&RxDataWithHost_Structure);
}
}
}
}
}
3.2 中断函数代码
中断函数主要是接收串口收到的数据,并进行逐一处理。因为串口是一个字节一个字节的接收数据,所以在每次串口接收到数据之后,都会置位下一次将要接收的标志。
这里也对数据的越界做了相应的处理,处理代码:
if ((tempData > 3) && (tempData <= (PARA_CODE_LEN + 3)))
extern CommDataWithHost_Typedef RxDataWithHost_Structure;
extern __IO uint8_t ReceiveData_InformFlag;
/* Definition for information flag of receive data from pc host */
typedef enum
{
RXDATA_FROMHOST_FRAMEHEAD1_FLAG = 0,
RXDATA_FROMHOST_FRAMEHEAD2_FLAG,
RXDATA_FROMHOST_LENGTH_FLAG,
RXDATA_FROMHOST_TYPE_FLAG,
RXDATA_FROMHOST_ID_FLAG,
RXDATA_FROMHOST_FUNCTION_FLAG,
RXDATA_FROMHOST_CHECKDATA_FLAG,
RXDATA_FROMHOST_FRAMEEND1_FLAG,
RXDATA_FROMHOST_FRAMEEND2_FLAG,
RXDATA_FROMHOST_COMPLETED_FLAG
}RXDATA_FROMHOST_ENUM;
/* RX帧头帧尾 */
#define RXDATA_WITHHOST_FRAMEHEAD1 '$'
#define RXDATA_WITHHOST_FRAMEHEAD2 '#'
#define RXDATA_WITHHOST_FRAMEEND1 '#'
#define RXDATA_WITHHOST_FRAMEEND2 '@'
/* TX帧头帧尾 */
#define TXDATA_WITHHOST_FRAMEHEAD1 '@'
#define TXDATA_WITHHOST_FRAMEHEAD2 '#'
#define TXDATA_WITHHOST_FRAMEEND1 '#'
#define TXDATA_WITHHOST_FRAMEEND2 '$'
/**
* @brief This function handles USART1 interrupt request.
* @param None
* @retval None
*/
void DEBUG_USART_IRQHandler(void)
{
uint8_t tempData = 0x00;
static uint8_t tempcnt = 0;
if (USART_GetITStatus(DEBUG_USARTx, USART_IT_RXNE) != RESET)
{
tempData = USART_ReceiveData(DEBUG_USARTx);
switch (ReceiveData_InformFlag)
{
/* Received 1 byte : frame head data 1 = '$' */
case RXDATA_FROMHOST_FRAMEHEAD1_FLAG:
if (tempData == RXDATA_WITHHOST_FRAMEHEAD1)
{
RxDataWithHost_Structure.framehead1 = tempData;
ReceiveData_InformFlag = RXDATA_FROMHOST_FRAMEHEAD2_FLAG;
}
else
{
ReceiveData_InformFlag = RXDATA_FROMHOST_FRAMEHEAD1_FLAG;
}
break;
/* Received a frame head data 2 : '#' */
case RXDATA_FROMHOST_FRAMEHEAD2_FLAG:
if (tempData == RXDATA_WITHHOST_FRAMEHEAD2)
{
RxDataWithHost_Structure.framehead2 = tempData;
ReceiveData_InformFlag = RXDATA_FROMHOST_LENGTH_FLAG;
}
else
{
ReceiveData_InformFlag = RXDATA_FROMHOST_FRAMEHEAD1_FLAG;
}
break;
/* Received data length */
case RXDATA_FROMHOST_LENGTH_FLAG:
/* 判断长度段的数据是否满足要求,至少为4,不能大于PARA_CODE_LEN+3 */
if ((tempData > 3) && (tempData <= (PARA_CODE_LEN + 3)))
{
RxDataWithHost_Structure.length = tempData;
ReceiveData_InformFlag = RXDATA_FROMHOST_TYPE_FLAG;
tempcnt = 0;
}
else
{
ReceiveData_InformFlag = RXDATA_FROMHOST_FRAMEHEAD1_FLAG;
}
break;
/* Received data type information */
case RXDATA_FROMHOST_TYPE_FLAG:
RxDataWithHost_Structure.type = tempData;
ReceiveData_InformFlag = RXDATA_FROMHOST_ID_FLAG;
break;
/* Received id information */
case RXDATA_FROMHOST_ID_FLAG:
RxDataWithHost_Structure.id = tempData;
ReceiveData_InformFlag = RXDATA_FROMHOST_FUNCTION_FLAG;
break;
/* Received function information */
case RXDATA_FROMHOST_FUNCTION_FLAG:
RxDataWithHost_Structure.function[tempcnt] = tempData;
tempcnt++;
if ((RxDataWithHost_Structure.length - 3) == tempcnt)
{
ReceiveData_InformFlag = RXDATA_FROMHOST_CHECKDATA_FLAG;
}
break;
/* Received check information */
case RXDATA_FROMHOST_CHECKDATA_FLAG:
RxDataWithHost_Structure.check = tempData;
ReceiveData_InformFlag = RXDATA_FROMHOST_FRAMEEND1_FLAG;
break;
/* Received frame end data 1 = '#' */
case RXDATA_FROMHOST_FRAMEEND1_FLAG:
if (tempData == RXDATA_WITHHOST_FRAMEEND1)
{
RxDataWithHost_Structure.frameend1 = tempData;
ReceiveData_InformFlag = RXDATA_FROMHOST_FRAMEEND2_FLAG;
}
else
{
ReceiveData_InformFlag = RXDATA_FROMHOST_FRAMEHEAD1_FLAG;
}
break;
/* Received frame end data 2 = '@' */
case RXDATA_FROMHOST_FRAMEEND2_FLAG:
if (tempData == RXDATA_WITHHOST_FRAMEEND2)
{
RxDataWithHost_Structure.frameend2 = tempData;
ReceiveData_InformFlag = RXDATA_FROMHOST_COMPLETED_FLAG;
}
else
{
ReceiveData_InformFlag = RXDATA_FROMHOST_FRAMEHEAD1_FLAG;
}
break;
default:
ReceiveData_InformFlag = RXDATA_FROMHOST_FRAMEHEAD1_FLAG;
break;
}
}
}
3.3 串口函数代码
串口初始化代码主要初始化串口的波特率、奇偶校验、停止位等信息。并使能接收中断。
/**
* @brief Configure NVIC.
* @param None
* @retval None
*/
static void NVIC_Configuration(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitStructure.NVIC_IRQChannel = DEBUG_USART_IRQ;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
/**
* @brief Initializes and config USART.
* @param None
* @retval None
*/
void USART_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
/* Enable USART and GPIO clock */
DEBUG_USART_TX_GPIO_AHBClkCmd(DEBUG_USART_TX_GPIO_CLK, ENABLE);
DEBUG_USART_RX_GPIO_AHBClkCmd(DEBUG_USART_RX_GPIO_CLK, ENABLE);
DEBUG_USART_APBxClkCmd(DEBUG_USART_CLK, ENABLE);
/* Configure the GPIO of USART_TX to AF-PP mode */
GPIO_InitStructure.GPIO_Pin = DEBUG_USART_TX_GPIO_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(DEBUG_USART_TX_GPIO_PORT, &GPIO_InitStructure);
/* Configure the GPIO of USART_RX to floating input mode */
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = DEBUG_USART_RX_GPIO_PIN;
GPIO_Init(DEBUG_USART_RX_GPIO_PORT, &GPIO_InitStructure);
/* Configure the working parameters of the USART */
USART_InitStructure.USART_BaudRate = DEBUG_USART_BAUDRATE;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No ;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Init(DEBUG_USARTx, &USART_InitStructure);
NVIC_Configuration();
USART_ITConfig(DEBUG_USARTx, USART_IT_RXNE, ENABLE);
USART_Cmd(DEBUG_USARTx, ENABLE);
}
3.4 事件处理函数代码
处理读写事件,读主要是读出寄存器的内容返回给主机,写主要是把主机下发的内容写入到从机的寄存器中,读写这两个函数需要自己实现。
/**
* @brief 读写寄存器
* @param RxData: 接收的串口数据.
* @retval None
*/
void RW_RegFunction(CommDataWithHost_Typedef *RxData)
{
CommDataWithHost_Typedef TxData;
uint8_t i = 0;
#ifdef DATA_CHECK
uint8_t tempCheckValue = 0;
tempCheckValue = CalcuCheckValue_DataWithHost(RxData);
if (tempCheckValue == RxData->check)
#endif
{
/* 读 */
if (RxData->id == 0x00)
{
TxData.length = RxData->length+RxData->function[1]-1; //总长度
TxData.type = RxData->type;
TxData.id = RxData->id;
TxData.function[0] = RxData->function[1]; //数据长度
/* 依次读出寄存器的值 */
// pEEPROMReg->Read((uint8_t*)&TxData.function[1+i], RxData->function[0], RxData->function[1]);
TransmitData_CommunicateWithHost(&TxData);
}
/* 写 */
else if (RxData->id == 0x01)
{
// pEEPROMReg->Write((uint8_t*)&RxData->function[2], RxData->function[0], RxData->function[1]);
TxData.length = RxData->length; //总长度
TxData.type = RxData->type;
TxData.id = RxData->id;
/* 依次读出寄存器的值,并反馈给TxData */
for (i=0; i<(RxData->length-3); i++)
{
TxData.function[i] = RxData->function[i];
}
TransmitData_CommunicateWithHost(&TxData);
}
}
}
3.5 串口发送代码
串口发送代码分为校验发送和不带校验发送,校验通过CalcuCheckValue_DataWithHost函数实现。串口发送通过TransmitData_CommunicateWithHost实现。
校验与否可通过宏定义决定,因为串口助手进行验证的时候,校验码不好计算,所以在测试阶段不校验,在交付阶段需要把校验打开。
/**
* @brief Calculate the data check value of communication with pc host.
* @param TxData: Communication data structure.
* @retval None
*/
uint8_t CalcuCheckValue_DataWithHost(CommDataWithHost_Typedef* TxData)
{
uint8_t i;
uint8_t tempValue = 0x00;
for(i=0; i<(TxData->length-3); i++)
{
tempValue = (uint8_t)(tempValue + TxData->function[i]);
}
tempValue = (uint8_t)(tempValue + \
TxData->length + \
TxData->type + \
TxData->id );
tempValue = tempValue ^ 0xFF;
return tempValue;
}
/**
* @brief Communicate and transmit data with pc host.
* @param TxData: Communication data structure.
* @retval None
*/
void TransmitData_CommunicateWithHost(CommDataWithHost_Typedef* TxData)
{
uint8_t i = 0;
uint8_t tempCheckValue = 0;
/* 防止数组越界 */
if ((TxData->length-3) <= PARA_CODE_LEN)
{
TxData->framehead1 = TXDATA_WITHHOST_FRAMEHEAD1;
TxData->framehead2 = TXDATA_WITHHOST_FRAMEHEAD2;
TxData->frameend1 = TXDATA_WITHHOST_FRAMEEND1;
TxData->frameend2 = TXDATA_WITHHOST_FRAMEEND2;
tempCheckValue = CalcuCheckValue_DataWithHost(TxData);
usartWithHost_SendByte(TxData->framehead1);
usartWithHost_SendByte(TxData->framehead2);
usartWithHost_SendByte(TxData->length);
usartWithHost_SendByte(TxData->type);
usartWithHost_SendByte(TxData->id);
for(i=0; i<(TxData->length-3); i++)
{
usartWithHost_SendByte(TxData->function[i]);
}
usartWithHost_SendByte(tempCheckValue);
usartWithHost_SendByte(TxData->frameend1);
usartWithHost_SendByte(TxData->frameend2);
}
}