串口异步IO

Posted

技术标签:

【中文标题】串口异步IO【英文标题】:Asynchronous IO on serial port 【发布时间】:2012-12-17 20:43:03 【问题描述】:

在半双工串行端口上设计完全异步 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有两个问题:

    从控制器向执行器发送很长的数据,当数据在端口上时,第一个执行器没有响应:

    一个执行器可能会响应,而另一个执行器的数据未完全传输

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;
 

来自人 fsync:

fsync() 传输(“刷新”)所有已修改的内核数据(即修改的缓冲区缓存页面)由 文件描述符 fd 到该文件所在的磁盘设备(或其他永久存储设备)。呼叫阻塞,直到设备报告传输已完成。它还刷新与文件关联的元数据信息

【问题讨论】:

@didierc Linux 基于 buildroot 好的,关于 io 命令包:执行器 ID 是什么? 每个执行器都有一个唯一的 ID,其内部微控制器仅检测标有该 ID 的数据包 好的,命令的答案是否包含其执行器 ID?我们可以在端口上编写多个命令并期望多个答案,并使用执行器 id 来区分它们吗? 好吧,毕竟我认为你不需要改变你当前处理 io 的方式。此外,异步 io 不是必需的(因为您一次只执行一个 io,并且 io 有效负载很小)。您应该删除无论如何都不正确的 fcntl 调用,并将打开的 ~O_NONBLOCK 替换为 O_RDWR。您可能需要添加 O_DIRECT 以确保 io 没有被缓冲,这样当您进行读取时,写入已经真正处理完毕。 【参考方案1】:

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

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

    当 read 返回成功时,您正在使用 errno 的值(并使用 perror 打印基于它的消息),而不仅仅是在出错时(返回值

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

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

希望这会有所帮助!

【讨论】:

以上是关于串口异步IO的主要内容,如果未能解决你的问题,请参考以下文章

# 进程/线程/协程 # IO:同步/异步/阻塞/非阻塞 # greenlet gevent # 事件驱动与异步IO # SelectPollEpoll异步IO 以及selectors模块 # (示

异步IO

IO模型--阻塞IO,非阻塞IO,IO多路复用,异步IO

自定义异步IO框架

异步IO原理及相应函数

Python学习---IO的异步[自定义异步IO]