거북이처럼 천천히

Verilog 팀 프로젝트 - 에너지 절약 선풍기 본문

RTL Design/Verilog 프로젝트

Verilog 팀 프로젝트 - 에너지 절약 선풍기

유로 청년 2024. 10. 5. 19:24

1. 서론 

  • 에너지 절약 선풍기 라는 주제로 팀 프로젝트를 진행하였습니다.
  • 선택의 배경에는 Verilog 수업에서 습득한 지식을 실제 응용하고, 다양한 센서 모듈을 활용할 수 있는 기회가 있었기 때문입니다. 우리의 목표는 기존 선풍기의 기능을 넘어서, 에너지 효율성을 향상시키는 기능들을 추가하는 것입니다. 이 프로젝트를 통해 우리는 이론적 지식을 실제 문제 해결에 적용하며, 동시에 환경 친화적이고 실용적인 가전제품을 개발하는 과정을 경험할 수 있었습니다.

 

 

 

2. 기존 선풍기의 기능 & 추가적인 에너지 절약 기능

  • 기존 선풍기에서 지원하는 기능들은 다음과 같습니다.
    ▶ 버튼을 통한 선풍기 풍속 조절
    ▶ Timer 모드를 통한 선풍기 동작 타이머 설정 가능
    ▶ 선풍기 헤드 방향 설정 (좌우)
    ▶ 선풍기 LED 밝기 조절


  • 기존 선풍기는 위와 같은 기능을 지원했으나, 이번 프로젝트에서는 에너지 절약에 중점을 두어 새로운 기능을 추가했습니다. 
  • 에너지 절약을 위해 크게 2가지 기능을 추가하였습니다.
    1) 온도에 따른 선풍기 파워 세기 조절
    2) 초음파 센서를 통해 선풍기 주변에 사람이 없을 경우, 자동으로 선풍기 종료
  • 이를 위해 크게 2가지 모드로 분리하여 설계하였습니다. 
    1) 일반 모드 : 일반적인 선풍기 모드
    2) 에코 모드 : 에너지 절약 모드, 해당 모드에서 온도, 초음파 센서로 부터 얻은 거리 값에 의해
                           선풍기 동작 여부 결정

 

 

 

3. 에너지 절약 선풍기의 특징

  • 1) 온도에 따른 선풍기 파워 자동 조절
    DHT11 센서로 외부 온도를 실시간으로 감지해, 적절한 선풍기 파워로 자동 설정합니다. 이를 통해 비효율적인 사용을 방지하고 에너지를 절약할 수 있도록 설계했습니다.

  • 2) 초음파 센서를 통한 자동 선풍기 끄기
    기존 선풍기는 사람이 없는데도 계속 작동해 에너지를 낭비할 수 있었습니다. 하지만 에너지 절약 선풍기는 전방에 부착된 초음파 센서를 통해 주변에 사람이 있는지 확인하고, 사람이 없을 경우 자동으로 꺼지도록 설계했습니다. 이를 통해 불필요한 에너지 낭비를 줄일 수 있을 것으로 기대합니다.

 

 

 

 

4. 에너지 절약 선풍기의 기능 및 동작

에너지 절약 선풍기 기능과 Extrea 기능들

 

에너지 절약 선풍기 동작 및 전체 천이도

 

 

 

 

 

5. 에너지 절약 선풍기의 컨트롤 버튼 및 스위치

에너지 절약 선풍기 제어를 위한 스위치 및 버튼

 

 

 

6. 에너지 절약 선풍기의 모듈 구현

  • 본인이 맡은 역활은 다음과 같습니다.
    ▶ 일반 모드에서 버튼을 통한 선풍기 파워 조절
    일반 모드에서 버튼을 통한 타이머 세팅 및 타이머를 통한 선풍기 파워 컨트롤
  • 에너지 절약 선풍기의 모듈에 대한 설명은 소스 코드와 함께 설명하도록 하겠습니다.

 

< Source, Top Modue의 Input / Output 변수 선언 >

