Boost ASIO System Timer伪超时问题:串口接收误判为多段传输
串口接收超时判定异常问题排查与解决
当前方案
开发串口程序,规则为:若25毫秒内无新数据接收,则判定当前传输接收完成。首次触发read_handler(Boost ASIO回调方法)时启动定时器,每次新的read_handler调用,都会取消定时器的等待异步操作并重新创建新的定时器异步任务。
问题描述
出现随机异常:单次传输的接收数据被判定为2-3次独立传输,根源是receive_timeout事件(receive_timeout_handler)被多次触发。无法确定问题源于Boost ASIO system_timer的实现/使用错误,还是USB转串口转换器的驱动问题。
测试环境使用FT4232模块(内置4个UART/串口):从UART1发送4KB文本文件,UART0接收数据。预期流程为接收完所有4KB数据后,串口类向主线程发送信号,但实际中有时单次4KB传输会触发2-3次信号。
代码实现
class SerialPort { public: SerialPort() : io(), port(io), receive_timeout_timer(io) {} bool open_port(void); bool read_async(std::int32_t read_timeout = -1); void read_handler(const boost::system::error_code& error, std::size_t bytes_transferred); void receive_timeout_handler(const boost::system::error_code& error); private: boost::asio::io_context io; boost::asio::serial_port port; boost::asio::system_timer receive_timeout_timer {io}; std::array<std::byte, 8096> read_byte_buffer; std::string received_data; std::uint32_t read_timeout; static constexpr std::uint32_t bulk_data_receive_complete = 25; }; bool SerialPort::open_port(void) { try { this->port.open("COM3"); return true; } catch (const std::exception& ex) { return false; } return false; } bool SerialPort::read_async(std::uint32_t read_timeout) { try { this->read_byte_buffer.fill(static_cast<std::byte>(0)); if (read_timeout != -1) { this->read_timeout = read_timeout; } this->port.async_read_some( boost::asio::buffer( this->read_byte_buffer.data(), this->read_byte_buffer.size() ), boost::bind( &SerialPort::read_handler, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred ) ); return true; } catch (const std::exception& ex) { return false; } } void SerialPort::read_handler(const boost::system::error_code& error, std::size_t bytes_transferred) { std::string temporary_recieve_data; try { if (error != boost::system::errc::success) { this->receive_timeout_timer.cancel(); return; } std::transform(this->read_byte_buffer.begin(), this->read_byte_buffer.begin() + bytes_transferred, std::back_inserter(temporary_recieve_data), [](std::byte character) { return static_cast<char>(character); } ); this->received_data += temporary_recieve_data; this->receive_timeout_timer.cancel(); this->receive_timeout_timer.expires_after(boost::asio::chrono::milliseconds(SerialPort::bulk_data_receive_complete)); this->receive_timeout_timer.async_wait(boost::bind(&SerialPort::receive_timeout_handler, this, boost::asio::placeholders::error)); this->read_async(); } catch (const std::exception& ex) { } } void SerialPort::receive_timeout_handler(const boost::system::error_code& error) { try { if (error != boost::system::errc::success) { return; } // this->signal(this->port_number, SerialPortEvents::read_data, this->received_data); this->received_data.clear(); } catch (const std::exception& ex) { } }
协议框图

问题分析与解决建议
- 定时器初始化错误:原代码中
receive_timeout_timer {25}初始化参数错误,system_timer构造需传入io_context作为第一个参数,修正后可避免定时器底层行为异常。 - 时序逻辑问题:原代码先启动新的异步读取再处理定时器,可能导致旧定时器超时事件已进入执行队列。调整为先更新定时器,再启动新读取,确保新数据到来时能及时终止旧定时器。
- 错误处理缺失:读取出错时未取消定时器,可能导致超时事件仍被触发,错误判定传输完成。需在读取错误分支添加定时器取消逻辑。
- 缓冲区未重置:超时触发后未清空
received_data,会导致下次接收数据与残留数据合并,或重复触发信号。需在超时处理完成后重置缓冲区。 - USB转串口设备特性:FT4232的USB批量传输特性可能将数据拆分为多包发送,若包间隔接近25ms会触发多次超时。可微调超时时间(如30ms),或在驱动中增大串口接收缓冲区、启用硬件流控优化传输连续性。
内容的提问来源于stack exchange,提问作者Dark Sorrow
相关产品推荐
相关产品推荐

