手把手教你学51单片机之十八RS485通信与Modbus协议.docx

上传人:牧羊曲112 文档编号:4263875 上传时间:2023-04-12 格式:DOCX 页数:26 大小:338.36KB
返回 下载 相关 举报
手把手教你学51单片机之十八RS485通信与Modbus协议.docx_第1页
第1页 / 共26页
手把手教你学51单片机之十八RS485通信与Modbus协议.docx_第2页
第2页 / 共26页
手把手教你学51单片机之十八RS485通信与Modbus协议.docx_第3页
第3页 / 共26页
手把手教你学51单片机之十八RS485通信与Modbus协议.docx_第4页
第4页 / 共26页
手把手教你学51单片机之十八RS485通信与Modbus协议.docx_第5页
第5页 / 共26页
点击查看更多>>
资源描述

《手把手教你学51单片机之十八RS485通信与Modbus协议.docx》由会员分享,可在线阅读,更多相关《手把手教你学51单片机之十八RS485通信与Modbus协议.docx(26页珍藏版)》请在三一办公上搜索。

1、在工业控制、电力通讯、智能仪表等领域,通常情况下是采用串口通信的方式进行数据交换。最初采用的方式是 RS232 接口,由于工业现场比较复杂,各种电气设备会在环境中产生比较多的电磁干扰,会导致信号传输错误。 除此之外,RS232 接口只能实现点对点通信, 不具备联网功能, 最大传输距离也只能达到几十米,不能满足远距离通信要求。而 RS485 则解决了这些问题,数据信号采用差分传输方式,可以有效的解决共模干扰问题,最大距离可以到1200 米,并且允许多个收发设备接到同一条总线上。随着工业应用通信越来越多, 1979 年施耐德电气制定了一个用于工业现场的总线协议 Modbus 协议,现在工业中使用R

2、S485 通信场合很多都采用 Modbus 协议,本节课我们要讲解一下 RS485 通信和 Modbus 协议。单单使用一块KST-51 开发板是不能够进行RS485 实验的, 应很多同学的要求,把这节课作为扩展课程讲一下,如果要做本课相关实验,需要自行购买USB 转 485 通信模块。18.1 RS485通信实际上在 RS485 之前 RS232 就已经诞生,但是RS232 有几处不足的地方:1、接口的信号电平值较高,达到十几V ,容易损坏接口电路的芯片,而且和TTL 电平不兼容,因此和单片机电路接起来的话必须加转换电路。2、传输速率有局限,不可以过高,一般到几十Kb/s 就到极限了。3、接

3、口使用信号线和GND 与其他设备形成共地模式的通信,这种共地模式传输容易产生干扰,并且抗干扰性能也比较弱。4、传输距离有限,最多只能通信几十米。5、通信的时候只能两点之间进行通信,不能够实现多机联网通信。针对 RS232 接口的不足,就不断出现了一些新的接口标准,RS485 就是其中之一,他具备以下的特点:1、我们在讲A/D 的时候,讲过差分信号输入的概念,同时也介绍了差分输入的好处,最大的优势是可以抑制共模干扰。尤其工业现场的环境比较复杂,干扰比较多, 所以通信如果采用的是差分方式,就可以有效的抑制共模干扰。而RS485 就是一种差分通信方式,它的通信线路是两根, 通常用A 和B 或者D+

4、和 D- 来表示。逻辑“ 1”以两线之间的电压差为+(0.26)V表示,逻辑“0”以两线间的电压差为-(0.26)V来表示,是一种典型的差分通信。2、 RS485通信速度快,最大传输速度可以达到10Mb/s以上。3、RS485 内部的物理结构,采用的是平衡驱动器和差分接收器的组合,抗干扰能力也大大增加。4、传输距离最远可以达到1200 米左右,但是他的传输速率和传输距离是成反比的,只有在 100Kb/s 以下的传输速度, 才能达到最大的通信距离, 如果需要传输更远距离可以使用中继。5、可以在总线上进行联网实现多机通信,总线上允许挂多个收发器,从现有的RS485 芯片来看,有可以挂32、 64、

