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。

相关推荐
懂点Tech4 小时前
ctfshow misc28:GIF 高度修改解题与脚本实现
开发语言·windows·python
云和数据.ChenGuang4 小时前
Python 重写(Override)深度解析:从 MRO 到 super() 的完整机制
java·开发语言·人工智能·python·langchain·1024程序员节
摇滚侠5 小时前
《On Java 中文版 基础卷》阅读笔记 实现隐藏 12
java·数据库·笔记
川石课堂软件测试5 小时前
学习软件测试需掌握的一些知识
数据库·python·sql·功能测试·网络协议·mysql·http
美狐美颜SDK开放平台5 小时前
视频美颜SDK试用DEMO阶段应该测试什么?直播APP接入实操指南
开发语言·人工智能·深度学习·音视频·美颜sdk·直播美颜sdk·第三方美颜sdk
dtq04245 小时前
C++小案例 - 日志实战
开发语言·c++
星夜离尘566 小时前
MATLAB ADC 码流触顶诊断:命中数、缺口分段与削顶不可辨识性
开发语言·matlab
2501_937767166 小时前
自建 SQL 数据库定时备份踩坑复盘与落地思路
数据库·sql
码匠许师傅6 小时前
【C++三方组件】Qt 概览:桌面 C++ 的事实标准与许可证三轨
开发语言·c++·qt
码匠许师傅7 小时前
【C++三方组件】POCO:跨平台系统编程全家桶
开发语言·c++