4
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Raspberry Pi Pico (RP2040) で水晶発振器と UART を使う

Posted at

この記事の目的

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本 (GPIN0GPIN1) ある。
また、水晶発振器の出力を入力とするPLLが2個 (pll_syspll_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 を変えても同じ通信速度を用いることができる。

ここまでをまとめると、以下の図になる。
赤い矢印は、デフォルトの設定を表す。

RP2040 のクロックの関係

これらに加え、USB・ADC・RTC 用のクロックとしてそれぞれ clk_usbclk_adcclk_rtc を設定できる。
これらの入力は、外部クロック、内蔵の発振器、PLLの出力から選択できる。 (clk_refclk_sysclk_peri は選択できない)
clk_usbclk_adc は限られた整数比の分周が、clk_rtc は小数比の分周が可能である。

クロックの入力の切り替え

clk_ref や clk_sys の入力の切り替え

clk_ref および clk_sys の入力の選択は、グリッチを起こさずに切り替えることが出来る部分と、切替時にグリッチを起こす部分に分かれている。
グリッチを起こさずに切り替えることが出来る部分の入力は、切替時にグリッチを起こす部分の出力、および入力の一部から選ぶことができる。

clk_ref と clk_sys の入力の選択

グリッチを起こさずに切り替えることが出来る部分については、それぞれ現在選択されている入力を表すレジスタがあり、このレジスタの値が意図した状態になったかをチェックすることで切り替えが完了したかをチェックすることができる。

clk_ref および clk_sys の入力を切替時にグリッチを起こす部分に入力されている信号に切り替えるには、以下の手順を行う。

  1. グリッチを起こさずに切り替えることが出来る部分の入力を、切替時にグリッチを起こす部分の出力以外に設定する
  2. この入力の切り替えが完了するまで待つ
  3. 切替時にグリッチを起こす部分の入力を、使用したい信号に切り替える
  4. グリッチを起こさずに切り替えることが出来る部分の入力を、切替時にグリッチを起こす部分の出力に設定する
  5. 切替時にグリッチを起こす部分の入力を、使用したい信号に切り替える

clk_peri の入力の切り替え

clk_peri にはグリッチを起こさずに切り替えることが出来る部分がなく、入力の切り替えはグリッチを起こす。

clk_peri の入力を切り替えるには、以下の手順を行う。

  1. クロックの出力を停止する設定にする
  2. クロックの出力が停止するまでの間 (現在の入力の2サイクル分) 待つ
  3. 入力を使用したい信号に切り替える
  4. クロックを出力させたい場合は、クロックを出力する設定にする

clk_usbclk_adcclk_rtc の入力の切り替えも、同様の手順で行う。

クロックの分周比の設定

クロックの分周比は、対応するレジスタに値を書き込むことで設定する。
設定を反映するタイミングは内部で調整されるため、分周比の変更時に対応するクロックの出力を停止しておく必要はない。
デフォルトの分周比は、いずれも1倍である。

今回はこの機能は用いないため、ここでは詳しく扱わない。
詳しくは、RP2040 のデータシートを参照してほしい。

水晶発振器の使用

今回は以下の設定を行い、システムを動かすクロックをリングオシレータから水晶発振器に切り替える。

  1. 水晶発振器を有効に設定する
  2. 水晶発振器の動作が安定するまで待つ
  3. clk_ref の入力を水晶発振器に切り替える
  4. clk_ref の入力の切り替えが完了するまで待つ
  5. リングオシレータを停止する

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 になっていることが確認できた。

GP21 に出力される信号

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_periclk_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 に設定する値は 78UARTFBRD に設定する値は 0.125×64 すなわち 8 となる。

ここで用いている商は、「clk_peri の周波数 ÷ ボーレート」の16分の1の64倍、すなわち4倍である。
そのため、以下のようにすることで、レジスタに書き込む値をプログラムで求めることができる。

  1. clk_peri の周波数を4倍 (2ビット左シフト) する
  2. その結果を設定したいボーレートで割る
  3. その結果 (小数点以下を切り捨てた商) の下位6ビットを UARTFBRD に、残りを UARTIBRD に書き込む

UART の有効化

フロー制御の形式の設定に用いた UARTCR レジスタには、送信および受信の有効フラグ、および UART 全体の有効フラグがある。
これらのフラグを表すビットを 1 にすることで、有効に設定する。
フロー制御の形式の設定と同時に行ってよい。

送信

RP2040 の UART における送信では、32 要素の FIFO (バッファ) が使用できる。
以下のようにすることで、UART によるデータの送信ができる。

  1. 送信用 FIFO が満杯でないことを確認する
  2. データレジスタに送信するデータを書き込む

受信

RP2040 の UART における受信では、32 要素の FIFO (バッファ) が使用できる。
以下のようにすることで、UART によるデータの受信ができる。

  1. 受信用 FIFO が空でないことを確認する
  2. データレジスタから受信したデータを読み込む

受信したデータを読み込む際、データレジスタの下位 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個出力された。

「hello, world」の出力

続いて文字を送ると、送った文字が3個ずつ送り返された。

送受信の実験

期待通りの動作ができていることがわかる。

まとめ

Raspberry Pi Pico (RP2040) におけるクロックの構成・クロックに水晶発振器を用いる方法・UART による通信の方法を確認した。

4
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?