www.mtkn.jp

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

rp2040_1.html (36610B)


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