在单片机开发中,串口是最常用的和外界交换数据的渠道,要使用串口,那必不可少的就是通信协议,通信协议就是单片机和外界通信的语言,要想正常和其他设备正常交流,首先语言必须相通。

  在实际开发过程中由于各种原因,导致很多时候单片机和外界其他设备协议不兼容,在使用的时候就比较麻烦。比如单片机要和两个设备通信,但是这两个设备的通信协议的不一样,在使用时单片机就必须使用两个串口分别和两个设备通信。如果这两个设备同时使用时还不感觉到资源浪费,如果每次只接一个设备,那么另一个串口也不能作为其他功能使用,还得留着备用。这样的话单片机的资源就被白白浪费掉了。于是想着能不能在一个串口上支持两个协议,让单片机自动去识别接收到的数据使用的是哪个协议。

  一般使用通信协议接收数据时,都需要通过判断数据头和数据尾来确定什么时候开始接收数据,什么时候停止接收数据。但是如果要兼容多个协议的话,就不能使用这个方式去接收数据。必须先将一组数据接收完毕,然后根据数据的特点去分析数据使用的是哪个协议。

  首先要实现的就是如何判断一组数据是否接收完毕。

  实现的大概思路就是,单片机使用中断去接收数据,同时记录接收到的数据长度,在主函数中循环的去读取串口接收到的字符长度,如果超过一定时间之后,串口中接收到的数据长度没有发生变化,就说明一组数据接收完毕了。

  比如在主函数中读取到了当前串口数据长度不为0,说明此时串口正在接收数据,此时记录下当前串口数据长度,延时一段时间再去读取一次串口接收数据的长度,如果此时数据长度和上一次数据长度一样,说明串口接收数据结束了,就可以去处理接收到的数据了,如果此时数据长度和上一次的数据长度不一样,说明串口正在接收数据,接收数据还未结束,不能去处理数据。

  下面就通过一个工程案例来演示一个串口兼容两种协议的使用方法。

  设备默认使用的是自定义协议,协议格式如下:
[ 头1 ] (0xA5) [ 头2 ] (0x5A) [ 地址 ] [ 命令 ] [ 数据高位 ] [ 数据低位 ] [ 尾1 ] (0x55) [尾2] (0xAA)

  后来设备需要和市场上其他的工业设备对接,而大多数工业设备使用的都是Modbus协议,如果直接将设备协议修改为Modbus协议的话,那么好多旧设备和新设备就会不兼容,为了兼容旧设备同时又要对接其他工业设备,那么设备要在自定义协议的基础上兼容Modbus协议。Modbus协议格式如下:

[ 地址 ] [ 功能码 ] [ 起始地址高 ] [ 起始地址低] [ 总寄存器数高 ] [ 总寄存器数低 ] [ CRC低 ] [ CRC高 ]

  下面分析这两种协议的特点。

  首先看自定义协议,自定义协议的数据长度是固定的,同时数据的开始和结尾都是由两个字节标识。那么识别自定义协议就和容易了,直接判断数据头和数据为就行了。当串口接收一组数据结束后,判断接收到的数据 是不是以 0xA5和0X5A 开头,同时以 0x55 和0xAA结尾。如果是那么就使用自定义协议去解析数据。

  接下来分析Modbus协议,由于设备都是单独使用的,不需要级联,所以设备的地址是固定的0x01,同时由于设备的功能比较简单,所以在使用Modubus协议的时候,只用到了读保持寄存器(0x03)和写保持寄存器(0x06)这两个功能,所以Modbus的数据开头只有两种情况 0x01 0x03 和 0x01 0x06,如果数据开头是这两种情况,那么就使用Modbus协议去解析数据。

  通过对协议的分析,思路已经很清晰了,接下来使用代码来实现。

struct uart_info
{u8 cnt;u8 rec_buf[10];
};struct uart_info uart1;//在Library Options中将Printf formatter改成Large
//重新定向putchar函数,使支持printf函数
int putchar( int ch )
{while( !( UART1_SR & 0X80 ) ); //循环发送,直到发送完毕UART1_DR = ( u8 ) ch;return ch;
}
static void uart_io_init( void )
{PD_DDR |= ( 1 << 5 ); //输出模式 TXDPD_CR1 |= ( 1 << 5 ); //推挽输出PD_DDR &= ~( 1 << 6 ); //输入模式 RXDPD_CR1 &= ~( 1 << 6 ); //浮空输入
}
//波特率最大可以设置为38400
void uart_init( unsigned int baudrate )
{unsigned int baud;uart_io_init();baud = 16000000 / baudrate;UART1_CR1 = 0;UART1_CR2 = 0;UART1_CR3 = 0;UART1_BRR2 = ( unsigned char )( ( baud & 0xf000 ) >> 8 ) | ( ( unsigned char )( baud & 0x000f ) );UART1_BRR1 = ( ( unsigned char )( ( baud & 0x0ff0 ) >> 4 ) );UART1_CR2_bit.REN = 1;        //接收使能UART1_CR2_bit.TEN = 1;        //发送使能UART1_CR2_bit.RIEN = 1;       //接收中断使能
}//接收中断函数 中断号18
#pragma vector = 20                             // IAR中的中断号,要在STVD中的中断号上加2
__interrupt void UART1_Handle( void )
{unsigned char res = 0;UART1_SR &= ~( 1 << 5 );                    //RXNE 清零res = UART1_DR;if( uart1.cnt < 9 )uart1.rec_buf[uart1.cnt++] = res;
}

  定义一个结构体来存储接收到的数据长度和数据,由于自定义协议和Modbus协议最长的数据只有8个字节,所以这里的数组长度设置10就可以了。下面初始化串口使用的IO口,设置数据位和波特率。最后在中断中接收数据并存储到数组中。

  接下来在编写一个函数用来分析接收到的数据。

