ATmega16的Modbus通讯技术

发布: 2009-9-14 15:20 |  作者: 玲珑 |   查看: 235次

基于ATmega16的Modbus通讯技术计算机、单片机(M16)收、发自如。-----zhzzh18 小章

unsigned int cal_crc(unsigned char *ptr, unsigned int len) 
             {  
              unsigned int crc=0xffff;  
              unsigned char i;  
              while(len!=0)
           {  
            crc^=*ptr;
               for(i=0;i<8;i++)
            {  
                if((crc&0x0001)==0) crc=crc>>1;
             else 
            {
              crc=crc>>1;
              crc^=0xa001; 
            }                    
               } 
            len-=1;
            ptr++; 
              }  
              return crc;
             }  
          
void fenli(unsigned int data,unsigned int data1)
     {
     send_data[data1]=(unsigned char)(data);
     send_data[data1+1]=(unsigned char)(data>>8);
    }

unsigned int zhuhe(unsigned char data1,unsigned char data)
    {
    unsigned int t;
    t=data1;
    t=(t<<8)+data;
    return t;
   }
   
void send_run(void)
    {
    if(send_sp>num_send-1)
    {
     send_sp=0;
     flag_send=0;
    }
    else
    {
     UDR=send_data[send_sp];
     send_sp++;   
    }
    num_stop=0;
   }
   
void error(unsigned char data)
     {
     unsigned char add;
     num_send=5;
     send_data[1]=0x11;
     send_data[2]=data;
     add=cal_crc(send_data,3);
     fenli(add,3);
    }
    
void data_act(void)
     {
     unsigned int add,i,n;
     send_data[0]=address;
     add=zhuhe(recieve_data[2],recieve_data[3]);
     add=add*2-2; /*µØÖ·*/
     if(add>49) error(2);
     else
     {
      if(recieve_data[1]==3)
      {
      n=zhuhe(recieve_data[4],recieve_data[5]); /*³¤¶È*/   
      num_send=6+n;
      send_data[1]=recieve_data[1];  /*¹¦ÄÜ*/
      send_data[2]=recieve_data[4];
      send_data[3]=recieve_data[5];  /*³¤¶È*/
      for(i=0;i<n;i++)
      send_data[i+4]=variable_data[i+add];
      n=4+n;
      i=cal_crc(send_data,n);
      fenli(i,n);
     }
     else if(recieve_data[1]==6)
     {      
      num_send=num_recieve;
      for(i=1;i<num_recieve;i++)
      send_data[1]=recieve_data[1];   
     }
     else error(1);
     }
    }
    
unsigned char crc_recieve(unsigned char data)
       {
       unsigned int crc0,crc1;
      crc0=cal_crc(recieve_data,num_recieve);
      crc1=zhuhe(recieve_data[data-1],recieve_data[data-2]);
      if(crc0=crc1) return 1;
      else return 0;
      }
   
    
void time1compatt(void)                  
    {   
     unsigned char flag;
     if(flag_send==2) send_run();
     num_stop+=1;
     if(num_stop>3)
     {         
      if(flag_send==1)
      { 
       flag=crc_recieve(num_recieve);
      if(flag==1)
      {
       data_act();
       flag_send=2;
       num_recieve=0;
      }
      }
      num_stop=0;                 
     }
    }
    
#pragma interrupt_handler usrt_resieve:12    
void usrt_resieve(void)                  
    {   
     recieve_data[num_recieve]=UDR;
     num_recieve+=1;
     num_stop=0;
     flag_send=1;
    }


     单片机与PC机通讯调试步骤: -----zhzzh18 小章
     1:确定PC机通讯没问题,方法:把PC机串口的数据发送脚与数据接收脚连在一起,看接收的数据是不是发送的数据
     2:确认接线的正确性
     3:PC机与单片机双方协议一定

     我建议你:一不一步来
     1:先发送一个字节数据,看对不对
     2:在发送一串数据,看对不对
     3:你发送,对方应答,你再接收,看对不对
     4:最后才做校验。



"zhzzh18 小章"大侠,你的程序我认真读过了, 写的真是不错. -----Asail

移植到我的程序中,基本调试通过.(在不发错的前提下,通讯正常)
我想还是把遇到的问题说一下, 大家讨论,共同进步.

我觉得前辈的程序, 某一个地方缺乏容错性,就是不够健壮.
如下:
<漏洞1>
flag=crc_recieve(num_recieve); 
      if(flag==1) 
      { 
       data_act(); 
       flag_send=2; 
       num_recieve=0; 
      } 
..................      

