modbus协议(2)

上一篇介绍了modbus协议的基本概念,这一篇主要介绍最近做的一个小项目:STM3210ZET6与昆仑屏(TPC)的通信。在该项目中最关键的技术就是下位机modbus协议的解析。

首先介绍下昆仑屏(TPC),项目中用到的触摸屏采用的RS232接口,modbus协议。

采用的驱动是:莫迪康ModbusRTU;本驱动支持 01、02、03、04、05、06、15、16
常用功能码。

本驱动构件支持的寄存器及功能码说明如下:

1、设备构件参数设置:

“莫迪康 ModbusRTU”子设备参数设置如下:

(1)内部属性:单击“查看设备内部属性” ,点击按钮进入内部属性

(2)最小采集周期:MCGS
对设备进行操作的时间周期, 单位为
ms, 默认为100ms,根据采集数据量的大小,设置值可适当调整

(3)设备地址:必须和实际设备的地址相一致,范围为0-255,默认值为
0。

(4)通讯等待时间:通讯数据接收等待时间,默认设置为
200ms,根据采集数据量的大小,设置值可适当调整。

(5)快速采集次数:对选择了快速采集的通道进行快采的频率(已不使用,为与老驱动兼容,故保留,无需设置) 。

(6)16位整数解码顺序:调整字元件的解码顺序,对于
Modicon PLC
及标准 PLC设备,使用默认值即可。

16
位整数解码顺序 举例:0x0001

0―12
表示字元件高低字节不颠倒(默认值) 表示
1

1―21
表示字元件高低字节颠倒            表示
256

(7)32位整数解码顺序:调整双字元件的解码顺序,对于
Modicon PLC,请设置为“2-3412”顺序解码。

32
位整数解码顺序 举例: 0x0000 0001

0―1234
表示双字元件不做处理直接解码(默认值) 表示
1

1―2143
表示双字元件高低字不颠倒,但字内高低字节颠倒   表示256

2—3412表示双字元件高低字颠倒,但字内高低字节不颠倒   表示65536

3—4321表示双字元件内
4
个字节全部颠倒 表示 1677 7216

(8)32位浮点数解码顺序:调整双字元件的解码顺序,对于
Modicon PLC,请设置为“2-3412”顺序解码。

32
位浮点数解码顺序 举例:0x3F80 0000

0―1234
表示双字元件不做处理直接解码(默认值) 表示
1.0

1―2143
表示双字元件高低字不颠倒,但字内高低字节颠倒 表示-5.78564e-039

2—3412表示双字元件高低字颠倒,但字内高低字节不颠倒  表示2.27795e-041

3—4321表示双字元件内
4
个字节全部颠倒 表示 4.60060e-041

(9)校验方式: 选择LRC
校验值的组合方式, 对于
Modicon PLC及标准
PLC 设备,

使用默认设置即可。

0—LH[低字节,高字节]:校验结果为
2
个字节,低字节在前,高字节在后。

1—HL[高字节,低字节]:校验结果为
2
个字节,高字节在前,低字节在后。

(10)分块采集方式:驱动采集数据分块的方式,对于Modicon
PLC及标准
PLC设备,使用默认设置可以提高采集效率。

0— 按最大长度分块:采集分块按最大块长处理,对地址不连续但地址相近的多个分块,分为一块一次性读取,以优化采集效率。

1— 按连续地址分块:采集分块按地址连续性处理,对地址不连续的多个分块,每次只采集连续地址,不做优化处理。

例如:有4
区寄存器地址分别为
1~5,7,9~12的数据需采集,如果选择“0-按最大长度分块”
,则两块可优化为地址1~12的数据打包
1次完成采集;如果选择“1-按连续地址分块”
,则需要采集 3
次。

(11)4区
16
位写功能码选择:写 4
区单字时功能码的选择,这个属性主要是针对自己制作设备的用户而设置的,这样的设备4
区单字写可能只支持
0x10 功能码,而不支持
0x06
功能码。

0—0x06:单字写功能码使用0x06。

1—0x10:单字写功能码使用0x10。

注意:

