www.mtkn.jp

Manuscripts for my personal webpage.
git clone https://git.mtkn.jp/www.mtkn.jp
Log | Files | Refs | LICENSE

rp2040_1.html (36126B)


      1 <h1>RP2040 SDKなしでLチカ</h1>
      2 <time>2023-04-25</time>: 作成<br />
      3 <time>2024-02-25</time>: ベクターテーブルの修正
      4 
      5 <h2>はじめに</h2>
      6 <p>
      7 パタヘネのRISC-V<sup>[1]</sup>版を買って一通り読んだらアセンブリ言語\
      8 で組込のプログラミングがしたくなった。\
      9 RISC-Vのマイコンボードが欲しかったのだが、安くていい感じ\
     10 のものが見付からなかった。\
     11 代わりに秋月電子通商でArmのものがあった。\
     12 RP2040マイコンボードキット<sup>[2]</sup>というものである。\
     13 ウェブ上の情報も多く、データシート<sup>[3]</sup>もしっかりしていそうなので、\
     14 とりあえずこれを買ってみた。\
     15 </p>
     16 <p>
     17 一般的にはSDK<sup>[4]</sup>をダウンロードしてあらかじめ用意された\
     18 ライブラリを使って開発するようだが、これはビルドシステムとしてcmake\
     19 というのを使っている。これがOpenBSDでは何かエラーがでて動かなかった。\
     20 僕はこういう便利ツールが嫌いだ。どうせ使わんからいいんやけど。\
     21 関係ないけど途中から開発環境がLinuxに替わった。\
     22 SDKには便利な関数がたくさん用意されているので楽である。\
     23 ハードウェアの面倒な部分がプログラマから見えないようにしているからである。\
     24 しかし今回はその面倒な部分に触れてみたくて買ったので、SDKを使うと意味がない。\
     25 </p>
     26 <p>
     27 ということでSDKなしで開発してみる。\
     28 とりあえず定番のLチカをば。\
     29 </p>
     30 <p>
     31 ソースコード: <a href="https://git.mtkn.jp/rp2040">git</a>
     32 </p>
     33 
     34 <h2>動作環境</h2>
     35 <ul>
     36 <li>Arch Linux 6.2.12-arch1-1
     37 	<ul>
     38 	<li>arm-none-eabi-binutils 2.40-1</li>
     39 	<li>GNU Make 4.4.1</li>
     40 	</ul>
     41 </li>
     42 <li>OpenBSD 7.3
     43 	<ul>
     44 	<li>arm-none-eabi-binutils 2.31.1</li>
     45 	<li>make (バージョン?)</li>
     46 	</ul>
     47 ※<code>make flash</code>は動かん。<code>dmesg</code>でデバイス確認して手動で\
     48 マウントする必要がある。
     49 </li>
     50 </ul>
     51 
     52 <h2>Boot Process</h2>
     53 <p>
     54 RP2040は電源を入れるといくつかの段階(ここでは関係ないので省略。\
     55 データシート「2.8.1 Processor Controlled Boot Sequence」に詳しく書いてある)\
     56 を踏んだあと、外部のフラッシュROMの先頭から\
     57 256バイトを内部のSRAMにコピーして、フラッシュにプログラムが書き込まれているか\
     58 どうか確認する。RP2040はフラッシュの先頭252バイトから計算したCRC32チェックサム\
     59 を、直後の253バイト目から256バイトに記録することになっている。\
     60 起動時にこのチェックサムを確認することで、フラッシュにプログラムが\
     61 書き込まれているかどうか確かめている。コピーした最後の4バイトと\
     62 起動時に最初の252バイトから計算したチェックサムが一致していれば、\
     63 そのままコピーしてきた256バイトの先頭にPCをセットして実行を開始する。\
     64 一致しなければUSBデバイスモードに切り替わり、パソコンに接続すると\
     65 ストレージとして認識される。このストレージにUF2という形式に変換した\
     66 プログラムをコピーするとプログラムがフラッシュROMやSRAMに書き込まれる。
     67 </p>
     68 <p>
     69 以上のことから、プログラムを実行するためにはCRC32を計算し、UF2という形式\
     70 に変換することが必要である。ソースコードからの流れは以下の通り:
     71 </p>
     72 <pre>\
     73 source                                                       bin             bin with
     74 code   ----------&gt; object ------&gt; elf --------&gt; bin -------&gt; with  --------&gt; crc32 in
     75                                                              crc32           uf2 format
     76         assemble           link        objcopy       bincrc         bin2uf2
     77 </pre>
     78 
     79 <h2>CRC(巡回冗長検査)</h2>
     80 <p>
     81 入力のデータをごにょごにょしてある値を出力する。\
     82 </p>
     83 <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">
     84 <p>
     85 データ転送等に伴う偶発的な誤りの検査によく使われている<sup>[5]</sup>。
     86 </p>
     87 </blockquote>
     88 <p>
     89 らしい。
     90 </p>
     91 <p>
     92 入力のビットを一列に並べて、除数で「割り算」していく。\
     93 この「割り算」が多項式の除算に似ているので、この除数をCRC多項式というらしい。\
     94 ただし多項式の除算と違い、引き算するところをXORする。\
     95 CRC32の場合、除数は33ビットである。\
     96 33ビットで割ると32ビットの余りが残る。この余りがCRC32のチェックサムである。\
     97 除数は色々あるようだが、標準的なものがWikipedia<sup>[5]</sup>に列挙されている。\
     98 除数<code>1011</code>を使ったCRC3の計算の手順は以下の通り:
     99 </p>
    100 <pre><code>\
    101 1110101011011100110101101101111  入力(適当)
    102 1011                             除数(4ビット)
    103 -------------------------------
    104  101101011011100110101101101111  結果(入力と除数のXOR)
    105  1011
    106  ------------------------------
    107   00001011011100110101101101111
    108       1011
    109       -------------------------
    110        000011100110101101101111
    111 	   1011
    112 	   --------------------
    113             1010110101101101111
    114 	    1011
    115 	    -------------------
    116              001110101101101111
    117 	       1011
    118 	       ----------------
    119                 101101101101111
    120 		1011
    121 		---------------
    122                  00001101101111
    123 		     1011
    124 		     ----------
    125                       110101111
    126 		      1011
    127 		      ---------
    128                        11001111
    129 		       1011
    130 		       --------
    131                         1111111
    132 			1011
    133 			-------
    134                          100111
    135 			 1011
    136 			 ------
    137                           01011
    138 			   1011
    139 			   ----
    140 			    000  CRC3チェックサム
    141 </code></pre>
    142 <p>
    143 普通の割り算と基本は同じであるが、引き算の部分だけXORになっている。\
    144 </p>
    145 <p>
    146 以上の計算をプログラムの先頭252バイトに対して、33ビットの除数を用いて行う。\
    147 データの並べ方は、上の例において左側を先頭に、フラッシュROM上の0番地から、\
    148 各バイトは最上位ビットから順に並べる。\
    149 入力のデータは253バイト目から256バイト目に<code>0</code>をひっつけて計算する。\
    150 これは多分予め長さが分からないデータでも計算できるようにしたかったからかな。\
    151 除数は<code>0x104c11db7</code>である(最上位ビットは常に1なのでデータシート\
    152 では省略されている)。\
    153 </p>
    154 <p>
    155 入力データは1バイトづつ処理したいみたいである。多分通信等で使う都合である。\
    156 この時XORは結合則が成り立つので1バイト処理した結果と\
    157 次のバイトとをXORして次の処理の入力として利用することができる:
    158 </p>
    159 <pre><code>\
    160 111000111000000110000110111000111000001010010011111000111000000110010011  入力(適当)
    161 |......|
    162 111000110000000000000000000000000                                         先頭1バイト
    163 100000100110000010001110110110111                                         除数
    164 ------------------------------------------------------------------------
    165 011000010110000010001110110110111
    166  100000100110000010001110110110111
    167  -----------------------------------------------------------------------
    168  010000001010000110010011011011001
    169   100000100110000010001110110110111
    170   ----------------------------------------------------------------------
    171   000000110010001110101000000000101
    172 |......|
    173         110010001110101000000000101000000                            1バイト目の結果
    174         |......|
    175         10000001                                                     入力の2バイト目
    176 	----------------------------------------------------------------
    177 	010010011110101000000000101000000            1バイト目の結果と2バイト目のXOR
    178          100000100110000010001110110110111                                除数
    179 	----------------------------------------------------------------
    180 	 000100011011010010001111100110111
    181 	 .
    182 	 .
    183 	 .
    184 </code></pre>
    185 <p>
    186 以上の操作は以下のようなアルゴリズムのループで実装できる。\
    187 </p>
    188 <ul>
    189 <li>前回の結果と、入力データの次のバイトをXOR</li>
    190 <li>
    191 	<ul>
    192 	<li>先頭の1ビットが1の場合、除数とXORを取り左シフト</li>
    193 	<li>先頭の1ビットが0の場合、そのまま左シフト</li>
    194 	</ul>
    195 </li>
    196 </ul>
    197 <p>
    198 これを<code>for</code>ループで回す都合上、最初のバイトもXORを取る。\
    199 上の例では最初は<code>0x0</code>とXORを取っているが、この値を<code>0x0</code>\
    200 以外にすることもできる。そうした方がいろいろいいこともあるらしい。\
    201 RP2040では<code>0xffffffff</code>を使う。\
    202 更にこの工程を32ビットの<code>int</code>だけで行うことを考える:
    203 </p>
    204 <pre><code>\
    205 111000111000000110000110111000111000001010010011111000111000000110010011  入力(適当)
    206 
    207 11111111111111111111111111111111  0xffffffff
    208 11100011000000000000000000000000  先頭1バイトを24ビットシフト
    209 --------------------------------  XOR
    210 00011100111111111111111111111111
    211 先頭1ビットが0なので1ビットシフト
    212 --------------------------------  シフト
    213 00111001111111111111111111111110
    214 先頭1ビットが0なので1ビットシフト
    215 --------------------------------  シフト
    216 01110011111111111111111111111100
    217 先頭1ビットが0なので1ビットシフト
    218 --------------------------------  シフト
    219 11100111111111111111111111111000
    220 先頭1ビットが1なので1ビットシフトした後、除数の下位32ビットとXOR:
    221 11001111111111111111111111110000  シフト
    222 00000100110000010001110110110111  除数の下位32ビット
    223 --------------------------------  XOR
    224 11001011001111101110001001000111
    225 先頭1ビットが1なので1ビットシフトした後、除数の下位32ビットとXOR:
    226 10010110011111011100010010001110  シフト
    227 00000100110000010001110110110111  除数の下位32ビット
    228 --------------------------------  XOR
    229 10010010101111001101100100111001
    230 先頭1ビットが1なので1ビットシフトした後、除数の下位32ビットとXOR:
    231 00100101011110011011001001110010  シフト
    232 00000100110000010001110110110111  除数の下位32ビット
    233 --------------------------------  XOR
    234 00100001101110001010111111000101
    235 先頭1ビットが0なので1ビットシフト
    236 --------------------------------  シフト
    237 01000011011100010101111110001010
    238 先頭1ビットが0なので1ビットシフト
    239 --------------------------------  シフト
    240 10000110111000101011111100010100  1バイト目の結果
    241 
    242 10000001                          入力の2バイト目
    243 --------------------------------  XOR
    244 00000111111000101011111100010100
    245 先頭1ビットが0なので1ビットシフト
    246 --------------------------------  シフト
    247 00001111110001010111111000101000
    248 .
    249 .
    250 .
    251 </code></pre>
    252 <p>
    253 これを実装したのが以下のコード:\
    254 </p>
    255 <pre><code>\
    256 uint32_t
    257 crc32(uint8_t *idata, size_t len)
    258 {
    259 	uint32_t pol = 0x04C11DB7;
    260 	uint32_t c   = 0xFFFFFFFF;
    261 	uint32_t b;
    262 
    263 	for (int i = 0; i &lt; len; i++) {
    264 		b = idata[i] &lt;&lt; 24;
    265 		c ^= b;
    266 		for (int j = 0; j &lt; 8; j++) {
    267 			c = c &gt;&gt; 31 & 1 ? c &lt;&lt; 1 ^ pol : c &lt;&lt; 1;
    268 		}
    269 	}
    270 
    271 	return c;
    272 }
    273 </code></pre>
    274 <p>
    275 <code>main()</code>関数では上の<code>crc32()</code>に、<code>idata</code>として入力\
    276 となるバイナリデータの先頭を、<code>len</code>として<code>252</code>を渡してCRC32\
    277 を計算させる。その後、出力先のファイルに入力元のデータをコピーしていき、253バイト\
    278 目から256バイト目だけ、計算したCRC32に置き換える。入力元のこの場所にデータが\
    279 書き込まれていないかどうかは確かめていない。
    280 </p>
    281 
    282 <h2>UF2(USB Flashing Format)</h2>
    283 <p>
    284 Microsoftが開発したフラッシュ書き込み用のファイル形式らしい:
    285 <blockquote cite="https://github.com/microsoft/uf2">
    286 <p>
    287 UF2 is a file format, developed by Microsoft for PXT (also known as
    288 Microsoft MakeCode), that is particularly suitable for flashing microcontrollers
    289 over MSC (Mass Storage Class; aka removable flash drive)<sup>[6]</sup>.
    290 </p>
    291 </blockquote>
    292 <p>
    293 このファイルに変換する上で必要な情報はGitHubのmicrosoft/uf2<sup>[6]</sup>に\
    294 表として纏められている:
    295 <blockquote cite="https://github.com/microsoft/uf2">
    296 <table>
    297 <thead><tr>
    298 <th>Offset</th><th>Size</th><th>Value</th>
    299 </tr></thead>
    300 <tbody>
    301 <tr>
    302 <td>0</td>
    303 <td>4</td>
    304 <td>First magic number, <code>0x0A324655</code> (<code>"UF2\n"</code>)</td>
    305 </tr>
    306 <tr>
    307 <td>4</td>
    308 <td>4</td>
    309 <td>Second magic number, <code>0x9E5D5157</code></td>
    310 </tr>
    311 <tr>
    312 <td>8</td>
    313 <td>4</td>
    314 <td>Flags</td>
    315 </tr>
    316 <tr>
    317 <td>12</td>
    318 <td>4</td>
    319 <td>Address in flash where the data should be written</td>
    320 </tr>
    321 <tr>
    322 <td>16</td>
    323 <td>4</td>
    324 <td>Number of bytes used in data (often 256)</td>
    325 </tr>
    326 <tr>
    327 <td>20</td>
    328 <td>4</td>
    329 <td>Sequential block number; starts at 0</td>
    330 </tr>
    331 <tr>
    332 <td>24</td>
    333 <td>4</td>
    334 <td>Total number of blocks in file</td>
    335 </tr>
    336 <tr>
    337 <td>28</td>
    338 <td>4</td>
    339 <td>File size or board family ID or zero</td>
    340 </tr>
    341 <tr>
    342 <td>32</td>
    343 <td>476</td>
    344 <td>Data, padded with zeros</td>
    345 </tr>
    346 <tr>
    347 <td>508</td>
    348 <td>4</td>
    349 <td>Final magic number, <code>0x0AB16F30</code></td>
    350 </tr>
    351 </tbody>
    352 </table>
    353 </blockquote>
    354 
    355 <p>
    356 RP2040のデータシート<sup>[3]</sup>「2.8.4.2 UF2 Format Details」\
    357 を見ると、\
    358 8バイト目のFlagsは、28バイト目に\
    359 ファミリーIDが書き込まれていることを示す<code>0x00002000</code>、\
    360 12バイト目は、書き込みを行うフラッシュROMの先頭アドレスである\
    361 <code>0x10000000</code>に、各ブロックの先頭からの位置を足したもの、\
    362 16バイト目の、各ブロックのデータサイズは256バイト、\
    363 28バイト目のファミリーIDは<code>0xe48bff56</code>である。\
    364 あとは表の通り3つのマジックナンバーをセットし、32バイト目以降にデータを書き込み、\
    365 20バイト目と24バイト目にブロックの通し番号と総数をそれぞれ書き込めばいい。\
    366 ブロックの通し番号はデータのついでに書き込めるが、総数はデータを全部\
    367 さばいた後でないと分からないので、最後全てのブロックにまとめて書き込むようにした。\
    368 できたのが以下のコード:
    369 </p>
    370 <pre><code>\
    371 #include &lt;stdio.h&gt;
    372 #include &lt;stdint.h&gt;
    373 #include &lt;stdlib.h&gt;
    374 #include &lt;string.h&gt;
    375 
    376 
    377 size_t
    378 fwrite32l(uint32_t d, FILE *f)
    379 {
    380 	int i;
    381 	uint8_t b;
    382 	for (i = 0; i &lt; 32; i += 8) {
    383 		b = (uint8_t) (d &gt;&gt; i & 0xff);
    384 		fwrite(&amp;b, 1, 1, f);
    385 		if (ferror(f)) {
    386 			fprintf(stderr, "Fwrite32l: write error.\n");
    387 			return 0;
    388 		}
    389 	}
    390 	return 4;
    391 }
    392 
    393 int
    394 main(int argc, char *argv[])
    395 {
    396 	FILE *src = NULL, *dst = NULL;
    397 	size_t sdata = 476;
    398 	int retnum = 0;
    399 
    400 	uint32_t mag1 = 0x0A324655;
    401 	uint32_t mag2 = 0x9E5D5157;
    402 	uint32_t flags = 0x00002000; // familyID present
    403 	uint32_t addr = 0x10000000;
    404 	uint32_t nbyte = 256;
    405 	uint32_t blk = 0;
    406 	uint32_t nblk = 0;
    407 	uint32_t famid = 0xe48bff56;
    408 	uint8_t data[sdata];
    409 	uint32_t mag3 = 0x0AB16F30;
    410 
    411 	memset(data, 0, sdata);	
    412 
    413 	if (argc != 3) {
    414 		fprintf(stderr, "Usage: %s src dst\n", argv[0]);
    415 		exit(1);
    416 	}
    417 
    418 	if ((src = fopen(argv[1], "rb")) == NULL) {
    419 		fprintf(stderr, "Could not open %s.\n", argv[1]);
    420 		retnum = 1;
    421 		goto defer;
    422 	}
    423 	if ((dst = fopen(argv[2], "wb")) == NULL) {
    424 		fprintf(stderr, "Could not open %s.\n", argv[2]);
    425 		retnum = 1;
    426 		goto defer;
    427 	}
    428 	
    429 	while (!feof(src)) {
    430 		fwrite32l(mag1, dst);
    431 		fwrite32l(mag2, dst);
    432 		fwrite32l(flags, dst);
    433 		fwrite32l(addr, dst);
    434 		fwrite32l(nbyte, dst);
    435 		fwrite32l(blk, dst);
    436 		fwrite32l(nblk, dst); // dummy
    437 		fwrite32l(famid, dst);
    438 
    439 		fread(data, 1, nbyte, src);
    440 		if (ferror(src)) {
    441 			fprintf(stderr, "Read error: %s.\n", argv[1]);
    442 			retnum = 1;
    443 			goto defer;
    444 		}
    445 		fwrite(data, 1, sdata, dst);
    446 		if (ferror(src)) {
    447 			fprintf(stderr, "Write error: %s.\n", argv[2]);
    448 			retnum = 1;
    449 			goto defer;
    450 		}
    451 
    452 		fwrite32l(mag3, dst);
    453 
    454 		addr += nbyte;
    455 		blk++;
    456 		nblk++;
    457 	}
    458 
    459 	for (int i = 0; i &lt; nblk; i++) {
    460 		if (i == 0)
    461 			if (fseek(dst, 24, SEEK_SET) &lt; 0) {
    462 				fprintf(stderr, "Seek error: %s.\n argv[2]");
    463 				retnum = 1;
    464 				goto defer;
    465 			}
    466 		fwrite32l(nblk, dst);
    467 		if (i &lt; nblk - 1)
    468 			if(fseek(dst, 512 - 4, SEEK_CUR) &lt; 0){
    469 				fprintf(stderr, "Seek error: %s.\n argv[2]");
    470 				retnum = 1;
    471 				goto defer;
    472 			}
    473 	}
    474 	
    475 defer:
    476 	if (src)
    477 		fclose(src);
    478 	if (dst)
    479 		fclose(dst);
    480 	return retnum;
    481 }
    482 </code></pre>
    483 <p>\
    484 <code>fwrite32l()</code>関数は指定されたファイルに32ビットの整数を\
    485 下位バイトから順に書き込む関数である。バイトオーダーとかややこしそうなので\
    486 作っておいたけど必要なのかな?あと名前が気に入らない。\
    487 </p>
    488 <p>
    489 CRC32のチェックサムが書き込まれたバイナリファイルを、このプログラムでUF2に\
    490 変換し、生成されたファイルをUSBストレージとして接続したRP2040にコピーすれば\
    491 フラッシュROMに書き込まれる。
    492 </p>
    493 
    494 <h2>Flash Second Stage</h2>
    495 <p>
    496 RP2040に電源を投入し、CRC32のチェックが通った後、フラッシュROMからコピー\
    497 されたプログラムの先頭から実行が開始される。このコピーされた部分で、\
    498 その後の動作に必要な各種の設定を行うことになる。\
    499 RP2040のデータシートには、フラッシュROMとSSIコントローラのXIP\
    500 を設定するようにと書かれている。\
    501 XIPはExecute in Placeの略で、フラッシュROMの内容をCPUから\
    502 直接実行するものである。SSIはSynchronous Serial Interfaceの略で、\
    503 周辺機器と情報のやりとりをする通信方式である。\
    504 RP2040はチップに内蔵されたこのSSIコントローラを通して、\
    505 外部のフラッシュROMと通信しているのだが、このコントローラを適切に設定すれば\
    506 フラッシュROMの内容がCPUから直接アクセスできる<code>0x10000000</code>番地以降\
    507 にマップされる。これによりフラッシュROMから内部のSRAMにデータをコピーする\
    508 ことなく命令を実行できるので、速くて便利だという。
    509 </p>
    510 <p>
    511 しかしこのSSIコントローラはSynopsysという会社のDW_apb_ssiというIPを\
    512 使っているようで、データシートのSSIコントローラの章は多分Synopsysの\
    513 人が書いている。その他の章はRaspberry Pi財団の書いたブリティッシュイングリッシュ\
    514 だが、この部分だけ多分ネイティブじゃない人の書いたいい加減な英語である。誤植も多い。\
    515 何日かかけて理解しようとしたがよく分からん。不毛なので一旦諦めた。\
    516 </p>
    517 <p>
    518 RP2040には内部にもROMがあり、はバージョン情報や電源を投入した時の動作、\
    519 その他便利な関数が書き込まれている。この関数の中に外部のフラッシュROMと\
    520 SSIコントローラを設定するものも含まれているので、今回はこれを利用した。\
    521 ただしこの方法だとフラッシュROMとの通信方式がStandard SPIのままなので少し\
    522 遅いらしい。詳しくはデータシートの「2.3.8. Bootrom Contents」を参照。
    523 </p>
    524 <p>
    525 RP2040の内蔵ROMの<code>0x00000018</code>番地に関数を検索するための関数がある。\
    526 この関数に<code>0x00000014</code>番地の<code>rom_func_table</code>と、\
    527 各関数に割り当てられた二文字の文字列を渡せば、欲しい関数へのポインタが\
    528 返ってくる。なお、二文字の文字列はそれぞれASCIIコードで現し、二文字目を\
    529 8ビットシフトしたものと1文字目のORを取ったものを渡すことになっている。\
    530 今回欲しい関数はフラッシュROMをXIPに設定するもの\
    531 (<code>_flash_enter_cmd_xip()</code>)なので、<code>'C', 'X'</code>を渡す。\
    532 関数のポインタが返ってきて、それを呼び出せばフラッシュROMとSSIはXIPモード\
    533 になる:
    534 </p>
    535 <pre><code>\
    536 setup_xip:
    537 	ldr r3, rom_base
    538 
    539 	ldrh r0, [r3, #0x14] // rom_func_table
    540 	ldr r1, =('C' | 'X' &lt;&lt; 8) // _flash_enter_cmd_xip()
    541 	ldrh r2, [r3, #0x18] // rom_table_lookup
    542 	blx r2
    543 	blx r0
    544 /* ... */
    545 rom_base:
    546 	.word 0x00000000
    547 </code></pre>
    548 
    549 <p>
    550 XIPの設定が完了すれば、次はメインのプログラムを実行するための準備である。\
    551 エントリーポイントの指定、スタックポインタの初期値の設定、ベクターテーブル\
    552 の設定である。Armのマニュアル<sup>[7]</sup>によると、\
    553 初期スタックポインタとエントリーポイントはベクターテーブルの\
    554 <code>0x0</code>バイト目と<code>0x4</code>バイト目に書くことになっている:\
    555 </p>
    556 <blockquote cite="https://developer.arm.com/documentation/ddi0419/c/System-Level-Architecture/System-Level-Programmers--Model/ARMv6-M-exception-model/Exception-number-definition">
    557 <table>
    558 <caption>
    559 Table 7.3. Exception numbers 
    560 </caption><colgroup><col><col></colgroup><thead><tr><th>Exception number</th><th>Exception</th></tr></thead><tbody><tr><td>1</td><td>Reset</td></tr><tr><td>2</td><td>NMI</td></tr><tr><td>3</td><td>HardFault</td></tr><tr><td>4-10</td><td>Reserved</td></tr><tr><td>11</td><td>SVCall</td></tr><tr><td>12-13</td><td>Reserved</td></tr><tr><td>14</td><td>PendSV</td></tr><tr><td>15</td><td>SysTick, optional</td></tr><tr><td>16</td><td>External Interrupt(0)</td></tr><tr><td>...</td><td>...</td></tr><tr><td>16 + N</td><td>External Interrupt(N)</td></tr></tbody>
    561 </table>
    562 </blockquote>
    563 
    564 <blockquote cite="https://developer.arm.com/documentation/ddi0419/c/System-Level-Architecture/System-Level-Programmers--Model/ARMv6-M-exception-model/The-vector-table">
    565 <table>
    566 <caption>
    567 Table 7.4. Vector table format 
    568 </caption><colgroup><col><col></colgroup><thead><tr><th>Word offset in table</th><th>Description, for all pointer address values</th></tr></thead><tbody><tr><td>0</td><td>SP_main. This is the reset value of the Main stack pointer.</td></tr><tr><td>Exception Number</td><td>Exception using that Exception Number</td></tr></tbody>
    569 </table>
    570 </blockquote>
    571 <p>
    572 RP2040のベクターテーブルはM0PLUS: VTOR(<code>0xe0000000 + 0xed08</code>)という\
    573 レジスタに書き込むことで設定する。\
    574 このとき、下位8ビットは0にしないといけないので、ベクターテーブルの位置は\
    575 256バイトでアラインする必要がある。\
    576 ベクターテーブルの定義は<code>main.s</code>に書き、<code>boot2.s</code>からは\
    577 ラベルを使ってアクセスすることにする。\
    578 以上をまとめると以下のコードになる:\
    579 </p>
    580 <pre><code>	ldr r0, =vectors
    581 	ldr r1, m0plus_vtor
    582 	str r0, [r1, #0] // vector table
    583 	ldr r1, [r0, #4] // entry point
    584 	ldr r0, [r0, #0] // stack pointer
    585 	mov sp, r0
    586 	bx r1
    587 
    588 /* ... */
    589 
    590 m0plus_vtor:
    591 	.word 0xe0000000 + 0xed08
    592 </code></pre>
    593 <p>\
    594 なお以上のコードは<code>.boot2</code>という名前のセクションにしてある。
    595 </p>
    596 
    597 <h2>メインのコード(<code>main.s</code>)</h2>
    598 
    599 <h3>ベクターテーブル</h3>
    600 <p>
    601 上で説明したように、ベクターテーブルのアドレスは256バイトの境界\
    602 にないといけないが、<code>boot2.s</code>をフラッシュの最初の256バイト\
    603 に配置しており、<code>main.s</code>はその直後から始まるように\
    604 リンクする。\
    605 そのためメインのコードの最初にベクターテーブルを配置すればいい。\
    606 ここでは割り込みの処理は考えないので、初期スタックポインタと\
    607 エントリーポイントだけである。初期スタックポインタはSRAMの最後?\
    608 (<code>0x20040000</code>)、エントリーポイントはエントリーポイントの\
    609 ラベルを用いて設定した。\
    610 また、別のファイル(<code>boot2.s</code>)からアクセスしたいので、\
    611 <code>.global</code>宣言をつけておく:
    612 </p>
    613 <pre><code>	.global vectors
    614 vectors:
    615 	.word 0x20040000 // initial SP
    616 	.word (reset+1)
    617 </code></pre>
    618 <p>
    619 <code>reset</code>ラベルに<code>1</code>を足しているのはRP2040が\
    620 Thumbモードのみに対応しているからである。\
    621 ArmのCPUはArmモードとThumbモードがあり、Armモードは32ビットの命令で\
    622 高機能。Thumbモードは16ビットの命令(一部32ビット)でコンパクトである。\
    623 どちらのモードでも命令は2の倍数のアドレスに並ぶことになる。\
    624 そのためジャンブ命令のジャンプ先のアドレスの最下位ビットは\
    625 常に0である。\
    626 この最下位ビットはジャンプ先のモードを示す為に利用される。\
    627 両方のモードに対応したCPUではジャンプ先のアドレスの\
    628 最下位ビットが0ならArmモード、1ならThumbモードに切り替わる。\
    629 ブランチ命令のオペランド等は多分アセンブラがいい感じにしてくれる\
    630 ので単にラベルを書けば動く。ベクターテーブルのこの部分は\
    631 自分で足す必要があるみたい。\
    632 あんまりちゃんと調べてないのでマニュアル読んでや。\
    633 </p>
    634 
    635 <h3>GPIOの設定</h3>
    636 <p>
    637 電源投入直後、RP2040の周辺機器はリセット状態になっている。\
    638 まずは今回利用するGPIOのリセット状態を解除する必要がある。\
    639 データシートの「2.14. Subsystem Resets」には以下のように書かれている:
    640 </p>
    641 <blockquote cite="https://datasheets.raspberrypi.com/rp2040/rp2040-datasheet.pdf">
    642 <p>
    643 Every peripheral reset by the reset controller is held in reset at power-up.
    644 It is up to software to deassert the reset of peripherals it intends to use.
    645 </p>
    646 </blockquote>
    647 <p>
    648 リセット状態を解除するには、RESETS_BASE(<code>0x4000c000</code>)から\
    649 <code>0x0</code>バイト目のRESETS: RESETレジスタのうち利用したい周辺機器の\
    650 ビットを<code>0x0</code>にすればいい。
    651 GPIOはIO Bank 0なので(これ明記されてなくない?)、RESETS: RESETレジスタの\
    652 IO_BANK0(5番ビット)を<code>0x0</code>にする。
    653 </p>
    654 <h4>レジスタのアトミックなクリア</h4>
    655 <p>
    656 RESETS: RESETレジスタのうち5番ビットだけを<code>0x0</code>に\
    657 したい。この時、まずこのレジスタを読み込んでから<code>~(1 &lt;&lt; 5)</code>と\
    658 論理積を取って同レジスタに書き戻してもいいのだが、RP2040にはこれを\
    659 一回の<code>str</code>でしかもアトミックにできる機能が用意されている。\
    660 今回の場合アトミックかどうかは関係ないと思うけど。\
    661 </p>
    662 <p>
    663 各レジスタには4個のアドレスが割り当てられている。\
    664 データシートの各章のList of Registersに記載されているアドレスは\
    665 通常の読み書きができる。そのアドレスに<code>0x1000</code>を足したもの\
    666 にアクセスするとアトミックなXORが、<code>0x2000</code>を足したものは\
    667 アトミックなセットが、<code>0x3000</code>を足したものはアトミックな\
    668 クリアができる。つまりレジスタのアドレスに<code>0x3000</code>を足した\
    669 ものに、<code>0x1 &lt;&lt; 5</code>を<code>str</code>すれば5番目のビットだけ\
    670 <code>0x0</code>にして、他のビットは変更されない。\
    671 逆に指定したビットだけ立てて他を触らない場合は<code>0x2000</code>を、\
    672 あるいは指定したビットだけトグルしたい場合は<code>0x1000</code>を足したアドレス\
    673 にアクセスすればいい。\
    674 </p>
    675 <h4>リセット状態の確認</h4>
    676 <p>\
    677 リセットの解除はすぐに完了するわけではないようである。\
    678 リセットの解除が完了したかどうか確認するにはRESETS: RESET_DONE\
    679 レジスタ(RESETS_BASEから<code>0x8</code>バイト目)の該当するビット\
    680 (ここでは5番目のビット)を読む。この値が<code>0x1</code>であれば\
    681 リセットの解除が完了している。<code>0x0</code>であれば処理が進行中\
    682 なので<code>0x1</code>が返ってくるまで繰り返し読み込んで<code>0x0</code>\
    683 になるまで待機する。\
    684 ところでこのレジスタはリセットの解除が完了したかどうか確かめるもの\
    685 なので、RESET_DONEという名前はどうなん?
    686 <p>
    687 以上から、GPIOのリセットを解除するのは以下のコード:
    688 </p>
    689 <pre><code>\
    690 reset:
    691 	// unreset gpio
    692 	mov r0, #1
    693 	lsl r0, r0, #5 // io_bank0
    694 	ldr r3, resets_base
    695 	ldr r1, atomic_clr
    696 	str r0, [r3, r1] // RESETS: RESET
    697 reset_chk:
    698 	ldr r1, [r3, #0x8] // RESETS: RESET_DONE
    699 	tst r0, r1
    700 	beq reset_chk
    701 
    702 /* ... */
    703 
    704 atomic_clr:
    705 	.word 0x00003000
    706 resets_base:
    707 	.word 0x4000c000
    708 </code></pre>
    709 
    710 <h3>GPIOの機能の選択</h3>
    711 <p>\
    712 RP2040のGPIOにはそれぞれ複数の機能が用意されていて、どれを使うかは\
    713 ソフトウェアから選択できる。利用できる機能の一覧と各機能の説明は\
    714 データシートの「2.19.2 Function Select」に詳しく書いてある。\
    715 ここではGPIO25番のピンをSIO(Single-cycle IO)として利用する。\
    716 同じCPUが載っているRaspberry Pi PicoはGPIO25番にLEDが半田付けされている。\
    717 25番にしたのはこれに合わせるためである。他のピンでもいい。\
    718 GPIOに1か0を印加するだけならこのSIOを使うみたいである。\
    719 Single-cycleはCPUから操作したときに1クロックでその操作が完了する\
    720 という意味らしい(本当か)。SIOの詳しい説明はデータシートの「2.3.1 SIO」にある。\
    721 </p>
    722 <p>
    723 GPIO25番の機能を選択するにはIO_BANK0_BASE(<code>0x40014000</code>)から\
    724 <code>0xcc</code>番目のGPIO25_CTRLレジスタの下位5ビットに、該当する機能\
    725 の番号を書き込めばいい。データシートの「2.19.2 Function Select」にある\
    726 表を見ると、GPIO25番のSIOは5である:\
    727 </p>
    728 <pre><code>	// set gpio functions
    729 	ldr r3, io_bank0_base
    730 	mov r0, #5 // sio
    731 	mov r1, #0xcc
    732 	str r0, [r3, r1] // IO_BANK0: GPIO25_CTRL
    733 
    734 /* ... */
    735 
    736 io_bank0_base:
    737 	.word 0x40014000
    738 </code></pre>
    739 
    740 <h3>GPIOの出力を有効化</h3>
    741 <p>
    742 GPIO25番がSIOになったので、次にこのピンからの出力を有効化する。\
    743 既定値では出力は無効になっている。ハイインピーダンスってことなのかな?\
    744 出力を有効にするには、SIO_BASE(<code>0xd0000000</code>)から\
    745 <code>0x24</code>バイト目のSIO: GPIO_OEレジスタの該当するビット\
    746 (25番のピンなので25番ビット)を<code>0x1</code>にする:
    747 </p>
    748 <pre><code>	// enable gpio output
    749 	ldr r3, sio_base
    750 	mov r0, #1
    751 	lsl r0, r0, #25 // gpio25
    752 	str r0, [r3, #0x24] // SIO: GPIO_OE
    753 
    754 /* ... */
    755 
    756 sio_base:
    757 	.word 0xd0000000
    758 </code></pre>
    759 
    760 <h3>LEDの点滅</h3>
    761 <p>\
    762 以上でGPIOの設定は完了したので、あとは実際にLEDに電圧を掛けるだけである。\
    763 レジスタのアドレスに<code>0x1000</code>を足したものに書き込むと\
    764 アトミックなレジスタのXORができると書いたが、SIOはこの機能がサポート\
    765 されていないようである。データシートの「2.1.2 Atomic Register Access」に、
    766 </p>
    767 <blockquote cite="https://datasheets.raspberrypi.com/rp2040/rp2040-datasheet.pdf">
    768 <p>
    769 The SIO (Section 2.3.1), a single-cycle IO block attached directly to the cores'
    770 IO ports, does <strong>not</strong> support atomic accesses at the bus level,
    771 although some individual registers (e.g. GPIO) have set/clear/xor aliases.
    772 </p>
    773 </blockquote>
    774 <p>
    775 と書かれている。そのかわりここにも書かれている通り、SIOの一部のレジスタには\
    776 アトミックなセット/クリア/XORをするためのレジスタが用意されている。\
    777 ここではLEDを点滅させるためにGPIOの出力をトグルしたいのでXOR用のレジスタを使う。\
    778 SIO_BASE(<code>0xd0000000</code>)から<code>0x1c</code>バイト目の\
    779 SIO: GPIO_OUT_XORレジスタがそれである。\
    780 このレジスタの25番ビットに<code>0x1</code>を書き込めばいい。\
    781 出力をトグルした後は少し間をおいて同じことを繰り返す。\
    782 間をおくためにここでは適当な数値を1づつ減らしていって0になったら\
    783 返る関数<code>delay</code>を作った。\
    784 タイマーと割り込みを使ったほうが消費電力等で優位なようだが、\
    785 面倒なのでとりあえずこれで:\
    786 </p>
    787 
    788 <pre><code>	// blink led on gpio25
    789 	ldr r4, sio_base
    790 	mov r5, r0 // r0 = 1 &lt;&lt; 25
    791 loop:
    792 	str r5, [r4, #0x1c] // SIO: GPIO_OUT_XOR
    793 	bl delay
    794 	b loop
    795 
    796 delay:
    797 	mov r0, #1
    798 	lsl r0, r0, #20
    799 delay_loop:
    800 	sub r0, r0, #1
    801 	bne delay_loop
    802 	bx lr
    803 
    804 /* ... */
    805 
    806 sio_base:
    807 	.word 0xd0000000
    808 </code></pre>
    809 <p>\
    810 なお以上のコードは<code>.text</code>セクションである。\
    811 </p>
    812 
    813 <h2>リンカスクリプト</h2>
    814 <p>
    815 以上のコードには<code>.boot2</code>、<code>.text</code>\
    816 の2つのセクションが含まれる。<code>.boot2</code>はフラッシュの先頭から\
    817 256(<code>0x100</code>)バイト目まで、<code>.text</code>\
    818 はその後ろに続くように配置する:
    819 <pre><code>\
    820 MEMORY
    821 {
    822 	FLASH(rx) : ORIGIN = 0x10000000, LENGTH = 2048k
    823 }
    824 
    825 SECTIONS
    826 {
    827 	.boot2 : {
    828 		*(.boot2)
    829 		. = 0x100;
    830 	} > FLASH
    831 
    832 	.text : {
    833 		*(.text)
    834 	} > FLASH
    835 }
    836 </code></pre>
    837 
    838 <h2>Makefile</h2>
    839 <p>
    840 以上のソースコードは以下のように配置している:
    841 </p>
    842 <pre>\
    843 rp2040
    844 ├── ex1
    845 │   ├── Makefile
    846 │   ├── boot2.s
    847 │   ├── main.s
    848 │   └── memmap.ld
    849 └── tools
    850     ├── Makefile
    851     ├── bin2uf2.c
    852     └── bincrc.c
    853 </pre>
    854 <p>
    855 toolsディレクトリのMakefileは同じディレクトリのソースファイルを<code>$(CC)</code>\
    856 でコンパイルするだけのものである(個人的な趣味で<code>tcc</code>を使っている)。\
    857 ex1ディレクトリのMakefileは以下の通り:
    858 </p>
    859 <pre><code>\
    860 AS = arm-none-eabi-as
    861 LD = arm-none-eabi-ld
    862 OBJCOPY = arm-none-eabi-objcopy
    863 BINCRC = ../tools/bincrc
    864 BIN2UF2 = ../tools/bin2uf2
    865 
    866 MCPU = -mcpu=cortex-m0plus
    867 ASFLAGS = $(MCPU)
    868 CFLAGS = $(MCPU) -ffreestanding -nostartfiles -O0 -fpic -mthumb -c
    869 LDFLAGS = --no-relax -nostdlib
    870 
    871 all: tools led.uf2
    872 
    873 clean:
    874 	rm -f *.o *.elf *.uf2 *.bin
    875 	cd ../tools &amp;&amp; make clean
    876 
    877 .s.o:
    878 	$(AS) $(ASFLAGS) -o $@ $&lt;
    879 
    880 led.elf: boot2.o main.o memmap.ld
    881 	$(LD) $(LDFLAGS) -o $@ -T memmap.ld boot2.o main.o 
    882 
    883 led.bin: led.elf
    884 	$(OBJCOPY) -O binary led.elf $@
    885 
    886 led.uf2: led.bin
    887 	$(BINCRC) led.bin led_crc.bin
    888 	$(BIN2UF2) led_crc.bin $@
    889 
    890 flash: all
    891 	mount /dev/disk/by-label/RPI-RP2 /mnt
    892 	cp led.uf2 /mnt
    893 
    894 tools:
    895 	cd ../tools &amp;&amp; make
    896 </code></pre>
    897 
    898 <p>
    899 RP2040のボードをUSBデバイスモードでLinuxのパソコンに接続し、ex1ディレクトリで\
    900 </p>
    901 <pre><code>\
    902 $ make
    903 # make flash
    904 </code></pre>
    905 <p>
    906 とすればプログラムがRP2040のボードに書き込まれて実行が開始される。\
    907 </p>
    908 
    909 <h2>最後に</h2>
    910 <p>
    911 光あれ。
    912 </p>
    913 
    914 <h2>参考</h2>
    915 <ul>
    916 <li>
    917 [1] Hennesy, J. L. and Patterson, D. A. 2017. Computer Organization And Design RISC-V Edition.
    918 </li>
    919 <li>
    920 [2] <a href="https://akizukidenshi.com/catalog/g/gK-17542/">RP2040マイコンボードキット.秋月電子通商</a>
    921 </li>
    922 <li>
    923 [3] <a href="https://datasheets.raspberrypi.com/rp2040/rp2040-datasheet.pdf">RP2040 Datasheet.Raspberry Pi Foundation</a>
    924 </li>
    925 <li>
    926 [4] <a href="https://github.com/raspberrypi/pico-sdk">pico-sdk.github</a>
    927 </li>
    928 <li>
    929 [5] <a href="https://ja.wikipedia.org/wiki/%E5%B7%A1%E5%9B%9E%E5%86%97%E9%95%B7%E6%A4%9C%E6%9F%BB">巡回冗長検査.Wikipedia</a>
    930 </li>
    931 <li>
    932 [6] <a href="https://github.com/microsoft/uf2">USB Flashing Format (UF2).GitHub</a>
    933 </li>
    934 <li>
    935 [7] <a href="https://developer.arm.com/documentation/ddi0419/c/">ARMv6-M Architecture Reference Manual</a>
    936 </li>
    937 </ul>