IAP15W413AS工业自动化控制之【18自定义协议通信】

  • SingleYork
  • LV5工程师
  • |      2017-10-31 18:20:34
  • 浏览量 4465
  • 回复:4
本帖最后由 SingleYork 于 2017-10-31 18:27 编辑

在一些工业应用的场合,我们经常需要用到串口通信,既然是要通信肯定是需要相关协议的支持,业内比较标准的协议当然要数MODBUS协议了,但是MODBUS协议要完全弄懂,也并非易事,很多时候,可能我们只需要简单控制一些输出同时读取输入输出状态,以及设置一些参数等。如果用标准的MODBUS肯定是没有问题的,但是并不是所有人都能在短时间内摸透MODBUS协议,那么,或许有人会说,自己随便写个简单的协议不就好了!没错,这样也是可以,只要通信设备双方都按照约定好的协议去执行相关动作即可,这一帖中,笔者就要着重介绍这种自定义协议的通信了。

说到自定义协议,笔者第一次接触的时候,还是在用迪文DGUS屏的时候,在接触了迪文DGUS屏的指令后,笔者才学会的使用自定义协议来做一些通信。那么,笔者就以迪文DGUS屏的指令为例,跟大家详细一下自定义协议的相关知识吧。

迪文DGUS串口数据帧的架构是由以下几个部分组成:

帧头(2字节)+ 数据长度(1字节)+ 指令(1字节)+ 数据(N字节)+ CRC校验(2字节)

所有指令都是以十六进制数发送,以写控制寄存器指令(80)为例:

例如从当前页面切换到第五幅图片,向屏发送如下指令即可:5A A5 04 80 03 00 05,那么这些十六进制数都代表什么意思呢?其含义如下:

5A A5:帧头由两个字节组成,可以自定义

04:发送数据的长度(指从指令开始到最后的数据长度,此处从80指令开始共发送4个字节)

80:写控制寄存器指令

03:控制寄存器地址

00 05:图片地址

在这条命令中,省去了CRC校验,由CRC校验相对比较复杂,很多场合也可以用和校验的方式替代CRC校验,比如,我们可以将上述指令改成如下方式:5A A5 04 80 03 00 05 92,最后一个数92即是一个校验和,从数据长度位开始到最后一个数据累加求余,即得到了校验和。当然,这个校验和我们可以用1个字节,也可以用2个字节,看大家使用习惯了,笔者经常都是用1个字节来做和校验位。看到这里,相信大家对于自定义协议应该有一定了解了。

其实,自定义协议很简单,可以自己任意定义一串数字,只要双方都按照定义好的格式收发数据即可。

接下来,笔者就一个实际的案例,在这款工控板上演示一下自定义协议通信。该工控板上有6个输出,分别是Y0-Y58个输入,分别是X00-X07,用串口助手模拟上位机来发送指令,分别控制每个输出口的输出状态,在工控板接收到串口助手发来的指令后,根据不同指令执行相关动作,并返回此时输入输出口的状态。

首先,笔者定义串口助手发送的通信帧格式如下:

帧头(2字节)+ 长度(1字节)+ 命令(1字节)+ 地址(1字节)+ 指令(1字节)

例如:

Y0输出ON 的指令为:5A A5 03 06 00 01

Y0输出OFF的指令为:5A A5 03 06 00 00

Y1输出ON 的指令为:5A A5 03 06 01 01

Y1输出OFF的指令为:5A A5 03 06 01 00

其中:

5A A5为帧头

03指数据长度

06为命令字

00Y0的地址/01Y1的地址

01为输出ON指令/00为输出OFF指令

本例中笔者偷懒了,也省去了和校验,不过笔者相信,看到这里了,大家对应该是有能力自己增加上和校验的,要是实在不知道,可以私聊笔者。

那么,发送问题是解决了,但是,要怎么接收这一串指令呢?还是参考迪文DGUS接收数据帧的方式,首先校验帧头:5A A5,然后再判断长度位,根据长度位来确定需要接收数据的长度,比如,此处长度位为03,那么,我们只需要在接收到长度位之后,继续再接收完3个数据即可认为数据接收完成,具体代码实现如下:

/********************* UART1中断函数************************/

void UART1_int (void) interrupt UART1_VECTOR

