관리 메뉴

거북이처럼 천천히

Verilog RTL 설계(8월 22일 - 1, I2C 통신을 통한 LCD 컨트롤 - (1)) 본문

RTL Design/Verilog RTL 설계

Verilog RTL 설계(8월 22일 - 1, I2C 통신을 통한 LCD 컨트롤 - (1))

유로 청년 2024. 8. 25. 15:05

1. I2C Master 모듈을 통한 LCD 디스플레이 모듈 컨트롤

  • 이번에는 I2C 통신을 통해 LCD 디스플레이 모듈과 Basys3 과 통신해보도록 하겠다.
  • 구현에 앞서 Slave 모듈인 LCD 디스플레이 모듈에 대해서 공부해보도록 하겠다.

 

 

 

2. I2C 통신, 왜 사용하는가?

  • 아래 사진은 1602 LCD 디스플레이 모듈과 HLF8574 칩으로 결합된 I2C 통신으로 통신하는 모듈이다.

(왼) LCD 1602 디스플레이 모듈, (오른) I2C 통신을 위해 HLF8574 칩과 결합한 형태

 

 

  • LCD 패널만 독립적으로 I2C 통신 불가능하기 때문에 LCD 패널 뒷면에 LCD 디스플레이 모듈과 8574칩을 결합하여 8574 칩을 통해 LCD 패널은 Master (Basys3)간에 I2C 통신이 가능해진다.
  • 물론, I2C 통신외에 Master (Basys3)와 선을 통해 직접적인 연결하여 통신이 가능하지만, 디스플레이와 Master (basys3) 간에 통신을 하기 위해서는 14개의 데이터 라인이 필요하다.
    ▶ 8bit 데이터를 송수신하는 데이터 버스 : 8bit
    ▶ 제어 라인 [RS (Register select), RW (Read / Write), E(Enable), Backlight] : 4bit
    ▶ 전원 연결 (Vcc, GND) : 2bit


  • Q) 그럼, 직접적인 연결을 통해 통신이 가능하지만, 왜 어렵게 I2C 통신을 사용하는가?
    A) 
    위에서 말했듯이 직접적인 연결으로 통신하기 위해서는 14개의 포트가 필요하지만, Basys3와 같이 한정적된 포트를 갖는 Master 모듈에서는 디스플레이와 통신을 위해 많은 포트를 사용해야 하기 때문에 비효율적이다. 

    따라서 이처럼 상대적으로 적은 포트를 사용하되, 디스플레이와 같은 Slave 모듈과 통신하기 위해 I2C 통신 방법을 사용한다. I2C 통신을 하게 되면 단 2개의 신호선만 필요하다. [SCL (Serial Clock), SDA (Serial Data)]

    물론, 직접적으로 연결할 경우, Master와 Slave 모듈간에 데이터 전송이 병렬적으로 이루어지기 때문에 빠르지만, 자원이 소모가 I2C 통신 방법보다 심하기 때문에 I2C 통신을 사용하게 된다.

 

 

 

 

3. LCD 1602 디스플레이 모듈

  • " I2C 통신을 왜 필요한가? " 에 대해서 살펴보았다면 이번에는 I2C 통신을 하기 위해 LCD 1602 디스플레이 모듈과 HLF 8574칩 에 대해서 살펴보자.
  • I2C 통신을 하기 위해서는 Slave 모듈의 주소가 필요하다. Slave 모듈의 주소를 알아야 해당 Slave 모듈과의 통신이 가능하여 데이터를 Read / Write가 가능하다.

  • Q) LCD 1602 디스플레이 모듈이 Slave 모듈인데, 해당 모듈의 주소를 어떻게 얻을 수 있는가?
    A)
    이는 LCD 디스플레이 모듈의 뒷 면을 보면 알 수 있다.

