siar

Snowy Institute

Linux上でのUSBシリアル通信の高速化 (C++)

Linuxのライブラリをわちゃわちゃして, C++でのUSBシリアル通信を高速化するお話です.

注意書き

実験的な部分が多いです.

他の手法やお気づきの点等ございましたら, ご一報くださると助かります.

環境

下記の動作環境で確認しています.

手法

1. ポートの常時解放 (送信)

当然ですが, 通信毎にポートを開閉していたら遅くなるので, 常時解放します.

例えば, コンストラクタでopen(), デストラクタでclose()を呼び出すように実装します.

2. ASYNC_LOW_LATENCY (受信)

デフォルトでは処理効率向上のため, カーネルが受信する際に待ち時間が設けられます.

この機能は下記のコードで無効化, つまり待ち時間を小さく設定できます.

これの実装は

#include <linux/serial.h>
#include <sys/ioctl.h>
struct serial_struct serial_setting;
ioctl(fd, TIOCGSERIAL, &serial_setting);
serial_setting.flags |= ASYNC_LOW_LATENCY;
ioctl(fd, TIOCSSERIAL, &serial_setting);

// fd: file descriptor

とすれば適用されます.

3. read関数による待機の阻止 (受信)

プログラムがカーネルバッファからデータを受け取るとき, 要求データ量に対して受信可能データ量が小さいとき, 待機が発生します.

実測では, 100ms程度の遅延が発生しました.

待機+タイムアウトで実装されているような気がするのですが, 確認しきれていません.

対策としては, ioctl()で受信可能なデータ量を先に知っておくという方法があります.

例えば, 10バイトのデータが欲しいとき, 普通に書けば次のようになります.

#include <unistd.h>
int req_size = 10;
read_size = read(fd, buf_ptr, req_size);

// fd: file descriptor
// buf_ptr: buffer pointer
// req_size: size of data this program requests (bytes)
// read_size: size of data this program read actually (bytes)

これでは, 受信可能なデータ (カーネルが所持しているデータ) が10バイト未満のとき, 待機が発生します.

次の実装でこの待機を起こさないようにできます.

#include <unistd.h>
#include <sys/ioctl.h>
int req_size = 10;
int available_size = 0;
ioctl(fd, FIONREAD, &available_size);

int read_size;
if(req_size > available_size) {
    read_size = read(fd, buf_ptr, available_size);
} else {
    read_size = read(fd, buf_ptr, req_size);
}

ここでは, ioctl(fd, FIONREAD, &available_size);によって, 受信可能なデータ量がavailable_sizeに格納されるようになっています.

遅延を避けるためにその時点で受信可能なものしか受け取っていないので, このあとリングバッファに蓄える等の作業が必要になるかもしれません.

参考

c++ - Low latency serial communication on Linux - Stack Overflow

Man page of SETSERIAL

IBM Knowledge Center : ioctl() - 装置の制御

IBM Knowledge Center : read() - ファイルまたはソケットからの読み取り