1、MODBUS 总结MODBUS总结这几天终于可以暂时的放松一下,说实话做技术有时真的很累,而有事也很清闲,当项目下来的时候也就是忙碌的开始,当项目验收成功后那种久违的满足兴奋感着实让人舒服、向往。其实做技术也挺有趣我喜欢那种“过关斩将”的感觉。但是技术真的需要不断的充电,因为更新实在太快了干本赶不上当今潮流。做技术一定要抓住技术的实质,这样才能灵活运用,正所谓万变不离其宗。做MODBUS通讯协议已经有一段时间了,回头看看这段漫长的路其中充满了一次一次的失望又一次有一次的喜悦,感觉很开心。想当初第一次接触MODBUS时,感觉没有什么难得不就是一个串口通信么?但是自己太过清高了,其实任何一项技术如
2、果要真真正正的掌握不下一番功夫,不经过一次次失败是不可能。关于MODBUS TCP协议我不做讲解,这个我没有做。至于MODBUS RTU 与MODBUS ASCII协议的格式我也不再多说,这些网上太多了,我只想把我自己遇到的问题以及很多容易忽略的细节做一下阐述(下文主要是从从机的角度考虑):1 通信速率如果我们用单片机做MODUS通讯,一定要注意单片机的晶振。很多人不要不在意,这是最为关键的硬件部分,如果晶振不正常或者选型不匹配,那么一切都是扯淡。不怕大家笑话,我在用51单片机做MODUBS RTU通讯实验时,为了查看单片机发送的命令字节,我用八个LED灯作为每个字节的显示,可是我发现单片机只
3、能正确的接收若干个字节的数据,而其他的都是错误的,起初我总怀疑是自己的软件设计有问题,可是改了很多遍也没有解决,很是郁闷啊 。可是我相信做技术的都有这样的经历,当你快要放弃的时候突然来了灵感,问题被很干脆的解决。我暂时放系软件去做别的事情,因为高技术最忌钻牛角尖,当遇到困难时要跳出来再跳进去。呵呵,引用我们领到的一句话,感觉说的很对很好。接下来的日子里,偶尔的一天我在焊接硬件电路时,突然眼睛定格在晶振上,一下子我全明白了,原来自己的晶振用错了(原本是11.0592M,自己用成了12M),随即换上问题不复存在。可是你们知道么就这样一个个小小的疏忽将近花费了经耗费了我一个星期的时间。当然晶振没有问
4、题时并不能保障你能够正确的接收信息,如果你的主从机波特率设置不同那出错也是在所难免的。2 A B端接反这也是很多人在不经意的时候犯下的小错误,一般很容易发现,但是不排除马大哈。3 MODBUS协议属于典型的主从问答式协议,只有在主机发送命令后,从机正确接收后才能响应主机命令,决不能主动上传。4 编写程序时必须严格的按照MODBUSRTU/ASCII通讯协议的格式发送数据特别要注意CRC/LRC校验码高位在先,低位在后。5 CRC校验码的的获得常见的有两种方式一是通过查表法,二是计算法。查表法速度更快,但是占用较大内存;计算法速度稍慢但是程序代码简洁。6 如果我们物理层上走的是232电路,数据的
5、方向无需控制额外控制,但是如果我么采用485电路,那么这时候由于多了一个数据方向控制端口,请务必留意控制信号要和当前的数据流向一致,避免数据冲突。(这也是初学者容易犯错的地方)。7 还有一个奇怪的现象,比如主机发送功能码03 ,当需要读取的字节很少时,从机很正常的回复,但是当数据超过一定数量后我们会发现出现了通讯超时的现象。后经分析发现是定义的发送缓冲区太小了。在C语言中经常建议我们在定义数组时确定数组的大小,即使不确定数组大小(多维除外)系统也能识别。有些时候我们忽略了数组的大小而去引用数组边界的数组元素(这样说不太恰当)举个例子:int table10=0; /定义了一个含有10个元素的整
6、型数组int a1; /定义了一个整型变量a1a1=table10; /对a1赋值其中对a1赋值这一语句在编译器中是不会报错的,尽管有些时候我们没有发现程序有错误,但是在默写情况下会引起某些不必要变量值的改变,从而引起系统崩溃。在程序中引用越界数组元素是一个很大的漏洞,会给程序稳定性留下很大的隐患,而且一般这类问题不易察觉。8要善于总结规律,细心发现。比如:在MODBUS RTU协议中主机发送功能码01 02 03 04 05 06时,其发送一帧数据的长度是固定的 (8个),即使功能码15 16我们也可以找到规律。这为我们编写程序提供了某些方便。我们可以根据从机接收的字节个数来判断主机命令帧是
7、否结束,因此在网上我们会发现有相当一部分程序中从机是通过该方法判断主机命令帧结束的。但是我建议大家不要这样用,如果应用不好会带来很多麻烦很多问题,接下来我将重点讨论这点。9 如何判断一帧数据是否接收完毕?在说明之前我先叙述一下我的经历:原本我是拿着自己编写的程序加载的实验板上和某公司生产的采集模块的MODBUS RTU通讯比较试验效果,我采用的正是依据个数来判断一帧数据结束,我惊奇的发现我的通信灯闪烁的频率远比它的快(通过通信灯可以查看通信速率),这下我高兴坏了,感觉他们编写的程序也不过如此,现在想起来真是好笑,自己太肤浅了。首先人家模块里不单单只是MODBUS通讯一个任务,还有SPI通讯,继
8、电器状态判断等多个任务,而我的程序只有一个任务,根本没有可比性嘛。再者人家是一个成型的产品,性能不好能占领那么大的市场么,哎太大言不惭了,羞愧难当啊!我在做实验时,主机只连接一个从机模块,效果相当好,正当我满怀信心的向领导汇报时问题来了,来的真打击人。当主机只连接一个模块确实相当给力,没有出现任何错误,领导也确实很满意,但是紧接着领导说,你多给几个模块通讯,看看是不是正常毕竟MOBUS是一主机多从机的通讯模式。当时的我当然不怕了,很快的连接上两个模块正准备看到那可爱的小精灵闪烁时,等了好久也没有见到,等来的却是触摸屏上显示的”Timeout error”.通讯根本不正常,一点都通不上,当时真是
9、失望到了极点,我苦苦努力地几个月就这样流产了吗,我不甘心。于是开始查找问题,首先从硬件入手,我怀疑是我的485芯片的问题,因为我用一个自己的模块和人家公司生产的模块同主机通讯时,尽管也出现了通讯超时,但是至少偶尔也能成功,于是我把人家的485芯片拆下来换到我的模块上实验,结果又一次让我失望,非常失望,还是一点都通讯不上。当我排出了硬件上可能存在的问题发现效果如初。看来问题不在硬件上,还是软件的问题,可是问题究竟出在哪个地方呢?由于第一次做MODBUS通讯,自己经验不足,接下来我就在网上寻求高人指点,可是人家根本不鸟你啊,更有甚者说别做了把你的要求给我,我给你写,一个功能码50,你看如何。我哪受
10、得了这般,当时就憋着一股劲,一定要把MODBUS解决.我一点一点的分析程序,任何一个串口通讯无外乎接收与发送两部分,发送由于是CPU主动控制不应该有太大的问题,很有可能是接收数据出现了问题,所以我重点分析接收中断中的数据处理部分。在这里插入一段小话题,有很多资料上说我们有时为了提高CPU的处理能力,当我们判断第一个字节不是本机地址时(假设主机发送的数据帧正确),那么该帧数据就不要接收了,直接丢掉;另一种说法是所有的从机全部接收主机发送的命令帧,当数据接受完毕时再对数据进行处理,如果是发给自己的再进行数据正确性判断。事实证明,这两种方法如果能够正确运用都是对的,但是关键就在于如何正确的运用。第一
11、种:如果接收到的第一个字节不是本机地址,直接丢掉,怎么丢掉?我相信很多人摇摇头,可能有人会说,禁止中断不就行了么,不错的确可以避免接收该帧数据,但是我问一下,你下次中断在什么时候打开?你是如何判断该帧数据接收完毕的?先说一下我的思路:当从机接收到第一个数据时首先判断是否与本机地址相符,如果不符合,那么接收缓冲区地址不偏移,直到道接收到某一个字节和本机地址匹配时才开始接收该帧数据,然后根据功能码计算出该帧数据的有效长度去判断该帧数据是否接收完毕。当初毕竟经验少,现在想想真是漏洞百出啊。假设某一时刻主机发送功能码如下(读取从机1寄存器起始地址为2的两个字节数据):01H 03H 00H 02H 0
12、0H 01H CRCH CRCL地址码 功能码 地址高低字节 数量高低字节 校验高地字节漏洞1:按照自己的思想当主机发送第二个字节(功能码03H)时,从机3就会错误的认为该字节数据于本机地址相符,就会启动接收,不做太多讨论,结果必定错误。漏洞2:从机将无法正确的计算该帧数据的长度。按常理说03H功能码的数据长度为8个字节,但是如果我们在程序中通过功能码计算数据帧长度时,问题将不期而至,试问从机03想当然的以为其后00H是功能码进而去计算长度,请问你见过功能码00 H 么?再说了你需要的是功能码03H啊。漏洞3:现在我们假设功能码03 H后面是03H,即从机3可以识别“有效功能码”并计算出该帧数
13、据的长度8,但是问题又来了,主机从03H功能码到CRC校验码共计7个字节的数据,从机3之多接收这七个数据,而当从机1响应(假设接收正确)或主机重发(接收失败,响应超时),那么这时候从机3只需再接收一个字节便认为该帧数据结束,从而置位接收玩标志,进而在主程序中做数据处理。我写着就笑了,这错误太低级了。漏洞4:有可能造成一连串的通讯超时。假设主机发送02H 03H 10H 02H 00H 01H CRCH CRCL地址码 功能码 地址高低字节 数量高低字节 校验高地字节 当从机3正确的识别所谓的地址码03H与功能码10H后,开始计算出该命令帧的长度,我们知道对于主机发送的功能码10H,对应该帧数据
14、长度为 Length=rx_buf6*2+9; 这里从机3缓冲区中的rx_buf6= CRCL;如CRCL较小那么Length也较小,假设Length=16;那么会出现漏洞3所说的那种错误,但是这种错误在较短的时间不会影响系统,当从机3再接收到10个字节就可认为该帧数据接收完毕,假设从机2正确的响应主机,而且响应数据帧长度大于10个,那么当从机3接受到10个后置位接收完标志,在主程序中判断该帧数据不是发给自己的,于是清除缓冲区,准备接收下一帧数据,如果从机3足够幸运,在主机发送命令之前完成准备工作,那么它将能正确接受到主机发给自己的命令帧;但是当CRCL较大时,情况就变得更为复杂,可能主机发送
15、N=重试次数,从机3还认为没有接收完毕,那么这是主机就认为从机3出现了故障,放弃了对它的询问。洛里啰嗦输了这么多,当中引用了大量的假设,这意味着程序中充满了太多的不确定性,自然出错的概率不言而喻。分析:造成上述错误的原因是从机不能有效的分辨出同本机地址相符的究竟是地址帧还是数据帧。为此我提出了两种解决问题的方法:方法1:无论从机收到的第一个字节数据与本机地址相符与否都放到缓冲区中,当接收玩该帧数据后在判断首字节是否同本机地址相符,若符合继续处理,反之清除缓冲区,准备接收下一帧数据。方法2:当判断第一个字节不是本机地址时,置位地址错误标志,接收缓冲区指针仍然偏移,但是数据不写入缓冲区中,当该帧数
16、据接收完毕后根据各个错误标志位的状态作进一步处理。比如检测到地址错误标志置位,则说明该帧数据有误,直接清除缓冲区,准备接搜下一帧数据。仔细想想这样还会有可行么?。上述方法虽然规避了之前出现的现象,其实啊这两种方法依然有漏洞,而且漏洞还很大,其带来的直接影响就是一帧数据的长度依然无法保障。也许有人会问,无论是命令帧还是响应帧我们通过功能码可以计算出该帧数据的长度,怎么会无法保障呢?这个问题问的好,长度我们能计算出来固然不错,但是请问你能分清什么时候是响应帧什么时候是命令帧么?好吧,又有人说了,那还不好分,命令帧之后不就是响应帧么?呵呵,理论上是这样,当主机发送命令帧之后,等待从机回复响应帧。但是
17、你忽略了实际情况,我们无法保障每次通信都成功,这也是主机必须有重发机制的重要原因。讲到这里我想我已经诠释的很清楚了。那么问什么一个模块没有问题呢,答案很简单,因为主机只发送给从机1命令码,绝大多数情况下,从机都能正确的收到第一个字节与本机的地址相符合,而且可以有效的判断该帧数据的长度,更不存其它从机响应帧带来的干扰。即使偶尔出错,利用主机的重发机制也可以弥补。建议:利用命令帧计算该帧数据长度带来的问题会有很多,而且很诡异,不建议这样用。根据帧结束后的3.5个字符时间来判断该帧数据结束。熟悉MODBUS RTU报文帧的都知道,通讯时一般在每帧数据发送前段和后端有一个短暂的延迟(=3.5个字符的时
18、间),这个时间根据波特率很容易计算出来,我们可以根据后端的延迟时间来判断该帧数据结束。具体方法:利用定时器,比如定时周期为1个字符时间,当我们每次接受一个字节数据后将定时器重新复位,计数寄存器清零,某一时刻当计数寄存器中的数满足3.5个字符时间时,认为该帧数据发送结束。在发送过程中,该帧数据必须作为一个连续的数据流发送,发送相邻两个字节的数据之间时间不得大于1.5个字符的时间。否则认为该帧数据不完整。从机在接收时只需在清零计数器之前判断计数寄存器中的数据是否大于1.5个字符对应的时间,若是认为该帧数据接收完毕,反之继续接收。用这种方式判断一帧数据是否发送、接收完毕思路很清晰。我们只需检测3.5
19、个字符时间即可,无需理会究竟是命令帧还是响应帧。MODBUS ASCII通信协议由于有固定的起始位与结束位,所以我们可以很清晰的去判断帧的起始与结束。MODBUS报文帧RTU传输模式 MODBUS ASCII报文帧实在不想抄了 就粘贴点 望大家包涵。在这里只想说明几点:MODBUS RTU/ASCII中每个字符都是11位。我们可以利用51单片机串口通信模式3轻松的实现;但是要特别注意MODBUS ASCII码中的数据位为7位,当无奇偶校验时我们用第八位数据填充移位停止位即可(SBUF=DATA|0x80);当有奇偶偶校验时我们只需根据奇偶校验位来进行相应的与或即可。比如偶校验,发送数据0X30
20、,首先我们将其写入ACC,然后通过查看PSW中的奇偶校验位P,然后进行与或,具体见下面程序:同样我们在解析时也要特别注意,要想得到真正的数据就要将停止位或校验位剥离出去。这一点很容易出错,笔者曾经清晰的记得当我采用MODBUS ASCII通信时采用无校验模式,串口助手发送0X30,其实收到的是0XB0,这点尤为重要。在接下来我主要讲讲主从机MODBUS通讯的实现方案,希望对大家有帮助,但是请注意任何事情都要它使用的范围。MODBUS通讯实现方案从机: 首先要明白从机永远处于被动接收,被动响应的地位。只有接收到正确的主机命令才能响应,这里面包含两点(1)正确接收;(2)响应。正确接收:无论主机发
21、送的一帧数据是对是错,从机都能够完整的接收每个字节。只有完整的接收到命令,接下来的判断、响应才有意义。 这就要求从机的接收程序一定要完善,一般我们利用单片机的中断接收数据,当然也可以用查询方式,但不建议,那是对CPU的极端不负责任,而且还极容易引起程序进入死循环。 其次从机要有一定的诊断错误的能力。比如主、从机校验方式为偶校验,从机必须对接收的每个字节进行偶校验,即使一帧数据中有一个校验位出错,那么该也要毫不留情的丢弃该帧数据;再如对于主机发送从机不能识别的功能码、寄存器地址越界、数据范围溢出等,从机要增加异常处理程序。响应:根据主机发送的命令,进行有效的操作,并反馈主机。 MODBUS采用主
22、从问答通信模式,响应的目的就是从机向主机汇报命令的执行结果,成功或失败。当然如果从机发现主机发送的命令有错误,可以不给出响应,当主机在规定的时间内没有收到有效数据帧,就执行重发机制或放弃该从机节点直接询问下一个从机节点。主机:相对于从机来说,主机处于主动地位,实现起来要容易的多。但是在设计主机通讯时,为了提高通讯的成功率,内部要增加更多的保障机制。比如:主机向从机发送命令帧后处于等待响应阶段,同志们这个等待响应时间要充足,具体依赖于从机执行的任务量以及任务执行的时间,因此我们要对从机执行任务的总时间有一个比较精确的定位;如果在规定的时间内主机没有收到有效的数据帧(数据帧错误或没有收到任何数据)
23、,则执行重发机制,重发次数可以程序设定,除此之外还有延迟发送,接收超时等设置,具体根据系统设计而定。编写程序的思想:从机:前面已经提到过,通过监测3.5个字符时间作为判断一帧数据的结束的标志。具体数据处理可以放在主程序中,亦可以放在中断程序中,也可两者兼而有之。具体方案:方案1:从机不管数据的正确与否一直接收直到检测到有效地帧结束标志,然后在主程序中判断数据的正确性。方案2:从机在接收到第一字节数据发现与本机地址不符,则该帧数据不写入接收缓冲区,等待下一帧有效数据帧。方案3:从机接收主机发送的数据,对地址不符的或奇偶校验位不正确的置位相关错误标志,在主程序中首先查看相关错误标志,若有误数据丢弃
24、,反之继续进行CRC校验。不同的方案依赖于不同的任务以及工作模式,不可死板硬套。建议:数据处理后,无论数据帧是否正确,最好清空缓冲区。 无论数据接收还是发送尽量采用中断方式,提高CPU的效率。 之前我都采用中断接收,查询发送的模式,直到我同事耿聪杰使用中断发送我再次将自己的程序修改,发现中断模式确实给力,但是我们也遇到了一些问题。1 响应数据丢失。 当从机正确收到主机的命令帧后,利用中断发送响应帧时通过串口软件发现有部分数据丢失。主程序如下:主循环中主要完成CRC校验、发送响应帧以及清空缓存的功能。 具体而言:当CRC校验正确后,转变485为发送模式,然后将第一个字节写入SBUF启动发送,其余
25、的数据在中断程序中国继续发送。那么为什么会出现发送数据部分丢失的现象呢?仔细分析发现:当数据写入SBUF后,便启动了串口发送,而这一部分是由单片机硬件完成的,在发送的同时,单片机程序仍然要向下执行。某一时刻如果单片机执行完”RSI=0”(转为接收模式),单片机可能还有部分数据没有发送结束,但是程序已经将数据变为接收模式,从而造成了数据部分丢失。举一个例子:假设串口波特率9600,那么当串口工作于方式1时,一个字符为11位,通过计算得知,单片机发送完这一个字节数据大约需要1ms的时间,尽管短,但是对于cpu而言已经是很长的时间了,可以执行1000条单周期指令了。可能发送一个字节这种现象不会出现,但是随着数据帧越长,这种现象也就越明显。解决方法:当最后一个字节响应数据发送结束后,再改变485为接收的模式。2 51单片机的串口通讯过程理解模糊导致调试时间过长,有关串口通讯请看我的相关文档。
copyright@ 2008-2022 冰豆网网站版权所有
经营许可证编号:鄂ICP备2022015515号-1