1.
“解码顺序”及“校验方式”设置:主要是针对非标准
ModbusRTU 协议的不同解码及校验顺序。当用户通过本驱动软件与设备通讯时,如果出现解析数据值不对,或者通讯校验错误(通讯状态为3),可与厂家咨询后对以上两项进行设置。而对于ModiconPLC及支持标准
ModbusRTU
的 PLC
及控制器等设备,一般需将“32位整数解码顺序”和“32位浮点数解码顺序”设置为“2-3412”
。 另外,在使用本驱动与“Modbus
串口数据转发设备”构件通讯时, “解码顺序”及“校验方式”均需按默认值设置,否则会导致通讯失败或解析数据错误。

2.
“分块采集方式”设置:主要是针对非标准 ModbusRTU协议设备。当用户通过本驱动软件与设备通讯时,如果按默认“0-按最大长度分块”时,出现读取连续地址正常,而不连续地址不正常时,可与厂家咨询,并设置为“1-按连续地址分块方式”尝试是否可正常通讯。
而对于 Modicon PLC
及支持标准 ModbusRTU
的 PLC
及控制器等设备,直接使用默认设置即可,这样可以提高采集效率。

2、采集通道

a、 通讯状态:

通讯状态值 代表意义

0                  表示当前通讯正常

1                  表示采集初始化错误

2
   表示采集无数据返回错误

3                 表示采集数据校验错误

4                 表示设备命令读写操作失败错误

5                 表示设备命令格式或参数错误

6                 表示设备命令数据变量取值或赋值错误

3、 内部属性

用户可通过内部属性,添加通道,本驱动构件可支持
ModbusRTU 寄存器类型及对应功能码如下:

寄存器                   数据类型                    读取功能码   写入功能码       操作方式         通道举例

[1
区]输入继电器           BT                                      
                 02                      —            只读        只读10001表示
1区地址
1

[0区]输出继电器
          BT                                                        01                   05、15
                   读写        读写
00001
表示 0区地址1

[3
区]输入寄存器 BT、WUB、WB、WDDUB、DB、DD、DF、STR
  04                     —          只读       只读30001
表示
3区地址1

[4
区]输出寄存器 BT、WUB、WB、WDDUB、DB、DD、DF、STR
   03              06、16                    读写       读写40001
表示
4区地址1

说明:

功能码:[1区]、[3区]不支持写操作;[4区]在双字(32位)数据写操作或批量写入多个

数据时,使用
16 功能码。

4、莫迪康ModbusRTU协议格式

读:

主机查询:

从机地址---功能码---起始地址----开关量或寄存器个数-----CRC校验码

1Byte------1Byte----2Byte-----------2Byte----------------------------2Byte

从机响应:

从机地址---功能码---数据长度----响应数据---CRC校验码

1Byte------1Byte----1Byte-----------nByte------------2Byte

写一路:

从机地址--功能码--起始地址----控制命令---CRC校验码

1Byte------1Byte----2Byte----------2Byte-------------2Byte

从机地址---功能码---起始地址------控制命令-------CRC校验码

1Byte------1Byte-------2Byte------------2Byte--------2Byte

写多路:

从机地址--功能码--起始地址--开关量或寄存器个数---数据长度-控制命令---CRC校验码

1Byte------1Byte----2Byte-----------1Byte----------------------1Byte-----------------2Byte

从机地址---功能码---起始地址--开关量或寄存器个数---CRC校验码

1Byte------1Byte-------1Byte------------1Byte---------------2Byte

起始地址的理解:起始地址是为了确定读哪几路信号,从第几路开始读。

起始地址是指每一路信号在主机里面的数据存储地址,与从机里的数据地址是有区别的。

要弄清楚从机里每一路信号的存储格式,使主机和从机每一路信号地址对应上。

以TPC为例:开关量信号在其内是连续存储的,递加1;而浮点数(32bit)不是连续的,是递加2的。

怎么获取TPC屏的功能码?

采取试验的方式,在设备窗口->设备编辑窗口里增加一些通道,未连接变量的通道主机不会向从机发送请求信息,只有连接变量的通道才会向从机发送请求信息,而且有几个通道连接变量,主机读或写几个通道。

功能码列表:


功能码(Hex)


定义


01


读一路或多路开关量输出(读DO)


读DO1:     01 01 00 01 00 01 AC 0A


