관리 메뉴

거북이처럼 천천히

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

RTL Design/Verilog RTL 설계

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

유로 청년 2024. 9. 5. 11:13

1.  서론

  • 이전 게시글까지 Basys3에서 I2C 프로토콜 생성 (i2c_master)에 대해서 살펴 보았으며, I2C 통신이 왜 필요하며, I2C 통신의 장점 및 특징에 대해서 살펴보았다.
  • 이를 통해 LCD 디스플레이 모듈와 I2C 통신을 통해 Basys3의 버튼을 이용하여 LCD 디스플레이의 Back light를 컨트롤하였다,
  • 이번에는 LCD 디스플레이 모듈에 Character 문자를 보내여 LCD 화면에 출력해보도록 하겠다.

 

 

 

 

2. I2C 통신을 통해 LCD 디스플레이에게 데이터를 전송하는 방법

  • MCU측에서 LCD 디스플레이 모듈인 HD44780U으로 데이터를 전송하는 방법에는 크게 2가지 방법이 있다.
    ▶ 8bit 데이터를 4bit 씩 두 번에 걸쳐서 보내는 방법
    ▶ 8bit 데이터를 8bit 씩 한 번에 보내는 방법 

Source : Data sheet of HD44780U LCD Display

 

 

  • 4bit 모드 (4bit씩 나누어 보내는 경우) 는 상대적으로 적은 핀을 사용하기 때문에 핀 자원을 절약할 수 있지만, 데이터를 두 번에 걸쳐서 보내기 때문에 8bit 모드 (8bit로 한 번에 보내는 경우) 보다 데이터 전송 단계 및 전송 시간이 상대적으로 많다.
  • 8bit 모드는 8bit 데이터를 한 번에 보내기 때문에 데이터 전송 단계 및 전송 시간이 짧다. 하지만, 8bit 데이터를 한 번에 전송하는 만큼 전송에 필요한 핀이 4bit 모드보다 상대적으로 많다.
  • 이번 I2C 통신을 통한 HD44780U LCD 디스플레이 모듈간에 통신은 4bit mode를 이용하여 데이터를 송신하고자 한다.



  • Q) 왜 8bit 모드를 사용하지 않고, 4bit 모드를 사용하는가?
    A) 
    HD44780U LCD dispaly는 독립적으로 I2C 통신할 수 있는 모듈이 아니기 때문에 PCF 8574칩을 매개체로 I2C 통신을 수행하며, 8574칩을 통해 데이터를 전달받게 된다. 따라서 8574칩의 8개의 핀으로 LCD의 모든 필수 기능을 제어 할 수 있다,

    하지만, 8574칩의 출력핀이 8개라고 하더라도 LCD 제어에 필요한 4개의 제어 신호 (BT, RS, Enable, RW)을 제외하면 실질적으로 데이터를 송수신할 수 있는 출력핀은 4개에 불과하다. 따라서 8bit 모드가 아닌 4bit를 모드를 사용하며, 만약 8bit 모드를 사용하고 싶다면 추가적인 8574칩을 사용하여 데이터를 송수신 할 수 있는 출력핀을 추가적으로 만들어 줘야 한다.

 

 

 

 

3. 4bit 모드를 사용하여 LCD 디스플레이와 I2C 통신하는 방법

  • 위 내용을 통해 PCF8574칩을 통해 HD44780U 모듈과 통신하기 위해서는 4bit 모드를 사용하여 8bit 데이터를 두 번에 걸쳐서 나눠서 보내야 하는 것을 알게 되었다.
  • 이번에는 구체적으로 4bit 모드를 통해 데이터를 보내기 위해서는 어떠한 형태로 데이터로 보내야 하는지에 대해서 알아보도록 하겠다.

3.1. 4bit Mode Interface