5、 128、 256 等不同个设备的驱动器。RS485 的接口非常简单,和RS232 所使用的MAX232是类似的,只需要一个RS485 转换器,就可以直接和我们单片机的UART 串行接口连接起来,并且完全使用的是和UART 一致的异步串行通信协议。但是由于RS485 是差分通信,因此接收数据和发送数据是不能同时进行的,也就是说它是一种半双工通信。 那我们如何判断什么时候发送, 什么时候接收呢? RS485 类的芯片很多,这节课我们以 MAX485 为例讲解 RS485 通信,如图 18-1 所示。图 18-1 MAX485 硬件接口MAX485 是美信 (Maxim) 推出的一款常用 RS48

6、5 转换器。其中 5 脚和 8 脚是电源引脚,6 脚和 7 脚就是 485 通信中的 A 和 B 两个引脚,而1 脚和 4 脚分别接到我们单片机的RXD 和TXD 引脚上,直接使用单片机UART 进行数据接收和发送。 而 2 脚和 3 脚就是方向引脚了,其中 2 脚是低电平使能接收器,3 脚是高电平使能输出驱动器。 我们把这两个引脚连到一起,平时不发送数据的时候,保持这两个引脚是低电平,让MAX485 处于接收状态,当需要发送数据的时候, 把这个引脚拉高,发送数据, 发送完毕后再拉低这个引脚就可以了。为了提高 RS485 的抗干扰性能, 需要在靠近 MAX485的 A 和 B 引脚之间并接一个

7、电阻,这个电阻阻值从 100 欧到 1K 都可以。在这里我们还要介绍一下如何使用KST-51 单片机开发板进行外围扩展实验。我们的开发板只能把基本的功能给同学们做出来提供实验练习,但是同学们学习的脚步不应该停留在这个实验板上。 如果想进行更多的实验,就可以通过单片机开发板的扩展接口进行扩展实验。大家可以看到蓝绿色的单片机座周围有32 个插针,这32 个插针就是把单片机的32个IO 引脚全部都引出来了。在原理图上体现出来的就是我们的J4、J5、J6、 J7 这 4 个器件,如图18-2 所示。图 18-2 单片机扩展接口这 32 个 IO 口不是所有的 IO 口都可以用来对外扩展,其中既作为数据

8、输出,又可以作为数据输入的引脚是不可以用的,比如P3.2、 P3.4、P3.6 引脚,这三个引脚是不可用的。比如P3.2 这个引脚,如果我们用来扩展,发送的信号如果和DS18B20 的时序吻合,会导致DS18B20 拉低引脚,影响通信。除这3 个 IO 口以外的其他 29 个 IO 口,都可以使用杜邦线接上插针,扩展出来使用。当然了,如果把当前的IO 口应用于扩展功能了,板子上的相应的功能就实现不了了,也就是说需要扩展功能和板载功能二选一。在进行 RS485 实验中,我们通信用的引脚必须是P3.0 和 P3.1,此外还有一个方向控制引脚,我们使用杜邦线将其连接到P1.7 上去。 RS485 的

9、另外一端,大家可以使用一个USB 转 485模块,用双绞线把开发板和模块上的A 和 B 分别对应连起来, USB 那头插入电脑,然后就可以进行通信了。学习了第 13 章的实用串口通信的方法和程序后,做这种串口通信的方法就很简单了,基本是一致的。 我们使用实用串口通信的思路,做了一个简单的程序, 通过串口调试助手下发任意个字符,单片机接收到后在末尾添加“回车+换行”符后再送回,在调试助手上重新显示出来,先把程序贴出来。程序中需要注意的一点是:因为平常都是将485 设置为接收状态, 只有在发送数据的时候才将 485 改为发送状态, 所以在 UartWrite() 函数开头将 485 方向引脚拉高,

10、 函数退出前再拉低。但是这里有一个细节, 就是单片机的发送和接收中断产生的时刻都是在停止位的一半上,也就是说每当停止位传送了一半的时候,RI或TI就已经置位并且马上进入中断(如果中断使能的话)函数了,接收的时候自然不会存在问题,但发送的时候就不一样了:当紧接这向SBUF写入一个字节数据时,UART硬件会在完成上一个停止位的发送后,再开始新字节的发送,但如果此时不是继续发送下一个字节,而是已经发送完毕了,要停止发送并将485方向引脚拉低以使485 重新处于接收状态时就有问题了,因为这时候最后的这个停止位实际只发送了一半,还没有完全完成,所以就有了UartWrite() 函数内 DelayX10u

