搜索
bottom↓
回复: 17

关于单片机串行通信协议实现方法的讨论

[复制链接]

出0入76汤圆

发表于 2012-8-31 14:50:39 | 显示全部楼层 |阅读模式
我们在做串行通信程序设计的时候都会碰到此类问题,不论是采用MODBUS标准协议,还是自定通信协议,在我们的程序中如何来实现这个协议呢?
先说说我自己采用的方法:
1.数据包结构:
帧头(4个字节) + 地址码(1个字节) + 功能码(1个字节) + 数据长度(1个字) + 数据(N个字节) + 校验(CRC16 2个字节,高字节在前) + 帧尾(4个字节)

2.通信处理方式:
中断接收/发送 + 缓冲区

3.协议的实现:
1)接收: 我是在接收中断去实现协议功能的,即每接收中断一次(接收到一个字节)的时候,按照协议的要求去进行处理,如果这帧数据是正确的就置位Flag标志位,在大循环中再去进命令的解析,错误时则丢弃。

我想还有一种方法是, 使用循环队列(缓冲区)的方法, 在中断接收的时候只管去接收数据而不进行数据的处理,而是在大循环中进行数据的处理,来实现协议功能,以及命令的解析。   这种方法我目前还没有去实现。

2)发送:按照协议的要求去组织数据,把数据装入发送缓冲区,由中断来完成发送。

另外,还想请教 对于 在超时及容错方面的处理。

在此请教各位大侠,谈谈你们的看法或自己实际工程中采用的方法,小弟在此先感谢了。 同时,也希望对于像初学者有所帮助。


阿莫论坛20周年了!感谢大家的支持与爱护!!

曾经有一段真挚的爱情摆在我的面前,我没有珍惜,现在想起来,还好我没有珍惜……

出0入264汤圆

发表于 2012-8-31 15:37:34 来自手机 | 显示全部楼层
我使用的是   使用循环队列(缓冲区)的方法, 在中断接收的时候只管去接收数据而不进行数据的处理,而是在大循环中进行数据的处理,来实现协议功能,以及命令的解析。 

出0入0汤圆

发表于 2012-8-31 16:31:45 | 显示全部楼层
我一直使用:使用循环队列(缓冲区)的方法, 在中断接收的时候只管去接收数据而不进行数据的处理,而是在大循环中进行数据的处理,来实现协议功能,以及命令的解析。   这种方法我目前还没有去实现。

出0入0汤圆

发表于 2012-8-31 20:40:21 | 显示全部楼层
看看这篇文章可能会受益匪浅!


另外关于超时我是这样处理的,增加一个通讯计数,系统原本有一个固定10ms的定时,当进入通讯时每10ms对通讯计数进行累加,而每接收一个字节对通讯计数进行清零,这样当通讯计数大于等于2时就认定为通讯超时了(数据间延时大于10ms了)。
这是我借鉴别人整出来的,可能不是最好,希望能抛砖引玉。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入76汤圆

 楼主| 发表于 2012-8-31 22:46:24 | 显示全部楼层
本帖最后由 foxpro2005 于 2012-8-31 23:31 编辑
souching 发表于 2012-8-31 20:40
看看这篇文章可能会受益匪浅!


嗯,首先感谢各位大侠这么及时的回帖,同时也非常感谢 souching 的分享。
1.我的程序在协议解析这一块就是按照状态机的思想去做的(同这篇文件的方法3),我就是在想利用FIFO的方法会不会更好些呢?(感觉这个去实现协议这一部份有点麻烦些,而方法3在接收中断时就实现了协议的处理,状态机的思路和结构比较清晰,只是与FIFO相比而言,在接收中断会多消耗几条指令。 相反,又有一个问题, 如果接收中帧错误,FIFO它还会继续接收字节装到接收缓冲区,只有到协议处理的时候才能去判断和处理, 相当于没有过滤功能;而接收中断状态机的方法,在接收过程中就实现的协议的处理,相当于进行了一次数据过滤,但会增加中断的指令消耗)。  所也就是想比较这两种方法,哪个比较好些,或者说哪个更适合些。  更或者说 还有更好的方法。