读地址为00 01的一路DO


02


读一路或多路开关量输入(读DI)


读16个DI(DI0--DI15):01 02 00 00 00 10 79 C6


起始地址00 00;DI个数为00
10


03


读一路或多路寄存器输出(读AO)


读AO0:   01 03 00 00 00 04 44 09


00 04表示读两路AO,在TPC中占4个寄存器(16bit)


04


读一路或多路寄存器输入(读AI)


  读AI0、AI1:01 04 00 00 00 04 F1 C9


00 04表示:在TPC中占4个寄存器(16bit)


05


写一路开关量输出(写DO)


DO0置1: 01 05 00 00 FF 00 8C 3A


DO1置1:   01 05 00 01 FF 00 DD FA


06


写一路寄存器输出(写AO)


0F


写一路或多路开关量输出(写DO)


10


写一路或多路寄存器输出(写AO)


AO1:01 10 00 02 00 02 04 40 40 00 00 66 62


AO0:01 10 00 00 00 02 04 40 00 00 00 E6 6F

5、程序参考:

下面是我在项目中写的modbus协议处理这块,数据主要包括:10路DO、16路DI、4路AI、7路温度、2路AO

char dealUsart2RecCom(void)
{
	  unsigned char  cCRC[2];//保存上位机发的命令的最后两位校验码

	  unsigned int RxDataBeginAddr;  //数据起始地址
	  unsigned int RxDataLen;//开关量或寄存器个数
	  //临时变量
		unsigned int tempDO=0x0000;
		unsigned int tempDO1=0xffff;
		unsigned int tempDI=0x0000;
		unsigned int tempDI1=0xffff;
	  unsigned int i;
		unsigned int tempDOH=0x00;
		unsigned int tempDOL=0x00;
		unsigned int tempvalue=0x00;
		unsigned int tempchID=0x00;

		float tempdax; 	

	        memset(cCRC,0,2);
		memset(SendData2,0,sizeof(SendData2));
                if(USART2_RX_LEN<8)
		{
			USART2_RX_LEN=0;//数据长度
                        memset(USART2_RX_BUF,0,sizeof(USART2_RX_BUF));
                        return 1; //命令至少8个字节
		}
	        cCRC[0]=USART2_RX_BUF[USART2_RX_LEN-2];
                cCRC[1]=USART2_RX_BUF[USART2_RX_LEN-1];
		PASSCRC(USART2_RX_BUF,USART2_RX_LEN-2);	//CRC校验
	        if((cCRC[0]==USART2_RX_BUF[USART2_RX_LEN-2])&&(cCRC[1]==USART2_RX_BUF[USART2_RX_LEN-1]))
		{//校验成功
			SendData2[0]=USART2_RX_BUF[0];//从机地址
			SendData2[1]=USART2_RX_BUF[1];//功能码
			//USART2_RX_BUF[2] 是高8位 USART2_RX_BUF[3]是低8位
			RxDataBeginAddr=USART2_RX_BUF[2]*256+USART2_RX_BUF[3];//转化成10进制
			RxDataLen=USART2_RX_BUF[4]*256+USART2_RX_BUF[5];//转化成10进制
		        if(SendData2[1]==0x01)//0x01-读开关量输出DO的状态 //功能码
			{
				if (RxDataLen%8==0)  SendData2[2]=RxDataLen/8;//字节个数   响应数据长度按字节来算
				else SendData2[2]=RxDataLen/8+1;
				//读取10路DO
			        tempDO=0x0000;
				tempDO1=0xffffffff;
				tempDO=DO_OUTState();
				tempDO=tempDO>>RxDataBeginAddr;//起始DO位 如RxDataBeginAddr=2,即从第三个DO开始
				tempDO1=~(tempDO1<<RxDataLen);//tempDO1的低RxDataLen位为1,其余为0
				tempDO=tempDO&tempDO1;//获取起始地址为RxDataBeginAddr,个数为RxDataLen的DO状态
                                if(RxDataLen<9)//小于等于8个DO,一个字节就够
	                        {
	        	        SendData2[3]=tempDO;
	        	        SendDataLength2=4;//发送数据的长度  不算校验
	                         }
				else
				{
					SendData2[3]=tempDO;
					SendData2[4]=tempDO>>8;
					SendDataLength2=5;//发送数据的长度
				}
			  }
			else if(SendData2[1]==0x02)//0x02-读开关量DI
			{
				if (RxDataLen%8==0)  SendData2[2]=RxDataLen/8;//字节个数   响应数据长度按字节来算
				else SendData2[2]=RxDataLen/8+1;
				//读取16路DI
				tempDI=0x0000;
				tempDI1=0xffffffff;
				tempDI=DI_In();
				tempDI=tempDI>>RxDataBeginAddr;//起始DI位 如RxDataBeginAddr=2,即从第三个DI开始
				tempDI1=~(tempDI1<<RxDataLen);//tempDI1的低RxDataLen位为1,其余为0
				tempDI=tempDI&tempDI1;//获取起始地址为RxDataBeginAddr,个数为RxDataLen的DI状态
				if(RxDataLen<9)//小于等于8个DI,一个字节就够
				{
					SendData2[3]=tempDI;
					SendDataLength2=4;//发送数据的长度  不算校验
				}
				else
				{
					SendData2[3]=tempDI;
					SendData2[4]=tempDI>>8;
					SendDataLength2=5;//发送数据的长度
				}
			}
			else if	(SendData2[1]==0x03)//读输出寄存器  2个AO
			{
				SendData2[2]=RxDataLen*2;//浮点数占4个字节
				for(i=0;i<RxDataLen/2;i++)//浮点数个数
				{
					ftoc(*(dax+RxDataBeginAddr/2+i));//浮点数转化成四个字节数据
					SendData2[3+i*4]=e[3];
					SendData2[3+i*4+1]=e[2];
					SendData2[3+i*4+2]=e[1];
					SendData2[3+i*4+3]=e[0];
				}
				SendDataLength2=3+i*4;//发送数据的长度
			}
			else if (SendData2[1]==0x04)//读输入寄存器 AI
			{
			       SendData2[2]=RxDataLen*2;//浮点数占4个字节,发长度为4。RxDataLen为寄存器(16bit)个数
				//AI  4路
				for(i=0;i<RxDataLen/2-7;i++)//RxDataLen/2为浮点数个数 4路AI
				{
					ftoc(adx[RxDataBeginAddr/2+i]);
					SendData2[3+i*4]=e[3];
					SendData2[3+i*4+1]=e[2];
					SendData2[3+i*4+2]=e[1];
					SendData2[3+i*4+3]=e[0];
				}
				//SendDataLength2=3+i*4;//发送数据的长度
				//温度数据  7路:6路温度 1路平均
			        for(i=0;i<7;i++)//7路温度
				{
					ftoc(fBuffTemp[i]);
					SendData2[3+(RxDataLen/2-7)*4+i*4]=e[3];
					SendData2[3+(RxDataLen/2-7)*4+i*4+1]=e[2];
					SendData2[3+(RxDataLen/2-7)*4+i*4+2]=e[1];
					SendData2[3+(RxDataLen/2-7)*4+i*4+3]=e[0];
				}
                                SendDataLength2=3+(RxDataLen/2-7)*4+i*4;//发送数据的长度
			}
			else if (SendData2[1]==0x05)//写一路开关量
			{
				for(i=2;i<6;i++)
				{
					SendData2[i]=USART2_RX_BUF[i];
				}
				SendDataLength2=6;
                                //DO数据处理  10路DO
				if(RxDataBeginAddr<10)
				{

					if (USART2_RX_BUF[4]==0xFF) DO_Out(1,RxDataBeginAddr);
					else DO_Out(0,RxDataBeginAddr);
				}
			}
			else if (SendData2[1]==0x0F)//写一路或多路开关量
			{
				for(i=2;i<6;i++)
				{
					SendData2[i]=USART2_RX_BUF[i];
				}
				SendDataLength2=6;
				//DO数据处理  10路DO
				tempDOH=0x00;
				tempDOL=0x00;
				tempvalue=0x00;
				tempchID=0x00;
				if(RxDataLen<9)//小于等于8路DO
				{
					tempDOL=USART2_RX_BUF[7];
					tempDOL=tempDOL>>RxDataBeginAddr;
					tempchID=RxDataBeginAddr;//起始地址
					for(i=0;i<RxDataLen;i++)
					{
						tempvalue=(tempDOL>>i)&0x01;
						DO_Out(tempvalue,tempchID+i);
					}
				}
				else
				{
					tempDOH=USART2_RX_BUF[7];
					tempDOL=USART2_RX_BUF[8];
					tempDOL=tempDOL>>RxDataBeginAddr;
					tempchID=RxDataBeginAddr;//起始地址
					for(i=0;i<8;i++)
					{
						tempvalue=(tempDOL>>i)&0x01;
						tempchID=i;
						DO_Out(tempvalue,tempchID);
					}
					tempchID=tempchID+1;
					for(i=0;i<RxDataLen-8;i++)
					{
						tempvalue=(tempDOH>>i)&0x01;
						DO_Out(tempvalue,tempchID+i);
					}
				}
			}
			else if (SendData2[1]==0x10)//写一路或多路寄存器	 AO
			{
				for(i=2;i<6;i++)
				{
					SendData2[i]=USART2_RX_BUF[i];
				}
				SendDataLength2=6;
				//写一路寄存器	 AO数据处理  2路AO
				for(i=0;i<4;i++)
				{
					e[i]=SendData2[10-i];//字节倒序存入e[4],e[0]=SendData[10];e[1]=SendData[9];                                        e[2]=SendData[8];e[3]=SendData[7];
				}
				if(RxDataLen==2)//TPC里面模拟量32bit 每个模拟量对应两个寄存器  RxDataLen是2的倍数
				{
					tempdax=ByteToFloat(e);//将字节转化成浮点数
					Dac_Set_Vol(RxDataBeginAddr/2,tempdax);
				}
			}
			//对数据进行校验
		PASSCRC(SendData2,SendDataLength2);
		SendDataLength2=SendDataLength2+2;
		USART2_Send_Data(SendData2,SendDataLength2);
		Delay_us(1000);//1ms
		}
		else
		{
			SendDataLength2=0;
			memset(SendData2,0,sizeof(SendData2));
		}
	  USART2_RX_LEN=0;//数据长度
	  memset(USART2_RX_BUF,0,sizeof(USART2_RX_BUF));
          return 1;
}
时间: 2024-10-07 05:26:04

