单片机论坛

 找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 4242|回复: 17
打印 上一主题 下一主题
收起左侧

51单片机RS485通讯程序仿真与MODBUS RTU静止时间详解

  [复制链接]
跳转到指定楼层
楼主
单片机RS485通讯的程序,工作中的成熟应用案例。分享给大家。

仿真原理图如下(proteus仿真工程文件可到本帖附件中下载)



由于MODBUS-RTU 要求每帧信息中的数据间隔时间不得超过1.5字符的静止时间。
  因此:
   (1)、当串口产生接收中断后,立即重装“超时定时器”的初始值。(注:超时定时器的初始值和波特率有关)
  问题如下: 
   (a)、由于要求在串口接收中断中重装超时定时器初始值,那么,该超时定时器就只能采用“溢出模式”,而不能采用“CTC模式”
   (b)、超时定时器必须在程序初始化时就一直开启。
  我的理解对吗?
(2)两帧之间的3.5字符的静止时间该如何实现呢?
接收到每个字节的时候,初始化定时器就行,最后一个字节后,定时器就溢出了
利用单独的软件定时器,来判断一帧接收报文结束,可以防止若报文接收不完整,该帧通信任务无法结束而影响下一帧的接收。
  由于一帧报文中字节与字节之间的时间间隔和帧与帧之间的时间间隔相比要小得多,因此每当接收一个新字节,就启动软件定时器开始计时,定时器的时间设定为帧与帧的最小时间间隔。波特率不同,该时间间隔也不同。若不到预定的时间内又接收到下一个字节,则说明一帧报文未结束,定时器重新计时;若定时器顺利计数到预定时间,就会触发相应的中断号,在该定时器中断子程序中设定帧结束标志字节,表明一帧报文接收完毕。当主程序内检测到一帧报文接收完毕后,会通过核查从方地址及循环冗余校验字节是否正确来判断该帧的有效性。若确定接收到的是一帧发送给已方的正确报文,则会根据报文内的功能码对该帧命令进行相应的处理,并准备发送帧。
上面就是解决以下两个问题的方法
  (1)、当前帧两个字节之间的1.5字符静止时间
  (2)、两帧之间3.5字符的静止时间
这里有个SPI串口扩展芯片uCSU122P,内置MODBUS引擎,DIP28,或许对你有用点击此处下载  (原文件名:ucmu2_dat_v122.pdf)
其实这个时间不用这么准确啦,因为是问答式的协议,你可以以某个定时时间查询串口缓冲区字符的长度,如果两次读入的长度一样就认为一帧结束了,这个查询间隔根据波特率微调,就是3.5个字符时间。
7楼的做法不严格,如果第一次定时查询的时候正在收最后一个字节,第二次查显然收完了,第三次查数据不变,那么就导致了7个字符的间隔,如果对方在3.5~7字符之间又来了数据,就麻烦了;

T1.5和T3.5最严格的方法还是开定时器,但是可以灵活一点;低波特率(<19200)的时候严格定时,和波特率相关;高波特率(>19200)的时候就固定定时(T1.5=750us,T3.5=1750us),这样降低了CPU中断响应的负担。
给你这个程序片段应该可以解决你的问题,我的程序经过严格的测试,高扫描周期、波特率19200下连续运行了一个星期,没出一个错误


#pragma interrupt_handler Timer1:iv_TIMER1_OVF  
void Timer1(void)
{
  unsigned short CRC;
  TCNT1=65525-51*11;//65535-(11*(ubbr+1)) 波特率9600   
  if(CNT<8)
  {
     CNT++;        
     if(CNT==4)
     {
    ModBusQueryDataLong=IsrCount;
    IsrCount=0;
     }
     else if(CNT==8)
     {   
    if(ModBusQueryDataLong>2)
{  
   
       CRC=CRC16((unsigned char *)&ModbusFunctionUnion,ModBusQueryDataLong-2);
   if((ModbusFunctionUnion.Data[ModBusQueryDataLong-2]==MSB(CRC))&&
          (ModbusFunctionUnion.Data[ModBusQueryDataLong-1]==LSB(CRC)))
{   
              FrameStatu=1;
    }   
    }   
     }
  }   
}
#pragma interrupt_handler UART_isr:iv_USART0_RX
void UART_isr(void)
{  
  CNT=0;  
  while(!(UCSR0A&(1<<RXC0)));
  ModbusFunctionUnion.Data[IsrCount++]=UDR0;
}



