sysfs経由のGPIO制御が低速。高速に制御できる方法を知りたい。

エンゼルプレイングカードの山本と申します。

Armadillo440でGPIOを高速に動作させる方法についての質問です。
知りたい内容は以下の2点です。

・sysfsドライバより高速でGPIOを制御する方法の有無

・あるとすればどのような方法か

(1点目の、高速でGPIOを制御する方法の有無だけでも
 早急にお教えいただけると助かります。)

以下に詳しい状況を記載します。

現在、Armadillo440でGPIOのテストを行っているのですが、
標準のsysfsドライバ(/sys/class/gpio/... に値を書き込む方法)では
必要な速度で通信ができず、困っています。

 ・ディストリビューションはdebian lennyを使用

調べたところ、ファイルへの書き込み速度が律速になっているようなので、
他の方法でGPIOを制御する必要があるのではないかと考えています。

・制御ファイルの値を1回書き換えるのに700マイクロ秒程度かかっている

 ・・C言語で、open, write, close する方法でかかった時間を計測

・GPIOピンの状態変更を数十マイクロ秒以内でできるようにしたい

マニュアルを参照すると、GPIOと同じピンを使用しているUARTでは、
最大4Mbpsでの通信が可能と記載されており、また、FAQサイトでも、
Armadillo300のGPIOで2MbpsのI/Oを確認したとの記載があるため、
Armadillo440でも同程度の速度でGPIOを制御する方法があるはずだ
と思っているのですが、方法がわかりません。

参考: 上述のArmadillo300のGPIO速度を計測したFAQ
http://armadillo.atmark-techno.com/faq/con9-baud-rate

Armadillo200シリーズ互換ドライバの使用、デバイスドライバの作成も
検討していますが、ドライバの開発・ビルドの知識が不足しており、
試せていない状況です。

製品: 
Armadillo-440

毎度お世話様、伊澤@イットーソフトウェアです。

ドライバを作れば余裕で高速制御できます。
私の処でsysfsを使って試した結果でも、1周期に1ms程度掛かっていました。
しかも8bit並列で処理したかった為にドライバを作りましたが、
記憶に間違いがなければ1μsオーダーだったはずです。

ドライバーの自作を避けるとすると、残念ながら私には分かりません。
I2CかSPIの振りができればそれぞれ最大400kbps、16Mbpsなんですが……

> sysfsドライバより高速でGPIOを制御する方法の有無
> あるとすればどのような方法か

http://manual.atmark-techno.com/armadillo-4x0/armadillo-400_series_software_manual_ja-1.9.1/ch09.html#sec-armadillo-200-series-gpio-driver

a400には、200シリーズ互換のドライバーがあります。こちらはキャラクター
デバイスとして実装されています。

> ビルドの知識が不足しており
とのことですが、マニュアル↓にも記載されていますので、お試しください。
http://manual.atmark-techno.com/armadillo-4x0/armadillo-400_series_software_manual_ja-1.9.1/ch07.html#sec-kernel-userland-image-build

分らないところがあれば、ここで質問してください。

> 制御ファイルの値を1回書き換えるのに700マイクロ秒程度かかっている
> C言語で、open, write, close する方法でかかった時間を計測

とありますが、毎回 open / close する必要は無いと思うのですが、どうでしょ
うか? 700us のうち 支配的なのは writeでしたか?

伊澤様

コメントありがとうございます。

やはりドライバを作れば高速制御できるのですね。安心しました。
ドライバの自作も含めて改めて検討してみます。

> 毎度お世話様、伊澤@イットーソフトウェアです。
>
> ドライバを作れば余裕で高速制御できます。
> 私の処でsysfsを使って試した結果でも、1周期に1ms程度掛かっていました。
> しかも8bit並列で処理したかった為にドライバを作りましたが、
> 記憶に間違いがなければ1μsオーダーだったはずです。
>
> ドライバーの自作を避けるとすると、残念ながら私には分かりません。
> I2CかSPIの振りができればそれぞれ最大400kbps、16Mbpsなんですが……

200シリーズ互換ドライバであれば、より高速に制御できるということでしょうか。
マニュアルも参考に試してみようと思います。

> とありますが、毎回 open / close する必要は無いと思うのですが、どうでしょ
> うか? 700us のうち 支配的なのは writeでしたか?

GPIOピンにLEDをつないで実験したところ、writeしただけでは反応せず、
close時にLEDが点灯/消灯していたため、値を変更するたびにcloseが必要と
認識していました。

open, write, closeそれぞれにかかる時間を計測してみたところ、おおよそ

open: 300us
write: 150us
close: 300us

というような結果が得られたので、open/closeの回数を減らすことができれば
高速化が期待できそうです。

