2024.8.14 수업날
LCD 데이터시트
I2C 통신모듈 데이터시트
I2C_LCD 데이터시트
< I2C_LCD 텍스트 제어 >
어제 배웠던 I2C통신으로 이번에는 LCD에 텍스트를 제어해본다.
아래는 4-Bit Interface를 기준으로 I2C 모듈과 LCD의 핀 연결이 어떤 방식으로 연결되어있는지에 대한 그림이다.
다음으로
아래의 데이터시트를 보고 LCD에 1byte를 전송하는 소스코드를 작성해보자.
i2c_lcd_send_byte 소스코드
/////////////////////////////////////////////////////// 2024.8.14
////////////////////////////////////////////// 텍스트 lcd에 1byte 보내기
module i2c_lcd_send_byte(
input clk, reset_p,
input [6:0] addr,
input [7:0] send_buffer,
input send, rs,
output scl, sda,
output reg busy);
parameter IDLE = 6'b00_0001;
parameter SEND_HIGH_NIBBLE_DISABLE = 6'b00_0010; // NIBBLE->4byte
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_0000;
reg[7:0] data;
reg comm_go;
wire send_pedge;
edge_detector_n ed_go(
.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(clk_usec));
reg [21:0] count_usec;
reg count_usec_e;
always @(negedge clk or posedge reset_p) begin
if(reset_p) begin
count_usec = 0;
end
else begin
if(clk_usec && count_usec_e)count_usec = count_usec + 1;
else if(!count_usec_e)count_usec = 0;
end
end
reg [5:0] state, next_state;
always @(negedge clk or posedge reset_p)begin
if(reset_p) state = IDLE;
else state = next_state;
end
always @(posedge clk or posedge reset_p) begin
if(reset_p) begin
next_state = IDLE;
busy = 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 // 200us
data = {send_buffer[7:4], 3'b100, rs}; //[d7 d6 d5 d4] [BL EN RW] RS
comm_go = 1;
count_usec_e = 1;
end
else begin
next_state = SEND_HIGH_NIBBLE_ENABLE;
count_usec_e = 0;
comm_go = 0;
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] [BL EN RW] RS
comm_go = 1;
count_usec_e = 1;
end
else begin
next_state = SEND_LOW_NIBBLE_DISABLE;
count_usec_e = 0;
comm_go = 0;
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] [BL EN RW] RS
comm_go = 1;
count_usec_e = 1;
end
else begin
next_state = SEND_LOW_NIBBLE_ENABLE;
count_usec_e = 0;
comm_go = 0;
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] [BL EN RW] RS
comm_go = 1;
count_usec_e = 1;
end
else begin
next_state = SEND_DISABLE;
count_usec_e = 0;
comm_go = 0;
end
end
SEND_DISABLE: begin
if(count_usec <= 22'd200)begin
data = {send_buffer[3:0], 3'b100, rs}; //[d7 d6 d5 d4] [BL EN RW] RS
comm_go = 1;
count_usec_e = 1;
end
else begin
next_state = IDLE;
count_usec_e = 0;
comm_go = 0;
busy = 0;
end
end
endcase
end
end
I2C_master master( .clk(clk), .reset_p(reset_p),
.addr(addr),
.data(data),
.rd_wr(0), .comm_go(comm_go),
.sda(sda), .scl(scl),
.led(led));
endmodule
다음으로 test_top 소스코드를 작성해보자.
test_top의 상태도는 아래와 같다.
그중에서 INIT state는 아래의 데이터시트를 보고 소스코드를 작성하면 된다.
i2_txtlcd_test_top 소스코드
///////////////////////////////////////////// 2024.8.14
////////////////////////////////// 텍스트 lcd
module i2c_txtlcd_test_top(
input clk, reset_p,
input [3:0] btn,
output scl, sda);
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(clk_usec));
reg [21:0] count_usec;
reg count_usec_e;
always @(negedge clk or posedge reset_p) begin
if(reset_p) begin
count_usec = 0;
end
else begin
if(clk_usec && count_usec_e)count_usec = count_usec + 1;
else if(!count_usec_e)count_usec = 0;
end
end
wire [3:0]btn_pedge;
button_cntr_reg_n_p btn_0(
.clk(clk), .reset_p(reset_p),
.btn(btn[0]),
.btn_pedge(btn_pedge[0]));
button_cntr_reg_n_p btn_1(
.clk(clk), .reset_p(reset_p),
.btn(btn[1]),
.btn_pedge(btn_pedge[1]));
button_cntr_reg_n_p btn_2(
.clk(clk), .reset_p(reset_p),
.btn(btn[2]),
.btn_pedge(btn_pedge[2]));
button_cntr_reg_n_p btn_3(
.clk(clk), .reset_p(reset_p),
.btn(btn[3]),
.btn_pedge(btn_pedge[3]));
reg [7:0] send_buffer;
reg send, rs;
wire busy; // busy는 i2c_lcd_send_byte에서 출력이므로 wire로 선언
i2c_lcd_send_byte(
.clk(clk), .reset_p(reset_p),
.addr(7'h27), .send_buffer(send_buffer),
.send(send), .rs(rs),
.scl(scl), .sda(sda),
.busy(busy));
reg [5:0] state, next_state;
always @(negedge clk or posedge reset_p) begin
if(reset_p)state = IDLE;
else state = next_state;
end
reg init_flag; //처음에만 초기화를 한 다음에 1로 세팅하기위함
//reg [2:0]cnt_data; // 6개 카운트-> 3비트 필요, 2^3=8
reg [4:0]cnt_data;
always @(posedge clk or posedge reset_p) begin
if(reset_p) begin
init_flag = 0;
next_state = IDLE;
send = 0;
send_buffer = 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 //80ms
count_usec_e = 1;
end
else begin
init_flag = 1;
next_state = INIT;
count_usec_e = 0;
end
end
end
INIT: begin
if(busy) begin
send = 0;
if (cnt_data >= 6) begin
cnt_data = 0;
next_state = IDLE;
init_flag = 1;
end
end
else begin
case(cnt_data)
0: send_buffer = 8'h33; // 상위 0011->3, 하위 0011->3
1: send_buffer = 8'h32;
2: send_buffer = 8'h28;
3: send_buffer = 8'h0c;
4: send_buffer = 8'h01;
5: send_buffer = 8'h06;
endcase
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
rs = 1;
send_buffer = "A"; // 베릴로그는"" 만 사용
send = 1;
end
end
endcase
end
end
endmodule
test_top 소스코드에서 7'h27를 쓰는 이유는 아래의 I2C 통신 모듈의 데이터시트를 보고 쓴 것이다.
하지만 위에서 작성한 test_top으로 실행하면 A가 나타나긴 하지만, 1칸 띄워진 상태에서 나타나고, Reset이 작동되지 않는다.
이럴때는 INIT state에서 else if(send == 0) begin 으로 수정하면 해결할 수 있다.
이번에는 다른 문자와 숫자도 출력해보자.
그러기 위해서는 아스키코드 표를 참고해야한다.
버튼0을 누를 때마다 알파벳 대문자가 차례대로 나타나고 다시 처음으로 돌아오고,
버튼0을 누를 때마다 숫자가 차례대로 나타나고 다시 처음으로 돌아오는 소스코드를 작성해본다.
버튼0을 누를 때마다 알파벳 대문자가 차례대로 나타나고 다시 처음으로 돌아오게 하려면 test_top 소스코드에서 아래와 같이 수정하면 된다.
알파벳 대문자가 A~H까지만 나타나는 이유는 reg [2:0]cnt_data;를 썼기 때문이다.
3비트->2^3=8 이므로 8개의 알파벳 대문자가 출력되는 것이다.
다음으로 버튼0을 누를 때마다 숫자가 차례대로 나타나고 다시 처음으로 돌아오게 하려면 test_top 소스코드에서 아래와 같이 수정하면 된다.
숫자가 0~9까지만 나타나는 이유는 reg [4:0]cnt_data;를 썼기 때문이다.
4비트->2^4=16 이므로 0~9까지의 10개의 숫자가 출력되기 위해서는 10 이상의 수가 필요하다.
다음으로 LCD 화면을 왼쪽, 오른쪽으로 shift하는 소스코드를 작성해보자.
여태 영상에서 보았던 것처럼, 2줄의 LCD는 눈으로 보이는 16칸 이 아니라 보이지 않는 34칸이 더 있다.
이는 LCD 데이터 시트에서 확인할 수 있다.
아래는 shift하는 test_top 소스코드를 만들기 위한 상태도이다.
아래는 LCD 데이터시트에서 LCD display를 shift 하려면 어떻게 작성해야하는지에 대한 내용이다.
그렇다면 데이터시트에 있는 것이 맞는지, LCD 화면을 shift하는 test_top 소스코드를 작성하여 결과를 확인해보자.
i2c_txtlcd_shift_test_top 소스코드
module i2c_txtlcd_shift_test_top(
input clk, reset_p,
input [3:0] btn,
output scl, sda);
parameter IDLE = 6'b00_0001;
parameter INIT = 6'b00_0010;
parameter SEND_BYTE = 6'b00_0100;
parameter SHIFT_RIGHT_DISPLAY = 6'b00_1000;
parameter SHIFT_LEFT_DISPLAY = 6'b01_0000;
wire clk_usec;
clock_div_100 usec_clk(
.clk(clk), .reset_p(reset_p),
.clk_div_100(clk_usec));
reg [21:0] count_usec;
reg count_usec_e;
always @(negedge clk or posedge reset_p) begin
if(reset_p) begin
count_usec = 0;
end
else begin
if(clk_usec && count_usec_e)count_usec = count_usec + 1;
else if(!count_usec_e)count_usec = 0;
end
end
wire [3:0]btn_pedge;
button_cntr_reg_n_p btn_0(
.clk(clk), .reset_p(reset_p),
.btn(btn[0]),
.btn_pedge(btn_pedge[0]));
button_cntr_reg_n_p btn_1(
.clk(clk), .reset_p(reset_p),
.btn(btn[1]),
.btn_pedge(btn_pedge[1]));
button_cntr_reg_n_p btn_2(
.clk(clk), .reset_p(reset_p),
.btn(btn[2]),
.btn_pedge(btn_pedge[2]));
button_cntr_reg_n_p btn_3(
.clk(clk), .reset_p(reset_p),
.btn(btn[3]),
.btn_pedge(btn_pedge[3]));
reg [7:0] send_buffer;
reg send, rs;
wire busy; // busy는 i2c_lcd_send_byte에서 출력이므로 wire로 선언
i2c_lcd_send_byte(
.clk(clk), .reset_p(reset_p),
.addr(7'h27), .send_buffer(send_buffer),
.send(send), .rs(rs),
.scl(scl), .sda(sda),
.busy(busy));
reg [5:0] state, next_state;
always @(negedge clk or posedge reset_p) begin
if(reset_p)state = IDLE;
else state = next_state;
end
reg init_flag; //처음에만 초기화를 한 다음에 1로 세팅하기위함
//reg [2:0]cnt_data; // 6개 카운트-> 3비트 필요, 2^3=8
reg [4:0]cnt_data;
always @(posedge clk or posedge reset_p) begin
if(reset_p) begin
init_flag = 0;
next_state = IDLE;
send = 0;
send_buffer = 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;
if(btn_pedge[1])next_state = SHIFT_RIGHT_DISPLAY;
if(btn_pedge[2])next_state = SHIFT_LEFT_DISPLAY;
end
else begin
if(count_usec <= 22'd80_000) begin //80ms
count_usec_e = 1;
end
else begin
init_flag = 1;
next_state = INIT;
count_usec_e = 0;
end
end
end
INIT: begin
if(busy) begin
send = 0;
if (cnt_data >= 6) begin
cnt_data = 0;
next_state = IDLE;
init_flag = 1;
end
end
else if(send == 0) begin
case(cnt_data)
0: send_buffer = 8'h33; // 상위 0011->3, 하위 0011->3
1: send_buffer = 8'h32;
2: send_buffer = 8'h28;
3: send_buffer = 8'h0c;
4: send_buffer = 8'h01;
5: send_buffer = 8'h06;
endcase
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
rs = 1;
//send_buffer = "A"; // 베릴로그는"" 만 사용
//send_buffer = "A" + cnt_data; //A~H 까지 반복
send_buffer = "0" + cnt_data; // 0~9까지 반복
send = 1;
end
end
SHIFT_RIGHT_DISPLAY: begin
if(busy) begin
next_state = IDLE;
send = 0;
end
else begin
rs = 0;
send_buffer = 8'h1c; // 화면을 오른쪽으로 shift
send = 1;
end
end
SHIFT_LEFT_DISPLAY: begin
if(busy) begin
next_state = IDLE;
send = 0;
end
else begin
rs = 0;
send_buffer = 8'h18; // 화면을 왼쪽으로 shift
send = 1;
end
end
endcase
end
end
endmodule
LCD display shift를 통해 눈으로만 보이는 16칸과 보이지 않는 칸이 더 있다는 것을 확인할 수 있다.
I2C_LCD 텍스트 제어 끝!
'Harman 세미콘 아카데미 > Verilog' 카테고리의 다른 글
2024.8.14 [Verilog] - 문잠김 구현이 가능한 도어락 만들기 프로젝트 (0) | 2024.08.14 |
---|---|
2024.8.13 [Verilog]19 - 조이스틱 3색 led 제어, I2C_LCD (0) | 2024.08.13 |
2024.8.12 [Verilog]18 - 주방타이머 수정, 조이스틱 ADC (0) | 2024.08.12 |
2024.7.11 [Verilog] 다기능 선풍기 만들기 팀 프로젝트 (0) | 2024.07.11 |
2024.7.10 [Verilog]17 - PWM으로 servo 모터 제어하기, ADC (0) | 2024.07.10 |