modbus协议(2)的相关文章

模拟Modbus协议问题

问题: 在嵌入式系统开发中,Modbus协议是工业控制系统中广泛应用的一种协议.本题用来简单模拟Modbus协议,只需根据条件生成符合该协议的数据帧,并解析所获取的数据.假设设备使用的协议发送数据格式如下:<SlaveAddress, 1 Byte> <Function, 1 Byte> <Start Address, 2 Bytes> <NumberofBytes, 2 Bytes> <Checksum, 2 Bytes>其中前四项将在输入条件

基于AVR128的简单Modbus协议实现

Modbus通讯协议是由Modicon公司在1979年开发的,应用于工业现场控制的总线协议.Modbus通讯系统包括带有可编程控制的芯片节点和公共传输线组成,其目的是用于多节点数据的采集和监控.Modbus协议采用主从模式,通讯系统中有一个主机对多个节点从机进行监控,从机节点最多支持247个.每个从机均有自己独立的从机地址,而且改地址能够被主机识别. 能够支持Modbus协议的通讯系统有RS-232,RS-422,RS-485等.同时Modbus协议具有标准.开放.免费.帧格式简单等特点而被广大

简单Modbus协议数据源工具实现(一)WinForm

这是一个学习C#.Winform的自我回顾过程,用来发现存在的不足,也为了推动自己继续学习. 大学通信专业毕业之后,进入了一家电力科技公司从事软件开发工作,主要用的是Delphi语言进行电力通信协议的上位机开发.因为上位机需要与下位机通信才好进行测试,而事实上没有那么多现成的装置给你借用调试,加上公司慢慢的开始推行C#/WPF来做一些定制软件,所以想学习一下C#,刚好现在也有一个自身的需求出现--上位机程序调试困难,所以就从最易入手的winform程序切入,慢慢的加深对于C#语言的理解.于是就打

