16桁2行のキャラクタ・ディスプレイとして知られるSC1602をFPGAで使います.
枯れたデバイスであり,現在もよく見かけるので,ウェブ上に実装例があると思いましたが,意外に少なかったので書きました.ボリュームも少なめであり,ステートマシンを書くトレーニングに丁度よさそうです.
本稿は2024年のAdvent Calendarの記事にもなっています.
●使ったハードウェア
Tang Nano 9K(Sipeed)とCiniml氏のPmod拡張ボードを使いました.
SC1602(3.3V品)をFPGAボードにつなげるために,Pmod規格の自作基板を使いました.Pmodになっているので製品に組み込んだり,デバッグ時のみ接続して使ったりできます.便利
装置に組み込めば,Cobalt RaQっぽくなれます.SC1602をもっと使いましょう.ヾ(*´∀`*)ノ
●参考サイト
仕様とか: http://219.117.208.26/~saka/ham/LCD2/
Verilog実装(8bitモード): https://sites.google.com/site/de0defpga/verilog-hdl/kyarakutalcd-display-mojuru-chu-li
PIC実装(4bitモード): http://yamatyuu.net/computer/pic/usbio2/lcdc_lib1/index.html
●全体像
●実装した機能
▲初期化
初期化のプロセスでは,RST
,WAIT
,FUNC_SET
,DSP_OFF
,CLEAR
,ENT_MODE
,DSP_ON
,の順にステートをたどります.
sc1602db[3:0]
に対して,4'h3
,4'h3
,4'h3
,4'h2
,4'h2
,4'h8
,4'h0
,4'h8
,4'h0
,4'h1
,4'h0
,4'h6
の順でデータを送ります.
=省略=
always @(posedge clk or negedge resetn) begin
if (!resetn)
begin
ready_o <= 0;
state <= RST;
locate <= 8'b0;
end
else
begin
case (state)
RST:
begin
sc1602_en <= 0;
sc1602_rs <= 0;
sc1602_rw <= 0;
sc1602_db <= 4'h3;
next_state <= RST1;
state <= WAIT1;
wait_counter <= 13'd4000; // more than 40ms
end
RST1:
begin
sc1602_en <= 1;
=省略=
end
RST2:
begin
=省略=
end
RST3:
begin
=省略=
end
WAIT1:
begin
if(sc1602_en == 1)begin
sc1602_en <= 0;
end else begin
if (wait_counter == 0)
state <= next_state;
else begin
wait_counter <= wait_counter - 1;
end
end
end
FUNC_SET0: // FUNCTION SET for SC1602
begin
sc1602_en <= 1;
sc1602_rs <= 0;
sc1602_rw <= 0;
sc1602_db <= 4'h2;
state <= WAIT1;
next_state <= FUNC_SET1;
wait_counter <= JUST_MOMENT;
end
FUNC_SET1:
begin
sc1602_en <= 1;
sc1602_rs <= 0;
sc1602_rw <= 0;
sc1602_db <= 4'h2;
state <= WAIT1;
next_state <= FUNC_SET2;
wait_counter <= JUST_MOMENT;
end
FUNC_SET2: // Function set
begin
sc1602_en <= 1;
sc1602_rs <= 0;
sc1602_rw <= 0;
sc1602_db <= 4'h8;
state <= WAIT1;
next_state <= DSPOFF1;
wait_counter <= JUST_MOMENT;
end
DSPOFF1: // Display Off
=省略=
DSPOFF2:
=省略=
CLEAR1: // Display Clear
=省略=
CLEAR2: //
=省略=
ENT_MODE1: // Entry Mode Setting
=省略=
ENT_MODE2: //
=省略=
DSPON1: // Display On
=省略=
DSPON2:
=省略=
=省略=
▲Clear Display
SC1602が持つClear Displayコマンドを実行させるため,SC1602にデータを送ります.
▲Return Home
SC1602のReturn Homeコマンドを実行させるため,SC1602にデータを送ります.
▲Cursor Shift
command_in
レジスタによって,3ビットのコマンドを受け取ります.現状は,ウィンドウ・シフト(表示窓のシフト)のみが実装されています.
command_in[2:1]
:命令種(01:文字シフト)
command_in[0]
:シフト方向(0:L,1:R)
命令のデコードと振り分けはHOMEステートで行います.
=省略=
HOME:begin // Instruction decoder
if(command_in[2:1] == 2'b00)begin // nothing to do
ready_o <= 1;
next_state <= WRITE1; // reflesh data
end else if(command_in[2:1] == 2'b01)begin // Display shift
ready_o <= 0;
next_state <= CURSOL1;
end
state <= WAIT1;
wait_counter <= JUST_MOMENT;
end
CURSOL1:begin // Display shift (L or R)
sc1602_db <= 4'd1; // MSB
sc1602_rs <= 0;
sc1602_rw <= 0;
sc1602_en <= 1;
next_state <= CURSOL2;
state <= WAIT1;
wait_counter <= JUST_MOMENT;
end
CURSOL2:begin
sc1602_db <= {1'b1, command_in[0], 2'b0}; // LSB (command_in[0] represents L or R ), (Most lsb 2bits doesn't matter)
sc1602_rs <= 0;
sc1602_rw <= 0;
sc1602_en <= 1;
next_state <= HOME;
state <= WAIT1;
wait_counter <= 4096;
end
endcase
end
=省略=
●実装1:ドライバ・モジュール全体(sc1602_driver.sv)
module sc1602_driver(
input logic clk,
input logic resetn,
input logic[7:0] character_in, // a character to display
input logic [2:0] command_in,
output logic ready_o,
output logic sc1602_en, // EN to connect SC1602
output logic sc1602_rs, // RS to connect SC1602
output logic sc1602_rw, // RW to connect SC1602
output logic[3:0] sc1602_db, // 4bit data to connect SC1602
output logic drawing
);
logic [7:0] state;
logic [7:0] next_state;
logic [7:0] locate; // position of write character
logic [12:0] wait_counter;
localparam RST=0;
localparam RST1=1;
localparam RST2=2;
localparam WAIT1=3;
localparam WAIT2=4;
localparam FUNC_SET0=5;
localparam FUNC_SET1=6;
localparam FUNC_SET2=7;
localparam DSPOFF1=8;
localparam DSPOFF2=9;
localparam CLEAR1=10;
localparam CLEAR2=11;
localparam DSPON1=12;
localparam DSPON2=13;
localparam ENT_MODE1=14;
localparam ENT_MODE2=15;
localparam RETURN1=16;
localparam RETURN2=17;
localparam HOME=18;
localparam WRITE1=19;
localparam WRITE2=20;
localparam DDRAM1=21;
localparam DDRAM2=22;
localparam RST3=23;
localparam CURSOL1=24;
localparam CURSOL2=25;
localparam JUST_MOMENT=1;
always @(posedge clk or negedge resetn) begin
if (!resetn)
begin
ready_o <= 0;
state <= RST;
locate <= 8'b0;
end
else
begin
case (state)
RST:
begin
sc1602_en <= 0;
sc1602_rs <= 0;
sc1602_rw <= 0;
sc1602_db <= 4'h3;
next_state <= RST1;
state <= WAIT1;
wait_counter = 13'd4000; // more than 40ms
end
RST1:
begin
sc1602_en <= 1;
sc1602_rs <= 0;
sc1602_rw <= 0;
sc1602_db <= 4'h3;
next_state <= RST2;
state <= WAIT1;
wait_counter = 13'd400; // more than 4.1ms
end
RST2:
begin
sc1602_en <= 1;
sc1602_rs <= 0;
sc1602_rw <= 0;
sc1602_db <= 4'h3;
next_state <= RST3;
state <= WAIT1;
wait_counter = 13'd140; // more than 100us
end
RST3:
begin
sc1602_en <= 1;
sc1602_rs <= 0;
sc1602_rw <= 0;
sc1602_db <= 4'h3;
next_state <= FUNC_SET0;
state <= WAIT1;
wait_counter = JUST_MOMENT; // more than 38us
end
WAIT1:
begin
if(sc1602_en == 1)begin
sc1602_en <= 0;
end else begin
if (wait_counter == 0)
state <= next_state;
else begin
wait_counter <= wait_counter - 1;
end
end
end
FUNC_SET0: // FUNCTION SET for SC1602
begin
sc1602_en <= 1;
sc1602_rs <= 0;
sc1602_rw <= 0;
sc1602_db <= 4'h2;
state <= WAIT1;
next_state <= FUNC_SET1;
wait_counter = JUST_MOMENT;
end
FUNC_SET1:
begin
sc1602_en <= 1;
sc1602_rs <= 0;
sc1602_rw <= 0;
sc1602_db <= 4'h2;
state <= WAIT1;
next_state <= FUNC_SET2;
wait_counter = JUST_MOMENT;
end
FUNC_SET2: // Function set
begin
sc1602_en <= 1;
sc1602_rs <= 0;
sc1602_rw <= 0;
sc1602_db <= 4'h8;
state <= WAIT1;
next_state <= DSPOFF1;
wait_counter = JUST_MOMENT;
end
DSPOFF1: // Display Off
begin
sc1602_en <= 1;
sc1602_rs <= 0;
sc1602_rw <= 0;
sc1602_db <= 4'h0; // MSB of 8bit
state <= WAIT1;
next_state <= DSPOFF2;
wait_counter = JUST_MOMENT;
end
DSPOFF2:
begin
sc1602_en <= 1;
sc1602_rs <= 0;
sc1602_rw <= 0;
sc1602_db <= 4'h8; // LSB of 8bit
state <= WAIT1;
next_state <= CLEAR1;
wait_counter = JUST_MOMENT;
end
CLEAR1: // Display Clear
begin
sc1602_en <= 1;
sc1602_rs <= 0;
sc1602_rw <= 0;
sc1602_db <= 4'h0; // MSB of 8bit
state <= WAIT1;
next_state <= CLEAR2;
wait_counter = JUST_MOMENT;
end
CLEAR2: //
begin
sc1602_en <= 1;
sc1602_rs <= 0;
sc1602_rw <= 0;
sc1602_db <= 4'h1; // LSB of 8bit
state <= WAIT1;
next_state <= ENT_MODE1;
wait_counter = 12'd200; // more than 1.52ms
end
ENT_MODE1: // Entry Mode Setting
begin
sc1602_en <= 1;
sc1602_rs <= 0;
sc1602_rw <= 0;
sc1602_db <= 4'h0; // MSB of 8bit
state <= WAIT1;
next_state <= ENT_MODE2;
wait_counter = JUST_MOMENT;
end
ENT_MODE2: //
begin
sc1602_en <= 1;
sc1602_rs <= 0;
sc1602_rw <= 0;
sc1602_db <= 4'h6; // LSB of 8bit
state <= WAIT1;
next_state <= DSPON1;
wait_counter = JUST_MOMENT;
end
DSPON1: // Display On
begin
sc1602_en <= 1;
sc1602_rs <= 0;
sc1602_rw <= 0;
sc1602_db <= 4'h0; // MSB of 8bit
state <= WAIT1;
next_state <= DSPON2;
wait_counter = JUST_MOMENT;
end
DSPON2:
begin
sc1602_en <= 1;
sc1602_rs <= 0;
sc1602_rw <= 0;
sc1602_db <= 4'hc; // LSB of 8bit
state <= WAIT1;
next_state <= RETURN1;
wait_counter = JUST_MOMENT;
end
RETURN1: // Return position
begin
sc1602_en <= 1;
sc1602_rs <= 0;
sc1602_rw <= 0;
sc1602_db <= 4'h0; // MSB of 8bit
state <= WAIT1;
next_state <= RETURN2;
wait_counter = JUST_MOMENT;
end
RETURN2:
begin
sc1602_en <= 1;
sc1602_rs <= 0;
sc1602_rw <= 0;
sc1602_db <= 4'h2; // LSB of 8bit
state <= WAIT1;
next_state <= WRITE1;
locate <= 8'b0; // character insert location 0 set
wait_counter = 12'd200; // more than 1.52ms
end
DDRAM1: // Memory Adress Set
begin
sc1602_en <= 1;
sc1602_rs <= 0;
sc1602_rw <= 0;
sc1602_db <= {1'b1, locate[6:4]};
state <= WAIT1;
next_state <= DDRAM2;
wait_counter = JUST_MOMENT;
end
DDRAM2:
begin
sc1602_en <= 1;
sc1602_rs <= 0;
sc1602_rw <= 0;
sc1602_db <= locate[3:0];
state <= WAIT1;
next_state <= WRITE1;
wait_counter = JUST_MOMENT;
end
WRITE1: // Show character
begin
drawing <= 1; // showing it's in a draw process
sc1602_db <= character_in[7:4]; //MSB of 8bit code that represents a character
sc1602_rs <= 1;
sc1602_rw <= 0;
sc1602_en <= 1;
locate <= locate + 8'b1; // increment position for character on LCD RAM
next_state <= WRITE2;
state <= WAIT1;
wait_counter = JUST_MOMENT;
end
WRITE2:
begin
sc1602_db <= character_in[3:0]; // LSB of 8bit code that represents a character
sc1602_rs <= 1;
sc1602_rw <= 0;
sc1602_en <= 1;
if (locate == 16)
begin
locate <= 8'h40; // increment row
next_state <= DDRAM1;
end
else if (locate > 8'h4F)
begin
next_state <= HOME;
end
else
next_state <= WRITE1;
state <= WAIT1;
drawing <= 0;
wait_counter = JUST_MOMENT;
end
HOME:begin // Instruction decoder
if(command_in[2:1] == 2'b00)begin // nothing to do
ready_o <= 1;
next_state <= WRITE1; // reflesh data
end else if(command_in[2:1] == 2'b01)begin // Display shift
ready_o <= 0;
next_state <= CURSOL1;
end
state <= WAIT1;
wait_counter <= JUST_MOMENT;
end
CURSOL1:begin // Display shift (L or R)
sc1602_db <= 4'd1; // MSB
sc1602_rs <= 0;
sc1602_rw <= 0;
sc1602_en <= 1;
next_state <= CURSOL2;
state <= WAIT1;
wait_counter <= JUST_MOMENT;
end
CURSOL2:begin
sc1602_db <= {1'b1, command_in[0], 2'b0}; // LSB (command_in[0] represents L or R ), (Most lsb 2bits doesn't matter)
sc1602_rs <= 0;
sc1602_rw <= 0;
sc1602_en <= 1;
next_state <= HOME;
state <= WAIT1;
wait_counter <= 4096;
end
endcase
end
end
endmodule
●実装2:トップ・モジュール全体(top.sv)
SC1602は90kHzで駆動しています.このためのクロックは,GOWINのIPである rPLL で生成しています.源振はTang Nano 9Kの持つ水晶振動子の27MHzです.
module top (
input sys_clk, // clk input
input sys_rst_n, // reset input
input sw,
output reg [5:0] led, // 6 LEDS pin
output sc1602_vo, // display intensity
output sc1602_rs,
output sc1602_rw,
output sc1602_enable,
output [3:0] sc1602_data
);
logic [23:0] counter;
logic contrast;
logic sc1602_clk;
logic clkout;
logic locked;
logic sc1602_drawing;
logic [7:0] word_counter;
logic [7:0] word[8] = {8'h20, 8'h46, 8'h50, 8'h47, 8'h41, 8'h20, 8'h20, 8'h20};
logic sc1602_valid, sc1602_ready;
logic [2:0] sc1602_command; // {2'b command, 1'b LR}
assign contrast = 0;
// to use 3.3v type SC1602(3state output is needed to contorl LCD intensity)
TBUF u0(
.O(sc1602_vo),
.I(1'b0),
.OEN(~contrast)
);
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n)begin
led[0] <= 0;
counter <= 0;
end else if (counter < 24'd1349_9999)begin // 0.5s delay
counter <= counter + 1'd1;
led[0] <= 1; //on board LED for debug
if(sc1602_ready)begin
if(!sw)begin
led[1] <= 0; // on board LED for debug
sc1602_command <= {2'b01, 1'b0}; // Window shift, left
end else begin
led[1] <= 1; // on board LED for bebug
end
end else begin
sc1602_command <= 3'b0;
end
end else begin
counter <= 24'd0;
end
end
Gowin_rPLL your_instance_name(
.clkout(clkout), //output clkout
.clkoutd(sc1602_clk), //output clkoutd
.clkin(sys_clk), //input clkin
.lock(locked)
);
always @(negedge sc1602_drawing or negedge sys_rst_n)begin
if(!sys_rst_n)begin
word_counter <= 0;
end else begin
word_counter <= word_counter + 8'b1;
end
end
sc1602_driver driver0(
.clk(sc1602_clk),
.resetn(sys_rst_n & locked),
.character_in(word[word_counter]),
.sc1602_en(sc1602_enable),
.sc1602_rs(sc1602_rs),
.sc1602_rw(sc1602_rw),
.sc1602_db(sc1602_data),
.drawing(sc1602_drawing),
.command_in(sc1602_command),
.ready_o(sc1602_ready)
//.frame_rate()
);
endmodule
●総括
SUNLIKE社の製品を利用して SUN like な装置を作れます(Cobaltだけど…).
●リポジトリ
https://github.com/Lathe-Mariel/Pmod/tree/main/Pmod_SC1602
RTLコードはsample
ディレクトリにあります.