ESP8266で高速GPIOアクセス (検証編)
ESP8266 GPIOこんにちは、今回はESP8266の高速GPIOアクセスについて調べてみました。
Arduinoの場合、デジタル入出力関数としてdigitalRead(), digitalWrite()を用いますが、直接メモリにアクセスすることでより高速にGPIO入出力することも可能です。
直接メモリにアクセスしたGPIO出力に関しては、これまでにmacsbugさんによって詳細に調べられています。
今回は、直接メモリにアクセスした場合のGPIO入力・出力の速度を求め、digitalRead(), digitalWrite()との速度の比較をしてみます。
オシロスコープを持っていないため、GPIO出力の波形はチェック出来ておりません。あくまでもGPIOアクセス速度の目安の1つとして参考にして頂けると幸いです。
実行時間
まずは結果から、青のバーが従来のdigitalRead/Write、オレンジのバーがdirectRead/Writeです。
横軸は10000回実行時の時間ですので、値が小さいほど高速です。
1回あたりの実行時間、カッコ内は10000回実行時のクロック数を示しています。
digitalRead | digitalWrite | directRead | directWrite | |
---|---|---|---|---|
ESP8266 (80MHz) | 0.60 us, 1.7 MHz (480642) |
0.46 us, 2.2 MHz (365334) |
0.21 us, 4.7 MHz (170335) |
0.28 us, 3.6 MHz (220659) |
ESP8266 (160MHz) | 0.33 us, 3.1 MHz (520637) |
0.23 us, 4.4 MHz (365642) |
0.14 us, 7.3 MHz (220643) |
0.20 us, 5.0 MHz (320648) |
実装コードについて
digitalRead
void inline loop_digitalRead() {
for(int i=0;i<10000;i++) {
digitalRead(13);
}
}
digitalWrite
void inline loop_digitalWrite() {
for(int i=0;i<10000/2;i++) {
digitalWrite(12, LOW);
digitalWrite(12, HIGH);
}
}
directRead
void inline loop_directRead() {
for(int i=0;i<10000;i++) {
((*PIN_IN_ADDR >> 13)&0x1);
}
}
GPIOn (n=0~15)の入力値は、PIN_IN_ADDRの(n+1)ビット目に記録されています。
directWrite
void inline loop_directWrite() {
for(int i=0;i<10000/2;i++) {
GPIO_OUT |= 0x1000;
GPIO_OUT &= ~0x1000;
}
}
この関数ではビット演算を使って、GPIO12のLOW, HIGHを切り替えています。
-
GPIO12を0にする
GPIO_OUT &= ~0x1000;
-
GPIO12を1にする
GPIO_OUT |= 0x1000;
GPIOn (n=0~15)のLOW/HIGHを指定する場合、GPIO_OUTの(n+1)ビット目を変更する必要があります。
今回、GPIO12の出力を変更するので13ビット目の値を変更しています。(00010000 00000000 = 0x1000)
また、このような手法で出力をする場合はsetup()内で予め以下のようにセットしておく必要があります。
GPIO_ENABLE |= 0x1000;
GPIO_PIN12 = 0;
GPIO_ENABLEでアウトプットするGPIOを指定します。
またGPIO_PIN12ではGPIO12の役割を指定します。その内訳は以下の通りです。
- GPIO_PIN12_WAKEUP_ENABLE
- GPIO_PIN12_INT_TYPE
- GPIO_PIN12_DRIVER
- GPIO_PIN12_SOURCE
アウトプットする場合は GPIO_PIN12 = 0;
にします。
詳しくはESP8266 Technical Reference Appendix 1を御覧ください。
検証コード全体
#define GPIO_OUT *(volatile uint32_t *)0x60000300
#define PIN_IN_ADDR ((volatile uint32_t *)0x60000318)
#define GPIO_ENABLE *(volatile uint32_t *)0x6000030C
#define GPIO_PIN12 *(volatile uint32_t *)0x60000358
#define CLOCK 160
static inline unsigned get_ccount(void)
{
unsigned r;
asm volatile ("rsr %0, ccount" : "=r"(r));
return r;
}
long startCount, endCount;
void inline loop_digitalRead() {
for(int i=0;i<10000;i++) {
digitalRead(13);
}
}
void inline loop_digitalWrite() {
for(int i=0;i<10000/2;i++) {
digitalWrite(12, LOW);
digitalWrite(12, HIGH);
}
}
void inline loop_directRead() {
for(int i=0;i<10000;i++) {
((*PIN_IN_ADDR >> 13)&0x1);
}
}
void inline loop_directWrite() {
for(int i=0;i<10000/2;i++) {
GPIO_OUT |= 0x1000;
GPIO_OUT &= ~0x1000;
}
}
void show_result(String title, long startCount, long endCount) {
int delta = endCount - startCount;
Serial.print(title);
Serial.print(" (10000 times): ");
Serial.print(delta);
Serial.println(" clocks");
}
void setup() {
Serial.begin(115200);
delay(100);
Serial.println();
delay(100);
pinMode(13, INPUT);
pinMode(12, OUTPUT);
GPIO_ENABLE |= 0x1000;
GPIO_PIN12 = 0;
Serial.print("ESP8266 (");
Serial.print(CLOCK);
Serial.println(" MHz)");
noInterrupts();
startCount = get_ccount();
loop_digitalRead();
endCount = get_ccount();
show_result("digitalRead", startCount, endCount);
startCount = get_ccount();
loop_digitalWrite();
endCount = get_ccount();
show_result("digitalWrite", startCount, endCount);
startCount = get_ccount();
loop_directRead();
endCount = get_ccount();
show_result("directRead", startCount, endCount);
startCount = get_ccount();
loop_directWrite();
endCount = get_ccount();
show_result("directWrite", startCount, endCount);
interrupts();
}
void loop() { }