2017-09-12

ESP8266で高速GPIOアクセス (検証編)

ESP8266 GPIO

こんにちは、今回はESP8266の高速GPIOアクセスについて調べてみました。

Arduinoの場合、デジタル入出力関数としてdigitalRead(), digitalWrite()を用いますが、直接メモリにアクセスすることでより高速にGPIO入出力することも可能です。

直接メモリにアクセスしたGPIO出力に関しては、これまでにmacsbugさんによって詳細に調べられています。

今回は、直接メモリにアクセスした場合のGPIO入力・出力の速度を求め、digitalRead(), digitalWrite()との速度の比較をしてみます。

オシロスコープを持っていないため、GPIO出力の波形はチェック出来ておりません。あくまでもGPIOアクセス速度の目安の1つとして参考にして頂けると幸いです。

実行時間

まずは結果から、青のバーが従来のdigitalRead/Write、オレンジのバーがdirectRead/Writeです。

横軸は10000回実行時の時間ですので、値が小さいほど高速です。

gpio-time

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() {  }

投稿をシェアする