modbus协议(2)

来源:互联网 发布:为什么淘宝订单异常 编辑:程序博客网 时间:2024/06/06 00:44

      上一篇介绍了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
23412表示双字元件高低字颠倒,但字内高低字节不颠倒   表示65536
34321表示双字元件内4个字节全部颠倒 表示 1677 7216

(8)32位浮点数解码顺序:调整双字元件的解码顺序,对于Modicon PLC,请设置为“2-3412”顺序解码。
32 位浮点数解码顺序 举例:0x3F80 0000
0―1234 表示双字元件不做处理直接解码(默认值) 表示 1.0
1―2143 表示双字元件高低字不颠倒,但字内高低字节颠倒 表示-5.78564e-039
23412表示双字元件高低字颠倒,但字内高低字节不颠倒  表示2.27795e-041
34321表示双字元件内4个字节全部颠倒 表示 4.60060e-041

(9)校验方式: 选择LRC校验值的组合方式, 对于 Modicon PLC及标准PLC 设备,
使用默认设置即可。
0LH[低字节,高字节]:校验结果为2个字节,低字节在前,高字节在后。
1HL[高字节,低字节]:校验结果为2个字节,高字节在前,低字节在后。

(10)分块采集方式:驱动采集数据分块的方式,对于Modicon PLC及标准 PLC备,使用默认设置可以提高采集效率。
0— 按最大长度分块:采集分块按最大块长处理,对地址不连续但地址相近的多个分块,分为一块一次性读取,以优化采集效率。
1— 按连续地址分块:采集分块按地址连续性处理,对地址不连续的多个分块,每次只采集连续地址,不做优化处理。
例如:有4区寄存器地址分别为1~579~12的数据需采集,如果选择“0-按最大长度分块” ,则两块可优化为地址1~12的数据打包1次完成采集;如果选择“1-按连续地址分块” ,则需要采集 3 次。
11416位写功能码选择:写 4 区单字时功能码的选择,这个属性主要是针对自己制作设备的用户而设置的,这样的设备4区单字写可能只支持0x10 功能码,而不支持0x06功能码。
00x06:单字写功能码使用0x06
10x10:单字写功能码使用0x10
注意:
1. “解码顺序”及“校验方式”设置:主要是针对非标准 ModbusRTU 协议的不同解码及校验顺序。当用户通过本驱动软件与设备通讯时,如果出现解析数据值不对,或者通讯校验错误(通讯状态为3),可与厂家咨询后对以上两项进行设置。而对于ModiconPLC及支持标准ModbusRTUPLC 及控制器等设备,一般需将“32位整数解码顺序”和“32位浮点数解码顺序”设置为“23412” 。 另外,在使用本驱动与“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                   0515                    读写        读写00001表示 0区地址1
[3 ]输入寄存器 BTWUBWBWDDUBDBDDDFSTR   04                     —          只读       只读30001表示3区地址1
[4 ]输出寄存器 BTWUBWBWDDUBDBDDDFSTR    03              0616                    读写       读写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 00DI个数为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

 DO01: 01 05 00 00 FF 00 8C 3A

 DO11:   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; <div>             //AI和温度数据整合        float temp_AI_T[11]={0.0};for(i=0;i<11;i++){if(i<=3){temp_AI_T[i]=adx[i];}else{  temp_AI_T[i]=fBuffTemp[i-4];}}</div>        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,其余为0tempDO=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路DItempDI=0x0000;tempDI1=0xffffffff;tempDI=DI_In();tempDI=tempDI>>RxDataBeginAddr;//起始DI位 如RxDataBeginAddr=2,即从第三个DI开始tempDI1=~(tempDI1<<RxDataLen);//tempDI1的低RxDataLen位为1,其余为0tempDI=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{<div>                                SendData2[2]=RxDataLen*2;//浮点数占4个字节,发长度为4。RxDataLen为寄存器(16bit)个数,for(i=0;i<RxDataLen/2;i++)//RxDataLen/2为浮点数个数  {        ftoc(temp_AI_T[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;//发送数据的长度 </div>}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路DOtempDOH=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;   }


1 0
原创粉丝点击