C++读取串口超时功能
计算学徒
2024年12月12日 21:07
收录于文集
共106篇

C++库 Boost.Asio 中提供了 serial_port 类用于读写串口操作, 提供的接口包括:

  • read_some: 同步读

  • async_read_some: 异步读

  • write_some: 同步写

  • async_write_some: 异步写

在某些极端情况下, 串口可能不稳定, 无论采用同步读还是异步读可能会一直阻塞读不到数据. 为了不影响用户软件体验, 需要设定一个读超时时间, 当在给定时间段内读不到数据就直接返回读出错.

为了实现读超时功能, 需要使用异步读 async_read_some 接口, 并配合 Boost.Asio 中提供的 deadline_timer 计时器一起来完成超时功能.

实现读超时功能的基本原理为: 

  • 同时开始 serial_port 的 async_read_some 异步读操作和 dealine_timer 计时器的 async_wait 异步等待计时操作;

  • 异步函数 async_read_some 在读到数据之后会调用回调函数 m_cb_completed, 回调函数 m_cb_completed 中会取消掉计时器计时;

  • 异步函数 async_wait 在到达指定超时时间之后会调用回调函数 m_cb_timeout, 回调函数 m_cb_timeout 中会取消 serial_port 串口读操作;

  • 如果在设定的超时时间之前串口读到了数据, 则执行回调函数 m_cb_completed, 计时器被取消, 计时器回调函数 m_cb_timeout 将不会执行;

  • 如果在超时时间之后串口还没有读到数据, 则计时器时间到会执行回调函数 m_cb_timeout, 同时取消串口读操作, 串口读回调函数 m_cb_completed 不会被执行.

具体看下面的代码: 

代码块
C++
自动换行
复制代码
#include <boost/asio/deadline_timer.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/serial_port.hpp>
#include <boost/bind/bind.hpp>
#include <boost/function.hpp>
#include <iostream>
#include <string>

using namespace boost;
using namespace boost::placeholders;

class SerialPortReader {
  /* 串口 */
  asio::serial_port& m_port;

  /* 串口的 io_context */
  asio::io_context& m_io_context;

  /* 用于记录超时的计时器 */
  asio::deadline_timer m_timer;

  /** 读取完成回调函数: @ref _callback_completed */
  boost::function<void(const system::error_code&, size_t)> m_cb_completed;

  /** 读取超时回调函数 @ref _callback_timeout */
  boost::function<void(const system::error_code&)> m_cb_timeout;

  /* 是否读取错误 */
  bool m_read_error;

  void _callback_completed(const system::error_code& error,
                           size_t bytes_transferred) {
    m_read_error = error.failed();
    m_timer.cancel();
  }

  void _callback_timeout(const system::error_code& error) {
    /** 超时计时器是否已经取消 */
    if (error) {
      return;
    }

    m_port.cancel();
  }

 public:
  SerialPortReader(asio::serial_port& port, asio::io_context& io_context)
      : m_port(port),
        m_io_context(io_context),
        m_timer(io_context),
        m_read_error(true),
        m_cb_completed(
            bind(&SerialPortReader::_callback_completed, this, _1, _2)),
        m_cb_timeout(bind(&SerialPortReader::_callback_timeout, this, _1)) {}

  /**
   * @brief 从串口中读取一个字节 (带超时判断逻辑)
   *
   * @param buf 用于保存所读取字节的缓存
   * @param timeout 读取超时时长 (单位: 毫秒). timeout = 0 表示同步读永不超时.
   * @return true 成功读取时, 返回 true
   * @return false 读取出错或者超时时, 返回 false
   */
  bool readChar(char& buf, std::size_t timeout = 0) {
    /* 重置 */
    m_io_context.reset();

    if (timeout > 0) {
      /* 设置异步读取 */
      m_port.async_read_some(asio::buffer(&buf, 1), m_cb_completed);

      /* 设置定时器 */
      m_timer.expires_from_now(posix_time::millisec(timeout));
      m_timer.async_wait(m_cb_timeout);

      /* 阻塞直到超时或者读出一个字符 */
      m_io_context.run();
    } else {
      try {
        m_port.read_some(asio::buffer(&buf, 1));
        m_read_error = false;
      } catch (system::error_code& error) {
        m_read_error = error.failed();
      }
    }

    /* 返回结果 */
    auto result = !m_read_error;
    this->m_read_error = true;
    return result;
  }
};

int main(int, char**) {
  asio::io_context io_context;
  asio::serial_port port(io_context);
  port.open("COM5");

  SerialPortReader reader(port, io_context);
  char c;
  
  if (reader.readChar(c, 5000)) {
    std::cout << "READ: " << c << std::endl;
  } else {
    std::cout << "TIMEOUT" << std::endl;
  }

  return 0;
}
复制成功

类似地, 也可以用于实现写入串口超时功能.

(完)