(왼) PCF8574칩의 Block diagram, (오른) HD44780U LCD 의 Block diagram

  • 4bit Mode에서 HD44780U 디스플레이 모듈은 4개의 데이터 라인 (DB7 ~ DB4)을 사용한다.
    [ 단, 데이터 라인 (DB0 ~ DB3)은 사용하지 않아 비활성화된다. ]
  • 8bit I/O bit 중 4bit는 송수신하고자 하는 데이터를 담고 있으며, 4bit는 LCD 제어에 필요한 제어 신호를 담고 있다.
  • 4bit 제어 신호는 다음과 같은 정보를 갖는다. 

    BT : LCD 디스플레의의 Backlight를 제어하는 신호
    RS (Regsister Select) : Read / Write 할 Register를 선택하는 제어 신호
                                              RS : 0 이면 Command Register, RS : 1 이면 Data Register
    Enable : LCD 모듈에게 "PCF8574의 출력핀에 유효한 데이터가 준비되어 있으니, 받을 준비하라."는 신호이다.
        이를 통해 LCD 모듈은 PCF8574칩으로부터 데이터를 받을 준비를 하게 된다.
    RW (Read/Write) : 목표인 Slave 모듈을 Read 할 것인지? Write 할 것인지?를 결정하는 제어 신호이다.

  • 8bit 데이터를 8574칩을 통해 4bit 모드로 LCD 모듈에게 전송한다면 상위 4비트부터 순차적으로 2번에 걸쳐서 전송된다.


  • 단, 주의할 점은 상위 4비트부터 무작위로 데이터를 2번 전송하면 안되고, Busy 플래그가 활성화되었는지 여부를 확인하고, Busy 플래그가 비활성화 상태일 때만 나머지 4비트 데이터를 전송해야 한다.

  • Q) Busy 플래그, 해당 플래그는 무엇을 의미하는가?
    A) 
    Busy 플래그는 "현재 데이터를 Basys3에서 8574칩으로 보내는 상태인가?"를 나타내는 플래그이다. 해당 플래그를 통해 현재 데이터를 보내는 중인지를 확인할 수 있으며, 이를 통해 데이터를 보내는 상태인데, 데이터를 또 보내는 불상사를 방지할 수 있다. 

    만약, Busy 플래그가 활성화 상태에서 이를 무시하고, 데이터를 전송하면 데이터를 정상적으로 전달하지 못한 상태에서 다음 데이터를 전송하게 된다.


3.2. 정리 

  • LCD 디스플레이와 Basys3 간에 I2C 통신을 하기 위해 PCF8574칩을 매개체로 사용한다.
  • Basys3에서 PCF8574칩에게 8bit 데이터를 전송하지만, 해당 8bti 데이터에는 4bit 제어신호와 4bit 데이터가 포함되어 있어 LCD 디스플레이와의 통신을 PCF8574칩을 매개체로 통신하기 위해서는 4bit mode를 사용해야 한다.
  • 4bit mode는 8bit 데이터를 2번에 걸쳐서 전송하며, 이 때 MSB부터 순차적으로 전송한다.
  • 단, 연속적으로 4bit 데이터를 2번 보내는 것이 아니라 Busy 플래그 신호를 보고, Busy 플래그 신호가 비활성화 상태일 때만 데이터를 전송한다.

 

 

 

 

 

4. 4bit mode을 이용하여 8bit 데이터 전송하는 모듈 설계

  • 이전 게시글에서 설계한 "i2c_master" 모듈은 I2C 통신 과정 및 방법을 토대로 설계한 모듈이기 때문에 "어떻게 Master가 Slave을 호출한 뒤, 8bit 데이터를 보내는가?"를 설계했다.
  • 이번에 설계한 모듈은 LCD 디스플레이 모듈과 4bit mode로 통신하기 위해 8bit 데이터를 4bit 씩 끊어서 순차적으로 보내는 모듈이다. 이 점을 유념하도록 하자.
  • 코드에 대한 설명은 코드와 함께 설명하도록 하겠다.

 

 

< Source, Input / Output 선언 >

module i2c_lcd_send_byte(
    input clk, reset_p,
    input [6:0] addr,
    input [7:0] send_buffer,
    input rs, send, // rs : Register select, send : 8574칩에서 LCD 디스플레이 전이 컨트롤
    output scl, sda,
    output reg busy );  // busy : 현재 데이터를 디스플레이 보내는 상태인지 여부를 표현
    
