8/22 I2C top모듈

정유석·2024년 8월 22일

교육 - 베릴로그

목록 보기
25/28

LCD

LCD는 8비트의 데이터 핀과 추가로 동작들을 제어하는 핀들이 있다(모두 합하여 16개) 하지만 포트를 너무 많이 사용해야하기 때문에 I2C통신이 가능하게하는 8574칩이 뒷면에 추가로 붙어있는것이 있다. 이칩을 이용하면 전원핀 2개와 scl, sda선 2개를 이용하여 LCD를 사용할 수 있고 칩에서 LCD 데이터 선 8개중 4개는 제어선, 4개는 데이터선으로 사용하여 4비트 모드만을 사용할 수 있다.

LCD와 뒤면에 붙어있는 I2C통신을 위한 칩

A0, A1, A2 기본 1,1,1(7) 납땜해서 분리된 위,아래 연결하면 0으로된다
일 대 다 연결을 위해 다른 주소가 필요하면 납땜해서 주소값을 바꿔 사용가능

(칩 내부의 모습)

shift register에 직렬인,병렬 아웃, I/O port 레지스터에 병렬인 병렬 아웃

버튼에 따른 i2c통신 데이터 보내기

0번 버튼 누르면 0000_0000 데이터 보내기
1번 버튼 누르면 1111_1111 데이터 보내기

module i2c_master_top(
    input clk, reset_p,
    input [1:0] btn, //버튼을 눌러 comm_go동작과 함께 데이터에 0000_0000 또는 1111_1111을 넣어주려함
    output scl, sda,
    output [15:0] led
);
    reg [7:0] data; //버튼을 눌러서 주는 값을 저장하기 위한 레지스터
    reg comm_go;
    
    wire [1:0] btn_pedge;
    button_cntr btn0(.clk(clk), .reset_p(reset_p), .btn(btn[0]), .btn_pedge(btn_pedge[0]));
    button_cntr btn1(.clk(clk), .reset_p(reset_p), .btn(btn[1]), .btn_pedge(btn_pedge[1]));
    
    always @(posedge clk or posedge reset_p)begin
        if(reset_p)begin
           data = 0;
           comm_go = 0; 
        end
        else begin
            if(btn_pedge[0])begin
               data = 8'b0000_0000;
               comm_go = 1; 
            end
            else if(btn_pedge[1])begin
