通信协议
- 串行通信接口(如RS232、RS485等)作为嵌入式设备之间交互数据的主要接口,广泛应用于各类仪器仪表、工业监测及自动控制领域中。
- 通信协议是需要通信双方所达成的一种约定,它对数据格式、同步方式、传输速度、传输步骤、校验、纠错方式以及控制字符定义等问题作出统一规定,在双方的通信中必须共同遵守。
- 在实际应用系统中,如果缺少一个严格、合理、规范的串口通信协议,将无法保证数据传输的正确性及通信的可靠性。因此,需要提出一种基于状态机的串口通信协议的设计方法:通过合理设置数据包格式来保证数据传输的正确性,引入状态机方法,简化协议的实现难度,提高通信的可靠性,同时使通信过程有较高的容错能力。
定义数据包格式
- 串口通信中的最小信息单元是数据帧。一个数据帧通常包括起始位、数据位、结束位,另外还可以包含用于检测传输错误的奇偶校验位。
- 实际通信过程中,数据的发送是一帧一帧地进行的,当被传输的数据超过一帧时,如果没有对数据帧进行必要的打包,发送出去的数据将很难被数据接收方解析,进而造成数据传输混乱与错误。因此,在一般应用中有必要将数据帧组装成数据包再发送。
- 起始标志:表示开始接收一个新的数据包。
- 数据长度:命令字段与载荷数据共占的字节数,用于接收端识别数据包长度,准确接收数据包。
- 命令字段:说明数据包的功能用途。
- 载荷数据:需要传输的实际数据,命令字段不同时含义不同。
- 校验位:对一帧数据包中所有字节数据的校验运算值(常见的有异或校验、和校验等)。
- 结束标志:表示该数据包结束。
另外,在多机通信中,数据包可能还会包含源地址与目标地址等信息。
通信状态机
状态机简介
- 状态机由 事物所处的状态 和 引发状态变化的外部事件 两部分组成。
- 在软件编程中,事物所处的状态可以描述为某个程序片段或函数,而引发状态发生变化的条件可以理解为条件判断语句,当条件为真时,事物的状态发生变化。事物发生变化前的状态称为现态,变化后的状态称为次态,程序中可以通过不同的数字对不同的状态进行编号。现态到次态的变化可以通过状态变量值的改变来描述。
- 在协议中需要传输的基本信息单元是数据包,一个数据包可以包含多个数据帧。实际传输过程中,通常是一帧一帧地进行的,数据包是被拆分成了若干帧数据后再进行的传输,数据接收方也是分帧接收一个数据包。
- 数据接收方在解析数据包时可能存在两个问题:
- 识别并接收完整的数据包
- 对于数据接收方,一个数据包是分若干批到来的,在识别包头与包尾时,存在帧同步问题。
- 具体编程时存在难度,特备对于已接收部分与未接收部分以及数据接收进度及状态的处理。
- 数据传输的容错能力
- 数据传输过程中出现错误是,系统应该具有摆脱错误状态、恢复正常状态的能力。
- 例如,当一个数据包只传输完一部分时,因为未知故障,下一个数据包就开始传输,系统应该能识别出传输错误,抛弃前一个出错的数据包,并且能够正确接受下一个数据包。
- 实际编程时处理这种问题难度较大,结果很可能出现第一个数据包的前一部分与后一个数据包的前一部分拼装成一个新的数据包的情况,这就损失了两个数据包,最严重的结果可能是系统无法从错误中恢复,这就严重降低了系统的安全性和可靠性。
- 识别并接收完整的数据包
- 为解决以上两个问题,在协议中引入状态机。在状态机中,状态的变化依赖于外部触发条件,当条件满足时,状态将发生变化。在协议中将数据包接收的各个阶段定义为不同的状态,将接收一帧新的数据或数据处理的结果作为外部触发条件,从而达到改变状态的目的,最终完成一个数据包的接收和解析。
串口通信状态图
- 串口通信协议中,考虑到提高发送速率和简化编程模型,发送数据包一般不需要引入状态机。
- 在协议中主要针对数据接收过程建立状态机。
串口数据接收过程
- 当未开始接收数据包或发现传输数据出错时,系统进入空闲状态。
- 当接收到数据包起始标志时,状态变为“收到起始标志”,如果收到的数据不是起始标志,系统继续保持空闲状态。
- 进入“收到起始标志”状态后,新接收的任何数据将被当做数据包中命令字段与载荷数据的总字节数(记为len),系统进入“收到数据长度状态”。
- 继续接收新的数据,直至新收到的总字节数达到len+2字节,进入“检验结束标志”状态。
- 这时可以检查结束标志是否为协议中定义的的标志值,如果是,说明传输正确,否则传输出错,出错后应查找接收缓冲区中本数据包的起始标志后有无其他起始标志,如果没有发现起始标志,系统应进入空闲状态,否则应直接进入“接收到起始标志”状态,这样可以提高系统的容错能力,方便系统从错误中恢复。
- 检验结束标志正确后,进入数据校验状态。
- 校验结果正确,数据包接收完成;否则数据传输出错,系统进入空闲状态。
上位机软件编程逻辑
- 上位机软件中,当收到数据时,串口控件会触发一个事件,在事件处理代码中应及时将收到的数据存入接收缓冲区,同时不应该把串口通信协议接收部分的代码放置在此事件中,否则后面到来的数据可能因为前面先到的数据没有及时处理完毕而被冲掉,导致数据丢失。
- 在上位机软件运行时,应该启动一个Windows线程,用于不断检测接收缓冲区是否为空,不为空时则对缓冲区中的数据进行处理。
- 线程类创建好之后,应具体编写线程类执行函数的处理过程,在其中通过状态指示变量实现状态机机制。
- 数据包的接收进度依赖于状态指示变量。
- 当数据顺利接收时,状态指示变量的变化将会引导完成一个数据包的接收过程。这样处理可以简化编程的模型,使协议易于实现;数据包接收过程中,一旦发现数据传输出错,立即将状态指示变量置为空闲状态,也就是状态复位,使系统进入准备接收下一个数据包的状态,这样可以提高通信过程的可靠性及容错能力。
状态机机制C语言实现
/* 串口状态机宏 */
#define DATA_HEAD 3
#define DATA_LEN 4
#define DATA_COM 5
#define DATA_NUM 6
#define DATA_CRC 7
#define DATA_TAIL 8
#define DATA_ADD 9
#define COMMAND_SIZE 20
int g_count = 0; //状态机缓冲区下标
int g_uart_state = DATA_HEAD;//串口状态机状态标志
unsigned char data; //串口数据
unsigned char command_buf[COMMAND_SIZE] = {0}; //状态机缓冲区
unsigned char *bufptr = &data;
while (1)
{
/* 从串口中一次只读取一个字符 */
retv = read(fd, bufptr, 1);
if (-1 == retv)
{
printf("read error!\n");
exit(1);
}
/*
---------------- 串口状态机-----------------
数据包格式
BYTE | BYTE | BYTE | BYTE | BYTE
包头 长度 命令 数据 包尾
*/
switch (g_uart_state)
{
/* 查找包头状态 */
case DATA_HEAD:
{
/* 找到包头 */
if (data == 包头)
{
/* 将包头存入指令数组 */
command_buf[g_count++] = data;
/* 改变串口状态机状态为长度 */
g_uart_state = DATA_LEN;
}
else
{
//包头匹配错误
g_count = 0;
}
break;
}
/* 检查数据长度 */
case DATA_LEN:
{
if (data == 长度)
{
//长度匹配
command_buf[g_count++] = data;
//改变状态机的状态为命令
g_uart_state = DATA_COM;
}
else
{
//长度匹配错误
g_count = 0;
//改变状态机的状态为包头
g_uart_state = DATA_HEAD;
}
break;
}
/* 检查命令是否合法 */
case DATA_COM:
{
if (data == 命令)
{
//合法存入
command_buf[g_count++] = data;
//改变状态机的状态为数据
g_uart_state = DATA_NUM;
}
else
{
//不合法改变状态机状态为包头
g_count = 0;
g_uart_state = DATA_HEAD;
}
break;
}
/* 检查数据是否合法 */
case DATA_NUM:
{
if (data是合法的数据)
{
//合法存入
command_buf[g_count++] = data;
//改变状态机的状态为包尾
g_uart_state = DATA_TAIL;
}
else
{
//不合法改变状态机状态为包头
g_count = 0;
g_uart_state = DATA_HEAD;
}
break;
}
/* 检查包尾*/
case DATA_TAIL:
{
if (data == 包尾)
{
command_buf[g_count++] = data;
//成功的解析到一个完整的符合传输协议的串口数据
}
/* 完成一次数据包的解析读取 */
g_count = 0;
g_uart_state = DATA_HEAD;
break;
}
default:
{
g_count = 0;
g_uart_state = DATA_HEAD;
memset(command_buf, '\0', sizeof(command_buf));
}
}
}
博客原文:https://www.cnblogs.com/quliuliu2013/p/13300834.html
学习交流,个人邮箱:imxyp9x@163.com