通信协议常见内容有哪些

有一些初学者总觉得通信协议是一个很复杂的知识,把它想的很高深,导致不知道该怎么学。
 同时,偶尔有读者问关于串口自定义通信协议相关的问题,今天就来写写串口通信协议,并不是你想想中的那么难?   1什么通信协议? 通信协议不难理解,就是两个(或多个)设备之间进行通信,必须要遵循的一种协议。  百度百科的解释: 通信协议是指双方实体完成通信或服务所必须遵循的规则和约定。通过通信信道和设备互连起来的多个不同地理位置的数据通信系统,要使其能协同工作实现信息交换和资源共享,它们之间必须具有共同的语言。交流什么、怎样交流及何时交流,都必须遵循某种互相都能接受的规则。这个规则就是通信协议。  
相应该有很多读者都买过一些基于串口通信的模块,市面上很多基于串口通信的模块都是自定义通信协议,有的比较简单,有的相对复杂一点。  举一个很简单的串口通信协议的例子:比如只传输一个温度值,只有三个字节的通信协议: 帧头 温度值 帧尾
5a 一字节数值 3b
这种看起来是不是很简单?它也是一种通信协议。  只是说这种通信协议应用的场合相对比较简单(一对一两个设备之间),同时,它存在很多弊端。   2过于简单的通信协议引发的问题 上面那种只有三个字节的通信协议,相信大家都看明白了。虽然它也能通信,也能传输数据,但它存在一系列的问题。  比如:多个设备连接在一条总线(比如485)上,怎么判断传输给谁?(没有设备信息)  还比如:处于一个干扰环境,你能保障传输数据正确吗?(没有校验信息)  再比如:我想传输多个不确定长度的数据,该怎么办?(没有长度信息)。  上面这一系列问题,相信做过自定义通信的朋友都了解。  所以,在通信协议里面要约定更多的“协议信息”,这样才能保证通信的完整。   3通信协议常见内容 基于串口的通信协议通常不能太复杂,因为串口通信速率、抗干扰能力以及其他各方面原因,相对于tcp/ip这种通信协议,是一种很轻量级的通信协议。  所以,基于串口的通信,除了一些通用的通信协议(比如:modubs、mavlink)之外,很多时候,工程师都会根据自己项目情况,自定义通信协议。  下面简单描述下常见自定义通信协议的一些要点内容。
(这是一些常见的协议内容,可能不同情况,其协议内容不同)  1.帧头帧头,就是一帧通信数据的开头。有的通信协议帧头只有一个,有的有两个,比如:5a、a5作为帧头。
2.设备地址/类型设备地址或者设备类型,通常是用于多种设备之间,为了方便区分不同设备。
 这种情况,需要在协议或者附录中要描述各种设备类型信息,方便开发者编码查询。  当然,有些固定的两种设备之间通信,可能没有这个选项。  3.命令/指令命令/指令比较常见,一般是不同的操作,用不同的命令来区分。
 举例:温度:0x01;湿度:0x02;  4.命令类型/功能码这个选项对命令进一步补充。比如:读、写操作。
 举例:读flash:0x01; 写flash:0x02;  5.数据长度数据长度这个选项,可能有的协议会把该选项提到前面设备地址位置,把命令这些信息算在“长度”里面。  这个主要是方便协议(接收)解析的时候,统计接收数据长度。
 比如:有时候传输一个有效数据,有时候要传输多个有效数据,甚至传输一个数组的数据。这个时候,传输的一帧数据就是不定长数据,就必须要有【数据长度】来约束。  有的长度是一个字节,其范围:0x01 ~ 0xff,有的可能要求一次性传输更多,就用两个字节表示,其范围0x0001 ~ 0xfffff。  当然,有的通信长度是固定的长度(比如固定只传输、温度、湿度这两个数据),其协议可能没有这个选项。  6.数据数据就不用描述了,就是你传输的实实在在的数据,比如温度:25℃。  7.帧尾有些协议可能没有帧尾,这个应该是可有可无的一个选项。  8.校验码校验码是一个比较重要的内容,一般正规一点的通信协议都有这个选项,原因很简单,通信很容易受到干扰,或者其他原因,导致传输数据出错。  如果有校验码,就能比较有效避免数据传输出错的的情况。
 校验码的方式有很多,校验和、crc校验算是比较常见的,用于自定义协议中的校验方式。  还有一点,有的协议可能把校验码放在倒数第二,帧尾放在最后位置。   4通信协议代码实现 自定义通信协议,代码实现的方式有很多种,怎么说呢,“条条大路通罗马”你只需要按照你协议要写实现代码就行。  当然,实现的同时,需要考虑你项目实际情况,比如通信数据比较多,要用消息队列(fifo),还比如,如果协议复杂,最好封装结构体等。  下面分享一些以前用到的代码,可能没有描述更多细节,但一些思想可以借鉴。  1.消息数据发送a.通过串口直接发送每一个字节这种对于新手来说都能理解,这里分享一个之前dgus串口屏的例子:                                                                                                   #define dgus_frame_head1 0xa5 //dgus屏帧头1#define dgus_frame_head2 0x5a //dgus屏帧头2#define dgus_cmd_w_reg 0x80 //dgus写寄存器指令#define dgus_cmd_r_reg 0x81 //dgus读寄存器指令#define dgus_cmd_w_data 0x82 //dgus写数据指令#define dgus_cmd_r_data 0x83 //dgus读数据指令#define dgus_cmd_w_curve 0x85 //dgus写曲线指令/* dgus寄存器地址 */#define dgus_reg_version 0x00 //dgus版本#define dgus_reg_led_now 0x01 //led背光亮度#define dgus_reg_bz_time 0x02 //蜂鸣器时长#define dgus_reg_pic_id 0x03 //显示页面id#define dgus_reg_tp_flag 0x05 //触摸坐标更新标志#define dgus_reg_tp_status 0x06 //坐标状态#define dgus_reg_tp_position 0x07 //坐标位置#define dgus_reg_tpc_enable 0x0b //触控使能#define dgus_reg_rtc_now 0x20 //当前rtcs//往dgds屏指定寄存器写一字节数据void dgus_reg_writeword(uint8_t regaddr, uint16_t data){ dgus_sendbyte(dgus_frame_head1); dgus_sendbyte(dgus_frame_head2); dgus_sendbyte(0x04); dgus_sendbyte(dgus_cmd_w_reg); //指令 dgus_sendbyte(regaddr); //地址 dgus_sendbyte((uint8_t)(data>>8)); //数据 dgus_sendbyte((uint8_t)(data&0xff));}//往dgds屏指定地址写一字节数据void dgus_data_writeword(uint16_t dataaddr, uint16_t data){ dgus_sendbyte(dgus_frame_head1); dgus_sendbyte(dgus_frame_head2); dgus_sendbyte(0x05); dgus_sendbyte(dgus_cmd_w_data); //指令 dgus_sendbyte((uint8_t)(dataaddr>>8)); //地址 dgus_sendbyte((uint8_t)(dataaddr&0xff)); dgus_sendbyte((uint8_t)(data>>8)); //数据 dgus_sendbyte((uint8_t)(data&0xff));}  b.通过消息队列发送在上面基础上,用一个buf装下消息,然后“打包”到消息队列,通过消息队列的方式(fifo)发送出去。                                                                             static uint8_t sdgus_sendbuf[dgus_package_len];//往dgds屏指定寄存器写一字节数据void dgus_reg_writeword(uint8_t regaddr, uint16_t data){ sdgus_sendbuf[0] = dgus_frame_head1; //帧头 sdgus_sendbuf[1] = dgus_frame_head2; sdgus_sendbuf[2] = 0x06; //长度 sdgus_sendbuf[3] = dgus_cmd_w_ctrl; //指令 sdgus_sendbuf[4] = regaddr; //地址 sdgus_sendbuf[5] = (uint8_t)(data>>8); //数据 sdgus_sendbuf[6] = (uint8_t)(data&0xff); dgus_crc16(&sdgus_sendbuf[3], sdgus_sendbuf[2] - 2, &sdgus_crc_h, &sdgus_crc_l); sdgus_sendbuf[7] = sdgus_crc_h; //校验 sdgus_sendbuf[8] = sdgus_crc_l; dgussend_packet_toqueue(sdgus_sendbuf, sdgus_sendbuf[2] + 3);}//往dgds屏指定地址写一字节数据void dgus_data_writeword(uint16_t dataaddr, uint16_t data){ sdgus_sendbuf[0] = dgus_frame_head1; //帧头 sdgus_sendbuf[1] = dgus_frame_head2; sdgus_sendbuf[2] = 0x07; //长度 sdgus_sendbuf[3] = dgus_cmd_w_data; //指令 sdgus_sendbuf[4] = (uint8_t)(dataaddr>>8); //地址 sdgus_sendbuf[5] = (uint8_t)(dataaddr&0xff); sdgus_sendbuf[6] = (uint8_t)(data>>8); //数据 sdgus_sendbuf[7] = (uint8_t)(data&0xff); dgus_crc16(&sdgus_sendbuf[3], sdgus_sendbuf[2] - 2, &sdgus_crc_h, &sdgus_crc_l); sdgus_sendbuf[8] = sdgus_crc_h; //校验 sdgus_sendbuf[9] = sdgus_crc_l; dgussend_packet_toqueue(sdgus_sendbuf, sdgus_sendbuf[2] + 3);}  c.用“结构体”代替“数组sendbuf”方式结构体对数组更方便引用,也方便管理,所以,结构体方式相比数组buf更高级,也更实用。(当然,如果成员比较多,如果用临时变量方式也会导致占用过多堆栈的情况)  比如:                   typedef struct{ uint8_t head1; //帧头1 uint8_t head2; //帧头2 uint8_t len; //长度 uint8_t cmd; //命令 uint8_t data[dgus_data_len]; //数据 uint16_t crc16; //crc校验}dgus_package_typedef;  d.其他更多串口发送数据的方式有很多,比如用dma的方式替代消息队列的方式。  2.消息数据接收串口消息接收,通常串口中断接收的方式居多,当然,也有很少情况用轮询的方式接收数据。  a.常规中断接收还是以dgus串口屏为例,描述一种简单又常见的中断接收方式:                                                                               void dgus_isrhandler(uint8_t data){ static uint8_t sdgus_rxnum = 0; //数量 static uint8_t sdgus_rxbuf[dgus_package_len]; static portbase_type xhigherprioritytaskwoken = pdfalse; sdgus_rxbuf[gdgus_rxcnt] = data; gdgus_rxcnt++; /* 判断帧头 */ if(sdgus_rxbuf[0] != dgus_frame_head1) //接收到帧头1 { gdgus_rxcnt = 0; return; } if((2 == gdgus_rxcnt) && (sdgus_rxbuf[1] != dgus_frame_head2)) { gdgus_rxcnt = 0; return; } /* 确定一帧数据长度 */ if(gdgus_rxcnt == 3) { sdgus_rxnum = sdgus_rxbuf[2] + 3; } /* 接收完一帧数据 */ if((6 <= gdgus_rxcnt) && (sdgus_rxnum sr & usart_flag_rxne) == usart_flag_rxne) { dgus_timingandupdate(5); //更新定时(防止超时) dgus_isrhandler((uint8_t)usart_receivedata(dgus_com)); }}  
