Language/Verilog

Vivado : 초음파 센서

짱도르딘 2024. 7. 25. 19:07
728x90

초음파 센서

오늘은 초음파 센서를 통해 물체 사이의 거리를 측정해보려 한다.

 

초음파 센서 모듈의 데이터 시트를 확인하면 아래와 같은 Timing Diagram 정보를 확인할 수 있다.

 

 

초음파 센서 공식

 

Echo Pulse Output부분은 초음파가 왔다가 가는 걸리는 시간을 의미한다.

 

IDLE은 최소 10ms이지만, 여유 있게 3초로 적용할 예정이다.

 

그리고 물체와의 거리를 cm 단위로 측정하기 위하여 위 그림에 나와있는 바와 같이 us를 58로 나누어 진행할 예정이다.


아래는 초음파 센서 모듈을 동작시킬 코드이다..

 

입출력 부분에 선언된 에코는 입력, 트리거는 출력으로 설정하여 진행하였다. 즉, 물체까지 도달하는 속도 및 거리는 트리거, 다시 입력으로 돌아오는 것은 에코로 설정하였다.

 

 

FSM 방식으로 진행할 예정이기 때문에 각각의 state를 parameter로 설정하여 진행하였다. 총 4가지의 parameter가 존재하며, 각각의 파라미터는 초기값, 10US, Positive Edge 그리고 측정을 진행하는 MEASURE로 선언하였다.

 

 

리셋이 진행될 때에는 IDLE 단계로 넘어가며, 그 외의 상황에서는 다음 단계로 넘어가도록 설정하였다.

 

cm 단위로 측정을 진행하기 전에 cm는 us를 나누어 계산하므로, us 단위로 동작을 하기 위하여 100 분주를 사용하여 us를 사용할 수 있도록 하였다.

 

코드의 중간부분을 확인해 보면 counter_usec를 22비트로 선언한 것을 확인할 수 있는데, 3,000,000을 2진수로 변환할 시 MSB 자리가 22번째에 존재하기 때문에 22비트로 선언한 것이다.

count_usec_en(able)은 특정 구간에서의 시간을 측정하기 위해 선언된 것으로, enable의 값이 1일 때 클락(usec)이 발생하는 횟수를 카운터 한다.

 

 

always문 내에서는 리셋버튼과 클락을 제어한다. 리셋버튼이 활성화될 시 usec은 초기화되며, 이외의 경우에는 위 언급된 count_usec_en이 활성화된 순간에 클락이 발생할 시, counter_usec을 1씩 증가시킨다.

다음은 State에 따른 case문이다. 각 state에 따른 동작이 정의되어 있다.

IDLE 상태일 때에는 usec을 3초간 기다린다. 3초가 지날 시 다음 단계로 넘어간다.

이다음 단계도 IDLE 단계와 동일하게, 주어진 시간이 지나게 되면 다음 단계로 넘어가는 방식으로 진행된다.

 

마지막 측정단계인 MEASURE 단계에서는 거리(distance)를 측정한다. 측정하는 공식은 위에 나와있듯이 us를 58로 나누어 진행한다.

 

총합하여 구현된 코드는 아래와 같다.

