この記事の目的
Raspberry Pi Pico (RP2040) で UART の送受信を行う。
その際、周波数がはっきりしたクロックを使いたいため、クロックを水晶発振器に切り替える。
この記事では、割り込み・DMA は扱わず、最低限の設定で UART を用いる。
前提
Raspberry Pi Pico や RP2040 の基本仕様や書き込み方については、
Raspberry Pi Pico でLチカ #RaspberryPiPico - Qiita
を参照してほしい。
RP2040 のデータシートは、
Buy a Raspberry Pi Pico – Raspberry Pi
に掲載されている。
この記事では、asm15 を用いて開発を行う。
クロックの設定
クロックの構成
RP2040 には、2種類の発振器 (リングオシレータ ROSC
と水晶発振器 XOSC
) がある。
さらに、外部からクロック信号を入力できる端子も2本 (GPIN0
・GPIN1
) ある。
また、水晶発振器の出力を入力とするPLLが2個 (pll_sys
・pll_usb
) ある。
clk_ref
は、外部クロック、発振器、pll_usb
の出力を入力としてとることができる。
(pll_sys
の出力は入力として選択できない)
デフォルトではリングオシレータ ROSC
を入力としている。
さらに、入力を限られた整数比で分周できる。
clk_ref
の出力は、周波数カウンタやタイマー (ウォッチドッグタイマーを含む) で用いられる。
clk_sys
は、clk_ref
、外部クロック、発振器、PLLの出力を入力としてとることができる。
デフォルトでは clk_ref
を入力としている。
さらに、入力を小数比で分周できる。
clk_sys
の出力は、メインのプロセッサやフラッシュメモリへのアクセスなどで用いられる。
clk_peri
は、clk_sys
、外部クロック、発振器、PLLの出力を入力としてとることができる。
デフォルトでは clk_sys
を入力としている。
clk_peri
の出力は、UART や SPI の通信速度の基準として用いられる。
これを clk_sys
と分けておくことで、clk_sys
を変えても同じ通信速度を用いることができる。
ここまでをまとめると、以下の図になる。
赤い矢印は、デフォルトの設定を表す。
これらに加え、USB・ADC・RTC 用のクロックとしてそれぞれ clk_usb
・clk_adc
・clk_rtc
を設定できる。
これらの入力は、外部クロック、内蔵の発振器、PLLの出力から選択できる。 (clk_ref
・clk_sys
・clk_peri
は選択できない)
clk_usb
・clk_adc
は限られた整数比の分周が、clk_rtc
は小数比の分周が可能である。
クロックの入力の切り替え
clk_ref や clk_sys の入力の切り替え
clk_ref
および clk_sys
の入力の選択は、グリッチを起こさずに切り替えることが出来る部分と、切替時にグリッチを起こす部分に分かれている。
グリッチを起こさずに切り替えることが出来る部分の入力は、切替時にグリッチを起こす部分の出力、および入力の一部から選ぶことができる。
グリッチを起こさずに切り替えることが出来る部分については、それぞれ現在選択されている入力を表すレジスタがあり、このレジスタの値が意図した状態になったかをチェックすることで切り替えが完了したかをチェックすることができる。
clk_ref
および clk_sys
の入力を切替時にグリッチを起こす部分に入力されている信号に切り替えるには、以下の手順を行う。
- グリッチを起こさずに切り替えることが出来る部分の入力を、切替時にグリッチを起こす部分の出力以外に設定する
- この入力の切り替えが完了するまで待つ
- 切替時にグリッチを起こす部分の入力を、使用したい信号に切り替える
- グリッチを起こさずに切り替えることが出来る部分の入力を、切替時にグリッチを起こす部分の出力に設定する
- 切替時にグリッチを起こす部分の入力を、使用したい信号に切り替える
clk_peri の入力の切り替え
clk_peri
にはグリッチを起こさずに切り替えることが出来る部分がなく、入力の切り替えはグリッチを起こす。
clk_peri
の入力を切り替えるには、以下の手順を行う。
- クロックの出力を停止する設定にする
- クロックの出力が停止するまでの間 (現在の入力の2サイクル分) 待つ
- 入力を使用したい信号に切り替える
- クロックを出力させたい場合は、クロックを出力する設定にする
clk_usb
・clk_adc
・clk_rtc
の入力の切り替えも、同様の手順で行う。
クロックの分周比の設定
クロックの分周比は、対応するレジスタに値を書き込むことで設定する。
設定を反映するタイミングは内部で調整されるため、分周比の変更時に対応するクロックの出力を停止しておく必要はない。
デフォルトの分周比は、いずれも1倍である。
今回はこの機能は用いないため、ここでは詳しく扱わない。
詳しくは、RP2040 のデータシートを参照してほしい。
水晶発振器の使用
今回は以下の設定を行い、システムを動かすクロックをリングオシレータから水晶発振器に切り替える。
- 水晶発振器を有効に設定する
- 水晶発振器の動作が安定するまで待つ
-
clk_ref
の入力を水晶発振器に切り替える -
clk_ref
の入力の切り替えが完了するまで待つ - リングオシレータを停止する
Raspberry Pi Pico には 12MHz の水晶が搭載されているため、これにより高精度の 12MHz のクロックを用いることができる。
clk_sys
および clk_peri
の入力はデフォルトの設定を用いるので、clk_ref
を参照し、これらのクロックも 12MHz となる。
実装
これまで解説してきた、クロックを水晶発振器に切り替える処理を実装する。
さらに、前回の記事 の要領で、clk_sys
の信号を 12,000,000 分の1に分周し、GP25 に出力する。
これにより、Raspberry Pi Pico に搭載された LED が 1Hz で点滅するはずである。
さらに、オシロスコープでの測定用に、clk_sys
の信号を 1,000 分の1に分周し、GP21 (Raspberry Pi Pico の 27 番ピン) に出力する。
これにより、GP21 には 12kHz の信号が出力されるはずである。
UF2FAMILY #E48BFF56
UF2BLOCK 256,#FF
ORG #10000000
R7 = @CLK_VALUES ' R7:定数テーブルのアドレス
' 水晶発振器を有効化する
R3 = [R7]L ' R3:水晶発振器のレジスタのアドレス
R2 = [R7 + 3]L ' R2:マスク
R0 = [R3]L ' R0:レジスタの値
R1 = [R7 + 9]W ' R1:レジスタに設定する値を構築する作業用
BIC R0, R2
R1 = R1 << 12
R0 |= R1
[R3]L = R0
' 水晶発振器が安定動作するまで待機する
@XOSC_WAIT
R0 = [R3 + 1]L
R0 = R0 << 1 ' トップビット (安定動作フラグ) を C フラグに入れる
IF CC GOTO @XOSC_WAIT
' clk_ref の入力を水晶発振器に切り替える
R3 = [R7 + 2]L ' R3:clk_ref 設定用のレジスタのアドレス
R0 = [R3]L ' R0:レジスタの値
R1 = 3
BIC R0, R1
R0 += 2
[R3]L = R0
' clk_ref の入力が水晶発振器に切り替わるまで待機する
R1 = 4
@CLK_REF_WAIT
R0 = [R3 + 2]L
R0 - R1
IF NE GOTO @CLK_REF_WAIT
' リングオシレータを停止する
R3 = [R7 + 1]L ' R3:リングオシレータのレジスタのアドレス
R1 = [R7 + 8]W ' R1:レジスタに設定する値を構築する作業用
R0 = [R3]L ' R0:レジスタの値
BIC R0, R2 ' R2:マスク (水晶発振器の有効化時に設定)
R1 = R1 << 12
R0 |= R1
[R3]L = R0
' GPIO のリセットを解除する
R7 = @IO_VALUES ' R7:定数テーブルのアドレス
R3 = [R7]L ' R3:リセット管理用レジスタのアドレス
R2 = `100000 ' R2:リセット解除用マスク
R0 = [R3]L ' R0:レジスタの値
BIC R0, R2
[R3]L = R0
' GPIO のリセット解除が完了するまで待機する
@GPIO_RESET_WAIT
R0 = [R3 + 2]L
R0 & R2
IF 0 GOTO @GPIO_RESET_WAIT
' sys_clk を分周して GPIO に出力する設定を行う
R3 = [R7 + 1]L ' R3:クロック出力設定用レジスタのアドレス
R1 = [R7 + 3]L ' R1:観測用分周比
R2 = [R7 + 4]L ' R2:Lチカ用分周比
[R3 + 1]L = R1 ' clk_gpout0 の分周比を 1,000 分の1に設定する
[R3 + 10]L = R2 ' clk_gpout3 の分周比を 12,000,000 分の1に設定する
R1 = `010_0_0110 ' ENABLE = 1, AUXSRC = CLK_SYS
R1 = R1 << 5
[R3]L = R1 ' clk_gpout0 を有効化し、clk_sys を出力させる
[R3 + 9]L = R1 ' clk_gpout3 を有効化し、clk_sys を出力させる
' クロックの出力をピンに反映する設定を行う
R3 = [R7 + 2]L ' GPIO21・GPIO25 設定用レジスタのアドレス
R1 = 8 ' ピン動作の上書きは行わず、クロックを出力する
[R3 + 1]L = R1 ' GPIO21 にクロックを出力させる
[R3 + 9]L = R1 ' GPIO25 にクロックを出力させる
' 処理が完了したので停止する
@END
WFI
GOTO @END
ALIGN 4
@CLK_VALUES
UDATAL #40024000 ' 水晶発振器のレジスタのアドレス
UDATAL #40060000 ' リングオシレータのレジスタのアドレス
UDATAL #40008030 ' clk_ref 設定用のレジスタのアドレス
UDATAL #00FFF000 ' 発振器の有効化/停止用マスク
UDATAW #D1E ' 発振器の停止コード
UDATAW #FAB ' 発振器の有効化コード
ALIGN 4
@IO_VALUES
UDATAL #4000C000 ' ペリフェラルのリセット管理用レジスタのアドレス
UDATAL #40008000 ' クロック出力設定用レジスタのアドレス
UDATAL #400140A8 ' GPIO21・GPIO25 設定用レジスタのアドレス
UDATAL #0003E800 ' 観測用分周比 ( 1000 << 8)
UDATAL #B71B0000 ' Lチカ用分周比 (12000000 << 8)
ORG #100000FC
CHECKSUM #10000000,252,CRC32MPEG2,#FF
書き込んで実行し、オシロスコープで GP21 の信号を確認すると、予想通り 12kHz になっていることが確認できた。
UART の使用
RP2040 には、UART0
および UART1
の2組の UART モジュールがある。
今回は、このうち UART0
を用いる。
また、GP0
ピンでデータを出力 (TX) し、GP1
ピンからデータを入力 (RX) する。
クロックの供給
RP2040 の UART モジュールは、プロセッサなどとのデータのやり取りに clk_sys
のクロックを、ボーレートの制御に clk_peri
のクロックを用いる。
このうち、clk_sys
は常に有効になっているが、clk_peri
はデフォルトでは無効なので、有効化しなければならない。
設定用レジスタ中の ENABLE
ビットを 1 にすることで、有効化する。
また、受信したデータを正常に受け渡すため、UART を用いる際のクロックの速度は
\textrm{clk_peri} \leq \frac{5}{3} \textrm{clk_sys}
を満たさなければならない。
clk_peri
として clk_sys
を用いる設定や、clk_peri
が clk_sys
より遅い設定は、この条件を満たす。
リセットの解除
前回の記事 で GPIO 設定用レジスタのリセットを解除したのと同様に、UART0
モジュールのリセットを解除する。
UART0
モジュールのリセットを制御するビットは、下から 22 ビット目 (0-origin) である。
また、この後 GPIO の設定も行うので、下から 5 ビット目 (0-origin) に割り当てられた GPIO 設定用レジスタのリセットも解除する。
さらに、プルアップ・プルダウンの設定に用いるレジスタのリセットも解除する。これは下から 8 ビット目 (0-origin) に割り当てられている。
clk_peri
を有効化しないと、UART0
モジュールのリセットの解除を指示しても解除が完了しないようである。
I/O ピンの割り当てと設定
GP0
ピンに機能 UART0 TX
を、GP1
ピンに機能 UART0 RX
を割り当てる。
これらの機能の番号はいずれも 2 である。
さらに、RP2040 の入出力ピンはデフォルトでプルダウンされている。
UART における通常時 (通信を行っていないとき) の信号は HIGH なので、受信に用いる GP1
ピンのプルダウンを解除し、プルアップを有効化する。
また、送信に用いる GP0
ピンのプルダウンも解除しておく。
プルアップ・プルダウンの設定について詳しくは、RP2040 のデータシートの 2.19.4. Pads を参照すること。
通信の速度と形式の設定
UART による通信を行う際は、以下の項目を取り決めておく必要がある。
- 通信速度
- 1フレームのデータビット数
- パリティビットの形式
- ストップビットの長さ
- フロー制御の形式
今回は、以下のように設定する。
項目 | 値 |
---|---|
通信速度 | 9600 bps |
1フレームのデータビット数 | 8 ビット |
パリティビットの形式 | パリティビットなし |
ストップビットの長さ | 1 ビット |
フロー制御の形式 | フロー制御なし |
これらの項目は、以下のレジスタを用いて設定できる。
詳細は RP2040 のデータシートの 4.2. UART を参照してほしい。
項目 | レジスタ |
---|---|
通信速度 |
UARTIBRD UARTFBRD
|
1フレームのデータビット数 パリティビットの形式 ストップビットの長さ |
UARTLCR_H |
フロー制御の形式 | UARTCR |
通信速度は、「clk_peri
の周波数 [Hz]」を「ボーレート [bps] の16倍」で割った商をレジスタに書き込むことで設定する。
UARTIBRD
にはこの商の整数部分を、UARTFBRD
にはこの商の小数部分の64倍を設定する。
これらのレジスタの値を設定後、UARTLCR_H
レジスタに何かを書き込むと、設定が反映される。
今回は、clk_peri
は 12MHz、ボーレートは 9600bps なので、商は 12000000÷9600=78.125 となる。
よって、UARTIBRD
に設定する値は 78
、UARTFBRD
に設定する値は 0.125×64 すなわち 8
となる。
ここで用いている商は、「clk_peri
の周波数 ÷ ボーレート」の16分の1の64倍、すなわち4倍である。
そのため、以下のようにすることで、レジスタに書き込む値をプログラムで求めることができる。
-
clk_peri
の周波数を4倍 (2ビット左シフト) する - その結果を設定したいボーレートで割る
- その結果 (小数点以下を切り捨てた商) の下位6ビットを
UARTFBRD
に、残りをUARTIBRD
に書き込む
UART の有効化
フロー制御の形式の設定に用いた UARTCR
レジスタには、送信および受信の有効フラグ、および UART 全体の有効フラグがある。
これらのフラグを表すビットを 1
にすることで、有効に設定する。
フロー制御の形式の設定と同時に行ってよい。
送信
RP2040 の UART における送信では、32 要素の FIFO (バッファ) が使用できる。
以下のようにすることで、UART によるデータの送信ができる。
- 送信用 FIFO が満杯でないことを確認する
- データレジスタに送信するデータを書き込む
受信
RP2040 の UART における受信では、32 要素の FIFO (バッファ) が使用できる。
以下のようにすることで、UART によるデータの受信ができる。
- 受信用 FIFO が空でないことを確認する
- データレジスタから受信したデータを読み込む
受信したデータを読み込む際、データレジスタの下位 8 ビットに受信したデータが格納され、その上のビットでそのデータに対応するエラーの状況を表している。
エラービットの意味は、下 (0-origin で 8 ビット目) から上に向かって順に以下のようになっている。
ビット | エラー名 | 内容 |
---|---|---|
8 | フレーミングエラー | ストップビットが有効でなかった |
9 | パリティエラー | パリティビットが期待した値でなかった |
10 | ブレークエラー | ブレーク状態が検出された |
11 | オーバーフローエラー | 受信用 FIFO が満杯のときにデータを受信した |
このうち、フレーミングエラー・パリティエラー・ブレークエラーは、読み取ったデータが無効であることを表している。
今回は、パリティは使用しておらず、ブレーク状態ならば必ずストップビットは無効なので、データが無効かをチェックするにはフレーミングエラーのみをチェックすればよい。
実装
クロックを水晶発振器に切り替える処理に続いて、UART を扱うプログラムを実装した。
今回のデモでは、まず「hello, world」と改行 (LF) を 10 回出力した後、入力を受け取り、受け取った文字を3個送り返す。
UF2FAMILY #E48BFF56
UF2BLOCK 256,#FF
ORG #10000000
R7 = @CLK_VALUES ' R7:定数テーブルのアドレス
' 水晶発振器を有効化する
R3 = [R7]L ' R3:水晶発振器のレジスタのアドレス
R2 = [R7 + 3]L ' R2:マスク
R0 = [R3]L ' R0:レジスタの値
R1 = [R7 + 9]W ' R1:レジスタに設定する値を構築する作業用
BIC R0, R2
R1 = R1 << 12
R0 |= R1
[R3]L = R0
' 水晶発振器が安定動作するまで待機する
@XOSC_WAIT
R0 = [R3 + 1]L
R0 = R0 << 1 ' トップビット (安定動作フラグ) を C フラグに入れる
IF CC GOTO @XOSC_WAIT
' clk_ref の入力を水晶発振器に切り替える
R3 = [R7 + 2]L ' R3:clk_ref 設定用のレジスタのアドレス
R0 = [R3]L ' R0:レジスタの値
R1 = 3
BIC R0, R1
R0 += 2
[R3]L = R0
' clk_ref の入力が水晶発振器に切り替わるまで待機する
R1 = 4
@CLK_REF_WAIT
R0 = [R3 + 2]L
R0 - R1
IF NE GOTO @CLK_REF_WAIT
' リングオシレータを停止する
R3 = [R7 + 1]L ' R3:リングオシレータのレジスタのアドレス
R1 = [R7 + 8]W ' R1:レジスタに設定する値を構築する作業用
R0 = [R3]L ' R0:レジスタの値
BIC R0, R2 ' R2:マスク (水晶発振器の有効化時に設定)
R1 = R1 << 12
R0 |= R1
[R3]L = R0
' clk_peri を有効化する
R7 = @UART_VALUES ' R7:定数テーブルのアドレス
R3 = [R7]L ' R3:clk_peri 設定用レジスタのアドレス
R0 = 1
R0 = R0 << 11 ' ENABLE ビットは下から 11 番目 (0-origin)
[R3]L = R0 ' 入力として clk_sys を選択し、clk_peri を有効化する
' 各ペリフェラルのリセットを解除する
R3 = [R7 + 1]L ' R3:リセット管理用レジスタのアドレス
R2 = [R7 + 5]L ' R2:リセット解除用マスク
R0 = [R3]L ' R0:レジスタの値
BIC R0, R2
[R3]L = R0
' ペリフェラルのリセット解除が完了するまで待機する
@GPIO_RESET_WAIT
R0 = [R3 + 2]L
R0 &= R2
R0 - R2
IF NE GOTO @GPIO_RESET_WAIT
' 入出力の設定を行う
R3 = [R7 + 2]L ' R3:GPIO 設定用レジスタのアドレス
R1 = 2 ' ピン動作の上書きは行わず、UART 関係の動作を行う
[R3 + 1]L = R1 ' GPIO0 に UART0 TX を割り当てる
[R3 + 3]L = R1 ' GPIO1 に UART0 RX を割り当てる
R3 = [R7 + 3]L ' R3:プルアップ・プルダウン設定用レジスタのアドレス
R1 = 4 ' R1:プルダウン設定用マスク
R2 = 8 ' R2:プルアップ設定用マスク
R0 = [R3 + 1]L
BIC R0, R1
[R3 + 1]L = R0 ' GPIO0 のプルダウンを無効化する
R0 = [R3 + 2]L
BIC R0, R1
R0 |= R2
[R3 + 2]L = R0 ' GPIO1 のプルダウンを無効化し、プルアップを有効化する
' UART のボーレートを 9600bps に設定する
R3 = [R7 + 4]L ' R3:UART0 のレジスタのアドレス
R0 = 78
[R3 + 9]L = R0 ' UARTIBRD = 78
R0 = 8
[R3 + 10]L = R0 ' UARTFBRD = 8
' UART のパラメータを設定する
R0 = `01110000 ' データ8ビット、FIFO有効、ストップ1ビット、パリティなし、ブレーク送信しない
[R3 + 11]L = R0 ' UARTLCR_H を設定する
R0 = `00000011 ' CTS制御無効、RTS制御無効、受信有効、送信有効
R0 = R0 << 8
R0 += `00000001 ' ループバック無効、IrDA無効、UART有効
[R3 + 12]L = R0 ' UARTCR を設定する
' UART の送信を行う
R4 = 10 ' R4:メッセージを送信する回数
@UART_TX_LOOP
R2 = @UART_MSG ' R2:メッセージのアドレス
GOTO @UART_TX_LOOP2_START
@UART_TX_LOOP2
R0 = [R3 + 6]L ' UART の状態を取得する
R0 = R0 >> 6 ' TX FIFO の満杯フラグを C フラグに入れる
IF CS GOTO @UART_TX_LOOP2 ' TX FIFO に空きが出るまで待機する
[R3]L = R1 ' 送信するデータを TX FIFO に入れる
R2 += 1 ' 次の文字の送信に移る
@UART_TX_LOOP2_START
R1 = [R2] ' R1:送信する文字
R1 & R1 ' 0があったらメッセージの終わりとみなす
IF !0 GOTO @UART_TX_LOOP2
R4 -= 1 ' メッセージを送信した回数をカウントする
IF !0 GOTO @UART_TX_LOOP ' 送信回数が残っているなら、次の送信に移る
' UART の受信を行う
@UART_RX_LOOP
R0 = [R3 + 6]L ' UART の状態を取得する
R0 = R0 >> 5 ' RX FIFO の空フラグを C フラグに入れる
IF CS GOTO @UART_RX_LOOP ' RX FIFO が空の間待機する
R0 = [R3]L ' RX FIFO からデータを取得する
R1 = R0 >> 9 ' フレーミングエラーフラグを C フラグに入れる
IF CS GOTO @UART_RX_LOOP ' フレーミングエラーであれば、無視する
' 受信した文字を3個送り返す
R2 = 3
@UART_RX_REPLY_LOOP
R1 = [R3 + 6]L ' UART の状態を取得する
R1 = R1 >> 6 ' TX FIFO の満杯フラグを C フラグに入れる
IF CS GOTO @UART_RX_REPLY_LOOP ' TX FIFO に空きが出るまで待機する
[R3]L = R0 ' 送信するデータを TX FIFO に入れる
R2 -= 1 ' 送り返す数を減らす
IF !0 GOTO @UART_RX_REPLY_LOOP
GOTO @UART_RX_LOOP
ALIGN 4
@CLK_VALUES
UDATAL #40024000 ' 水晶発振器のレジスタのアドレス
UDATAL #40060000 ' リングオシレータのレジスタのアドレス
UDATAL #40008030 ' clk_ref 設定用のレジスタのアドレス
UDATAL #00FFF000 ' 発振器の有効化/停止用マスク
UDATAW #D1E ' 発振器の停止コード
UDATAW #FAB ' 発振器の有効化コード
ALIGN 4
@UART_VALUES
UDATAL #40008048 ' clk_peri 設定用レジスタのアドレス
UDATAL #4000C000 ' ペリフェラルのリセット管理用レジスタのアドレス
UDATAL #40014000 ' GPIO 設定用レジスタのアドレス
UDATAL #4001C000 ' プルアップ・プルダウン設定用レジスタのアドレス
UDATAL #40034000 ' UART0 のレジスタのアドレス
UDATAL #00400120 ' UART0・PADS_BANK0・IO_BANK0 のリセットを解除する用のマスク
ALIGN 4
@UART_MSG
UDATA "hello, world\n", 0
ORG #100000FC
CHECKSUM #10000000,252,CRC32MPEG2,#FF
書き込んで実行すると、まず「hello, world」が10個出力された。
続いて文字を送ると、送った文字が3個ずつ送り返された。
期待通りの動作ができていることがわかる。
まとめ
Raspberry Pi Pico (RP2040) におけるクロックの構成・クロックに水晶発振器を用いる方法・UART による通信の方法を確認した。