c.更多
接收和发送一样,实现方法有很多种,比如接收同样也可以用结构体方式。但有一点,都需要结合你实际需求来编码。
  5最后 以上自定义协议内容仅供参考,最终用哪些、占用几个字节都与你实际需求有关。  基于串口的自定义通信协议,有千差万别,比如:mcu处理能力、设备多少、通信内容等都与你自定义协议有关。  有的可能只需要很简单的通信协议就能满足要求。有的可能需要更复杂的协议才能满足。  最后强调两点:1.以上举例并不是完整的代码(有些细节没有描述出来),主要是供大家学习这种编程思想,或者实现方式。  2.一份好的通信协议代码,必定有一定容错处理,比如:发送完成检测、接收超时检测、数据出错检测等等。所以说,以上代码并不是完整的代码。  
原文标题:通信教程 | 自定义串口通信协议
文章出处:【微信公众号:strongerhuang】欢迎添加关注!文章转载请注明出处。


iPhone XS跟iPhone XS Max那LTE信号不佳已经困扰着不少用户
电装即将亮相2019东京车展
六种常见的DC-DC升压电路
还在骂路虎新发现?只能说你真的不懂路虎
基于CSR8670的蓝牙4.0智能眼镜的实体图
通信协议常见内容有哪些
TDA8177各引脚功能及电压
奥拓电子与信达信息联手进军智能型Mini & Micro LED显示市场
引领能源新风向!涂鸦户用光储方案:赋能企业加速布局千亿新赛道
AGV机器人市场前景良好,但还有这些制约发展问题丞待解决!
小米发布蓝牙K歌耳机故宫特别版 定价399元
加密货币交易网络中的安全漏洞正在被越来越多的人利用
基于Linux模块的IDT PCIe热插拔驱动程序
S7-200 SMART与台达变频器通讯,其实并不难
华为是怎样一步步成为AI公司的
华尔街将采用人工智能技术监管股市欺诈行为
工业网关如何实现MQTT、MODBUS、OPCUA、SQL、HTTP之间协议转换
印度将打造成为全球智能手机生产中心
工业互联网和工业物联网之间是什么关系
固态硬盘等存储元器件价格疯长,涨价超50%,消费者成待宰羔羊