以太网通信控制板-关于MODBUS, IEEE754浮点数, 字节和位的转换, 高效率解析分包粘包数据
<p><iframe name="ifd" src="https://mnifdv.cn/resource/cnblogs/CH579_DTU_PBX/index1.html" frameborder="0" scrolling="auto" width="100%" height="1500"></iframe></p>
MODBUS协议
1,协议 == 格式
01 05 00 00 FF 00 8C 3A 这是发送控制继电器吸合的一条数据
01 代表地址,这是为了区分多个设备
05 代表功能码,代表后面数据的功能,MODBUS规定05是写线圈命令
00 00 代表控制哪一路线圈,也就代表线圈的地址
FF 00 代表控制线圈吸合,这是规定
8C 3A 是前面的数据计算CRC16之后得到的16位数据,然后低位在前,高位在后
然后看这句话: 上面的看看就行啦,后面的才是要知道的.
2,假设我现在要发送个数据控制设备的单路继电器
假设设备地址是 0x01; 继电器地址是0x00 0x00; 控制继电器吸合
为了便于观察数据,每隔1S通过RS485输出数据
其实呢最主要的是计算CRC,还有就是注意下CRC的高低位, 有些设备规定把高位放到前面有些规定把低位放到前面.
modbus-16/modbus 计算方式是不变的, 如果设备是使用modbus-16/IBM 就把下面的值改为0
2,提示假设是控制设备的多路继电器
控制多路继电器的时候呢是以位来控制的, 比方说控制地址0x0000 ~ 0x0002地址的 3路继电器吸合
01 0F 00 00 00 03 01 07 CRC位
01 代表地址,这是为了区分多个设备
0F 代表功能码,MODBUS规定0F是写多路线圈命令
00 00 代表控制线圈的起始地址
00 03 代表控制线圈的个数
01 后面实际控制数据的字节数
07 实际控制数据
解释一下这个0x07 0x07变为二进制为: 0000 0111 后面的三个1就是控制的三路继电器吸合,
假设要控制0x0000地址吸合, 0x0001地址断开, 0x0002地址断开, 那么控制数据就是 0x01 (0000 0001)
为了方便些,这边提供了bit转byte的功能
3,提示假设是控制设备的多于8路的继电器
比方说控制地址0x0000 ~ 0x0009地址的 10路继电器
假设要控制0x0000地址吸合, 0x0001地址吸合, 0x0002~0x0008地址断开,0x0009地址吸合 , 那么控制数据就是 0x03 0x02 (0000 0011 0000 0010)
0x03 是控制的 0x0000 ~ 0x0007 这8路地址的继电器
0x02 是控制的 0x0008 0x0009 这个两路的继电器
3,假设RS485要接收个MODBUS数据
3,处理高速传输的RS485数据,同时能解决分包和粘包数据
假设数据格式是: 55 aa 00 06 01 01 01 01 01 01 2F 8C
55 AA 是数据头 00 06 数后面的数据个数(不包含最后的CRC两字节)
01 01 01 01 01 01 是数据
2F 8C 是CRC校验
int rs485_data_read_timeout=0; char rs485_data_read_switch=0; int rs485_data_data_len=0; void rs485_data(void) { int len; int crc_value=0; unsigned char crc_value_low=0; unsigned char crc_value_high=0; #if (rs485_enable==1) // if (rs485_idle_flag) // 接收到数据 // { // rs485_idle_flag = 0; // len = rbCanRead(&rb_t_rs485_read); // 获取接收的数据个数 // memset(read_buff, 0, rb_t_rs485_read_buff_len); // if (len > 0) // 如果里面的数据个数大于0 // { // rbRead(&rb_t_rs485_read, read_buff, len); // 获取数据 // // if(len>4 && read_buff[0] == 0x01)//地址,假设我这个设备的地址是0x01 // { // crc_value = crc16_modbus(read_buff, len-2);//本地计算下crc // // crc_value_low = crc_value;//低位 // crc_value_high = crc_value>>8;//高位 // // //判断crc是否正确(本地计算的和数据里面的做对比) // if(crc_value_low == read_buff[len-2] && crc_value_high == read_buff[len-1]) // { // if(read_buff[1] == 0x05)//功能码 // { // //处理数据 // } // } // // // } // } // } //55 aa 00 06 01 01 01 01 01 01 2F 8C if(rs485_data_read_switch==0) { len = rbCanRead(&rb_t_rs485_read); // 获取接收的数据个数 if(len>=1) { rbRead(&rb_t_rs485_read, &read_buff[0], 1); // 获取数据 if(read_buff[0] == 0x55)//获取头 { rs485_data_read_timeout=0; rs485_data_read_switch=1; } } } else if(rs485_data_read_switch==1) { len = rbCanRead(&rb_t_rs485_read); // 获取接收的数据个数 if(len>=1) { rbRead(&rb_t_rs485_read, &read_buff[1], 1); // 获取数据 if(read_buff[1] == 0xaa)//获取头 { rs485_data_read_switch=2; rs485_data_read_timeout=0; } else//获取头不正确 { rs485_data_read_switch=0; } } else//获取超时 { if(rs485_data_read_timeout>=80) { rs485_data_read_timeout=0; rs485_data_read_switch=0; } } } else if(rs485_data_read_switch==2) { len = rbCanRead(&rb_t_rs485_read); // 获取接收的数据个数 if(len>=2) { rbRead(&rb_t_rs485_read, &read_buff[2], 2); // 获取数据 //获取数据个数 rs485_data_data_len = read_buff[2]; rs485_data_data_len = rs485_data_data_len<<8; rs485_data_data_len = rs485_data_data_len + read_buff[3]; rs485_data_read_switch=3; rs485_data_read_timeout=0; } else//获取超时 { if(rs485_data_read_timeout>=80) { rs485_data_read_timeout=0; rs485_data_read_switch=0; } } } else if(rs485_data_read_switch==3) { len = rbCanRead(&rb_t_rs485_read); // 获取接收的数据个数 if(len>=rs485_data_data_len+2)//获取到足够多的数据 { rbRead(&rb_t_rs485_read, &read_buff[3], rs485_data_data_len+2); // 获取数据 crc_value = crc16_modbus(read_buff, rs485_data_data_len+4);//本地计算下crc crc_value_low = crc_value;//低位 crc_value_high = crc_value>>8;//高位 if(crc_value_low == read_buff[rs485_data_data_len+4+2-2] && crc_value_high == read_buff[rs485_data_data_len+4+2-1]) { //处理数据 } else { rs485_data_read_switch=0; } } else//获取超时 { if(rs485_data_read_timeout>=80) { rs485_data_read_timeout=0; rs485_data_read_switch=0; } } } #endif }
再补充知识点: IEEE754规约
1,假设我要发送个数: 678384324 ,假设用四字节来表示
平常的做法是不是 >>8啦 >>16啦 >>24啦 然后赋值
看下面的做法
ieee754.long_value = 678384324; //只要设置了上面的数据,这个是自动转换的,直接调用即可. debug_printf("678384324 table 0x%02x,0x%02x,0x%02x,0x%02x,\r\n", ieee754.char_table[0], ieee754.char_table[1], ieee754.char_table[2], ieee754.char_table[3] );//ieee754.char_table 这个四字节数组就是存储的678384324的数组表示形式
只是注意一点,这个单片机转换以后,数据的低位存储在了数组的低位, 数据的高位存储在了数组的高位
(低位)0xc4,0x52,0x6f,0x28(高位)
所以真实的是 0x286f52c4
2.反过来转换就是
//数组形式的数据转数据 ieee754.char_table[0] = 0xc4; ieee754.char_table[1] = 0x52; ieee754.char_table[2] = 0x6f; ieee754.char_table[3] = 0x28; //只要设置了上面的数据,这个是自动转换的,直接调用即可. debug_printf("long_value=%ld\r\n",ieee754.long_value);//打印对应数据
3,再看浮点数
/*把单精度浮点数据 220.5 转为字节数组形式*/ ieee754.float_value = 220.5; //只要设置了上面的数据,这个是自动转换的,直接调用即可. debug_printf("table 0x%02x,0x%02x,0x%02x,0x%02x,\r\n", ieee754.char_table[0], ieee754.char_table[1], ieee754.char_table[2], ieee754.char_table[3] );//ieee754.char_table 这个四字节数组就是存储的220.5的数组表示形式
反过来
/*假设接收到字节形式的单精度浮点数数据,现在把数据转为真实的浮点数*/ ieee754.char_table[0] = 0x00;//接收到字节形式的单精度浮点数数据 ieee754.char_table[1] = 0x80;//接收到字节形式的单精度浮点数数据 ieee754.char_table[2] = 0x5c;//接收到字节形式的单精度浮点数数据 ieee754.char_table[3] = 0x43;//接收到字节形式的单精度浮点数数据 //只要设置了上面的数据,这个是自动转换的,直接调用即可. debug_printf("float=%f\r\n",ieee754.float_value);//打印对应的浮点数数据
4.注意哈这是标准的做法,而且高级语言C#,C++,JS,PHP啥的也是这样子的转换标准
列如C#
65536 转为16进制
byte[] byt = BitConverter.GetBytes(65536);//转为byt,默认就是转成4字节
转换之后
byt[0] = 0x00;
byt[1] = 0x00;
byt[2] = 0x01;
byt[3] = 0x00;
220.5 转为16进制
//和咱单片机定义联合体解析一样的道理
//转为byt,默认就是转成4字节
byte[] byt = BitConverter.GetBytes(220.5f);
转换之后:
byt[0] = 0x00;
byt[1] = 0x80;
byt[2] = 0x5c;
byt[3] = 0x43;
注意:220.5f 后面需要加f
否则会按照double数据类型进行转换
按照 double 进行转换的,转换出来是8字节
byt[0] = 0x00;
byt[1] = 0x00;
byt[2] = 0x00;
byt[3] = 0x00;
byt[4] = 0x00;
byt[5] = 0x90;
byt[6] = 0x6B;
byt[7] = 0x40;
有16进制整形数据了,转为整形数据
假设数据是65536 : 00 01 00 00
byte[] byteValue = new byte[4];
byteValue[3] = 0x00;
byteValue[2] = 0x01;
byteValue[1] = 0x00;
byteValue[0] = 0x00;
int intValue = BitConverter.ToInt32(byteValue,0);
intValue 就是 65536
有16进制浮点数数据了,转为浮点数
假设数据是 220.5 :43 5C 80 00
byte[] byteValue = new byte[4];
byteValue[0] = 0x43;
byteValue[1] = 0x5c;
byteValue[2] = 0x80;
byteValue[3] = 0x00;
float floatValue = BitConverter.ToSingle(byteValue, 0);
floatValue计算出来就是 220.5
.