MODBUS协议相关代码(CRC验证 客户端程序)

Modbus协议是一种已广泛应用于当今工业控制领域的通用通讯协议.通过此协议,控制器相互之间.或控制器经由网络(如以太网)可以和其它设备之间进行通信.Modbus协议使用的是主从通讯技术,即由主设备主动查询和操作从设备.一般将主控设备方所使用的协议称为Modbus Master,从设备方使用的协议称为Modbus Slave.典型的主设备包括工控机和工业控制器等:典型的从设备如PLC可编程控制器等.Modbus通讯物理接口可以选用串口(包括RS232和RS485),也可以选择以太网口. 1.十六

RTU的优势与Modbus协议介绍

RTU是REMOTE TERMINAL UNIT 的简称,即远方数据终端,用于监视.控制与数据采集的应用.具有遥测.遥信.遥调.遥控功能.RTU功能必须确保两种功能:1,有数据传输功能.2,有采集和控制功能. 经过多年的PLC+DTU在工业自动化应用中已经普遍采用这种方式在进行数据的在线监测和远程控制.当RTU面世后,RTU集成的A/D和I/O采集功能已经可以达到取代部分PLC功能了. 1,在一些相对简单的温度.压力.湿度.水位.烟雾等传感器的数据采集监测,完全已经可以通过RTU取代前端早期的P