11、s(5)这个操作,这是人为的增加了延时50us ,这 50us 的时间正好让剩下的一半停止位完成,那么这个时间自然就是由通信波特率决定的了,为波特率周期的一半。/*RS485.c文件程序源代码*/#include #include sbit RS485_DIR = P17; /RS485方向选择引脚bit flagOnceTxd = 0; /单次发送完成标志,即发送完一个字节bit cmdArrived = 0;/ 命令到达标志,即接收到上位机下发的命令unsigned char cntRxd = 0;unsigned char pdata bufRxd40; /串口接收缓冲区void Con

12、figUART(unsigned int baud) /串口配置函数,baud 为波特率RS485_DIR = 0; /RS485设置为接收方向SCON = 0x50;/配置串口为模式1TMOD &= 0x0F; /清零 T1 的控制位TMOD |= 0x20; /配置 T1 为模式 2TH1 = 256 - (11059200/12/32) / baud; /TL1 = TH1;/ 初值等于重载值ET1 = 0;/ 禁止 T1 中断ES = 1;/使能串口中断TR1 = 1;/启动 T1计算 T1 重载值unsigned char UartRead(unsigned char *buf, u

13、nsigned char len) /据接收指针buf ,读取数据长度len ,返回值为实际读取到的数据长度串口数据读取函数,数unsigned char i;if (len cntRxd) /读取长度大于接收到的数据长度时,len = cntRxd; /读取长度设置为实际接收到的数据长度for (i=0; i 0) /接收计数器大于零时,监控总线空闲时间if (cntbkp != cntRxd) /接收计数器改变,即刚接收到数据时,清零空闲计时cntbkp = cntRxd;idletmr = 0;elseif (idletmr = 30) /空闲时间超过30ms 即认为一帧命令接收完毕cm