单片机源程序如下:
  1. /*------------------------------------------------------------------------------
  2. MEASURE.C:  Remote Measurement Recorder using the C51 COMPILER

  3. Copyright 1990-2005 Keil Software, Inc.
  4. ------------------------------------------------------------------------------*/
  5. #include "includes.h"

  6. sbit       CS=P1^0;                                  //看门狗端口
  7. sbit       ctrl_485=P1^5;         //RS485控制端口

  8. unsigned char RxBuf[LenRxBuf];           //接收缓冲区
  9. unsigned char *inRxBuf,*outRxBuf; //接收缓冲区指针
  10. /***************判断静止字符**************/
  11. unsigned char  time_3;
  12. unsigned char  time_2;
  13. bit                  rt_rxbuf;
  14. bit                   rt_ctrl;
  15. /***************接收字符**************/
  16. unsigned char  ComBuf[MaxLenComBuf+1] ;
  17. unsigned char  ch;
  18. unsigned char  k1;
  19. unsigned char  n1;
  20. int          i=-1;
  21. int State=StatInputCom;


  22. /*
  23. *********************************************************************************************************
  24. *                                                                                 串口初始化函数
  25. *********************************************************************************************************
  26. */
  27. void InitSerial()
  28. {
  29.         TMOD=TMOD&0x0F;
  30.         TMOD=TMOD|0x20;
  31.         TH1=0xF4;//2400 , 11.0592MHz
  32.         TL1=0xF4,
  33.         SCON=0x50;PCON=0x00;
  34.         TR1=1;
  35. }
  36. /*
  37. *********************************************************************************************************
  38. *                                                             缓冲区初始化(只定义接收缓冲区)
  39. *********************************************************************************************************
  40. */
  41. void InitSerialBuffer(void)                   //串口缓冲区初始化
  42. {
  43.         inRxBuf=RxBuf;outRxBuf=RxBuf;
  44.         ctrl_485=0;                              //接收模式
  45.         ES=1;
  46.         EA=1;
  47. }
  48. /*
  49. *********************************************************************************************************
  50. *                                                                  定时器函数
  51. *********************************************************************************************************
  52. */
  53. void timefunc(void)
  54. {        unsigned char k;
  55.         unsigned char *tt;

  56.         if(!rt_ctrl)  time_2++;          //判断静止时间,配合下面添加结束符
  57.     time_3++;

  58.         if(time_2>20&&!rt_ctrl)
  59.         {   //ES=0;
  60.                 for(k=0;k<4;k++)
  61.                 {
  62.                         tt=inRxBuf;tt++;                        //数据暂存        5555
  63.                         if(tt==RxBuf+LenRxBuf) tt=RxBuf;        //                5555
  64.                         if(tt==outRxBuf) {break;}          //如果缓冲期满停止加结束标志  5555
  65.                         else
  66.                         {
  67.                                 if(k==0)*inRxBuf=0x4A ;
  68.                                 if(k==1)*inRxBuf=0x59 ;
  69.                                 if(k==2)*inRxBuf=0x48 ;
  70.                                 if(k==3)*inRxBuf=0x59 ;
  71.                                 inRxBuf=tt;
  72.                         }        //          5555
  73.                 }       
  74.                          rt_ctrl=1;
  75.                                                          //  ES=1;                                                          
  76.         }

  77. //        time_1++;
  78. //        time_4++;//接收数据时间判断

  79. }
  80. /*
  81. *********************************************************************************************************
  82. *                                                                串口接收
  83. *********************************************************************************************************
  84. */
  85. void serial(void) //串口中断服务子程序
  86. {  
  87.         unsigned char *t;

  88.         if(RI)
  89.         {
  90.                 RI=0;
  91.                 if(time_3>4)   //判断静止时间
  92.                 { time_3=0;
  93.                         //         rt_rxbuf=0;
  94.                         rt_rxbuf=1;        // if(SBUF==0x01)rt_rxbuf=1;
  95.                                         // time_3的作用是为判断静止字符之后必须为i本机地址,否则不接受数据,判断下一个静止字符
  96.                 }
  97. //                 rt1_ctrl=0;
  98.                  time_3=0;

  99.                 if(rt_rxbuf)
  100.                 {
  101.                         rt_ctrl=0;
  102.                         time_2=0;//如果有中断,将时间清零          5555
  103.                         RI=0;                        //清接收中断标志
  104.                         t=inRxBuf;t++;        //缓冲器中,当前尾端指针存入t
  105.                         if(t==RxBuf+LenRxBuf) t=RxBuf;         //判断缓冲器是否指向最后,如果T为缓冲器最后,重新指向开始
  106.                         if(t==outRxBuf) return;         //RxBuf Full。缓冲器满,(缓冲器命令并未执行,则为满),停止接受
  107.                         *inRxBuf=SBUF;                                //如果缓冲器未满,将接受到的数据存入缓冲器中,
  108.                         inRxBuf=t;                                        //缓冲器尾端指针移向下一位
  109.                 }
  110.         }
  111. }

  112. //void serial(void) //串口中断服务子程序
  113. //{
  114. //    unsigned char *t;
  115. //    if(RI)
  116. //    {       
  117. //        RI=0;
  118. //        t=inRxBuf;
  119. //                t++;
  120. //                if(t==RxBuf+LenRxBuf) t=RxBuf;
  121. //                if(t==outRxBuf) return;
  122. //                *inRxBuf=SBUF;
  123. //                inRxBuf=t;
  124. //    }
  125. //}

  126. /*
  127. *********************************************************************************************************
  128. *                                                                    定时器0初始化
  129. *********************************************************************************************************
  130. */void InterruptInitial()
  131. {
  132.         /* set timer. 50ms, THTL = 3caf when cpu at 12MHz */
  133.         TMOD |= 0x01;
  134.         TH0 = 0xEE;
  135.         TL0 = 0x00;
  136.         /* TR0 is set in OSStart() */
  137.         TR0=1;   /*启动定时器0 */
  138.         ET0 = 1; /*定时器中断开*/       
  139. }
  140. /*
  141. *********************************************************************************************************
  142. *                                                                     看门狗复位
  143. *********************************************************************************************************
  144. */
  145. void rst_wdog()
  146. {
  147.         CS = 0;
  148.     _nop_();
  149.         _nop_();
  150.     CS = 1;
  151. }
  152. /*
  153. *********************************************************************************************************
  154. *                                                                  从串口缓冲区读数据
  155. *********************************************************************************************************
  156. */
  157. bit yygetch(unsigned char *ch)                  //从串口缓冲区读1字节数据
  158. {
  159.         //ES=0;        
  160.         if(inRxBuf==outRxBuf) {ES=1;return 0;}  //RxBuf Empty
  161.         *ch=*outRxBuf;  outRxBuf++;
  162.         if(outRxBuf==RxBuf+LenRxBuf) outRxBuf=RxBuf;
  163.         //ES=1;        
  164.         return 1;
  165. }
  166. /*
  167. *********************************************************************************************************
  168. *                                                                  从串口缓冲区读数据
  169. *********************************************************************************************************
  170. */
  171. void getstr(void)
  172. {
  173.         switch(State)
  174.         {
  175.                 case StatInputCom:         //接收数据
  176.                 {
  177.                         if(yygetch(&ch))
  178.                         {
  179.                                 if(k1==3){if(ch==0x59)k1=4;else k1=0;}
  180.                                 if(k1==2){if(ch==0x48)k1=3;else k1=0;}
  181.                                 if(k1==1){if(ch==0x59)k1=2;else k1=0;}
  182.                                 if(ch==0x4A)k1=1;

  183.                                 if(k1>=4)        /*Enter return key*/
  184.                                 {
  185.                                         k1=0;
  186.                                         ComBuf[i+1]='Y';        //设                               
  187.                                         if(i+1==0) {;}                 //如果无接收到字符,不处理
  188.                                         else {State=StatExeCom;}
  189.                                 }
  190.                                 else
  191.                                 {
  192.                                         i=i+1;         //保存字符到缓冲器
  193.                                         if(i>=MaxLenComBuf){i=MaxLenComBuf-1;} //如果缓冲器满了,将数据存入最后一个地址
  194.                                         ComBuf[i]=ch;                       
  195.                                 }
  196.                                         break;
  197.                         }
  198.                         else
  199.                         {
  200.                                 break;
  201.                         }
  202.                 }        //case StatInputCom
  203.                 case StatExeCom:         //接收完毕后,解析数据
  204.                 {         n1=ComBuf[1];
  205.                  PrintStr1(ComBuf,10);
  206.                          n1=0;
  207.        
  208.                         State=StatInputCom;
  209.                         i=-1;
  210.                         break;                               
  211.    
  212.             }        //case StatExeCom       
  213.         }        //switch
  214. }
  215. /*
  216. *********************************************************************************************************
  217. *                                                                  发送字符串
  218. *********************************************************************************************************
  219. */
  220. void PrintStr1(unsigned char *str,unsigned char n)//显示字符串
  221. {
  222.         int i;
  223. ……………………

  224. …………限于本文篇幅 余下代码请从51黑下载附件…………
复制代码

所有资料51hei提供下载:
01.02.26通讯.rar (77.34 KB, 下载次数: 135)



分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
收藏收藏3 转播转播 分享分享 分享淘帖 顶1 踩
回复

使用道具 举报

沙发
xinxiaoci 发表于 2018-4-13 13:09 | 只看该作者
谢谢楼主的帖子,我会仔细研究的,努力学习中。
回复

使用道具 举报

板凳
ccppww806 发表于 2018-5-12 20:58 | 只看该作者
刚好正在学习这个,真的很感谢
回复

使用道具 举报

地板
mick32 发表于 2018-5-16 05:13 | 只看该作者
Just wonder
Is it faster to use lookup table instead of calculating CRC
Thanks
回复

使用道具 举报

5#
livingchina 发表于 2018-6-21 07:53 | 只看该作者
好东西,学习了,可惜没有 黑币
回复

使用道具 举报

6#
yansome 发表于 2018-12-8 10:00 | 只看该作者
学习学习先,感谢楼主分享
回复

使用道具 举报

7#
yansome 发表于 2018-12-8 10:01 | 只看该作者
只言片语,不能窥其全貌,遗憾
回复

使用道具 举报

8#
ybx1 发表于 2019-5-22 13:53 | 只看该作者
谢谢楼主
回复

使用道具 举报

9#
littlejia 发表于 2019-6-1 08:26 | 只看该作者
正在学习modbus,感谢楼主无私奉献!
回复

使用道具 举报

10#
腾飞的龙 发表于 2019-6-3 16:28 | 只看该作者
感谢楼主分享,学习啦。谢谢!
回复

使用道具 举报

11#
littlejia 发表于 2019-6-15 06:22 | 只看该作者
proteus仿真的,好,方便测试
回复

使用道具 举报

12#
littlejia 发表于 2019-6-15 06:23 | 只看该作者
mick32 发表于 2018-5-16 05:13
Just wonder
Is it faster to use lookup table instead of calculating CRC
Thanks

查表是要比计算快,但表格是不太占地方了
回复

使用道具 举报

13#
littlejia 发表于 2019-6-15 06:55 | 只看该作者
是要同过串口连接modbus主机软件来仿真么?下载后发现电路好多标号都是独立的,根本就没有连接的,LED0--LED7等,感觉是几个电路拼接到一起的,运行后无法测试
回复

使用道具 举报

14#
gongzhu 发表于 2019-7-11 10:54 | 只看该作者
以前没在意modbus帧间隔时间,现在正为它头疼
回复

使用道具 举报

15#
懒散专业户 发表于 2019-7-17 17:13 | 只看该作者
这个应该是最全的485通讯了,正好学习下,感谢楼主分享
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|手机版|小黑屋|单片机论坛 |51黑电子论坛单片机 联系QQ:125739409;技术交流QQ群582644647

Powered by 单片机教程网

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