2.我之前也是这么想的,根据波特率的要求,来确定两个接收的两个字节之间的时间长短,然后用一个定时器来负责计时接收两个字节的时间,当接收两个字节之间的时间大于规定的时间就认为超时了,那么本帧就做丢弃处理。

也或许是我在FIFO这种方法中实现协议处理的理解或思路 不完全正确?  还请各位大侠指教, 讲讲您们的思路 或者 分享你的方法(有示例代码更好,让吾等小弟观摩和学习,小弟先感谢啦 ^|^)

出0入0汤圆

发表于 2012-9-1 16:56:02 | 显示全部楼层
当项目需要全双工通讯时,唤醒缓冲区的优势就来了。
中断只负责收发数据,大循环才进行协议的处理

出0入264汤圆

发表于 2012-9-1 19:17:47 | 显示全部楼层
本帖最后由 mcu_lover 于 2012-9-1 19:25 编辑
foxpro2005 发表于 2012-8-31 22:46
嗯,首先感谢各位大侠这么及时的回帖,同时也非常感谢 souching 的分享。
1.我的程序在协议解析这一块就 ...


首先,明白一个概念,FIFO 只是负责数据缓冲。
其次,数据的超时处理并不需要太精确。例如9600 bps 时候,每个字节间隔时间约1ms左右。如果收到一个字节之后,2ms 3 ms 或者10ms没有收到数据皆可认为接收超时。

设计串口通信的第一原则就是中断接收,主程序处理。一般中断只负责收数据,则主循环中进行协议处理。大多数情况下面,主循环都是处理的过来的,除非你的程序结构非常之乱。
中断收到数据放到FIFO。主循环从FIFO中取数据,进行解析,解析的方式有很多种,状态机是其一。

void ComProtocolMainLoop(void)
{
        static enum
        {
         DECODE_INIT = 0 ,
        COM_DECODE_START,
        COM_DECODE_ANALYSIS,
        COM_DECODE_ACTION
        }s_eComState = COM_DECODE_INIT;
       
        uint8 u8Dat,u8CheckSum,u8Length,u8Addr ;
        uint8 i ;
        static uint8 s_u8aSendBuffer[9] = {0};
        static uint8 s_u8aRecvBuffer[9] = {0};       
        static uint8 s_u8TimeOut = 0 ;
               

        switch(s_eComState)
        {
                case COM_DECODE_INIT:
                                                        memset(s_u8aRecvBuffer,0,sizeof(s_u8aRecvBuffer)/sizeof(s_u8aRecvBuffer[0]));
                                                        memset(s_u8aSendBuffer,0,sizeof(s_u8aSendBuffer)/sizeof(s_u8aSendBuffer[0]));
                                                        s_eComState = COM_DECODE_START ;        
                break ;
               
                case COM_DECODE_START:
                                                        if(UartGetByte(USART_1,&u8Dat))
                                                        {
                                                                if(u8Dat == COM_DECODE_HEADER) //receive data frame header
                                                                {
                                                                        s_u8aRecvBuffer[0] = COM_DECODE_HEADER ;
                                                                        s_eComState = COM_DECODE_ANALYSIS;       
                                                                       
                                                                                                                                                                                                  }       
                                                        }                                       
                break;
               
                case COM_DECODE_ANALYSIS:
                                                                u8Length = UartGetBytesCount();   //wait get whole data frame
                                //time out process
                                                                if(s_u8TimeOut++ >250)
                                                                {
                                                                        //receive time out
                                                                        s_u8TimeOut = 0 ;
                                                                        //reset uart receive buffer
                                                                        UartClearRxBuffer();
                                                                        s_eComState = COM_DECODE_INIT;
                                                                }
                                                               
                                                            if(u8Length >= (COM_FRAME_LENGTH-1))  // if protocol is modbus 485 ,how to write ?
                                                            {       
                                                               
                                                                    //modify by chenhui.jiang 2011/4/18
                                                                        //UartGetBytes(USART_1,&s_u8aRecvBuffer[1],u8Length); //get receive data/
                                                                        UartGetBytes(USART_1,&s_u8aRecvBuffer[1],COM_FRAME_LENGTH-1); //get receive data/
                                                                       
                                                                         ///////////////////////////////////////////////
                                                                        //UartSendBytes(USART_1,&s_u8aRecvBuffer[1],COM_FRAME_LENGTH-1);
                                                                       
                                                                        u8CheckSum = ComProtocolCheckSum(s_u8aRecvBuffer,COM_FRAME_LENGTH-1); //cacl checksum
                                                                               
                                                                        //check data frame address
                                                                        u8Addr = ComProtocolGetAddr(&s_u8aRecvBuffer[1]);
                       
                                                                        if((GetDisplayAddr() == u8Addr) || (u8Addr == 0x00))  //check address
                                                                        {                                                               //0x00 return self address
                                                                                if(u8CheckSum == s_u8aRecvBuffer[8])      //check add sum
                                                                                {
                                                                                        s_eComState = COM_DECODE_ACTION;     //data frame is mine ,process it
                                                                                }
                                                                                else                                     //check sum error, data frame error
                                                                                {
                                                                                        CmdFrameError(s_u8aRecvBuffer);
                                                                                        s_eComState = COM_DECODE_INIT;
                                                                                }
                                                                               
                                                                        }
                                                                        else     //data frame is not mine ,abort it
                                                                        {       
                                                                                s_eComState = COM_DECODE_INIT;     
                                                                        }       
                                                           }//end if(u8Length >= (COM_FRAME_LENGTH-1))               
                break ;
               
                case COM_DECODE_ACTION:
                                        //SetLed(LED_ERROR,LED_ON);
                                        for(i = 0 ; i < (sizeof(s_fnaCmdAction)/sizeof(s_fnaCmdAction[0])) ; i++)
                                        {
                                                if(s_fnaCmdAction.m_eCmd == s_u8aRecvBuffer[3]) //execute cmd action
                                                {
                                                        (*s_fnaCmdAction.m_pFunc)(s_u8aRecvBuffer);
                                                        break ;
                                                }
                                        }
                                        //SetLed(LED_ERROR,LED_OFF);
                                        s_eComState = COM_DECODE_INIT;
                break ;
               
                default :
                break ;
        }
}

