Qt 串口/网口通信:Hex 与 ASCII 编码转换深度指南

做 Qt 上位机开发时,常常会遇到这样的疑问:设备到底需要我发送 "01 03 00 00",还是发送 0x01 0x03 0x00 0x00?

假设你在代码中定义:

cpp 复制代码
QString str = "01 03 00 00";

这只是一个字符串。如果直接发送出去,硬件线路上实际传输的二进制字节是:

cpp 复制代码
30 31 20 30 33 20 30 30 20 30 30

对应关系:

cpp 复制代码
'0' = 0x30 | '1' = 0x31 | ' ' = 0x20

但从协议文档来看,设备真正期望收到的往往是 01 03 00 00 这 4 个字节。


一、概念厘清:Hex、ASCII 与字节

1. Hex 到底是什么?

Hex(十六进制)本质上只是二进制数据的显示与表达方式,并不是一种独立的编码类型。

cpp 复制代码
十进制:1  |  十六进制:0x01  |  二进制:0000 0001

对于数值 0x41:

  • 它既可以表示整数 65;

  • 在 ASCII 表中也可以映射为字符 'A'。

核心误区:0x41 ≠ "0x41"。

  • 0x41 是 1 个原始字节;

  • 而 "0x41" 是 4 个 ASCII 字符:'0'、'x'、'4'、'1',对应字节为 30 78 34 31。

2. ASCII 又是什么?

ASCII 是将物理字节映射为可视文本的字符编码标准。

字符 ASCII 十六进制 字符 ASCII 十六进制
'A' 0x41 'a' 0x61
'B' 0x42 '0' 0x30
'C' 0x43 '1' 0x31
cpp 复制代码
QByteArray data1 = "ABC"; // 实际字节:41 42 43

QByteArray data2;
data2.append(char(0x41));
data2.append(char(0x42));
data2.append(char(0x43)); // 实际字节:41 42 43

两者发送到总线上完全一致,但它们在逻辑上的语义完全不同。


二、Qt 核心数据类型映射

在工业通信开发中,最关键的三个数据类型职责清晰明确:

cpp 复制代码
 QString ────────► [ 文本层 ]  例:"HELLO"(用于 UI 控件显示)
         │
         ▼
    QByteArray ───────► [ 字节层 ]  例:48 45 4C 4C 4F(物理通信传输)
         │
         ▼
       char ──────────► [ 元素层 ]  例:0x48(单个字节存储)

通信层与协议层必须统一以 QByteArray 为核心。切忌把所有数据都直接用 QString 处理。


三、常用编码转换技巧与 API 规范

1. ASCII / 文本 与 QByteArray 转换

文本转字节(TX)
cpp 复制代码
QString text = "HELLO";

// Latin1 编码(单字节字符)
QByteArray dataLatin1 = text.toLatin1(); // 48 45 4C 4C 4F

// UTF-8 编码(多字节字符)
QString cnText = "你好";
QByteArray dataUtf8 = cnText.toUtf8(); // UTF-8 下中文占多字节

注意:QString::length() 代表字符数,而 QByteArray::size() 代表实际字节数,二者在处理非纯 ASCII 字符串时不一定相等。

字节转文本(RX)​​​​​​​
cpp 复制代码
QByteArray data = "HELLO";

// 确认收到的是文本协议时方可转换
QString text1 = QString::fromLatin1(data);
QString text2 = QString::fromUtf8(data);

重要原则:只有确定收到的是纯文本协议时,才使用 QString 转换。

对于二进制协议,绝不能先转 QString 再解析,而应该直接按字节偏移截取:​​​​​​​

cpp 复制代码
// 假设二进制接收:01 03 02 00 64
quint8 address  = static_cast<quint8>(data[0]); // 0x01
quint8 function = static_cast<quint8>(data[1]); // 0x03
quint16 value   = (static_cast<quint8>(data[3]) << 8) |
                   static_cast<quint8>(data[4]); // 0x0064 -> 100

2. Hex 字符串与真实字节互转(关键场景)

工业调试软件用户常在输入框填入 Hex 文本,例如 "01 03 00 00 00 02",需通过 QByteArray::fromHex() 还原为真正的物理字节发送。

Hex 文本转物理字节(TX)
复制代码
Hex 文本 "01 03 FF A0" ──[ QByteArray::fromHex() ]──► 物理二进制 01 03 FF A0
cpp 复制代码
QByteArray hexStr = "01 03 00 00 00 02";
QByteArray sendData = QByteArray::fromHex(hexStr); // 转换后变为真正的 6 个字节

