0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

6桁7セグメントLEDを Shrike-lite で駆動してみる

Last updated at Posted at 2026-02-01

きっかけは X

X に

という投稿が流れてきたのが始まりでした。ForgeFPGA ですと?

その RP2040 と ForgeFPGA (SLG47910) が載っている FPGA ボードらしい、という情報だけで、下調べもろくにせずに翌日開店直後の秋月に駆け込んで購入しました。

気に入ったので、紹介させていただきます。

さぁ、みんなも Shrike-lite を買おう(秋月では在庫僅少。無かったらごめん)!

秋月の Shrike-lite ページ
https://akizukidenshi.com/catalog/g/g131458/


これ動かしてみたら?

早々に知人にこのボードのことを話してみると既に知ってるようで、
これ動かしてみたら?と下の情報を教えてもらう。
https://fpga.tokyo/pmod-multisegment-sp-rev-a/

動かす対象は 6桁7セグメントLED表示器 OSL60362-LX。
https://akizukidenshi.com/catalog/g/g117987/

LED ドライバは 74HC595。
https://akizukidenshi.com/catalog/g/g114053/
この手のLED表示器を動かすにはよく使われている模様。

では、早速!
ただし Shrike-lite 以外なにも持ってないので、翌週部品を揃えに再び秋月に訪問。ユニバーサル基板も用意したけど、とりあえずブレッドボードで組んでみました。

ちなみに設計データは以下に置いたので参考までにどうぞ。
https://github.com/ttrsato/shirike-lite/tree/main/7_seg_disp_drv

ではどういうふうに回路とプログラムを作ってみたのかを、つらつら書いてみます。


仕様を検討する

6桁7セグメントLED表示器 OSL60362-LX

まずはこれがどんなものか知る必要があるので、データシートを確認しました。

LED の接続はマトリクス状で、アノード側の A-G,DP と DIG.X と組み合わせて任意の LED を光らせられます。

光らせたい LED につながる DIG.X は L、それ以外の DIG.X は H、光らせたい A-G.DP の端子をHにすれば点灯です。

全ての LED は同時に点灯、非点灯させることができないので時分割で DIG.1~DIG.6 それぞれにつながる LED を順次点灯をします。

スクリーンショット 2026-01-31 10.17.37.png

電気的特性も確認しておきます。

IF=20mA, VF=2.1V ということがわかります。抵抗値の検討に使えますね。

システム全体

FPGA (SLG47910) に 6桁のデータを保持する表示メモリを内蔵させ、そのデータに従い表示タイミングを作り出します。RP2040 は FPGA の表示メモリに動的に表示データを書き込む制御を行います。

表示タイミングは伝統的で、ちらつきが感じにくい1/60秒周期に決定。
PMOD には 74HC595 それぞれの SER, SRCLK, RCLK 及び OE が繋がれています。OE は今回特に制御してないので、回路から L にフォースしてます。

システム全体はこんな感じ。

スクリーンショット 2026-01-31 10.13.51.png

  • 74HC595 (COM)
    OSL60362-LR の DIG.X 6本を駆動します。点灯させたい DIG 一つだけ L でそれ以外は H。
  • 74HC595 (SEG)
    OSL60362-LR の A-G,DP の計8本を駆動します。点灯したいところはH、それ以外はL。
  • Shrike-lite
    • SLG47910
      • COM コモン
        74HC595 (COM)のシリアル入力パターンを生成します
        DISP MEM から表示データを読み出すタイミングを作ります
        RP2040 への同期信号 v_sync もここで生成します
      • SEG セグメント
        74HC595 (SEG)のシリアル入力パターンを生成します
        DISP MEM から読み出された表示データより 74HC595 の入力パターンを生成します
      • DISP MEM 表示メモリ
        BRAM で作られた6桁の表示したいデータを書く桁1バイトで保持しているメモリです。COM からの同期タイミングで読み出され、また RP2040 から SPI 経由で書き換えられます
      • SPI
        RP2040 とのシリアルインターフェースで、受け側 (Target)です。
        2byte のフォーマットで上位バイト側が表示メモリのアドレス、下位バイト側がデータです。とりあえず最大動作周波数は。。適当
    • RP2040
      Shrike-lite に SPI 経由で表示メモリを書き換えます。今回は初期化時に SLG47910 のビットストリームも書き換えます。

