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

串口重叠操作返回错误码87及Arduino通信异常问题排查

Arduino Nano串口重叠I/O通信问题解决

问题概述

  • 测试场景:Arduino Nano每秒通过串口发送字符p,Windows端使用**重叠I/O(overlapped I/O)**模式,通过WaitCommEvent监听串口事件
  • 遇到的问题:
    1. 移除WaitCommEvent前的Sleep调用后,无法接收并显示Arduino发送的字符
    2. SetCommMask必须放在循环内,否则WaitCommEvent返回错误码87(ERROR_INVALID_PARAMETER)

用户原代码

BOOL   Status; // Status

// Defining COM port id
const wchar_t PortNo[20] = L"\\\\.\\COM4";
//Open the serial com port
HANDLE hComm;  // Handle to the Serial port
printf("Opening %ls\n", PortNo);
hComm = CreateFile(PortNo, //friendly name
    GENERIC_READ | GENERIC_WRITE,      // Read/Write Access
    0,                                 // No Sharing, ports cant be shared
    NULL,                              // No Security
    OPEN_EXISTING,                     // Open existing port only
    FILE_FLAG_OVERLAPPED,              // Overlapped I/O
    NULL);                             // Null for Comm Devices
if (hComm == INVALID_HANDLE_VALUE)
{
    printf_s("\n Port %ls can't be opened\n\n", PortNo);
}
//Setting the Parameters for the SerialPort
DCB dcbSerialParams = { 0 };  // Initializing DCB structure
dcbSerialParams.DCBlength = sizeof(dcbSerialParams);
Status = GetCommState(hComm, &dcbSerialParams); //retreives  the current settings

dcbSerialParams.BaudRate = CBR_9600;      //BaudRate = 9600
dcbSerialParams.ByteSize = 8;             //ByteSize = 8
dcbSerialParams.StopBits = ONESTOPBIT;    //StopBits = 1
dcbSerialParams.Parity = NOPARITY;      //Parity = None
Status = SetCommState(hComm, &dcbSerialParams);
EXIT_ON_ERROR(Status, __LINE__);

//Setting Timeouts
COMMTIMEOUTS timeouts = { 0 };  //Initializing timeouts structure
timeouts.ReadIntervalTimeout = 50;
timeouts.ReadTotalTimeoutConstant = 50;
timeouts.ReadTotalTimeoutMultiplier = 10;
timeouts.WriteTotalTimeoutConstant = 50;
timeouts.WriteTotalTimeoutMultiplier = 10;
if (SetCommTimeouts(hComm, &timeouts) == FALSE)
{
    printf_s("\nError to Setting Time outs");
    CloseHandle(hComm);
}

DWORD BytesWritten = 0;          // No of bytes written to the port

DWORD dwEventMask;     // Event mask to trigger
DWORD NoBytesRead;     // Bytes read by ReadFile()
int index = 0;
OVERLAPPED o = { 0 };
o.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (o.hEvent == NULL)
    return FALSE; // Error creating overlapped event handle.
assert(o.hEvent);
DWORD dwRes;
while (1) {
      Status = SetCommMask(hComm, EV_RXCHAR); 
    if (Status == FALSE)
    {
        printf_s("\nError to in Setting CommMask\n\n");
        CloseHandle(hComm);
    }
    //Setting WaitComm() Event
    char  ReadData[100] = { 0 };        //temporary Character

    Status = WaitCommEvent(hComm, &dwEventMask, &o); //Wait for the character to be received
    if (GetLastError() != ERROR_IO_PENDING) {
        printf("IO_NOT_PENDING %d\n", GetLastError());
    }
    DWORD lpModemStat;
    dwRes = WaitForSingleObject(o.hEvent, INFINITE);
    switch (dwRes) {
    case WAIT_OBJECT_0:

        if (dwEventMask & EV_RXCHAR) {
            Status = ReadFile(hComm, &ReadData, sizeof(ReadData), &NoBytesRead, &o);
            ResetEvent(o.hEvent);
            for (int i = 0; i < 100; i++) {
                std::cout << ReadData[i];
            }
        }
        break;
    case WAIT_TIMEOUT:
        break;
    default:
        break;
    }
}

问题原因分析

1. SetCommMask必须放循环内的原因

Windows串口API中,WaitCommEvent在完成异步操作后会自动清除当前设置的通信掩码。如果不在每次调用WaitCommEvent前重新调用SetCommMask设置EV_RXCHAR,后续的WaitCommEvent会因为掩码无效而返回错误码87(参数不正确),这是API的既定行为。

2. 移除Sleep后无法接收字符的原因

  • 重叠操作的结果未正确获取:原代码中,WaitCommEvent返回ERROR_IO_PENDING后,直接通过WaitForSingleObject等待事件,但未调用GetOverlappedResult获取异步操作的最终结果,导致dwEventMask的值可能未被正确更新,后续判断EV_RXCHAR事件可能失效。
  • OVERLAPPED结构体复用冲突:原代码中WaitCommEvent和ReadFile复用同一个OVERLAPPED结构体,事件对象的状态会被两个异步操作干扰,导致信号触发逻辑混乱。
  • 读取结果处理错误:原代码固定输出100个字符,但实际每次只收到1个字符,多余的空字符输出会掩盖有效数据,且未正确判断ReadFile的异步操作结果。

修正后的代码

#include <windows.h>
#include <stdio.h>
#include <iostream>
#include <assert.h>

