commit a8e5efec8f0c940bbf1d1ce0dbc024a6c84f2002
parent 889cf19eb83f318b4f8aa2d848e4124b1ce98fa1
Author: Matsuda Kenji <info@mtkn.jp>
Date: Wed, 21 Feb 2024 17:05:13 +0900
update draft
Diffstat:
3 files changed, 572 insertions(+), 27 deletions(-)
diff --git a/man/draft/rp2040_2.html b/man/draft/rp2040_2.html
@@ -1,25 +1,24 @@
<h1>RP2040 SDKなし2 Clock, UART</h1>
<time>2023-05-10</time>
<p>
+今回はClockとUARTを設定してパソコンに繋ぎ、\
+キーボードからの入力をオウム返しするプログラムを作成する。
+<p>
+</p>
前回: <a href="rp2040_1.html">RP2040 SDKなしでLチカ</a><br>
ソースコード: <a href="https://git.mtkn.jp/rp2040">git</a>/ex2
</p>
<h2>動作環境</h2>
<ul>
-<li>Arch Linux 6.2.12-arch1-1
+<li>Void Linux
<ul>
- <li>arm-none-eabi-binutils 2.40-1</li>
+ <li>cross-arm-none-eabi-binutils-2.32_2</li>
<li>GNU Make 4.4.1</li>
+ <li>minicom version 2.7.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><a href="https://akizukidenshi.com/catalog/g/g108461/">FT234X 超小型USBシリアル変換モジュール</a>
</li>
</ul>
@@ -52,6 +51,322 @@ RP2040は電源を入れると、このリング発振回路を動作用のク
</p>
<h2>UART</h2>
+<p>
+Universal Asynchronous Receiver/Transmitterの略。\
+</p>
+
+<h2>プログラム</h2>
+<h3>初期設定</h3>
+<p>
+後で見るように、UARTの動作には多分水晶発振子とPLLが必要なので、\
+まずはそれを設定する。\
+起動後、メインのプログラムが読み込まれるまでの<code>boot2</code>は\
+前回と同じものである。\
+<code>main.s</code>ではまず前回と同様に初期スタックポインタとエントリーポイント\
+を定義する:
+</p>
+<pre><code> .section .vectors
+vectors:
+ .word 0x20040000 // initial SP
+ .word(reset+1) // entry point
+</code></pre>
+<p>
+続いて利用するサブシステムのリセットを解除する。\
+PLLとUARTが追加されている。\
+今回使うUARTはUART0だけである:
+</p>
+<pre><code> .section .text
+reset:
+ // unreset gpio, pll_sys, uart0
+ ldr r0, =(1 << 22 | 1 << 12 | 1 << 5) // uart0 | pll_sys | io_bank0
+ ldr r3, resets_base
+ ldr r1, atomic_clr
+ str r0, [r3, r1] // RESETS: RESET
+unreset_chk:
+ ldr r1, [r3, #0x8] // RESETS: RESET_DONE
+ tst r0, r1
+ beq unreset_chk
+
+/* ... */
+
+atomic_clr:
+ .word 0x00003000
+resets_base:
+ .word 0x4000c000
+</code></pre>
+
+<h3>GPIOの設定</h3>
+<p>
+次にGPIOの役割を設定する。\
+前回はLEDを点滅させるためにGPIO25をSIOに設定したが、\
+今回はGPIO0とGPIO1をUART0にする:
+</p>
+<pre><code> // set gpio functions
+ ldr r3, io_bank0_base
+ mov r0, #2 // uart0
+ mov r1, #0x4
+ str r0, [r3, r1] // IO_BANK0: GPIO0_CTRL
+ mov r1, #0xc
+ str r0, [r3, r1] // IO_BANK0: GPIO1_CTRL
+
+/* ... */
+
+io_bank0_base:
+ .word 0x40014000
+</code></pre>
+
+<h3>Clockの設定</h3>
+<p>
+Clockの設定をする。\
+まずは水晶発振子を起動する。\
+推奨発振子は起動してから周波数が安定するまで少し時間がかかるようで、\
+その間待たないといけない。\
+この時間は1msあれば十分だとデータシートに書いている。\
+この待ち時間はXOSC: STARTUPレジスタに、256サイクル単位で記述する。\
+データシートによると初期のリング発振子は最大で12MHzなので、\
+<code>(12 * 10^6 * 1 * 10^-3) / 256 = 47</code>をこのレジスタにセットする。\
+ところでデータシートではこの計算は水晶発振子の周波数で書かれている。\
+起動直後でまだ使えない水晶発振子の周波数を使うのはなんでやろ。\
+SDKでも<code>pico-sdk/src/rp2_common/hardware_xosc/xosc.c</code>で、
+</p>
+<pre><code>\
+#define STARTUP_DELAY (((((XOSC_MHZ * MHZ) / 1000) + 128) / 256) * PICO_XOSC_STARTUP_DELAY_MULTIPLIER)
+</code></pre>
+<p>
+と定義されている(PICO_XOSC_STARTUP_DELAY_MULTIPLIERは1)。\
+とりあえず47に設定しているが、試しに0や1にしても動いた。よくわからん。\
+</p>
+<p>
+待ち時間を設定したら、起動する。\
+XOSC: CTRLに起動用のコマンド的なものを入力し、周波数が安定するのを待つ。\
+</p>
+<p>
+以上を実装したのが以下のコード:
+</p>
+<pre><code> // setup xosc
+ ldr r3, xosc_base
+ mov r0, #47 // start up delay for 12MHz rosc (xosc?)
+ str r0, [r3, #0xc] // XOSC: STARTUP
+ ldr r0, =(0xfab << 12 | 0xaa0)
+ str r0, [r3, #0] // XOSC: CTRL
+wait_xosc:
+ ldr r0, [r3, #0x4] // XOSC: STATUS
+ lsr r0, r0, #31 // STABLE bit
+ beq wait_xosc
+
+/* ... */
+
+xosc_base:
+ .word 0x40024000
+</code></pre>
+
+<h3>PLLの設定</h3>
+<p>
+水晶発振子が起動できたので、次にPLLを設定する。\
+CPUが133MHzまで対応しているので133MHzになるようにした。\
+</p>
+<p>
+PLLは入力となる振動(ここでは水晶発振子の振動)を加工して\
+周波数を上げたり下げたりする。\
+出力の周波数は以下の式で決まる:
+</p>
+<pre>(FREF / REFDIV) * FBDIV / (POSTDIV1 * POSTDIV2)</pre>
+<p>
+FREFは入力の周波数(ここでは12MHz)で、その他の変数はプログラマが設定できる。\
+ただしデータシートによると(FREF / REFDIV)は5MHz以上でないといけないので、\
+REFDIVは1である。\
+また、FBDIVは16~320、POSTDIV1とPOSTDIV2は1~7で、POSTDIV1とPOSTDIV2に違う値を\
+代入する場合、POSTDIV1に大きい方を入れたほうが消費電力が少なくなるとのことなので、\
+133MHzにするには、FBDIV=133、POSTDIV1=6、POSTDIV=2とすればいい\
+(POSTDIV1=4、POSTDIV2=3も可能だが、pico-sdkに付属するvcocalc.pyというスクリプト\
+のコメントには、この2つの値の差が大きい方がいいと書いている)。
+</p>
+<p>
+PLL設定の手順は、FBDIVの設定、PLLとVCOの起動、VOCが安定するまで待機、\
+POSTDIV1とPOSTDIV2の設定、Post Dividerの起動、そして最後にシステムとUARTのクロック\
+を今設定したPLLに変更、である。\
+以上を実装したのが以下のコード:
+</p>
+<pre><code> // setup pll_sys 133MHz
+ ldr r3, pll_sys_base
+ // set feedback divider
+ mov r0, #133
+ str r0, [r3, #0x8] // PLL: FBDIV_INT
+ // power on pll and vco
+ ldr r0, =(1 << 5 | 1) // VCOPD | PD
+ ldr r1, atomic_clr
+ add r1, r1, #0x4
+ str r0, [r3, r1] // PLL: PWR
+ // wait vco to lock
+wait_vco:
+ ldr r0, [r3, #0] // PLL: CS
+ lsl r0, r0, #31
+ beq wait_vco
+ // setup post dividers
+ ldr r0, =(4 << 16 | 3 << 12)
+ str r0, [r3, #0xc] // PLL: PRIM
+ // power on post divider
+ mov r0, #8 // POSTDIVPD
+ str r0, [r3, r1] // PLL: PWR
+
+ // set system clock clksrc_pll_sys
+ ldr r3, clocks_base
+ ldr r0, =(0x0 << 5 | 0x1)
+ str r0, [r3, #0x3c] // CLOCKS: CLK_SYS_CTRL
+ // enable clk_peri
+ mov r0, #1
+ lsl r0, r0, #11
+ str r0, [r3, #0x48] // CLOCKS: CLK_PERI_CTRL
+
+/* ... */
+
+atomic_clr:
+ .word 0x00003000
+clocks_base:
+ .word 0x40008000
+pll_sys_base:
+ .word 0x40028000
+</code></pre>
+
+<h3>UARTの設定</h3>
+<p>
+データシートによるとUART設定の手順は以下の通り:
+</p>
+<ul>
+<li>リセットの解除</li>
+<li>clock_periの設定</li>
+<li>UARTの有効化</li>
+<li>FIFOの有効化</li>
+<li>転送速度の設定</li>
+<li>フォーマットの設定</li>
+</ul>
+<p>
+上の2つは既に終えている。\
+残りの部分はこの順番どおりに設定しても動かなかった。\
+C言語で書かれたサンプルを見ると、クロックを設定した後、\
+転送速度の設定、UARTの有効化、FIFOの有効化の順になっている。\
+そのとおりにすると動いた。\
+理由はよく理解していないが、変数を設定してから起動するほうが素直ではある。\
+</p>
+<p>
+転送速度はminicomのデフォルトである115200 baudに設定する。\
+データシート「4.2.7.1. Baud Rate Calculation」の計算式において、\
+クロック周波数を125MHzから133MHzに変えて計算して、\
+BRDI=72、BDRF=0.157(=10/64)となる。\
+この数値をUART: UARTIBRD、UART: UARTFBRDレジスタにそれぞれ代入する。
+</p>
+<p>
+UARTの有効化はUART: UARTCRレジスタのUARTENビットをセットすることで行う。\
+C言語のサンプルでは同じレジスタのRXE、TXEビットもセットしているが、\
+この2つはもともと1になっているのでほっといてよさそう。\
+</p>
+<p>
+FIFOの有効化はUART: UARTLCR_HレジスタのFENビットをセットすることで行う。\
+また、同じレジスタの他のビットで、データーのフォーマットを設定できる。\
+ここではminicomのデフォルトに合わせてWLENを8bitにする。\
+</p>
+<p>
+以上をまとめると以下のようになる:
+</p>
+<pre><code> // setup uart0
+ ldr r3, uart0_base
+ // set baudrate 115200
+ // BDRI = 72, BDRF = 0.157 (10 / 64)
+ mov r0, #72
+ str r0, [r3, #0x24] // UART: UARTIBRD
+ mov r0, #10
+ str r0, [r3, #0x28] // UART: UARTFBRD
+ // enable uart0
+ mov r0, #1 // UARTEN
+ ldr r1, atomic_set
+ add r1, r1, #0x30
+ str r0, [r3, r1] // UART: UARTCR
+ // enable FIFO and set format
+ ldr r0, =(3 << 5 | 1 << 4) // WLEN = 8, FEN = 1
+ str r0, [r3, #0x2c] // UART: UARTLCR_H
+
+/* ... */
+
+atomic_set:
+ .word 0x00002000
+uart0_base:
+ .word 0x40034000
+</code></pre>
+
+<h3>UARTの入出力</h3>
+<p>
+設定が終わったので実際にUARTの入出力を処理するコードを書く。\
+まずUARTからの出力は、出力したいバイトをUART: UARTDRに書き込むことで\
+行う。\
+その際、書き込まれたデータは一時的に出力用FIFOに保持されるので、このFIFO\
+が満杯でないことを確認する必要がある。\
+FIFOの状態はUART: UARTFRレジスタで確認できる。\
+このレジスタのTXFFの値が1であればデータを書き込めないので、\
+0になるまで待機する。\
+関数名は<code>putchar</code>にした。\
+また出力したいデータは<code>r0</code>レジスタにの下位8ビットに\
+入れられているものとした。\
+書き込めるデーターは8ビットだけなので、<code>0xff</code>と論理積をとってから\
+書き込んでいる:
+</p>
+<pre><code>putchar:
+ ldr r3, uart0_base
+ mov r1, #1
+ lsl r1, r1, #5 // TXFF
+txff:
+ ldr r2, [r3, #0x18] // UART: UARTFR
+ tst r1, r2
+ bne txff
+ mov r1, #0xff
+ and r0, r0, r1
+ str r0, [r3, #0] // UART: UARTDR
+ bx lr
+
+/* ... */
+
+uart0_base:
+ .word 0x40034000
+</code></pre>
+
+<p>
+入力はUART: UARTDRの下位8ビットを読むことで得られる。\
+UARTからの入力は、一時的に入力用FIFOに保存される。\
+このFIFOが空の状態でデータを読んでも意味がないので、\
+FIFOが空でないことを確認する必要がある。\
+これはUART: UARTFRレジスタのRXFEを読むことで確認できる。\
+本来は入力があったときに割り込みを発生させて、それまでは\
+CPUを休ませるか別の処理をさせておくべきだが、とりあえずここでは\
+ループでFIFOの状態を確認し続けている。\
+関数名は<code>getchar</code>にした。
+読み込んだデータは<code>r0</code>レジスタに保存している:\
+</p>
+<pre><code>getchar:
+ ldr r3, uart0_base
+ mov r1, #1
+ lsl r1, r1, #4 // RXFE
+rxfe:
+ ldr r2, [r3, #0x18] // UART: UARTFR
+ tst r1, r2
+ bne rxfe
+ ldr r0, [r3, #0] // UART: UARTDR
+ mov r1, #0xff
+ and r0, r0, r1
+ bx lr
+
+/* ... */
+
+uart0_base:
+ .word 0x40034000
+</code></pre>
+<p>
+あとはこの2つの関数をループの中で交互に呼び出せば、\
+オウム返しするだけのプログラムが完成する:
+</p>
+<pre><code>loop:
+ bl getchar
+ bl putchar
+ b loop
+</code></pre>
<h2>リング発振回路でUARTは動くんかな?</h2>
<p>\
@@ -231,7 +546,7 @@ pico-sdk/src/common/pico_sync/include/pico/mutex.h
何度か頑張って読もうとしたが、面白くないのでやめた。\
数百行のファイルをあっちからこっちから<code>include</code>してるし、\
大文字ばかりの変数だらけで目が痛い。\
-こんなものを扱えるというのはえらいええ頭してはるんやね<sup>†</sup>。\
+こんなものを扱えるというのはえらいええ頭してはるんやね。\
</p>
@@ -253,6 +568,3 @@ pico-sdk/src/common/pico_sync/include/pico/mutex.h
<a href="https://www5.epsondevice.com/ja/information/technical_info/osc.html">水晶発振器とは? 原理と仕組み、水晶振動子との違い、選び方のポイントを解説.エプソン水晶デバイス</a>
</li>
</ul>
-
-<p><sup>†</sup>僕は和歌山の人間である。</p>
-
diff --git a/pub/draft/rp2040_2.html b/pub/draft/rp2040_2.html
@@ -25,24 +25,23 @@
<h1>RP2040 SDKなし2 Clock, UART</h1>
<time>2023-05-10</time>
<p>
+今回はClockとUARTを設定してパソコンに繋ぎ、キーボードからの入力をオウム返しするプログラムを作成する。
+<p>
+</p>
前回: <a href="rp2040_1.html">RP2040 SDKなしでLチカ</a><br>
ソースコード: <a href="https://git.mtkn.jp/rp2040">git</a>/ex2
</p>
<h2>動作環境</h2>
<ul>
-<li>Arch Linux 6.2.12-arch1-1
+<li>Void Linux
<ul>
- <li>arm-none-eabi-binutils 2.40-1</li>
+ <li>cross-arm-none-eabi-binutils-2.32_2</li>
<li>GNU Make 4.4.1</li>
+ <li>minicom version 2.7.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><a href="https://akizukidenshi.com/catalog/g/g108461/">FT234X 超小型USBシリアル変換モジュール</a>
</li>
</ul>
@@ -59,6 +58,243 @@
<h2>UART</h2>
+<h2>プログラム</h2>
+<h3>初期設定</h3>
+<p>
+後で見るように、UARTの動作には多分水晶発振子とPLLが必要なので、まずはそれを設定する。起動後、メインのプログラムが読み込まれるまでの<code>boot2</code>は前回と同じものである。<code>main.s</code>ではまず前回と同様に初期スタックポインタとエントリーポイントを定義する:
+</p>
+<pre><code> .section .vectors
+vectors:
+ .word 0x20040000 // initial SP
+ .word(reset+1) // entry point
+</code></pre>
+<p>
+続いて利用するサブシステムのリセットを解除する。PLLとUARTが追加されている。今回使うUARTはUART0だけである:
+</p>
+<pre><code> .section .text
+reset:
+ // unreset gpio, pll_sys, uart0
+ ldr r0, =(1 << 22 | 1 << 12 | 1 << 5) // uart0 | pll_sys | io_bank0
+ ldr r3, resets_base
+ ldr r1, atomic_clr
+ str r0, [r3, r1] // RESETS: RESET
+unreset_chk:
+ ldr r1, [r3, #0x8] // RESETS: RESET_DONE
+ tst r0, r1
+ beq unreset_chk
+
+/* ... */
+
+atomic_clr:
+ .word 0x00003000
+resets_base:
+ .word 0x4000c000
+</code></pre>
+
+<h3>GPIOの設定</h3>
+<p>
+次にGPIOの役割を設定する。前回はLEDを点滅させるためにGPIO25をSIOに設定したが、今回はGPIO0とGPIO1をUART0にする:
+</p>
+<pre><code> // set gpio functions
+ ldr r3, io_bank0_base
+ mov r0, #2 // uart0
+ mov r1, #0x4
+ str r0, [r3, r1] // IO_BANK0: GPIO0_CTRL
+ mov r1, #0xc
+ str r0, [r3, r1] // IO_BANK0: GPIO1_CTRL
+
+/* ... */
+
+io_bank0_base:
+ .word 0x40014000
+</code></pre>
+
+<h3>Clockの設定</h3>
+<p>
+Clockの設定をする。まずは水晶発振子を起動する。推奨発振子は起動してから周波数が安定するまで少し時間がかかるようで、その間待たないといけない。この時間は1msあれば十分だとデータシートに書いている。この待ち時間はXOSC: STARTUPレジスタに、256サイクル単位で記述する。データシートによると初期のリング発振子は最大で12MHzなので、<code>(12 * 10^6 * 1 * 10^-3) / 256 = 47</code>をこのレジスタにセットする。ところでデータシートではこの計算は水晶発振子の周波数で書かれている。起動直後でまだ使えない水晶発振子の周波数を使うのはなんでやろ。SDKでも<code>pico-sdk/src/rp2_common/hardware_xosc/xosc.c</code>で、
+</p>
+<pre><code>#define STARTUP_DELAY (((((XOSC_MHZ * MHZ) / 1000) + 128) / 256) * PICO_XOSC_STARTUP_DELAY_MULTIPLIER)
+</code></pre>
+<p>
+と定義されている(PICO_XOSC_STARTUP_DELAY_MULTIPLIERは1)。とりあえず47に設定しているが、試しに0や1にしても動いた。よくわからん。</p>
+<p>
+待ち時間を設定したら、起動する。XOSC: CTRLに起動用のコマンド的なものを入力し、周波数が安定するのを待つ。</p>
+<p>
+以上を実装したのが以下のコード:
+</p>
+<pre><code> // setup xosc
+ ldr r3, xosc_base
+ mov r0, #47 // start up delay for 12MHz rosc (xosc?)
+ str r0, [r3, #0xc] // XOSC: STARTUP
+ ldr r0, =(0xfab << 12 | 0xaa0)
+ str r0, [r3, #0] // XOSC: CTRL
+wait_xosc:
+ ldr r0, [r3, #0x4] // XOSC: STATUS
+ lsr r0, r0, #31 // STABLE bit
+ beq wait_xosc
+
+/* ... */
+
+xosc_base:
+ .word 0x40024000
+</code></pre>
+
+<h3>PLLの設定</h3>
+<p>
+水晶発振子が起動できたので、次にPLLを設定する。CPUが133MHzまで対応しているので133MHzになるようにした。</p>
+<p>
+PLLは入力となる振動(ここでは水晶発振子の振動)を加工して周波数を上げたり下げたりする。出力の周波数は以下の式で決まる:
+</p>
+<pre>(FREF / REFDIV) * FBDIV / (POSTDIV1 * POSTDIV2)</pre>
+<p>
+FREFは入力の周波数(ここでは12MHz)で、その他の変数はプログラマが設定できる。ただしデータシートによると(FREF / REFDIV)は5MHz以上でないといけないので、REFDIVは1である。また、FBDIVは16~320、POSTDIV1とPOSTDIV2は1~7で、POSTDIV1とPOSTDIV2に違う値を代入する場合、POSTDIV1に大きい方を入れたほうが消費電力が少なくなるとのことなので、133MHzにするには、FBDIV=133、POSTDIV1=6、POSTDIV=2とすればいい(POSTDIV1=4、POSTDIV2=3も可能だが、pico-sdkに付属するvcocalc.pyというスクリプトのコメントには、この2つの値の差が大きい方がいいと書いている)。
+</p>
+<p>
+PLL設定の手順は、FBDIVの設定、PLLとVCOの起動、VOCが安定するまで待機、POSTDIV1とPOSTDIV2の設定、Post Dividerの起動、そして最後にシステムとUARTのクロックを今設定したPLLに変更、である。以上を実装したのが以下のコード:
+</p>
+<pre><code> // setup pll_sys 133MHz
+ ldr r3, pll_sys_base
+ // set feedback divider
+ mov r0, #133
+ str r0, [r3, #0x8] // PLL: FBDIV_INT
+ // power on pll and vco
+ ldr r0, =(1 << 5 | 1) // VCOPD | PD
+ ldr r1, atomic_clr
+ add r1, r1, #0x4
+ str r0, [r3, r1] // PLL: PWR
+ // wait vco to lock
+wait_vco:
+ ldr r0, [r3, #0] // PLL: CS
+ lsl r0, r0, #31
+ beq wait_vco
+ // setup post dividers
+ ldr r0, =(4 << 16 | 3 << 12)
+ str r0, [r3, #0xc] // PLL: PRIM
+ // power on post divider
+ mov r0, #8 // POSTDIVPD
+ str r0, [r3, r1] // PLL: PWR
+
+ // set system clock clksrc_pll_sys
+ ldr r3, clocks_base
+ ldr r0, =(0x0 << 5 | 0x1)
+ str r0, [r3, #0x3c] // CLOCKS: CLK_SYS_CTRL
+ // enable clk_peri
+ mov r0, #1
+ lsl r0, r0, #11
+ str r0, [r3, #0x48] // CLOCKS: CLK_PERI_CTRL
+
+/* ... */
+
+atomic_clr:
+ .word 0x00003000
+clocks_base:
+ .word 0x40008000
+pll_sys_base:
+ .word 0x40028000
+</code></pre>
+
+<h3>UARTの設定</h3>
+<p>
+データシートによるとUART設定の手順は以下の通り:
+</p>
+<ul>
+<li>リセットの解除</li>
+<li>clock_periの設定</li>
+<li>UARTの有効化</li>
+<li>FIFOの有効化</li>
+<li>転送速度の設定</li>
+<li>フォーマットの設定</li>
+</ul>
+<p>
+上の2つは既に終えている。残りの部分はこの順番どおりに設定しても動かなかった。C言語で書かれたサンプルを見ると、クロックを設定した後、転送速度の設定、UARTの有効化、FIFOの有効化の順になっている。そのとおりにすると動いた。理由はよく理解していないが、変数を設定してから起動するほうが素直ではある。</p>
+<p>
+転送速度はminicomのデフォルトである115200 baudに設定する。データシート「4.2.7.1. Baud Rate Calculation」の計算式において、クロック周波数を125MHzから133MHzに変えて計算して、BRDI=72、BDRF=0.157(=10/64)となる。この数値をUART: UARTIBRD、UART: UARTFBRDレジスタにそれぞれ代入する。
+</p>
+<p>
+UARTの有効化はUART: UARTCRレジスタのUARTENビットをセットすることで行う。C言語のサンプルでは同じレジスタのRXE、TXEビットもセットしているが、この2つはもともと1になっているのでほっといてよさそう。</p>
+<p>
+FIFOの有効化はUART: UARTLCR_HレジスタのFENビットをセットすることで行う。また、同じレジスタの他のビットで、データーのフォーマットを設定できる。ここではminicomのデフォルトに合わせてWLENを8bitにする。</p>
+<p>
+以上をまとめると以下のようになる:
+</p>
+<pre><code> // setup uart0
+ ldr r3, uart0_base
+ // set baudrate 115200
+ // BDRI = 72, BDRF = 0.157 (10 / 64)
+ mov r0, #72
+ str r0, [r3, #0x24] // UART: UARTIBRD
+ mov r0, #10
+ str r0, [r3, #0x28] // UART: UARTFBRD
+ // enable uart0
+ mov r0, #1 // UARTEN
+ ldr r1, atomic_set
+ add r1, r1, #0x30
+ str r0, [r3, r1] // UART: UARTCR
+ // enable FIFO and set format
+ ldr r0, =(3 << 5 | 1 << 4) // WLEN = 8, FEN = 1
+ str r0, [r3, #0x2c] // UART: UARTLCR_H
+
+/* ... */
+
+atomic_set:
+ .word 0x00002000
+uart0_base:
+ .word 0x40034000
+</code></pre>
+
+<h3>UARTの入出力</h3>
+<p>
+設定が終わったので実際にUARTの入出力を処理するコードを書く。まずUARTからの出力は、出力したいバイトをUART: UARTDRに書き込むことで行う。その際、書き込まれたデータは一時的に出力用FIFOに保持されるので、このFIFOが満杯でないことを確認する必要がある。FIFOの状態はUART: UARTFRレジスタで確認できる。このレジスタのTXFFの値が1であればデータを書き込めないので、0になるまで待機する。関数名は<code>putchar</code>にした。また出力したいデータは<code>r0</code>レジスタにの下位8ビットに入れられているものとした。書き込めるデーターは8ビットだけなので、<code>0xff</code>と論理積をとってから書き込んでいる:
+</p>
+<pre><code>putchar:
+ ldr r3, uart0_base
+ mov r1, #1
+ lsl r1, r1, #5 // TXFF
+txff:
+ ldr r2, [r3, #0x18] // UART: UARTFR
+ tst r1, r2
+ bne txff
+ mov r1, #0xff
+ and r0, r0, r1
+ str r0, [r3, #0] // UART: UARTDR
+ bx lr
+
+/* ... */
+
+uart0_base:
+ .word 0x40034000
+</code></pre>
+
+<p>
+入力はUART: UARTDRの下位8ビットを読むことで得られる。UARTからの入力は、一時的に入力用FIFOに保存される。このFIFOが空の状態でデータを読んでも意味がないので、FIFOが空でないことを確認する必要がある。これはUART: UARTFRレジスタのRXFEを読むことで確認できる。本来は入力があったときに割り込みを発生させて、それまではCPUを休ませるか別の処理をさせておくべきだが、とりあえずここではループでFIFOの状態を確認し続けている。関数名は<code>getchar</code>にした。
+読み込んだデータは<code>r0</code>レジスタに保存している:</p>
+<pre><code>getchar:
+ ldr r3, uart0_base
+ mov r1, #1
+ lsl r1, r1, #4 // RXFE
+rxfe:
+ ldr r2, [r3, #0x18] // UART: UARTFR
+ tst r1, r2
+ bne rxfe
+ ldr r0, [r3, #0] // UART: UARTDR
+ mov r1, #0xff
+ and r0, r0, r1
+ bx lr
+
+/* ... */
+
+uart0_base:
+ .word 0x40034000
+</code></pre>
+<p>
+あとはこの2つの関数をループの中で交互に呼び出せば、オウム返しするだけのプログラムが完成する:
+</p>
+<pre><code>loop:
+ bl getchar
+ bl putchar
+ b loop
+</code></pre>
+
<h2>リング発振回路でUARTは動くんかな?</h2>
<p>UARTの通信には正確なクロックが必要である。その為上では<code>clk_peri</code>として水晶発振子とPLLを用いた。ところがpico-examplesのhello_uartでは<code>main()</code>関数で水晶発振子を設定していない。そこでリング発振回路を用いてみたのだが、どうもうまく通信できない。出力されている正確な周波数も分からないのであきらめることにした。オシロスコープなんていうものは持っていない。</p>
@@ -151,7 +387,7 @@ pico-sdk/src/common/pico_sync/include/pico/mutex.h
<p>やっぱり水晶発振子じゃないとあかんのかな。</p>
<h2>CMake</h2>
-<p>上ではビルドしたバイナリを逆アッセンブルして読んだ。わざわざこんなことをしなくてもMakefile読めばなにがどうなって最終生成物に辿りつくのか分かればいいのだが、そうもいかない。このSDKとpico-examplesにはビルドシステムとしてCMakeなるものが使われている。これがどうも複雑でよく分からない。勉強する気にもならん。上で見た<code>crt0.S</code>や<code>runtime.c</code>といったファイルも<code>hello_uart</code>で本当に使われているものなのかもよく分からない。こんな煩雑なものは本当に必要なのかな。無駄に複雑にしてるだけとちゃうんかな。特に僕は勉強用に使ってるので、ソースコードの依存関係をもっと分かりやすくしてくれないと、内部でなにがどうなってるのか理解しにくい。何度か頑張って読もうとしたが、面白くないのでやめた。数百行のファイルをあっちからこっちから<code>include</code>してるし、大文字ばかりの変数だらけで目が痛い。こんなものを扱えるというのはえらいええ頭してはるんやね<sup>†</sup>。</p>
+<p>上ではビルドしたバイナリを逆アッセンブルして読んだ。わざわざこんなことをしなくてもMakefile読めばなにがどうなって最終生成物に辿りつくのか分かればいいのだが、そうもいかない。このSDKとpico-examplesにはビルドシステムとしてCMakeなるものが使われている。これがどうも複雑でよく分からない。勉強する気にもならん。上で見た<code>crt0.S</code>や<code>runtime.c</code>といったファイルも<code>hello_uart</code>で本当に使われているものなのかもよく分からない。こんな煩雑なものは本当に必要なのかな。無駄に複雑にしてるだけとちゃうんかな。特に僕は勉強用に使ってるので、ソースコードの依存関係をもっと分かりやすくしてくれないと、内部でなにがどうなってるのか理解しにくい。何度か頑張って読もうとしたが、面白くないのでやめた。数百行のファイルをあっちからこっちから<code>include</code>してるし、大文字ばかりの変数だらけで目が痛い。こんなものを扱えるというのはえらいええ頭してはるんやね。</p>
<h2>参考</h2>
@@ -172,9 +408,6 @@ pico-sdk/src/common/pico_sync/include/pico/mutex.h
<a href="https://www5.epsondevice.com/ja/information/technical_info/osc.html">水晶発振器とは? 原理と仕組み、水晶振動子との違い、選び方のポイントを解説.エプソン水晶デバイス</a>
</li>
</ul>
-
-<p><sup>†</sup>僕は和歌山の人間である。</p>
-
</article>
</main>
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>Sat, 17 Feb 2024 09:33:06 +0900</lastBuildDate>
-<pubDate>Sat, 17 Feb 2024 09:33:06 +0900</pubDate>
+<lastBuildDate>Wed, 21 Feb 2024 15:43:26 +0900</lastBuildDate>
+<pubDate>Wed, 21 Feb 2024 15:43:26 +0900</pubDate>
<docs>https://www.rssboard.org/rss-specification</docs>
<item>
<title>Gitサーバーの設定 on OpenBSD</title>