Harman 세미콘 아카데미/Verilog

2024.7.4 [Verilog]13 - 초음파센서

U_Pong 2024. 7. 4. 19:56

2024.7.4 수업날

오늘은 습도센서와 방식이 유사한 초음파센서를 베릴로그로 구현한다.


초음파센서는 Atmega 128a에서도 사용한적이 있다.

Atmega 128a에서는 Uart 통신을 사용하여 초음파 센서를 사용하였는데,

이번에는 Basys3 보드와 연결하여 사용한다.

 

먼저 소스코드를 작성하기 전에 상태도를 그려서 초음파센서가 어떻게 구동되는지 전체적인 흐름을 파악해야한다.

크게 4가지 단계로 구분할 수 있다.

1. S_ILDE: 1초 딜레이 시간이 있으며 Trigger가 0인 상태이다.

2. Trigger: Trigger가 1이고, 10us를 카운트한다. 그 후에는 Trigger가 0이된다.

3. Echo: 상승에지를 먼저 기다린 후에 초음파 왕복펄스를 구하기 위해서 usec_count_e를 1로 하여 카운터를 허용한다. 그리고 하강에지를 기다린다.

4. distance: 초음파 왕복 거리를 측정하기 위해 측정한 값을 58로 나누면 거리값을 알 수 있다.

 

이러한 흐름을 가지고 소스코드를 작성하면 된다.

 

초음파센서 소스코드
////////////////////////////////////////////2024.7.4
////////////////////////////////// 초음파센서
module ultrasonic(
    input clk, reset_p,
    input echo, 
    output reg trigger,
    output reg [11:0] distance,
    output [3:0] led);
    
    parameter S_IDLE    = 3'b001;
    parameter TRI_10US  = 3'b010;
    parameter ECHO_STATE= 3'b100;
    
    parameter S_WAIT_PEDGE = 2'b01;
    parameter S_WAIT_NEDGE = 2'b10;
    
    reg [21:0] count_usec;				
    wire clk_usec;
    reg count_usec_e;
    
    clock_usec usec_clk(clk, reset_p, clk_usec);
    
    always @(negedge clk or posedge reset_p)begin
        if(reset_p) count_usec = 0;
        else begin
            if(clk_usec && count_usec_e) count_usec = count_usec + 1;
            else if(!count_usec_e) count_usec = 0;
        end
    end

    wire echo_pedge, echo_nedge;
    edge_detector_n ed(.clk(clk), .reset_p(reset_p), .cp(echo), .p_edge(echo_pedge), .n_edge(echo_nedge));

    reg [3:0] state, next_state;
    reg [1:0] read_state;
    
    assign led[3:0] = state;

    always @(negedge clk or posedge reset_p)begin
        if(reset_p) state = S_IDLE;
        else state = next_state;
    end
    