(왼) LCD 디스플레이 모듈의 주소, (오른) 변수 A0, A1, A2의 정보를 담고 있는 모듈의 위치

 

  • LCD 1602 디스플레이 모듈의 데이터 시트를 통해 아래와 같은 정보를 얻을 수 있다.
    ▶ A0, A1, A2 주소 연결 영역에서 bridged with solder 형태로 아래, 위를 납뗌하지 않았다면 LCD 1602
        디스플레이 모듈의 주소는 0x27이다.

 

  • Q) 납땜 한다는 것은 정확히 어떤 걸 의미하는가?
    A) 
    왼쪽 위 사진과 같이 LCD 패널 뒷 면을 보면 A0, A1, A2 가 적힌 영역을 찾을 수 있으며, 각각의 문자 위에 납뗌 할 수 있는 부분이 각각 2개씩 존재함을 확인할 수 있다. 즉, 데이터 시트에서 말하는 의미는 해당 영역에 납뗌하지 않는다면 A0, A1, A2가 각각 1이라는 값을 가지며, 해당 영역에 납뗌하면 A0, A1, A2의 값은 0이 된다.


  • Q) 왜 디스플레이 모듈의 주소는 고정되어 있지 않고, 납땜을 통해 변경할 수 있도록 만들었는가?
    A) 
    이는 I2C 통신의 특징 중 하나인 1개의 Master 모듈이 여러 개의 Slave 모듈과 통신을 할 수 있게 하기 위해서이다. 이전 게시글에서도 말했듯이 I2C 통신은 하나의 SCL 신호선과 SDA 신호선을 갖는다. 따라서 하나의 신호선을 통해 여러 개의 Slave 모듈들과 정보를 주고 받기 위해서는 각  Slave 모듈의 주소를 통해 데이터를 송수신하게 된다. 

    이때, 만약 Slave 모듈이 고정되어 있다면 동일한 주소들을 갖는 Slave 모듈들은 사용할 수 없으며, 사용하게 된다면 데이터 충돌이 발생하여 통신이 불가능하다.

    따라서 사용자에게 선택권을 줌으로서 사용자는 환경 및 상황에 맞게 Slave 모듈의 주소를 변화시켜 데이터 충돌을 방지할 수 있다.

 

 

 

 

 

4. LCD 1602 디스플레이 모듈과 HLF 8574 칩과의 관계

  • 위에서도 설명했듯이 LCD 디스플레이 모듈은 독립적으로 I2C 통신이 불가능하기 때문에 HLF 8574 칩과 결합함으로서 HLF 8574 칩을 통해 I2C 통신을 하여 데이터를 주고 받게 된다.
  • 이번에는 "어떤 과정을 통해 HLF 8574 칩은 I2C 통신을 통해 데이터를 받아서 LCD 1602 디스플레이 모듈에게 전달 받은 데이터를 전달하는가?" 에 대해서 살펴보도록 하겠다.
  • 아래 그림은 HLF 8574 칩에 대한 Block diagram 이다.

(왼) Block diagram of HLF8574 Chip, (오른) 간략화한 HLF 8574칩

 

  • I2C 통신을 통해 전달받은 데이터는 8574칩 내부에서 다음과 같은 과정을 통해 처리된다.
    ▶ I2C 통신을 통해 전달 받은 데이터들은 I2C-BUS CONTROL 블록에서 처리한다.
    ▶ 처리된 데이터들은 I2C-BUS CONTROL 블록에서 SHIFT Register 블록으로 Serial 형태로 전달된다.
    ▶ Shift register 블록에서 8bit 데이터를 모두 받으면 Parallel 형태로 데이터를 변환한 뒤, I/O Port 블록으로 전달한다.
    ▶ 최종적으로 전달 받은 Parallel 8bit 데이터들을 I/O Port 블록에서 P0 ~ P7까지 8개의 핀으로 내보내지게 된다.


LCD 1602 디스플레이 모듈

  • 이렇게 전달 받은 8bit 데이터들은 LCD 디스플레이 모듈로 전달 된다.


  • Q) I2C 통신을 통해 전달 받은 8bit 데이터들이 8574칩에서 LCD 디스플레이 모듈로 전달된다고 했는데, 디스플레이 모듈의 블록도를 보면 데이터 선은 D4 ~ D7, 4개 밖에 없다. 이건 무엇인가?
    A)
    I2C 통신을 통해 전달되는 데이터는 8bit이지만, 8bit 모두 데이터가 아니다. I2C 통신을 통해 전달받은 8bit 데이터 내에는 4bit 제어 신호와 4bit 데이터로 이루어 져있다. 

    4bit 제어 신호에는 다음과 같은 제어 신호들이 있다.
    ▶ BT : LCD의 Back Light 제어 신호
    ▶ E : 8574칩에서 LCD 디스플레이로 데이터를 전송할지 여부를 결정하는 신호
    ▶ RS : Register select, 데이터 레지스터나 명령어 레지스터 중 어느 레지스터에 전달할 지 여부를 나타내는 신호
    ▶ Read / Write : 해당 레지스터를 Read 할 것인지, Write 할 것인지를 나타내는 신호


  • 따라서 I2C 통신을 통해 8bit 데이터가 전송하더라도 4bit는 제어 신호, 4bit 데이터 이기 때문에 8bit 데이터를 전송하기 위해서는 2번의 과정이 필요하다.

 

 

 

 

 

5. I2C 통신을 통해 LCD 디스플레이의 Backlight 컨트롤하기.

  • 이번에는 이전 게시글에서 설계한 I2C_master 모듈을 이용하여 LCD 디스플레이간에 I2C 통신하여 LCD의 Back light를 제어해보도록 하겠다.
  • i2c_master 모듈에 대한 설명은 아래 게시글을 참고하길 바란다.
    Verilog RTL 설계(8월 21일 - 2, I2C 통신 - 2) (tistory.com)
 