// Top module of Electric Fan
module top_module_of_electric_fan (
    input clk, reset_p,
    input [3:0] btn,
    input sw_direction_cntr,
    input echo,
    inout dht11,
    output trig,
    output [8:0] led_debug,
    output led, pwm,
    output [3:0] com,
    output [7:0] seg_7,
    output rotation);
    
    ''' ( Body of Top module ) '''
endmodule
  • 각각의 Input / Output 변수들은 다음과 같은 역활을 수행한다.
    - btn [0] : 선풍기 파워 조절 (Power OFF, 1단, 2단, 3단)
    - btn [1] : 타이머 모드 조절 (Timer OFF, 5초, 10초, 15초)
    - btn [2] : LED 밝기 조절 (LED OFF, 1단, 2단, 3단)
    - btn [3] : Echo 모드 전환 ( 선풍기 파워만 조정 )
    - sw_direction_cntr : 선풍기 헤드 전환 컨트롤 스위치
    - pwm : 선풍기 DC 모터의 PWM 
    - rotation : 선풍기 헤드를 조절하는 서보 모터의 PWN

 

 

< Source, Button Chattering를 해결하기 위해 1ms Delay Time을 갖고 버튼 값을 읽기 >

// Button 0 (btn_power) : 선풍기 파워 조절 (off, 1단, 2단, 3단)
// Button 1 (btn_timer) : 타이머 모드 (off, 5초, 10초 15초)
// Button 2 (btn_led) : LED 밝기 조절 (off, 1단, 2단, 3단)
// Button 3 (btn_echo) : Echo 모드 ( 선풍기 파워만 조정 )
// Switch (sw_direction) : 방향 설정
// Get One Cycle Pulse of buttons.
wire btn_power_pedge, btn_timer_pedge, btn_led_pedge, btn_echo_pedge;
button_cntr btn_power_cntr (.clk(clk), .reset_p(reset_p), .btn(btn[0]), .btn_pedge(btn_power_pedge));
button_cntr btn_timer_cntr (.clk(clk), .reset_p(reset_p), .btn(btn[1]), .btn_pedge(btn_timer_pedge));
button_cntr btn_led_cntr (.clk(clk), .reset_p(reset_p), .btn(btn[2]), .btn_pedge(btn_led_pedge));
button_cntr btn_echo_cntr (.clk(clk), .reset_p(reset_p), .btn(btn[3]), .btn_pedge(btn_echo_pedge));

 

 

 

 

< Source, FSM 형식으로 설계하기 위해 State 선언 >

 // Declare Parameter
 parameter POWER_CONTROL = 4'b0001;
 parameter TIMER_CONTROL = 4'b0010;
 parameter LED_CONTROL = 4'b0100;
 parameter ECHO_CONTROL = 4'b1000;
 parameter ROTATION_CONTROL = 4'b0001;
    
 // Declare current state
 reg [4:0] current_state;

 

 

 

< Source, 각각의 버튼에 의해 현재 모드 선택 및 전환 >

// 눌러진 버튼에 따른 선풍기 변화    
    always @(posedge clk or posedge reset_p) begin
        if(reset_p) current_state = POWER_CONTROL;
        else if(btn_power_pedge)  current_state = POWER_CONTROL;
        else if(btn_echo_pedge) begin
            if(current_state == ECHO_CONTROL) current_state = POWER_CONTROL;
            else current_state = ECHO_CONTROL;
        end
    end
  • btn_power_pedge 버튼을 통해 현재 상태 모드를 일반 모드, POWER_CONTROL 상태로 전환 가능합니다.
  • btn_echo_pedge 버튼을 통해 일반 모드와 에코 모드를 손쉽게 전환할 수 있습니다.

 

 

