관리 메뉴

거북이처럼 천천히

Verilog RTL 설계(7월 24일 - 1, HC-SR04 구현 ) 본문

RTL Design/Verilog RTL 설계

Verilog RTL 설계(7월 24일 - 1, HC-SR04 구현 )

유로 청년 2024. 8. 21. 18:48

1. HC-SR04 초음파 센서

  • 굉장히 늦었지만, 이번 게시글에서는 초음파 센서인 HC-SR04에 대해서 다루어보도록 하겠다.
  • HC-SR04 초음파 센서는 40kHz 주파수를 갖는 초음파를 통해 거리를 측정하는 초음파 센서 모듈이다.
  • HC-SR04 초음파 센서를 통해 2cm ~ 400cm까지 거리 측정이 가능하며, DataSheet에 의하면 정확도는 3mm의 오차를 갖는다고 한다.

 

 

2. HC-SR04 초음파 센서의 동작 원리

  • HC-SR04 초음파 센서는 아래와 같은 Timing diagram을 갖고, 동작하게 된다.

  • HC-SR04 초음파 센서의 기본 작동 원리 및 과정은 다음과 같다.
    1단계) MCU 측에서 HC-SR04의 TRIG 단자로 최소 10us 동안 높은 전압 레벨의 신호를 보낸다.
    2단계) HC-SR04 모듈에서 MCU측에서 보낸 높은 레벨의 신호를 받으면 아래와 같이 HC-SR04의
               Transmitter에서 40kHz 주파수의 초음파 신호를 8번 발송한다.
    3단계) HC-SR04의 Transmitter에서 발사된 초음파 신호가 전방에 있는 물체에 반사되어 되돌아 
               오면 HC-SR04의 Receiver 측에서 반사된 신호를 받게 된다.
    4단계) HC-SR04는 초음파가 발사된 시간부터 반사되어 되돌아 오는데 까지 걸린 시간을 구한다.
    5단계) 이렇게 계산된 소요된 시간을 HC-SR04의 ECHO 단자를 통해 높은 레벨의 출력 신호를
               발생시키는데, 이 때, 발생한 초음파의 출력 지속 시간은 초음파를 보내고 받는데 걸린 시간
               을 의미한다.

  • Q) HC-SR04 모듈로 부터 받은 정보는 초음파를 보내고 받는데 소요된 시간 정보라면 어떻게 HC-SR04와 사물과의 거리를 계산할 수 있는가?
    A)
    HC-SR04 모듈로부터 받은 정보와 다음과 같은 수식을 통해 물체와 모듈간에 거리를 구할 수 있다.

  • Q) 왜 거리를 구하는 과정에서 2를 나누는가?
    A) 
    HC-SR04로 부터 얻은 정보는 초음파를 보내고 받는데까지 소요된 시간이기 때문에 초음파가 반사되어 되돌아 오는 상황을 고려하여 2를 나눌 필요가 있다.

 

 

 

 

 

 

 

 

3. HC-SR04 초음파 센서의 구현

  • HC-SR04 초음파 센서 컨트롤 모듈을 구현하여 HC-SR04 모듈을 동작 및 사용해보도록 하겠다.
  • HC-SR04 초음파 센서의 구현 코드에 대한 설명은 코드와 함께 설명하도록 하겠다.

 

< Source, Input / Output 변수 선언 >

// HC-SR-04 Control Module
module hc_sr04_cntr(
    input clk, reset_p,
    input echo,
    output reg trig,
    output reg [8:0] distance_cm,
    output [3:0] led_state
);
  • HC-SR04와 Basys3간에 데이터를 주고 받기 위해 Echo 단자는 input, Trig 단자는 Output으로 선언한다.
    ( 주의 : HC-SR04 모듈 관점이 아닌 basys3 관점에서 보고, input / output 으로 선언해야 한다. )
  • HC-SR04는 최대 400cm 까지 측정이 가능하기 때문에 400까지 데이터를 수용할 수 있도록 distance_cm 레지스터를 9bit로 선언하였다.
  • led_state 는 "현재 어떤 상태 단계에 있는가?" 를 확인하기 위해 선언하였다.

 

