Modbus RTU 通信工具设计
Modbus 是一个工业上常用的通讯协议、一种通讯约定。
ModBus 协议是应用层报文传输协议(OSI 模型第7层),它定义了一个与通信层无关的协议数据单元(PDU),即PDU=功能码+数据域。
ModBus 协议能够应用在不同类型的总线或网络。对应不同的总线或网络,Modbus 协议引入一些附加域映射成应用数据单元(ADU),即ADU=附加域+PDU。目前,Modbus 有下列三种通信方式:
1. 以太网,对应的通信模式是Modbus TCP。
2. 异步串行传输(各种介质如有线RS-232-/422/485/;光纤、无线等),对应的通信模式是 Modbus RTU 或 Modbus ASCII。Modbus 的ASCII、RTU 协议规定了消息、数据的结构、命令和应答的方式,数据通讯采用Maser/Slave方式。
3. 高速令牌传递网络,对应的通信模式是Modbus PLUS。
Modbus 需要对数据进行校验,串行协议中除有奇偶校验外,ASCII 模式采用LRC 校验;RTU 模式采用16位CRC 校验;TCP 模式没有额外规定校验,因为TCP 是一个面向连接的可靠协议。
Modbus 协议的应用中,最常用的是Modbus RTU传输模式。
RTU 传输模式当设备使用RTU(RemoteTerminalUnit)模式在Modbus串行链路通信,报文中每个8位字节含有两个4位十六进制字符。这种模式的主要优点是较高的数据密度,在相同的波特率下比ASCII模式有更高的吞吐率。每个报文必须以连续的字符流传送。
RTU 模式每个字节(11位)的格式为:
编码系统: 8位二进制。 报文中每个8位的字节含有两个4位十六进制字符(0–9,A–F)
BitsperByte: 1起始位
8数据位,首先发送最低有效位
1位作为奇偶校验
1停止位
偶校验是要求的,其它模式(奇校验,无校验)也可以使用。为了保证与其它产品的最大兼容性,同时支持无校验模式是建议的。默认校验模式模式必须为偶校验。注:使用无校验要求2个停止位。
字符的串行传送方式:
每个字符或字节均由此顺序发送(从左到右):最低有效位(LSB)...最高有效位(MSB)
图1:RTU模式位序列
设备配置为奇校验、偶校验或无校验都可以接受。如果无奇偶校验,将传送一个附加的停止位以填充字符帧:
图2:RTU模式位序列(无校验的特殊情况)
帧检验域:循环冗余校验(CRC)
在RTU模式包含一个对全部报文内容执行的,基于循环冗余校验(CRC-CyclicalRedundancyChecking)算法的错误检验域。
CRC域检验整个报文的内容。不管报文有无奇偶校验,均执行此检验。
CRC包含由两个8位字节组成的一个16位值。
CRC域作为报文的最后的域附加在报文之后。计算后,首先附加低字节,然后是高字节。CRC高字节为报文发送的最后一个子节。
附加在报文后面的CRC的值由发送设备计算。接收设备在接收报文时重新计算CRC的值,并将计算结果于实际接收到的CRC值相比较。如果两个值不相等,则为错误。
CRC的计算,开始对一个16位寄存器预装全1。然后将报文中的连续的8位子节对其进行后续的计算。只有字符中的8个数据位参与生成CRC的运算,起始位,停止位和校验位不参与CRC计算。
CRC的生成过程中,每个8–位字符与寄存器中的值异或。然后结果向最低有效位(LSB)方向移动(Shift)1位,而最高有效位(MSB)位置充零。然后提取并检查LSB:如果LSB为1,则寄存器中的值与一个固定的预置值异或;如果LSB为0,则不进行异或操作。
这个过程将重复直到执行完8次移位。完成最后一次(第8次)移位及相关操作后,下一个8位字节与寄存器的当前值异或,然后又同上面描述过的一样重复8次。当所有报文中子节都运算之后得到的寄存器中的最终值,就是CRC。
当CRC附加在报文之后时,首先附加低字节,然后是高字节。
CRC 算法如下:
privateboolCheckResponse(byte[]response){//PerformabasicCRCcheck:byte[]CRC=newbyte[2];GetCRC(response,refCRC);if(CRC[0]==response[response.Length-2]&&CRC[1]==response[response.Length-1])returntrue;elsereturnfalse;}privatevoidGetCRC(byte[]message,refbyte[]CRC){//Functionexpectsamodbusmessageofanylengthaswellasa2byteCRCarrayinwhichto//returntheCRCvalues:ushortCRCFull=0xFFFF;byteCRCHigh=0xFF,CRCLow=0xFF;charCRCLSB;for(inti=0;i<(message.Length)-2;i++){CRCFull=(ushort)(CRCFull^message[i]);for(intj=0;j<8;j++){CRCLSB=(char)(CRCFull&0x0001);CRCFull=(ushort)((CRCFull>>1)&0x7FFF);if(CRCLSB==1)CRCFull=(ushort)(CRCFull^0xA001);}}CRC[1]=CRCHigh=(byte)((CRCFull>>8)&0xFF);CRC[0]=CRCLow=(byte)(CRCFull&0xFF);}
帧描述 (如下图所示) :
图3:RTU报文帧
注意:ModbusRTU帧最大为256字节。
下面是我为公司设计的一个 Modbus RTU 通信测试小工具,界面截图如下:
图4:Modbus RTU 通信工具
我的通用Modbus RTU 动态库,modbus.cs 如下:
modbus.csusingSystem;usingSystem.Collections.Generic;usingSystem.Text;usingSystem.IO.Ports;usingSystem.Threading;namespaceSerialPort_Lib{publicclassmodbus{privateSerialPortsp=newSerialPort();publicstringmodbusStatus;#regionConstructor/Deconstructorpublicmodbus(){}~modbus(){}#endregion#regionOpen/CloseProcedurespublicboolOpen(stringportName,intbaudRate,intdatabits,Parityparity,StopBitsstopBits){//Ensureportisn'talreadyopened:if(!sp.IsOpen){//Assigndesiredsettingstotheserialport:sp.PortName=portName;sp.BaudRate=baudRate;sp.DataBits=databits;sp.Parity=parity;sp.StopBits=stopBits;//Thesetimeoutsaredefaultandcannotbeedittedthroughtheclassatthispoint:sp.ReadTimeout=-1;sp.WriteTimeout=10000;try{sp.Open();}catch(Exceptionerr){modbusStatus="Erroropening"+portName+":"+err.Message;returnfalse;}modbusStatus=portName+"openedsuccessfully";returntrue;}else{modbusStatus=portName+"alreadyopened";returnfalse;}}publicboolClose(){//Ensureportisopenedbeforeattemptingtoclose:if(sp.IsOpen){try{sp.Close();}catch(Exceptionerr){modbusStatus="Errorclosing"+sp.PortName+":"+err.Message;returnfalse;}modbusStatus=sp.PortName+"closedsuccessfully";returntrue;}else{modbusStatus=sp.PortName+"isnotopen";returnfalse;}}#endregion#regionCRCComputationprivatevoidGetCRC(byte[]message,refbyte[]CRC){//Functionexpectsamodbusmessageofanylengthaswellasa2byteCRCarrayinwhichto//returntheCRCvalues:ushortCRCFull=0xFFFF;byteCRCHigh=0xFF,CRCLow=0xFF;charCRCLSB;for(inti=0;i<(message.Length)-2;i++){CRCFull=(ushort)(CRCFull^message[i]);for(intj=0;j<8;j++){CRCLSB=(char)(CRCFull&0x0001);CRCFull=(ushort)((CRCFull>>1)&0x7FFF);if(CRCLSB==1)CRCFull=(ushort)(CRCFull^0xA001);}}CRC[1]=CRCHigh=(byte)((CRCFull>>8)&0xFF);CRC[0]=CRCLow=(byte)(CRCFull&0xFF);}#endregion#regionBuildMessageprivatevoidBuildMessage(byteaddress,bytetype,ushortstart,ushortregisters,refbyte[]message){//ArraytoreceiveCRCbytes:byte[]CRC=newbyte[2];message[0]=address;message[1]=type;message[2]=(byte)(start>>8);message[3]=(byte)start;message[4]=(byte)(registers>>8);message[5]=(byte)registers;GetCRC(message,refCRC);message[message.Length-2]=CRC[0];message[message.Length-1]=CRC[1];}#endregion#regionCheckResponseprivateboolCheckResponse(byte[]response){//PerformabasicCRCcheck:byte[]CRC=newbyte[2];GetCRC(response,refCRC);if(CRC[0]==response[response.Length-2]&&CRC[1]==response[response.Length-1])returntrue;elsereturnfalse;}#endregion#regionGetResponseprivatevoidGetResponse(refbyte[]response){//Thereisabugin.Net2.0DataReceivedEventthatpreventspeoplefromusingthis//eventasaninterrupttohandledata(itdoesn'tfireallofthetime).Therefore//wehavetousetheReadBytecommandforafixedlengthasit'sbeenshowntobereliable.for(inti=0;i<response.Length;i++){response[i]=(byte)(sp.ReadByte());}}#endregion#regionGetModbusData获得接收数据publicboolGetModbusData(refbyte[]values){//Ensureportisopen:if(sp.IsOpen){//等待线程进入//Monitor.Enter(sp);//Clearin/outbuffers://sp.DiscardOutBuffer();//sp.DiscardInBuffer();//Messageis1addr+1type+NData+2CRCtry{//GetResponse(refreadBuffer);//stringstr=readBuffer.ToString();intcount=sp.BytesToRead;if(count>0){byte[]readBuffer=newbyte[count];GetResponse(refreadBuffer);//readData=newbyte[29];//Array.Copy(readBuffer,readData,readData.Length);//CRC验证if(CheckResponse(readBuffer)){//显示输入数据values=readBuffer;modbusStatus="Writesuccessful";sp.DiscardInBuffer();//values=System.Text.Encoding.ASCII.GetString(readData);returntrue;}else{modbusStatus="CRCerror";sp.DiscardInBuffer();returnfalse;}}elsereturnfalse;}catch(Exceptionerr){modbusStatus="Errorinwriteevent:"+err.Message;sp.DiscardInBuffer();returnfalse;}//finally//{//通知其它对象//Monitor.Pulse(sp);//释放对象锁//Monitor.Exit(sp);//}}else{modbusStatus="Serialportnotopen";returnfalse;}}#endregion#regionSendModbusData打包发送数据publicboolSendModbusData(refbyte[]values){//Ensureportisopen:if(sp.IsOpen){//Clearin/outbuffers:sp.DiscardOutBuffer();sp.DiscardInBuffer();//Function3responsebuffer:byte[]response=newbyte[values.Length+2];Array.Copy(values,response,values.Length);//BuildMessage(address,(byte)3,start,registers,refmessage);//打包带有CRC验证的modbus数据包:byte[]CRC=newbyte[2];GetCRC(response,refCRC);response[response.Length-2]=CRC[0];response[response.Length-1]=CRC[1];values=response;//返回带有CRC验证的modbus数据包//SendmodbusmessagetoSerialPort:try{sp.Write(response,0,response.Length);//GetResponse(refresponse);returntrue;}catch(Exceptionerr){modbusStatus="Errorinreadevent:"+err.Message;returnfalse;}//Evaluatemessage://if(CheckResponse(response))//{////Returnrequestedregistervalues://for(inti=0;i<(response.Length-5)/2;i++)//{//values[i]=response[2*i+3];//values[i]<<=8;//values[i]+=response[2*i+4];//}//modbusStatus="Readsuccessful";//returntrue;//}//else//{//modbusStatus="CRCerror";//returnfalse;//}}else{modbusStatus="Serialportnotopen";returnfalse;}}#endregion}}
调用的主要代码如下:
modbus类的winform调用代码publicpartialclassFormConfig:Form,IModbusData{//业务处理类B_ModbusDataModbusDataBLL=newB_ModbusData();modbusmb=newmodbus();//SerialPortsp=newSerialPort();System.Timers.Timertimer=newSystem.Timers.Timer();publicFormConfig(){InitializeComponent();timer.Elapsed+=newElapsedEventHandler(timer_Elapsed);}#regionTimerElapsed事件处理程序boolrunEnd=true;voidtimer_Elapsed(objectsender,ElapsedEventArgse){if(runEnd==true){runEnd=false;PollFunction();runEnd=true;}}//定时器调用方法privatevoidPollFunction(){byte[]values=null;try{mb.GetModbusData(refvalues);//while(!mb.SendFc3(Convert.ToByte(txtSlaveID.Text),pollStart,pollLength,refvalues));}catch(Exceptionerr){DoGUIStatus("Errorinmodbusread:"+err.Message);}if(values!=null){//业务处理byte[]sendData=ModbusDataProcess(values);}}#endregion#regionIModbusData接口成员处理publicbyte[]ModbusDataProcess(byte[]_data){byte[]sendData=ModbusDataBLL.ModbusDataProcess(_data);//CRC验证,并打包发送数据。mb.SendModbusData(refsendData);returnsendData;}#endregion}
其实,三步就能成功调用:
modbusmb=newmodbus();mb.GetModbusData(refvalues);//从串口设备获得数据。byte[]sendData=ModbusDataBLL.ModbusDataProcess(values);//你的业务处理,并产生最终返回数据。mb.SendModbusData(refsendData);//CRC验证,并打包发送数据。
主要代码已全部提供,由于工作原因暂不提供完整工具源代码,见谅!
(完)
声明:本站所有文章资源内容,如无特殊说明或标注,均为采集网络资源。如若本站内容侵犯了原著者的合法权益,可联系本站删除。