< Source, 각각 모듈과 현재 상태 모드를 통해 선풍기 파워, 서보 모터, 타이머의 동작 여부를 결정 >

    // Declare Instance of module
    wire [1:0] power_duty, echo_duty;  // duty ratio of motor
    wire [3:0] left_time;              // if current state is timer mode, this variable has data of lefted time.
    wire rotation_enable;
    power_cntr power_cntr_0 (.clk(clk), .reset_p(reset_p), .btn_power_enable(btn_power_pedge), .btn_timer_enable(btn_timer_pedge), .duty(power_duty), .left_time(left_time), .rotation_enable(rotation_enable));
    
    // current state 값에 따라 모터에 적용한 duty 값을 선택 
    wire [1:0] duty;
    wire [15:0]temp_data;
    wire led_debug_echo;
    wire [11:0] distance, temperature;
    assign duty = (current_state == ECHO_CONTROL) ? echo_duty : power_duty;
    dht11_usonic_duty  (.clk(clk),.reset_p(reset_p), .dht11_data(dht11), .echo(echo), .trig(trig), .echo_btn_enable(btn_echo_pedge),.duty(echo_duty),.t_data_out(temp_data),
                        .led_debug(led_debug_echo), .echo_buffer_out(distance), .temperature_bcd_out(temperature));
                        
    // 변화된 모터의 duty값을 모터에 적용                                                                                     
    pwm_cntr #(.pwm_freq(100), .duty_step(4)) control_pwm (.clk(clk), .reset_p(reset_p), .duty(duty), .pwm(pwm));
  • Echo 모드의 Motor duty 값(echo_duty) 과 Normal 모드의 Motor duty 값(power_duty)를 모두 구한 뒤, 삼항 조건 연산자를 통해 현재 모드 상태의 duty 값으로 선택하게 됩니다,

 

 

 

< Source, fan_led 모듈을 통해 선풍기 LED 제어 >

   //led_mode
   wire led_blue;
   wire [7:0]led_led_debug; 
   fan_led blue_led(.clk(clk), .reset_p(reset_p), .btn_led(btn_led_pedge),
                    .led_blue(led_blue), .led_debug(led_led_debug[7:4]));

 

 

 

< Source, 타이머, 온도, 습도 데이터들을 Basys3의 FND로 출력 >

    // FND로 현재의 모터 파워 출력
    wire [15:0] bcd_left_time;
    bin_to_dec convert_bin_to_dec_for_left_time (.bin(left_time), .bcd(bcd_left_time));
    
    wire [15:0] fnd_led;
    assign fnd_led =  (current_state == ECHO_CONTROL) ? {temperature[7:0], 6'b0, duty}  : {bcd_left_time[7:0], 6'b0, duty};
    
    fnd_cntr fnd(.clk(clk), .reset_p(reset_p), .value(fnd_led), .com(com), .seg_7(seg_7));
  • bin_to_dec 모듈을 통해 각각의 데이터들을 BCD Code로 변환해줍니다.
  • 타이머, 온도, 습도 데이터를 현재 상태 모드에 따라 출력할 데이터를 선택하여 결합 연산자를 통해 결합하여 fnd_led 변수에 저장합니다,
  • fnd_cntr 모듈을 통해 Basys3의 FND에 해당 데이터 값들을 순차적으로 출력합니다.

 

 

