Modbus协议完全资料与程序解析
switch(Modbus_mode) //通过判断模式来进行对响应的发送
{
case Modbus_read_coil:
read_coil_proc();
break;
……
default:
return;
break;
}
这样的做的话,就可以吧解析函数,执行函数和具体的实施函数分开来弄,层次多多少少要清晰一些
下面就是针对01,02,03,04,05,06,15,16几个功能码的执行及返回进行说明
在说明各功能函数之前,先说说响应。
上面说的那两个函数只不过是对一帧的外围进行解析与判断,至于具体的参数,还需要功能函数去解析与返回,功能函数要做的事情有3个,1个是参数的解析,2是执行,3是返回响应。
先说响应,响应是有特点的,第一个字节肯定是自己的本机地址,第二个字节肯定是功能码,最后两个字节肯定是crc校验,所以说,在发送缓冲中,基本上4个字节已经定死了
Modbus_send_buf[0] = Modbus_addr;
Modbus_send_buf[1] = Modbus_read_input_reg; //相应的功能码,每个功能寒暑都不一样
再经过执行函数最后算crc
modbus_crc = crc16(Modbus_send_buf,temp); //计算发送crc数据
Modbus_send_buf[temp] = modbus_crc >> 8; //计算
temp++;
Modbus_send_buf[temp] = modbus_crc & 0xff; //return num 高位
5.1 01 读线圈状态
#define Modbus_read_coil 0x01
其实表面上挺难理解的,啥线圈啥的,但你仔细看看就可以了解,就是读输出数字量,如果你写下位机的话,其实就是控制读取输出io,说白了,就是把目前的io输出状态返回给主机。这些io连接的可能是继电器,也可能是一些开关之类的东西,也就是些数字信号。读数字输出信号。
计算机发送命令:[设备地址] [命令号01] [起始寄存器地址高8位] [低8位] [读取的寄存器数高8位] [低8位]
设备响应:[设备地址] [命令号01] [返回的字节个数][数据1][数据2]...[数据n][CRC校验的低8位] [CRC校验的高8位]
简单的说就是返回所有的输出io的值,放在一个或者几个字节里,可以用判断的方法来实现,当然,也可以用与或的方式实现。
if(P1_0 == 1)
{
temp |= (1<<8);
}
else
{
temp &= (1<<8);
}
将temp的值放入第四个缓冲区,当然这根据设备的io口,编程时就已经确定了的。接下来就可以进行crc计算了。最后发送即可。
Modbus_send_buf[3] = temp;
modbus_crc = crc16(Modbus_send_buf,4);
Modbus_send_buf[4] = modbus_crc >> 8;
Modbus_send_buf[5] = modbus_crc & 0xff; //return num 高位
5.2 02 读只可读数字量寄存器(输入状态)
基本上和01意思差不多,只不过这个功能码返回的数据是输入io的数据,和01的区别是01可读可改,而02只可读不可改。也就是输入的状态。数据不可由设备本身控制。程序方面和01程序一样。
5.3 03读可读写模拟量寄存器(保持寄存器)
说简单点就是读da,da属于模拟量,也可以输出,但是以模拟量的方式来进行传输的
计算机发送命令:[设备地址] [命令号03] [起始寄存器地址高8位] [低8位] [读取的寄存器数高8位] [低8位] [CRC校验的低8位] [CRC校验的高8位]
设备响应:[设备地址] [命令号03] [返回的字节个数][数据1][数据2]...[数据n][CRC校验的低8位] [CRC校验的高8位]
其中返回字节个数,为读取寄存器数乘2
写程序时,首先要注意数据个数,temp = Modbus_recevie_buf[5];一般寄存器个数不会超过255,个数取读取寄存器个数的低八位即可。返回即乘2,temp = temp << 1;,下面要做的就是一个循环for(i = 0;i < temp ; i += 2),把需要的数据放入发送数组。其内容是
Modbus_send_buf[i+3]=(data_v&0xff00)>>8;
Modbus_send_buf[i+4]=data_v&0x0ff;
由于帧的前面3个是地址,功能码,和返回字节个数,所以循环从第四个数据开始存放。data_v为读取的数据,在程序中还需要其他语句配合。比如:data_v = updateValue();
循环后就可以进入crc校验了可以利用返回字节数来确定crc的校验个数temp = temp + 3;,最后计算发送字节的个数
send_cnt = Modbus_recevie_buf[5]*2 + 5 ; //数据发送个数 数据+地址+命令+返回数据个数+crc低+crc高
最后将数据发送出去即可。
5.4 04读只可读模拟量寄存器(输入寄存器)
和03的区别是04就是读ad,ad输入输入模拟两,只能读,不能改,同样也是以模拟两的方式来进行传输的。其程序 与03类似
5.5 05写数字量(线圈状态)
05则是修改io口输出状态,数字量输出。
计算机发送命令:[设备地址] [命令号05] [需下置的寄存器地址高8位] [低8位] [下置的数据高8位] [低8位] [CRC校验的低8位] [CRC校验的高8位]
设备响应:若执行成功,则原样返回
写程序时,首先确定需要修改的io口,然后根据0xff00或0x0000来置位或清零该数据位。执行完成后,将接收到的数据重新发送即可 Uart0_senddata(Modbus_recevie_buf,8);
5.6 06写单个模拟量寄存器(保持寄存器)
06为修改设备da数据,模拟量传输数据。
计算机发送命令:[设备地址] [命令号06] [需下置的寄存器地址高8位] [低8位] [下置的数据高8位] [低8位] [CRC校验的低8位] [CRC校验的高8位]
设备响应:若执行成功,原样返回即可
5.7 16主机设置寄存器
简单的说,就是一次设置多个da,以一个偏移量为准,一次设置多个输出模拟里量
计算机发送命令:[设备地址] [命令号10] [开始地址高8位] [低8位] [寄存器个数高8位] [低8位] [第一个寄存器数据高][第一个寄存器数据低][第二个寄存器数据高][第二个寄存器数据低]……[CRC校验的低8位] [CRC校验的高8位]
命令响应:功能码[0x10],寄存器起始地址高字节,低字节,要写的寄存器数量的高字节,低字节,CRC校验低字节,高字节
在程序中,首先要获取寄存器个数
num = Modbus_recevie_buf[6] - 2;
然后进入循环,一次把寄存器数据提取出来for(i = 0; i < num; i = i + 2)
在循环的内部提取数据temp = (((unsigned int)(Modbus_recevie_buf[i+7])<<8)|(Modbus_recevie_buf[i+8]));
以上就是我在项目中涉及到的一点modbus的通讯的下位机程序,不全,但总体的思路,接收数据并解析,解析后提取数据在设备上加载或采集,然后再按照响应的方式发送回去。
下回改进的方向,1,增加功能码2,增加宏定义及编译定义,3增加单片主机的程序,和pc主从机的程序。4,增加ascii的程序,和rtu同时设置。Pc机程序,采用c#号编写。
评论