Verilog RTL 설계(8월 21일 - 2, I2C 통신 - 2)

1. I2C 통신이전 게시글에서 다루었던 I2C 통신 과정을 이번에는 vivado를 통해 Master 관점에서의 I2C 통신 모듈을 설계하도록 하겠다.I2C 통신 과정에 대해서 궁금하면 아래 게시글을 참고하길 바란다

jbhdeve.tistory.com

 

 

 

 

 

< Source, I2C_Master Module >

module i2c_master(
    input clk, reset_p,
    input comm_go,
    input [6:0] addr,
    input read_write,
    input [7:0] data,
    output reg scl, sda);
    
    // Declare State Machine
    parameter S_IDLE = 7'b000_0001;
    parameter S_START_I2C = 7'b000_0010;
    parameter S_SEND_ADDR = 7'b000_0100;
    parameter S_WAIT_ACK = 7'b000_1000;
    parameter S_SEND_DATA = 7'b001_0000;
    parameter S_STOP_I2C = 7'b010_0000;
    parameter S_END_I2C = 7'b100_0000;
    
    // Declare state, next state
    reg [6:0] state, next_state;
    
    // 언제 다음 상태로 전이되는가?
    always @(posedge clk or posedge reset_p) begin
        if(reset_p) state = S_IDLE;
        else state = next_state;
    end
    
    // 100kHz Clock Pulse 만들기
    // 100분주화 
    wire clk_1usec;
    clk_div_100 clk_div_1usec (.clk(clk), .reset_p(reset_p), .clk_div_100_nedge(clk_1usec));
    
    // 10분주화 
    reg [2:0] counter_5usec;
    reg counter_enable;
    always @(negedge clk or posedge reset_p) begin
        if(reset_p) begin
            counter_5usec = 0;
            scl = 1;
        end
        else if(counter_enable && clk_1usec) begin
              if(counter_5usec >= 4) begin
                    counter_5usec = 0;
                    scl = ~scl;
              end
              else counter_5usec = counter_5usec + 1;
        end
        else if(!counter_enable) begin 
            counter_5usec = 0;
            scl = 1;
        end
    end
    
    // Get Positive edge of comm-go
    wire comm_go_pedge;
    edge_detector edge_detector_comm_go (.clk(clk), .reset_p(reset_p), .cp(comm_go), .p_edge(comm_go_pedge));
    
    // Get edge of SCL
    wire scl_pedge, scl_nedge;
    edge_detector edge_detector_scl (.clk(clk), .reset_p(reset_p), .cp(scl), .p_edge(scl_pedge), .n_edge(scl_nedge));
    
    // Combine address of target slave module and read / write data
    wire [7:0] addr_read_write = {addr, read_write};
    
    // Register of counting send data
    reg [2:0] cnt_data;
    
    // Flag register for ACK stage
    reg flag_ack;
    
    // 각 상태 단계의 동작 과 다음 상태 전이 조건 정의
    always @(negedge clk or posedge reset_p) begin
        if(reset_p) begin
            next_state = S_IDLE;
            sda = 1;
            cnt_data = 7;
            flag_ack = 0;
            counter_enable = 0;
        end
        else begin
            case(state)
                // 1단계) S_IDLE
                S_IDLE : begin
                    counter_enable = 0;
                    sda = 1;
                    
                    if(comm_go_pedge) next_state = S_START_I2C;
                end
                
                // 2단계) S_START_I2C
                S_START_I2C : begin
                    sda = 0;
                    counter_enable = 1;
                    
                    next_state = S_SEND_ADDR;
                end
                
                // 3단계) S_SEND_ADDR
                S_SEND_ADDR : begin
                    if(scl_nedge) sda = addr_read_write [cnt_data];
                    else if(scl_pedge) begin
                        if(cnt_data == 0) begin
                            cnt_data = 7;
                            next_state = S_WAIT_ACK;
                        end
                        else cnt_data = cnt_data - 1;
                    end
                end
                
                // 4단계) S_WAIT_ACK
                S_WAIT_ACK : begin
                    if(scl_nedge) sda = 'bz;
                    else if(scl_pedge) begin
                        if(!flag_ack) begin
                            flag_ack = 1;
                            next_state = S_SEND_DATA;
                        end
                        else begin
                            flag_ack = 0;
                            next_state = S_STOP_I2C;
                        end
                    end
                end
                
                // 5단계) S_SEND_DATA
                S_SEND_DATA : begin
                    if(scl_nedge) sda = data[cnt_data];
                    else if(scl_pedge) begin
                        if(cnt_data == 0) begin
                            cnt_data = 7;
                            next_state = S_WAIT_ACK;
                        end
                        else cnt_data = cnt_data - 1;
                    end 
                end
                
                // 6단계) S_STOP_I2C
                S_STOP_I2C : begin
                    if(scl_nedge) sda = 0;
                    else if(scl_pedge ) begin 
                        counter_enable = 0;
                        next_state = S_END_I2C;
                    end
                end
                
                // 7단계) S_END_I2C
                S_END_I2C : begin
                    counter_enable = 1;
                    
                    if(counter_5usec > 2) begin
                        sda = 1;
                        counter_enable = 0;
                        next_state = S_IDLE;
                    end
                end
            endcase
        end
    end