COM_DECODE_START 状态即进行了帧同步状态处理
COM_DECODE_ANALYSIS 状态等待数据帧全部到达,并有超时处理。当数据帧完整到达时候,进行帧解析,地址匹配等等。如果数据帧校验正确,且地址码为本机或广播地址,则跳转到状态
COM_DECODE_ACTION 进行命令解析。命令解析部分采用表驱动方式,方便扩展。当有新命令需要增加和处理时候,整个解析函数均不需要做任何修改,只是需要定义命令码及其对应的响应函数即可。
上述代码结构清晰易读易懂,可扩展性强。

出0入0汤圆

发表于 2013-4-4 21:06:47 | 显示全部楼层
,好帖,看了之后受益匪浅

出0入0汤圆

发表于 2013-4-4 22:14:06 | 显示全部楼层
好贴,mark

出0入0汤圆

发表于 2013-6-5 10:24:02 | 显示全部楼层
好贴,MARK

出0入0汤圆

发表于 2013-6-5 11:48:46 来自手机 | 显示全部楼层
在中断中接受,发现帧头后置位标志,开始往fifo中扔数据,接受到帧尾后置位标志,主循环中校验,无误后解析。

出0入0汤圆

发表于 2013-6-5 13:23:07 来自手机 | 显示全部楼层
好帖,学习了

出0入0汤圆

发表于 2013-8-31 09:28:44 | 显示全部楼层
好贴,学习了

出0入0汤圆

发表于 2013-8-31 18:55:03 | 显示全部楼层
好贴

出0入4汤圆

发表于 2013-8-31 21:45:54 | 显示全部楼层
中断只负责把UART接收的数据保存起来,在MAIN_LOOK里面进行处理。

出0入0汤圆

发表于 2013-8-31 22:20:31 | 显示全部楼层
哥们,你的帧头和帧尾需要4字节码?太浪费资源了吧。

出0入0汤圆

发表于 2013-9-1 09:02:28 | 显示全部楼层
很好,学习了。

出0入0汤圆

发表于 2015-6-3 14:26:26 | 显示全部楼层
mark    说的好  就是没有对应源码  现在理论上都理解了  就是还没用程序实现   所以还是停留在理论阶段
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片。注意:要连续压缩2次才能满足要求!!】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-8-25 23:14

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表