endmodule
  • 각각의 Input / Output 변수들은 다음과 같은 의미 및 역활을 한다.
    addr : Read/Write할 Slave 모듈의 주소
    send_buffer : Target slave 모듈에 Read / Write 할 데이터를 임시 저장한 Buffer
    rs : Register Selector
    send : 8574칩에서 LCD 디스플레이 전이 컨트롤 변수
    busy : 외부 모듈에게 현재 Basys3에서 Slave 모듈에게 데이터를 전송하는 중인지 여부를 나타내는 플래그

 

 

 

 

< Source, State machine 정의 >

// ******** Declare state machine ********
parameter S_IDLE = 6'b00_0001;
parameter S_SEND_HIGH_NIBBLE_DISABLE = 6'b00_0010;
parameter S_SEND_HIGH_NIBBLE_ENABLE = 6'b00_0100;
parameter S_SEND_LOW_NIBBLE_DISABLE = 6'b00_1000;
parameter S_SEND_LOW_NIBBLE_ENABLE = 6'b01_0000;
parameter S_SEND_DISABLE = 6'b10_0000;
  • 각각의 상태 단계는 다음과 같은 동작을 수행하게 된다.
    ▶ S_IDLE : 외부에서 send 값이 활성화되기 전까지 대기하는 상태 단계
         해당 단계에서는 send 값의 Positive edge가 발생하면 순차적으로 데이터를 전송하게 된다.
    ▶ S_SEND_HIGH_NIBBLE_DISABLE : 8bit 데이터의 상위 4bit 데이터를 Basys3에서 8574칩에게 전달하
         는 과정이다. 해당 단계에서는 8574칩에게 제어신호와 데이터가 혼합된 8bit 데이터를 전달하지만,
         아직 LCD까지 데이터가 전달되지 않은 상태이다.

  • Q) S_SEND_HIGH_NIBBLE_DISABLE 상태 단계에서 LCD 모듈에게 8bit 데이터가 전달되지 않았다면 언제 8bit 데이터가 전달되는가?
  • A) 4bit 모드에서 Enable 신호 동작에 의해서 LCD 디스플레이에게 데이터를 줄지 여부가 결정한다. 각 4비트 전송할 때마다 Enable 신호는 LOW → HIGH → LOW 싸이클이 필요하다. Enable 신호값의 변화는 어떤 의미 및 메세지를 갖는가? 
    Enable 신호 값이 Positive edge가 발생하면 LCD에게 "8574칩의 출력핀에 데이터가 준비되어 있으니, 데이터를 읽을 준비하세요." 알린다. 하지만, 이 시점에는 아직 LCD에게 실제로 데이터가 전달되지 않는다.
    그리고 난 뒤, Enable 신호 값이 Negative edge가 발생하면 LCD가 실제로 8574칩의 출력핀에 준비된 데이터를 읽는다. 따라서 Enable 신호는 LCD에게 데이터가 준비되었음을 알려주는 신호라고 볼 수 있는 것이다.


  • 정리) 4bit 모드에서의 데이터 전송 순서
    1.
    8bit 데이터 중 상위 4bit를 PCF8574칩에게 전달한다. (Enable = 0)
    2.
    8574칩에게 데이터를 전달 한 뒤, Enable 값을 Low → High 로 변경할 준비 (LCD에게 준비 신호)
    3.
    잠시 대기 (LCD가 인식할 시간)
    4.
    Enable 값을 High → Low 로 변경 (LCD가 8574칩의 출력핀에 준비된 데이터를 읽는 트리거)
    5.
    8bit 데이터 중 하위 4bit를 8574칩에게 전달한다. (Enable = 0)
    6.
    8574칩에게 데이터를 전달 한 뒤, Enable 값을 Low→ High 로 변경할 준비 (LCD에게 준비 신호)
    7.
    잠시 대기 (LCD가 인식할 시간)
    8.
    Enable 값을 High→ Low 로 변경 (LCD가 8574칩의 출력핀에 준비된 데이터를 읽는 트리거)


    ▶ S_SEND_HIGH_NIBBLE_ENABLE : 해당 단계에서는 8574칩에게 전달된 데이터를 LCD 모듈에게 "8574칩의 출력핀에 LCD 모듈이 읽어야 할 데이터가 준비된 상태이니 데이터를 읽을 준비해." 을 알려주는 단계이다. 이를 위해 Enable 값을 0 → 1로 변경하여 Positive edge를 발생 시킨다.

    ▶ S_SEND_LOW_NIBBLE_DISABLE :  해당 단계에서는 Enable 값을 1 → 0으로 떨어뜨려서 Negative edge가 발생하는데, 이로 인해 LCD 디스플레이 모듈은 8574칩의 출력핀에 준비된 데이터를 읽게 된다. 동시에 Basys3는 8574칩에게 나머지 하위 4bit 데이터를 전달한다.

    ▶ S_SEND_LOW_NIBBLE_ENABLE : 해당 단계에서는 Enable 값을 0 → 1로 변경하여 Positive edge를 발생시킴으로서 LCD 모듈에게 "나머지 하위 4bit 데이터도 8574칩에 준비된 상태이니 읽을 준비해."을 알려주는 단계이다. 

    ▶  S_SEND_DISABLE : 해당 단계에서는 Enable 값을 1 → 0으로 떨어뜨려서 Negative edge가 발생하는데, 이로 인해 LCD 디스플레이 모듈은 8574칩의 출력핀에 준비된 나머지 4bit 데이터를 읽게 된다.

 

 

 