//    reg [11:0] echo_time;
    
    always @(posedge clk or posedge reset_p)begin
        if(reset_p)begin
            count_usec_e = 0;
            next_state = S_IDLE;
            trigger = 0;
            read_state = S_WAIT_PEDGE;
        end
        else begin
            case(state)
                S_IDLE:begin
                    if(count_usec < 22'd100_000)begin 		//100ms, 빠르면 빠를수록 갱신 속도가 빨라짐
                        count_usec_e = 1; 
                    end
                    else begin 
                        next_state = TRI_10US;
                        count_usec_e = 0; 
                    end
                end
                TRI_10US:begin 
                    if(count_usec <= 22'd10)begin 
                        count_usec_e = 1;
                        trigger = 1;
                    end
                    else begin
                        count_usec_e = 0;
                        trigger = 0;
                        next_state = ECHO_STATE;
                    end
                end
                ECHO_STATE:begin 
                    case(read_state)
                        S_WAIT_PEDGE:begin
                            count_usec_e = 0;
                            if(echo_pedge)begin
                                read_state = S_WAIT_NEDGE;
                                cnt_e = 1;
                            end
                        end
                        S_WAIT_NEDGE:begin
                            if(echo_nedge)begin       
                                read_state = S_WAIT_PEDGE;
                                count_usec_e = 0;                    
                                distance = cm;
                                cnt_e = 0;
                                next_state = S_IDLE;
                            end
                            else begin
                                count_usec_e = 1;
                            end
                        end
                    endcase
                end
                default:next_state = S_IDLE;
            endcase
        end
    end
    
    
    always @(posedge clk or posedge reset_p)begin
        if(reset_p)distance = 0;
        else begin
            
            //distance = echo_time / 58;
            if(echo_time < 58) distance = 0;
            else if(echo_time < 116) distance = 1;
            else 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 if(echo_time < 1508) distance = 25;
            else if(echo_time < 1566) distance = 26;
            else if(echo_time < 1624) distance = 27;
            else if(echo_time < 1682) distance = 28;
            else if(echo_time < 1740) distance = 29;
            else if(echo_time < 1798) distance = 30;
            
        end
    end

endmodule

 

하지만 위와같이 소스코드를 작성하고 Bitstream을 실행하면, Design Runs에서 빨간색으로 숫자가 표시되는 것을 볼 수 있다. 베릴로그로 작성된 설계가 실제 FPGA 구현 과정에서 타이밍 제약을 만족하지 못한 결과인 것이다.

이를 네거티브 슬랙이라고 하는데, 네거티브 슬랙은 발생하면 안되는 것이다.

WNS (Worst Negative Slack): 이 값은 타이밍 분석에서 최악의 경로에 대해 요구된 타이밍을 충족하지 못한 정도를 나타내는 것이다. WNS가 음수이면 설계가 타이밍 요구사항을 충족하지 못했다는 뜻이다. 여기서 -11.255는 최악의 경로에서 11.255 ns가 부족하다는 것을 의미한다.

TNS (Total Negative Slack): 이 값은 모든 타이밍 위반 경로에 대해 타이밍 부족을 합산한 값을 나타내는 것이다. TNS가 음수이면 타이밍 위반이 여러 경로에 걸쳐 발생했음을 의미한다. 여기서 -149.813이라는 값은 모든 경로에서의 총 타이밍 부족을 나타낸다.

 

즉, fpga에는 나누기를 쓰면 안된다. 속도가 빠른 회로에서 나누기를 쓰면 워낙 복잡하기 때문에 pdt가 길게 나오는 것이다. 그리고 c언어의 나누기와 베릴로그에서의 나누기는 의미가 다르다.

해결하기 위해서는 나누기를 쓰지 말고 직접 지정해줘야한다. 그러면 음수로 나타나지 않는다.
하지만 일일히 적는것은 너무 코드가 길어지니까, 58로 나누었으니 58분주기를 만들면 간편하게 해결할 수 있다.

 

 

58분주기 소스코드
///////////////////////////////////////////////////////// 2024.7.4
////////////////////////// 1초음파센서 전용 cm를 출력하는 58분주기, 카운트 값을 내보냄, 나누기 안써도 됨
module sr_04_div_58(        
      input clk, reset_p,
      input clk_usec, cnt_e,                  
      output reg [11:0] cm );    
      
      integer cnt;                       
      
      always @(negedge clk or posedge reset_p) begin        
            if(reset_p)begin 
                  cnt = 0;
                  cm = 0;
            end      
            else if(cnt_e)begin
                  if(clk_usec) begin
                  //1000원짜리 100개를 10개씩 카운트해서 마지막에 10묶음이 몇개인지 확인하는 것처럼 분주기도 마찬가지
                        if(cnt >= 58)begin cnt = 0; cm = cm +1; end           //58us마다 cm가 1올라감
                        else cnt = cnt + 1;         
                  end
            end
            else begin
                  cnt = 0;
                  cm = 0;
            end            
      end            
      
endmodule

 

58분주기를 추가한 초음파센서 소스코드
module ultrasonic(
    input clk, reset_p,
    input echo, 
    output reg trigger,
    output reg [11:0] distance,
    output [3:0] led
);
    
    parameter S_IDLE    = 3'b001;               // 3개의 단계로 나누어서 3비트를 사용
    parameter TRI_10US  = 3'b010;
    parameter ECHO_STATE= 3'b100;
    
    parameter S_WAIT_PEDGE = 2'b01;       // 2개의 단계(상승에지, 하강에지)로 사용하여 2비트를 사용
    parameter S_WAIT_NEDGE = 2'b10;
    
    reg [21:0] count_usec;                //[19:0]
    wire clk_usec;
    reg count_usec_e;
    
    clock_div_100 us_clk(.clk(clk), .reset_p(reset_p), .clk_div_100(clk_usec));           // 100분주기, 10ns인 기본 클록을 100분주기 하면 1us가 되므로
    
    always @(negedge clk or posedge reset_p)begin
        if(reset_p) count_usec = 0;
        else begin
            if(clk_usec && count_usec_e) count_usec = count_usec + 1;         // 1us 클록과 카운터를 활성화하겠다는 것이 둘다 참일 때, 카운트의 값을 1씩 증가하겠다는 의미
            else if(!count_usec_e) count_usec = 0;
        end
    end

    wire echo_pedge, echo_nedge;
    edge_detector_n ed(.clk(clk), .reset_p(reset_p), .cp(echo), .p_edge(echo_pedge), .n_edge(echo_nedge));

    reg [3:0] state, next_state;                // 3단계로 나누어서 
    reg [1:0] read_state;
    
    reg cnt_e;
    wire [11:0] cm;
    sr_04_div_58 div58(clk, reset_p, clk_usec, cnt_e, cm);
    
    assign led[3:0] = state;

    always @(negedge clk or posedge reset_p)begin
        if(reset_p) state = S_IDLE;
        else state = next_state;
    end
    
//    reg [11:0] echo_time;
    
    always @(posedge clk or posedge reset_p)begin
        if(reset_p)begin
            count_usec_e = 0;
            next_state = S_IDLE;
            trigger = 0;
            read_state = S_WAIT_PEDGE;
        end
        else begin
            case(state)
                S_IDLE:begin
                    if(count_usec < 22'd100_000)begin             // 100ms, 빠르면 빠를수록 갱신 속도가 빨라짐
                        count_usec_e = 1; 
                    end
                    else begin 
                        next_state = TRI_10US;
                        count_usec_e = 0; 
                    end
                end
                TRI_10US:begin 
                    if(count_usec <= 22'd10)begin                 // 10us
                        count_usec_e = 1;
                        trigger = 1;
                    end
                    else begin
                        count_usec_e = 0;
                        trigger = 0;
                        next_state = ECHO_STATE;
                    end
                end
                ECHO_STATE:begin 
                    case(read_state)
                        S_WAIT_PEDGE:begin
                            count_usec_e = 0;
                            if(echo_pedge)begin
                                read_state = S_WAIT_NEDGE;
                                cnt_e = 1;
                            end
                        end
                        S_WAIT_NEDGE:begin
                            if(echo_nedge)begin       
                                read_state = S_WAIT_PEDGE;
                                count_usec_e = 0;                    
                                distance = cm;
                                cnt_e = 0;
                                next_state = S_IDLE;
                            end
                            else begin
                                count_usec_e = 1;
                            end
                        end
                    endcase
                end
                default:next_state = S_IDLE;
            endcase
        end
    end

endmodule

 

초음파센서 test_top 소스코드
//////////////////////////////////////////////////2024.7.4
module ultrasonic_test_top(    
    input clk, reset_p,              // 입력 clk, reset_p 정의
    input echo,                      // 입력 echo 정의 
    output trigger,
    output [3:0] com,                // 출력 [3:0] com 정의   
    output [7:0] seg_7,             // 출력 [7:0] seg_7 정의 
    output [15:0] led);             // 출력 [15:0] led 정의
    
    wire [15:0] distance;
    wire [15:0] bcd_distance;
    ultrasonic(clk, reset_p, echo, trigger, distance, led);
     
    bin_to_dec btd_distance(.bin(distance), .bcd(bcd_distance));
    
     wire [15:0] value;                                              // [15:0] value 와이어 선언
    assign value = bcd_distance;                              
    fnd_4digit_cntr fnd(clk, reset_p, value, seg_7, com);           // 4자리 FND 디스플레이 모듈을 인스턴스화하여 현재 value 값을 seg_7와 com으로 출력
    
        
endmodule

 

58분주기를 추가한 후 wns, tns가 음수로 나오지 않는 것을 확인할 수 있다.

 

 

 

회로도는 아래와 같이 연결하면 된다.

초음파 센서는 5v로 작동하는 것이기 때문에 초음파 센서의 Vcc를 Atmega128a에 연결한다.

그리고 Trigger, Echo는 Basys3 보드의 지정된 포트에 연결하고,

초음파 센서의 Gnd는 Basys3 보드와 Atmega128a에 모두 연결한다.

 

그러면 LED에 불이 켜지면서 초음파센서로부터의 거리가 Basys3 보드의 fnd에 출력되는 것을 확인할 수 있다.

Basys3 보드 초음파센서 결과

 

 

네거티브 슬랙에 대해서 다른 코드에도 적용해서 추가로 살펴보자.

2024.7.2 때 했던 습도센서와 관련된 dht11_cntr에서 edge_detector_n으로 하면 네거티브 슬랙이 출력된다.

 

하지만 edge_detector_p로 수정하면 네거티브 슬랙이 출력되지 않는 것을 확인할 수 있다.


초음파센서 끝!