Harman 세미콘 아카데미/Verilog

2024.8.14 [Verilog]20 - I2C_LCD 텍스트 제어

U_Pong 2024. 8. 14. 21:01

2024.8.14 수업날

 

 

LCD 데이터시트

HD44780.pdf
0.27MB

 

I2C 통신모듈 데이터시트

HLF8574T.pdf
1.38MB

 

I2C_LCD 데이터시트

I2C_1602_LCD.pdf
0.86MB


< I2C_LCD 텍스트 제어 >

어제 배웠던 I2C통신으로 이번에는 LCD에 텍스트를 제어해본다.

아래는 4-Bit Interface를 기준으로 I2C 모듈과 LCD의 핀 연결이 어떤 방식으로 연결되어있는지에 대한 그림이다.

 

다음으로

아래의 데이터시트를 보고 LCD에 1byte를 전송하는 소스코드를 작성해보자.

LCD 데이터 시트

 

I2C 통신 모듈 데이터시트

 

I2C_LCD 데이터시트

 

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는 아래의 데이터시트를 보고 소스코드를 작성하면 된다.

LCD 데이터 시트(46p)

 

LCD 데이터 시트(24p)

 

LCD 데이터 시트(25p)

 

LCD 데이터 시트(15p)

 

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이 작동되지 않는다.

I2C_LCD INIT state -> else begin 결과

 

이럴때는 INIT state에서 else if(send == 0) begin 으로 수정하면 해결할 수 있다.

 

I2C_LCD INIT state -> else if(send == 0) begin 결과

 

 

이번에는 다른 문자와 숫자도 출력해보자.

그러기 위해서는 아스키코드 표를 참고해야한다.

버튼0을 누를 때마다 알파벳 대문자가 차례대로 나타나고 다시 처음으로 돌아오고,

버튼0을 누를 때마다 숫자가 차례대로 나타나고 다시 처음으로 돌아오는 소스코드를 작성해본다.

 

버튼0을 누를 때마다 알파벳 대문자가 차례대로 나타나고 다시 처음으로 돌아오게 하려면 test_top 소스코드에서 아래와 같이 수정하면 된다.

 

알파벳 대문자가 A~H까지만 나타나는 이유는 reg [2:0]cnt_data;를 썼기 때문이다.

3비트->2^3=8 이므로 8개의 알파벳 대문자가 출력되는 것이다.

I2C_LCD send ABCDEGH 결과

 

다음으로 버튼0을 누를 때마다 숫자가 차례대로 나타나고 다시 처음으로 돌아오게 하려면 test_top 소스코드에서 아래와 같이 수정하면 된다.

 

숫자가 0~9까지만 나타나는 이유는 reg [4:0]cnt_data;를 썼기 때문이다.

4비트->2^4=16 이므로 0~9까지의 10개의 숫자가 출력되기 위해서는 10 이상의 수가 필요하다.

I2C_LCD send 0123456789 결과

 

 

다음으로 LCD 화면을 왼쪽, 오른쪽으로 shift하는 소스코드를 작성해보자.

여태 영상에서 보았던 것처럼, 2줄의 LCD는 눈으로 보이는 16칸 이 아니라 보이지 않는 34칸이 더 있다.

이는 LCD 데이터 시트에서 확인할 수 있다.

 

아래는 shift하는 test_top 소스코드를 만들기 위한 상태도이다.

 

아래는 LCD 데이터시트에서 LCD display를 shift 하려면 어떻게 작성해야하는지에 대한 내용이다.

화면 오른쪽 shift 할때 1,C 부분
화면 왼쪽 shift 할때 1,8부분

 

그렇다면 데이터시트에 있는 것이 맞는지, 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 shift 결과

I2C_LCD 텍스트 제어 끝!