//                data = 8'b1111_1111;
                data = 8'b0000_1000; //백라이트 BT는 3번 비트(P3)임
               comm_go = 1;
            end
            else comm_go = 0;
        end
    end
    
    I2C_master maste(.clk(clk), .reset_p(reset_p),
        .addr(7'h27), // Slave의 주소는 7bit이다.
        .rd_wr(0),       // 데이터를 read할것인지 write할 것인지
        .data(data),  // Slave쪽으로 보낼 데이터의 크기는 8bit
        .comm_go(comm_go), //1을 주면 통신 시작
        .scl(scl), .sda(sda),
        .led(led) );
    
endmodule

(칩의 포트들)

왼쪽에 BT는 LCD의 백라이트, P3이 포함된 모든 포트에 0000_0000 또는 1111_1111을 주면 LCD의 백라이트를 켜고, 끄고 할 수 있다.

데이터에 따른 오실로스코프

데이터 0000_0000

데이터 1111_1111

LCD 내부

(칩과 LCD의 (데이터 레지스터, 커맨드 레지스터) 사이의 관계)
데이터 전송 8비트
-E : enable
-data레지스터는 안에 있는 아스키코드에관한 디코더를 거져서 lcd에 값 출력
-command레지스터는 특정한 동작 수행
-RS는 data비트들을 data레지스터로 전송할지 command레지스터로 전송할지 선택
-RW는 읽기, 쓰기 (읽기는 동작을 수행하는중인지를 확인 할 수 있는 신호를 읽음)


4비트 모드 사용
8비트를 모드 쓰기에는 포트를 많이 사용하기 때문에 4비트 모드로 데이터 상위 4비트를 이용해서 데이터를 2번에 나눠서 전송

(칩과 LCD 사이의 관계)

I2C-BUS CONTRL과 SHIFT REGISTER간의 데이터 통신(시리얼 통신이기 때문에 4비트씩 2번에 나눠 보낼 수 있다)

(칩과 LCD 사이의 관계)

I2C통신을 위한 LCD뒷면에 붙은 칩과 LCD사이의 통신 관계를 그려놓은 그림

데이터 보내기 코드

module i2c_lcd_send_byte(
    input clk, reset_p,
    input [6:0] addr,
    input [7:0] send_buffer, //보낼 데이터
    input rs, send, // send가 1됬을때 1바이트 보냄
    output scl, sda,
    output reg busy, //보내는 동안 시간이 걸리기 때문에 busy가 0일때만 send 가능    
    output [15:0] led
);
    parameter IDLE                      = 6'b00_0001;
    parameter SEND_HIGH_NIBBLE_DISABLE  = 6'b00_0010; //NIBBLE : 4비트를 의미
    parameter SEND_HIGH_NIBBLE_ENABLE   = 6'b00_0100;
    parameter SEND_LOW_NIBBLE_DISABLE   = 6'b00_1000;
    parameter SEND_LOW_NIBBLE_ENABLE    = 6'b01_0000;
    parameter SEND_DISABLE              = 6'b10_0010;
    
    reg [7:0] data;
    reg comm_go;
    
    wire send_pedge;
    edge_detector_n _edge (.clk(clk), .reset_p(reset_p), .cp(send), .p_edge(send_pedge));
    
    wire clk_usec;
    clock_div_100 usec_clk (.clk(clk), .reset_p(reset_p), .clk_div_100_nedge(clk_usec));
    
    reg [21:0] count_usec; //3초 대기를 위함
    reg count_usec_e;
    always @(negedge clk or posedge reset_p)begin
        if(reset_p)count_usec = 0;
        else if(clk_usec && count_usec_e)count_usec = count_usec + 1; //S_IDLE에서 count_usec_e를 1주면 카운트 시작
        else if(!count_usec_e)count_usec = 0; //count_usec_e가 0이되면 리셋
    end
    
    reg [5:0] state ,next_state;
    always @(negedge clk or posedge reset_p)begin // neg 엣지
        if(reset_p)state = IDLE;
        else state = next_state;
    end
    
    always @(posedge clk or posedge reset_p)begin // pos 엣지
        if(reset_p)begin
            next_state = IDLE;
            busy = 0;
            comm_go = 0;
            data = 0;
            count_usec_e = 0;
        end
        else begin
            case(state)
                IDLE:begin
                    if(send_pedge)begin
                        next_state = SEND_HIGH_NIBBLE_DISABLE;
                        busy = 1;
                    end
                end
                SEND_HIGH_NIBBLE_DISABLE:begin
                    if(count_usec <= 22'd200)begin //I2C통신 데이터 전송 시간, 180us 정도에 넉넉히 시간 더 줌
                        data = {send_buffer[7:4], 3'b100, rs}; //{[d7 d6 d5 d4], BT, E, RW, RS}
                        comm_go = 1;
                        count_usec_e = 1;
                    end
                    else begin
                        count_usec_e = 0;
                        comm_go = 0;
                        next_state = SEND_HIGH_NIBBLE_ENABLE;
                    end
                end
                SEND_HIGH_NIBBLE_ENABLE:begin
                    if(count_usec <= 22'd200)begin
                        data = {send_buffer[7:4], 3'b110, rs}; //{[d7 d6 d5 d4], BT, E, RW, RS}
                        comm_go = 1;
                        count_usec_e = 1;
                    end
                    else begin
                        count_usec_e = 0;
                        comm_go = 0;
                        next_state = SEND_LOW_NIBBLE_DISABLE;
                    end
                end
                SEND_LOW_NIBBLE_DISABLE:begin
                    if(count_usec <= 22'd200)begin
                        data = {send_buffer[3:0], 3'b100, rs}; //{[d7 d6 d5 d4], BT, E, RW, RS}
                        comm_go = 1;
                        count_usec_e = 1;
                    end
                    else begin
                        count_usec_e = 0;
                        comm_go = 0;
                        next_state = SEND_LOW_NIBBLE_ENABLE;
                    end
                end
                SEND_LOW_NIBBLE_ENABLE:begin
                    if(count_usec <= 22'd200)begin
                        data = {send_buffer[3:0], 3'b110, rs}; //{[d7 d6 d5 d4], BT, E, RW, RS}
                        comm_go = 1;
                        count_usec_e = 1;
                    end
                    else begin
                        count_usec_e = 0;
                        comm_go = 0;
                        next_state = SEND_DISABLE;
                    end
                end
                SEND_DISABLE:begin
                    if(count_usec <= 22'd200)begin
                        data = {send_buffer[3:0], 3'b100, rs}; //{[d7 d6 d5 d4], BT, E, RW, RS}
                        comm_go = 1;
                        count_usec_e = 1;
                    end
                    else begin
                        count_usec_e = 0;
                        comm_go = 0;
                        next_state = IDLE;
                        busy = 0;
                    end
                end
            endcase
        end
    end
    
    I2C_master master(.clk(clk), .reset_p(reset_p),
        .addr(addr), // Slave의 주소는 7bit이다.
        .rd_wr(0),       // 데이터를 read할것인지 write할 것인지
        .data(data),  // Slave쪽으로 보낼 데이터의 크기는 8bit
        .comm_go(comm_go), //1을 주면 통신 시작
        .scl(scl), .sda(sda),
        .led(led));
    
endmodule

데이터 시트 46p

(LCD의 4비트모드 초기화)
4.1ms 안기다려도 가능

데이터시트 24p~25p
N : lcd는 화면에 윗줄과 아랫줄 총 두줄이 있는데 두줄중 몇 줄 쓸지를 정함
F : 문자를 출력하기 위해 5x8, 5x10비트중 어떤걸 쓸지 정함
D : 디스플레이 on/off
C : 커서 on/off
B : 커서가 있는 자리에 커서의 상태를 정함(1: 커서 깜빡깜빡, 0:커서 깜빡깜빡 안함)
클리어 디스플레이 : null문자로 덮어 쓰는거라 시간 딜레이 필요, 한문자당 37us필요, 디스플레이 40x2 문자 사용


-왼쪽 상단부터 출력, lcd내부에는 80개의 data레지스터가 있고 0번 주소지부터 lcd에 출력되고, 어드레스 카운터가 자동으로 증가하며 차례로 주소값 증가
-데이터시트 24p의 entry mode set의 I/D(increase/decrease) 주소값 증가 또는 감소 조절로 글씨 출력을 왼쪽->오른쪽, 왼쪽<-오른쪽 방향 조절
-S는 쉬프트 화면 넘어에있는 글자들 쉬프트 하면서 출력

I2C 탑 모듈

module i2c_txtlcd_top(
    input clk, reset_p,
    input [3:0] btn,
    output scl, sda,
    output [15:0]led);
    
    parameter IDLE = 6'b00_0001;
    parameter INIT = 6'b00_0010;
    parameter SEND_BYTE = 6'b00_0100;
    
     wire clk_usec;
    clock_div_100 usec_clk (.clk(clk), .reset_p(reset_p), .clk_div_100_nedge(clk_usec));
    
    reg [21:0] count_usec; //3초 대기를 위함
    reg count_usec_e;
    always @(negedge clk or posedge reset_p)begin
        if(reset_p)count_usec = 0;
        else if(clk_usec && count_usec_e)count_usec = count_usec + 1; //S_IDLE에서 count_usec_e를 1주면 카운트 시작
        else if(!count_usec_e)count_usec = 0; //count_usec_e가 0이되면 리셋
    end
    
    wire [3:0] btn_pedge;
    button_cntr btn0(.clk(clk), .reset_p(reset_p), .btn(btn[0]), .btn_pedge(btn_pedge[0]));
    button_cntr btn1(.clk(clk), .reset_p(reset_p), .btn(btn[1]), .btn_pedge(btn_pedge[1]));
    button_cntr btn2(.clk(clk), .reset_p(reset_p), .btn(btn[2]), .btn_pedge(btn_pedge[2]));
    button_cntr btn3(.clk(clk), .reset_p(reset_p), .btn(btn[3]), .btn_pedge(btn_pedge[3]));
    
    reg [7:0] send_buffer;
    reg rs, send;
    wire busy;
    i2c_lcd_send_byte txtlcd(.clk(clk), .reset_p(reset_p),
        .addr(7'h27),
        .send_buffer(send_buffer), //보낼 데이터
        .rs(rs), .send(send), // send가 1됬을때 1바이트 보냄
        .scl(scl), .sda(sda),
        .busy(busy), //보내는 동안 시간이 걸리기 때문에 busy가 0일때만 send 가능    
        .led(led));
    
    reg [5:0] state, next_state; //6비트짜리 상태들
    always @(negedge clk or posedge reset_p)begin
        if(reset_p)state = IDLE;
        else state = next_state;
    end
    
    reg init_flag;
    reg [3:0] cnt_data;
    always @(posedge clk or posedge reset_p)begin
        if(reset_p)begin
            next_state = IDLE;
            init_flag = 0;
            cnt_data = 0;
            rs = 0;
        end
        else begin
            case(state)
                IDLE:begin
                    if(init_flag)begin
                        if(btn_pedge[0])next_state = SEND_BYTE;
                    end
                    else begin
                        if(count_usec <= 22'd80_000)begin
                            count_usec_e = 1;
                        end
                        else begin
                            next_state = INIT;
                            count_usec_e = 0;
                        end
                    end
                end
                INIT:begin
                    if(busy)begin
                        send = 0;
                        if(cnt_data >= 6)begin
                            next_state = IDLE;
                            init_flag = 1;
                            cnt_data = 0;
                        end                        
                    end
                    else if(!send)begin //send가 1이되면 i2c_lcd_send_byte모듈에서 엣지디텍터에 의해 한 클락 뒤에 send_pedge가 발생하고 이 send_pedge신호를 보고 i2c_lcd_send_byte모듈의 상태머신에서 busy가 1이된다, 
                        case(cnt_data) //그러면 busy가 1이되기 전에 이미 이쪽 상태머신에 들어와 다시 case문에 들어와서 1번케이스의 send_buffer를 바꾸기 때문에 send가 0일때만 들어올 수 있도록 조건을 건다
                            0:send_buffer = 8'h33;
                            1:send_buffer = 8'h32;
                            2:send_buffer = 8'h28;
                            3:send_buffer = 8'h0f;
                            4:send_buffer = 8'h01;
                            5:send_buffer = 8'h06;
                        endcase 
                        rs = 0;                                              
                        send = 1;
                        cnt_data = cnt_data + 1;
                    end
                    
                end
                SEND_BYTE:begin
                    if(busy)begin
                        next_state = IDLE;
                        send = 0;
                        if(cnt_data >= 9)cnt_data = 0;
                        else cnt_data = cnt_data + 1;    
                    end
                    else begin
                        send_buffer = "A"+cnt_data; //A의 대문자 아스키값 ""사용//아스키코드 값 점점 증가
                        rs = 1;
                        send = 1;
                    end
                    
                end
            endcase
        end
    end
    
endmodule
profile
개인 기록공간

0개의 댓글