WinApi与Raspberry Pi Pico串口通信异常问题排查
Raspberry Pi Pico USB串口通信卡在WaitCommEvent的解决方案
问题分析
你的程序卡在WaitCommEvent的核心原因是:Raspberry Pi Pico的USB CDC串口默认需要Windows端断言DTR(数据终端就绪)信号才会启动数据发送。Putty在打开串口时会自动设置DTR信号,触发Pico开始发送数据;关闭Putty后DTR虽然撤销,但Pico会持续发送直到重启,因此你的程序后续能正常读取。而你的初始代码没有处理DTR信号,导致Pico始终未启动数据传输,Windows端自然检测不到EV_RXCHAR事件。
另外,串口缓冲区的残留数据也可能干扰事件检测,需要在初始化时清空。
解决方案
1. 启用DTR/RTS信号
在设置完串口参数后,主动触发DTR和RTS信号,让Pico开始发送数据:
// 设置完SetCommState后添加 if (!EscapeCommFunction(hComm, SETDTR)) { cout << "\nError setting DTR!\n"; } if (!EscapeCommFunction(hComm, SETRTS)) { cout << "\nError setting RTS!\n"; }
也可以直接在DCB参数中配置自动启用:
dcbParams.fDtrControl = DTR_CONTROL_ENABLE; dcbParams.fRtsControl = RTS_CONTROL_ENABLE;
2. 初始化时清空串口缓冲区
打开串口后立即清空输入输出缓冲区,避免残留数据影响事件检测:
// 打开串口成功后添加 if (!PurgeComm(hComm, PURGE_TXCLEAR | PURGE_RXCLEAR)) { cout << "\nError purging comm buffers!\n"; }
3. 优化WaitCommEvent与ReadFile的配合
每次事件触发后,先确认缓冲区状态再读取数据,同时处理可能的错误重试:
for (int i = 0; i <= 99; i+=1) { if (!WaitCommEvent(hComm, &dwEventMask, NULL)) { DWORD err = GetLastError(); cout << "\n Error in WaitCommEvent! Code: " << err << "\n"; // 重置事件掩码,继续等待 SetCommMask(hComm, EV_RXCHAR); continue; } if (dwEventMask & EV_RXCHAR) { COMSTAT cs; DWORD err; ClearCommError(hComm, &err, &cs); if (cs.cbInQue > 0) { ReadFile(hComm, &readBuffer, min(sizeof(readBuffer), cs.cbInQue), &noBytesRead, NULL); cout << " " << string(readBuffer, noBytesRead); // 清空缓冲区,避免残留数据干扰下次读取 memset(readBuffer, 0, sizeof(readBuffer)); } } }
修改后的完整代码片段
hComm = CreateFileA(commPortName, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); if (hComm == INVALID_HANDLE_VALUE) cout << "\nError Port " << commPortName << " can't be opened!!!\n"; else { cout << "\nPort " << commPortName << " is opened!!\n"; // 清空串口缓冲区 if (!PurgeComm(hComm, PURGE_TXCLEAR | PURGE_RXCLEAR)) cout << "\nError purging comm buffers!\n"; } dcbParams.DCBlength = sizeof(dcbParams); if (!GetCommState(hComm, &dcbParams)) cout << "\nError in GetCommState()\n"; dcbParams.BaudRate = CBR_115200; dcbParams.ByteSize = 8; dcbParams.StopBits = ONESTOPBIT; dcbParams.Parity = NOPARITY; // 自动启用DTR/RTS dcbParams.fDtrControl = DTR_CONTROL_ENABLE; dcbParams.fRtsControl = RTS_CONTROL_ENABLE; if (!SetCommState(hComm, &dcbParams)) cout << "\nError in setting Device-Control Block(DCB)\n"; else { cout << "\n\n Setting DCB Structure Successfull" << "\n Baudrate --> " << dcbParams.BaudRate << "\n ByteSize --> " << (unsigned int)dcbParams.ByteSize << "\n StopBits --> " << (unsigned int)dcbParams.StopBits << "\n Parity --> " << (unsigned int)dcbParams.Parity; } // 手动断言DTR/RTS(可选,若DCB配置已生效可省略) if (!EscapeCommFunction(hComm, SETDTR)) cout << "\nError setting DTR!\n"; if (!EscapeCommFunction(hComm, SETRTS)) cout << "\nError setting RTS!\n"; timeouts.ReadIntervalTimeout = 50; timeouts.ReadTotalTimeoutConstant = 50; timeouts.ReadTotalTimeoutMultiplier = 10; if (!SetCommTimeouts(hComm, &timeouts)) cout << "\n\n Error! in Setting Time Outs\n"; else cout << "\n\n Setting Serial Port Timeouts Successfull\n"; if (!SetCommMask(hComm, EV_RXCHAR)) cout << "\n\n Error! in Setting Communication Mask\n"; else cout << "\n\n Setting Communication Mask successfull\n"; cout << "\n\n Waiting for Data Reception....\n"; for (int i = 0; i <= 99; i+=1) { if (!WaitCommEvent(hComm, &dwEventMask, NULL)) { DWORD err = GetLastError(); cout << "\n Error in WaitCommEvent! Code: " << err << "\n"; SetCommMask(hComm, EV_RXCHAR); continue; } if (dwEventMask & EV_RXCHAR) { COMSTAT cs; DWORD err; ClearCommError(hComm, &err, &cs); if (cs.cbInQue > 0) { ReadFile(hComm, &readBuffer, min(sizeof(readBuffer), cs.cbInQue), &noBytesRead, NULL); cout << " " << string(readBuffer, noBytesRead); memset(readBuffer, 0, sizeof(readBuffer)); } } } CloseHandle(hComm);
额外说明
- Pico的USB CDC驱动设计:部分Pico串口程序会等待DTR信号才启动数据发送,这是为了兼容传统串口设备的握手逻辑。
- Putty的行为:Putty默认会在打开串口时设置DTR/RTS,关闭时撤销,但Pico一旦开始发送就会持续输出直到重启。
内容的提问来源于stack exchange,提问作者Luzian
相关产品推荐
相关产品推荐

