Teensy4.1 (Teensy4)は高速・高性能なマイコンで、ArduinoやESP32では難しい処理をする時に役立ちます。以前の記事では、GPIOのHIGH/LOWの切り替えがESP32よりもかなり速いことを検証しました。
この時の結果はdigitalWriteで35.84ns(クロックで14.0MHz)、digitalWriteFastで3.334ns(クロックで150MHz)というスピードであった。これでも驚異的な速度なのですが、レジスタを直接書き換えることで複数のPinを同時に・digitalWriteFastよりも速く書き換えられる可能性があるので、それを検証します。
レジスタを書き換える方法
マイコンの中ではレジスタという領域にデータが入っていおり、この値を変えることでマイコンの設定を変えることができます。Teensy4.1に搭載されたARM Cortex-M7は32bitのマイコンなので、特定の領域(アドレスと言います)の中にある、32桁(2進数)の0と1の並びを書き換えることでGPIOのHIGH/LOWを切り替えたり、読み込んだりすることができます。
ともかくLチカをやってみよう
1 2 3 4 5 6 7 8 9 10 11 |
void setup() { pinMode(13,OUTPUT); } //13pinはGPIO7の4桁目(0b1000) void loop() { GPIO7_DR_SET = 1<<(4-1);//13pinをHIGHに delay(500); GPIO7_DR_CLEAR = 1<<(4-1);//13pinをLOWに delay(500); } |
これで13pin(内蔵LED)が1秒周期でチカチカします。これは13pinがGPIO7というグループの4番目に属しているため、GPIO7_DR_SETの領域にある4桁目(2進数)を1にすると出力がHIGHに(0にしていると影響を受けない)、GPIO7_DR_CLEARの領域にある4桁目(2進数)を1にすると出力がLOWになります。本当はGPIO7ではなくGPIO2に属しているらしいのですが、Arduinoでは高速にON/OFFするためにGPIO7の方を操作しているとのこと(なので他のピンもGPIO1~4ではなくGPIO6~9を操作します)。