#define EXIT_ON_ERROR(expr, line) if(!(expr)) { printf("Error at line %d\n", line); CloseHandle(hComm); return FALSE; }

int main() {
    BOOL Status;

    const wchar_t PortNo[20] = L"\\\\.\\COM4";
    HANDLE hComm = CreateFile(PortNo,
        GENERIC_READ | GENERIC_WRITE,
        0,
        NULL,
        OPEN_EXISTING,
        FILE_FLAG_OVERLAPPED,
        NULL);

    if (hComm == INVALID_HANDLE_VALUE) {
        printf_s("\n Port %ls can't be opened\n\n", PortNo);
        return FALSE;
    }

    // 配置串口参数
    DCB dcbSerialParams = { 0 };
    dcbSerialParams.DCBlength = sizeof(dcbSerialParams);
    Status = GetCommState(hComm, &dcbSerialParams);
    EXIT_ON_ERROR(Status, __LINE__);

    dcbSerialParams.BaudRate = CBR_9600;
    dcbSerialParams.ByteSize = 8;
    dcbSerialParams.StopBits = ONESTOPBIT;
    dcbSerialParams.Parity = NOPARITY;
    Status = SetCommState(hComm, &dcbSerialParams);
    EXIT_ON_ERROR(Status, __LINE__);

    // 配置超时
    COMMTIMEOUTS timeouts = { 0 };
    timeouts.ReadIntervalTimeout = 50;
    timeouts.ReadTotalTimeoutConstant = 50;
    timeouts.ReadTotalTimeoutMultiplier = 10;
    timeouts.WriteTotalTimeoutConstant = 50;
    timeouts.WriteTotalTimeoutMultiplier = 10;
    if (!SetCommTimeouts(hComm, &timeouts)) {
        printf_s("\nError Setting Timeouts\n");
        CloseHandle(hComm);
        return FALSE;
    }

    // 为WaitCommEvent和ReadFile分别创建OVERLAPPED结构体,避免冲突
    OVERLAPPED ovWait = { 0 };
    ovWait.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
    EXIT_ON_ERROR(ovWait.hEvent != NULL, __LINE__);

    OVERLAPPED ovRead = { 0 };
    ovRead.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
    EXIT_ON_ERROR(ovRead.hEvent != NULL, __LINE__);

    DWORD dwEventMask;
    DWORD NoBytesRead;
    char ReadData[100];

    while (1) {
        // 每次WaitCommEvent前必须重新设置掩码
        Status = SetCommMask(hComm, EV_RXCHAR);
        if (!Status) {
            printf_s("\nError Setting CommMask\n");
            break;
        }

        // 异步等待接收事件
        Status = WaitCommEvent(hComm, &dwEventMask, &ovWait);
        if (!Status) {
            if (GetLastError() != ERROR_IO_PENDING) {
                printf("WaitCommEvent Error: %d\n", GetLastError());
                break;
            }
        }

        // 等待事件触发
        DWORD dwRes = WaitForSingleObject(ovWait.hEvent, INFINITE);
        if (dwRes != WAIT_OBJECT_0) {
            printf("WaitForSingleObject Error: %d\n", GetLastError());
            break;
        }

        // 获取异步操作的最终结果,更新dwEventMask
        if (!GetOverlappedResult(hComm, &ovWait, NULL, FALSE)) {
            printf("GetOverlappedResult (WaitCommEvent) Error: %d\n", GetLastError());
            break;
        }
        ResetEvent(ovWait.hEvent);

        // 处理接收事件
        if (dwEventMask & EV_RXCHAR) {
            memset(ReadData, 0, sizeof(ReadData));
            // 异步读取串口数据
            Status = ReadFile(hComm, ReadData, sizeof(ReadData)-1, &NoBytesRead, &ovRead);
            if (!Status) {
                if (GetLastError() == ERROR_IO_PENDING) {
                    WaitForSingleObject(ovRead.hEvent, INFINITE);
                    if (!GetOverlappedResult(hComm, &ovRead, &NoBytesRead, FALSE)) {
                        printf("GetOverlappedResult (ReadFile) Error: %d\n", GetLastError());
                        continue;
                    }
                } else {
                    printf("ReadFile Error: %d\n", GetLastError());
                    continue;
                }
            }
            ResetEvent(ovRead.hEvent);

            // 只输出实际读取到的字节数
            if (NoBytesRead > 0) {
                std::cout << "Received: ";
                for (DWORD i = 0; i < NoBytesRead; i++) {
                    std::cout << ReadData[i];
                }
                std::cout << std::endl;
            }
        }
    }

    // 清理资源
    CloseHandle(ovWait.hEvent);
    CloseHandle(ovRead.hEvent);
    CloseHandle(hComm);
    return TRUE;
}

关键修改点

  1. 分离OVERLAPPED结构体:为WaitCommEvent和ReadFile分别创建独立的OVERLAPPED对象,避免事件信号互相干扰。
  2. 正确获取异步结果:在WaitForSingleObject后调用GetOverlappedResult,确保dwEventMask和读取字节数的正确性。
  3. 按需输出数据:根据NoBytesRead的值输出实际收到的字符,避免空字符干扰。
  4. 规范资源清理:在程序退出时关闭所有创建的事件句柄和串口句柄。

内容的提问来源于stack exchange,提问作者l_KarmaKat

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 19:57:00