< Source, state 정의 및 state, next_state 변수 선언 >

 // Declare state parameter
parameter S_IDLE = 4'b0_001;
parameter S_TRIGGER_INPUT = 4'b0_010;
parameter S_WAIT_ECHO_PULSE_PEDGE = 4'b0_100;
parameter S_WAIT_ECHO_PULSE_NEDGE = 4'b1_000;

    
// Aassign led state.
assign led_state = state;
    
// Declare main state, next state variable
reg [3:0] state, next_state;
  • HC-SR04 모듈과 Basys3 간에 통신 과정을 위와 같이 4단계로 나누어 Parameter 및 상태 단계로 정의하였다.
    S_IDLE : 1초동안 대기 상태
    S_TRIGGER_INPUT : Basys3 측에서 10us동안 높은 신호 레벨을 주는 단계
    S_WAIT_ECHO_PULSE_PEDGE : Echo 단자로부터 positive edge를 기다리는 단계
    S_WAIT_ECHO_PULSE_NEDGE : Echo 단자로부터 negative edge를 기다리는 단계

  • usecond counter를 통해 S_WAIT_ECHO_PULSE_PEDGE  ~ S_WAIT_ECHO_PULSE_NEDGE 동안 ECHO측으로부터 받은 신호가 활성화 지속된 시간을 Counting하며, S_WAIT_ECHO_PULSE_NEDGE 에 도달하면 이 때까지 Counting된 시간을 Top module로 전달한다.

 

 

< Source, usecond counter 만들기 >

// Get 1usec one cycle pulse
wire clk_1usec;
clk_div_100 clk_div_100_hc_sr04 (.clk(clk), .reset_p(reset_p), .clk_div_100_nedge(clk_1usec));
    
// Making usecond counter
reg counter_enable;
reg [21:0] counter;
always @(posedge clk or posedge reset_p) begin
    if(reset_p) counter = 0;
    else if(clk_1usec && counter_enable) counter = counter + 1;
    else if(!counter_enable) counter = 0; 
end
  • Q) usecond counter를 만드는가?
    A)
    HC-SR04 모듈과 통신하는 과정에서 대기하거나 신호를 일정 시간동안 지속적으로 유지해야하기 때문에 이를 위해 usecond 단위로 counting이 가능한 usecond counter를 생성하였다.

  • Q) counter는 왜 22bit 크기를 갖는가?
    A) HC-SR04 모듈과 통신하는 과정에서 시간이 가장 많이 소요되는 단계는 S_IDLE 단계이다. S_IDLE 단계에서는 1초 동안 대기하기 때문에 usecond counter를 통해 대기하기 위해서는 usecond counter는 1,000,000동안 counting할 수 있어야 한다. 따라서 counter를 22bit 크기로 설정함으로서 S_IDLE 단계에서 1초 동안 대기할 수 있다.

 

< Source, 다음 상태로 넘어가는 시점 정의 >

// 언제 다음 상태로 넘어가는가?
always @(posedge clk or posedge reset_p) begin
    if(reset_p) state = S_IDLE;
    else state = next_state;
end

 

 

 

< Source, Echo 단자로부터 얻은 신호의 edge detect하기 >

// Get edge of ECHO
wire echo_pedge, echo_nedge;
edge_detector edge_detector_echo (.clk(clk), .reset_p(reset_p), .cp(echo), .p_edge(echo_pedge), .n_edge(echo_nedge));
  • Q) Echo 단자로부터 얻은 신호의 edge를 검출하고자 하는가?
    A) S_WAIT_ECHO_PULSE_PEDGE 단계
    S_WAIT_ECHO_PULSE_NEDGE 단계 에서는 언제 신호가 들어오며, 언제까지 신호가 활성화 상태를 유지 할 지를 알 수 없기 때문에 edge를 통해 신호를 감지하고, 활성화 지속 시간을 계산하게 된다. 

    환경에 따라서 정면 물체와 HC-SR04와의 거리가 길수록 Positive edge of Echo가 늦게 발생하며, HC-SR04와의 거리가 길수록 Echo Signal의 활성화 지속 시간이 길게 유지된다.

 

 

