この記事は..
きのう投稿した『Raspberry Pi Pico(ラズピコ):各種SDKによるIO速度の比較』の補足
この記事ではArduino-Picoを使ってIO周波数:600kHzが得られたことを書いた.記事内ではMicroPythonではIOレジスタへの直接アクセスは試してみたのに,Arduinoではやってなかった.これってフェアぢゃない.
ArduinoのコードはC/C++でコンパイルされるので,digitalWrite()
ってAPIを使わずに直接レジスタへ書き込めばかなり速くなるはず.で,それをやってみた.
追記:この記事を投稿した直後,Twitter(ていうが「X」)で,別の方法があることを教えていただいた.
https://twitter.com/FremenHlide/status/1691233928554672129
Raspberry Pi Pico C/C++ SDKのgpio_put()
がそのまま使えるらしい.
この方法も試してみたので,記事の構成をちょっと変えた
追記2:さらに同じ方から教えていただいたのだけど,Raspberry Pi Pico C/C++ SDKのAPIとしてインラインで直接アクセスする関数が充実しているので,こちらを使う方が便利ですね.
これらの関数については,こちらのドキュメントを参照↓↓
https://www.raspberrypi.com/documentation/pico-sdk/gpio_8h.html
結果は次のとおり👍
SDK | 周波数 |
---|---|
Raspberry Pi Pico SDK | 50MHz(推定値) |
Arduino-Pico (digitalWrite() 使用) |
600kHz |
Arduino-Pico (IOレジスタ直接アクセス) | 60MHz(推定値) |
Arduino-Pico (gpio_put() 使用) |
60MHz(推定値) |
MicroPython | 88k~120Hz |
ふたつの方法
最初に思いついたのが「直接レジスタにアクセス」による高速化だった.単純にハードにアクセスするので速くなる.
その後,この記事を投稿したあとで,Raspberry Pi Pico C/C++ SDKのAPIがそのまま使えることを教えていただいたので,最後の節にそれを追加した.
直接レジスタにアクセス
『Raspberry Pi Pico(ラズピコ):各種SDKによるIO速度の比較』の記事の「あとは..」の節に書いたとおり,ラズピコに搭載されているマイコン・チップ:RP2040のデータシートには,IOがマップされている場所が「2.3.1.7. List of Registers」に書かれている.これによるとIOのベース・アドレスが0xD0000000,出力のオフセットが0x010になってるのでここに値を書き込めば,ピン出力の状態を変更できる.
ラズピコのpin1(GPIOの0番)はこのレジスタのビット0にマップされているので,アドレス「0xD0000000 + 0x010」に「0x1」と「0x0」を交互に書き込めばピンがパタパタ(HIGHとLOWをトグル)する.
この「レジスタ直接書き込み」はこんな感じで書ける.
volatile uint32_t *GPIO_OUTPORT = (uint32_t *)(0xD0000010);
void setup() {
pinMode(0, OUTPUT);
}
void loop() {
*GPIO_OUTPORT = 0x1;
*GPIO_OUTPORT = 0x0;
}
自前でループさせてみる
細いパルスが400nsちょっとの間隔で出ている.この400nsはloop()
がコールされるオーバーヘッド分と思われる.
これを確認するために自前のループを作って実行してみる.そうすると..
volatile uint32_t *GPIO_OUTPORT = (uint32_t *)(0xD0000010);
void setup() {
pinMode(0, OUTPUT);
}
void loop() {
for (;;) { // 自前のループ
*GPIO_OUTPORT = 0x1;
*GPIO_OUTPORT = 0x0;
}
}
コードで周波数を下げてみる
パルスが間欠ではなくて連続して出ているっぽいのだけど,残念ながらこのオシロスコープでの測定限界(30MHzまで)を超えていて,うねった波が見えるだけ.
なので同じレジスタアクセスを複数書いて周波数を下げてみる.
volatile uint32_t *GPIO_OUTPORT = ( uint32_t *)(0xD0000010);
void setup() {
pinMode(0, OUTPUT);
}
void loop() {
for (;;) {
*GPIO_OUTPORT = 0x1;
*GPIO_OUTPORT = 0x1;
*GPIO_OUTPORT = 0x1;
*GPIO_OUTPORT = 0x1;
*GPIO_OUTPORT = 0x1;
*GPIO_OUTPORT = 0x1;
*GPIO_OUTPORT = 0x1;
*GPIO_OUTPORT = 0x1;
*GPIO_OUTPORT = 0x1;
*GPIO_OUTPORT = 0x1;
*GPIO_OUTPORT = 0x0;
*GPIO_OUTPORT = 0x0;
*GPIO_OUTPORT = 0x0;
*GPIO_OUTPORT = 0x0;
*GPIO_OUTPORT = 0x0;
*GPIO_OUTPORT = 0x0;
*GPIO_OUTPORT = 0x0;
*GPIO_OUTPORT = 0x0;
*GPIO_OUTPORT = 0x0;
*GPIO_OUTPORT = 0x0;
}
}
gpio_put()
を使ってみる
Raspberry Pi Pico C/C++ SDKのAPI,gpio_put()
がそのまま使えるというのを教えていただいたので,それも試してみた.
この実験も上記と同じく周波数を下げて試してみた.
void setup() {
pinMode(0, OUTPUT);
}
void loop() {
for (;;) {
gpio_put(0, 1);
gpio_put(0, 1);
gpio_put(0, 1);
gpio_put(0, 1);
gpio_put(0, 1);
gpio_put(0, 1);
gpio_put(0, 1);
gpio_put(0, 1);
gpio_put(0, 1);
gpio_put(0, 1);
gpio_put(0, 0);
gpio_put(0, 0);
gpio_put(0, 0);
gpio_put(0, 0);
gpio_put(0, 0);
gpio_put(0, 0);
gpio_put(0, 0);
gpio_put(0, 0);
gpio_put(0, 0);
gpio_put(0, 0);
}
}
直接レジスタ・アクセスと同じ結果が得られた!
まとめ
周波数を下げたいずれの実験結果でも,約6MHzは出ている.これらのコードでは同じ代入を10回書いて周波数を落としたので,この10倍の60MHzは出るものと想像できる.
今回の結果は「Raspberry Pi Pico SDK」を使った時の50MHz(推定値)よりも速い.これはチップ内部のクロック設定などの違いが現れているのかもしれない.
では,直接レジスタ・アクセスとgpio_put()
ではどちらを使う方がいいかというと,どちらでもいいと思う.ハードを直接触るよりもAPI経由の方がコードが読みやすくなるかもしれない.
一方,直接レジスタ・アクセスは複数のビットを同時に操作できるし,別に用意されているレジスタでは細かいビット操作なども可能そうなので,そのような用途には有効かも.
追記
上記の最後の一文を消した.というのも『複数ビット操作ならvoid gpio_put_masked (uint32_t mask, uint32_t value)
が使える』と,さらに『インラインで同じ直接アクセスを行う便利な関数がたくさんあります』と,またしても教えてもらったから.
Arduino-PicoでRaspberry Pi Pico C/C++ SDKのGPIO関連のAPIドキュメントへのリンクも教えていただいた.ありがたい.
これだけあればレジスタに直接アクセスする必要は無さそうに思う.
@FremenHlideさん,ありがとうございました!