Seeeduino XIAOのdigitalReadを高速化する
概要
Seeeduino XIAOのdigitalReadを高速化したいので、レジスタから読み出すように変更すると3倍ほど早くなった。シングルサイクルIOを使用すると5倍ほどはやくなった。
目的
Arduinoは簡単に移植性が高いプログラムが作成できるものの、互換の維持やエラーをチェックする処理が入っているため実行速度が多少犠牲になっている(らしい)。もったいないので、特に面倒くさくならないレベルで高速化したい。
方法
Arduino DIO高速化に関する記事は探すといっぱい出てきて正規?のArduinoではdigitalReadの代わりにPINxレジスタを使用して読み取りを行うと高速化ができるようだ。しかし互換機であるSeeeduino XIAOにはこのレジスタが定義されていないようで使用できない。
そこでdigitalReadに関数するソースを読んで該当するIOをレジスタを探した。Arduino IDEを使用している場合、以下のコードを読めばおおよそ動きはつかめた。
【digitalReadの実装】
C:\Users\****\AppData\Local\Arduino15\packages\Seeeduino\hardware\samd\1.8.2\cores\arduino\wiring_digital.c
【おそらくハードの差異を吸収している場所】
C:\Users\****\AppData\Local\Arduino15\packages\Seeeduino\hardware\samd\1.8.2\variants\XIAO_m0\variant.cpp
【PinDescriptionの定義】
C:\Users\****\AppData\Local\Arduino15\packages\Seeeduino\hardware\samd\1.8.2\cores\arduino\WVariant.h
読んでみると、digitalReadでは、指定したピンが読み込み可能か?などのチェックが入っているみたい。肝心の読み出し部分はPORT->Group[N].IN.regから読んでいるのだが、PORT->Groupはどこに定義されてるのかよくわからなかったので、これが最終到達地点みたい。
とりえあえず2番ピン読み出しをdigitalReadとPORT->Groupから読む方法で速度を比較することにした。用いたコードは以下。countは、ちゃんと読めているかの確認用である。
unsigned long start;
unsigned long count;
void setup() {
pinMode(2, INPUT_PULLUP );
Serial.begin(9600);
}
void loop() {
start = micros();
count = 0;
for (int i = 0; i < 1000000; ++i)
{
//if (digitalRead(2) == HIGH) //素直な方法
if((PORT->Group[0].IN.reg & (1ul << 10)) > 0) //レジスタよみこみ
{
count += 1;
}
}
Serial.print("Time:");
Serial.println(micros() - start);
Serial.print("Count:");
Serial.println(count);
}
方法2(IOBUS)
調べているうちに、こちらの記事を発見した。シングルサイクルIOというものがSAMD21にあるらしく、それを簡単に扱えるようにしたライブラリも公開してくださっている。この方法はライブラリ名からIOBUSと呼ぶことにする。以下のコードより試してみた。
#include "IOBUS.h"
unsigned long start;
unsigned long count;
void setup(void)
{
Serial.begin(9600);
IOBUS::pinMode(IOBUS_PIN_D2, INPUT_PULLUP );
}
void loop() {
start = micros();
count = 0;
for (int i = 0; i < 1000000; ++i)
{
if(IOBUS::digitalRead(IOBUS_PIN_D2))
{
count += 1;
}
}
Serial.print("Time:");
Serial.println(micros() - start);
Serial.print("Count:");
Serial.println(count);
}
結果
素直な方法 Time:1300903⇒ 1回あたり1.30us
レジスタ読みだし Time:419651 ⇒ 1回あたり0.42us
IOBUS Time:272769 ⇒ 1回あたり0.27us
レジスタ読みだしを使用した場合で3倍くらい早くなった。IOBUSを使用した場合で4.8倍となった。すげぇ。
まとめ
Seeeduino XIAOのdigitalReadを高速化した。digitalReadの定義を読んだ感じは対して変わらなさそうと思ったのだがやってみると3倍速くなって驚いた。Arduino使っておいて言うのもなんだがdigitalReadUnsafeとかで、はえーやつも用意しておいてほしい(贅沢)。
用意してくださった方がいました。こちらを使用すると5倍くらい。パネェ。周波数にすると37MHz程度だが、こちらは理論上48MHzで回るということなのだろうか?だとすると残りの10MHzはループの条件分岐やインクリメントで消費されているのかな?
IOBUSの作成者さんの記事を色々見ているとSeeeduino XIAO(というかSAMD21?)でインプットキャプチャなども取り扱えるらしいので試してみたい・・と調べてみたのだがまったくわからなかった。そもそもどこを調べたらいいのかすらわからん…。