当前位置:
文档之家› 单片机通信协议处理(状态机)
单片机通信协议处理(状态机)
资源,而且对比直接发送来说也没有太多的优点。以下是 51 系列单片机中发送 单个字节的函数。 void SendByte(unsigned char ch) { SBUF = ch; while(TI == 0); TI = 0; } 上位机中关于串口通信的方式也有多种, 这种方式不是指数据有没有缓 冲的问题,而是操作串口的方式不同,因为 PC 上数据发送基本上都会被缓冲后 再发送。对于编程来说操作串口有三种方式,一、使用 windows 系统中自带的 串口通信控件, 这种方式使用起来比较简单,需要注意的是接收时的阻塞处理和 线程机制。二、使用系统的 API 直接进行串口数据的读取,在 windows 和 linux 系统中,设备被虚拟为文件,只需要利用系统提供的 API 函数即可进行串口数 据的发送和读取。三、使用串口类进行串口操作。在此只介绍 windows 环境下 利用串口类编程的方式。 CSerialPort 是比较好用的串口类。它提供如下的串口操作方法: void WriteToPort(char* string, int len); 串口初始化成功后, 调用此函数即可向串口发送数据。为了避免串口缓 冲所带来的延时,可以开启串口的冲刷机制。 3. 下位机中的数据接收和协议解析 下位机接收数据也有两种方式,一、等待接收,处理器一直查询串口状 态,来判断是否接收到数据。二、中断接收。两种方法的优缺点在此前的一篇关 于串口通信的文章中详细讨论过。得出的结论是采用中断接收的方法比较好。 数据包的解析过程可以设置到不同的位置。如果协议比较简单,整个系 统只是处理一些简单的命令, 那么可以直接把数据包的解析过程放入到中断处理 函数中,当收到正确的数据包的时候,置位相应的标志,在主程序中再对命令进 行处理。如果协议稍微复杂,比较好的方式是将接收的数据存放于缓冲区中,主 程序读取数据后进行解析。也有两种方式交叉使用的,比如一对多的系统中,首 先在接收中断中解析“连接”命令,连接命令接收到后主程序进入设置状态,采用 查询的方式来解析其余的协议。 以下给出具体的实例。在这个系统中,串口的命令非常简单。所有的协 议全部在串口中断中进行。数据包的格式如下: 0x55, 0xAA, 0x7E, 0x12, 0xF0, 0x02, 0x23, 0x45, SUM, XOR, 0x0 D 其中 0x55, 0xAA, 0x7E 为数据帧的帧头,0x0D 为帧尾,0x12 为设备 的目的地址,0xF0 为源地址,0x02 为数据长度,后面接着两个数据 0x23, 0x45, 从目的地址开始结算累加、异或校验和,到数据的最后一位结束。 协议解析的目的,首先判断数据包的完整性,正确性,然后提取数据类 型,数据等数据,存放起来用于主程序处理。代码如下: if(state_machine == 0) // 协议解析状态机 { if(rcvdat == 0x55) // 接收到帧头第一个数据 state_machine = 1;
上位机中数据接收的过程与下位机可以做到完全一致, 不过针对不同的 串口操作方法有所不同。对于阻赛式的串口读函数,例如直接进行 API 操作或 者调用 windows 的串口通信控件,最好能够开启一个线程专门用于监视串口的 数据接收,每接收到一个数据可以向系统发送一个消息。笔者常用的 CSerialPor t 类中就是这样的处理过程。CSerialPort 打开串口后开启线程监视串口的数据接 收,将接收的数据保存到缓冲区,并向父进程发送接收数据的消息,数据将随消 息一起发送到父进程。 父进程中开启此消息的处理函数,从中获取串口数据后就 可以把以上的代码拷贝过来使用。 CSerialPort 向父类发送的消息号如下: #define WM_COMM_RXCHAR WM_USER+7 // A character was rec eived and placed in the input buffer. 因此需要手动添加此消息的响应函数: afx_msg LONG OnCommunication(WPARAM ch, LPARAM port); ON_MESSAGE(WM_COMM_RXCHAR, OnCommunication) 响应函数的具体代码如下: LONG CWellInfoView::OnCommunication(WPARAM ch, LPARAM port) { int retval = 0; rcvdat = (BYTE)ch; if(state_machine == 0) // 协议解析状态机 { if(rcvdat == 0x55) // 接收到帧头第一个数据 state_machine = 1; else state_machine = 0; // 状态机复位 } else if(state_machine == 1) { if(rcvdat == 0xAA) // 接收到帧头第二个数据 state_machine = 2; else state_machine = 0; // 状态机复位 ...... 5. 总结 以上给出的是通信系统运作的基本雏形,虽然简单,但是可行。实际的 通信系统中协议比这个要复杂,而且涉及到数据包响应、命令错误、延时等等一 系列的问题, 在这样的一个基础上可以克服这些困难并且实现出较为稳定可靠的 系统。
ຫໍສະໝຸດ Baidu单片机通信协议处理
现在大部分的仪器设备都要求能过通过上位机软件来操作,这样方便调试, 利于操作。其中就涉及到通信的过程。在实际制作的几个设备中,笔者总结出了 通信程序的通用写法,包括上位机端和下位机端等 1. 自定义数据通信协议 这里所说的数据协议是建立在物理层之上的通信数据包格式。 所谓通信 的物理层就是指我们通常所用到的 RS232、RS485、红外、光纤、无线等等通信 方式。在这个层面上,底层软件提供两个基本的操作函数:发送一个字节数据、 接收一个字节数据。所有的数据协议全部建立在这两个操作方法之上。 通信中的数据往往以数据包的形式进行传送的, 我们把这样的一个数据包称 作为一帧数据。类似于网络通信中的 TCPIP 协议一般,比较可靠的通信协议往 往包含有以下几个组成部分:帧头、地址信息、数据类型、数据长度、数据块、 校验码、帧尾。 帧头和帧尾用于数据包完整性的判别, 通常选择一定长度的固定字节组 成, 要求是在整个数据链中判别数据包的误码率越低越好。减小固定字节数据的 匹配机会, 也就是说使帧头和帧尾的特征字节在整个数据链中能够匹配的机会最 小。通常有两种做法,一、减小特征字节的匹配几率。二、增加特征字节的长度。 通常选取第一种方法的情况是整个数据链路中的数据不具有随即性,数据可预 测, 可以通过人为选择帧头和帧尾的特征字来避开,从而减小特征字节的匹配几 率。使用第二种方法的情况更加通用,适合于数据随即的场合。通过增加特征字 节的长度减小匹配几率, 虽然不能够完全的避免匹配的情况,但可以使匹配几率 大大减小, 如果碰到匹配的情况也可以由校验码来进行检测,因此这种情况在绝 大多说情况下比较可靠。 地址信息主要用于多机通信中, 通过地址信息的不同来识别不同的通信 终端。在一对多的通信系统中,可以只包含目的地址信息。同时包含源地址和目 的地址则适用于多对多的通信系统。 数据类型、 数据长度和数据块是主要的数据部分。数据类型可以标识后 面紧接着的是命令还是数据。数据长度用于指示有效数据的个数。 校验码则用来检验数据的完整性和正确性。通常对数据类型、数据长度 和数据块三个部分进行相关的运算得到。最简单的做法可是对数据段作累加和, 复杂的也可以对数据进行 CRC 运算等等,可以根据运算速度、容错度等要求来 选取。 2. 上位机和下位机中的数据发送 物理通信层中提供了两个基本的操作函数, 发送一个字节数据则为数据 发送的基础。 数据包的发送即把数据包中的左右字节按照顺序一个一个的发送数 据而已。当然发送的方法也有不同。 在单片机系统中, 比较常用的方法是直接调用串口发送单个字节数据的 函数。 这种方法的缺点是需要处理器在发送过程中全程参与,优点是所要发送的 数据能够立即的出现在通信线路上,能够立即被接收端接收到。另外一种方法是 采用中断发送的方式, 所有需要发送的数据被送入一个缓冲区,利用发送中断将 缓冲区中的数据发送出去。 这种方法的优点是占用处理器资源小,但是可能出现 需要发送的数据不能立即被发送的情况,不过这种时延相当的小。对于 51 系列 单片机,比较倾向于采用直接发送的方式,采用中断发送的方式比较占用 RAM
{ m_ucData[lencnt++] = rcvdat; // 数据保存 sumchkm += rcvdat; xorchkm ^= rcvdat; if(lencnt == rcvcount) // 判断数据是否接收完毕 state_machine = 8; else state_machine = 7; } else if(state_machine == 8) { if(sumchkm == rcvdat) // 判断累加和是否相等 state_machine = 9; else state_machine = 0; } else if(state_machine == 9) { if(xorchkm == rcvdat) // 判断异或校验和是否相等 state_machine = 10; else state_machine = 0; } else if(state_machine == 10) { if(0x0D == rcvdat) // 判断是否接收到帧尾结束符 { retval = 0xaa; // 置标志,表示一个数据包接收到 } state_machine = 0; // 复位状态机 } 此过程中,使用了一个变量 state_machine 作为协议状态机的转换状态, 用于确定当前字节处于一帧数据中的那个部位, 同时在接收过程中自动对接收数 据进行校验和处理, 在数据包接收完的同时也进行了校验的比较。因此当帧尾结 束符接收到的时候,则表示一帧数据已经接收完毕,并且通过了校验,关键数据 也保存到了缓冲去中。主程序即可通过 retval 的标志位来进行协议的解析处理。 接收过程中, 只要哪一步收到的数据不是预期值, 则直接将状态机复位, 用于下一帧数据的判断,因此系统出现状态死锁的情况非常少,系统比较稳定, 如果出现丢失数据包的情况也可由上位机进行命令的补发, 不过这种情况笔者还 没有碰到。 对于主程序中进行协议处理的过程与此类似, 主程序循环中不断的读取 串口缓冲区的数据, 此数据即参与到主循环中的协议处理过程中,代码与上面所 述完全一样。 4. 上位机中的数据接收和命令处理
else state_machine = 0; // 状态机复位 } else if(state_machine == 1) { if(rcvdat == 0xAA) // 接收到帧头第二个数据 state_machine = 2; else state_machine = 0; // 状态机复位 } else if(state_machine == 2) { if(rcvdat == 0x7E) // 接收到帧头第三个数据 state_machine = 3; else state_machine = 0; // 状态机复位 } else if(state_machine == 3) { sumchkm = rcvdat; // 开始计算累加、异或校验和 xorchkm = rcvdat; if(rcvdat == m_SrcAdr) // 判断目的地址是否正确 state_machine = 4; else state_machine = 0; } else if(state_machine == 4) { sumchkm += rcvdat; xorchkm ^= rcvdat; if(rcvdat == m_DstAdr) // 判断源地址是否正确 state_machine = 5; else state_machine = 0; } else if(state_machine == 5) { lencnt = 0; // 接收数据计数器 rcvcount = rcvdat; // 接收数据长度 sumchkm += rcvdat; xorchkm ^= rcvdat; state_machine = 6; } else if(state _machine == 6 || state _machine == 7)