この記事の目的
Raspberry Pi Pico およびそこで使用されているCPUである RP2040 の仕様 (プログラムの書き込み方など) を確認し、Lチカ (Raspberry Pi Pico に標準搭載されたLEDを点滅させる) を行う。
Raspberry Pi Pico とは
Raspberry Pi Pico は、RP2040 というCPUを搭載したマイコンボードである。
デュアルコアで 133MHz までのクロックに対応し、RAM は 264kB (CPUの仕様)、プログラムを置けるフラッシュメモリは 2MB (マイコンボードの仕様) である。
GPIO は 26+1(基板上のLED) 本が利用できる。
デュアルコアではあるが、システムが2個目のコアをスリープさせた状態でユーザープログラムを実行するため、2個目のコアを起動させなければ通常の (シングルコアの) マイコンとして利用可能である。
秋月電子通商 (販売コード:116132)、千石電商、マルツ、スイッチサイエンス などで販売されている。
ピンヘッダがあらかじめ取り付けられた Raspberry Pi Pico H というバージョンも存在する。
命令セット
Raspberry Pi Pico (RP2040) は、IchigoJam (LPC1114) のマシン語と同じ命令セットを実行することができる。
そのため、たとえば asm15 を用いてプログラムを開発することができる。
この記事では、これを用いてプログラムを開発する。
実行できる命令は同じであるが、一部の命令の実行にかかるクロック数は異なる。
※IchigoJamはjig.jpの登録商標です。
メモリマップ
RP2040 のメモリ空間は、以下のように割り当てられている。(主なものを示す)
先頭アドレス | サイズ | 役割 |
---|---|---|
0x00000000 | 16kB | Bootrom |
0x10000000 | 16MB | フラッシュメモリ |
0x20000000 | 264kB | SRAM (~0x20041fff) |
0x40000000 | - | ペリフェラル |
0xd0000000 | - | GPIOなど |
RP2040 で対応しているフラッシュメモリの最大容量は 16MB であるが、実際に接続されアクセス可能なフラッシュメモリの容量はそれより少ない可能性がある。
Raspberry Pi Pico におけるフラッシュメモリの容量は 2MB である。
プログラムの書き込み方法
Raspberry Pi Pico の BOOTSEL ボタンを押しながらUSBでパソコンに接続すると、USBメモリとして認識される。
ここに UF2 と呼ばれるフォーマットのファイルを書き込むことで、プログラムの書き込みを行う。
初期状態など、フラッシュメモリに有効なプログラムが書き込まれていない場合は、BOOTSEL ボタンを押さずに接続してもUSBメモリとして認識されることがある。
RP2040 に書き込む用の UF2 は、以下の512バイトのブロックを並べたものである。
値はリトルエンディアンである。
オフセット | バイト数 | 値 | 解説 |
---|---|---|---|
0 | 4 | 0x0a324655 | マジックナンバー |
4 | 4 | 0x9e5d5157 | マジックナンバー |
8 | 4 | 0x00002000 | フラグ (family ID あり) |
12 | 4 | 可変 | データの書き込み先アドレス |
16 | 4 | 256 | このブロックの有効なデータサイズ |
20 | 4 | 可変 | ブロック番号 (最初のブロックが 0) |
24 | 4 | 可変 | このファイルに格納されているブロックの数 |
28 | 4 | 0xe48bff56 | family ID |
32 | 256 | 可変 | 書き込むデータ |
288 | 220 | ゼロ | データ用領域の余り |
508 | 4 | 0x0ab16f30 | マジックナンバー |
フラッシュメモリに書き込む際は、書き込み先アドレスは 0x10000000~0x10ffff00 の範囲で、256 (0x100) の倍数でなければならない。
また、フラッシュメモリの消去は 4kB 単位で行われるため、最後以外は 4kB のブロック全体のデータ (256バイト×16ブロック) を連続で配置しなければならない。
(RP2040のデータシート 2.8.4.2. UF2 Format Details を参照)
Lチカ
この章では、実際に asm15 を用いて Raspberry Pi Pico でLチカを行うプログラムを作成する。
UF2 形式のパラメータを設定する
RP2040 の仕様に合わせ、UF2 形式のファイルを出力する際の family ID を 0xe48bff56
に、ペイロードのブロックサイズを 256 バイトに設定する。
family ID は UF2FAMILY
疑似命令で、ブロックサイズは UF2BLOCK
疑似命令で設定できる。
UF2FAMILY
は1個の引数をとり、family ID として設定する数値または NONE
(family ID なし) を指定する。
UF2FAMILY #E48BFF56
UF2BLOCK
は、最初の引数でブロックサイズ (バイト数) を、最後の引数でデータが足りない部分を埋めるバイトの値を設定する。
UF2BLOCK 256,#FF
セクタサイズ (バイト数) を指定する2番目の引数を追加しても良い。今回は連続した領域のみを扱い、セクタの考慮は不要であるので、用いない。
UF2BLOCK 256,4096,#FF
また、UF2BLOCK NONE
とすることで、ブロックサイズとセクタサイズの指定を解除できる。(これは、ブロックサイズとセクタサイズをともに 1 バイトにすることに相当する)
有効なプログラムとして認識させる
書き込んだデータを有効なプログラムとして認識させ、実行してもらうためには、先頭 252 バイト (すなわち、アドレス 0x10000000~0x100000fb) の CRC32_MPEG2
チェックサムをその次の 4 バイト (すなわち、アドレス 0x100000fc~0x100000ff) にリトルエンディアンで書き込んでおかなければならない。
チェックサムが正しい場合、BOOTSEL ボタンを押さずに電源を投入すると、アドレス 0x10000000 に書き込まれているプログラムから実行が開始される。
以下のように記述することで、このチェックサムを配置できる。
ORG #100000FC
CHECKSUM #10000000,252,CRC32MPEG2,#FF
また、ORG
疑似命令を用い、プログラムを配置する位置を指定する。
ORG #10000000
データシートでは CRC32 checksum と主張しているが、CyberChef や CRC32ハッシュ値計算 Online - DenCode などで計算できる一般的な CRC32 を置いても認識しなかった。
データシートに書かれているチェックサムのパラメータと Sunshine's Homepage - Online CRC Calculator Javascript で用意されているパラメータを見比べると、CRC32_MPEG2 のパラメータが一致していた。
この CRC32_MPEG2 は、PNGで使うCRC32を計算する #crc - Qiita で紹介したアルゴリズムに以下の変更を加えることで計算できるようである。
- マジックナンバーとして
0x04c11db7
を (反転せずに) 用いる - データを下位ではなく上位に投入 (XOR) する
- 右シフトではなく左シフトを行う
- 最後のビット反転 (NOT) をしない
(RP2040のデータシート 2.8.1.3.1. Checksum および 2.8.4.2. UF2 Format Details を参照)
GPIO 設定レジスタのリセットを解除する
RP2040 の GPIO を設定してLチカ用の信号を出力させるためには、GPIO 設定用のレジスタを用いる。
このレジスタは、初期状態ではリセットがかかっており、操作を受け付けない。
そこで、まずはリセットを解除し、操作ができるようにする。
アドレス 0x4000c000 に、GPIO を含むペリフェラルのリセットを行うかを設定する4バイトのレジスタがある。
このレジスタの下から 5 ビット目 (0-origin) が、IO_BANK0
すなわち GPIO 設定用レジスタのリセットを設定するフラグである。
このビットが 1 であればリセット、0 であればリセット解除である。
まずこのレジスタの値を読み取り、BIC
命令で下から 5 ビット目を 0 にし、書き戻すことで、GPIO 設定用レジスタのリセット解除を指示する。
' IO_BANK0 のリセットを解除する
R3 = [@RESETS_ADDR]L
R0 = [R3]L
R1 = `100000
BIC R0, R1
[R3]L = R0
リセット解除を指示したら、リセットが完了するまで待機する。
アドレス 0x4000c008 から4バイトのレジスタが、ペリフェラルのリセットが完了したかを表す。
下から 5 ビット目 (0-origin) が IO_BANK0
のリセットが完了したかを表しており、1 であればリセットが完了している。
以下のコードでは、リセット解除のためのビットクリアに用いた R1
レジスタを、引き続きリセット完了の判定に用いている。
' IO_BANK0 の準備が完了するまで待つ
@RESET_WAIT
R0 = [R3 + 2]L
R0 & R1
IF 0 GOTO @RESET_WAIT
この処理で用いるレジスタのアドレスを、データとして用意する。
@RESETS_ADDR
UDATAL #4000C000
(RP2040のデータシート 2.14. Subsystem Resets を参照)
クロックを分周して出力する設定を行う
RP2040 には、指定のクロックを分周した信号を生成するクロックジェネレーターがいくつか搭載されている。
今回は、その中の一つである clk_gpout3
を設定し、システムクロック (clk_sys
) を分周した信号を出力させる。
clk_sys
は、初期状態では clk_ref
を参照しており、clk_ref
はリングオシレーターを参照している。
リングオシレーターの周波数は 4~8MHz 程度である。(RP2040のデータシート 2.15.2.1. Ring Oscillator より)
よって、初期状態の clk_sys
の周波数も 4~8MHz 程度である。
そこで、この信号を 4,000,000 分の1に分周し、1~2Hz 程度の信号を得る。
clk_gpout3
の分周比は、アドレス 0x40008028 から4バイトのレジスタで設定する。
整数の分周比を設定する場合、このレジスタの上位24ビットに書き込む。
このレジスタの下位8ビットは分周比の小数点以下の設定であり、整数に設定する場合は 0 を書き込む。
R3 = [@CLK_GPOUT3_ADDR]L
' clk_gpout3 の分周比を 4,000,000 分の1に設定する
R0 = 20
R1 = 100
R0 *= R1
R0 *= R0
R0 = R0 << 8
[R3 + 1]L = R0
アドレス 0x40008024 から4バイトのレジスタで、clk_gpout3
が参照する (入力として受け取る) クロックの選択と、clk_gpout3
の有効化を行う。
参照するクロックの選択は、下から 5 ビット目 (0-origin) からの4ビットに番号を書き込む。clk_sys
を選択する場合は 6
を書き込む。
下から 11 ビット目 (0-origin) に 1
を書き込むことで、clk_gpout3
を有効にできる。
' clk_gpout3 のソースに clk_sys を設定し、有効化する
R0 = `100_0110
R0 = R0 << 5
[R3]L = R0
この処理で用いるレジスタのアドレスを、データとして用意する。
@CLK_GPOUT3_ADDR
UDATAL #40008024
(RP2040のデータシート 2.15. Clocks を参照)
クロックの出力をピンに反映する設定を行う
clk_gpout3
に適当な速さの信号が出力されるように設定したので、次にこの信号をピンに出力する設定を行う。
RP2040 のデータシートの 2.19.2. Function Select に載っている表を見ると、この clk_gpout3
(CLOCK GPOUT3) の信号は GPIO25 に出力することができ、その機能番号は 8
であることがわかる。
GPIO25 は、Raspberry Pi Pico では標準搭載された LED に繋がっており、Lチカをするのに都合が良い。
アドレス 0x400140cc からの4バイトに、GPIO25 の設定を行うレジスタがある。
このレジスタの下位 5 ビットに機能番号を書き込むことで、GPIO25 のピンに割り当てる機能を選択できる。
このレジスタのそれ以外のビットは、予約または 0 を書き込むことで「通常動作」に設定するものであるため、0 としておいてよいと考えられる。
R3 = [@GPIO25_ADDR]L
' GPIO25 の機能を CLOCK GPOUT3 に設定する
R0 = 8
[R3 + 1]L = R0
この処理で用いるレジスタのアドレスを、データとして用意する。
@GPIO25_ADDR
UDATAL #400140C8
(RP2040のデータシート 2.19. GPIO を参照)
CPU の処理を停止する
今回は、設定を行えばあとはハードウェアで自動で信号が出力されるので、CPU にはもうするべきことが無い。
そこで、WFI
命令を実行し、スリープモードに入る。
スリープが解除されてもいいように、ループで繰り返し実行するようにする。
' 何もしない
@HALT_LOOP
WFI
GOTO @HALT_LOOP
(RP2040のデータシート 2.4.2.8. Power Management Unit を参照)
プログラム全体
UF2FAMILY #E48BFF56
UF2BLOCK 256,#FF
ORG #10000000
' IO_BANK0 のリセットを解除する
R3 = [@RESETS_ADDR]L
R0 = [R3]L
R1 = `100000
BIC R0, R1
[R3]L = R0
' IO_BANK0 の準備が完了するまで待つ
@RESET_WAIT
R0 = [R3 + 2]L
R0 & R1
IF 0 GOTO @RESET_WAIT
R3 = [@CLK_GPOUT3_ADDR]L
' clk_gpout3 の分周比を 4,000,000 分の1に設定する
R0 = 20
R1 = 100
R0 *= R1
R0 *= R0
R0 = R0 << 8
[R3 + 1]L = R0
' clk_gpout3 のソースに clk_sys を設定し、有効化する
R0 = `100_0110
R0 = R0 << 5
[R3]L = R0
R3 = [@GPIO25_ADDR]L
' GPIO25 の機能を CLOCK GPOUT3 に設定する
R0 = 8
[R3 + 1]L = R0
' 何もしない
@HALT_LOOP
WFI
GOTO @HALT_LOOP
ALIGN 4
@RESETS_ADDR
UDATAL #4000C000
@CLK_GPOUT3_ADDR
UDATAL #40008024
@GPIO25_ADDR
UDATAL #400140C8
ORG #100000FC
CHECKSUM #10000000,252,CRC32MPEG2,#FF
実行結果
Raspberry Pi Pico H にこのプログラムを書き込んで実行し、LED を点滅させることができた。
まとめ
Raspberry Pi Pico (RP2040) で実行できるプログラムの仕様と書き込み方を確認し、実際にLチカを行うプログラムを書き込んで実行できた。
レジスタなどの詳細な定義は、データシートを参照してほしい。
参考資料
-
Buy a Raspberry Pi Pico – Raspberry Pi:Documents として、以下の資料などがある
- 「Raspberry Pi Pico 1 datasheet」:ボードのピン配置など
- 「Raspberry Pi RP2040 datasheet」:CPUのペリフェラルなど
- GitHub - microsoft/uf2: UF2 file format specification:書き込み時に用いる UF2 形式の定義がある
執筆時点において、秋月電子通商のページでも RP2040 のデータシートが掲載されているが、bootrom の V3 への言及が無いなど古いようである。