c++ - 串口异步IO

标签 c++ multithreading asynchronous serial-port

在半双工串行端口上设计完全异步 IO 时,我需要您的建议。目前我有一个读者线程和许多由信号量和互斥锁控制的作者线程。现在我想通过消除线程来简化同步。主要问题是串行端口 IO 有一个奇怪的行为。

我只需要确保 readwrite 系统调用只阻塞调用线程,直到 IO 操作实际完成。我假设 read 默认是一个阻塞系统调用。尽管我得到 -1 作为 read 的返回值。有一个奇怪的 EBUSY 错误,我没有对此进行描述。当前代码:

bool SerialManager::initialize(const PortType& portType, const size_t& number)
{
// Open Serial port (/dev/ttyS2 in this case)
fd = open(portName.str().c_str(), O_RDWR ); //O_NOCTTY
if (fd < 0) // if open is not successful
{
    cerr << ERROR << "Unable to open `" << portName << "'." << endl;
    return false;
}
else
{
    cout << INFO << "Port " << portName.str() << " successfully opened."
            << endl;
    cout << INFO << "Configuring port..." << endl;
    fcntl(fd, F_SETFL,~O_NONBLOCK);
    struct termios port_settings; // structure to store the port settings in
    cfsetispeed(&port_settings, B38400); // set baud rate
    cfsetospeed(&port_settings, B38400); // set baud rate
    port_settings.c_cflag |= CLOCAL | CREAD;
    port_settings.c_cflag &= ~CRTSCTS; // disable H/W flow control
    port_settings.c_lflag &= ~( ISIG | // disable SIGxxxx signals
            IEXTEN | // disable extended functions
            ECHO | ECHOE); // disable all auto-echo functions
    port_settings.c_lflag &= ~ICANON ; // raw mode
    port_settings.c_oflag &= ~OPOST; // raw output
    port_settings.c_iflag &= ~(IXON | IXOFF | IXANY); // disable S/W flow control;
    port_settings.c_cc[VTIME] = 20; // wait 0.1 second to get data
    port_settings.c_cc[VMIN] = 0;

    port_settings.c_cflag = (port_settings.c_cflag &= ~CSIZE) | CS8; // set data byte size
    port_settings.c_cflag &= ~CSTOPB; // set stop bit 1
    port_settings.c_cflag &= ~PARENB; // set no parity
    port_settings.c_iflag |= IGNPAR; // ignore parity
    port_settings.c_iflag &= ~(INPCK | ISTRIP | PARMRK);

    // Set
    if (tcsetattr(fd, TCSANOW, &port_settings) < 0)
    {
        cerr << ERROR << "Unable to configure serial port." << endl;
        return false;
    }
    else
    {
        cout << INFO << "Port `" << portName.str()
                << "' configuration was successful." << endl;
            return true;
    }
}
}

写入数据:

int SerialManager::asyncWriteData(const byte* data, const size_t& size)
{
    int writeSize = write(fd, data, size);
    return writeSize;
}

阅读:

void SerialManager::asyncRead(byte* buffer, const size_t& size, bool& ok)
{
    byte temp[256];
    ssize_t packetSize = read(fd, temp, 256);
    if (packetSize > 0)
    {
        for (size_t i = 0; i < size; ++i)
            buffer[i] = temp[i];
        ok = true;
    }
    cout << errno << endl;
    perror("Error occured: "); // <=== Here I'm getting EBUSY (code 16)
    ok = false;
}

在外部使用 SerialManager 类:

....
word checksum = this->id + 0x2C;
checksum = ~checksum;
// Send read command
byte command[] =
{ 0xff, // heading
        0xff, // ~
        this->id, // id of actuator
        0x04, // length
        0x02, // instruction: read
        0x24, // start address: present position
        0x02, // data length
        static_cast<byte>(checksum) //checksum
        };
SerialManager::lockPort(); // lock a mutex to avoid collitions
int numbytes = SerialManager::asyncWriteData(command, 8);
if (numbytes < 0)
{
    cerr << ERROR << "Could not write to serial port." << endl;
    return 0;
}
cout << INFO << numbytes << " bytes has been written." << endl;
for (size_t i = 0; i < 8; ++i)
{
    cout << hex << setfill('0') << setw(2) << (int) command[i] << ' ';
}
cout << endl;