< Source, 현재 어떤 기능이 활성화 상태인지를 확인하기 위해 Basys3 LED로 해당 정보를 출력 >

    // LED로 표시
    assign led_debug[3:0] = (duty == 2'd0)? 4'b0001 : (duty == 2'd1)? 4'b0010 :
                            (duty == 2'd2)? 4'b0100 : 4'b1000;
    assign led = led_blue;
    assign led_debug[7:4] = led_led_debug[7:4];
    assign  led_debug[8] = led_debug_echo;

 

 

 

< Source, 선풍기 헤드를 제어하는 서보 모터의 모듈 선언 및 사용 >

    // rotation instance
    rotation_cntr( .clk(clk), .reset_p(reset_p), .sw_direction(sw_direction_cntr),.rotation(rotation), .rotation_enable(rotation_enable));

 

 

 

 

 

< Source, 선풍기의 파워, 타이머, 서보 모터의 동작 여부를 결정하는 모듈 >

// Power Control Module
module power_cntr (
    input clk, reset_p,
    input btn_power_enable,
    input btn_timer_enable,
    output [3:0] left_time,
    output reg [1:0] duty,
    output reg rotation_enable );
    
    // Declare Parameter
    parameter TURN_OFF = 4'b0001;
    parameter FIRST_SPEED = 4'b0010;
    parameter SECOND_SPEED = 4'b0100;
    parameter THIRD_SPEED = 4'b1000;
    
    // Declare state, next_state value
    reg duty_enable;
    wire reset_time_out;
    reg [3:0] speed_state, speed_next_state;
    
    // 언제 다음 state로 넘어가는가?
    always @(posedge clk or posedge reset_p) begin
        if(reset_p | reset_time_out) speed_state = TURN_OFF;
        else if(btn_power_enable) speed_state = speed_next_state;
    end

    
    // 각 state의 동작 및 다음 state로 동작하도록 설계
    always @(negedge clk or posedge reset_p) begin
        if(reset_p | reset_time_out) begin
            speed_next_state = FIRST_SPEED;
            duty = 2'd0;
            rotation_enable =0;
        end 
        else begin
            case (speed_state)
            // 0단계 : 선풍기 끄기
            TURN_OFF : begin
                duty = 2'd0;
                speed_next_state = FIRST_SPEED;
                rotation_enable =0;
            end    
            
            // 1단계 : 선풍기 약풍
            FIRST_SPEED : begin
                duty = 2'd1;
                speed_next_state = SECOND_SPEED;
                rotation_enable =1;
            end
            
            // 2단계 : 선풍기 중풍
            SECOND_SPEED : begin
                duty = 2'd2;
                speed_next_state = THIRD_SPEED;
                rotation_enable =1;
            end
            
            // 3단계 : 선풍기 강풍
            THIRD_SPEED : begin
                duty = 2'd3;
                speed_next_state = TURN_OFF;
                rotation_enable =1;
            end
               
            // Default case 
            default : begin
                duty = duty;
                speed_next_state = speed_next_state;
                rotation_enable =0;
            end    
            endcase
        end
    end  
    
    //// Timer Setting

    // Prescaling operation to create a counter in seconds
    wire clk_1usec, clk_1msec, clk_1sec;
    clock_div_100 usec_clk(.clk(clk), .reset_p(reset_p), .clk_div_100(clk_1usec));
    clock_div_1000 msec_clk(.clk(clk), .reset_p(reset_p), .clk_source(clk_1usec), .clk_div_1000(clk_1msec));
    clock_div_1000 sec_clk(.clk(clk), .reset_p(reset_p), .clk_source(clk_1msec), .clk_div_1000_nedge(clk_1sec));

    // Motor Run Enable variable Setting
    reg timer_enable; 

    // Declare Parameter
    parameter SETTING_0SEC = 4'd0;
    parameter SETTING_5SEC = 4'd5;
    parameter SETTING_10SEC = 4'd10;
    parameter SETTING_15SEC = 4'd15;
    
    // Declare state, next_state value
    reg [3:0] timer_state, timer_next_state;
    
    // 언제 다음 state로 넘어가는가?
    always @(posedge clk or posedge reset_p) begin
        if(reset_p | reset_time_out) timer_state = SETTING_0SEC;  
        else if(btn_timer_enable) timer_state = timer_next_state;
    end
    
    // 각 state의 동작 및 다음 state로 동작하도록 설계
    always @(negedge clk or posedge reset_p) begin
        if(reset_p | reset_time_out) begin
            timer_next_state = SETTING_5SEC;
            timer_enable = 0;
        end
        else begin
            case (timer_state)
                // 0단계 : Turn off electric fan
                SETTING_0SEC : begin
                    timer_next_state = SETTING_5SEC;
                    timer_enable = 0;
                end    
            
                // 1단계 : Setting Timer 5sec
                SETTING_5SEC : begin
                    timer_next_state = SETTING_10SEC;
                    timer_enable = 1;
                end
            
                // 2단계 : Setting Timer 10sec
                SETTING_10SEC : begin
                    timer_next_state = SETTING_15SEC;
                    timer_enable = 1;
                end
            
                // 3단계 : Setting Timer 15sec
                SETTING_15SEC : begin
                    timer_next_state = SETTING_0SEC;
                    timer_enable = 1;
                end
               
                // Default case 
                default : begin
                    timer_next_state = timer_next_state;
                    timer_enable = timer_enable;
                end  
            endcase
         end
    end
    
    
    // Down Counting of Timer
    reg [3:0] timer;
    always @(posedge clk or posedge reset_p) begin
        if(reset_p | reset_time_out) begin timer = 0; duty_enable = 1; end
        else if(btn_timer_enable) timer = timer_next_state;
        else if(clk_1sec && timer_enable && timer >= 0) begin
            if(timer <= 0 && timer_enable) duty_enable = 0; 
            else if(duty_enable) timer = timer - 1;
        end
    end
    
    // Print lefted time to FND.
    assign left_time = timer;
    
    // When the duty_enable variable is 0 and the btn_power_enable variable is activated, the reset for Time out is enabled.
    assign reset_time_out = timer_enable && (timer == 0) &&  duty_enable;
endmodule

 

 

 

 

 

< Source, 전달 받은 Duty step 값을 활용하여 선풍기 파워를 제어하는 100Hz PWM 생성 >

// PWM Control Module
module pwm_cntr #(
    parameter sys_clk = 100_000_000,
    parameter pwm_freq = 100,
    parameter duty_step = 4,

    parameter temp = sys_clk / pwm_freq / duty_step,
    parameter temp_half = temp / 2 )
    (
    input clk, reset_p,
    input [31:0] duty,
    output pwm );


    // pwm_freq 주파수를 갖는 PWM을 생성하기 위한 temp 분주화 작업
    integer cnt_temp;
    always @(posedge clk or posedge reset_p) begin
        if(reset_p) cnt_temp = 0;
        else begin
            if(cnt_temp >= temp - 1) cnt_temp = 0;
            else cnt_temp = cnt_temp + 1;          
        end
    end

    // sys_clk / temp 주파수를 갖는 PWM를  생성
    wire temp_pwm;
    assign temp_pwm = (cnt_temp < temp_half) ? 0 : 1;

    // Get One Cycle Pulse of negative edge of temp_pwm.
    wire temp_pwm_nedge;
    edge_detector_p ed(.clk(clk), .reset_p(reset_p), .cp(temp_pwm), .n_edge(temp_pwm_nedge));

    // PWM의 Duty ratio를 duty_step 단계로 구분하여 컨트롤 하기 위해 
    // temp_pwm을 duty_step 분주화 작업
    integer cnt_duty;
    always @(posedge clk or posedge reset_p) begin
        if(reset_p) cnt_duty = 0;
        else if(temp_pwm_nedge) begin
            if(cnt_duty >= duty_step-1) cnt_duty = 0;
            else cnt_duty = cnt_duty + 1;
        end
    end

    // Get sys_clk / temp / duty_step = pwm_freq(Hz) 주파수를 갖는 PWM 생성
    assign pwm = (cnt_duty < duty) ? 1 : 0;
endmodule

 

 

 

< Source, 선풍기 헤드를 제어하는 서보 모터를 컨트롤하는 모듈 >

module rotation_cntr(
        input clk, reset_p,
        input sw_direction,    // rotation switch
        input rotation_enable,
        output rotation              //  surbo moter output
        );
        
        integer clk_div;
        always @(posedge clk)  clk_div =clk_div +1;   //clock divider
        
        wire clk_div_24_nedge;
        edge_detector_n ed( .clk(clk), .reset_p(reset_p), . cp(clk_div[24]), .n_edge(clk_div_24_nedge));  
        
        reg [6:0] duty, duty_max, duty_min;
        reg down_up;
        always @(posedge clk or posedge reset_p) begin
                    if(reset_p) begin
                            duty =45;
                            duty_max = 75;     //  12.5% of  pwm
                            duty_min = 15;       // 2.5% of pwm
                    end
                    
                    else if(sw_direction&&rotation_enable) begin   // rotation switch 'on' -> start
                             if (clk_div_24_nedge) begin
                                    if(down_up)begin    // duty goes down until reach duty_min
                                            if(duty>= duty_min) duty = duty-1;
                                            else down_up =0;
                                    end
                                    else if(!down_up) begin // duty goes up until reach duty_max
                                            if(duty<=duty_max)duty = duty+1;
                                            else down_up =1;
                                    end                              
                            end
                      end
         end
         
         pwm_Nstep_freq     #(.duty_step(600), .pwm_freq(50))  
                 pwm_rotation       (.clk(clk), .reset_p(reset_p),  .duty(duty), .pwm(rotation));   //pwm pulse = rotation
          
endmodule

 

 

 

< Source, 선풍기의 LED를 4단계로 나누어 컨트롤 하는 모듈 >

module fan_led(
    input clk,reset_p,
    input btn_led,
    output led_blue,
    output [7:4] led_debug,
    output [3:0] com,
    output [7:0] seg_7); 
    
    //duty_code
    reg [6:0] duty;
    always @(posedge clk or posedge reset_p) begin
        if(reset_p) duty = 0;
        else if(btn_led) begin
            if(duty>=99) duty =0;
            else duty = duty + 33;
        end
     end
     
   //PWM_inst
   pwm_100step led_b(.clk(clk), .reset_p(reset_p) , .duty(duty), .pwm(led_blue));   

   //led
   assign led_debug[4] = (duty == 0);
   assign led_debug[5] = (duty == 33);
   assign led_debug[6] = (duty == 66);
   assign led_debug[7] = (duty == 99);
   

   //convert_bcd
  wire [6:0] duty_bcd;
  bin_to_dec duty_bcds(.bin(duty),  .bcd(duty_bcd));
  
  //FND
  fnd_cntr fnd(.clk(clk), .reset_p(reset_p), .value(duty_bcd), 
                                                .com(com), .seg_7(seg_7));   
                              
endmodule

 

 

 

< Source, DHT11, 초음파 센서를 통해 에코 모드에서의 선풍기 동작을 결정하는 모듈 > 

//dht11 module
module dht11_usonic_duty(
    input clk, reset_p, 
    input echo,
    inout dht11_data,
    input echo_btn_enable,
    output trig,
    output [8:11]led_debug,
    output t_data_out,
    output [1:0]duty,
    
    output [11:0] temperature_bcd_out,
    output [11:0] echo_buffer_out);

    parameter ECHO_ON = 2'b01;
    parameter ECHO_OFF = 2'b10;

    wire [7:0] humidity, temperature; 
    dht11_cntrl dth11(.clk(clk), .reset_p(reset_p), .dht11_data(dht11_data), .humidity(humidity), .temperature(temperature));
    
    reg[2:0] ehco_state, ehco_next_state;
    wire [21:0] distance_cm;
    HC_SR04_cntr HC_SR04_cntr_0(.clk(clk), .reset_p(reset_p), .hc_sr04_echo(echo), .hc_sr04_trig(trig), .distance(distance_cm));   
       
    always@(posedge clk or posedge reset_p)begin
        if(reset_p)ehco_state = ECHO_OFF;
        else if(echo_btn_enable) ehco_state = ehco_next_state;
    end

    assign usonic_enable =  (distance_cm >= 22'd10) ? 0 : 1;
    
    reg [1:0] temp_duty; 
    always@(negedge clk or posedge reset_p)begin
        if(reset_p)begin
            ehco_next_state = ECHO_ON;
            temp_duty = 2'd0;
        end
        else begin 
            case(ehco_state)
                ECHO_ON: begin
                    if(8'd24 <= temperature && temperature <= 8'd26) temp_duty = 2'd1;
                    else if(8'd27 <= temperature && temperature <= 8'd29) temp_duty = 2'd2;
                    else if(temperature > 8'd29) begin temp_duty = 2'd3;
                    ehco_next_state = ECHO_OFF;
                    end
                end
                ECHO_OFF: begin
                    temp_duty = 2'd0;
                    ehco_next_state = ECHO_ON;
                end
            endcase
        end
    end
    
    assign duty =( temp_duty && usonic_enable) ? temp_duty : 0;
    
    wire [11:0] distance_cm_bcd;
    bin_to_dec bcd_humi_distance(.bin(distance_cm[11:0]),  .bcd(distance_cm_bcd));
    
    bin_to_dec bcd_humi_temperature(.bin(temperature),  .bcd(temperature_bcd_out));
    
    assign echo_buffer_out = distance_cm_bcd;

    assign led_debug[8] = ehco_state;
    
    endmodule

 

 

 

 

7. 시연 영상