goo blog サービス終了のお知らせ 

ゴーイングマイウェイの戯言

パソコンに関することなど、作者の趣味が中心のブログです

Arduino digitalRead/digitalWrite/pinModeの高速化

2018-02-13 23:16:56 | Arduino&Raspberry Pi
前回は、analogReadの高速化にチャレンジした。

今回は、digitalReadやdigitalWrite、pinModeの高速化にトライしてみる。


参考にしたもの
 Arduinoの高速化 | なんでも独り言
 Arduinoプログラムの高速化
 ポート・レジスタについての説明 | Arduino 日本語リファレンス
 ATmega168/328P-Arduino Pin Mapping | Arduino.cc (英語)



1.高速化の原理&Arduinoのピン配置
 Arduinoのデジタル入力/デジタル出力を高速化するには、Arduinoの関数(digitalRead()など)を使わずに、
 マイコンのレジスタを直接操作することで行う。
 この際、ピン番号の指定はArduinoのピン番号ではなく、マイコンのピン名を指定しなければならない。

 Arduino UNOの場合は、ATmega328Pというマイコンを使っているので、このマイコンのピン名と
 Arduinoのピン番号の関連付けを調査した。

 
 上の図は、Arduino.ccのサイトから抜粋したものである。
 これを見ると、Arduinoのdigital pin 7はマイコンのPD7と、digital pin 13はマイコンのPB5と
 紐づいていることがわかる。


2.高速化の方法

◆digitalRead : デジタル入力の高速化

 ポート入力レジスタ(読み取り専用)のPINxレジスタを使って、高速化する。
 PDのところを読み込みたい場合は『PIND』、PBのところを読み込みたい場合は『PINB』とする。

 int val = digitalRead(7);
      ↓
 int val = PIND & _BV(7);

 int val = digitalRead(13);
      ↓
 int val = PINB & _BV(5);


◆digitalWrite : デジタル出力の高速化
 ポートデータレジスタであるPORTxレジスタの該当ビットへ、HIGH, LOWを直接代入することで高速化する。
 PDのところを動かしたい場合は『PORTD』、PBのところの場合は『PORTB』とする。
 HIGHの時はPORTxと_BV()の間を『|=』で括る。
 LOWの時は、PORTxと~_BV()の間を『&=』で括る。

 digitalWrite(7, HIGH);
      ↓
 PORTD |= _BV(7);

 digitalWrite(13, LOW);
      ↓
 PORTB &= ~_BV(5);


複数ピンを同時に操作する場合は、以下のようにする。

 PORTD |= _BV(5) | _BV(6) | _BV(7);
 PORTD &= ~(_BV(5) | _BV(6) | BV(7));



◆pinMode : ピンモード選択の高速化
 ポート方向レジスタであるDDRxレジスタの該当ビットへ、OUTPUT, INPUTを直接セットすることで高速化する。
 PDのところを操作したい場合は『DDRD』、PBのところの操作は『DDRB』とする。
 OUTPUTの時はDDRxと_BV()の間を『|=』で括る。
 INPUTの時は、DDRxと~_BV()の間を『&=』で括る。

 pinMode(7, OUTPUT);
      ↓
 DDRD |= _BV(7);

 pinMode(13, INPUT);
      ↓
 DDRB &= ~_BV(5)



3.高速化の検証
 下記スケッチでdigitalReadの高速化の検証を行った。

void setup() {
  // シリアルポート初期化
  Serial.begin(9600);
}

void loop() {

  int moji;
  // シリアルポートより、文字を1字読み込む
  moji = Serial.read();

  // 文字入力があった場合のみif関数を実行
  if(moji != -1){


    // 開始時間を記録
   unsigned long StartTime = micros();

    // 100,000回 digitalReadを実行
    for(long i=0; i<100000; i++){
      int val = digitalRead(7);
    }

    // digitalRead後の時間を記録
    unsigned long CheckTime = micros();

    // 100,000回 高速化を実行
    for(long i=0; i<100000; i++){
      int val = PIND & _BV(7);
    }

     // 高速化Read後の時間を記録
    unsigned long StopTime = micros();
    

    // 結果を出力
    Serial.print("digitalRead(100,000cyc)   = ");
    Serial.print(CheckTime - StartTime);
    Serial.println(" [us]");

    Serial.print("Speed-Up Read(100,000cyc) =  ");
    Serial.print(StopTime - CheckTime);
    Serial.println(" [us]");
  }
}


digitalRead 100,000回の実行時間は238,944[us]だったので、1回あたり2.39[us]だった。
一方、高速化して100,000回実行した結果は44,020[us]だったので、1回あたり0.44[us]となった。
レジスタから直接読み取ることで、digitalRead関数を使った時より5倍強の高速化をすることができた。

2 コメント

コメント日が  古い順  |   新しい順
参考にさせていただきました (中納言)
2021-10-25 13:09:08
本情報ありがとうございます。
GPIOで10kHzのPulseを2系統、On/Offを1μsec以下のタイミングで交互に切り換えて出す信号が欲しかったのですが、
Arduino UNOでdigitalWrite()を使ったら、3μsec程度マシンタイムがかかり、いいタイミングが作れませんでした
ここで紹介いただいた、
PORTD |= _BV(7);
PORTB &= ~_BV(5);
この辺の命令語だと、50nsecくらいで動作するので調整ができるようになりました。
ありがとうございます。
一点だけ補足で
上の3個目の黄色い四角内のサンプルSketchに、
PORTD &= ~(_BV(5) | _BV(6) | BV(7));
とありますが、
PORTD &= ~(_BV(5) | _BV(6) | _BV(7));
でないとエラーが出てしまいました。

今後も参考にしていただくことがありましたらまた連絡させていただきます。
返信する
レビュー依頼 (林遠)
2022-05-23 15:55:52
失礼致しました。Amazonで日本のラズベリーパイを販売している林遠です。
ブログを拝見しました。弊社のラズベリーパイ4bレビューブログ記事を書きしてくれませんか。
こちらは無料でサンプルを提供します。
連絡メールはjp02@vertue.cnです。
御返事お待ちしております。どうぞよろしくお願いします。
返信する

コメントを投稿