< Source, Clock divider 58 >

// Clock divider 58
wire [8:0] cm;
clk_div_58 clk_div_58_0 (.clk(clk), .reset_p(reset_p), .clk_source_nedge(clk_1usec),
                         .counter_enable(counter_enable), .cm(cm));

  • Q) 왜 58분주화가 필요한가?
    A) 위에서 말했듯이 HC-SR04 모듈로부터 받은 정보는 초음파를 보내고 받는데 걸린 시간 이며, 이를 토대로 거리를 계산하기 위해서는 58로 나누어야 하는데, Basys3에서 나눗셈 연산을 수행하면 나눗셈 연산을 위한 회로가 추가적으로 생성되어야 하기 때문에 Flip-Flop, LUT 자원과 시간 자원 측면에서 효율성이 떨어진다. 

    따라서 58 분주화를 통해 58 usecond 마다 distance 값을 1씩 증가 시킴으로서 나눗셈 연산을 대체할 수 있으며, 이를 통해 Flip-Flop, LUT 자원과 시간 자원을 절약할 수 있는 효율성 측면에서 좋다고 볼 수 있다.

 

 

 

 

< Source, 각각의 상태 단계의 동작 및 다음 상태 단계 전이되기 위한 조건 정의 >

// 각 state에 대한 동작 및 다음 상태로 넘어가는 조건 정의
always @(negedge clk or posedge reset_p) begin
    if(reset_p) begin
        next_state = S_IDLE;
        counter_enable = 0;
    end
    else begin
        case(state)
            // 1단계) S_IDLE 단계
            S_IDLE : begin
                if(counter < 22'd1_000_000) begin
                    counter_enable = 1;
                    trig = 0;
                end
                else begin
                    counter_enable = 0;
                    next_state = S_TRIGGER_INPUT;
                end
            end
            
            // 2단계) S_TRIGGER_INPUT 단계
            S_TRIGGER_INPUT : begin
                if(counter < 22'd10) begin
                    counter_enable = 1;
                    trig = 1;
                end
                else begin
                    trig = 0;
                    counter_enable = 0;
                    next_state = S_WAIT_ECHO_PULSE_PEDGE;
                end
            end
            
            // 3단계) S_WAIT_ECHO_PULSE_PEDGE 단계 
            S_WAIT_ECHO_PULSE_PEDGE : begin
                if(echo_pedge) begin
                    counter_enable = 1;
                    next_state = S_WAIT_ECHO_PULSE_NEDGE;
                end
            end
            
            // 4단계) S_WAIT_ECHO_PULSE_NEDGE 단계
            S_WAIT_ECHO_PULSE_NEDGE : begin
                if(echo_nedge) begin
                    distance_cm = cm;
                    counter_enable = 0;
                    next_state = S_IDLE;
                end
            end
        endcase
    end
end
  • counter_enable 변수는 counter를 활성화를 결정하는 변수로서 0인 경우, counter를 초기화 시킨 뒤, 비활성화 상태로 전환하며, 1인 경우에는 counter를 활성화 상태로 전환하여 1usec 마다 1씩 증가 시킨다.
  • S_IDLE 상태 : Basys3와 HC-SR04 모듈과 통신하기 전에 1초 동안 대기하는 상태
    ▶ usecond counter를 통해 1000000를 카운트하여 1초 동안 대기하게 된다.
    ▶ usecond counter를 통해 1000000를 카운트를 끝나게 되면 다음 상태로 전이되게 된다.

  • S_TRIGGER_INPUT 상태 : TRIG 단자를 통해 10us 동안 높은 신호를 HC-SR04에게 전달하는 상태 
    ▶ usecond counter를 통해 10를 카운트하여 10us 동안 TRIG 변수를 통해 높은 전압 신호를 인가하게 된다.
    ▶ usecond counter를 통해 10를 카운트를 끝나게 되면 다음 상태로 전이되게 된다.
  • S_WAIT_ECHO_PULSE_PEDGE 상태 : ECHO 단자로부터 Positive edge 신호를 기다리는 상태
    ▶ HC-SR04 모듈이 초음파를 통해  초음파를 보내고 받는데 걸린 시간 정보를 보내기 시작하면 ECHO
        단자의 신호 레벨이 0 → 1로 변화하게 된다.
    이를 통해 Positive edge of Ehco 을 감지함으로서 Basys3측에서 데이터를 받을 준비를 할 수 있게 
         된다.

  • S_WAIT_ECHO_PULSE_NEDGE 상태 : ECHO 단자로부터 Negative edge 신호를 기다리는 상태
     HC-SR04 모듈측에서 초음파를 보내고 받는데 걸린 시간 정보를 그만 보내게 되면 ECHO 단자의
         신호 레벨이 1 → 0로 변화하게 된다.
    ▶ 따라서 Negative edge of ECHO 을 감지함으로서 Basys3측에서 모든 데이터를 받았다고 판단하고,
        받은 데이터를 토대로 거리를 계산하게 된다.
    ▶ 거리의 계산은 58분주 모듈을 통해서 usecond counter를 통해 카운트된 값을 58로 나누어 거리 값을
        얻는다.

 

 

 

 

< Source, Clock divider 58 >

// Clock divider 58
module clk_div_58 (
    input clk, reset_p,
    input clk_source_nedge,
    input counter_enable, 
    output reg [8:0] cm );
    
    reg [5:0] counter;
    always @(posedge clk or posedge reset_p) begin
        if(reset_p || !counter_enable) begin
            cm = 0;
            counter = 0;
        end
        else if(counter_enable && clk_source_nedge) begin
            if(counter >= 57) begin
                cm = cm + 1;
                counter = 0;
            end
            else counter = counter + 1;
        end
    end  
endmodule
  • usecond counter를 통해 카운트한 값을 58로 나눔으로서 HC-SR04와 물체간의 거리 값을 구할 수 있다.
  • 하지만, 나눗셈 연산을 할 경우 Basys3내에서 나눗셈 연산을 수행하는 회로가 생성되어야 할 필요가 있기 때문에 Flip Flop과 LUT 자원 및 시간 자원이 소모되어 비효율적이기 때문에 분주화를 통해 상대적으로 자원을 덜 소모하여 효율적으로 나눗셈 연산을 수행하고자 설계하였다.

 

 

 

4. 전체 소스 코드

// Top module of HC SR04
module top_module_hc_sr04 (
    input clk, reset_p,
    input echo,
    output trig,
    output [3:0] com,
    output [7:0] seg_7,
    output [4:0] led_state);
 
    // HC SR-04 Control module
    wire [8:0] distance_cm;
    hc_sr04_cntr control_hc_sr04_0 (.clk(clk), .reset_p(reset_p), .echo(echo), .trig(trig), .distance_cm(distance_cm), .led_state(led_state));
 
    // Convert from binary to BCD
    wire [15:0] distance_bcd;
    bin_to_dec convert_from_bin_to_bcd (.bin(distance_cm), .bcd(distance_bcd));
    
    // FND Control
    fnd_cntr fnd_cntr_0 (.clk(clk), .reset_p(reset_p), .hex_value({8'b0, distance_bcd[7:0]}),
                         .com(com), .seg_7(seg_7));
    
endmodule

// HC-SR-04 Control Module
module hc_sr04_cntr(
    input clk, reset_p,
    input echo,
    output reg trig,
    output reg [8:0] distance_cm,
    output [3:0] led_state
);
    
    // Declare state parameter
    parameter S_IDLE = 4'b0_001;
    parameter S_TRIGGER_INPUT = 4'b0_010;
    parameter S_WAIT_ECHO_PULSE_PEDGE = 4'b0_100;
    parameter S_WAIT_ECHO_PULSE_NEDGE = 4'b1_000;

    
    // Aassign led state.
    assign led_state = state;
    
    // Declare main state, next state variable
    reg [3:0] state, next_state;
    
    // Get 1usec one cycle pulse
    wire clk_1usec;
    clk_div_100 clk_div_100_hc_sr04 (.clk(clk), .reset_p(reset_p), .clk_div_100_nedge(clk_1usec));
    
    // Making usecond counter
    reg counter_enable;
    reg [21:0] counter;
    always @(posedge clk or posedge reset_p) begin
        if(reset_p) counter = 0;
        else if(clk_1usec && counter_enable) counter = counter + 1;
        else if(!counter_enable) counter = 0; 
    end
    
    // 언제 다음 상태로 넘어가는가?
    always @(posedge clk or posedge reset_p) begin
        if(reset_p) state = S_IDLE;
        else state = next_state;
    end
    
    // Get edge of ECHO
    wire echo_pedge, echo_nedge;
    edge_detector edge_detector_echo (.clk(clk), .reset_p(reset_p), .cp(echo), .p_edge(echo_pedge), .n_edge(echo_nedge));
    
    // Clock divider 58
    wire [8:0] cm;
    clk_div_58 clk_div_58_0 (.clk(clk), .reset_p(reset_p), .clk_source_nedge(clk_1usec),
                       .counter_enable(counter_enable), .cm(cm));
    
    // 각 state에 대한 동작 및 다음 상태로 넘어가는 조건 정의
    always @(negedge clk or posedge reset_p) begin
        if(reset_p) begin
            next_state = S_IDLE;
            counter_enable = 0;
        end
        else begin
            case(state)
            
                // 1단계) S_IDLE 단계
                S_IDLE : begin
                    if(counter < 22'd1_000_000) begin
                        counter_enable = 1;
                        trig = 0;
                    end
                    else begin
                        counter_enable = 0;
                        next_state = S_TRIGGER_INPUT;
                    end
                end
                
                // 2단계) S_TRIGGER_INPUT 단계
                S_TRIGGER_INPUT : begin
                    if(counter < 22'd10) begin
                        counter_enable = 1;
                        trig = 1;
                    end
                    else begin
                        trig = 0;
                        counter_enable = 0;
                        next_state = S_WAIT_ECHO_PULSE_PEDGE;
                    end
                end
                
                // 3단계) S_WAIT_ECHO_PULSE_PEDGE 단계 
                S_WAIT_ECHO_PULSE_PEDGE : begin
                    if(echo_pedge) begin
                        counter_enable = 1;
                        next_state = S_WAIT_ECHO_PULSE_NEDGE;
                    end
                end
                
                // 4단계) S_WAIT_ECHO_PULSE_NEDGE 단계
                S_WAIT_ECHO_PULSE_NEDGE : begin
                    if(echo_nedge) begin
                        distance_cm = cm;
                        counter_enable = 0;
                        next_state = S_IDLE;
                    end
                end
            endcase
        end
    end
endmodule

// Clock divider 58
module clk_div_58 (
    input clk, reset_p,
    input clk_source_nedge,
    input counter_enable, 
    output reg [8:0] cm );
    
    reg [5:0] counter;
    always @(posedge clk or posedge reset_p) begin
        if(reset_p || !counter_enable) begin
            cm = 0;
            counter = 0;
        end
        else if(counter_enable && clk_source_nedge) begin
            if(counter >= 57) begin
                cm = cm + 1;
                counter = 0;
            end
            else counter = counter + 1;
        end
    end  
endmodule

 

 

 

5. 구현 영상

< HC-SR04 와 물체 사이 거리 : 10cm >

 

 

< HC-SR04 와 물체 사이 거리 : 13cm >

 

 

< HC-SR04 와 물체 사이 거리 : 3cm >

 

 

< HC-SR04 와 물체 사이 거리 : 8cm >