タイミングチャート

ブロック図を書いて(上記)、機能分割と個々のブロックの役割を決めたらタイミングチャートを起こします。

その前に、74HC595 と、BRAM の AC 特性を確認します。

74HC595 の AC 特性

ディスクリートあんまり扱ったことないので、ホールド時間が気になる。
3nsか。。だよね。

データシートにあるように Hold 時間を確実に稼ぐよう SRCLK の立ち下がりで SER を作るのが無難。Setup も Fmax 抑えとけば余裕でしょう。RCLK も SER が SRCLK 立ち上がりで SR レジスタにラッチされることを考えると、RCLK の立ち上がりでそのデータを拾うので SRCLK の立ち下がりと RCLK 立ち上がりを同じにすると無難ですね。

BRAM の AC 特性

SLG47910 Datasheet からこれを見つけたけど肝心の tsu, th の値が見つからない。。もうここは普通?の同期回路の作り方で大丈夫だと見切りをつけた。知ってもどうなるわけでも無いし、BRAM に対して立ち下がりで出したり受けたり考えるのはナンセンス。

全体のタイミングチャート

以上を踏まえタイミングチャートを描いたのが以下。
スクリーンショット 2026-01-31 11.21.01.png

ガチ勢ならこれぐらいの規模なら別に描かなくてもシミュレーション流しながらトライアンドエラーで作れちゃうんでしょうが、あえてタイミングチャート描いてみました。

僕はもうこれで満足です。
実装も検証も実機で動かすのも面倒くさいです(おいおい)。


回路を作成する前に

公式のドキュメントですが、これ見れば環境構築できて L チカができます。
https://vicharak-in.github.io/shrike/index.html

ファイル構成

ファイルはこのように構成しました。RTL そのものの名前の制約は特に無いですが、トップモジュールの指定と GPIO に出したいピンには Verilog Style Guide に従ってアトリビュートを設定する必要があるのでそれだけ注意。BRAM もトップモジュールの外に置かれるので、必要なピンを出して以下のアトリビュートも設定します。
(* iopad_external_pin *)

テストベンチファイルの名前に関してはサフィックスには _tb が付いてないと勝手に付けられます。

ちなみにシミュレーションや合成の対象になるよう、ファイルの追加、削除、名前の変更は GUI 上から行う必要があるようです。メニュー、ボタン、Source ブラウザを右クリックでそれぞれのファイルを作ります。

ただし RTL コーディングそのものはこの ForgeFPGA Workshop のエディタである必要はなく、一旦ファイルを準備したら、VSCode や Emacs その他お好きなエディタで編集すると変更は即座に反映されるので問題なかったです。

Module Library (SPI)

SPI に関して書き起こすのも面倒だったので、お試しも兼ねて Module Library を使ってみました。ソフトIPですね。目的が回路を作る勉強じゃなければ使えるものは使う。

SPI モジュールを GUI から作ると Module Library フォルダの下に置かれ、同時にこのライブラリ用のテストベンチが自動で生成されます(いらないのでテストベンチは消しました)。

BRAM (ハードマクロ)

BRAM が使えると便利だろうということで表示メモリとして使ってみました。ハードマクロなので LUT を消費しなくて済む。

既に書きましたが、BRAM は RTL のトップモジュールの外側に置かれます。
トップからピンを出して I/O Planner で接続し、制御することになります。
また、GUI から BRAM を有効にする必要があるので、その設定をします。

スクリーンショット 2026-01-31 12.06.59.png

この画面で、BRAM0 を使いたいので North BRAM を Enable にします。Memory retention もついでに。右側の Components の BRAM にもチェックが入ってる必要あるかと思います。

データシートより。BRAM0 は North ですね。

I/O planner では、BRAM と CLK カテゴリーに BRAM 設定があります。BRAM0_WRITE_CLK と BRAM0_READ_CLK がどこにあるのかわからず迷いました。

動作に関してはデータシート通りなのだろうから、それに従います。
BRAMx_WCLKINV 端子は反転することがなければ I/O Planner で特に信号アサインせずともOKでした。

クロック構成

