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

C++串口读取函数致程序停顿、数据堆积的原因及解决方法

串口读取重复输出+程序冻结问题排查与修复

问题描述

我用Arduino持续发送字符串<123456789ABC>,但独立的C++串口读取程序会一次性批量输出数十次该字符串,之后冻结wait_time时长,循环往复。

读取串口数据的函数

std::string SerialPort::readSerialPort(int wait_time) {

    DWORD bytes_read;
    char inc_msg[1];
    std::string complete_inc_msg;
    bool began = false;

    // Timer
    unsigned long start_time = time(nullptr);

    ClearCommError(this->handler, &this->errors, &this->status);

    // after wait_time elapsed
    while ((time(nullptr) - start_time) < wait_time) {

        if (this->status.cbInQue > 0){

            if (ReadFile(this->handler, inc_msg, 1, &bytes_read, NULL)) {

                // < and > delimiters

                if (inc_msg[0] == '<' || began) {
                    began = true;

                    if (inc_msg[0] == '>') {
                        return complete_inc_msg;
                    }

                    if (inc_msg[0] != '<')
                        complete_inc_msg.append(inc_msg, 1);
                }
            }
            else
                return "Warning: Failed to receive data.\n";
        }
    }
    return complete_inc_msg;
}

主程序调用方式

std::string readResult = gyroport->readSerialPort(1);
cout << readResult << "\n";

gyroport是包含所有串口通信函数(init、read、write等)的串口类对象。

控制台输出

123456789ABC
123456789ABC
123456789ABC
...(重复数十次)

123456789ABC
123456789ABC
...(重复数十次)

每段批量输出间隔期间,程序会冻结wait_time时长,之后瞬间输出一批消息,再次冻结。

问题原因

  1. 串口状态未实时更新:ClearCommError仅在函数开头执行一次,后续循环中status.cbInQue的值不会刷新,导致串口缓冲区新到的数据无法被检测,直到下次调用函数才会重新查询。
  2. 阻塞式循环等待:函数内的while循环会持续占用CPU,直到wait_time超时,期间程序完全冻结,无法处理其他逻辑。
  3. 重复读取历史数据:每次调用readSerialPort时,只要缓冲区有数据(包括上次未处理完的),就会从头解析,导致同一个字符串被反复读取输出。

修复方案

修改思路

  • 实时更新串口缓冲区状态:每次循环都调用ClearCommError刷新cbInQue的值。
  • 避免阻塞等待:改为非阻塞方式处理当前缓冲区已有数据,解析完完整帧后立即返回,不等到超时。
  • 保持帧解析状态:跨函数调用跟踪帧的起始/结束,避免重复解析同一帧数据。

修改后的读取函数

std::string SerialPort::readSerialPort() {
    DWORD bytes_read;
    char inc_msg[1];
    std::string complete_inc_msg;
    static bool began = false; // 用static保持状态,跨调用持续跟踪帧起始

    // 每次循环都刷新串口状态
    ClearCommError(this->handler, &this->errors, &this->status);

    // 只处理当前缓冲区已有数据,不阻塞等待
    while (this->status.cbInQue > 0) {
        if (ReadFile(this->handler, inc_msg, 1, &bytes_read, NULL)) {
            if (inc_msg[0] == '<' || began) {
                began = true;

                if (inc_msg[0] == '>') {
                    began = false; // 重置状态,准备下一帧
                    return complete_inc_msg;
                }

                if (inc_msg[0] != '<') {
                    complete_inc_msg.append(inc_msg, 1);
                }
            }
        } else {
            began = false; // 出错时重置状态
            return "Warning: Failed to receive data.\n";
        }
        // 读完一个字节后再次刷新状态
        ClearCommError(this->handler, &this->errors, &this->status);
    }

    // 没有完整帧时返回空字符串,避免输出空内容
    return "";
}

主程序调用修改

改成非阻塞循环检查,避免程序冻结:

while (true) {
    std::string readResult = gyroport->readSerialPort();
    if (!readResult.empty()) {
        std::cout << readResult << "\n";
    }
    // 给系统留时间处理其他任务,降低CPU占用
    Sleep(10);
}

额外说明

  • static bool began用于跨函数调用保持帧解析状态,确保能连续跟踪一帧的起始和结束。
  • 去掉固定时长的阻塞等待,改为只处理当前缓冲区已有数据,配合主程序的短间隔循环,实现实时响应。
  • 每次读取字节后刷新串口状态,保证cbInQue是最新的缓冲区数据量。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.21 21:15:46