< Source, state, next_state 변수 선언 및 언제 다음 상태로 전이되는가를 정의 >

// ******** Declare state, next-state ********
reg [5:0] state, next_state;
    
// ******** 언제 다음 상태로 전이되는가? ********
always @(posedge clk or posedge reset_p) begin
   if(reset_p) state = S_IDLE;
   else state = next_state;
end

 

 

 

 

< Source, usecond counter 생성 >

    // ******** Get 1usec pulse ********
    wire clk_1usec;
    clk_div_100 clk_div_usec (.clk(clk), .reset_p(reset_p), .clk_div_100_nedge(clk_1usec));
    
    // ******** Create Microsecond Counter ********
    reg [21:0] counter_usec;
    reg counter_enable;
    
    always @(posedge clk or posedge reset_p) begin
        if(reset_p || !counter_enable) counter_usec = 0;
        else if(clk_1usec && counter_enable) counter_usec = counter_usec + 1;
    end
  • I2C 통신을 통해 8bit 데이터를 전송하기 위해서는 최소한 180us 시간이 소요된다.
  • 따라서 4bit 모드로 2번에 걸쳐 데이터를 보내기 위해서는 상위 4bit 데이터를 보낸 뒤, 180us 동안 usecond counter를 통해 대기할 필요가 있다.

 

 

 

< Source, send 신호의 Positive edge 얻기 >

    // ******** Get positive edge of send ********
    wire send_pedge;
    edge_detector edge_detector_send (.clk(clk), .reset_p(reset_p), .cp(send), .p_edge(send_pedge));
  • send 신호는 Basys3 에서 PCF 8574칩으로 데이터를 전송하는 시점을 결정하는 트리거이다.
  • send 신호 값에서 Positive edge가 발생하면 Basys3에서 PCF 8574칩으로 데이터를 전송하기 시작한다.
  • 이 과정은 I2C 통신 프로토콜을 통해 이루어 진다.

 

 

 

< Source, 필요한 레지스터 및 와이어 선언 >

    // ******** Declare necessary variables ********
    reg [7:0] data;   // data = {4bit data, BT, Enable, RW, RS}
    reg comm_go;
  • data 8bit 레지스터는 Basys3에서 8574칩으로 전달될 8bit 데이터이다.
  • data 레지스터는 4bit data, BT, Enable, RW, RS 순으로 데이터를 구성하고 있다.
  • comm_go는 Basys3에서 8574칩으로 데이터를 전달하는 Trigger이다. 

 

 

< Source, 각 상태 단계의 동작 및 다음 상태 전이 조건 정의 (큰틀) >