endmodule

// Clock Divider 100
module clk_div_100 (
    input clk, reset_p,
    output clk_div_100,
    output clk_div_100_nedge, clk_div_100_pedge );
    
    reg [6:0] counter;
    always @(posedge clk or posedge reset_p) begin
        if(reset_p) counter = 0;
        else begin
            if(counter >= 99) counter = 0;
            else counter = counter + 1; 
        end
    end
    
    assign clk_div_100 = (counter < 50)? 0 : 1;
    
    edge_detector edge_detector_0 (.clk(clk), .reset_p(reset_p), .cp(clk_div_100), 
                       .p_edge(clk_div_100_pedge), .n_edge(clk_div_100_nedge));
endmodule

// Edge detector 
module edge_detector (
    input clk, reset_p,
    input cp,
    output p_edge, n_edge );
    
    reg flip_flop_current, flip_flop_old;
    
    always @(posedge clk or posedge reset_p) begin
        if(reset_p) begin
            flip_flop_current <= 0;
            flip_flop_old <= 0;
        end
        else begin
            flip_flop_current <= cp;
            flip_flop_old <= flip_flop_current;
        end
    end
    
    assign p_edge = ({flip_flop_current, flip_flop_old} == 2'b10) ? 1 : 0;
    assign n_edge = ({flip_flop_current, flip_flop_old} == 2'b01) ? 1 : 0;
    
endmodule

 

 

< Source, Top Module >

module Top_module_of_LCD_Backligh_Control(
    input clk, reset_p,
    input [1:0] btn,
    output scl, sda );
    
    // Button Control Module
    wire btn_back_light_on, btn_back_light_off;
    btn_cntr btn_cntr_on (.clk(clk), .reset_p(reset_p), .btn(btn[0]), .btn_pedge(btn_back_light_on));
    btn_cntr btn_cntr_off (.clk(clk), .reset_p(reset_p), .btn(btn[1]), .btn_pedge(btn_back_light_off));
    
    // 버튼 입력
    reg comm_go;
    reg [7:0] data;
    
    always @(posedge clk or posedge reset_p) begin
        if(reset_p) begin
            comm_go = 0;
            data = 8'b0000_000;
        end
        else begin
            if(btn_back_light_on) begin
                data = 8'b1111_11111;
                comm_go = 1;
            end
            else if(btn_back_light_off) begin
                data = 8'b0000_0000;
                comm_go = 1;
            end
            else comm_go = 0;
        end
    end
    
    // Instance of i2c master module
    i2c_master instance_i2c (.clk(clk), .reset_p(reset_p), .comm_go(comm_go), .addr(7'h27), .read_write(0),
                             .data(data), .scl(scl), .sda(sda));
endmodule
  • 1번 버튼, btn_back_light_on 을 누르면 LCD 디스플레이의 Backlight이 켜지며,
    2번 버튼, btn_back_light_off 을 누르면 LCD 디스플레이의 Backlight이 꺼진다.
  • btn_back_light_on이 활성화되면 data = 8'b1111_1111을 전달한 뒤, comm_go = 1 대입함으로서 
    comm_go 가 활성화되면 data가 Master에서 Slave로 전달된다.
  • btn_back_light_off이 활성화되면 data = 8'b0000_0000을 전달한 뒤, comm_go = 0 대입함으로서
    comm_go 가 활성화되면 data가 Master에서 Slave로 전달된다.
  • 나머지 경우는 comm_go = 0이기 때문에 btn이 활성화되지 않으면 어떠한 데이터도 Basys3에서 LCD 디스플레이로 전달 되지 않는다.

 

 

 

 

6. Oscilloscope를 통한 측정

(왼) btn_back_light_on 버튼이 활성화 된 경우, (오른) btn_back_light_off 버튼이 활성화 된 경우

 

btn_back_light_on 버튼이 활성화 된 경우
btn_back_light_off 버튼이 활성화 된 경우

 

 

7. 동작 확인