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 레지스터에 병렬인 병렬 아웃
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의 백라이트를 켜고, 끄고 할 수 있다.



(칩과 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는 쉬프트 화면 넘어에있는 글자들 쉬프트 하면서 출력
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