You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 02:50:22