//检测串口数据
void read_uart( void )
{static u8 recevie_buf[10];static u8 recevie_cnt = 0;u8 i = 0;delay_ms( 10 ); //隔一段时间检测一次串口数据长度,如果数据长度没有发生变化说明串口接收数据完成if( ( uart1.cnt != recevie_cnt ) && ( uart1.cnt > 6 ) ){recevie_cnt = uart1.cnt;for( i = 0; i < recevie_cnt; i++ )            //拷贝数据{recevie_buf[i] = uart1.rec_buf[i];}//根据接收到的数据区分协议//自定义协议if( ( recevie_buf[0] == 0xA5 ) && ( recevie_buf[1] == 0x5A ) ){self_define_protocol( recevie_buf, recevie_cnt );}//modbus协议if( ( recevie_buf[0] == 0x01 ) && ( ( recevie_buf[1] == 0x03 ) || ( recevie_buf[1] == 0x06 ) ) ){modbus_protocol( recevie_buf, recevie_cnt );}//清空数组for( i = 0; i < 10; i++ ){uart1.rec_buf[i] = 0;recevie_buf[i] = 0;}uart1.cnt = recevie_cnt  = 0;}
}

  由于此设备中两种协议的数据长度都比较小,所以这里并没有分两次去判断数据长度,然后根据数据长度来判断数据接收是否完毕。只是延时10ms之后去判断数据长度,当接收的数据长度大于6时,说明数据基本已经接收完成了。由于数据的最大长度是8,所以接收的数据长度大于6时,基本数据已经接收完了,在中断中接收8个数据速度还是非常快的。如果数据量比较大,数据比较长时,最好还是通过数据长度去判断比较可靠。这里为了编写方便,就简单的时候延时去判断了。

  当串口接收的数据长度大于6时,说明一组数据已经接收完毕了,此时需要将串口接收的数据拷贝一份出来在使用。那为什么要将数据拷贝出来,而不是直接使用串口接收缓存区的数据呢?这是为了防止在处理数据的过程中国,串口又接收到了新的数据,这样新数据就会将旧的数据覆盖掉,有可能导致数据异常。为了数据的安全性将数据拷贝一份使用,即使在处理数据过程中串口缓存区的数据发生了变化,也不会破坏掉上一次接收的数据。

  数据拷贝结束后,就根据数据的特点来判断当前接收到的数据使用的是哪种协议。如果接收到的数据前两个是 0xA5和0x5A那么就直接调用自定义协议处理函数,如果前面的数据是0x01和0x03或者是0x01和0x06那么就直接调用Modbus协议去处理函数。

  接下来就可以单独编写两个函数分别处理这这种协议。

