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

●全体像

モジュール間の主要な信号

●実装した機能

▲初期化

 初期化のプロセスでは,RSTWAITFUNC_SETDSP_OFFCLEARENT_MODEDSP_ON,の順にステートをたどります.

 sc1602db[3:0]に対して,4'h34'h34'h34'h24'h24'h84'h04'h84'h04'h14'h04'h6の順でデータを送ります.

SystemVerilog
=省略=

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ステートで行います.

SystemVerilog
=省略=
                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)

SystemVerilog
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です.

SystemVerilog
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ディレクトリにあります.

コメントを残す