{

        u8 UART1_DataTemp;

  

        //命令格式:帧头+长度+命令字+地址+指令

        //5A A5 03 06 00 01--Y00-ON 5A A5 03 06 00 00--Y00-OFF

        

  if(RI)

        {

                RI = 0;

                UART1_DataTemp = SBUF;

                RX_Busy            = 1;        //接收忙

                

                if(RX_5A_OK)

                {

                        if(RX_A5_OK)

                        {

                                RX1_Buffer = UART1_DataTemp;      //将接收到的数组暂存到RX1_Buffer数组

                                COM1.RX_write ++; 

                                

                                if(COM1.RX_write == RX1_Buffer + 1)                                 //接收完成

                                {

                                        Uart1RXFinish   = 1;                                                             //数据接收完成,将标志位置1 

                                        RX_5A_OK                                 = 0;

                                        RX_A5_OK                                 = 0;

                                        COM1.RX_write         = 0;

                                        RX_Busy                                        = 0;

                                }

                        }

                        else

                        {

                                if(UART1_DataTemp == 0xA5)

                                {

                                        RX_A5_OK                                 = 1;

                                        COM1.RX_write         = 0;

                                }

                        }

                }

                else 

                {

                        if(UART1_DataTemp == 0x5A)

                        {

                                RX_5A_OK = 1;

                        }

                }

        

                if(COM1.RX_write >= RX_Length)        COM1.RX_write = 0;

  }



        if(TI)

        {

                TI = 0;

                COM1.TX_Busy = 0;

        }

}

当然,这里其实我们也可以使用结束符来实现,比如回车换行符,也很简单,只要找到回车换行对应的ASCII码的十六进制数就好了,要是笔者没记错的话,应该是:0x0D0x0A,那么我们可以在串口接收到0x0D0x0A两个数据之后认为是接收完成,当然,代码部分笔者就不再贴出来了,留给读者去完成了。

最后,讲一下返回数据帧的格式,笔者在本例中用了如下格式:

帧头(2字节)+ 长度(1字节)+ 命令(1字节)+ X00状态(1字节)+ X01状态(1字节)+ X02状态(1字节)+ X03状态(1字节)+ X04状态(1字节)+ X05状态(1字节)+ X06状态(1字节)+ X07状态(1字节)+ Y0状态(1字节)+ Y1状态(1字节)+ Y2状态(1字节)+ Y3状态(1字节)+ Y4状态(1字节)+ Y5状态(1字节)

当然,这里笔者写的有点繁琐,其实8个输入状态完全可以用一个字节来实现,6个输出状态也可以完全用一个字节来实现,这里笔者只是为了让大家更好的理解这个协议,所以写的繁琐了一点。数据返回的关键代码如下:

/********************* 串口1发送命令************************/

void Uart1_Send(void)