module HC_SR04_cntr(
    input clk, reset_p, 
    input echo, 
    output reg trig,
    output reg [21:0] distance,
    output [7:0] led_debug);
    
    // For Test
    assign led_debug[3:0] = state;
    
    // Define state 
    parameter S_IDLE = 4'b0001;
    parameter S_10US_TTL = 4'b0010;
    parameter S_WAIT_PEDGE = 4'b0100;
    parameter S_MEASURE = 4'b1000;
    
    // Define state, next_state value.
    reg [3:0] state, next_state;
    
    // 언제 next_state를 state 변수에 넣는가?
    always @(posedge clk or posedge reset_p) begin
        if(reset_p) state = S_IDLE;
        else state = next_state;
    end
    
    // get 10us negative one cycle pulse
    wire clk_usec;
    clock_div_100 usec_clk( .clk(clk), .reset_p(reset_p), .clk_div_100_nedge(clk_usec));     // 1us
    
    // making usec counter.
    reg [21:0] counter_usec; // 3초까지 잴 수 있도록 21비트 선언
    reg counter_usec_en;
    
    always @(posedge clk or posedge reset_p) begin
        if(reset_p) begin counter_usec = 0;
        end else if(clk_usec && counter_usec_en) counter_usec = counter_usec + 1;
        else if(!counter_usec_en) counter_usec = 0;
    end

    // hc_sr04_data의 Negative edge, Positive edge 얻기.
    wire echo_n_edge, echo_p_edge;
    edge_detector_p edge_detector_0 (.clk(clk), .reset_p(reset_p), .cp(echo), .n_edge(echo_n_edge), .p_edge(echo_p_edge));
    
    // 상태 천이도에 따른 case문 정의
    // 각 상태에 따른 동작 정의
    always @(negedge clk or posedge reset_p) begin
        if(reset_p) begin
            next_state = S_IDLE;
            counter_usec_en = 0;  
        end else begin
            case(state)
                S_IDLE : begin        
                    if(counter_usec < 22'd3_000_000) begin
                        counter_usec_en = 1;  
                        trig = 0;
                    end
                    else begin
                        counter_usec_en = 0;
                        next_state = S_10US_TTL;
                    end
                end
                S_10US_TTL : begin
                    if(counter_usec < 22'd10) begin
                        counter_usec_en = 1;
                        trig = 1;
                    end
                    else begin
                        trig = 0;
                        counter_usec_en = 0;
                        next_state = S_WAIT_PEDGE;
                    end
                end
                S_WAIT_PEDGE :  
                    if(echo_p_edge) begin
                         next_state = S_MEASURE;    
                         counter_usec_en = 1;
                    end
                S_MEASURE : begin          
                     if(echo_n_edge) begin
                                distance = counter_usec / 58; // cm미터로 만드는 공식
                                counter_usec_en = 0;
                                next_state = S_IDLE;
                      end
                      else next_state = S_MEASURE;
                end
            endcase
        end
    end
endmodule

 

 

하지만 이렇게 코드를 진행할 경우 아래와 같은 오류가 검출되는 것을 확인할 수 있다.

 

 

이는 PDT가 길어져 (클럭) - (도달시간)이 도달시간이 클럭보다 다 커져 Negative형태를 띠기 때문이다

 


위의 오류를 수정하기 위하여 아래와 같은 코드를 작성하였다. 이번에는 MUX를 사용하여 초음파 센서의 거리를 측정할 것이다. 편의를 위해 24cm까지만 설정하였다.

 

module HC_SR04_cntr(
    input clk, reset_p, 
    input echo, 
    output reg trig,
    output reg [21:0] distance,
    output [7:0] led_debug);
    
    // For Test
    assign led_debug[3:0] = state;
    
    // Define state 
    parameter S_IDLE = 4'b0001;
    parameter S_10US_TTL = 4'b0010;
    parameter S_WAIT_PEDGE = 4'b0100;
    parameter S_MEASURE = 4'b1000;
    
    // Define state, next_state value.
    reg [3:0] state, next_state;
    
    // 언제 next_state를 state 변수에 넣는가?
    always @(posedge clk or posedge reset_p) begin
        if(reset_p) state = S_IDLE;
        else state = next_state;
    end
    
    // get 10us negative one cycle pulse
    wire clk_usec;
    clock_div_100 usec_clk( .clk(clk), .reset_p(reset_p), .clk_div_100_nedge(clk_usec));     // 1us
    
    // making usec counter.
    reg [21:0] counter_usec; // 3초까지 잴 수 있도록 21비트 선언
    reg counter_usec_en;
    
    always @(posedge clk or posedge reset_p) begin
        if(reset_p) begin counter_usec = 0;
        end else if(clk_usec && counter_usec_en) counter_usec = counter_usec + 1;
        else if(!counter_usec_en) counter_usec = 0;
    end

    // hc_sr04_data의 Negative edge, Positive edge 얻기.
    wire echo_n_edge, echo_p_edge;
    edge_detector_p edge_detector_0 (.clk(clk), .reset_p(reset_p), .cp(echo), .n_edge(echo_n_edge), .p_edge(echo_p_edge));
    
    // 상태 천이도에 따른 case문 정의
    // 각 상태에 따른 동작 정의
    
    reg [21:0] echo_time;
    
    always @(negedge clk or posedge reset_p) begin
        if(reset_p) begin
            next_state = S_IDLE;
            counter_usec_en = 0;  
            echo_time = 0;
        end else begin
            case(state)
                S_IDLE : begin        
                    if(counter_usec < 22'd3_000_000) begin
                        counter_usec_en = 1;  
                        trig = 0;
                    end
                    else begin
                        counter_usec_en = 0;
                        next_state = S_10US_TTL;
                    end
                end
                S_10US_TTL : begin
                    if(counter_usec < 22'd10) begin
                        counter_usec_en = 1;
                        trig = 1;
                    end
                    else begin
                        trig = 0;
                        counter_usec_en = 0;
                        next_state = S_WAIT_PEDGE;
                    end
                end
                S_WAIT_PEDGE :  
                    if(echo_p_edge) begin
                         next_state = S_MEASURE;    
                         counter_usec_en = 1;
                    end
                S_MEASURE : begin          
                     if(echo_n_edge) begin
                                echo_time = counter_usec;
                                counter_usec_en = 0;
                                next_state = S_IDLE;
                      end
                      else next_state = S_MEASURE;
                end
            endcase
        end
    end   
    
    always @(posedge clk or posedge reset_p)begin
        if(reset_p) distance = 0;
        else begin
            if(echo_time < 174) distance = 2;
            else if (echo_time < 232) distance = 3;
            else if (echo_time < 290) distance = 4;
            else if (echo_time < 348) distance = 5;
            else if (echo_time < 406) distance = 6;
            else if (echo_time < 464) distance = 7;
            else if (echo_time < 522) distance = 8;
            else if (echo_time < 580) distance = 9;
            else if (echo_time < 638) distance = 10;
            else if (echo_time < 696) distance = 11;
            else if (echo_time < 754) distance = 12;
            else if (echo_time < 812) distance = 13;
            else if (echo_time < 870) distance = 14;
            else if (echo_time < 928) distance = 15;
            else if (echo_time < 986) distance = 16;
            else if (echo_time < 1044) distance = 17;
            else if (echo_time < 1102) distance = 18;
            else if (echo_time < 1160) distance = 19;
            else if (echo_time < 1218) distance = 20;
            else if (echo_time < 1276) distance = 21;
            else if (echo_time < 1334) distance = 22;
            else if (echo_time < 1392) distance = 23;
            else if (echo_time < 1450) distance = 24;    
            else distance = 25;
        end
    end
endmodule

 

위 코드를 동작시키면 아래와 같이 오류 없이 동작이 실행되는 것을 확인할 수 있다.

 

하지만 위 방법은 너무 번거로우므로 다른 방법으로 대안해야 한다.

 

그 방법은 바로 58 분주를 설정하여 진행하는 것이다

 

58분주 코드는 아래와 같다.

 

cnt(count)가 57이 넘을 시 count에 1을 더하는 방식으로, 이는 58 분주의 역할을 수행한다.

module sr04_div_58(
    input clk, reset_p,
    input clk_usec, cnt_e, // count enable = 에코 상승엣지 1이 들어오면 usec를 카운트해서 58마다 cm값을 1씩 증가 
    output reg [11:0] cm);
    
    reg [5:0] cnt;
    
    always @(negedge clk or posedge reset_p)begin
        if(reset_p)begin
            cnt = 0;
            cm = 0;
        end
        else if(clk_usec)begin
            if(cnt_e)begin
                if(cnt >= 57)begin
                    cnt = 0;
                    cm = cm + 1;
                end
                else cnt = cnt + 1;
            end    
        end
        else if(!cnt_e)begin
            cnt = 0;
            cm = 0;
        end    
    end
endmodule

 

58분주 코드를 사용하여 구현된 동작 코드는 아래와 같다.

 

 

module HC_SR04_cntr(
    input clk, reset_p, 
    input echo, 
    output reg trig,
    output reg [21:0] distance,
    output [7:0] led_debug);
    
    // For Test
    assign led_debug[3:0] = state;
    
    // Define state 
    parameter S_IDLE = 4'b0001;
    parameter S_10US_TTL = 4'b0010;
    parameter S_WAIT_PEDGE = 4'b0100;
    parameter S_MEASURE = 4'b1000;
    
    // Define state, next_state value.
    reg [3:0] state, next_state;
    
    // 언제 next_state를 state 변수에 넣는가?
    always @(posedge clk or posedge reset_p) begin
        if(reset_p) state = S_IDLE;
        else state = next_state;
    end
    
    // get 10us negative one cycle pulse
    wire clk_usec;
    clock_div_100 usec_clk( .clk(clk), .reset_p(reset_p), .clk_div_100_nedge(clk_usec));     // 1us
    
    reg cnt_e;
    wire [11:0] cm;
    sr04_div_58 div58(.clk(clk), .reset_p(reset_p), .clk_usec(clk_usec), .cnt_e(cnt_e), .cm(cm));
    
    
    // making usec counter.
    reg [21:0] counter_usec; // 3초까지 잴 수 있도록 21비트 선언
    reg counter_usec_en;
    
    always @(posedge clk or posedge reset_p) begin
        if(reset_p) begin counter_usec = 0;
        end else if(clk_usec && counter_usec_en) counter_usec = counter_usec + 1;
        else if(!counter_usec_en) counter_usec = 0;
    end

    // hc_sr04_data의 Negative edge, Positive edge 얻기.
    wire echo_n_edge, echo_p_edge;
    edge_detector_p edge_detector_0 (.clk(clk), .reset_p(reset_p), .cp(echo), .n_edge(echo_n_edge), .p_edge(echo_p_edge));
    
    // 상태 천이도에 따른 case문 정의
    // 각 상태에 따른 동작 정의
    
    reg [21:0] echo_time;
    
    always @(negedge clk or posedge reset_p) begin
        if(reset_p) begin
            next_state = S_IDLE;
            counter_usec_en = 0;  
            echo_time = 0;
            cnt_e = 0;
        end else begin
            case(state)
                S_IDLE : begin        
                    if(counter_usec < 22'd3_000_000) begin
                        counter_usec_en = 1;  
                        trig = 0;
                    end
                    else begin
                        counter_usec_en = 0;
                        next_state = S_10US_TTL;
                    end
                end
                S_10US_TTL : begin
                    if(counter_usec < 22'd10) begin
                        counter_usec_en = 1;
                        trig = 1;
                    end
                    else begin
                        trig = 0;
                        counter_usec_en = 0;
                        next_state = S_WAIT_PEDGE;
                    end
                end
                S_WAIT_PEDGE :  
                    if(echo_p_edge) begin
                         next_state = S_MEASURE;    
                         cnt_e = 1;
                    end
                S_MEASURE : begin          
                     if(echo_n_edge) begin
                                distance = cm;
                                cnt_e = 0;
                                next_state = S_IDLE;
                      end
                      else next_state = S_MEASURE;
                end
            endcase
        end
    end   
endmodule

 

구현된 코드를 바탕으로 물리적으로 동작을 시키려면 아래와 같은 Top 모듈을 필요로 한다.

 

위 구현된 HC_SR04 모듈과 FND를 통하여 거리를 확인할 FND 모듈을 선언하여 진행하였다.

module sonic_test_top(
    input clk, reset_p,
    input  echo,
    output trig,
    output [3:0] com,
    output [7:0] seg_7,
    output [15:0] led_debug);
    
    wire [21:0]  distance;
    HC_SR04_cntr (.clk(clk), .reset_p(reset_p), .echo(echo), .trig(trig), .distance(distance), .led_debug(led_debug));
    
    wire [15:0] trig_bcd;
    bin_to_dec bcd_humi(.bin(distance), .bcd(trig_bcd));
    
    
    fnd_cntr fnd(.clk(clk), .reset_p(reset_p), .value(trig_bcd), .com(com), .seg_7(seg_7));    
    
endmodule

 

동작 모습은 아래와 같다. 손바닥을 가까이 댈 시 작은 숫자가 나타나며 이는 초음파센서와 손바닥의 위치가 가까움을 나타내며, 멀리 있을 때는 거리값이 커지는 것을 확인할 수 있다.

 

 

728x90

'Language > Verilog' 카테고리의 다른 글

Vivado : 온도 습도 센서  (0) 2024.07.25
Vivado : 키패드  (0) 2024.07.25
Vivado : Basys3 Stop watch  (0) 2024.07.18
Vivado : Basys3 FND / 디지털 시계, 분주  (0) 2024.07.17
Vivado : Basys3 7segment  (0) 2024.07.16