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 不会被执行.
具体看下面的代码:
#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;
}
类似地, 也可以用于实现写入串口超时功能.
(完)