byte* data = new byte[8];
bool ok;
// Here I need to make sure data write is completed before start reading
SerialManager::asyncRead(data, 8, ok);
if (ok)
{
    word position = data[5] + (static_cast<word>(data[6]) << 8);
    return position;
}
else
{
    cerr << ERROR << "Unable to read data from serial port..." << endl;
    return -1;
}
SerialManager::unlockPort(); // Unlock previously locked mutex
....

更新:

我删除了毫无意义的读者线程。因为我们有一条无法控制传输的半双工线路。同步IO有两个问题:

  1. 从 Controller 向执行器发送一个非常长的数据,当数据在端口上时,第一个执行器无响应地响应: First

  2. 执行器可能会在另一个执行器的数据未完全传输时做出响应 Second

EBUSY 的问题也通过在write 之后添加fsync 来解决。这就是我所需要的。 (一个阻塞的write):

int SerialManager::asyncWriteData(const byte* data, const size_t& size)
{
    ssize_t packetSize = write(fd, data, size);
    if (packetSize > 0)
    {
        fsync(fd);
    }
    return packetSize;
} 

来自 man fsync:

fsync() transfers ("flushes") all modified in-core data of (i.e., modified buffer cache pages for) the file referred to by the file descriptor fd to the disk device (or other permanent storage device) where that file resides. The call blocks until the device reports that the transfer has completed. It also flushes metadata information associated with the file

最佳答案

我怀疑这是否能回答您的问题,但我认为它会有所帮助。

在void SerialManager::asyncRead(byte*, const size_t&, bool&)中,有两个错误:

  1. 当 read 返回成功时,您正在使用 errno 的值(并使用 perror 根据它打印消息),而不仅仅是在它出错时(返回值 < 0)。成功时,未指定 errno 具有任何特定值。所以你在有效地使用垃圾数据;您得到的 EBUSY 可能没有任何意义。 errno 仅在发生特定错误时才定义为具有特定值。请参阅 Linux 系统上的“man errno”,它在我的 Debian 系统上的相关部分中说:“只有当调用的返回值指示错误时,它的值才有意义(即,-1 来自大多数系统调用;-1或来自大多数库函数的 NULL);允许成功的函数更改 errno。”。请注意“允许”,而不是“必须”。

  2. 您读入一个临时缓冲区,然后将其逐字节复制到调用者传递的目标缓冲区中。这是对 CPU 周期和内存带宽的毫无意义的浪费。您可以直接读入调用者的缓冲区。即使您必须(出于某种奇怪的原因)读入临时缓冲区然后将其复制到其他地方,您也应该使用 memcpy(3) 或 memmove(3) 将其复制到其他地方,而不是使用您自己的逐字节循环。

关于设计,我不明白在只有一个串口的情况下有多个读写器有什么意义。你想实现并行吗?我不明白你怎么能,只有一个串口。 如果你想要异步而不是并行性,你应该调查 fcntl(fd, F_SETFL, existing_flags & O_ASYNC) (尽管信号相当陈旧和丑陋),或者可能只是将 fd 设置为非阻塞,然后在内部事件中调用 select()应用程序循环并处理任何可用的 I/O(如果有)。

希望这对您有所帮助!

关于c++ - 串口异步IO,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13921870/

相关文章:

java - JNA native 函数调用和具有 Double**-Pointer/Array 内存分配的结构

c++ - 为什么 C++ 不允许重新绑定(bind)引用?

Java 多线程输出在线程安全的 System.out 上看似模棱两可

java - 计时器和线程问题

asynchronous - 在异步Rust中,如何实现围绕民意调查的包装?

c - aio_write() 的简单示例

c++ - 带有 C++ 主文件的 FreeRTOS

c++ - 如何在 C++ 生成器中隐藏任务栏中的表单?

java - 如何决定使用newCachedThreadPool还是newFixedThreadPool?

ios - Swift - 专门的 _VariantDictionaryBuffer.ensureUniqueNativeBuffer(Int)