2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Raspberry Pi Pico でLチカ

Posted at

この記事の目的

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

ピンヘッダがあらかじめ取り付けられた Raspberry Pi Pico H というバージョンも存在する。

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 と主張しているが、CyberChefCRC32ハッシュ値計算 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 を点滅させることができた。

LEDが点滅する様子

まとめ

Raspberry Pi Pico (RP2040) で実行できるプログラムの仕様と書き込み方を確認し、実際にLチカを行うプログラムを書き込んで実行できた。

レジスタなどの詳細な定義は、データシートを参照してほしい。

参考資料

執筆時点において、秋月電子通商のページでも RP2040 のデータシートが掲載されているが、bootrom の V3 への言及が無いなど古いようである。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?