serial.write(sendData); // 正确发往串口
物理字节转 Hex 文本(日志输出 / RX 展示)​​​​​​​
cpp 复制代码
QByteArray rxBuffer;
rxBuffer.append(char(0x01));
rxBuffer.append(char(0x03));
rxBuffer.append(char(0x00));
rxBuffer.append(char(0x64));

// 加空格分隔并转大写,提升日志可读性
QString logText = rxBuffer.toHex(' ').toUpper();
qDebug() << "[RX]" << logText; // 输出: [RX] 01 03 00 64

四、高级开发坑点与避坑策略

1. ASCII 协议与二进制协议对比​​​​​​​

复制代码
ASCII 协议(文本流) : "TEMP=25.6\r\n" ──► 可读性好、占用空间大二进制协议(字节流) : AA 55 01 03 00 64  ──► 数据紧凑、解析高效、无编码歧义

2. char 类型符号问题(极易踩坑)

在 C++/Qt 中,QByteArray 的元素类型是 char,其是否有符号(signed / unsigned)取决于编译器与平台实现。​​​​​​​

cpp 复制代码
QByteArray data;
data.append(char(0xFF));

// ❌ 错误:在很多平台 char 为 signed,0xFF 会变负数 -1
char rawVal = data[0];

// ✅ 正确:显式强转为 quint8 (0~255)
quint8 byteVal = static_cast<quint8>(data[0]); // 255

3. 大小端序(Endianness)

组合十六进制显示只代表字节顺序,不代表数据高低位组合逻辑:​​​​​​​

cpp 复制代码
// 接收到两个字节:12 34
// 1. 大端模式(Big-Endian / 网络字节序) -> 0x1234
quint16 beVal = (static_cast<quint8>(data[0]) << 8) | static_cast<quint8>(data[1]);

// 2. 小端模式(Little-Endian) -> 0x3412
quint16 leVal = static_cast<quint8>(data[0]) | (static_cast<quint8>(data[1]) << 8);

五、工程化推荐实现

1. Hex 工具类封装

为避免在项目各处重复编写转化与去空格逻辑,推荐封装全局命名空间:​​​​​​​

cpp 复制代码
namespace HexUtils {

/**
 * @brief 将带有空格/无空格的 Hex 字符串转为真实 QByteArray
 */
inline QByteArray fromHex(const QString& text) {
    QByteArray hex = text.toLatin1();
    hex.replace(" ", ""); // 自动清除空格
    return QByteArray::fromHex(hex);
}

/**
 * @brief 将物理字节数组格式化为带空格的大写 Hex 字符串
 */
inline QString toHex(const QByteArray& data) {
    return QString::fromLatin1(data.toHex(' ').toUpper());
}

} // namespace HexUtils

2. 上位机分层数据流模型

成熟的工控上位机系统应当将通信解耦,通信层只传递 QByteArray,展示层再转 QString

六、速查对账表与核心总结

操作需求 API / 实现方式 发送/得到示例
发送文本 QString::toLatin1() "A" → 0x41
发送二进制指令 QByteArray::fromHex("01 03") "01 03" → 0x01 0x03
格式化输出日志 data.toHex(' ').toUpper() 0x01 0x03 → "01 03"
提取无符号单字节 static_cast<quint8>(data[i]) 防止 0xFF 溢出变 -1

一句话要领:

ASCII "01 03" 发送的是字符 30 31 20 30 33;Hex "01 03" 还原后发送的是真正字节 01 03。保持通信逻辑全程采用 QByteArray,只在 UI 呈现时转 QString,即可避开绝大多数上位机通信 Bug。

相关推荐
码爸1 小时前
多源异构数据库实时同步至 Doris
数据库·flink·scala
王大傻09281 小时前
堆叠注入(Stacked Queries Injection)详解:原理、利用与防御
服务器·网络·数据库·web安全·网络安全
佳児素花痴╮1 小时前
线程知识点
java·开发语言·jvm
ClouGence1 小时前
数据库迁移工具 CloudCanal v6.5.0.0 发布:新增 TDSQL PostgreSQL 多条链路,支持 MongoDB 双向同步
数据库·mysql·mongodb
AIGS0012 小时前
从814张表到250个本体:本体建模在做什么
数据库·excel·erp·数据中台·智能问数·本体语义·企业大脑
学掌门2 小时前
老司机带你十分钟入门Python!
开发语言·python
zhangzeyuaaa2 小时前
深入 Ruby:return 退出规则完全指南
开发语言·ruby
fengkai45452 小时前
十二、Redis -1
运维·数据库·redis
shaibdoio2 小时前
AI主动获客的意图识别工程实现:从规则匹配到模型微调
java·开发语言·人工智能·长沙瞬维ai