Harman 세미콘 아카데미/Verilog

2024.8.13 [Verilog]19 - 조이스틱 3색 led 제어, I2C_LCD

U_Pong 2024. 8. 13. 20:24

2024.8.13 수업날


< 조이스틱 3색 led 제어 >

이번에는 조이스틱을 사용하여 3색 led의 빨강, 초록색을 pwm으로 제어해본다.

2개의 색만 사용하는 이유는 조이스틱에서 x,y 결과값만 사용하기 때문이다.

 

adc_sequence2_led_top 소스코드
//////////////////////////////////////////////////// 2024.8.13
/////////////////////////// 조이스틱으로 3색 led 제어
module adc_sequence2_led_top(
    input clk, reset_p,
    input vauxp6, vauxn6, vauxp15, vauxn15,
    output led_r, led_g,
    output [3:0] com,
    output [7:0] seg_7);
    
    wire [4:0] channel_out;
      wire eoc_out;
      wire [15:0] do_out;
      xadc_sequencer adc_seq2
          (
          .daddr_in({2'b0, channel_out}),            // Address bus for the dynamic reconfiguration port
          .dclk_in(clk),                                          // Clock input for the dynamic reconfiguration port
          .den_in(eoc_out),                                 // Enable Signal for the dynamic reconfiguration port
          .reset_in(reset_p),                               // Reset signal for the System Monitor control logic
          .vauxp6(vauxp6),                               // Auxiliary channel 6
          .vauxn6(vauxn6),
          .vauxp15(vauxp15),                               // Auxiliary channel 15
          .vauxn15(vauxn15),
          .channel_out(channel_out),            // Channel Selection Outputs
          .do_out(do_out),                            // Output data bus for dynamic reconfiguration port
          .eoc_out(eoc_out)                           // End of Conversion Signal
          );
          
      wire eoc_out_pedge;
      edge_detector_n ed(
            .clk(clk), .reset_p(reset_p), .cp(eoc_out), 
            .n_edge(eoc_out_pedge));
      
      reg [11:0] adc_value_x, adc_value_y;               // 2^12=4096, 비트수가 많을수록 정밀도가 높다, 대신 정밀도가 높으면 안정도가 낮다
      always @(posedge clk or posedge reset_p)begin
            if(reset_p)begin
                adc_value_x = 0;
                adc_value_y = 0;
            end
            else if(eoc_out_pedge)begin
                case(channel_out[3:0])
                    6: adc_value_x = do_out[15:4];
                    15: adc_value_y = do_out[15:4];
                endcase
            end
      end
      
      pwm_128step pwm_r(
            .clk(clk), .reset_p(reset_p),
            .duty(adc_value_x[11:5]),                   // 7비트 정도만 쓰기 대문에 정밀도는 떨어지지만, 안정도는 높다
            .pwm(led_r)); 
      
      pwm_128step pwm_g(
            .clk(clk), .reset_p(reset_p),
            .duty(adc_value_y[11:5]),                   // 7비트 정도만 쓰기 대문에 정밀도는 떨어지지만, 안정도는 높다
            .pwm(led_g));       
       
      wire [15:0] value, adc_value_bcd_x, adc_value_bcd_y;
      bin_to_dec bcd_x(.bin({6'b0, adc_value_x[11:6]}), .bcd(adc_value_bcd_x));
      bin_to_dec bcd_y(.bin({6'b0, adc_value_y[11:6]}), .bcd(adc_value_bcd_y));
      
      // 상위 2비트 따로, 하위 2비트 따로 읽어야함
      assign value = { adc_value_bcd_x[7:0], adc_value_bcd_y[7:0]};
      fnd_4digit_cntr FND_duty(.clk(clk), .value(value), .com(com), .seg_7(seg_7)); 
          
          
endmodule

 

.xdc  수정해야하는 곳

 

 

조이스틱 3색 led 제어 결과

 

 

< I2C_LCD >

I2C 통신으로 LCD를 제어해본다. 16x2 LCD를 사용하며, 각 행에 16문자를 쓸 수 있다.

기존의 LCD는 핀이 너무 많아 사용에 불편함이 있었기 때문에 아래 사진과 같은 I2C모듈이 뒤에 납땜된 형태로 되어있다.

 

 

아래의 사진은 I2C 통신을 하기 위해서 state를 정한 순서이다.

SCL이 HIGH인 상태에서는 SDA가 HIGH에서 LOW가 되면 Start 상태이고,

SCL이 HIGH인 상태에서 SDA가 LOW에서 HIGH가 되면 Stop 상태가 된다.

첫번째 사진은 7비트를 전송하는 주소 패킷과 달리 데이터 패킷은 8비트를 보내는 것을 나타낸다.

두번째 사진은 첫번째 사진을 참고하여 state를 구분한 것이다. 이를 코드로 구현하면 된다.

I2C 통신 프로토콜 : 네이버 블로그 (naver.com)

 

I2C_master 소스코드
////////////////////////////////////////////////// 2024.8.13
/////////////////////////////// 마스터 디바이스가 데이터를 송신하고 수신하는 과정
module I2C_master(
    input clk, reset_p,                   // 입력 클럭과 리셋 신호 (positive reset)
    input [6:0] addr,                     // 7비트 주소 입력
    input [7:0] data,                     // 8비트 데이터 입력
    input rd_wr, comm_go,                 // 읽기/쓰기 제어 신호, 통신 시작 신호 (읽기=1, 쓰기=0)
    output reg sda, scl,                  // I2C 데이터선(SDA)과 클럭선(SCL) 출력
    output reg [6:0] led);                // LED 상태 표시 출력
    
    parameter IDLE                      = 7'b000_0001;  // 대기 상태
    parameter COMM_START    = 7'b000_0010;  // 통신 시작 상태
    parameter SEND_ADDR        = 7'b000_0100;  // 주소 전송 상태
    parameter RD_ACK                 = 7'b000_1000;  // 확인 응답 상태
    parameter SEND_DATA         = 7'b001_0000;  // 데이터 전송 상태
    parameter SCL_STOP             = 7'b010_0000;  // 클럭 정지 상태
    parameter COMM_STOP        = 7'b100_0000;  // 통신 종료 상태
    
    wire [7:0] addr_rw;
    assign addr_rw = {addr, rd_wr};         // 주소와 읽기/쓰기 비트를 결합하여 addr_rw로 설정
    
    wire clk_usec;
    clock_div_100 usec_clk(  
            .clk(clk), .reset_p(reset_p), 
            .clk_div_100(clk_usec));        // 클럭을 100분의 1로 나누는 모듈 인스턴스화
    
    reg [2:0] count_usec5;
    reg scl_e;
    always @(posedge clk or posedge reset_p) begin
    		if(reset_p) begin
            	count_usec5 = 0;            // 리셋 시 5us 카운터 초기화
                	scl = 1;                    // SCL을 기본적으로 HIGH로 설정
            end
            else if(scl_e) begin            // SCL이 활성화된 경우
            	if(clk_usec) begin           // 1us 클럭이 들어오면
                  	if(count_usec5 >= 4) begin
                        	count_usec5 = 0;  // 5us 이후 SCL 신호 반전
                            	scl = ~scl;
                        end
                        else count_usec5 = count_usec5 + 1;  // 5us 카운터 증가
                    end
            end
            else if(!scl_e) begin           // SCL이 비활성화된 경우
            	count_usec5 = 0;            // 카운터 초기화
                	scl = 1;                    // SCL을 HIGH로 설정
            end   
    end
    
    wire comm_go_pedge;
    edge_detector_n ed_go(
                  .clk(clk), .reset_p(reset_p), 
                  .cp(comm_go), .p_edge(comm_go_pedge)); // 통신 시작 신호의 상승 에지 검출
    
    wire scl_pedge, scl_nedge;
    edge_detector_n ed_scl(
                  .clk(clk), .reset_p(reset_p), 
                  .cp(scl), .p_edge(scl_pedge), .n_edge(scl_nedge)); // SCL 신호의 상승 및 하강 에지 검출
    
    reg [6:0] state, next_state;
    always @(negedge clk or posedge reset_p) begin
    		if(reset_p) state = IDLE;  // 리셋 시 상태를 IDLE로 초기화
        	else state = next_state;   // 현재 상태를 다음 상태로 갱신
    end
    
    reg [2:0] cnt_bit;
    reg stop_flag;
    always @(posedge clk or posedge reset_p)begin
    		if(reset_p)begin
			next_state = IDLE;          // 리셋 시 다음 상태를 IDLE로 설정
			scl_e = 0;                  // SCL을 비활성화
			sda = 1;                    // SDA를 HIGH로 설정
			cnt_bit = 7;                // 비트 카운터 초기화
			stop_flag = 0;              // 종료 플래그 초기화
			led = 0;                    // LED를 모두 끔
		end
        	else begin
			case(state)
				IDLE: begin
					led[0] = 1;          // IDLE 상태 LED ON
					scl_e = 0;          // SCL 비활성화
					sda = 1;            // SDA HIGH
					if(comm_go_pedge) next_state = COMM_START;  // 통신 시작 신호가 들어오면 COMM_START 상태로 전환
				end
			    
				COMM_START: begin
					led[1] = 1;          // COMM_START 상태 LED ON
					sda = 0;            // 통신 시작을 위해 SDA LOW로 설정
					scl_e = 1;          // SCL 활성화
					next_state = SEND_ADDR;  // 다음 상태는 SEND_ADDR
				end
			    
				SEND_ADDR: begin
					led[2] = 1;          // SEND_ADDR 상태 LED ON
					if(scl_nedge) sda = addr_rw[cnt_bit];  // SCL 하강 에지에서 주소 비트를 전송
					if(scl_pedge) begin
						if(cnt_bit == 0) begin
							cnt_bit = 7;  // 모든 주소 비트 전송 후 비트 카운터 초기화
							next_state = RD_ACK;  // 다음 상태는 RD_ACK
						end
						else cnt_bit = cnt_bit - 1;  // 다음 비트를 전송하기 위해 비트 카운터 감소
					 end
				end
			    
				RD_ACK: begin
					led[3] = 1;          // RD_ACK 상태 LED ON
					if(scl_nedge) sda = 'bz;  // 슬레이브로부터 확인 응답을 받기 위해 SDA를 고임피던스 상태로 설정
					else if(scl_pedge) begin
						if(stop_flag) begin
							stop_flag = 0;  // 종료 플래그 리셋
							next_state = SCL_STOP;  // 다음 상태는 SCL_STOP
						end
						else begin
						stop_flag = 1;  // 종료 플래그 설정
						next_state = SEND_DATA;  // 다음 상태는 SEND_DATA
						end
					end
				end
			    
				SEND_DATA: begin
					led[4] = 1;          // SEND_DATA 상태 LED ON
					if(scl_nedge) sda = data[cnt_bit];  // SCL 하강 에지에서 데이터 비트를 전송
					if(scl_pedge) begin
						if(cnt_bit == 0) begin
							cnt_bit = 7;  // 모든 데이터 비트 전송 후 비트 카운터 초기화
							next_state = RD_ACK;  // 다음 상태는 RD_ACK
						end
						else cnt_bit = cnt_bit - 1;  // 다음 비트를 전송하기 위해 비트 카운터 감소
					end
				end
			    
				SCL_STOP: begin
					led[5] = 1;          // SCL_STOP 상태 LED ON
					if(scl_nedge) sda = 0;  // 통신 종료를 위해 SDA를 LOW로 설정
					else if(scl_pedge) next_state = COMM_STOP;  // 다음 상태는 COMM_STOP
				end
			    
				COMM_STOP: begin
					led[6] = 1;          // COMM_STOP 상태 LED ON
					if(count_usec5 >= 3) begin
						scl_e = 0;  // SCL 비활성화
						sda = 1;  // SDA를 HIGH로 설정
						next_state = IDLE;  // 다음 상태는 IDLE
					end
				end
			endcase    
        	end
    end
    
endmodule

 

I2C_master_top 소스코드
//////////////////////////////// 버튼 2개 입력 받아서 lcd on, lcd off를 할 수 있는모듈
module I2C_master_top(
    input clk, reset_p,
    input [1:0] btn,
    output sda, scl,
    output [6:0] led);
    
    reg [7:0] data;
    reg comm_go;
    I2C_master master ( .clk(clk), .reset_p(reset_p),
                                        .addr(7'h27),
                                        .data(data),
                                        .rd_wr(0), .comm_go(comm_go),
                                        .sda(sda), .scl(scl),
                                        .led(led));
    
    wire [1: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]));       
    
    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;
                		comm_go = 1;
            	end
                	else comm_go = 0;
        	end
    end
    
endmodule

 

아래는 .xdc에서 수정해야하는 부분이다.

 

I2C_LCD 전원 제어 결과

 

아래의 사진은 I2C_master_top 소스코드를 오실로스코프로 파형을 확인한 것인데,

SEND_DATA에서 data = 8'b1111_1111; 를 나타낸 것이다.


조이스틱 3색 led 제어, I2C_LCD 끝!