//处理自定义协议
// [头1](0xA5) [头2](0x5A) [地址]  [命令]  [数据高位] [数据低位]  [尾1](0x55) [尾2](0xAA)
//A5 5A 00 01 01 90 55 AA
void self_define_protocol( u8 arr[], u8 size )
{if( ( arr[0] == 0xA5 ) && ( arr[1] == 0x5A ) && ( arr[6] == 0x55 ) && ( arr[7] == 0xAA ) ){//根据命令值执行不同的动作}
}//处理modbus协议
//[地址][功能码][起始地址高][起始地址低][总寄存器数高][总寄存器数低][CRC低][CRC高]
//01 03 00 00 00 01 84 0A
//01 06 00 00 03 E8 89 74
void modbus_protocol( u8 arr[], u8 size )
{//调用 modbus相关处理代码
}

  将数据协议识别出来之后,就可以按照通常处理协议的方式去处理数据了。最后在主函数中循环的调用数据查询函数,检查串口是否有接收到数据。

void main( void )
{__asm( "sim" );                             //禁止中断SysClkInit();delay_init( 16 );LED_GPIO_Init();uart_init( 9600 );__asm( "rim" );                             //开启中断while( 1 ){read_uart();}
}

  接下来分别用这两种协议测试代码。

  使用串口助手发送自定义协议时,代码就会调用自定义协议处理函数。

  发送Modbus协议时,代码就会调用Modbus协议处理函数。

  这样根据协议的特点可以通过代码自动去识别协议的类型,用一个串口就可以实现不同协议的解析。按照同样的方法还可以解析更多的协议。当前在不同的协议使用的波特率要相同,否则波特率不同解析出来的数据就会出现错误,导致协议解析失败。

STM8单片机串口同时识别自定义协议和Modbus协议相关推荐

  1. fx5u mc协议_SLMP协议和MC协议

    一.概述 SLMP(Seamless Message Protocol)是在以太网中使用的协议.MC协议则包含了串口以及以太网的通信协议,范围更广. SLMP的3E帧或4E帧(3)的报文格式与MC协议 ...

  2. TCP协议和UDP协议的区别及其应用

    下面是TCP和UDP的简单程序: 简单的TCP网络程序 https://blog.csdn.net/qq_37941471/article/details/80738319 简单的UDP网络程序 ht ...

  3. Bytom BIP-32协议和BIP-44协议解读

    我们知道HD(分层确定性)钱包,基于 BIP-32:多币种和多帐户钱包,基于 BIP-44:最近比原社区的钱包开发者对比原的BIP-32和BIP-44协议有疑问,所以我今天就专门整理了一下该协议的内容 ...

  4. RabbitMQ MQTT协议和AMQP协议

    RabbitMQ MQTT协议和AMQP协议 1        序言... 1 1.1     RabbitMq结构... 1 1.2     RabbitMq消息接收... 4 1.3     Ex ...

  5. tcp协议和udp协议区别_TCP和UDP协议有什么区别?

    tcp协议和udp协议区别 TCP and UDP are two protocols that are part of the transport layer in a TCP/IP model o ...

  6. PPP协议和PPPoE协议

    以前没有关注过这两个协议,因为我一直认为PPP协议和PPPoE协议属于底层协议.但是在一次抓包时候发现在校园网内的数据包都带有PPP协议层,因此去研究了一下. 1 PPP协议介绍 PPP 点对点协议( ...

  7. jt808终端鉴权_驾培协议和jt808协议的区别 | 车载GPS和视频平台产品经理

    交通部颁发的<机动车驾驶员计时培训系统平台技术规范>中的驾培TCP协议和jt808协议虽然很像,但有本质的不同,很多人以为就是部标808协议,这个认识是错误的,驾培协议是驾培协议,jt80 ...

  8. 计算机网络——数据链路层局域网、以太网、PPP协议和HDLC协议、链路层设备

    文章目录 前言 一.局域网简介 1.局域网的基本概念和特点 2.局域网的主要要素 3.局域网的分类与 IEEE 802 标准 4.LLC 子层和 MAC 子层 二.以太网 三.无线局域网 四.PPP ...

  9. IP协议和TCP协议详解

    IP协议和TCP协议详解 IP协议 IP协议的特点 IPV4头部信息 IP分片 重定向 IPV6头部结构 TCP协议 TCP协议的特点 TCP头部结构 TCP连接的建立与关闭 异常终止连接 异常终止连 ...

最新文章

  1. CSS之布局(轮廓和圆角)
  2. 机器学习也能套模版:在线选择模型和参数,一键生成demo
  3. stopstart按钮怎么用_烟雾报警器一直响吵人!怎么彻底给关掉?
  4. 限制textbox中的内容
  5. 私有云的部署(1)_ISCSI 无盘引导的一些心得
  6. 最近发包给朋友,搞定软件小活儿、解决小功能模块的感受
  7. 如何使用ES6中的参数
  8. SFTP连接服务器后,PWD显示的目录、是用户的home目录
  9. MFC 教程【14_SOCKET类的设计和实现】
  10. 操作系统基本概念汇总
  11. 如何一键开通局域网共享
  12. offer oracle svp_SVP - Uncyclopedia
  13. html js input fileupload,简单 js fileUpload控件
  14. icem合并面网格_ICEM CFD中合并多个网格
  15. 常见文件扩展名和它们的说明(转)
  16. android 实体 快捷键,as快捷键
  17. 绕过tp路由器管理密码_普联(TPLink)路由器管理员密码是什么?
  18. CentOS7系统安装搜狗拼音输入法
  19. 2021-06-10 MYSQL存储过程与触发器作业
  20. bde oracle 商友的流程_怎么用BDE连接Oracle?(100分)

热门文章

  1. UIWebView清空本地缓存
  2. Appfuse 教程
  3. VirtualBox 安装ghost版windows XP
  4. Effective C# Item30:尽可能实现CLS兼容的程序集
  5. 10.08-vscode-plantuml建模
  6. Linux下 对文件行数打乱(乱序排列)
  7. 02--Tomcat总体结构分析一
  8. iOS开发——自定义下拉框
  9. 单链表的插入操作的实现(0952)SUWST-OJ
  10. SPOJ DQUERY 求区间内不同数的个数 主席树