1.问题一
1.1.Modbus报文转int
方式一:
QByteArray data = QByteArray::fromHex(“0103040016003A9BE4”);
data[3]=0x00,data[4]=0x16
// 将十六进制字符串转换为字节
bool ok;
quint8 byte3 = hexBytes[3].toUShort(&ok, 16); // 00
quint8 byte4 = hexBytes[4].toUShort(&ok, 16); // 16
// 组合成 16 位整数
quint16 value1 = (byte3 << 8) | byte4; // 00 16
quint16 value2 = (byte5 << 8) | byte6; // 00 3A
std::cout << "Value 1 (00 16): " << value1 << std::endl; // 输出 22
方式二:
QByteArray data = QByteArray::fromHex(“0103040016003A9BE4”);
data[3]=0x00,data[4]=0x16
// 提取 00 16
quint16 value1 = static_cast((unsigned char)data[3] << 8 | (unsigned char)data[4]);
std::cout << "Value 1 (00 16): " << value1 << std::endl; // 输出 22
将 data[3] 左移 8 位,然后与 data[4] 按位或得到十进制22,如何经行进制转化得到22的?为什么是data[3]左移?为什么是按位或而不是按位与?什么情况下才能使用按位或?为什么要组成16位整数?数据本身一定要是8位的吗?
例子中,我们有 data[3] = 0x00 和 data[4] = 0x16,目标是将它们组合成一个 16 位的整数,并最终得到十进制的 22。下面我将详细解释这个过程,包括为什么选择左移和按位或操作,以及按位或和按位与的使用场景。
1.详细计算过程:
1.1左移操作:
data[3] = 0x00,二进制表示为 0000 0000。
将 0x00 左移 8 位:
结果是 0000 0000 0000 0000(即 0x0000)。
左移的目的是将 data[3] 的值放置到 16 位整数的高 8 位,为 data[4] 腾出低 8 位的位置。
1.2按位或操作:
data[4] = 0x16,二进制表示为 0001 0110。
将 0x0000 与 0x0016 进行按位或操作:
0000 0000 0000 0000 | 0000 0000 0001 0110 = 0000 0000 0001 0110。
结果是 0x0016。
1.3十六进制到十进制的转换:
0x0016 转换为十进制:
2.为什么是 data[3] 左移?
字节顺序:
在组合字节时,通常需要考虑字节顺序。常见的有大端序(big-endian)和小端序(little-endian)。
大端序:高位字节存储在低地址。
小端序:低位字节存储在低地址。
组合成 16 位整数:
假设我们要按照某种顺序(比如大端序)组合字节,data[3] 应该成为 16 位整数的高字节,data[4] 成为低字节。
为了将 data[3] 放置在 16 位整数的高 8 位,我们需要将其左移 8 位。
字节顺序:在组合多个字节成一个更大的整数时,需要考虑字节顺序。假设 data[3] 是高字节,data[4] 是低字节,因此需要将 data[3] 左移 8 位以放置在高 8 位。
组合目的:左移是为了在组合时保留各个字节的相对位置。
为什么是按位或而不是按位与?
按位或(|):
用于组合字节,因为它可以保留两个字节中的所有 1 位。
当需要将多个字节合并成一个更大的整数时,按位或能够确保所有信息都被保留。
按位与(&):
用于屏蔽位,即只保留两个数在该位上都是 1 的结果。
这通常用于清除特定位或检查特定位是否为 1。
使用场景
按位或的使用场景:
组合多个字节成一个更大的数。
设置特定位为 1。
合并多个标志位。
按位与的使用场景:
清除特定位为 0。
屏蔽不需要的位。
检查特定位是否为 1(通常与掩码一起使用)。
为什么要组成 16 位整数?
数据表示:许多数据格式和协议要求将多个字节组合成一个更大的整数进行处理。
处理效率:在某些硬件和编程语言中,处理更大单位的数据(如 16 位、32 位整数)可能比逐个字节处理更高效。
数据范围:16 位整数可以表示更大的数值范围(0 到 65535),适合需要更大数值范围的场景。
数据本身一定要是 8 位的吗?
不一定:数据的大小可以根据具体需求而变化。
常见情况:在计算机中,字节通常是 8 位,因为这是最小的可寻址数据单元。
其他情况:在某些应用中,可能会处理不同大小的数据单元,如 16 位、32 位或 64 位整数,具体取决于应用需求。
通过这种操作,我们可以有效地将两个字节组合成一个 16 位的整数,这在处理二进制数据、文件格式、网络协议等场景中非常常见。
2.Modbus报文float
2.1读取modbus float
报文
发送命令:01 03 00 00 00 02 C4 0B
01为从机地址,03为读多个寄存器,00 寄存器地址高八位,00为寄存器地址低8位,00为寄存器个数高八位,02位寄存器个数低八位,C4 0B为CRC校验,意思就是从地址为01的从机中在保持寄存器03中从寄存器地址为0000开始读取2个寄存器中的数据(0000,0001)。
接受命令:01 03 04 00 00 41 B4 CA 14
01为从机地址 03功能码 04字节数,(00 00 41 B4)为两个寄存器储存的数据内容其中(0000)寄存器与(0001)寄存器,CA 14为CRC校验
2.2取值
把00 00 41 B4取出来
uint8_t r[4];
r[0] = response[3];//00
r[1] = response[4];//00
r[2] = response[5];//41
r[3] = response[6];//B4
qDebug() << r[0] << r[1] << r[2] << r[3];//0 0 65 180(十进制的)、
2.3组合为uint_16的整形
uint16_t r2[2];
r2[0] = response[3] << 8 | response[4];//0
r2[1] = response[5] << 8 | response[6];//16820
2.3组合为uint_32的整形
2.3.1 // 两个寄存器组成一个浮点数,大端字节序
// uint32_t combinedRegister = (r2[0] << 16) | r2[1];
2.3.2// 两个寄存器组成一个浮点数,小端字节序
uint32_t combinedRegister = (r2[1] << 16) | r2[0];
2.4转化为float
方式一:
// float receivedFloat =
reinterpret_cast<float>(&combinedRegister);
方式二:(推荐)
float receivedFloat;
std::memcpy(&receivedFloat, &combinedRegister, sizeof(receivedFloat));
2.5 打印浮点数
qDebug() << “Value:” << receivedFloat;//22.5
问题二:
modbus写float
eg:
float valueToWrite=22.5
uint16_t wdata[2] { 0 };
memcpy(wdata, &valueToWrite, sizeof(float));
// 交换字节顺序(如果需要小端到大端的转换)
// uint16_t t = wdata[1];
// wdata[1] = wdata[0];
// wdata[0] = t;
uint8_t custom[4];
custom[0] = (wdata[0] >> 8) & 0xFF;//00
custom[1] = wdata[0] & 0xFF;//00
custom[2] = (wdata[1] >> 8) & 0xFF;//65
custom[3] = wdata[1] & 0xFF;//180
// 构造请求帧
// 写多个寄存器
// 0x01: 从站地址
// 0x52: 自定义功能码
// 0x00, 寄存器地址2个字节,寄存器地址高字节八位
// 0x00: 寄存器地址低字节八位,根据从机被读取的地址而定,范围是0x0000 至 0xFFFF
// 0x00, 0x02: 2个寄存器(如果是单个寄存器当前行可省略)
// 0x04: 数据个数
// custom[0], custom[1],custom[2], custom[3]: 数据
// CRC不用写
// 示例
uint8_t custom_request[] = { 0x01, 0x10, 0x00, 0x00, 0x00, 0x02, 0x04, custom[0], custom[1], custom[2], custom[3] };