在Android程序中使用Modbus协议时报 java.net.SocketException: recvfrom failed: ECONNRESET解决办法

最近在开发基本Modbus协议的Android端PLC控制程序,C#版程序没有任何问题,移到JAVA下出现各种问题,其中比较苦恼的是java.net.SocketException: recvfrom failed: ECONNRESET错误. 开始我的解决方法是每次向PLC发送一条数据后关掉socket,但是这样做太极端了. 经过分析发现是由于发送的数据包大小我设定为256,当写入1个寄存器值时,Modbus服务端直接报错,关掉了socket连接,将发送的数据包大小改成实际的大小后解决问题.

【modbus】modbus协议入门讲解

前言 modbus是自动控制行业一个应用较为广泛的通信协议,就像RS485和RS232一样常用. 应用 工业现场的控制设备,有继电器.接近开关.电磁阀.压力表等.按信号类型可以归纳为:DI(数字输入).DO(数字输出).AI(模拟输入).AO(模拟输出). modbus协议可以

modbus协议讲义

Modbus 一个工业上常用的通讯协议.一种通讯约定.Modbus协议包括RTU.ASCII.TCP.其中MODBUS-RTU最常用,比较简单,在单片机上很容易实现.虽然RTU比较简单,但是看协议资料.手册说得太专业了,起初很多内容都很难理解.    所谓的协议是什么?就是互相之间的约定嘛,如果不让别人知道那就是暗号.现在就来定义一个新的最简单协议.例如, 协议: “A” --“LED灭”       “B” --“报警”       “C” --“LED亮”单片机接收到“A”控制一个LED灭,

基于MODBUS协议的单片机与(串口屏)触摸屏通信(图文)

导读:触摸屏能够直观.生动地显示运行参数和运行状态,而且通过触摸屏画面可以直接修改系统运行参数,人机交互性好.触摸屏和单片机通信,需要根据触摸屏采用的通信协议为单片机编写相应的通信程序.Modbus协议是美国Modicon公司推出的一种有效支持控制器之间以及控制器经由网络(例如以太网)和其它设备之间进行通信的协议.关键词:触摸屏,MCS-51单片机,Modbus协议,通信 在工业控制中经常需要观察系统的运行状态或者修改运行参数.触摸屏能够直观.生动地显示运行参数和运行状态,而且通过触摸屏画面可以

C# MODBUS协议 上位机(转)

源:C# MODBUS协议 上位机 C#写了一款上位机监控软件,基于MODBUS_RTU协议. 软件的基本结构: 采用定时器(Timer控件)为时间片. 串口采用serialPort1_DataReceived中断接收,并进行MODBUS格式判断. 把正确接收的数据取出,转换为有特定的结构体中. 数据通过时间片实时刷新. MODBUS协议(这里不介绍了,网上有很多的权威资料). 串口接收问题 这里采用的是MODBUS_RTU协议,是没有回车等明显的结束符的哈.所以在C#也不可以用serialPo