特に初期設定からなにも触ってません。SLG47910 は 50MHz の内蔵 OSC が使えるのでそれを使ってます。これ一本で、ゲートなどもしてません。
IO Planner で OSC_CLK をクロック入力ピンにアサインしてます。

ところで、回路を作成するにあたり同期回路を作るわけですが、最初は分周して作った遅いクロックで回路全体を駆動しようとしました。ところがビットストリームを作る段階で複数のクロックがなんやらかんやらと怒られたので、50MHz で駆動する回路に書き換えました。

もしかしたらなんか対処方法があったのかもとは思ったけど、完全同期にしても構わないなと思って。

あと今回は速いクロックはいらないので PLL 使いませんでした。

GPIO

既存の PMOD モジュールに合わせるのでピン配を合わせます。
実はブレッドボードで動かせた後にいただいたPMODモジュールに置き換えたら上下間違って一発目で動かず。。orz

Shrike-lite と PMOD の端子表

74HC595 Shrike PMOD PMOD Shrike 74HC595
COM_SER F14 7 1 F15 COM_SRCLK
COM_RCLK F12 8 2 F13 COM_OE
SEG_SER F10 9 3 F11 SEG_SRCLK
SEG_RCLK F08 10 4 F9 SEG_OE
GND 11 5 GND
V3.3 12 6 V3.3

GPIO を出力で使いたい場合は、_oe を制御する端子を回路のトップから出す必要があるのですが、一つの信号で複数の GPIO の oe を駆動することはできず、それぞれ個別の端子で駆動せざるをえなく面倒です。

試してはいないのですが以下の画面にて、GPIO の Output mode やプルアップ、駆動能力を設定できそうです。

スクリーンショット 2026-01-31 13.56.50.png


回路

特筆すべきことはやってないんですが、強いて言えば全ての回路は 50MHz で駆動するので、clk_div.v というモジュールを作ってそこで 1/120秒の定期的にパルスを出すタイミング信号作ってます。COM/SEG などはこのパルス信号基準に動かしています。

あとは回路みてください。


シミュレーション

ビヘイビア

シミュレーションを流すに当たって、周辺回路のビヘイビア(振る舞いを模倣する何か)を準備しておくと何かと便利。同期回路のシミュレーションならオシレータは準備するでしょうが、今回は 74HC595 と BRAM のビヘイビアなども用意してシミュレーションに適用しました。データシートを参考に作っています。

ちなみにビヘイビアはそれぞれ個別のファイルにしたいところでしたが、やり方がわからず、テストベンチファイルの中に module 宣言をしてビヘイビアを記述しています。

74HC595

xoe=1'b1 の時に HiZ 出力を出すのがミソ。ちゃんと制御間違いを見つけられるようにしている。lr はリセットがないとか。

sn74hc595.v
module sn74hc595
  (
   input  xsrclr,
   input  srclk,
   input  ser,
   input  rclk,
   input  xoe,
   output qa,
   output qb,
   output qc,
   output qd,
   output qe,
   output qf,
   output qg,
   output qh,
   output qhd
   );

  reg [7:0] sr;
  reg [7:0] lr;
  assign {qh, qg, qf, qe, qd, qc, qb, qa} = (xoe) ? 8'bzzzz_zzzz : lr;
  assign qhd = sr[7];
  
  always @(posedge srclk, negedge xsrclr) begin
    if (!xsrclr)
      sr <= 8'd0;
    else
      sr <= {sr[6:0], ser};
  end
  
  always @(posedge rclk)
    lr <= sr;

endmodule

BRAM

実際の BRAM は BRAM_RATIO[1:0] でビット幅とアドレスの深さを変えられるのですが、今回は設定 2'b00 のみのビヘイビアを作った。BRAM_RATIO ポートは用意したが使ってない。拡張してみるのも面白いかも。バイト単位のデータをさらに分解してアクセスするようにできればいいかな。

