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个应该够了.
超限似乎意味着--->异常.



