以同步方式将全双工串行端口与 ASIO(或其他库)一起使用

Using Full-Duplex Serial Port with ASIO (or other libraries) in a Synchronous Manner

提问人:Rick 提问时间:9/20/2023 更新时间:9/20/2023 访问量:58

问:

我正在处理一个项目,我需要通过串行端口与设备进行通信。我一直在考虑使用 ASIO 库来处理通信,但我有几个问题:

  1. 串行端口本质上是全双工的吗?我可以同时读取和写入端口而不会出现任何问题吗?
  2. 如果我使用 ASIO,是否可以在不使用互斥锁的情况下从两个不同的线程对同一个 asio::serial_port 对象执行同步读取和写入?具体来说,我想要一个线程专门用于阅读,另一个线程专门用于写作。需要注意的是,只有一个线程会写入,一个线程会读取,因此不可能有多个读取或写入,但同时读取和写入应该是可能的。
  3. 如果 ASIO 不是最佳选择,您是否推荐用于 C++ 中的同步串行端口通信的其他库?

现在,我使用互斥锁保护 asio::serial_port,因此只能进行一次读取或写入。 我正在以同步方式使用 ASIO。

C++ 串口 boost-asio asio 全双工

评论

0赞 user4581301 9/20/2023
1.取决于串口的类型。例如,RS485 串行不是全双工的。
0赞 Jonathan 9/20/2023
@user4581301 全双工操作 RS-485 和 RS-422 一样,可以使用四根线制成全双工。[8] 然而,由于 RS-485 是一种多点规范,因此在许多情况下,这并不是必要或不可取的。RS-485 和 RS-422 可以互操作,但有一定的限制
1赞 user4581301 9/20/2023
@Jonathan 也许我是老派,但是当您拥有 4 根电线时,您不再拥有 RS485,还不如使用 RS422。我发现最好坚持最低的公分母,而不是让那些只有 2 线接口的客户感到惊讶和不安。
0赞 Jonathan 9/20/2023
@user4581301直接来自您链接的 wiki。我从未使用过那个特定的标准。
0赞 user4581301 9/20/2023
是的。我不同意。坚持使用 2 线操作或设计并销售具有 422 接口的设备。

答:

0赞 Jonathan 9/20/2023 #1

Asio 已经线程化了。它使用线程来防止阻塞,这是库的目的(异步输入输出)。RS232 在设计上是全双工的。该协议在端口上同时处理发送和接收。

评论

2赞 sehe 9/20/2023
Asio 不是线程的。显然,它允许线程。但是,使用异步比在同一端口对象上正确协调不同的线程要容易得多。
0赞 Jonathan 9/20/2023
@sehe 感谢您的澄清。无论如何,我从未使用过没有设置后台线程来处理读/写操作的 ASIO,所以这就是我感到困惑的地方。
0赞 sehe 9/20/2023
在答案中添加了单线程异步全双工串行 IO 的示例
0赞 sehe 9/20/2023 #2

是的,您可以通过同步对 IO 对象的多线程访问来以复杂的方式做到这一点:

(todo想出复杂的例子)

更简单的单线程异步

#include <boost/asio.hpp>
#include <boost/asio/serial_port.hpp>
#include <deque>
#include <iomanip>
#include <iostream>

namespace asio = boost::asio;
using sp       = asio::serial_port;
using boost::system::error_code;
using namespace std::literals;

struct Example {
    Example(asio::any_io_executor ex, std::string dev) : sp_(ex, dev) {
        readLoop();
    }

    void Send(std::string cmd) {
        std::cerr << "Queue: " << quoted(cmd) << std::endl;
        outbox_.push_back(std::move(cmd));
        if (outbox_.size() == 1)
            writeLoop();
    }

    void Stop() {
        sp_.cancel();
    }

  private:
    void readLoop() {
        asio::async_read_until( //
            sp_, asio::dynamic_buffer(incoming_), "\n", [this](error_code ec, size_t len) {
                std::cerr << "Read: " << len << " (" << ec.message() << ")" << std::endl;
                if (!ec.failed()) {
                    Send("echo: "s + incoming_.substr(0, len));
                    incoming_.erase(0, len);
                    readLoop();
                }
            });
    }
    void writeLoop() {
        if (outbox_.empty())
            return;
        asio::async_write(sp_, asio::buffer(outbox_.front()), [this](error_code ec, size_t len) {
            std::cerr << "Wrote: " << len << " (" << ec.message() << ")" << std::endl;
            if (!ec.failed()) {
                outbox_.pop_front();
                writeLoop();
            }
        });
    }

    asio::serial_port       sp_;
    std::string             incoming_;
    std::deque<std::string> outbox_;
};

int main(int argc, char** argv) {
    asio::io_context ioc;
    Example          drone(ioc.get_executor(), (argc > 1 ? argv[1] : "/dev/stty0"));

    ioc.run_for(30s);

    // not really needed, but to show graceful shutdown
    drone.Stop();
    ioc.run();
}

现场演示:

enter image description here

评论

1赞 sehe 9/20/2023
老实说,由于 asio::serial_port 的线程安全性,我认为我无法使多线程方法工作:这意味着两个线程都在锁下阻塞。(如果你想看看我写了什么 - 这是损坏的代码:coliru.stacked-crooked.com/a/9b5104a6f55e6dd2)