bram.v
module bram
  (
   input  [1:0] BRAM_RATIO,
   input  [7:0] BRAM_WDATA,
   input        BRAM_nWEN,
   input        BRAM_nWRITE_CLK,
   input        BRAM_nWCLKEN,
   input  [8:0] BRAM_WADDR,
   input        BRAM_nREN,
   input        BRAM_nREAD_CLK,
   input        BRAM_nRCLKEN,
   input  [8:0] BRAM_RADDR,
   output [7:0] BRAM_RDATA
   );

  reg [7:0] mem [0:511];
  reg [7:0] mem_out;

  always @(posedge BRAM_nWRITE_CLK) begin
    if (!BRAM_nWEN && !BRAM_nWCLKEN) begin
      mem[BRAM_WADDR] <= BRAM_WDATA;
    end
  end

  always @(posedge BRAM_nREAD_CLK) begin
    if (!BRAM_nREN && !BRAM_nRCLKEN) begin
      mem_out <= mem[BRAM_RADDR];
    end
  end

  assign BRAM_RDATA = mem_out;

endmodule

MISO バッファ

SPI の MISO 端子は、一般的に nCS 端子が H の期間は HiZ になるよう作られている場合が多いです。これは複数の SPI target が一つの SPI controller で制御されている場合、すべての MISO 端子はワイヤードORされているのでデータの衝突を避けるためです。

SPI IP は o_miso_oe という端子があるので oe に繋いだのですが、ちゃんとこれが制御出来ているか確認するために、以下の記述でバッファのビヘイビアを用意しました。

でも、そもそも相手が一対一なので HiZ にする意味ないし、最悪 HiZ 期間にバスが浮いちゃうのもいやだよなぁと。一応該当端子は pull-up 設定になってるのでそれはなさそう。

miso.v
  bufif1 (spi_miso, spi_miso_o, spi_miso_en);  

タスク

SPI アクセスで表示メモリが更新できるかどうかを確認するには SPI のパケットを生成する必要があるのでこのパケットをテストベンチで作るひつようがありました。

セットした 16bit のデータを転送します。別途用意した rdata に読み値を格納するようにはしてるけど、テストベンチで Read はちゃんと使ってないな。。

spi_txrx.v
  task spi_txrx;
    input [15:0] wdata;
    integer i;
    begin
      spi_ss_n = 1'b0;
      spi_sck  = 1'b0;
      spi_mosi = 1'b0;
      #(SPI_CLK_PERIOD2);
      for (i = 0; i < 16; i = i + 1) begin
        spi_sck  = 1'b0;
        spi_mosi = wdata[15 - i];
        #(SPI_CLK_PERIOD2);
        spi_sck  = 1'b1;
        rdata[15 - i] = spi_miso;
        #(SPI_CLK_PERIOD2);
      end
      spi_sck  = 1'b0;
      spi_mosi = 1'b0;
      #(SPI_CLK_PERIOD2);
      spi_ss_n = 1'b1;
      #(SPI_CLK_PERIOD2);
    end
  endtask

テストケース

テストケースは一本。しかもビヘイビアを分割しておく方法調べてないので、テストベンチ兼テストケース兼ビヘイビア。仕事じゃないんで、これぐらいで勘弁。

やっていることは先頭で VCD の設定して、端子の初期化して、リセット解除。
その後、SPI で表示メモリにデータを書き込んで待っておしまい。

