commit 527cf03c0fb609be0a87e0bb25b2f51a518d2fb9
parent 3c6cf67f41f3205baa05c9d714d3124f2aad9a32
Author: Matsuda Kenji <info@mtkn.jp>
Date: Tue, 25 Apr 2023 09:41:13 +0900
update
Diffstat:
3 files changed, 92 insertions(+), 87 deletions(-)
diff --git a/man/draft/rp2040_1.html b/man/draft/rp2040_1.html
@@ -26,6 +26,9 @@ SDKには便利な関数がたくさん用意されているので楽である
ということでSDKなしで開発してみる。\
とりあえず定番のLチカをば。\
</p>
+<p>
+ソースコード: <a href="https://git.mtkn.jp/rp2040">git</a>
+</p>
<h2>動作環境</h2>
<ul>
@@ -94,7 +97,7 @@ CRC32の場合、除数は33ビットである。\
</p>
<pre><code>\
1110101011011100110101101101111 入力(適当)
-1011 除数(4bit)
+1011 除数(4ビット)
-------------------------------
101101011011100110101101101111 結果(入力と除数のXOR)
1011
@@ -140,7 +143,7 @@ CRC32の場合、除数は33ビットである。\
<h3>細かいこと</h3>
<p>
-以上の計算をプログラムの先頭252バイトに対して、33bitの除数を用いて行う。\
+以上の計算をプログラムの先頭252バイトに対して、33ビットの除数を用いて行う。\
データの並べ方は、上の例において左側を先頭に、フラッシュROM上の0番地から順に、\
各バイトは最上位ビットから順に並べる。\
入力のデータは253バイト目から256バイト目に<code>0</code>をひっつけて計算する。\
@@ -351,10 +354,12 @@ over MSC (Mass Storage Class; aka removable flash drive)<sup>[6]</sup>.
<p>
RP2040のデータシート<sup>[3]</sup>「2.8.4.2 UF2 Format Details」\
-を見ると、8バイト目のFlagsは28バイト目に\
+を見ると、\
+8バイト目のFlagsは、28バイト目に\
ファミリーIDが書き込まれていることを示す<code>0x00002000</code>、\
-12バイト目は書き込みを行うフラッシュROMの先頭アドレスである\
-<code>0x10000000</code>、16バイト目の、各ブロックのデータサイズは256バイト、\
+12バイト目は、書き込みを行うフラッシュROMの先頭アドレスである\
+<code>0x10000000</code>に、各ブロックの先頭からの位置を足したもの、\
+16バイト目の、各ブロックのデータサイズは256バイト、\
28バイト目のファミリーIDは<code>0xe48bff56</code>である。\
あとは表の通り3つのマジックナンバーをセットし、32バイト目以降にデータを書き込み、\
20バイト目と24バイト目にブロックの通し番号と総数をそれぞれ書き込めばいい。\
@@ -395,7 +400,7 @@ main(int argc, char *argv[])
uint32_t mag1 = 0x0A324655;
uint32_t mag2 = 0x9E5D5157;
uint32_t flags = 0x00002000; // familyID present
- uint32_t saddr = 0x10000000;
+ uint32_t addr = 0x10000000;
uint32_t nbyte = 256;
uint32_t blk = 0;
uint32_t nblk = 0;
@@ -403,6 +408,8 @@ main(int argc, char *argv[])
uint8_t data[sdata];
uint32_t mag3 = 0x0AB16F30;
+ memset(data, 0, sdata);
+
if (argc != 3) {
fprintf(stderr, "Usage: %s src dst\n", argv[0]);
exit(1);
@@ -423,13 +430,12 @@ main(int argc, char *argv[])
fwrite32l(mag1, dst);
fwrite32l(mag2, dst);
fwrite32l(flags, dst);
- fwrite32l(saddr, dst);
+ fwrite32l(addr, dst);
fwrite32l(nbyte, dst);
fwrite32l(blk, dst);
- fwrite32l(nblk, dst); // dammy
+ fwrite32l(nblk, dst); // dummy
fwrite32l(famid, dst);
- memset(data, 0, sdata);
fread(data, 1, nbyte, src);
if (ferror(src)) {
fprintf(stderr, "Read error: %s.\n", argv[1]);
@@ -445,7 +451,7 @@ main(int argc, char *argv[])
fwrite32l(mag3, dst);
- saddr += nbyte;
+ addr += nbyte;
blk++;
nblk++;
}
@@ -506,15 +512,14 @@ RP2040はチップに内蔵されたこのSSIコントローラを通して、\
使っているようで、データシートのSSIコントローラの章は多分Synopsysの\
人が書いている。その他の章はRaspberry Pi財団の書いたブリティッシュイングリッシュ\
だが、この部分だけ多分ネイティブじゃない人の書いたいい加減な英語である。誤植も多い。\
-何日かかけて理解しようとしたが、あまりにも必要な情報が書かれていないので\
-よく分からん。不毛なので一旦諦めた。\
+何日かかけて理解しようとしたがよく分からん。不毛なので一旦諦めた。\
</p>
<p>
-RP2040には内部にもROMがあり、中にはバージョン情報や、電源を投入した時の動作、\
-その他便利な関数が書き込まれている。この関数のなかに外部のフラッシュROMと\
-SSIコントローラを設定するものも含まれているので今回はこれを利用した。\
+RP2040には内部にもROMがあり、はバージョン情報や電源を投入した時の動作、\
+その他便利な関数が書き込まれている。この関数の中に外部のフラッシュROMと\
+SSIコントローラを設定するものも含まれているので、今回はこれを利用した。\
ただしこの方法だとフラッシュROMとの通信方式がStandard SPIのままなので少し\
-遅い。詳しくはデータシートの「2.3.8. Bootrom Contents」を参照。
+遅いらしい。詳しくはデータシートの「2.3.8. Bootrom Contents」を参照。
</p>
<p>
RP2040の内蔵ROMの<code>0x00000018</code>番地に関数を検索するための関数がある。\
@@ -524,14 +529,10 @@ RP2040の内蔵ROMの<code>0x00000018</code>番地に関数を検索するため
8ビットシフトしたものと1文字目のORを取ったものを渡すことになっている。\
今回欲しい関数はフラッシュROMをXIPに設定するもの\
(<code>_flash_enter_cmd_xip()</code>)なので、<code>'C', 'X'</code>を渡す。\
-関数のポインタが返ってきたら、それを呼び出せばフラッシュROMとSSIはXIPモード\
+関数のポインタが返ってきて、それを呼び出せばフラッシュROMとSSIはXIPモード\
になる:
</p>
<pre><code>\
-.cpu cortex-m0plus
-.thumb
-
- .section .boot2
setup_xip:
ldr r3, rom_base
@@ -549,12 +550,13 @@ rom_base:
XIPの設定が完了すれば、次はメインのプログラムを実行するための準備である。\
エントリーポイントの指定、スタックポインタの初期値の設定、ベクターテーブル\
の設定である。初期スタックポインタとエントリーポイントはベクターテーブルの\
-<code>0x0</code>バイト目と<code>0x4</code>バイト目に書くことになっている[要出展]。\
-また、ベクターテーブルはメインのプログラムの先頭に擱くおとにする。\
+<code>0x0</code>バイト目と<code>0x4</code>バイト目に書くことになっている\
+<sup>[要出展]</sup>。\
+また、ベクターテーブルはメインのプログラムの先頭に置くことにする。\
メインのプログラムはFlash Second Stageが占有する256バイトの直後、\
-フラッシュROMの257バイト目から配置することにする。
+フラッシュROMの257バイト目から配置することにする。\
RP2040のベクターテーブルは<code>M0PLUS: VTOR(0xe0000000 + 0xed08)</code>という\
-レジスタに書き込むことで設定する。以上をまとめると以下のコードになる:
+レジスタに書き込むことで設定する。以上をまとめると以下のコードになる:\
</p>
<pre><code>\
ldr r0, flash_main
@@ -570,6 +572,9 @@ flash_main:
m0plus_vtor:
.word 0xe0000000 + 0xed08
</code></pre>
+<p>\
+なお以上のコードは<code>.boot2</code>という名前のセクションにしてある。
+</p>
<h2>メインのコード(<code>main.s</code>)<h2>
<h3>ベクターテーブル</h3>
@@ -582,21 +587,20 @@ m0plus_vtor:
ラベルに<code>0x1</code>を足している[要説明?]:
</p>
<pre><code>\
-.cpu cortex-m0plus
-.thumb
-
- .section .vectors
vectors:
.word 0x20040000 // initial SP
.word (reset+1)
</code></pre>
+<p>
+この部分のセクション名は<code>.vectors</code>である。
+</p>
<h3>GPIOの設定</h3>
<p>
電源投入直後、RP2040の周辺機器はリセット状態になっている。\
まずは今回利用するGPIOのリセット状態を解除する必要がある。\
-日本語だと意味が分かりにくいが英語でも同じようなものである。\
-データシートの2.14. Subsystem Resetsには以下のように書かれている:
+日本語だと意味が分かりにくいが英語でも分かりにくい。\
+データシートの「2.14. Subsystem Resets」には以下のように書かれている:
</p>
<blockquote cite="https://datasheets.raspberrypi.com/rp2040/rp2040-datasheet.pdf">
<p>
@@ -609,12 +613,12 @@ It is up to software to deassert the reset of peripherals it intends to use.
<code>0x0</code>バイト目のRESETS: RESEレジスタのうち利用したい周辺機器の\
ビットを<code>0x0</code>にすればいい。
GPIOはIO Bank 0なので(これ明記されてなくない?)、RESETS: RESETレジスタの\
-IO_BANK0(5番目のビット)を<code>0x0</code>にする。
+IO_BANK0(5番ビット)を<code>0x0</code>にする。
</p>
<h4>レジスタのアトミックなクリア</h4>
<p>
-RESETS: RESETレジスタのうち5番目のビットだけを<code>0x0</code>に\
-したい。この時、まずこのレジスタを読み込んでから<code>~(1 << 5)</code>と\
+RESETS: RESETレジスタのうち5番ビットだけを<code>0x0</code>に\
+したい。この時、まずこのレジスタを読み込んでから<code>~(1 << 5)</code>と\
論理積を取って同レジスタに書き戻してもいいのだが、RP2040にはこれを\
一回の<code>str</code>でしかもアトミックにできる便利な機能が用意されている。\
今回の場合アトミックかどうかは関係ないと思うが。\
@@ -626,9 +630,9 @@ RESETS: RESETレジスタのうち5番目のビットだけを<code>0x0</code>
にアクセスするとアトミックなXORが、<code>0x2000</code>を足したものは\
アトミックなセットが、<code>0x3000</code>を足したものはアトミックな\
クリアができる。つまりレジスタのアドレスに<code>0x3000</code>を足した\
-ものに、<code>0x1 << 5</code>を<code>str</code>すれば5番目のビットだけ\
+ものに、<code>0x1 << 5</code>を<code>str</code>すれば5番目のビットだけ\
<code>0x0</code>にして、他のビットは変更されない。\
-逆に指定したビットだけ立てて他は触らない場合は<code>0x2000</code>を、\
+逆に指定したビットだけ立てて他を触らない場合は<code>0x2000</code>を、\
あるいは指定したビットだけトグルしたい場合は<code>0x1000</code>を足したアドレス\
にアクセスすればいい。\
</p>
@@ -642,13 +646,11 @@ RESETS: RESETレジスタのうち5番目のビットだけを<code>0x0</code>
なので<code>0x1</code>が返ってくるまで繰り返し読み込んで<code>0x0</code>\
になるまで待機する。\
ところでこのレジスタはリセットの解除が完了したかどうか確かめるもの\
-なので、RESET_DONEという名前はどうなのか。リセットが完了したかどうか\
-確かめるものではないのに。
+なので、RESET_DONEという名前はどうなん?
<p>
以上から、GPIOのリセットを解除するのは以下のコード:
</p>
<pre><code>\
- .section .text
reset:
// unreset gpio
mov r0, #1
@@ -675,9 +677,9 @@ 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を使うみたいである。\
+同じCPUが載っているRaspberry Pi PicoはGPIO25番にLEDが半田付けされている。\
+25番にしたのはこれに合わせるためである。他のピンでもいい。\
+GPIOに1(3.3V)か0を印加するだけならこのSIOを使うみたいである。\
Single-cycleはCPUから操作したときに1クロックでその操作が完了する\
という意味らしい。SIOの詳しい説明はデータシートの「2.3.1 SIO」にある。\
</p>
@@ -723,8 +725,7 @@ sio_base:
<h3>LEDの点滅</h3>
<p>\
-ここまででGPIOの設定は完了したので、あとは実際にLEDに電圧を掛けるだけである\
-(電圧を掛けるはこの漢字なんや...)。\
+以上でGPIOの設定は完了したので、あとは実際にLEDに電圧を掛けるだけである。\
レジスタのアドレスに<code>0x1000</code>を足したものに書き込むと\
アトミックなレジスタのXORができると書いたが、SIOだけこの機能がサポート\
されていないようである。データシートの「2.1.2 Atomic Register Access」に、
@@ -737,12 +738,15 @@ although some individual registers (e.g. GPIO) have set/clear/xor aliases.
</p>
</blockquote>
<p>
-と書かれている。LEDを点滅させるためにGPIOの出力をトグルしたいので\
+と書かれている。ここにも書かれている通り、SIOの一部のレジスタには\
+アトミックなセット/クリア/XORをするためのレジスタが用意されている。\
+ここではLEDを点滅させるためにGPIOの出力をトグルしたいのでXOR用のレジスタを使う。\
SIO_BASE(<code>0xd0000000</code>)から<code>0x1c</code>バイト目の\
-SIO: GPIO_OUT_XORレジスタの25番ビットに<code>0x1</code>を書き込む。\
+SIO: GPIO_OUT_XORレジスタがそれである。\
+このレジスタの25番ビットに<code>0x1</code>を書き込めばいい。\
出力をトグルした後は少し間をおいて同じことを繰り返す。\
-間を置くためにここでは適当な数値を1づつ減らしていって0になったら\
-リターンする関数<code>delay</code>を作った。\
+間をおくためにここでは適当な数値を1づつ減らしていって0になったら\
+返る関数<code>delay</code>を作った。\
タイマーと割り込みを使ったほうが消費電力等で優位なようだが、\
面倒なのでとりあえずこれで:\
</p>
@@ -769,6 +773,9 @@ delay_loop:
sio_base:
.word 0xd0000000
</code></pre>
+<p>\
+なお以上のコードは<code>.text</code>セクションである。\
+</p>
<h2>リンカスクリプト</h2>
<p>
@@ -859,7 +866,7 @@ tools:
<p>
RP2040のボードをUSBデバイスモードでLinuxのパソコンに接続し、ex1ディレクトリで\
</p>
-<pre><code>
+<pre><code>\
$ make
# make flash
</code></pre>
diff --git a/pub/draft/rp2040_1.html b/pub/draft/rp2040_1.html
@@ -31,6 +31,9 @@
一般的にはSDK<sup>[4]</sup>をダウンロードしてあらかじめ用意されたライブラリを使って開発するようだが、これはビルドシステムとしてcmakeというのを使っている。これがOpenBSDでは何かエラーがでて動かなかった。僕はこういう便利ツールが嫌いだ。どうせ使わんからいいんやけど。関係ないけど途中から開発環境がLinuxに替わった。SDKには便利な関数がたくさん用意されているので楽である。ハードウェアの面倒な部分がプログラマから見えないようにしているからである。しかし今回はその面倒な部分に触れてみたくて買ったので、SDKを使うと意味がない。</p>
<p>
ということでSDKなしで開発してみる。とりあえず定番のLチカをば。</p>
+<p>
+ソースコード: <a href="https://git.mtkn.jp/rp2040">git</a>
+</p>
<h2>動作環境</h2>
<ul>
@@ -76,7 +79,7 @@ code ---------> object ------> elf --------> bin -------> with --------> crc3
入力のビットを一列に並べて、除数で「割り算」していく。この「割り算」が多項式の除算に似ているので、この除数をCRC多項式というらしい。ただし多項式の除算と違い、引き算するところをXORする。CRC32の場合、除数は33ビットである。33ビットで割ると32ビットの余りが残る。この余りがCRC32のチェックサムである。除数は色々あるようだが、標準的なものがWikipedia<sup>[5]</sup>に列挙されている。除数<code>1011</code>を使ったCRC3の計算の手順は以下の通り:
</p>
<pre><code>1110101011011100110101101101111 入力(適当)
-1011 除数(4bit)
+1011 除数(4ビット)
-------------------------------
101101011011100110101101101111 結果(入力と除数のXOR)
1011
@@ -121,7 +124,7 @@ code ---------> object ------> elf --------> bin -------> with --------> crc3
<h3>細かいこと</h3>
<p>
-以上の計算をプログラムの先頭252バイトに対して、33bitの除数を用いて行う。データの並べ方は、上の例において左側を先頭に、フラッシュROM上の0番地から順に、各バイトは最上位ビットから順に並べる。入力のデータは253バイト目から256バイト目に<code>0</code>をひっつけて計算する。これは多分予め長さが分からないデータでも計算できるようにしたかったからかな。除数は<code>0x104c11db7</code>である(最上位ビットは常に1なのでデータシートでは省略されている)。</p>
+以上の計算をプログラムの先頭252バイトに対して、33ビットの除数を用いて行う。データの並べ方は、上の例において左側を先頭に、フラッシュROM上の0番地から順に、各バイトは最上位ビットから順に並べる。入力のデータは253バイト目から256バイト目に<code>0</code>をひっつけて計算する。これは多分予め長さが分からないデータでも計算できるようにしたかったからかな。除数は<code>0x104c11db7</code>である(最上位ビットは常に1なのでデータシートでは省略されている)。</p>
<p>
入力データは1バイトづつ処理したいみたいである。多分通信等で使う都合である。この時XORは結合則が成り立つので1バイト処理した結果と次のバイトとをXORして次の処理の入力として利用することができる:
</p>
@@ -308,7 +311,7 @@ over MSC (Mass Storage Class; aka removable flash drive)<sup>[6]</sup>.
</blockquote>
<p>
-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バイト目にブロックの通し番号と総数をそれぞれ書き込めばいい。ブロックの通し番号はデータのついでに書き込めるが、総数はデータを全部さばいた後でないと分からないので、最後全てのブロックにまとめて書き込むようにした。できたのが以下のコード:
+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>
@@ -342,7 +345,7 @@ main(int argc, char *argv[])
uint32_t mag1 = 0x0A324655;
uint32_t mag2 = 0x9E5D5157;
uint32_t flags = 0x00002000; // familyID present
- uint32_t saddr = 0x10000000;
+ uint32_t addr = 0x10000000;
uint32_t nbyte = 256;
uint32_t blk = 0;
uint32_t nblk = 0;
@@ -350,6 +353,8 @@ main(int argc, char *argv[])
uint8_t data[sdata];
uint32_t mag3 = 0x0AB16F30;
+ memset(data, 0, sdata);
+
if (argc != 3) {
fprintf(stderr, "Usage: %s src dst\n", argv[0]);
exit(1);
@@ -370,13 +375,12 @@ main(int argc, char *argv[])
fwrite32l(mag1, dst);
fwrite32l(mag2, dst);
fwrite32l(flags, dst);
- fwrite32l(saddr, dst);
+ fwrite32l(addr, dst);
fwrite32l(nbyte, dst);
fwrite32l(blk, dst);
- fwrite32l(nblk, dst); // dammy
+ fwrite32l(nblk, dst); // dummy
fwrite32l(famid, dst);
- memset(data, 0, sdata);
fread(data, 1, nbyte, src);
if (ferror(src)) {
fprintf(stderr, "Read error: %s.\n", argv[1]);
@@ -392,7 +396,7 @@ main(int argc, char *argv[])
fwrite32l(mag3, dst);
- saddr += nbyte;
+ addr += nbyte;
blk++;
nblk++;
}
@@ -431,18 +435,14 @@ CRC32のチェックサムが書き込まれたバイナリファイルを、こ
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にデータをコピーすることなく命令を実行できるので、速くて便利だという。
</p>
<p>
-しかしこのSSIコントローラはSynopsysという会社のDW_apb_ssiというIPを使っているようで、データシートのSSIコントローラの章は多分Synopsysの人が書いている。その他の章はRaspberry Pi財団の書いたブリティッシュイングリッシュだが、この部分だけ多分ネイティブじゃない人の書いたいい加減な英語である。誤植も多い。何日かかけて理解しようとしたが、あまりにも必要な情報が書かれていないのでよく分からん。不毛なので一旦諦めた。</p>
+しかしこのSSIコントローラはSynopsysという会社のDW_apb_ssiというIPを使っているようで、データシートのSSIコントローラの章は多分Synopsysの人が書いている。その他の章はRaspberry Pi財団の書いたブリティッシュイングリッシュだが、この部分だけ多分ネイティブじゃない人の書いたいい加減な英語である。誤植も多い。何日かかけて理解しようとしたがよく分からん。不毛なので一旦諦めた。</p>
<p>
-RP2040には内部にもROMがあり、中にはバージョン情報や、電源を投入した時の動作、その他便利な関数が書き込まれている。この関数のなかに外部のフラッシュROMとSSIコントローラを設定するものも含まれているので今回はこれを利用した。ただしこの方法だとフラッシュROMとの通信方式がStandard SPIのままなので少し遅い。詳しくはデータシートの「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モードになる:
+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モードになる:
</p>
-<pre><code>.cpu cortex-m0plus
-.thumb
-
- .section .boot2
-setup_xip:
+<pre><code>setup_xip:
ldr r3, rom_base
ldrh r0, [r3, #0x14] // rom_func_table
@@ -456,9 +456,7 @@ rom_base:
</code></pre>
<p>
-XIPの設定が完了すれば、次はメインのプログラムを実行するための準備である。エントリーポイントの指定、スタックポインタの初期値の設定、ベクターテーブルの設定である。初期スタックポインタとエントリーポイントはベクターテーブルの<code>0x0</code>バイト目と<code>0x4</code>バイト目に書くことになっている[要出展]。また、ベクターテーブルはメインのプログラムの先頭に擱くおとにする。メインのプログラムはFlash Second Stageが占有する256バイトの直後、フラッシュROMの257バイト目から配置することにする。
-RP2040のベクターテーブルは<code>M0PLUS: VTOR(0xe0000000 + 0xed08)</code>というレジスタに書き込むことで設定する。以上をまとめると以下のコードになる:
-</p>
+XIPの設定が完了すれば、次はメインのプログラムを実行するための準備である。エントリーポイントの指定、スタックポインタの初期値の設定、ベクターテーブルの設定である。初期スタックポインタとエントリーポイントはベクターテーブルの<code>0x0</code>バイト目と<code>0x4</code>バイト目に書くことになっている<sup>[要出展]</sup>。また、ベクターテーブルはメインのプログラムの先頭に置くことにする。メインのプログラムはFlash Second Stageが占有する256バイトの直後、フラッシュROMの257バイト目から配置することにする。RP2040のベクターテーブルは<code>M0PLUS: VTOR(0xe0000000 + 0xed08)</code>というレジスタに書き込むことで設定する。以上をまとめると以下のコードになる:</p>
<pre><code> ldr r0, flash_main
ldr r1, m0plus_vtor
str r0, [r1, #0] // vector table
@@ -472,24 +470,25 @@ flash_main:
m0plus_vtor:
.word 0xe0000000 + 0xed08
</code></pre>
+<p>なお以上のコードは<code>.boot2</code>という名前のセクションにしてある。
+</p>
<h2>メインのコード(<code>main.s</code>)<h2>
<h3>ベクターテーブル</h3>
<p>
メインのコードの最初には上で説明したベクターテーブルを配置する。ここでは割り込みの処理は考えないので、初期スタックポインタとエントリーポイントだけである。初期スタックポインタはSRAMの最後?(<code>0x20040000</code>)、エントリーポイントはエントリーポイントのラベルを用いて設定した。ただしこのCPUはThumbモードなので、ラベルに<code>0x1</code>を足している[要説明?]:
</p>
-<pre><code>.cpu cortex-m0plus
-.thumb
-
- .section .vectors
-vectors:
+<pre><code>vectors:
.word 0x20040000 // initial SP
.word (reset+1)
</code></pre>
+<p>
+この部分のセクション名は<code>.vectors</code>である。
+</p>
<h3>GPIOの設定</h3>
<p>
-電源投入直後、RP2040の周辺機器はリセット状態になっている。まずは今回利用するGPIOのリセット状態を解除する必要がある。日本語だと意味が分かりにくいが英語でも同じようなものである。データシートの2.14. Subsystem Resetsには以下のように書かれている:
+電源投入直後、RP2040の周辺機器はリセット状態になっている。まずは今回利用するGPIOのリセット状態を解除する必要がある。日本語だと意味が分かりにくいが英語でも分かりにくい。データシートの「2.14. Subsystem Resets」には以下のように書かれている:
</p>
<blockquote cite="https://datasheets.raspberrypi.com/rp2040/rp2040-datasheet.pdf">
<p>
@@ -499,20 +498,19 @@ It is up to software to deassert the reset of peripherals it intends to use.
</blockquote>
<p>
リセット状態を解除するには、RESETS_BASE(<code>0x4000c000</code>)から<code>0x0</code>バイト目のRESETS: RESEレジスタのうち利用したい周辺機器のビットを<code>0x0</code>にすればいい。
-GPIOはIO Bank 0なので(これ明記されてなくない?)、RESETS: RESETレジスタのIO_BANK0(5番目のビット)を<code>0x0</code>にする。
+GPIOはIO Bank 0なので(これ明記されてなくない?)、RESETS: RESETレジスタのIO_BANK0(5番ビット)を<code>0x0</code>にする。
</p>
<h4>レジスタのアトミックなクリア</h4>
<p>
-RESETS: RESETレジスタのうち5番目のビットだけを<code>0x0</code>にしたい。この時、まずこのレジスタを読み込んでから<code>~(1 << 5)</code>と論理積を取って同レジスタに書き戻してもいいのだが、RP2040にはこれを一回の<code>str</code>でしかもアトミックにできる便利な機能が用意されている。今回の場合アトミックかどうかは関係ないと思うが。</p>
+RESETS: RESETレジスタのうち5番ビットだけを<code>0x0</code>にしたい。この時、まずこのレジスタを読み込んでから<code>~(1 << 5)</code>と論理積を取って同レジスタに書き戻してもいいのだが、RP2040にはこれを一回の<code>str</code>でしかもアトミックにできる便利な機能が用意されている。今回の場合アトミックかどうかは関係ないと思うが。</p>
<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>
+各レジスタには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>リセットの解除はすぐに完了するわけではないようである。リセットの解除が完了したかどうか確認するにはRESETS: RESET_DONEレジスタ(RESETS_BASEから<code>0x8</code>バイト目)の該当するビット(ここでは5番目のビット)を読む。この値が<code>0x1</code>であればリセットの解除が完了している。<code>0x0</code>であれば処理が進行中なので<code>0x1</code>が返ってくるまで繰り返し読み込んで<code>0x0</code>になるまで待機する。ところでこのレジスタはリセットの解除が完了したかどうか確かめるものなので、RESET_DONEという名前はどうなのか。リセットが完了したかどうか確かめるものではないのに。
+<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>
-<pre><code> .section .text
-reset:
+<pre><code>reset:
// unreset gpio
mov r0, #1
lsl r0, r0, #5 // io_bank0
@@ -533,7 +531,7 @@ resets_base:
</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>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
@@ -566,7 +564,7 @@ sio_base:
</code></pre>
<h3>LEDの点滅</h3>
-<p>ここまででGPIOの設定は完了したので、あとは実際にLEDに電圧を掛けるだけである(電圧を掛けるはこの漢字なんや...)。レジスタのアドレスに<code>0x1000</code>を足したものに書き込むとアトミックなレジスタのXORができると書いたが、SIOだけこの機能がサポートされていないようである。データシートの「2.1.2 Atomic Register Access」に、
+<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>
@@ -576,7 +574,7 @@ 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>
+と書かれている。ここにも書かれている通り、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>
<pre><code> // blink led on gpio25
ldr r4, sio_base
@@ -599,6 +597,7 @@ delay_loop:
sio_base:
.word 0xd0000000
</code></pre>
+<p>なお以上のコードは<code>.text</code>セクションである。</p>
<h2>リンカスクリプト</h2>
<p>
@@ -680,8 +679,7 @@ tools:
<p>
RP2040のボードをUSBデバイスモードでLinuxのパソコンに接続し、ex1ディレクトリで</p>
-<pre><code>
-$ make
+<pre><code>$ make
# make flash
</code></pre>
<p>
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>Mon, 24 Apr 2023 12:11:02 +0900</lastBuildDate>
-<pubDate>Mon, 24 Apr 2023 12:11:02 +0900</pubDate>
+<lastBuildDate>Tue, 25 Apr 2023 09:40:58 +0900</lastBuildDate>
+<pubDate>Tue, 25 Apr 2023 09:40:58 +0900</pubDate>
<docs>https://www.rssboard.org/rss-specification</docs>
<item>
<title>Xlibで遊んでみる6</title>