14、dArrived = 1; /设置命令到达标志elsecntbkp = 0;void InterruptUART() interrupt 4 /UART中断服务函数if (RI) / 接收到字节RI = 0;/手动清零接收中断标志位if (cntRxd sizeof(bufRxd) /接收缓冲区尚未用完时,bufRxdcntRxd+ = SBUF; /保存接收字节,并递增计数器if (TI) / 字节发送完毕TI = 0;/手动清零发送中断标志位flagOnceTxd = 1; /设置单次发送完成标志/*main.c文件程序源代码*/#include unsigned char T0RH =

15、0; /T0重载值的高字节unsigned char T0RL = 0; /T0重载值的低字节void ConfigTimer0(unsigned int ms);extern void ConfigUART(unsigned int baud);extern void UartRxMonitor(unsigned char ms);extern void UartDriver();void main ()EA = 1;/ 开总中断ConfigTimer0(1); /配置 T0 定时 1msConfigUART(9600); /配置波特率为9600while(1)UartDriver();vo

16、id ConfigTimer0(unsigned int ms) /T0配置函数unsigned long tmp;tmp = 11059200 / 12;/定时器计数频率tmp = (tmp * ms) / 1000; /计算所需的计数值tmp = 65536 - tmp;/计算定时器重载值tmp = tmp + 34;/ 修正中断响应延时造成的误差T0RH = (unsigned char)(tmp 8); /定时器重载值拆分为高低字节T0RL = (unsigned char)tmp;TMOD &= 0xF0;/ 清零 T0 的控制位TMOD |= 0x01;/配置T0为模式1TH0 =

17、 T0RH;/ 加载T0 重载值TL0 = T0RL;ET0 = 1;/ 使能T0 中断TR0 = 1;/启动T0void InterruptTimer0() interrupt 1 /T0中断服务函数TH0 = T0RH; /定时器重新加载重载值TL0 = T0RL;UartRxMonitor(1); /串口接收监控现在看这种串口程序,是不是感觉很简单了呢?串口通信程序我们反反复复的使用,加上随着我们学习的模块越来越多,实践的越来越多,原先感觉很复杂的东西,现在就会感到简单了。我们的下载程序模块用的是COM4,而 USB转 485 虚拟的是COM5,通信的时候我们用的是COM5口,如图18-

18、3所示。图 18-3 RS485 串行通信18.2 Modbus通信协议介绍我们前边学习UART、I 2C 、SPI 这些通信协议,都是最底层的协议,是“位”级别的协议。而我们在学习13 章实用串口通信程序的时候,我们通过串口发给单片机三条指令,让单片机做了三件不同的事情,分别是buzz on 、 buzz off 、和 showstr 。随着我们系统复杂性的增加,我们希望可以实现更多的指令。而指令越来越多,带来的后果就是非常杂乱无章,尤其是这个人喜欢写成 buzz on 、buzz off ,而另外一个人喜欢写成on buzz 、off buzz 。导致不同开发人员写出来的代码指令不兼容,不

19、同厂家的产品不能挂到一条总线上通信。随着这种矛盾的日益严重, 就会有聪明人提出更合理的解决方案,提出一些标准来, 今后我们的编程必须按照这个标准来,这种标准也是一种通信协议,但是和UART 、I 2C、SPI 通信协议不同的是,这种通信协议是字节级别的,叫做应用层通信协议。在1979 年由Modicon(现为施耐德电气公司的一个品牌) 提出了全球第一个真正用于工业现场总线的协议,就是Modbus 协议。18.2.1 Modbus协议特点Modbus协议是应用于电子控制器上的一种通用语言。通过此协议,控制器相互之间、控制器经由网络 ( 例如以太网 ) 和其他设备之间可以通信,已经成为一种工业标准

20、。有了它,不同厂商生产的控制设备可以连成工业网络,进行集中监控。 这种协议定义了一种控制器能够认识使用的数据结构,而不管它们是经过何种网络进行通信的。它描述了控制器请求访问其他设备的过程, 如何回应来自其他设备的请求,以及怎样侦测错误记录,它制定了通信数据的格局和内容的公共格式。在进行多机通信的时候,Modbus 协议规定每个控制器必须要知道他们的设备地址,识别按照地址发送过来的数据,决定是否要产生动作,产生何种动作,如果要回应,控制器将生成的反馈信息用Modbus 协议发出。Modbus协议允许在各种网络体系结构内进行简单通信,每种设备 (PLC 、人机界面、 控制面板、驱动程序、输入输出设

21、备) 都能使用Modbus 协议来启动远程操作,一些网关允许在几种使用 Modbus 协议的总线或网络之间的通信,如图18-4 所示。图 18-4 Modbus 网络体系结构实例Modbus 协议的整体架构和格式比较复杂和庞大, 在我们的课程里, 我们重点介绍数据帧结构和数据通信控制方式, 作为一个入门级别的了解。 如果大家要详细了解, 或者使用 Modbus 开发相关设备,可以查阅相关的国标文件再进行深入学习。1.2.2 RTU协议帧数据Modbus 有两种通信传输方式,一种是 ASCII 模式,一种是 RTU 模式。由于 ASCII 模式的数据字节是 7bit 数据位, 51 单片机无法实

22、现, 而且应用也相对较少, 所以这里我们只用 RTU 模式。两种模式相似, 会用一种另外一种也就会了。 一条典型的 RTU 数据帧如图 18-5 所示。图 18-5 RTU 数据帧和我们实用串口通信程序类似, 我们一次发送的数据帧必须是作为一个连续的数据流进行传输。我们在实用串口通信程序中采用的方法是定义30ms,如果接收到的数据超过了30ms还没有接收到下一个字节,我们就认为这次的数据结束。而Modbus 的 RTU 模式规定不同数据帧之间的间隔是 3.5 个字节通信时间以上。如果在一帧数据完成之前有超过3.5个字节时间的停顿, 接收设备将刷新当前的消息并假定下一个字节是一个新的数据帧的开始

23、。同样的,如果一个新消息在小于3.5 个字节时间内接着前边一个数据开始的,接收的设备将会认为它是前一帧数据的延续。这将会导致一个错误,因此大家看RTU 数据帧最后还有16bit的 CRC 校验。起始位和结束符:图 18-5 上代表的是一个数据帧,前后都至少有3.5个字节的时间间隔,起始位和结束符实际上没有任何数据,T1-T2-T3-T4 代表的是时间间隔3.5个字节以上的时间,而真正有意义的第一个字节是设备地址。设备地址: 很多同学不理解,在多机通信的时候,数据那么多,我们依靠什么判断这个数据帧是哪个设备的呢?没错,就是依靠这个设备地址字节。每个设备都有一个自己的地址,当设备接收到一帧数据后,

24、程序首先对设备地址字节进行判断比较,如果与自己的地址不同,则对这帧数据直接不予理会,如果如果与自己的地址相同,就要对这帧数据进行解析,按照之后的功能码执行相应的功能。如果地址是0x00,则认为是一个广播命令,就是所有的从机设备都要执行的指令。功能代码:在第二个字节功能代码字节中,Modbus 规定了部分功能代码,此外也保留了一部分功能代码作为备用或者用户自定义,这些功能码大家不需要去记忆,甚至都不用去看,直到你有用到的那天再过来查这个表格即可,如表18-1 所示。表 18-1 Modbus 功能码功能码名称作用01读取线圈状态取得一组逻辑线圈的当前状态(ON/OFF)0203040506070

25、809101112131415161718192021读取输入状态取得一组开关输入的当前状态(ON/OFF)读取保持寄存器在一个或多个保持寄存器中取得当前的二进制值读取输入寄存器在一个或多个输入寄存器中取得当前的二进制值强置单线圈强置一个逻辑线圈的通断状态预置单寄存器把具体二进值装入一个保持寄存器读取异常状态取得 8 个内部线圈的通断状态,这8 个线圈的地址由控制器决定,可以将这些线圈定义,以说明从机状态,短报文适宜于迅速读取状回送诊断校验把诊断校验报文送从机,以对通信处理进行评鉴编程 ( 只用于 484)使主机模拟编程器作用,修改PC 从机逻辑控询 ( 只用于 484)可使主机与一台正在执行

26、长程序任务从机通信,探询该从机是否已任务,仅在含有功能码9 的报文发送后,本功能码才发送读取事件计数可使主机发出单询问,并随即判定操作是否成功,尤其是该命令或生通信错误时读取通信事件记录可是主机检索每台从机的ModBus 事务处理通信事件记录。如果某完成,记录会给出有关错误编程 (184/384 484 584 )可使主机模拟编程器功能修改PC 从机逻辑探询 (184/384 484 584)可使主机与正在执行任务的从机通信,定期控询该从机是否已完成其仅在含有功能 13 的报文发送后,本功能码才得发送强置多线圈强置一串连续逻辑线圈的通断预置多寄存器把具体的二进制值装入一串连续的保持寄存器报告从

27、机标识可使主机判断编址从机的类型及该从机运行指示灯的状态884 和 MICRO 84可使主机模拟编程功能,修改PC 状态逻辑重置通信链路发生非可修改错误后,是从机复位于已知状态,可重置顺序字节读取通用参数 (584L)显示扩展存储器文件中的数据信息写入通用参数 (584L)把通用参数写入扩展存储文件,或修改2264保留作扩展功能备用6572保留以备用户功能所用留作用户功能的扩展编码73119非法功能120127保留留作内部作用128255保留用于异常应答我们程序对功能码的处理,就是程序来检测这个字节的数值,然后根据其数值来做相应的功能处理。数据:跟在功能代码后边的是n 个 8bit 的数据。这

28、个n 值的到底是多少,是功能代码来确定的,不同的功能代码后边跟的数据数量不同。举个例子,如果功能码是0x03,也就是读保持寄存器,那么主机发送数据n 的组成部分就是:2 个字节的寄存器起始地址,加2 个字节的寄存器数量N*。从机数据n 的组成部分是:1 个字节的字节数,因为我们回复的寄存器的值是2 个字节,所以这个字节数也就是2N*个,再加上2N*个寄存器的值,如图18-6所示。图 18-6 读保持寄存器数据结构CRC 校验: CRC 校验是一种数据算法,是用来校验数据对错的。CRC 校验函数把一帧数据除最后两个字节外,前边所有的字节进行特定的算法计算,计算完后生成了一个16bit 的数据,作

29、为 CRC 校验码,添加在一帧数据的最后。接收方接收到数据后,同样会把前边的字节进行 CRC 计算,计算完了再和发过来的CRC 的 16bit 的数据进行比较,如果相同则认为数据正常, 没有出错,如果比较不相同,则说明数据在传输中发生了错误,这帧数据将被丢弃,就像没收到一样,而发送方会在得不到回应后做相应的处理错误处理。RTU 模式的每个字节的位是这样分布的:1 个起始位、 8 个数据位,最小有效位先发送、1个奇偶校验位 (如果无校验则没有这一位)、 1 位停止位 (有校验位时 )或者 2 个停止位 (无校验位时 )。18.3 Modbus多机通信例程给从机下发不同的指令,从机去执行不同的操作

30、,这个就是判断一下功能码即可,和我们前边学的实用串口例程是类似的。多机通信, 无非就是添加了一个设备地址判断而已,难度也不是很大。我们找了一个Modbus 调试精灵,通过设置设备地址,读写寄存器的地址以及数值数量等参数,可以直接替代串口调试助手,比较方便的下发多个字节的数据,如图18-7所示。 我们先来就图中的设置和数据来对Modbus 做进一步的分析,图中的数据来自于调试精灵与我们接下来要讲的例程之间的交互。图 18-7 Modbus 调试精灵如图:我们的 USB 转 485 模块虚拟出的是COM5 ,波特率 9600,无校验位, 数据位是 8 位,1 位停止位,设备地址假设为1。写寄存器的

31、时候, 如果我们要把01 写到一个地址是0000 的寄存器地址里, 点一下“写入” ,就会出现发送指令: 01 06 00 00 00 01 48 0A 。我们来分析一下这帧数据,其中01 是设备地址, 06 是功能码,代表写寄存器这个功能,后边跟00 00 表示的是要写入的寄存器的地址,00 01 就是要写入的数据, 48 0A 就是 CRC 校验码,这是软件自动算出来了。而根据 Modbus协议,当写寄存器的时候,从机成功完成该指令的操作后,会把主机发送的指令直接返回,我们的调试精灵会接收到这样一帧数据:010600000001480A。假如我们现在要从寄存器地址0002 开始读取寄存器,

32、并且读取的数量是2 个。点一下“读出”,就会出现发送指令:01 03 00 02 00 02 65 CB 。其中 01 是设备地址, 03 是功能码,代表写寄存器这个功能,00 02就是读寄存器的起始地址,后一个00 02就是要读取2 个寄存器的数值,65 CB就是CRC校验。而接收到的数据是:01 03 04 00 00 00 00 FA 33。其中01是设备地址,03 是功能码,04 代表的是后边读到的数据字节数是4个,00000000分别是地址为00 02和00 03的寄存器内部的数据,而FA 33就是CRC 校验了。似乎越来越明朗了,所谓的Modbus 这种通信协议,无非就是主机下发了

33、不同的指令,从机根据指令的判断来执行不同的操作而已。由于我们的开发板没有Modbus 功能码那么多相应的功能,我们在程序中定义了一个数组regGroup5 ,相当于5 个寄存器,此外又定义了第6 个寄存器,控制蜂鸣器,通过下发不同的指令我们改变寄存器组的数据或者改变蜂鸣器的开关状态。在Modbus 协议里寄存器的地址和数值都是16 位的,即2 个字节,我们默认高字节是0x00,低字节就是数组 regGroup 对应的值。其中地址 0x0000 到 0x0004 对应的就是 regGroup 数组中的元素,我们写入的同时把数字又显示到我们的LCD1602 液晶上,而0x0005这个地址,写入 0x00,蜂鸣器就不响,写入任何其他数字,蜂鸣器就报警。我们单片机的主要工作也就是解析串口接收的数据执行不同操作,也就是主要在RS485.C 这个文件中了/*RS485.c文件程序源代码*/#include #include sbit RS485_DIR = P17; /RS485方向选择引脚bit flagOnceTxd = 0; /单次发送完成标志,即发送完一个字节bit cmdArrived = 0;/ 命令到达标志,即接收到上位机下发的命令unsigned char cntRxd = 0;

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 生活休闲 > 在线阅读


备案号:宁ICP备20000045号-2

经营许可证:宁B2-20210002

宁公网安备 64010402000987号