// ******** 각 상태의 동작 정의 및 다음 상태 전이 조건 정의 ********
    always @(negedge clk or posedge reset_p) begin
        if(reset_p) begin
            data = 8'b0000_0000;
            comm_go = 0;
            counter_enable = 0;
            next_state = S_IDLE;
            busy = 0;
        end
        else begin
            case(state) 
            
            ''' 각 상태 동작 및 다음 상태 전이 조건 '''
            
            endcase
        end
    end

 

 

 

 

< Source, 1단계) S_IDLE 상태 단계 >

// 1단계) S_IDLE 단계
S_IDLE: begin
    if (send_pedge) begin
        busy = 1;
        next_state = S_SEND_HIGH_NIBBLE_DISABLE;
    end
end
  • send 신호는 8bit 데이터를 Basys 3에서 8574칩으로 전송할 지 여부를 결정하는 트리거 신호이다.
  • send 신호값에서 Positive edge이 발생하면  Basys3에서 PCF8574칩으로 8bit 데이터를 보내기 시작한다.
  • Basys3 측에서 8bit 데이터를 보내기 시작하기 때문에 외부 모듈에게 "이전에 전달 받은 8bit 데이터를 Basys3에서 PCF 8574칩으로 전달하는 중입니다. 따라서 대기 시간을 갖고, 나중에 보내주세요."라는 신호 보내기 위해 Busy 변수값을 1로 변환한다.

  • Q) send 신호와 comm_go 신호는 어떤 차이를 갖는가?
  • A) send 신호는 8bit 데이터를 Basys3에서 PCF 8574칩으로 전달할 지 여부를 결정하는 트리거 신호이며, 반대로 comm_go 신호는 8bit 데이터를 4bit mode 방식으로 보내는 과정에서 4bit 제어 신호와 4bit 데이터를 혼합한 8bit 데이터를 PCF 857칩으로 전달할 지 여부를 결정하는 트리거 신호이다. 

 

 

 

 

< Source, 2단계) S_SEND_HIGH_NIBBLE_DISABLE 상태 단계 >

// 2단계) S_SEND_HIGH_NIBBLE_DISABLE 단계
S_SEND_HIGH_NIBBLE_DISABLE: begin
    if (counter_usec <= 22'd200) begin  // i2c_master 모듈을 통해 8bit 데이터를 보내는 소요되는 시간 = 180us
                                        // 하지만, 넉넉하게 200us 동안 기다린다.
        data = {send_buffer[7:4], 3'b100, rs};
        counter_enable = 1;
        comm_go = 1;
    end 
    else begin
        counter_enable = 0;
        comm_go = 0;
        next_state = S_SEND_HIGH_NIBBLE_ENABLE;
    end
end
  • S_SEND_HIGH_NIBBLE_DISABLE 상태 단계는 8bit 데이터 중 상위 4bit만 먼저 Basys3측에서 LCD 모듈에게 전달하는 상태 단계이며, 해당 단계에서는 PCF 8574칩에 전달 되었을 뿐, 아직 LCD 모듈까지 데이터가 전달되지 않은 상태이다.
  • Q) 왜 usecond counter를 이용하여 200us 동안 대기하는가? 
    A) I2C 통신 프로토콜을 통해 8bit 데이터를 보내는 데, 소요되는 시간은 최소 180us 시간이 소요된다. 따라서 180us 대기 시간 없이 바로 데이터를 전송한다면 Basys3에서 8574칩으로 데이터를 보내는 중인데, Basys3 측에서 데이터를 또 보내는 것이기 때문에 온전히 데이터가 8574칩으로 전달되지 않는다. 
    이러한 문제점을 방지하고자 usecond counter를 이용하여 200us 대기 시간을 갖고, 순차적으로 데이터를 순차적으로 보내는 것이다. 
  • 200us 동안 대기하며, 상위 4비트 데이터를 보낸 뒤, 다음 상태인 S_SEND_HIGH_NIBBLE_ENABLE 로 전이한다.

 

 

 

 

< Source, 3단계) S_SEND_HIGH_NIBBLE_ENABLE 상태 단계 >

