commit 3c6cf67f41f3205baa05c9d714d3124f2aad9a32
parent b62d9eec09ab894ea91feb9248c8599a709361fe
Author: Matsuda Kenji <info@mtkn.jp>
Date: Mon, 24 Apr 2023 12:11:07 +0900
update
Diffstat:
3 files changed, 243 insertions(+), 78 deletions(-)
diff --git a/man/draft/rp2040_1.html b/man/draft/rp2040_1.html
@@ -1,4 +1,4 @@
-<h1>RP2040 アセンブリでLチカ</h1>
+<h1>RP2040 SDKなしでLチカ</h1>
<time>2023-04-19</time>
<h2>はじめに</h2>
@@ -16,17 +16,40 @@ RP2040マイコンボードキット<sup>[2]</sup>というものである。\
一般的にはSDK<sup>[4]</sup>をダウンロードしてあらかじめ用意された\
ライブラリを使って開発するようだが、これはビルドシステムとしてcmake\
というのを使っている。これがOpenBSDでは何かエラーがでて動かなかった。\
-僕はこういう便利ツールが嫌いだ。どうせ使わんからいいんやけど。
+僕はこういう便利ツールが嫌いだ。どうせ使わんからいいんやけど。\
+関係ないけど途中から開発環境がLinuxに替わった。\
+SDKには便利な関数がたくさん用意されているので楽である。\
+ハードウェアの面倒な部分がプログラマから見えないようにしているからである。\
+しかし今回はその面倒な部分に触れてみたくて買ったので、SDKを使うと意味がない。\
</p>
<p>
-ということでアセンブリ言語で開発してみる。\
-とりあえず定番のLチカをば。
+ということでSDKなしで開発してみる。\
+とりあえず定番のLチカをば。\
</p>
+<h2>動作環境</h2>
+<ul>
+<li>Arch Linux 6.2.12-arch1-1
+ <ul>
+ <li>arm-none-eabi-binutils 2.40-1</li>
+ <li>GNU Make 4.4.1</li>
+ </ul>
+</li>
+<li>OpenBSD 7.3
+ <ul>
+ <li>arm-none-eabi-binutils 2.31.1</li>
+ <li>make (バージョン?)</li>
+ </ul>
+※<code>make flash</code>は動かん。<code>dmesg</code>でデバイス確認して手動で\
+マウントする必要がある。
+</li>
+</ul>
+
<h2>Boot Process</h2>
<p>
-RP2040は電源を入れるといくつかの段階(ここでは関係ないので\
-省略。データシートに詳しい<sup>[3]</sup>)を踏んだあと、外部のフラッシュROMの先頭から\
+RP2040は電源を入れるといくつかの段階(ここでは関係ないので省略。\
+データシート「2.8.1 Processor Controlled Boot Sequence」に詳しく書いてある)\
+を踏んだあと、外部のフラッシュROMの先頭から\
256バイトを内部のSRAMにコピーして、フラッシュにプログラムが書き込まれているか\
どうか確認する。RP2040はフラッシュの先頭252バイトから計算したCRC32チェックサム\
を、直後の253バイト目から256バイトに記録することになっている。\
@@ -34,20 +57,22 @@ RP2040は電源を入れるといくつかの段階(ここでは関係ないの
書き込まれているかどうか確かめている。コピーした最後の4バイトと\
起動時に最初の252バイトから計算したチェックサムが一致していれば、\
そのままコピーしてきた256バイトの先頭にPCをセットして実行を開始する。\
-一致しなければUSBモード?に切り替わり、パソコンに接続すると\
+一致しなければUSBデバイスモードに切り替わり、パソコンに接続すると\
ストレージとして認識される。このストレージにUF2という形式に変換した\
プログラムをコピーするとプログラムがフラッシュROMやSRAMに書き込まれる。
</p>
<p>
以上のことから、プログラムを実行するためにはCRC32を計算し、UF2という形式\
-に変換することが必要である。
+に変換することが必要である。ソースコードからの流れは以下の通り:
</p>
<pre>\
-source code ---------> object ------> elf -------->----->---> uf2
- compile link objcopy crc32 uf2
+source bin bin with
+code ---------> object ------> elf --------> bin -------> with --------> crc32 in
+ crc32 uf2 format
+ compile link objcopy bincrc bin2uf2
</pre>
-<h2>CRC32(巡回冗長検査)</h2>
+<h2>CRC(巡回冗長検査)</h2>
<h3>基本</h3>
<p>
入力のデータをごにょごにょしてある値を出力する。\
@@ -59,10 +84,11 @@ source code ---------> object ------> elf -------->----->---> uf2
らしい。
</p>
<p>
-入力のビットを一列に並べて、33bitの除数で「割り算」していく。\
-この「割り算」が多項式の除算に似ているので、この33bitの除数をCRC多項式というらしい。\
-ただし多項式の除算と違い、引き算するところをXORする。説明が下手である。\
-33bitで割ると32bitの余りが残る。この余りがCRC32のチェックサムである。\
+入力のビットを一列に並べて、除数で「割り算」していく。\
+この「割り算」が多項式の除算に似ているので、この除数をCRC多項式というらしい。\
+ただし多項式の除算と違い、引き算するところをXORする。\
+CRC32の場合、除数は33ビットである。\
+33ビットで割ると32ビットの余りが残る。この余りがCRC32のチェックサムである。\
除数は色々あるようだが、標準的なものがWikipedia<sup>[5]</sup>に列挙されている。\
除数<code>1011</code>を使ったCRC3の計算の手順は以下の通り:
</p>
@@ -118,13 +144,13 @@ source code ---------> object ------> elf -------->----->---> uf2
データの並べ方は、上の例において左側を先頭に、フラッシュROM上の0番地から順に、\
各バイトは最上位ビットから順に並べる。\
入力のデータは253バイト目から256バイト目に<code>0</code>をひっつけて計算する。\
-これは多分<code>for</code>ループを252回回す都合かな?\
+これは多分予め長さが分からないデータでも計算できるようにしたかったからかな。\
除数は<code>0x104c11db7</code>である(最上位ビットは常に1なのでデータシート\
では省略されている)。\
</p>
<p>
入力データは1バイトづつ処理したいみたいである。多分通信等で使う都合である。\
-この時XORは結合則が成り立つので1バイト処理した結果を8ビットシフトしたものと\
+この時XORは結合則が成り立つので1バイト処理した結果と\
次のバイトとをXORして次の処理の入力として利用することができる:
</p>
<pre><code>\
@@ -160,8 +186,8 @@ source code ---------> object ------> elf -------->----->---> uf2
<li>前回の結果と、入力データの次のバイトをXOR</li>
<li>
<ul>
- <li>先頭の1ビットが1の場合、除数とXORを取りシフト</li>
- <li>先頭の1ビットが0の場合、そのままシフト</li>
+ <li>先頭の1ビットが1の場合、除数とXORを取り左シフト</li>
+ <li>先頭の1ビットが0の場合、そのまま左シフト</li>
</ul>
</li>
</ul>
@@ -173,11 +199,10 @@ RP2040では<code>0xffffffff</code>を使う。\
更にこの工程を32ビットの<code>int</code>だけで行うことを考える:
</p>
<pre><code>\
-計算間違いしてる気がする。
111000111000000110000110111000111000001010010011111000111000000110010011 入力(適当)
-11111111111111111111111111111111 0xffffffff
-11100011000000000000000000000000 先頭1バイトを24ビットシフト
+11111111111111111111111111111111 0xffffffff
+11100011000000000000000000000000 先頭1バイトを24ビットシフト
-------------------------------- XOR
00011100111111111111111111111111
先頭1ビットが0なので1ビットシフト
@@ -248,7 +273,9 @@ crc32(uint8_t *idata, size_t len)
となるバイナリデータの先頭を、<code>len</code>として<code>252</code>を渡してCRC32\
を計算させる。その後、出力先のファイルに入力元のデータをコピーしていき、253バイト\
目から256バイト目だけ、計算したCRC32に置き換える。入力元のこの場所にデータが\
-書き込まれていないかどうかは確かめていない。\
+書き込まれていないかどうかは確かめていない。
+</p>
+
<h2>UF2(USB Flashing Format)</h2>
<p>
Microsoftが開発したフラッシュ書き込み用のファイル形式らしい:
@@ -323,7 +350,8 @@ over MSC (Mass Storage Class; aka removable flash drive)<sup>[6]</sup>.
</blockquote>
<p>
-RP2040のデータシート<sup>[3]</sup>を見ると、8バイト目のFlagsは28バイト目に\
+RP2040のデータシート<sup>[3]</sup>「2.8.4.2 UF2 Format Details」\
+を見ると、8バイト目のFlagsは28バイト目に\
ファミリーIDが書き込まれていることを示す<code>0x00002000</code>、\
12バイト目は書き込みを行うフラッシュROMの先頭アドレスである\
<code>0x10000000</code>、16バイト目の、各ブロックのデータサイズは256バイト、\
@@ -486,7 +514,7 @@ RP2040には内部にもROMがあり、中にはバージョン情報や、電
その他便利な関数が書き込まれている。この関数のなかに外部のフラッシュROMと\
SSIコントローラを設定するものも含まれているので今回はこれを利用した。\
ただしこの方法だとフラッシュROMとの通信方式がStandard SPIのままなので少し\
-遅い。詳しくはデータシート<sup>[3]</sup>の2.3.8. 「Bootrom Contents」を参照。
+遅い。詳しくはデータシートの「2.3.8. Bootrom Contents」を参照。
</p>
<p>
RP2040の内蔵ROMの<code>0x00000018</code>番地に関数を検索するための関数がある。\
@@ -606,7 +634,16 @@ RESETS: RESETレジスタのうち5番目のビットだけを<code>0x0</code>
</p>
<h4>リセット状態の確認</h4>
<p>\
-リセットの解除はすぐに完了するわけではないようである。
+リセットの解除はすぐに完了するわけではないようである。\
+リセットの解除が完了したかどうか確認するにはRESETS: RESET_DONE\
+レジスタ(RESETS_BASEから<code>0x8</code>バイト目)の該当するビット\
+(ここでは5番目のビット)を読む。この値が<code>0x1</code>であれば\
+リセットの解除が完了している。<code>0x0</code>であれば処理が進行中\
+なので<code>0x1</code>が返ってくるまで繰り返し読み込んで<code>0x0</code>\
+になるまで待機する。\
+ところでこのレジスタはリセットの解除が完了したかどうか確かめるもの\
+なので、RESET_DONEという名前はどうなのか。リセットが完了したかどうか\
+確かめるものではないのに。
<p>
以上から、GPIOのリセットを解除するのは以下のコード:
</p>
@@ -621,7 +658,7 @@ reset:
str r0, [r3, r1] // RESETS: RESET
reset_chk:
ldr r1, [r3, #0x8] // RESETS: RESET_DONE
- and r0, r0, r1
+ tst r0, r1
beq reset_chk
/* ... */
@@ -632,6 +669,24 @@ resets_base:
.word 0x4000c000
</code></pre>
+<h3>GPIOの機能の選択</h3>
+<p>\
+RP2040のGPIOにはそれぞれ複数の機能が用意されていて、どれを使うかは\
+ソフトウェアから選択できる。利用できる機能の一覧と各機能の説明は\
+データシートの「2.19.2 Function Select」に詳しく書いてある。\
+ここではGPIO25番のピンをSIO(Single-cycle IO)として利用する。\
+同じCPUが載っているRaspberry Pi PicoはGPIO25番にLEDが半田付けされている\
+ので、25番にした。他のピンでもいい。\
+GPIOに1(3.3V)か0を印加する場合はこのSIOを使うみたいである。\
+Single-cycleはCPUから操作したときに1クロックでその操作が完了する\
+という意味らしい。SIOの詳しい説明はデータシートの「2.3.1 SIO」にある。\
+</p>
+<p>
+GPIO25番の機能を選択するにはIO_BANK0_BASE(<code>0x40014000</code>)から\
+<code>0xcc</code>番目のGPIO25_CTRLレジスタの下位5ビットに、該当する機能\
+の番号を書き込めばいい。データシートの「2.19.2 Function Select」にある\
+表を見ると、GPIO25番のSIOは5である:\
+</p>
<pre><code>\
// set gpio functions
ldr r3, io_bank0_base
@@ -639,6 +694,21 @@ resets_base:
mov r1, #0xcc
str r0, [r3, r1] // IO_BANK0: GPIO25_CTRL
+/* ... */
+
+io_bank0_base:
+ .word 0x40014000
+</pre></code>
+
+<h3>GPIOの出力を有効化</h3>
+<p>
+GPIO25番がSIOになったので、次にこのピンからの出力を有効化する。\
+既定値では出力は無効になっている。ハイインピーダンスってことなのかな?\
+出力を有効にするには、SIO_BASE(<code>0xd0000000</code>)から\
+<code>0x24</code>バイト目のSIO: GPIO_OEレジスタの該当するビット\
+(25番のピンなので25番ビット)を<code>0x1</code>にする:
+</p>
+<pre><code>
// enable gpio output
ldr r3, sio_base
mov r0, #1
@@ -647,21 +717,42 @@ resets_base:
/* ... */
-io_bank0_base:
- .word 0x40014000
sio_base:
.word 0xd0000000
</code></pre>
<h3>LEDの点滅</h3>
+<p>\
+ここまででGPIOの設定は完了したので、あとは実際にLEDに電圧を掛けるだけである\
+(電圧を掛けるはこの漢字なんや...)。\
+レジスタのアドレスに<code>0x1000</code>を足したものに書き込むと\
+アトミックなレジスタのXORができると書いたが、SIOだけこの機能がサポート\
+されていないようである。データシートの「2.1.2 Atomic Register Access」に、
+</p>
+<blockquote cite="https://datasheets.raspberrypi.com/rp2040/rp2040-datasheet.pdf">
+<p>
+The SIO (Section 2.3.1), a single-cycle IO block attached directly to the cores'
+IO ports, does <strong>not</strong> support atomic accesses at the bus level,
+although some individual registers (e.g. GPIO) have set/clear/xor aliases.
+</p>
+</blockquote>
+<p>
+と書かれている。LEDを点滅させるためにGPIOの出力をトグルしたいので\
+SIO_BASE(<code>0xd0000000</code>)から<code>0x1c</code>バイト目の\
+SIO: GPIO_OUT_XORレジスタの25番ビットに<code>0x1</code>を書き込む。\
+出力をトグルした後は少し間をおいて同じことを繰り返す。\
+間を置くためにここでは適当な数値を1づつ減らしていって0になったら\
+リターンする関数<code>delay</code>を作った。\
+タイマーと割り込みを使ったほうが消費電力等で優位なようだが、\
+面倒なのでとりあえずこれで:\
+</p>
+
<pre><code>\
// blink led on gpio25
ldr r4, sio_base
- mov r5, r0
+ mov r5, r0 // r0 = 1 << 25
loop:
- str r5, [r4, #0x10] // SIO_GPIO_OUT_SET
- bl delay
- str r5, [r4, #0x18] // SIO_GPIO_OUT_CLR
+ str r5, [r4, #0x1c] // SIO: GPIO_OUT_XOR
bl delay
b loop
@@ -673,9 +764,8 @@ delay_loop:
bne delay_loop
bx lr
-// literals
- .align 2
/* ... */
+
sio_base:
.word 0xd0000000
</code></pre>
@@ -684,7 +774,7 @@ sio_base:
<p>
以上のコードには<code>.boot2</code>、<code>.vectors</code>、<code>.text</code>\
の3つのセクションが含まれる。<code>.boot2</code>はフラッシュの先頭から\
-256バイト(<code>0x100</code>)目まで、<code>.vectors</code>と<code>.text</code>\
+256(<code>0x100</code>)バイト目まで、<code>.vectors</code>と<code>.text</code>\
はその後ろに続くように配置する:
<pre><code>\
MEMORY
@@ -707,9 +797,11 @@ SECTIONS
</code></pre>
<h2>Makefile</h2>
-ディレクトリ構造:
+<p>
+以上のソースコードは以下のように配置している:
+</p>
<pre><code>\
-rp2040_baremetal
+rp2040
├── ex1
│ ├── Makefile
│ ├── boot2.s
@@ -721,7 +813,7 @@ rp2040_baremetal
└── bincrc.c
</code></pre>
<p>
-toolsディレクトリのMakefileは同じディレクトリのソースファイルを<code>cc</code>\
+toolsディレクトリのMakefileは同じディレクトリのソースファイルを<code>$(CC)</code>\
でコンパイルするだけのものである(個人的な趣味で<code>tcc</code>を使っている)。\
ex1ディレクトリのMakefileは以下の通り:
</p>
@@ -764,6 +856,22 @@ tools:
cd ../tools && make
</code></pre>
+<p>
+RP2040のボードをUSBデバイスモードでLinuxのパソコンに接続し、ex1ディレクトリで\
+</p>
+<pre><code>
+$ make
+# make flash
+</code></pre>
+<p>
+とすればプログラムがRP2040のボードに書き込まれて実行が開始される。\
+</p>
+
+<h2>最後に</h2>
+<p>
+光あれ。
+</p>
+
<h2>参考</h2>
<ul>
<li>
diff --git a/pub/draft/rp2040_1.html b/pub/draft/rp2040_1.html
@@ -5,7 +5,7 @@
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link rel="stylesheet" type="text/css" href="/style.css" />
<link rel="icon" type="image/x-icon" href="/pics/favicon.ico" />
- <title>RP2040 アセンブリでLチカ</title>
+ <title>RP2040 SDKなしでLチカ</title>
</head>
<body>
<header>
@@ -21,31 +21,48 @@
</header>
<main>
<article>
-<h1>RP2040 アセンブリでLチカ</h1>
+<h1>RP2040 SDKなしでLチカ</h1>
<time>2023-04-19</time>
<h2>はじめに</h2>
<p>
パタヘネのRISC-V<sup>[1]</sup>版を買って一通り読んだらアセンブリ言語で組込のプログラミングがしたくなった。RISC-Vのマイコンボードが欲しかったのだが、安くていい感じのものが見付からなかった。代わりに秋月電子通商でArmのものがあった。RP2040マイコンボードキット<sup>[2]</sup>というものである。ウェブ上の情報も多く、データシート<sup>[3]</sup>もしっかりしていそうなので、とりあえずこれを買ってみた。</p>
<p>
-一般的にはSDK<sup>[4]</sup>をダウンロードしてあらかじめ用意されたライブラリを使って開発するようだが、これはビルドシステムとしてcmakeというのを使っている。これがOpenBSDでは何かエラーがでて動かなかった。僕はこういう便利ツールが嫌いだ。どうせ使わんからいいんやけど。
-</p>
+一般的にはSDK<sup>[4]</sup>をダウンロードしてあらかじめ用意されたライブラリを使って開発するようだが、これはビルドシステムとしてcmakeというのを使っている。これがOpenBSDでは何かエラーがでて動かなかった。僕はこういう便利ツールが嫌いだ。どうせ使わんからいいんやけど。関係ないけど途中から開発環境がLinuxに替わった。SDKには便利な関数がたくさん用意されているので楽である。ハードウェアの面倒な部分がプログラマから見えないようにしているからである。しかし今回はその面倒な部分に触れてみたくて買ったので、SDKを使うと意味がない。</p>
<p>
-ということでアセンブリ言語で開発してみる。とりあえず定番のLチカをば。
-</p>
+ということでSDKなしで開発してみる。とりあえず定番のLチカをば。</p>
+
+<h2>動作環境</h2>
+<ul>
+<li>Arch Linux 6.2.12-arch1-1
+ <ul>
+ <li>arm-none-eabi-binutils 2.40-1</li>
+ <li>GNU Make 4.4.1</li>
+ </ul>
+</li>
+<li>OpenBSD 7.3
+ <ul>
+ <li>arm-none-eabi-binutils 2.31.1</li>
+ <li>make (バージョン?)</li>
+ </ul>
+※<code>make flash</code>は動かん。<code>dmesg</code>でデバイス確認して手動でマウントする必要がある。
+</li>
+</ul>
<h2>Boot Process</h2>
<p>
-RP2040は電源を入れるといくつかの段階(ここでは関係ないので省略。データシートに詳しい<sup>[3]</sup>)を踏んだあと、外部のフラッシュROMの先頭から256バイトを内部のSRAMにコピーして、フラッシュにプログラムが書き込まれているかどうか確認する。RP2040はフラッシュの先頭252バイトから計算したCRC32チェックサムを、直後の253バイト目から256バイトに記録することになっている。起動時にこのチェックサムを確認することで、フラッシュにプログラムが書き込まれているかどうか確かめている。コピーした最後の4バイトと起動時に最初の252バイトから計算したチェックサムが一致していれば、そのままコピーしてきた256バイトの先頭にPCをセットして実行を開始する。一致しなければUSBモード?に切り替わり、パソコンに接続するとストレージとして認識される。このストレージにUF2という形式に変換したプログラムをコピーするとプログラムがフラッシュROMやSRAMに書き込まれる。
+RP2040は電源を入れるといくつかの段階(ここでは関係ないので省略。データシート「2.8.1 Processor Controlled Boot Sequence」に詳しく書いてある)を踏んだあと、外部のフラッシュROMの先頭から256バイトを内部のSRAMにコピーして、フラッシュにプログラムが書き込まれているかどうか確認する。RP2040はフラッシュの先頭252バイトから計算したCRC32チェックサムを、直後の253バイト目から256バイトに記録することになっている。起動時にこのチェックサムを確認することで、フラッシュにプログラムが書き込まれているかどうか確かめている。コピーした最後の4バイトと起動時に最初の252バイトから計算したチェックサムが一致していれば、そのままコピーしてきた256バイトの先頭にPCをセットして実行を開始する。一致しなければUSBデバイスモードに切り替わり、パソコンに接続するとストレージとして認識される。このストレージにUF2という形式に変換したプログラムをコピーするとプログラムがフラッシュROMやSRAMに書き込まれる。
</p>
<p>
-以上のことから、プログラムを実行するためにはCRC32を計算し、UF2という形式に変換することが必要である。
+以上のことから、プログラムを実行するためにはCRC32を計算し、UF2という形式に変換することが必要である。ソースコードからの流れは以下の通り:
</p>
-<pre>source code ---------> object ------> elf -------->----->---> uf2
- compile link objcopy crc32 uf2
+<pre>source bin bin with
+code ---------> object ------> elf --------> bin -------> with --------> crc32 in
+ crc32 uf2 format
+ compile link objcopy bincrc bin2uf2
</pre>
-<h2>CRC32(巡回冗長検査)</h2>
+<h2>CRC(巡回冗長検査)</h2>
<h3>基本</h3>
<p>
入力のデータをごにょごにょしてある値を出力する。<blockquote cite="https://ja.wikipedia.org/wiki/%E5%B7%A1%E5%9B%9E%E5%86%97%E9%95%B7%E6%A4%9C%E6%9F%BB">
@@ -56,7 +73,7 @@ RP2040は電源を入れるといくつかの段階(ここでは関係ないの
らしい。
</p>
<p>
-入力のビットを一列に並べて、33bitの除数で「割り算」していく。この「割り算」が多項式の除算に似ているので、この33bitの除数をCRC多項式というらしい。ただし多項式の除算と違い、引き算するところをXORする。説明が下手である。33bitで割ると32bitの余りが残る。この余りがCRC32のチェックサムである。除数は色々あるようだが、標準的なものがWikipedia<sup>[5]</sup>に列挙されている。除数<code>1011</code>を使ったCRC3の計算の手順は以下の通り:
+入力のビットを一列に並べて、除数で「割り算」していく。この「割り算」が多項式の除算に似ているので、この除数をCRC多項式というらしい。ただし多項式の除算と違い、引き算するところをXORする。CRC32の場合、除数は33ビットである。33ビットで割ると32ビットの余りが残る。この余りがCRC32のチェックサムである。除数は色々あるようだが、標準的なものがWikipedia<sup>[5]</sup>に列挙されている。除数<code>1011</code>を使ったCRC3の計算の手順は以下の通り:
</p>
<pre><code>1110101011011100110101101101111 入力(適当)
1011 除数(4bit)
@@ -104,9 +121,9 @@ RP2040は電源を入れるといくつかの段階(ここでは関係ないの
<h3>細かいこと</h3>
<p>
-以上の計算をプログラムの先頭252バイトに対して、33bitの除数を用いて行う。データの並べ方は、上の例において左側を先頭に、フラッシュROM上の0番地から順に、各バイトは最上位ビットから順に並べる。入力のデータは253バイト目から256バイト目に<code>0</code>をひっつけて計算する。これは多分<code>for</code>ループを252回回す都合かな?除数は<code>0x104c11db7</code>である(最上位ビットは常に1なのでデータシートでは省略されている)。</p>
+以上の計算をプログラムの先頭252バイトに対して、33bitの除数を用いて行う。データの並べ方は、上の例において左側を先頭に、フラッシュROM上の0番地から順に、各バイトは最上位ビットから順に並べる。入力のデータは253バイト目から256バイト目に<code>0</code>をひっつけて計算する。これは多分予め長さが分からないデータでも計算できるようにしたかったからかな。除数は<code>0x104c11db7</code>である(最上位ビットは常に1なのでデータシートでは省略されている)。</p>
<p>
-入力データは1バイトづつ処理したいみたいである。多分通信等で使う都合である。この時XORは結合則が成り立つので1バイト処理した結果を8ビットシフトしたものと次のバイトとをXORして次の処理の入力として利用することができる:
+入力データは1バイトづつ処理したいみたいである。多分通信等で使う都合である。この時XORは結合則が成り立つので1バイト処理した結果と次のバイトとをXORして次の処理の入力として利用することができる:
</p>
<pre><code>111000111000000110000110111000111000001010010011111000111000000110010011 入力(適当)
|......|
@@ -139,19 +156,18 @@ RP2040は電源を入れるといくつかの段階(ここでは関係ないの
<li>前回の結果と、入力データの次のバイトをXOR</li>
<li>
<ul>
- <li>先頭の1ビットが1の場合、除数とXORを取りシフト</li>
- <li>先頭の1ビットが0の場合、そのままシフト</li>
+ <li>先頭の1ビットが1の場合、除数とXORを取り左シフト</li>
+ <li>先頭の1ビットが0の場合、そのまま左シフト</li>
</ul>
</li>
</ul>
<p>
これを<code>for</code>ループで回す都合上、最初のバイトもXORを取る。上の例では最初は<code>0x0</code>とXORを取っているが、この値を<code>0x0</code>以外にすることもできる。そうした方がいろいろいいこともあるらしい。RP2040では<code>0xffffffff</code>を使う。更にこの工程を32ビットの<code>int</code>だけで行うことを考える:
</p>
-<pre><code>計算間違いしてる気がする。
-111000111000000110000110111000111000001010010011111000111000000110010011 入力(適当)
+<pre><code>111000111000000110000110111000111000001010010011111000111000000110010011 入力(適当)
-11111111111111111111111111111111 0xffffffff
-11100011000000000000000000000000 先頭1バイトを24ビットシフト
+11111111111111111111111111111111 0xffffffff
+11100011000000000000000000000000 先頭1バイトを24ビットシフト
-------------------------------- XOR
00011100111111111111111111111111
先頭1ビットが0なので1ビットシフト
@@ -216,7 +232,10 @@ crc32(uint8_t *idata, size_t len)
}
</code></pre>
<p>
-<code>main()</code>関数では上の<code>crc32()</code>に、<code>idata</code>として入力となるバイナリデータの先頭を、<code>len</code>として<code>252</code>を渡してCRC32を計算させる。その後、出力先のファイルに入力元のデータをコピーしていき、253バイト目から256バイト目だけ、計算したCRC32に置き換える。入力元のこの場所にデータが書き込まれていないかどうかは確かめていない。<h2>UF2(USB Flashing Format)</h2>
+<code>main()</code>関数では上の<code>crc32()</code>に、<code>idata</code>として入力となるバイナリデータの先頭を、<code>len</code>として<code>252</code>を渡してCRC32を計算させる。その後、出力先のファイルに入力元のデータをコピーしていき、253バイト目から256バイト目だけ、計算したCRC32に置き換える。入力元のこの場所にデータが書き込まれていないかどうかは確かめていない。
+</p>
+
+<h2>UF2(USB Flashing Format)</h2>
<p>
Microsoftが開発したフラッシュ書き込み用のファイル形式らしい:
<blockquote cite="https://github.com/microsoft/uf2">
@@ -289,7 +308,7 @@ over MSC (Mass Storage Class; aka removable flash drive)<sup>[6]</sup>.
</blockquote>
<p>
-RP2040のデータシート<sup>[3]</sup>を見ると、8バイト目のFlagsは28バイト目にファミリーIDが書き込まれていることを示す<code>0x00002000</code>、12バイト目は書き込みを行うフラッシュROMの先頭アドレスである<code>0x10000000</code>、16バイト目の、各ブロックのデータサイズは256バイト、28バイト目のファミリーIDは<code>0xe48bff56</code>である。あとは表の通り3つのマジックナンバーをセットし、32バイト目以降にデータを書き込み、20バイト目と24バイト目にブロックの通し番号と総数をそれぞれ書き込めばいい。ブロックの通し番号はデータのついでに書き込めるが、総数はデータを全部さばいた後でないと分からないので、最後全てのブロックにまとめて書き込むようにした。できたのが以下のコード:
+RP2040のデータシート<sup>[3]</sup>「2.8.4.2 UF2 Format Details」を見ると、8バイト目のFlagsは28バイト目にファミリーIDが書き込まれていることを示す<code>0x00002000</code>、12バイト目は書き込みを行うフラッシュROMの先頭アドレスである<code>0x10000000</code>、16バイト目の、各ブロックのデータサイズは256バイト、28バイト目のファミリーIDは<code>0xe48bff56</code>である。あとは表の通り3つのマジックナンバーをセットし、32バイト目以降にデータを書き込み、20バイト目と24バイト目にブロックの通し番号と総数をそれぞれ書き込めばいい。ブロックの通し番号はデータのついでに書き込めるが、総数はデータを全部さばいた後でないと分からないので、最後全てのブロックにまとめて書き込むようにした。できたのが以下のコード:
</p>
<pre><code>#include <stdio.h>
#include <stdint.h>
@@ -414,7 +433,7 @@ RP2040に電源を投入し、CRC32のチェックが通った後、フラッシ
<p>
しかしこのSSIコントローラはSynopsysという会社のDW_apb_ssiというIPを使っているようで、データシートのSSIコントローラの章は多分Synopsysの人が書いている。その他の章はRaspberry Pi財団の書いたブリティッシュイングリッシュだが、この部分だけ多分ネイティブじゃない人の書いたいい加減な英語である。誤植も多い。何日かかけて理解しようとしたが、あまりにも必要な情報が書かれていないのでよく分からん。不毛なので一旦諦めた。</p>
<p>
-RP2040には内部にもROMがあり、中にはバージョン情報や、電源を投入した時の動作、その他便利な関数が書き込まれている。この関数のなかに外部のフラッシュROMとSSIコントローラを設定するものも含まれているので今回はこれを利用した。ただしこの方法だとフラッシュROMとの通信方式がStandard SPIのままなので少し遅い。詳しくはデータシート<sup>[3]</sup>の2.3.8. 「Bootrom Contents」を参照。
+RP2040には内部にもROMがあり、中にはバージョン情報や、電源を投入した時の動作、その他便利な関数が書き込まれている。この関数のなかに外部のフラッシュROMとSSIコントローラを設定するものも含まれているので今回はこれを利用した。ただしこの方法だとフラッシュROMとの通信方式がStandard SPIのままなので少し遅い。詳しくはデータシートの「2.3.8. Bootrom Contents」を参照。
</p>
<p>
RP2040の内蔵ROMの<code>0x00000018</code>番地に関数を検索するための関数がある。この関数に<code>0x00000014</code>番地の<code>rom_func_table</code>と、各関数に割り当てられた二文字の文字列を渡せば、欲しい関数へのポインタが返ってくる。なお、二文字の文字列はそれぞれASCIIコードで現し、二文字目を8ビットシフトしたものと1文字目のORを取ったものを渡すことになっている。今回欲しい関数はフラッシュROMをXIPに設定するもの(<code>_flash_enter_cmd_xip()</code>)なので、<code>'C', 'X'</code>を渡す。関数のポインタが返ってきたら、それを呼び出せばフラッシュROMとSSIはXIPモードになる:
@@ -488,7 +507,7 @@ RESETS: RESETレジスタのうち5番目のビットだけを<code>0x0</code>
<p>
各レジスタには4個のアドレスが割り当てられている。データシートの各章のList of Registersに記載されているアドレスは通常の読み書きができる。そのアドレスに<code>0x1000</code>を足したものにアクセスするとアトミックなXORが、<code>0x2000</code>を足したものはアトミックなセットが、<code>0x3000</code>を足したものはアトミックなクリアができる。つまりレジスタのアドレスに<code>0x3000</code>を足したものに、<code>0x1 << 5</code>を<code>str</code>すれば5番目のビットだけ<code>0x0</code>にして、他のビットは変更されない。逆に指定したビットだけ立てて他は触らない場合は<code>0x2000</code>を、あるいは指定したビットだけトグルしたい場合は<code>0x1000</code>を足したアドレスにアクセスすればいい。</p>
<h4>リセット状態の確認</h4>
-<p>リセットの解除はすぐに完了するわけではないようである。
+<p>リセットの解除はすぐに完了するわけではないようである。リセットの解除が完了したかどうか確認するにはRESETS: RESET_DONEレジスタ(RESETS_BASEから<code>0x8</code>バイト目)の該当するビット(ここでは5番目のビット)を読む。この値が<code>0x1</code>であればリセットの解除が完了している。<code>0x0</code>であれば処理が進行中なので<code>0x1</code>が返ってくるまで繰り返し読み込んで<code>0x0</code>になるまで待機する。ところでこのレジスタはリセットの解除が完了したかどうか確かめるものなので、RESET_DONEという名前はどうなのか。リセットが完了したかどうか確かめるものではないのに。
<p>
以上から、GPIOのリセットを解除するのは以下のコード:
</p>
@@ -502,7 +521,7 @@ reset:
str r0, [r3, r1] // RESETS: RESET
reset_chk:
ldr r1, [r3, #0x8] // RESETS: RESET_DONE
- and r0, r0, r1
+ tst r0, r1
beq reset_chk
/* ... */
@@ -513,12 +532,27 @@ resets_base:
.word 0x4000c000
</code></pre>
+<h3>GPIOの機能の選択</h3>
+<p>RP2040のGPIOにはそれぞれ複数の機能が用意されていて、どれを使うかはソフトウェアから選択できる。利用できる機能の一覧と各機能の説明はデータシートの「2.19.2 Function Select」に詳しく書いてある。ここではGPIO25番のピンをSIO(Single-cycle IO)として利用する。同じCPUが載っているRaspberry Pi PicoはGPIO25番にLEDが半田付けされているので、25番にした。他のピンでもいい。GPIOに1(3.3V)か0を印加する場合はこのSIOを使うみたいである。Single-cycleはCPUから操作したときに1クロックでその操作が完了するという意味らしい。SIOの詳しい説明はデータシートの「2.3.1 SIO」にある。</p>
+<p>
+GPIO25番の機能を選択するにはIO_BANK0_BASE(<code>0x40014000</code>)から<code>0xcc</code>番目のGPIO25_CTRLレジスタの下位5ビットに、該当する機能の番号を書き込めばいい。データシートの「2.19.2 Function Select」にある表を見ると、GPIO25番のSIOは5である:</p>
<pre><code> // set gpio functions
ldr r3, io_bank0_base
mov r0, #5 // sio
mov r1, #0xcc
str r0, [r3, r1] // IO_BANK0: GPIO25_CTRL
+/* ... */
+
+io_bank0_base:
+ .word 0x40014000
+</pre></code>
+
+<h3>GPIOの出力を有効化</h3>
+<p>
+GPIO25番がSIOになったので、次にこのピンからの出力を有効化する。既定値では出力は無効になっている。ハイインピーダンスってことなのかな?出力を有効にするには、SIO_BASE(<code>0xd0000000</code>)から<code>0x24</code>バイト目のSIO: GPIO_OEレジスタの該当するビット(25番のピンなので25番ビット)を<code>0x1</code>にする:
+</p>
+<pre><code>
// enable gpio output
ldr r3, sio_base
mov r0, #1
@@ -527,20 +561,28 @@ resets_base:
/* ... */
-io_bank0_base:
- .word 0x40014000
sio_base:
.word 0xd0000000
</code></pre>
<h3>LEDの点滅</h3>
+<p>ここまででGPIOの設定は完了したので、あとは実際にLEDに電圧を掛けるだけである(電圧を掛けるはこの漢字なんや...)。レジスタのアドレスに<code>0x1000</code>を足したものに書き込むとアトミックなレジスタのXORができると書いたが、SIOだけこの機能がサポートされていないようである。データシートの「2.1.2 Atomic Register Access」に、
+</p>
+<blockquote cite="https://datasheets.raspberrypi.com/rp2040/rp2040-datasheet.pdf">
+<p>
+The SIO (Section 2.3.1), a single-cycle IO block attached directly to the cores'
+IO ports, does <strong>not</strong> support atomic accesses at the bus level,
+although some individual registers (e.g. GPIO) have set/clear/xor aliases.
+</p>
+</blockquote>
+<p>
+と書かれている。LEDを点滅させるためにGPIOの出力をトグルしたいのでSIO_BASE(<code>0xd0000000</code>)から<code>0x1c</code>バイト目のSIO: GPIO_OUT_XORレジスタの25番ビットに<code>0x1</code>を書き込む。出力をトグルした後は少し間をおいて同じことを繰り返す。間を置くためにここでは適当な数値を1づつ減らしていって0になったらリターンする関数<code>delay</code>を作った。タイマーと割り込みを使ったほうが消費電力等で優位なようだが、面倒なのでとりあえずこれで:</p>
+
<pre><code> // blink led on gpio25
ldr r4, sio_base
- mov r5, r0
+ mov r5, r0 // r0 = 1 << 25
loop:
- str r5, [r4, #0x10] // SIO_GPIO_OUT_SET
- bl delay
- str r5, [r4, #0x18] // SIO_GPIO_OUT_CLR
+ str r5, [r4, #0x1c] // SIO: GPIO_OUT_XOR
bl delay
b loop
@@ -552,16 +594,15 @@ delay_loop:
bne delay_loop
bx lr
-// literals
- .align 2
/* ... */
+
sio_base:
.word 0xd0000000
</code></pre>
<h2>リンカスクリプト</h2>
<p>
-以上のコードには<code>.boot2</code>、<code>.vectors</code>、<code>.text</code>の3つのセクションが含まれる。<code>.boot2</code>はフラッシュの先頭から256バイト(<code>0x100</code>)目まで、<code>.vectors</code>と<code>.text</code>はその後ろに続くように配置する:
+以上のコードには<code>.boot2</code>、<code>.vectors</code>、<code>.text</code>の3つのセクションが含まれる。<code>.boot2</code>はフラッシュの先頭から256(<code>0x100</code>)バイト目まで、<code>.vectors</code>と<code>.text</code>はその後ろに続くように配置する:
<pre><code>MEMORY
{
FLASH(rx) : ORIGIN = 0x10000000, LENGTH = 2048k
@@ -582,8 +623,10 @@ SECTIONS
</code></pre>
<h2>Makefile</h2>
-ディレクトリ構造:
-<pre><code>rp2040_baremetal
+<p>
+以上のソースコードは以下のように配置している:
+</p>
+<pre><code>rp2040
├── ex1
│ ├── Makefile
│ ├── boot2.s
@@ -595,7 +638,7 @@ SECTIONS
└── bincrc.c
</code></pre>
<p>
-toolsディレクトリのMakefileは同じディレクトリのソースファイルを<code>cc</code>でコンパイルするだけのものである(個人的な趣味で<code>tcc</code>を使っている)。ex1ディレクトリのMakefileは以下の通り:
+toolsディレクトリのMakefileは同じディレクトリのソースファイルを<code>$(CC)</code>でコンパイルするだけのものである(個人的な趣味で<code>tcc</code>を使っている)。ex1ディレクトリのMakefileは以下の通り:
</p>
<pre><code>AS = arm-none-eabi-as
LD = arm-none-eabi-ld
@@ -635,6 +678,20 @@ tools:
cd ../tools && make
</code></pre>
+<p>
+RP2040のボードをUSBデバイスモードでLinuxのパソコンに接続し、ex1ディレクトリで</p>
+<pre><code>
+$ make
+# make flash
+</code></pre>
+<p>
+とすればプログラムがRP2040のボードに書き込まれて実行が開始される。</p>
+
+<h2>最後に</h2>
+<p>
+光あれ。
+</p>
+
<h2>参考</h2>
<ul>
<li>
diff --git a/pub/rss.xml b/pub/rss.xml
@@ -5,8 +5,8 @@
<description>ウェブページの更新履歴</description>
<language>ja-jp</language>
<link>https://www.mtkn.jp</link>
-<lastBuildDate>Sun, 23 Apr 2023 11:47:12 +0900</lastBuildDate>
-<pubDate>Sun, 23 Apr 2023 11:47:12 +0900</pubDate>
+<lastBuildDate>Mon, 24 Apr 2023 12:11:02 +0900</lastBuildDate>
+<pubDate>Mon, 24 Apr 2023 12:11:02 +0900</pubDate>
<docs>https://www.rssboard.org/rss-specification</docs>
<item>
<title>Xlibで遊んでみる6</title>