1. 项目背景与需求分析
在嵌入式开发和工业控制领域,串口通信是最基础也是最关键的技术之一。当项目环境限制使用Qt框架时,开发者需要寻找替代方案来实现可靠的串口通信功能。这种情况常见于:
- 需要减少运行时依赖的轻量级应用
- 运行环境无法部署Qt运行时库的嵌入式系统
- 对执行文件大小有严格限制的场景
- 需要直接与硬件交互的低层开发
Windows平台提供了完整的API支持串口操作,主要包括:
- 文件系统接口(CreateFile/ReadFile/WriteFile)
- 设备控制接口(DeviceIoControl)
- 通信配置接口(SetCommState/GetCommState)
2. Windows API串口通信实现
2.1 基础通信流程
典型的Windows串口通信包含以下步骤:
c复制// 1. 打开串口
HANDLE hCom = CreateFile("\\\\.\\COM3",
GENERIC_READ | GENERIC_WRITE,
0, // 独占模式
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
// 2. 配置通信参数
DCB dcb = {0};
dcb.DCBlength = sizeof(DCB);
GetCommState(hCom, &dcb);
dcb.BaudRate = CBR_115200;
dcb.ByteSize = 8;
dcb.Parity = NOPARITY;
dcb.StopBits = ONESTOPBIT;
SetCommState(hCom, &dcb);
// 3. 设置超时
COMMTIMEOUTS timeouts = {0};
timeouts.ReadIntervalTimeout = 50;
timeouts.ReadTotalTimeoutConstant = 50;
timeouts.ReadTotalTimeoutMultiplier = 10;
SetCommTimeouts(hCom, &timeouts);
// 4. 读写数据
char buffer[256];
DWORD bytesRead;
ReadFile(hCom, buffer, sizeof(buffer), &bytesRead, NULL);
// 5. 关闭串口
CloseHandle(hCom);
2.2 关键参数详解
2.2.1 波特率设置
Windows支持的标准波特率包括:
- 110 bps
- 300 bps
- 600 bps
- 1200 bps
- 2400 bps
- 4800 bps
- 9600 bps
- 14400 bps
- 19200 bps
- 38400 bps
- 57600 bps
- 115200 bps
- 128000 bps
- 256000 bps
对于非标准波特率,可以使用以下方法:
c复制dcb.BaudRate = 460800; // 直接设置数值
2.2.2 数据流控制
流控制方式通过DCB结构的fOutX/fInX成员配置:
- 无流控:fOutX = FALSE, fInX = FALSE
- 软件流控(XON/XOFF):fOutX = TRUE, fInX = TRUE
- 硬件流控(RTS/CTS):fRtsControl = RTS_CONTROL_HANDSHAKE
2.3 异步通信实现
同步通信会阻塞线程,实际项目中更推荐异步方式:
c复制OVERLAPPED ov = {0};
ov.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
char buffer[256];
DWORD bytesRead;
if (!ReadFile(hCom, buffer, sizeof(buffer), &bytesRead, &ov)) {
if (GetLastError() == ERROR_IO_PENDING) {
WaitForSingleObject(ov.hEvent, INFINITE);
GetOverlappedResult(hCom, &ov, &bytesRead, FALSE);
}
}
CloseHandle(ov.hEvent);
3. 封装C++串口类
为提高代码复用性,可以封装一个SerialPort类:
cpp复制class SerialPort {
public:
SerialPort() : hCom(INVALID_HANDLE_VALUE) {}
bool open(const char* port, int baudRate) {
hCom = CreateFile(port, GENERIC_READ | GENERIC_WRITE,
0, NULL, OPEN_EXISTING,
FILE_FLAG_OVERLAPPED, NULL);
if (hCom == INVALID_HANDLE_VALUE) return false;
// 配置代码...
return true;
}
int read(char* buf, int len) {
OVERLAPPED ov = {0};
ov.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
DWORD bytesRead;
if (!ReadFile(hCom, buf, len, &bytesRead, &ov)) {
if (GetLastError() == ERROR_IO_PENDING) {
WaitForSingleObject(ov.hEvent, INFINITE);
GetOverlappedResult(hCom, &ov, &bytesRead, FALSE);
}
}
CloseHandle(ov.hEvent);
return bytesRead;
}
void close() {
if (hCom != INVALID_HANDLE_VALUE) {
CloseHandle(hCom);
hCom = INVALID_HANDLE_VALUE;
}
}
private:
HANDLE hCom;
};
4. 常见问题解决方案
4.1 端口占用问题
当CreateFile返回ERROR_ACCESS_DENIED时,表示端口被其他程序占用。解决方法:
- 使用任务管理器关闭可能占用端口的程序
- 重启计算机
- 使用以下命令释放端口:
cmd复制mode COM3: baud=9600
4.2 数据接收不完整
可能原因及解决方案:
- 缓冲区太小:增大输入缓冲区
c复制SetupComm(hCom, 4096, 4096); - 接收超时设置不合理:调整COMMTIMEOUTS参数
- 流控配置错误:检查RTS/CTS或XON/XOFF设置
4.3 波特率不匹配
症状表现为接收乱码,解决方法:
- 确认设备端和主机端波特率一致
- 检查时钟源是否稳定
- 对于长距离传输,降低波特率
5. 性能优化技巧
- 使用重叠I/O提高吞吐量
- 合理设置缓冲区大小(建议4KB以上)
- 批量读写减少系统调用次数
- 避免频繁打开关闭端口
- 对于高速通信(>115200bps),考虑使用内存映射文件
6. 跨平台兼容性考虑
虽然本文主要讨论Windows实现,但为方便后续移植,建议:
- 将平台相关代码隔离在单独模块
- 定义统一的接口规范
- 对于Linux/macOS,可以使用termios实现类似功能
7. 调试工具推荐
- 串口调试助手(如AccessPort、Termite)
- 逻辑分析仪(Saleae Logic等)
- Wireshark(配合USBPcap捕获USB转串口数据)
- 自定义的十六进制日志工具
8. 扩展功能实现
8.1 自定义协议解析
cpp复制class ProtocolParser {
public:
void feed(char data) {
buffer.push_back(data);
if (buffer.size() > MAX_FRAME_SIZE) {
buffer.clear();
}
if (checkComplete()) {
processFrame();
buffer.clear();
}
}
private:
std::vector<char> buffer;
bool checkComplete() {
// 实现协议特定的帧检测逻辑
}
void processFrame() {
// 处理完整数据帧
}
};
8.2 数据可视化
对于需要图形化显示的场景,可以:
- 使用GDI绘制实时曲线
- 集成第三方绘图库(如Matplotlib-cpp)
- 输出到CSV后用Excel分析
9. 安全注意事项
- 验证输入数据长度防止缓冲区溢出
- 检查返回值和错误代码
- 重要操作添加日志记录
- 实现看门狗机制检测通信异常
- 对关键配置参数进行范围检查
10. 替代方案比较
除了原生API,Windows平台还有其他串口通信方案:
| 方案 | 优点 | 缺点 |
|---|---|---|
| Windows API | 无需额外依赖,性能好 | 接口复杂,跨平台差 |
| .NET SerialPort | 使用简单 | 需要.NET环境 |
| boost.asio | 跨平台,现代C++接口 | 学习曲线陡峭 |
| libserial | 开源,接口友好 | Windows支持有限 |
在实际项目中,我通常会根据以下因素选择方案:
- 项目规模和复杂度
- 团队技术栈
- 后期维护需求
- 性能要求
- 跨平台需求
对于大多数嵌入式应用,原生API仍然是最可靠的选择,特别是在资源受限的环境中。虽然初期开发工作量较大,但可以获得最佳的性能和控制粒度。
