如何在RS232字节传输后以最小延迟切换RTS电平?
问题分析与解决方案
核心问题
你遇到的问题本质是Windows串口API的WriteFile并非等待数据实际发送完成才返回——它只是将数据写入系统的串口发送缓冲区就返回了。此时直接拉低RTS,缓冲区里的数据还没发完就被截断。而Sleep(1)的延迟不准确是因为Windows系统默认的时钟调度粒度(通常是10-15ms),导致实际延迟远超5ms的限制。
解决步骤
- 监听串口发送完成事件:使用
SetCommMask设置监听EV_TXEMPTY事件(发送缓冲区为空,即数据已全部发送到硬件),通过WaitCommEvent等待该事件触发,确保字节完全发送后再拉低RTS。 - 缩小发送缓冲区:用
SetupComm将发送缓冲区设为1字节,减少数据在缓冲区的停留时间,进一步降低延迟。 - 移除冗余操作:main函数中重复调用的
SETRTS是多余的,com_write函数内已完成RTS拉高操作,需删除。 - 放弃Sleep依赖:Sleep的精度无法满足5ms以内的要求,必须依赖串口自身的事件通知同步RTS切换时机。
修改后的完整代码
#include <iostream> #include <windows.h> #define hard_pause while(1){} using namespace std; HANDLE hSerial; void port_set() { LPCTSTR sPortName = "\\\\.\\COM4"; hSerial = ::CreateFile(sPortName, GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); if (hSerial == INVALID_HANDLE_VALUE) { cout << "Failed to open serial port\n"; return; } DCB dcbSerialParams = { 0 }; dcbSerialParams.DCBlength = sizeof(dcbSerialParams); if(!GetCommState(hSerial, &dcbSerialParams)) { cout << "getting state error\n"; } dcbSerialParams.BaudRate = CBR_9600; dcbSerialParams.ByteSize = 8; dcbSerialParams.StopBits = ONESTOPBIT; dcbSerialParams.Parity = NOPARITY; dcbSerialParams.fRtsControl = RTS_CONTROL_DISABLE; // 手动控制RTS if(!SetCommState(hSerial, &dcbSerialParams)) { cout << "error setting serial port state\n"; } // 设置发送缓冲区为1字节,减少延迟 if (!SetupComm(hSerial, 1024, 1)) { cout << "SetupComm failed\n"; } // 设置监听EV_TXEMPTY事件(发送缓冲区为空) if (!SetCommMask(hSerial, EV_TXEMPTY)) { cout << "SetCommMask failed\n"; } } void com_write() { // 拉高RTS EscapeCommFunction(hSerial, SETRTS); char data[1] = {0x81}; DWORD dwSize = sizeof(data); DWORD dwBytesWritten; BOOL iRet = WriteFile(hSerial, data, dwSize, &dwBytesWritten, NULL); if(!iRet) { cout << "WriteFile failed\n"; EscapeCommFunction(hSerial, CLRRTS); return; } // 等待发送缓冲区为空,确保数据已完全发送到硬件 DWORD dwEventMask; if (WaitCommEvent(hSerial, &dwEventMask, NULL)) { if (dwEventMask & EV_TXEMPTY) { // 立即拉低RTS EscapeCommFunction(hSerial, CLRRTS); } } else { cout << "WaitCommEvent failed\n"; EscapeCommFunction(hSerial, CLRRTS); } } void com_read() { DWORD iSize; char sReceivedChar; // 单次读取后返回,避免阻塞主循环 if (ReadFile(hSerial, &sReceivedChar, 1, &iSize, NULL) && iSize > 0) { cout << sReceivedChar; } } int main() { port_set(); if (hSerial == INVALID_HANDLE_VALUE) { hard_pause; return 1; } while(true) { com_write(); com_read(); Sleep(1000); } hard_pause; CloseHandle(hSerial); return 0; }
关键说明
WaitCommEvent会阻塞直到EV_TXEMPTY事件触发,此时数据已完全发送到硬件,拉低RTS不会截断数据。SetupComm将发送缓冲区设为1字节,让WriteFile写入的数据直接进入硬件发送队列,消除缓冲区带来的额外延迟。- 原com_read函数的死循环会阻塞主流程,修改为单次读取后返回,保证循环正常执行。
内容的提问来源于stack exchange,提问作者FiveSeven
相关产品推荐
相关产品推荐