closeせずにGPIOピンの電位を変更する方法があるということでしたら、
是非教えていただきたいです。

> > sysfsドライバより高速でGPIOを制御する方法の有無
> > あるとすればどのような方法か
>
> http://manual.atmark-techno.com/armadillo-4x0/armadillo-400_series_software_manual_ja-1.9.1/ch09.html#sec-armadillo-200-series-gpio-driver
>
> a400には、200シリーズ互換のドライバーがあります。こちらはキャラクター
> デバイスとして実装されています。
>
> > ビルドの知識が不足しており
> とのことですが、マニュアル↓にも記載されていますので、お試しください。
> http://manual.atmark-techno.com/armadillo-4x0/armadillo-400_series_software_manual_ja-1.9.1/ch07.html#sec-kernel-userland-image-build
>
> 分らないところがあれば、ここで質問してください。
>
> > 制御ファイルの値を1回書き換えるのに700マイクロ秒程度かかっている
> > C言語で、open, write, close する方法でかかった時間を計測
>
> とありますが、毎回 open / close する必要は無いと思うのですが、どうでしょ
> うか? 700us のうち 支配的なのは writeでしたか?
>

齊藤と申します。
sysfsはよく知らないので一般論ですが・・・

> GPIOピンにLEDをつないで実験したところ、writeしただけでは反応せず、
> close時にLEDが点灯/消灯していたため、値を変更するたびにcloseが必要と
> 認識していました。

write後にfsync(fd)したらどうなりますか?これで反応するようならopen時に O_SYNCオプションをつけておけば解決するのではないかと思います。

中村です。

なんか、みなさんが書かれていることが
私のこれまでの経験と違うので、今、
確かめてみました。

テスト環境は、Armadillo-440にオシロです。

あらかじめ
echo out > /sys/class/gpio/CON9_1/direction
しておくものとします。

まずは、こちら。
> GPIOピンにLEDをつないで実験したところ、writeしただけでは反応せず、
> close時にLEDが点灯/消灯していたため、値を変更するたびにcloseが必要と
> 認識していました。

テストコード

int main(int argc, char *argv[])
{
    int fd = open("/sys/class/gpio/CON9_1/value", O_RDWR);
 
    for (;;) {
        printf("1 ");
        fflush(stdout);
        write(fd, "1", 1);
        sleep(10);
        printf("0 ");
        fflush(stdout);
        write(fd, "0", 1);
        sleep(10);
    }
    return 0;
}

これを実行すると、画面表示とほぼ同じタイミングで
波形が変化します。

次に、writeにかかる時間。

int main(int argc, char *argv[])
{
    int fd = open("/sys/class/gpio/CON9_1/value", O_RDWR);
 
    for (;;) {
        write(fd, "1", 1);
        write(fd, "0", 1);
    }
    return 0;
}

オシロで波形の周期を計測したところ
約11usecでした。周波数にすると約90kHz。
これから1回のwrite()にかかる時間は約5.5usecです。

ちなみに、毎回open/closeしたときは、
1周期が約250usecでした。

int main(int argc, char *argv[])
{
    int fd;
 
    for (;;) {
        fd = open("/sys/class/gpio/CON9_1/value", O_RDWR);
        write(fd, "1", 1);
        close(fd);
        fd = open("/sys/class/gpio/CON9_1/value", O_RDWR);
        write(fd, "0", 1);
        close(fd);
    }
 
    return 0;
}

テストコードのエラー処理省略です。
停止はCTRL-Cのみ。

--
なかむら

斉藤様

コメントありがとうございます。

結論から言いますと、fopenとopenを混同し、fopenをしようしていたことが原因でした。

fsyncを試すため、openを使ってテストプログラムを書き直したところ、
fsyncなしでもwriteと同時にGPIOピンが反応するようになりました。

さらに、openで書き直したことによって速度も大きく向上し、
1回の値の変更が5マイクロ秒程度で済むようになりました。

斉藤様のアドバイスから幾分低レベルなところでの解決となってしてしまい、
お恥ずかしい限りですが、ともかく大変助かりました。ありがとうございます。

伊澤様、at_yashi様もありがとうございました。この場を借りてお礼申し上げます。

> 齊藤と申します。
> sysfsはよく知らないので一般論ですが・・・
>
> > GPIOピンにLEDをつないで実験したところ、writeしただけでは反応せず、
> > close時にLEDが点灯/消灯していたため、値を変更するたびにcloseが必要と
> > 認識していました。
>
> write後にfsync(fd)したらどうなりますか?これで反応するようならopen時に O_SYNCオプションをつけておけば解決するのではないかと思います。