以太网通信控制板-关于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

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

.

posted on 2023-05-10 15:30  广源时代  阅读(250)  评论(0编辑  收藏  举报

导航

支付宝 QQ群