PicoのPIOを検証
Raspberry Pi PicoにはProgrammable I/O(PIO)という新しいハードウェアがあります。
PIOはアセンブリっぽいコードでシステムクロックの速度で動作するとのことですのでシステムクロック133MHzのPicoだと66.5MHzの矩形波が出力できることになります。ホントかな?ということで検証してみました。
PIOのプログラミング自体は中々難しいのでサンプルプログラム”pico-examples”のpioサンプルを動かしてみます。
“pico-examples”のpicoサンプルはいくつかありますが、そのうちの”pwm”サンプルを使ってみます。
40 41 42 43 44 45 46 47 48 49 50 |
pwm_program_init(pio, sm, offset, PICO_DEFAULT_LED_PIN); pio_pwm_set_period(pio, sm, (1u << 16) - 1); int level = 0; while (true) { printf("Level = %d\n", level); pio_pwm_set_level(pio, sm, level * level); level = (level + 1) % 256; sleep_ms(10); } #endif |
細かくはわかりませんがざっくり見るとシステムクロックを2^16(=65536)分周した矩形波を10msおきに256階調で変化するPWM信号を出力してLEDを光らせているようです。
実際に動作させると2.56sec周期でLEDが暗→明に変化します。
さらにもう少し具体的にそのパルス周期を測ってみます。
ロジアナとかないのでSTマイクロのNucleoボードを使いTimerのインプットキャプチャー機能でパルス幅を測定してみました(エッジ時間を測定してエッジ間隔を算出)。
Nucleoボードの設定・コードの説明は省きますが、概略は以下のとおりです。
- ボード機種名:Nucleo-F411RE
- システムクロック:100MHz
- インプットキャプチャー入力端子:TIM2CH1
- TIM2設定:プリスケーラーなし(分割無し)、周期最大、キャプチャー極性:両エッジ
また元の”pwm”サンプルのLED出力だと信号が取り出しづらいのでGP2(4ピン)に出力するようにして、それをNucleoのTIM2CH1(PA0)とつなぎます。
1 2 3 4 |
int main() { stdio_init_all(); #define PICO_DEFAULT_LED_PIN 2 #ifndef PICO_DEFAULT_LED_PIN |
結果は以下のようになりました。
100MHzで計測しているので単位は10nsecです。段々奇数番目の数字が大きくなっていきます。これはHighレベルの時間が増えていっている→LEDが明るくなる、ということです。
周期は、例えば1570.84usec + 2.42usec = 1573.26usecなのでPWM周波数は約635.6Hzとなります。
picoのシステムクロックは133MHzなので133MHz/(2^16)=133MHz/65536≒2029.4Hz、あれ?合わないな、ということで調べてみました。
まずpicoのシステムクロックは133MHzでなく125MHzのようです。
これはpico-sdk\src\rp2_common\hardware_clocks\clocks.cで設定されています。
1 2 3 4 5 6 7 8 9 10 11 |
/// \tag::pll_settings[] // Configure PLLs // REF FBDIV VCO POSTDIV // PLL SYS: 12 / 1 = 12MHz * 125 = 1500MHZ / 6 / 2 = 125MHz // PLL USB: 12 / 1 = 12MHz * 40 = 480 MHz / 5 / 2 = 48MHz /// \end::pll_settings[] /// \tag::pll_init[] pll_init(pll_sys, 1, 1500 * MHZ, 6, 2); pll_init(pll_usb, 1, 480 * MHZ, 5, 2); /// \end::pll_init[] |
サンプル作成者は少し余裕を持たせたのと10進数的に割りやすい値を選んだのでしょう。
また実際にPWMを生成しているpwm.pioを見ると
1 2 3 4 5 6 7 8 9 10 11 12 13 |
.program pwm .side_set 1 opt pull noblock side 0 ; Pull from FIFO to OSR if available, else copy X to OSR. mov x, osr ; Copy most-recently-pulled value back to scratch X mov y, isr ; ISR contains PWM period. Y used as counter. countloop: jmp x!=y noset ; Set pin high if X == Y, keep the two paths length matched jmp skip side 1 noset: nop ; Single dummy cycle to keep the two paths the same length skip: jmp y-- countloop ; Loop until Y hits 0, then pull a fresh PWM value from FIFO |
よくわかっていませんがcountloop:以下をループしてPWMを生成しているようです。x!=yのときは8行目→11行目→13行目→8行目と推移してx==yのときは8行目→9行目→13行目→8行目と推移するのかな。11行目はどちらになっても3サイクルになるようにするためのダミーサイクルになります。
結果として125MHz / 3cycle / (2^16) ≒ 635.8Hzとなり実測結果と一致します。多少の誤差はnucleoとpicoそれぞれのクロック精度の差だと思われます。
それにしても、1 / (125MHz / 3) = 24nsec 単位のPWM出力が任意に作れるのはすごいですね。PWMはともかくもう少し高度なインターフェイスを実装したいとき低速な動作ならGPIOを個別に動かせば何とかなりますが(bit-bangingというらしい)、高速な動作だと今までだったら、専用ICを使うか、そのインターフェイスを持っているマイコンを他のことを目をつぶって使うか、FPGAで実現するか、とかだったと思いますが、それがpicoでできるのは用途が広がります。まだpioプログラミングは全然わかっていませんが…
終わり