レジスタマップを見ると、一つのGPIOに割り当てられているのはGPIO6が最も多く、20ピンとなっています。一度にHIGH/LOWを切り替えられるのは同じGPIOグループのピン同士なので、最大20ピンを同時に制御できるということになります。(Teensy4だと12ピン)
GPIOの出力を制御するレジスタはそれぞれ以下の通りです。(*はGPIOグループの番号)
GPIO*_DR:0で出力をLOW、1で出力をHIGH(OUTPUTに設定した全Pinに影響あり)
GPIO*_DR_SET:1で出力をHIGH、0は変化なし
GPIO*_DR_CLEAR:1で出力をLOW、0は変化なし
GPIO*_DR_TOGGLE:1で出力を反転、0は変化なし
GPIOの場所を毎回調べるのは面倒なので
Arduinoのライブラリの記載を借りてきましょう。Teensyduinoライブラリの”core_pins.h”の中に記載された、以下の定数を使います。ちなみに今回はdigitalWriteに関する箇所しか使いませんが、同じファイルにdigitalRead、analogRead/Writeに関する記載もあるので、興味ある人は調べてみてください。
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 |
#define CORE_PIN0_BIT 3 …(省略)… #define CORE_PIN13_BIT 3 …(省略)… #define CORE_PIN54_BIT 29 #define CORE_PIN0_BITMASK (1<<(CORE_PIN0_BIT)) …(省略)… #define CORE_PIN13_BITMASK (1<<(CORE_PIN13_BIT)) …(省略)… #define CORE_PIN54_BITMASK (1<<(CORE_PIN54_BIT)) // Fast GPIO #define CORE_PIN0_PORTREG GPIO6_DR …(省略)… #define CORE_PIN13_PORTREG GPIO7_DR …(省略)… #define CORE_PIN54_PORTREG GPIO9_DR #define CORE_PIN0_PORTSET GPIO6_DR_SET …(省略)… #define CORE_PIN13_PORTSET GPIO7_DR_SET …(省略)… #define CORE_PIN54_PORTSET GPIO9_DR_SET #define CORE_PIN0_PORTCLEAR GPIO6_DR_CLEAR …(省略)… #define CORE_PIN13_PORTCLEAR GPIO7_DR_CLEAR …(省略)… #define CORE_PIN54_PORTCLEAR GPIO9_DR_CLEAR #define CORE_PIN0_PORTTOGGLE GPIO6_DR_TOGGLE …(省略)… #define CORE_PIN13_PORTTOGGLE GPIO7_DR_TOGGLE …(省略)… #define CORE_PIN54_PORTTOGGLE GPIO9_DR_TOGGLE |
これらの定数をもとに13ピンのLチカを書くとこうなります。
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 |
void setup() { pinMode(13,OUTPUT); Serial.begin(115200); } void loop() { Serial.println("Set & Clear Test"); for(int i=0;i<10;i++){ CORE_PIN13_PORTSET = CORE_PIN13_BITMASK;//HIGH delay(500); CORE_PIN13_PORTCLEAR = CORE_PIN13_BITMASK;//LOW delay(500); } Serial.println("Toggle Test"); for(int i=0;i<20;i++){ CORE_PIN13_PORTTOGGLE = CORE_PIN13_BITMASK;//HIGH-LOW切り替え delay(500); } Serial.println("REG Test"); for(int i=0;i<20;i++){ CORE_PIN13_PORTREG = CORE_PIN13_BITMASK;//HIGH delay(500); CORE_PIN13_PORTREG = 0;//LOW delay(500); } } |
“Set & Clear Test”、”Toggle Test”、”REG Test”は全て同じ動作になります(0.5秒ごとにLEDがOn/OFFを繰り返す)。ただ、”REG Test”は他のGPIOピンの出力に影響を与えるので注意が必要です(今回の場合、他のGPIOピンは(ピンモードをOUTPUTにした場合でも)ずっとLOW)。
複数のPinを同時に切り替える方法
CORE_PIN_BITMASKを足してレジスタに代入すればよいです。ただし、同時に切り替えられるのは同じGPIOのグループに所属しているPin同士のみです。
1 2 3 4 5 6 7 8 9 10 |
//pinModeをOUTPUTにする pinMode(11,OUTPUT); pinMode(12,OUTPUT); pinMode(13,OUTPUT); //11,12,13ピンをHIGHにする CORE_PIN13_PORTSET = CORE_PIN11_BITMASK+CORE_PIN12_BITMASK+CORE_PIN13_BITMASK; //もしくはこう書く GPIO7_DR_SET = CORE_PIN11_BITMASK+CORE_PIN12_BITMASK+CORE_PIN13_BITMASK; |
速度計測
digitalWrite、digitalWriteFast、レジスタ書き換え(Set&CLEAR、TOGGLE、REG)の5種類でHIGH/LOWを1,000,000サイクル繰り返して速度を計測した。
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 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
unsigned long Time; void setup() { pinMode(11,OUTPUT); pinMode(12,OUTPUT); pinMode(13,OUTPUT); Serial.begin(115200); delay(1000); Serial.print("digitalWrite"); Time = micros(); for(int i=0;i<1000000;i++){ digitalWrite(13,HIGH); digitalWrite(13,LOW); } Serial.printf(" %ld us\n",micros()-Time); Serial.print("digitalWriteFast"); Time = micros(); for(int i=0;i<1000000;i++){ digitalWriteFast(13,HIGH); digitalWriteFast(13,LOW); } Serial.printf(" %ld us\n",micros()-Time); Serial.print("Set & Clear Test:Single Pin"); Time = micros(); for(int i=0;i<1000000;i++){ GPIO7_DR_SET = CORE_PIN13_BITMASK;//HIGH GPIO7_DR_CLEAR = CORE_PIN13_BITMASK;//LOW } Serial.printf(" %ld us\n",micros()-Time); Serial.print("Toggle Test:Single Pin"); Time = micros(); for(int i=0;i<2000000;i++){ CORE_PIN13_PORTTOGGLE = CORE_PIN13_BITMASK;//HIGH-LOW切り替え } Serial.printf(" %ld us\n",micros()-Time); Serial.print("REG Test:Single Pin"); Time = micros(); for(int i=0;i<1000000;i++){ CORE_PIN13_PORTREG = CORE_PIN13_BITMASK;//HIGH CORE_PIN13_PORTREG = 0;//LOW } Serial.printf(" %ld us\n",micros()-Time); Serial.print("Set & Clear Test:Multi Pin"); Time = micros(); for(int i=0;i<1000000;i++){ GPIO7_DR_SET = CORE_PIN11_BITMASK + CORE_PIN12_BITMASK + CORE_PIN13_BITMASK;//HIGH GPIO7_DR_CLEAR = CORE_PIN11_BITMASK + CORE_PIN12_BITMASK + CORE_PIN13_BITMASK;//LOW } Serial.printf(" %ld us\n",micros()-Time); Serial.print("Toggle Test:Multi Pin"); Time = micros(); for(int i=0;i<2000000;i++){ CORE_PIN13_PORTTOGGLE = CORE_PIN11_BITMASK + CORE_PIN12_BITMASK + CORE_PIN13_BITMASK;//HIGH-LOW切り替え } Serial.printf(" %ld us\n",micros()-Time); Serial.print("REG Test:Multi Pin"); Time = micros(); for(int i=0;i<1000000;i++){ CORE_PIN13_PORTREG = CORE_PIN11_BITMASK + CORE_PIN12_BITMASK + CORE_PIN13_BITMASK;//HIGH CORE_PIN13_PORTREG = 0;//LOW } Serial.printf(" %ld us\n",micros()-Time); } |
実行結果
1 2 3 4 5 6 7 8 |
digitalWrite 68337 us digitalWriteFast 6668 us Set & Clear Test:Single Pin 6668 us Toggle Test:Single Pin 6668 us REG Test:Single Pin 6668 us Set & Clear Test:Multi Pin 6668 us Toggle Test:Multi Pin 6668 us REG Test:Multi Pin 6668 us |
方式 | 測定結果 [us] | GPIO切り替え速度 [ns] |
---|---|---|
digitalWrite | 68,337 | 34.17 |
digitalWriteFast | 6,668 | 3.334 |
Set & Clear:Single Pin | 6,668 | 3.334 |
Toggle:Single Pin | 6,668 | 3.334 |
REG:Single Pin | 6,668 | 3.334 |
Set & Clear:Multi Pin | 6,668 | 3.334 |
Toggle:Multi Pin | 6,668 | 3.334 |
REG:Multi Pin | 6,668 | 3.334 |
digitalWriteとdigitalWriteFastは前回の結果と同じ、レジスタを書き換える方式はPinひとつでも複数でもdigitalWriteFastと同じスピードとなった。
結論
単一のPinの出力だけ変える場合:digitalWriteFast()で十分
複数のPinの出力を高速に変えたい:レジスタを書き換える
おまけ:さらに高速にする方法
実はTeensy4.1はクロックスピードを通常の600MHzからOverClockすることができる(高い周波数の時は冷却が必要)。OverClockにすると、GPIOスピードがもっと高速になります。

クロックスピード | digitalWriteFastの時間 [ns] |
---|---|
600MHz | 3.334 |
816MHz | 2.452 |
912MHz | 2.084 |
1.008GHz | 1.985 |
速…!!
250MHzのクロック信号出せるのね…
コメント