top_tb.vt
  initial begin
    // VCD setup
    $dumpfile ("top_tb.vcd");
    $dumpvars (0, top_tb);
    
    // Init SPI
    spi_ss_n = 1;
    spi_sck  = 0;
    spi_mosi = 0;
    
    // Init sys
    rst_n = 0;
    clk   = 0;
    #5;
    rst_n = 1;
    
    #100;
    repeat (3) @(negedge v_sync);
    #100;

    spi_txrx(16'h0001);
    #5000;
    spi_txrx(16'h0102);
    #5000;
    spi_txrx(16'h0203);
    #5000;
    spi_txrx(16'h0304);
    #5000;
    spi_txrx(16'h0405);
    #5000;
    spi_txrx(16'h0506);

    #50_000_000;
    #50_000_000;

    $finish;
  end

シミュレーションの実行

シミュレーションの実行は簡単。Icarus Verilog をインストールして、パス設定していれば Run RTL Simulation ボタンを押すだけ。テストベンチも複数作っておくとどれを実行するかここで選択できる。

GTKWave のパスもセットしてあると、ここからシミュレーションを実行するたびに新しい GTKWave が開かれる。がうざい。GTKWave のパスは設定せずに別途 GTKWave を開いて VCD をロードした方が良いかもしれない。特に Applie Silicon の Mac だと GTKWave のインストールや起動に失敗する可能性があるので、別な Surfer など Waveform ビュアーを使うのもありだと思います。

注意点としては VCD はめちゃめちゃ大きい。こまめに削除した方がいいです。

これが実行結果。
スクリーンショット 2026-01-31 14.48.54.png

起動後に v_sync 3回目の立ち下がりエッジから 1MHz で 6バイトの表示メモリを書き換えている。

カーソルの位置までは SEG (74HC595) 出力が真っ赤(不定)。これは表示メモリが初期化されていないからで想定内。

そのあとは表示メモリを書き換えているので、データにあわせて表示が更新されています。

SEG 出力も表示メモリ通りに出ているようなので思ったように設計できてるみたいです(ヨシ)。

ただ一つ気になることがあって、起動直後の COM 出力 (digX) が全部 L になる期間がそこそこあること。

つまりこの期間は複数の COM が同時に電流を流せる状態で、最悪全ポートから電流をながしこめること。リセットで COM 用の 74HC595 が L に初期化されてしまうので、避けられない。対処方法考えられるけど、ま、いいか。


合成とIO設定とビットストリーム生成そして書き込み

合成 (Synthesize)

これぐらいの回路規模だとボタンポチで比較的すぐに終わりました。
特に問題なさそう。

IO 設定 (IO Planner)

合成が終わると IO Planner で設定可能です。ちなみにトップの回路が変わったら一旦合成してから再度 IO 設定です。

非同期リセットは MISC 内の PIN11 に繋ぎましたが、単に初期化としてのリセットなら FPGA_CORE_READY でもいいようです。むしろこっちがいいのか?

ビットストリーム生成 (Generate Bitstream)

実行に少々時間がかかりますが特に問題なかったです。

Utilization

P&R 後のレポートを見ると、Utilization は 11.34%。まだまだ LUT の空きはあります。FPGA というか、SLG47910 は上限どれぐらいまでいけるんでしょうね?回路次第ということもあると思いますが。50% ぐらいは行って欲しい。

タイミングバイオレーション

うわー。出てるなぁ。気が向いたらみてみるか。Constraint も与えられるらしいのだけど、どう書けばいいんだろ?SDC なんて滅多に書かないからすっかり忘れたよ。

とりあえずビットストリーム出来たし、動いてるからよし。よし??

書き込み

ドラッグ&ドロップ。
で終了!

簡単簡単〜。


RP2040 のプログラミングと実行

MicroPython 環境にしました。Arduino でもよかったんだけど、たまに更新かけるとハマる場合があるんで、今回は避けときました。

デバッグ時は Thonny を使ってプログラムをデバッグしたけど、VSCode でもプログラム転送する方法があるっぽい。そちらもそのうち試してみよう。
Thonny のエディタは使いにくいので、結局 VSCode で書いているから。

この回路を動かすデモ用の Python スクリプト作ったのでざっくりと解説します。

ライブラリのインポート

import.py
from machine import Pin
import time
import shrike
from machine import Pin, SPI

サンプルに加え、SPI をインポートして使えるようにします

SPI の初期化と SPI データ転送関数の定義

Shirike-lite のピン配にあわせます SPI の 転送クロック速度は 1MHz で。

spi_setting.py
######################
# RP2040 SPI setting
SCK  = 2  
CS   = 1  
MOSI = 3  
MISO = 0  

# Chip Select pin
cs = Pin(CS, Pin.OUT, value=1)

# SPI configuration (MODE 0, MSB first)
spi = SPI(0,
          baudrate=1_000_000,
          polarity=0,
          phase=0,
          bits=8,
          firstbit=SPI.MSB,
          sck=Pin(SCK),
          mosi=Pin(MOSI),
          miso=Pin(MISO))

def spi_exchange(addr, data):
    Read_buf  = bytearray(2)
    Write_buf = bytearray(2)
    Write_buf[0] = addr
    Write_buf[1] = data
    cs.value(0)          # Select FPGA
    spi.write_readinto(Write_buf, Read_buf)
    cs.value(1)          # Deselect FPGA
    return Read_buf[0], Read_buf[1] #rx[0]

表示メモリとパターン

表示メモリ用配列を用意し、ここをプログラムで書き換え、SPI 書き込み時はこの配列データを転送するようにします。

また表示パターンを作りやすいように A-G,DP の定義及び、数値表示のパターンを用意しておきます。

disp.py
disp_mem = [0, 0, 0, 0, 0, 0]

S_A   = 1 << 0
S_B   = 1 << 1
S_C   = 1 << 2
S_D   = 1 << 3
S_E   = 1 << 4
S_F   = 1 << 5
S_G   = 1 << 6
S_DP  = 1 << 7

DIG_0 = S_A | S_B | S_C | S_D | S_E | S_F
DIG_1 = S_B | S_C
DIG_2 = S_A | S_B | S_G | S_E | S_D
DIG_3 = S_A | S_B | S_G | S_C | S_D
DIG_4 = S_F | S_G | S_B | S_C
DIG_5 = S_A | S_F | S_G | S_C | S_D
DIG_6 = S_A | S_G | S_C | S_D | S_E | S_F
DIG_7 = S_A | S_B | S_C
DIG_8 = S_A | S_B | S_C | S_D | S_E | S_F | S_G
DIG_9 = S_A | S_B | S_C | S_D | S_F | S_G

def disp_update():
    for i in range(6):
        spi_exchange(i, disp_mem[i])

表示メモリ配列にデータをセットして、更新関数をコールすると、、、

pi_disp.py
disp_mem[0] = DIG_3 | S_DP
disp_mem[1] = DIG_1
disp_mem[2] = DIG_4
disp_mem[3] = DIG_1
disp_mem[4] = DIG_5
disp_mem[5] = DIG_9

disp_update()

じゃーん、表示できた!

ものが動くと楽しいですね。

ただ一発では動かなかったです。。IO Planner でのピン配が問題だったらしく、オシロで一つづつ確認しながらでした。オシロあってよかったです。

あとはシミュレーションで最低限の動作は確認してたので、回路ミスの手直しはなかったです。ほっと。

GitHub に上がってる Python コードは、改良して Class 作ったりして若干異なります。


おまけ

モノホンで動かす!

こちらも無事動かせました。

Pmod MultiSegment SP モジュールありがとうございます!


おわりに

結構お手軽に楽しめました。

多くの人に Shrike-lite 試してもらって、SLG47910 の入手がしやすくなりますように!


MycroPython で割り込み (追記)

MicroPython で割り込み使ったことなかったので試してみました。
割り込みバージョンは main_ini.py としてアップしておきました。

今回作った割り込み無しの main.py での表示データは、数百ms 以上の間隔(Wait)で 6桁を全部更新するというような作りをしています。

ハード的に、6桁を表示する直前の表示メモリを書き換えても表示データが乱れないタイミングを v_sync (L 期間 2.76ms, 周期 16.56ms) として用意し、PR2040 に信号を出力しています。

v_sync の立ち下がりを割り込みで捕まえて、その後に 6桁分のデータを連続して v_sync L 期間に書き込めば良いような作りです。

ところで元の main.py では表示したいデータは一旦 DispMultiSeg クラスのメンバ変数 digit 配列に格納し、

dm.disp_update_digit_all()

をコールして SPI で連続書き込みを行ってます。

したがって、dm.disp_update_digit_all() をコールする代わりに表示データが準備できたとうフラグ

update_flag=True

をセットして、v_sync の割り込みが生じた時に update_flag == True の場合に表示を更新したいデータがあることがわかるので dm.disp_update_digit_all() をコールして表示データを SPI で転送してやればよさそうです。

目論見は成功。特に変わった様子(問題)もなく表示できています。

そこでオシロで F0 と F1 にアサインした内部信号 v_sync と SPI の nCS をモニターして割り込みが正しく行われているか観察してみました。

オシロの CH1 は v_sync, CH2 は SPI nCS。
v_sync 立ち下がり割り込みから、最初の nCS 下がりまでがおおよそ 250us。6byte 書き込みは約 780us でした(2byte/150us ほど)。
v_sync = COM (DIG.X) 1サイクルで 2.76ms = 16.56ms で設計通りなのがみてとれます。v_sync 内で十分書き込みが終わっているので期待通りです。遅いけど。

ついでに RP2040 + MicroPython での割り込みのレイテンシーと SPI アクセス速度がわかってよかったです。

IMG_5768.jpeg


参考資料

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?