2020/11/3修正:ESP32の速度が遅かったのはレジスタの選択ミスだったので、そこを修正
GPIOスピード比較
Arduinoの”digitalWrite()”はまあ遅いことで有名なので、Lチカのような時は使っても、高速通信をするのには向かないのだが、じゃあ何を使うかというとArduinoでは”FastGPIO”というライブラリがある。(”FastGPIO”はArduino IDEでライブラリを検索して導入できる。)
しかし、これはESP32系とTeensy系では使えないようで、他の方法でGPIOを速くオンオフする方法を検証してみる。
・レジスタを直接書き換える
マイコンの中ではレジスタという領域にデータが入っていおり、この値を変えることでマイコンの設定を変えることができる。ArduinoのpinModeやdigitalWriteも結局は特定のレジスタの値を変更して実現している。この方法の良いところは最速であるのと、複数のIOを同時に変更できることで、欠点はマイコンの種類ごとにレジスタを調べる必要があること。
ESP32では表のようになっており、例えば、5ピンのpinModeをOUTPUTにして、GPIO_OUT_REGの6ビット目を1にした値(b100000)を代入すると、5ピンがHIGHになる。GPIO_IN_REGはインプットなので、例えば2ピンをINPUTにしておいて、GPIO_IN_REGの3ビット目が1であれば2ピンの入力はHIGHであるはず(未検証)。
※GPIO_OUT_REGを書き換えるよりも速い方法がありました。詳しくは後述
Name | Description | Address |
GPIO_OUT_REG | GPIO 0-31 output register | 0x3FF44004 |
GPIO_OUT1_REG | GPIO 32-39 output register | 0x3FF44010 |
GPIO_IN_REG | GPIO 0-31 input register | 0x3FF4403C |
GPIO_IN1_REG | GPIO 32-39 input register | 0x3FF44040 |
(https://www.espressif.com/sites/default/files/documentation/esp32_technical_reference_manual_en.pdf)
一方のTeensy4.1も同様のことができるはずなのだが、正直資料が少なくてよく分からない。。。
・digitalWriteFast()を使う
これはTeensyでのみ使える方法。使い方は簡単でdigitalWrite()をdigitalWriteFast()に置き換えるだけ。
速度の測定
ESP32はM5Stackを使って測定した。測定に使ったコードは以下の通り。
・ESP32
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
#define GPIO_0to31_REG *((volatile unsigned long *)GPIO_OUT_REG) #define pin 5 unsigned long Time; void setup() { Serial.begin(115200); pinMode(pin,OUTPUT); } void loop() { Serial.println("Normal digitalWrite"); for(int j=0;j<100;j++){ Time = micros(); for(int i=0;i<1000000;i++){ digitalWrite(pin,HIGH); digitalWrite(pin,LOW); } Serial.printf("%ld,",micros()-Time); } Serial.println(); Serial.println("Direct digitalWrite"); for(int j=0;j<100;j++){ Time = micros(); for(int i=0;i<1000000;i++){ GPIO_0to31_REG |= (1<<pin); GPIO_0to31_REG &= ~(1<<pin); } Serial.printf("%ld,",micros()-Time); } Serial.println(); } |
・Teensy 4.1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
#define pin 5 unsigned long Time; void setup() { Serial.begin(115200); pinMode(pin,OUTPUT); } void loop() { Serial.println("Normal digitalWrite"); for(int j=0;j<100;j++){ Time = micros(); for(int i=0;i<1000000;i++){ digitalWrite(pin,HIGH); digitalWrite(pin,LOW); } Serial.printf("%ld,",micros()-Time); } Serial.println(); Serial.println("Fast digitalWrite"); for(int j=0;j<100;j++){ Time = micros(); for(int i=0;i<1000000;i++){ digitalWriteFast(pin,HIGH); digitalWriteFast(pin,LOW); } Serial.printf("%ld,",micros()-Time); } Serial.println(); } |
ESP32 | 平均[ns] | 標準偏差[ns] |
digitalWrite | 119.7 | 0.0042 |
directIO | 126.0 | 0.0008 |
Teensy4.1 | 平均[ns] | 標準偏差[ns] |
digitalWrite | 35.84 | 0.0003 |
digitalWriteFast | 3.334 | 0.00007 |
結果
あれ…?ESP32のdirectIOが全然速くない。。。というかdigitalWriteが意外と速い?Teensy4.1に関してはdigitalWriteFastを使うほうが10倍速いね、ESP32と比べると36倍速い。なお…↓
レジスタを別のものにしたらもっと速くなりました(20/11/3追記)
「レジスタのリードとライト2回アクセスが発生して」しまうというコメントをいただき、もう一度データシートを見直してみたらこんなのが
Name | Description | Address | Access |
GPIO_OUT_W1TS_REG | GPIO 0-31 output bit set register | 0x3FF44008 | WO |
GPIO_OUT1_W1TS_REG | GPIO 32-39 output bit set register | 0x3FF44014 | WO |
GPIO_OUT_W1TC_REG | GPIO 0-31 output bit clear register | 0x3FF4400C | WO |
GPIO_OUT1_W1TC_REG | GPIO 32-39 output bit clear register | 0x3FF44018 | WO |
(https://www.espressif.com/sites/default/files/documentation/esp32_technical_reference_manual_en.pdf)
つまり、これらのレジスタを1にするとポートの出力がW1TSの場合HIGHに、W1TCの場合はLOWになるよ、というものでGPIO_OUT_REGのアクセスが”R/W”(Read/Write)であるのに対して、このレジスタは”WO”(Write Only)のため、一回のアクセスで済むということの様です。
速度測定
改めて速度を測定したら表のようになりました。GPIO_OUT_REGを書き換えていた時よりも2倍以上速くなり、digitalWriteを使うよりも速くなりました。複数のポートを一度に書き換えられるので、digitalWriteの代わりに使用する機会は多そうです。
ESP32 | 平均[ns] | 標準偏差[ns] |
digitalWrite | 120.0 | 0.0011 |
GPIO_OUT_REG | 125.9 | 0.0012 |
GPIO_OUT_W1T_REG | 50.46 | 0.0010 |
・測定に使用したコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
#define GPIO_0to31_REG *((volatile unsigned long *)GPIO_OUT_REG) #define GPIO_0to31SET_REG *((volatile unsigned long *)GPIO_OUT_W1TS_REG) #define GPIO_0to31CLR_REG *((volatile unsigned long *)GPIO_OUT_W1TC_REG) #define pin1 3 #define pin2 4 #define pin3 5 unsigned long Time; void setup() { Serial.begin(115200); pinMode(pin1,OUTPUT); pinMode(pin2,OUTPUT); pinMode(pin3,OUTPUT); } void loop() { Serial.println("Normal digitalWrite"); for(int j=0;j<100;j++){ Time = micros(); for(int i=0;i<1000000;i++){ digitalWrite(pin1,HIGH); digitalWrite(pin1,LOW); } Serial.printf("%ld,",micros()-Time); } Serial.println(); Serial.println("Direct digitalWrite_GPIO_OUT_REG"); for(int j=0;j<100;j++){ Time = micros(); for(int i=0;i<1000000;i++){ GPIO_0to31_REG |= (1<<pin2); GPIO_0to31_REG &= ~(1<<pin2); } Serial.printf("%ld,",micros()-Time); } Serial.println(); Serial.println("Direct digitalWrite_GPIO_OUT_W1T_REG"); for(int j=0;j<100;j++){ Time = micros(); for(int i=0;i<1000000;i++){ GPIO_0to31SET_REG = 1<<pin3; GPIO_0to31CLR_REG = 1<<pin3; } Serial.printf("%ld,",micros()-Time); } Serial.println(); } |
追記
なんで(GPIO編)としているかというと、(SDカード編)をやりたいからなのだけど、一度計ろうとして挫折しているのでここ(エレホビカ様)を参照してやってみたい。
コメント
ESP32
上記のDirectIOの例だと、1回のポート出力にレジスタのリードとライト2回アクセスが発生してしまいます。ESP32にはセット、リセットを行う別のレジスタがあります。それだと1回のライトで出力できます。指定しないビットのポートは変化なしなので、排他制御もいりません。digitalWriteでは、セット、リセットをするレジスタを使用しているので速度が逆転したんだとおもいます。
aさん
ありがとうございます!レジスタを別のものにして試したら速度が改善しました。