// 3단계) S_SEND_HIGH_NIBBLE_ENABLE 단계
S_SEND_HIGH_NIBBLE_ENABLE: begin
    if (counter_usec <= 22'd200) begin
        data = {send_buffer[7:4], 3'b110, rs};
        counter_enable = 1;
        comm_go = 1;
    end 
    else begin
        counter_enable = 0;
        comm_go = 0;
        next_state = S_SEND_LOW_NIBBLE_DISABLE;
    end
end
  • S_SEND_HIGH_NIBBLE_ENABLE 상태 단계는 Enable 신호값을 0 → 1로 변화시켜 Positive edge를 발생시켜 LCD에게 "8574칩의 출력핀에 데이터가 준비되어 있으니, 데이터를 읽을 준비해." 를 알려주는 단계이다.
  • 이를 위해 Enable 신호가 있는 bit 값을 0 → 1로 변화시켜준 뒤, 똑같이 usecond counter를 이용하여 200us 대기 시간을 갖는다.
  • 200us 동안 대기하며, 상위 4비트 데이터를 보낸 뒤, 다음 상태인 S_SEND_LOW_NIBBLE_DISABLE 로 전이한다.

 

 

 

 

< Source, 4단계) S_SEND_LOW_NIBBLE_DISABLE 상태 단계 >

// 4단계) S_SEND_LOW_NIBBLE_DISABLE 단계
S_SEND_LOW_NIBBLE_DISABLE: begin
    if (counter_usec <= 22'd200) begin  // i2c_master 모듈을 통해 8bit 데이터를 보내는 소요되는 시간 = 180us
                                        // 하지만, 넉넉하게 200us 동안 기다린다.
        data = {send_buffer[3:0], 3'b100, rs};
        counter_enable = 1;
        comm_go = 1;
    end 
    else begin
        counter_enable = 0;
        comm_go = 0;
        next_state = S_SEND_LOW_NIBBLE_ENABLE;
    end
end
  • S_SEND_LOW_NIBBLE_DISABLE  상태 단계는 나머지 하위 4bit 데이터를  Basys3측에서 LCD 모듈에게 전달하는 동시에 LCD 디스플레이 모듈에서 8574칩의 출력핀에 준비된 데이터를 읽는 상태 단계이다.
  • 하위 4bit 데이터와 4bit 제어 신호를 결합하여 데이터를 전달 한 뒤, usecond counter를 통해 200us 동안 대기한다.
  • 200us 동안 대기하며, 하위 4비트 데이터를 보낸 뒤, 다음 상태인 S_SEND_LOW_NIBBLE_ENABLE 로 전이한다.

 

 

 

 

< Source, 5단계) S_SEND_LOW_NIBBLE_ENABLE 상태 단계 >

// 5단계) S_SEND_LOW_NIBBLE_ENABLE 단계
S_SEND_LOW_NIBBLE_ENABLE: begin
    if (counter_usec <= 22'd200) begin  // i2c_master 모듈을 통해 8bit 데이터를 보내는 소요되는 시간 = 180us
                                        // 하지만, 넉넉하게 200us 동안 기다린다.
        data = {send_buffer[3:0], 3'b110, rs};
        counter_enable = 1;
        comm_go = 1;
    end 
    else begin
        counter_enable = 0;
        comm_go = 0;
        next_state = S_SEND_DISABLE;
    end
end
  • S_SEND_LOW_NIBBLE_ENABLE 상태 단계는 Enable 신호값을 0 → 1로 변화시켜 Positive edge를 발생시켜 LCD에게 "8574칩의 출력핀에 데이터가 준비되어 있으니, 데이터를 읽을 준비해." 를 알려주는 단계이다.
  • 이를 위해 Enable 신호가 있는 bit 값을 0 → 1로 변화시켜준 뒤, 똑같이 usecond counter를 이용하여 200us 대기 시간을 갖는다.
  • 200us 동안 대기하며, 하위 4비트 데이터를 보낸 뒤, 다음 상태인 S_SEND_LOW_NIBBLE_DISABLE 로 전이한다.

 

 

 

 

< Source, 6단계) S_SEND_DISABLE 상태 단계 >

// 6단계) S_SEND_DISABLE 단계
S_SEND_DISABLE: begin
    if (counter_usec <= 22'd200) begin  // i2c_master 모듈을 통해 8bit 데이터를 보내는 소요되는 시간 = 180us
                                        // 하지만, 넉넉하게 200us 동안 기다린다.
        data = {send_buffer[3:0], 3'b100, rs};
        counter_enable = 1;
        comm_go = 1;
    end 
    else begin
        counter_enable = 0;
        comm_go = 0;
        next_state = S_IDLE;
        busy = 0;
    end
end
  • S_SEND_DISABLE 상태 단계는 Enable 신호 값을 1→ 0으로 떨어뜨려 Negative edge를 발생시킴으로서 LCD 디스플레이 모듈이 8574의 출력핀에 준비된 마지막 하위 4비트를 읽는 단계이다. 
  • Enable 신호 값에서 Positive edge가 발생하면 LCD 모듈은 8574칩으로부터 데이터를 읽을 준비를 하며,
    Enable 신호 값에서 Negative edge가 발생하면 실제로 LCD 모듈은 8574칩으로부터 데이터를 읽는다.
  • 마지막 단계인  S_SEND_DISABLE 상태 단계 를 끝으로 8bit 데이터가 Basys3에서 PCF 8574칩을 거쳐 LCD 모듈에 전달된다.

 

 

 

< Source, i2c_master 모듈의 인스턴스 선언 >

// Instance of i2c-master module
i2c_master i2c_master_instance (.clk(clk), .reset_p(reset_p), .addr(8'h27), .read_write(0),
                    .data(data), .comm_go(comm_go), .scl(scl), .sda(sda));
  • i2c_master 모듈을 통해 I2C 통신 프로토콜 방식으로 Basys3에서 PCF8574칩으로 데이터가 전달된다.
  • i2c_master 모듈에 대해 궁금하다면 아래 게시글 참고하길 바란다.
    https://jbhdeve.tistory.com/315
 

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

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

jbhdeve.tistory.com

 

 

 

 

 

 

 

5. 전체 소스 코드

module i2c_lcd_send_byte(
    input clk, reset_p,
    input [6:0] addr,
    input [7:0] send_buffer,
    input rs, send, // rs : Register select, send : 8574칩에서 LCD 디스플레이 전이 컨트롤
    output scl, sda,
    output reg busy );  // busy : 현재 데이터를 디스플레이 보내는 상태인지 여부를 표현
    
    // ******** Declare state machine ********
    parameter S_IDLE = 6'b00_0001;
    parameter S_SEND_HIGH_NIBBLE_DISABLE = 6'b00_0010;
    parameter S_SEND_HIGH_NIBBLE_ENABLE = 6'b00_0100;
    parameter S_SEND_LOW_NIBBLE_DISABLE = 6'b00_1000;
    parameter S_SEND_LOW_NIBBLE_ENABLE = 6'b01_0000;
    parameter S_SEND_DISABLE = 6'b10_0000;
    
    // ******** Declare state, next-state ********
    reg [5:0] state, next_state;
    
    // ******** 언제 다음 상태로 전이되는가? ********
    always @(posedge clk or posedge reset_p) begin
        if(reset_p) state = S_IDLE;
        else state = next_state;
    end
    
    // ******** Get 1usec pulse ********
    wire clk_1usec;
    clk_div_100 clk_div_usec (.clk(clk), .reset_p(reset_p), .clk_div_100_nedge(clk_1usec));
    
    // ******** Create Microsecond Counter ********
    reg [21:0] counter_usec;
    reg counter_enable;
    
    always @(posedge clk or posedge reset_p) begin
        if(reset_p || !counter_enable) counter_usec = 0;
        else if(clk_1usec && counter_enable) counter_usec = counter_usec + 1;
    end
    
    // ******** Get positive edge of send ********
    wire send_pedge;
    edge_detector edge_detector_send (.clk(clk), .reset_p(reset_p), .cp(send), .p_edge(send_pedge));
    
    // ******** Declare necessary variables ********
    reg [7:0] data;   // data = {4bit data, BT, Enable, RW, RS}
    reg comm_go;
    
    // ******** 각 상태의 동작 정의 및 다음 상태 전이 조건 정의 ********
    always @(negedge clk or posedge reset_p) begin
        if(reset_p) begin
            data = 8'b0000_0000;
            comm_go = 0;
            counter_enable = 0;
            next_state = S_IDLE;
            busy = 0;
        end
        else begin
            case(state) 
                // 1단계) S_IDLE 단계
                S_IDLE : begin
                    if(send_pedge) begin
                        busy = 1;
                        next_state = S_SEND_HIGH_NIBBLE_DISABLE;
                    end
                end
                
                // 2단계) S_SEND_HIGH_NIBBLE_DISABLE 단계
                S_SEND_HIGH_NIBBLE_DISABLE : begin
                    if(counter_usec <= 22'd200) begin // i2c_master 모듈을 통해 8bit 데이터를 보내는 소요되는 시간 = 180us
                                                      // 하지만, 넉넉하게 200us 동안 기다린다.
                        data = {send_buffer[7:4], 3'b100, rs};
                        counter_enable = 1;
                        comm_go = 1;
                    end 
                    else begin
                        counter_enable = 0;
                        comm_go = 0;
                        next_state = S_SEND_HIGH_NIBBLE_ENABLE;
                    end
                end
                
                // 3단계) S_SEND_HIGH_NIBBLE_ENABLE 단계
                S_SEND_HIGH_NIBBLE_ENABLE : begin
                    if(counter_usec <= 22'd200) begin
                        data = {send_buffer[7:4], 3'b110, rs};
                        counter_enable = 1;
                        comm_go = 1;
                    end
                    else begin
                        counter_enable = 0;
                        comm_go = 0;
                        next_state = S_SEND_LOW_NIBBLE_DISABLE;
                    end
                end
                
                // 4단계) S_SEND_LOW_NIBBLE_DISABLE 단계
                S_SEND_LOW_NIBBLE_DISABLE : begin
                    if(counter_usec <= 22'd200) begin // i2c_master 모듈을 통해 8bit 데이터를 보내는 소요되는 시간 = 180us
                                                      // 하지만, 넉넉하게 200us 동안 기다린다.
                        data = {send_buffer[3:0], 3'b100, rs};
                        counter_enable = 1;
                        comm_go = 1;
                    end 
                    else begin
                        counter_enable = 0;
                        comm_go = 0;
                        next_state = S_SEND_LOW_NIBBLE_ENABLE;
                    end
                end
                
                // 5단계) S_SEND_LOW_NIBBLE_ENABLE 단계
                S_SEND_LOW_NIBBLE_ENABLE : begin
                    if(counter_usec <= 22'd200) begin // i2c_master 모듈을 통해 8bit 데이터를 보내는 소요되는 시간 = 180us
                                                      // 하지만, 넉넉하게 200us 동안 기다린다.
                        data = {send_buffer[3:0], 3'b110, rs};
                        counter_enable = 1;
                        comm_go = 1;
                    end 
                    else begin
                        counter_enable = 0;
                        comm_go = 0;
                        next_state = S_SEND_DISABLE;
                    end
                end
                
                // 6단계) S_SEND_DISABLE 단계
                S_SEND_DISABLE : begin
                    if(counter_usec <= 22'd200) begin // i2c_master 모듈을 통해 8bit 데이터를 보내는 소요되는 시간 = 180us
                                                      // 하지만, 넉넉하게 200us 동안 기다린다.
                        data = {send_buffer[3:0], 3'b100, rs};
                        counter_enable = 1;
                        comm_go = 1;
                    end 
                    else begin
                        counter_enable = 0;
                        comm_go = 0;
                        next_state = S_IDLE;
                        busy = 0;
                    end
                end
                
            endcase
        end
    end
    
    // Instance of i2c-master module
    i2c_master i2c_master_instance (.clk(clk), .reset_p(reset_p), .addr(8'h27), .read_write(0),
                    .data(data), .comm_go(comm_go), .scl(scl), .sda(sda));
    
endmodule