也就是说,只有CRC16通过,才能使flag=1, 才能执行内部的程序.
如果CRC16没有通过,那么num_recieve不会清零.
这样在随后的,crc0=cal_crc(recieve_data,num_recieve)语句中
那个发错而没有得到清楚的数据一直参与CRC检测运算, 并且cal_crc
永远不会返回1,不会通过.......
连锁反应:num_recieve得不到清零,recieve_data早晚要溢出.

结论:一个坏数据,会影响整个程序!!
ps:我知道大家会编写通信的上位机软件,通过软件做界面的通讯似乎不会
有数据错发, 但是我不敢确定没有异常状况造成数据错发,一旦有,全完了,等复位吧.

针对这个漏洞,我想应该加入判断机制,从接受到完整贞到处理回应一定是在有限时间
内完成的. 这样当num_recieve不为0时,判断它的变化情况, 一个例子,超过2秒还没有
变化, 应该就是数据错误造成的了. (我说的2秒当然不够精确,具体的时间需要大家验证)


<漏洞2>
这个.......不知道算不算漏洞, 因为大家的理解也许不一样.
如下:

crc0=cal_crc(recieve_data,num_recieve); 
crc1=zhuhe(recieve_data[data-1],recieve_data[data-2]); 
if(crc0=crc1) return 1; 
else return 0; 

大家看cal_crc的参数, 假如已经收到了一个完整的贞
假设num_recieve=8 这样 第7个和第8个数据分别是crc16_high,crc16_low 
做为输入cal_crc计算却是8,整个贞全参与了cal_crc!!!
如果这样的话,crc0=crc1怎么可能相等呢??
应改成,crc0=cal_crc(recieve_data,num_recieve-2);
用前6个数据参与校验, 然后结果和第7,第8数据进行比较,如果相等,则通过.
程序应该是这样的吧......

其中对(num_recieve-2)要加入长度判断,防止在num_recieve<=2时执行语句.
实际上modbus协议的最小长度贞一般应该大于7个字节. 所以num_recieve>7再cal_crc( )吧


针对第2个漏洞,改正后,在硬件上调试通过.
如下: 串口助手输入: 05 03 00 01 00 01 4E D4  
      串口助手输出: 05 03 00 02 00 13 43 A4 

就写这么多吧. 



to:Asail 
    不好意思,我休息了一段时间,没上网,几天才看到,不好意思
    第一个问题非常好,一个人的思维总是有他的局限的,需要别人的指点,三人行必有我师焉。应该把num_recieve=0;  
放在if(num_stop>3) {  这后面。
   第二问题,也有道理
  谢谢   Asail   -----zhzzh18 小章


还有一个问题: -----igoal
#pragma interrupt_handler usrt_resieve:12     
void usrt_resieve(void)                   
    {    
     recieve_data[num_recieve]=UDR; 
     num_recieve+=1; 
     num_stop=0; 
     flag_send=1; 
    } 
中 是否应该加一句判断num_recieve超限的语句呢?要不然接收中断一直有效的话就要超出数组recieve_data[num_recieve]的能力了。如果数组定义了20个字节的大小,那么可以在接收中断的末尾加两句
if(recieve_data[num_recieve]>=20)
    num_recieve=0;
就可以了。 

zhzzh18 小章前辈,不客气. -----Asail

三人行必有我师焉.
你能把自己的modbus程序完全贴出来,首先你就是大家的老师.大家最应该感谢你.

楼上的igoal兄弟,我觉得你的修改方法好象欠妥.(只是个人看法)

num_recieve的清零应该建立在正确应答的基础上.
如果超出数组的容限,应该做出错误应答及时报告出来.

理由如下:
由于AVR的处理速度很快,我认为recieve_data只要做到存储下一贞完整的
通讯数据即可.收到完整贞后,即清零并回应.
而数据贞的大小是由modbus协议所决定的.20个应该够了.
超限似乎意味着--->异常. 
TAG: 通讯技术 Modbus
打印 | 收藏此页 |  推荐给好友 | 举报
上一篇 下一篇
jsnjchenyue (2010-7-03 21:42:42)
楼主写的非常详细,我收下了
 

评分:0

发表评论
查看全部回复【已有1位网友发表了看法】

开发板推荐导购

 电子园服务子站:  电子园主站   电子园论坛    电子园社区    电子园商城    电子园百科    开发工具网    项目交易网    在线学习网
 电子园技术子站:   51单片机学习网   USB开发学习网   AVR单片机学习网   CAN总线学习网   PIC学习网   FPGA学习网   ARM学习网   DIY学习网   STM32学习网
  DSP学习网   EDA软件学习网   GPS开发学习网   GUI技术学习网   电源技术网   RF射频技术网   汽车电子技术网   医疗电子技术网   消费电子技术网
 助学开发板资源:  51单片机开发学习板   USB开发学习板   AVR M64单片机开发学习板   AVR M16单片机开发学习板   CAN总线开发学习板