{

        //定时自动发送数据

        if(Uart1RXFinish)        //

        {

                if((RX1_Buffer==0x03)&&(RX1_Buffer==0x06))

                {

                        if(RX1_Buffer==0x00)//Y0

                        {

                                if(RX1_Buffer==0x01)        

                                {

                                        OUT00           = 1;

                                        Y00_State = 1;

                                }

                                if(RX1_Buffer==0x00)        

                                {

                                        OUT00           = 0;

                                        Y00_State = 0;

                                }

                        }

                        if(RX1_Buffer==0x01)//Y1

                        {

                                if(RX1_Buffer==0x01)        

                                {

                                        OUT01           = 1;

                                        Y01_State = 1;

                                }

                                if(RX1_Buffer==0x00)        

                                {

                                        OUT01           = 0;

                                        Y01_State = 0;

                                }

                        }

                        if(RX1_Buffer==0x02)//Y2

                        {

                                if(RX1_Buffer==0x01)        

                                {

                                        OUT02           = 1;

                                        Y02_State = 1;

                                }

                                if(RX1_Buffer==0x00)        

                                {

                                        OUT02           = 0;

                                        Y02_State = 0;

                                }

                        }

                        if(RX1_Buffer==0x03)//Y3

                        {

                                if(RX1_Buffer==0x01)        

                                {

                                        OUT03           = 1;

                                        Y03_State = 1;

                                }

                                if(RX1_Buffer==0x00)        

                                {

                                        OUT03           = 0;

                                        Y03_State = 0;

                                }

                        }

                        if(RX1_Buffer==0x04)//Y4

                        {

                                if(RX1_Buffer==0x01)        

                                {

                                        OUT04           = 1;

                                        Y04_State = 1;

                                }

                                if(RX1_Buffer==0x00)        

                                {

                                        OUT04           = 0;

                                        Y04_State = 0;

                                }

                        }

                        if(RX1_Buffer==0x05)//Y5

                        {

                                if(RX1_Buffer==0x01)        

                                {

                                        OUT05           = 1;

                                        Y05_State = 1;

                                }

                                if(RX1_Buffer==0x00)        

                                {

                                        OUT05           = 0;

                                        Y05_State = 0;

                                }

                        }

                        

                        //输入状态刷新

                        if(IN00)X00_State  = 0;else X00_State = 1;

                        if(IN01)X01_State  = 0;else X01_State = 1;

                        if(IN02)X02_State  = 0;else X02_State = 1;

                        if(IN03)X03_State  = 0;else X03_State = 1;

                        if(IN04)X04_State  = 0;else X04_State = 1;

                        if(IN05)X05_State  = 0;else X05_State = 1;

                        if(IN06)X06_State  = 0;else X06_State = 1;

                        if(IN07)X07_State  = 0;else X07_State = 1;

                        

                        Uart1RXFinish = 0;

                        Uart1SendEN                = 1;

                        SendDataInit        = 0;

                }

        }

                

        if(Uart1SendEN)

        {

                if(!RX_Busy)

                {

                        //发送命令

                        if(COM1.TX_Busy == 0)

                        {

                                COM1.TX_Busy = 1;                                        //标志发送忙



                                if(!SendDataInit)

                                {

                                        SendData  = 0x5A;//帧头

                                        SendData  = 0xA5;//帧头

                                        SendData  = 0x0F;//长度

                                        SendData  = 0x06;//命令0x06

                                        SendData  = X00_State;//X00输入状态L

                                        SendData  = X01_State;//X01输入状态L

                                        SendData  = X02_State;//X02输入状态L

                                        SendData  = X03_State;//X03输入状态L

                                        SendData  = X04_State;//X04输入状态L

                                        SendData  = X05_State;//X05输入状态L

                                        SendData = X06_State;//X06输入状态L

                                        SendData = X07_State;//X07输入状态L

                                        SendData = Y00_State;//Y00输出状态L

                                        SendData = Y01_State;//Y01输出状态L

                                        SendData = Y02_State;//Y02输出状态L

                                        SendData = Y03_State;//Y03输出状态L

                                        SendData = Y04_State;//Y04输出状态L

                                        SendData = Y05_State;//Y05输出状态L



                                        SendDataInit = 1;

                                        COM1.TX_read = 0;

                                }



                                SBUF = SendData;        //发一个字节



                                COM1.TX_read++;



                                if(COM1.TX_read>=18)//命令长度4位

                                {

                                        COM1.TX_read  = 0;

                                        Uart1SendEN          = 0;

                                }

                        }

                }

        }

}

接下来,我们只需要将程序下载到控制板中,再用串口助手来发送指令,即可验证效果了,笔者在此就用串口监控精灵来验证数据的收发,具体控制板上的输入输出状态就不贴图了:

从串口精灵监控到的数据我们可以看到:

当串口助手发送指令:5A A5 03 06 00 01的时候,控制板返回数据:5A A5 0F 06 00 00 00 00 00 00 00 00 01 00 00 00 00 00 ,即Y0输出ON;

当串口助手发送指令:5A A5 03 06 00 00的时候,控制板返回数据:5A A5 0F 06 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ,即Y0输出OFF;

当串口助手发送指令:5A A5 03 06 01 01的时候,控制板返回数据:5A A5 0F 06 00 00 00 00 00 00 00 00 00 01 00 00 00 00 ,即Y1输出ON;

当串口助手发送指令:5A A5 03 06 01 00的时候,控制板返回数据:5A A5 0F 06 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ,即Y1输出OFF;

同时,在控制板上我们也可以分别看到Y0先输出ON,然后再输出OFF;Y1先输出ON,然后再输出OFF(此处省去动图),至此,一个完整的通信就完成了,感兴趣的读者,可以在此基础上进行改善,比如,数据发送帧和数据返回帧都加上校验和。

好了,有关自定义协议的知识就简单介绍到这里了,有疑问的读者可以跟帖回复。

  • 0
  • 收藏
  • 举报
  • 分享
我来回复

登录后可评论,请 登录注册

所有回答 数量:3
追梦少年 2017-11-28
大佬要不来一篇 时间片轮询 的玩法:)
0   回复
举报
发布
12138 2017-11-02
为什么要帧头要那样设置成5AA5有什么好处吗?
0   回复
举报
发布
奔跑的黑蚂蚁 回复 2018-08-22
我觉得5AA5这个帧头比较好,我之前用的是F0和0F,其实就是为了方便判断,你想用什么都可以。
0   回复
举报
瞎折腾 回复 2019-07-27
兄弟学习了,但是看到没有结束的字节呢!
0   回复
举报
great_CC 2017-11-01
持续尾随楼主。
0   回复
举报
发布
x
收藏成功!点击 我的收藏 查看收藏的全部帖子