Notice
Recent Posts
Recent Comments
Link
관리 메뉴

거북이처럼 천천히

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

RTL Design/Verilog RTL 설계

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

유로 청년 2024. 7. 25. 10:12

1. HC-SR04와 Basys3의 통신을 FSM (Finite State Machine)으로 구현해보자.

  • 이전 게시글에서 HC-SR04와 MCU 간의 통신과정을 State diagram으로 그린 것을 토대로 FSM (Finite State Machine)방식으로 구현하고자 한다.
  • 이를 통해 FSM(Finite State Machine)에 대해서 공부를 하는 동시에 Data Sheet에 대한 읽는 능력을 향상하는 것을 목적으로 하고 있다.
  • HC-SR04의 동작 원리와 HC-SR04와 MCU 간의 통신 과정이 궁금하다면 이전 게시글을 참고하길 바란다.
    Verilog RTL 설계(7월 24일 - 2, HC-SR04 기초) (tistory.com)
 

Verilog RTL 설계(7월 24일 - 2, HC-SR04 기초)

1. HC-SR04 초음파를 이용해 거리를 측정하는 센서 모듈HC-SR04의 data sheet를 활용하여 basys3간에 연결하여 HC-SR04측에서 측정한 거리 값을 받도록 설계해 보겠다.이를 통해 FSM(Finite State Machine)에 대해

jbhdeve.tistory.com

 

 

 

 

 

2. HC-SR04 센서를 컨트롤 하는 모듈부터 설계해보자.

  • Basys3 관점에서 HC-SR04 센서를 컨트롤 하는 모듈부터 차근차근 설계해보고자 한다.
  • HC-SR04와 MCU 간의 통신 과정을 4개의 state로 나누어 FSM 방식으로 설계할 것이다.




< Source, HC_SR04_cntr 모듈의 변수 선언 >

module HC_SR04_cntr (
    input clk, reset_p, 
    input hc_sr04_echo,
    output reg hc_sr04_trig,
    output reg [21:0] distance,
    output [7:0] led_debug);
    
endmodule
  • basys3 관점에서 HC_SR04의 TRIG 단자에 Trigger Pulse를 줘야 하며, ECHO 단자로 부터 Echo Pulse를 받아야 하기 때문에 위와 같이 hc_sr04_echo를 Input, hc_sr04_trig를 Output으로 설정하였다.
  • distance 변수는 HC_SR04로부터 받은 Echo Pulse를 토대로 계산한 물체사이의 거리 정보를 담고 있다.
  • led_debug 변수는 4개의 state 중 현재 수행 중인 state를 LED로 표시하기 위한 변수이다.

 

 

 

< Source, State 상수 및 State 변수 선언  >

// 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_CALC_DIST              = 4'b1000;
    
// Define state, next_state value.
reg [3:0] state, next_state;
  • State diagram을 토대로 4개의 상태로 나누어 각각의 상태에 이름을 붙어주어 상수로 선언하였다.
  • 총 4개의 상태로 나누어져 있기 때문에 각각의 상태를 변수로 담기 위해서는 state, next_state 변수는 4bit 크기를 가져야 한다.

 

 

 

< Source, 언제 현재 state에서 다음 state로 넘어가는가? >

// 언제 next_state를 state 변수에 넣는가?
always @(posedge clk or posedge reset_p) begin
    if(reset_p) state = S_IDLE;
    else state = next_state;
end
  • 해당 소스 블록은 " 언제 현재 state에서 다음 state로 넘어가는가?" 에 대해서 정의한 소스 블록이다.
  • 즉, Clock Pulse가 Positive edge일 때, next_state의 변수 데이터를 state 변수에 대입함으로서 다음 상태로 넘어가게 된다.

 

 

 

< Source, 1usec Counter 생성 >

// 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;
reg counter_usec_en;
    
always @(posedge clk or posedge reset_p) begin
    if(reset_p) counter_usec = 0;
    else if(clk_usec && counter_usec_en) counter_usec = counter_usec + 1;
    else if(!counter_usec_en) counter_usec = 0;
end
  • clk_usec 변수 :  10ns 주기를 갖는 CP을 100분주화하여 얻은 1usec 주기를 갖는 One Cycle Pulse
  • counter_usec_en 변수 : usec counter의 활성화를 컨트롤하는 변수
    - counter_usec_en = 0 이면 usec counter을 비활성화시키는 동시에 카운터값을 0으로 초기화한다.
    - counter_usec_en = 1 이면 usec counter를 활성화시켜 counting할 수 있게 해준다.
  • Q) 왜 usec counter의 크기를 22bit를 갖도록 설정하였는가?
    A) HC-SR04와 MCU 간의 통신 과정이 50ms 주기를 갖고 이루어 진다. 1초 동안 20번의 거리 측정이 이루어지기 때문에 이처럼 빠르게 측정이 이루어 질 경우, 결과값을 확인하기 어렵다.

    따라서 HC-SR04와 MCU 간의 통신 과정을 50ms에서 3sec으로 늘려 천천히 결과값을 확인하고자 하며, 이를 위해서는 usec counter는 최소 22bit 크기를 가져야 2^22 * 1use = 4,194,304 * 1use = 4.2sec 까지 Counting이 가능하다.

 

 

 

< Source, Echo 단자의 Positive edge, Negative edge 변수 선언 및 감지하여 저장 >

// hc_sr04_data의 Negative edge, Positive edge 얻기.
wire hc_sr04_echo_n_edge, hc_sr04_echo_p_edge;
edge_detector_p edge_detector_0 (.clk(clk), .reset_p(reset_p), .cp(hc_sr04_echo), .n_edge(hc_sr04_echo_n_edge), .p_edge(hc_sr04_echo_p_edge));
  • MCU 측에서는 HC-SR04으로 부터 얻은 Echo Pulse의 Positive edge에서부터 Negative edge까지의 시간을 usec counter를 통해 계산한 뒤, 계산한 결과값을 토대로 거리 값을 계산한다.
  • 이를 위해서는 HC-SR04의 Echo 단자로부터 나오는 Pulse의 Negative edge와 Positive edge를 감지할 필요가 있다.

 

 

 

< Source, 각 state에서의 동작 및 다음 state로 넘어가기 위한 조건 정의 >

// 상태 천이도에 따른 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;  
                        hc_sr04_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;
                        hc_sr04_trig = 1;
                    end
                    else begin
                        hc_sr04_trig = 0;
                        counter_usec_en = 0;
                        next_state = S_WAIT_PEDGE;
                    end
                end
                
                
                
                S_WAIT_PEDGE :  
                    if(hc_sr04_echo_p_edge) begin
                         next_state = S_CALC_DIST;    
                         counter_usec_en = 1;
                    end     
                
                
                
                S_CALC_DIST : begin          
                     if(hc_sr04_echo_n_edge) begin
                                distance = counter_usec / 58;
                                counter_usec_en = 0;
                                next_state = S_IDLE;
                      end
                      else next_state = S_CALC_DIST;
                end
            endcase
        end
    end
  • 위 블록은 각각의 상태에서 Basys3의 동작을 정의하는 동시에 다음 상태로 넘어가기 위한 조건을 나타내고 있다.
  • 각각의 상태별로 나누어 천천히 설명하고자 한다.

 

 

 

 

< Source, IDLE 상태 >

S_IDLE : begin        
       if(counter_usec < 22'd3_000_000) begin
           counter_usec_en = 1;  
           hc_sr04_trig = 0;
       end
       else begin
           counter_usec_en = 0;
           next_state = S_10US_TTL;
       end
 end

 

< S_IDLE 상태 동작 >

  • IDLE 상태에서는 Basys3 측에서 HC-SR04의 Trig 단자로 10us Trigger Pulse를 보내기 전까지 대기하는 상태이다.
  • 해당 단계에서는 전체 통신과정을 50ms → 3sec로 늘려주기 위해 3sec 동안 대기하도록 하겠다.
  • 따라서 3초 동안 counter_usec  = 1을 대입하여 usce counter를 활성화시킨 뒤, hc_sr04_trig = 1을 대입하여 trig 단자에 Low 전압 레벨 값을 준다.

< S_10US_TTL 상태로 넘어가기 위한 조건 >

  • usec counter가 3초를 counting 한 뒤, 다음 상태인 S_10US_TTL 상태로 넘어간다.
  • 다음 상태로 넘어가기 전 counter_usec_en 변수를 통해 usec counter를 초기화시켜 준다.

 

 

 

 

 

< Source, S_10US_TTL  상태 >

S_10US_TTL : begin
      if(counter_usec < 22'd10) begin
           counter_usec_en = 1;
           hc_sr04_trig = 1;
      end
      else begin
           hc_sr04_trig = 0;
           counter_usec_en = 0;
           next_state = S_WAIT_PEDGE;
      end
end

 

< S_10US_TTL  상태 동작 >

  • S_10US_TTL 상태는 Basys3에서 HC-SR04의 Trig 단자로 10us Trigger Pulse를 보내는 상태이다.
  • 조건문을 이용하여 usec counter가 카운트한 값이 10이 되기 전까지 if문의 명령문을 수행하도록 설계함으로서 10us 동안 Trigger Pulse를 보내게 된다.
  • 10us 동안 S_10US_TTL  상태에 있으면서 hc_sr04_trig 변수에 1값을 대입함으로서 HC-SR04의 Trig 단자로 10us Trigger Pulse를 보내지게 된다.

 

 

< S_WAIT_PEDGE 상태로 넘어가기 위한 조건 >

  • usec counter가 10까지 counting 한 뒤, 다음 상태인 S_WAIT_PEDGE 상태로 넘어간다.
  • 다음 상태로 넘어가기 전 counter_usec_en 변수를 통해 usec counter를 초기화시켜 준다.
  • 더 이상 TRIG 단자에 신호를 보내지 주지 않기 위해 hc_sr04_trig = 0으로 대입한다.

 

 

 

 

 

< Source, S_WAIT_PEDGE  상태 >

 S_WAIT_PEDGE :  
      if(hc_sr04_echo_p_edge) begin
           next_state = S_CALC_DIST;    
           counter_usec_en = 1;
      end

 

< S_WAIT_PEDGE  상태 동작 >

  • S_WAIT_PEDGE  상태는 HC-SR04의 Echo 단자로부터 Echo Pulse를 받기를 기다리는 상태이다.
  • Q) 왜 Echo Pulse의 Positive edge를 기다리는가?
    A) HC-SR04로부터 언제 Echo Pulse를 받을지 모르기 때문이다. HC-SR04과 Basys3 사이의 거리에 따라 Echo Pulse를 받는 시점이 변화하게 될 것이며, 또한 HC-SR04와 Basys3 간에 연결이 끊겨 Echo Pulse를 받지 못하는 상황이 있을 수도 있다.

    이처럼 수치적으로 HC-SR04로부터 언제 Echo Pulse를 받을지 모르기 때문에 가장 확실한 방법은 Echo Pulse의 Positive edge를 감지하도록 설계함으로서 Positive edge가 발생하면 HC-SR04로부터 거리의 데이터를 얻은 것이며, 반대로 Positive edge가 발생하지 않았다면 HC-SR04와 Basys3 간에 연결이 끊겼다고 생각하여 첫 단계인 S_IDLE 부터 다시 수행할 수 있다.

 

 

< S_CALC_DIST 상태로 넘어가기 위한 조건 >

  • Echo Pulse의 Positive edge을 감지하면 다음 상태인 S_CALC_DIST 상태로 넘어간다.
  • 이 때, Echo Pulse의 시간을 usec counter를 통해 Counting해야 하기 때문에 counter_usec_en = 1 대입하여 usec counter를 활성화시켜 준다.

 

 

 

 

 

< Source, S_CALC_DIST  상태 >

 S_CALC_DIST : begin          
      if(hc_sr04_echo_n_edge) begin
            distance = counter_usec / 58;
            counter_usec_en = 0;
            next_state = S_IDLE;
      end
end

 

< S_CALC_DIST  상태 동작 >

  • S_CALC_DIST 상태는 usec counter를 통해 Echo Pulse를 길이 및 시간을 Counting하는 상태이다.
  • Echo Pulse는 HC-SR04에서 초음파가 발사된 시점부터 반사된 초음파를 받는 시점까지의 시간만큼 활성된 펄스파이다.
  • usec counter를 통해 Echo Pulse가 활성화된 시간을 계산하며,  usec counter가 Counting한 시간은 초음파가 발사된 시점부터 반사된 초음파를 받는 시점까지의 시간 을 의미한다.

 

 

< 거리를 계산하는 방법 >

  • 거리 = 속도 * 시간 관계를 갖기 때문에 usec counter를 통해 Counting된 시간을 58로 나누면 HC-SR04와 물체간의 거리를 얻을 수 있다.
  • Q) Echo Pulse가 활성화된 시간에 왜 58로 나누는가?
    A) 초음파의 속도는 343m/sec이며, 이를 m/sec 단위에서 cm/usec 단위로 변환하면 1/29.154 [cm/usec]로 값이 변환된다. 그런데, 반사되어 다시 되돌아온 것이기 때문에 왕복인 점을 고려하여 나누기 2를 할 필요가 있어 최종적으로 58로 나누어 주게 된다.

 

 

 

< S_IDLE 상태로 넘어가기 위한 조건 >

  • Echo Pulse 에서 Negative edge가 발생하면 거리 계산후, 다음 상태인  S_IDLE  상태로 넘어간다.
  • 다음 상태로 넘어가기 전 counter_usec_en 변수를 통해 usec counter를 초기화시켜 준다.

 

 

 

 

 

 

3. HC-SR04 센서 동작 

3.1. HC-SR04와 물체간의 거리가 7cm인 경우

 

 

 

3.2. HC-SR04와 물체간의 거리가 3cm인 경우

 

 

 

3.3. HC-SR04와 물체간의 거리가 10cm인 경우

 

 

 

3.4. HC-SR04와 물체간의 거리가 13cm인 경우

 

 

 

 

 

4. Oscilloscope를 통한 Echo Pulse 측정

4.1. HC-SR04와 물체간의 거리가 5cm인 경우

  • 전압 레벨이 5V이며, 활성화 시간이 300usec인 Echo Pulse가 측정되었음을 확인할 수 있다.
  • Echo Pulse를 토대로 거리를 계산하면 300 [usec] / 58 [cm/usec] = 5.1724 cm임을 계산할 수 있으며, 실제로도 5cm가 FND에서 출력되었다.

 

 

 

4.2. HC-SR04와 물체간의 거리가 10cm인 경우

  • 전압 레벨이 5V이며, 활성화 시간이 600usec인 Echo Pulse가 측정되었음을 확인할 수 있다.
  • Echo Pulse를 토대로 거리를 계산하면 600 [usec] / 58 [cm/usec] = 10.344 cm임을 계산할 수 있으며, 실제로도 10cm가 FND에서 출력되었다.



 

5. 전체 소스 코드

< Source, HC_SR04_cntr >

module HC_SR04_cntr (
    input clk, reset_p, 
    input hc_sr04_echo,
    output reg hc_sr04_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_CALC_DIST             = 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;
    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 hc_sr04_echo_n_edge, hc_sr04_echo_p_edge;
    edge_detector_p edge_detector_0 (.clk(clk), .reset_p(reset_p), .cp(hc_sr04_echo), .n_edge(hc_sr04_echo_n_edge), .p_edge(hc_sr04_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;  
                        hc_sr04_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;
                        hc_sr04_trig = 1;
                    end
                    else begin
                        hc_sr04_trig = 0;
                        counter_usec_en = 0;
                        next_state = S_WAIT_PEDGE;
                    end
                end
                
                
                
                S_WAIT_PEDGE :  
                    if(hc_sr04_echo_p_edge) begin
                         next_state = S_CALC_DIST;    
                         counter_usec_en = 1;
                    end     
                
                
                
                S_CALC_DIST : begin          
                     if(hc_sr04_echo_n_edge) begin
                                distance = counter_usec / 58;
                                counter_usec_en = 0;
                                next_state = S_IDLE;
                      end
                      else next_state = S_CALC_DIST;
                end
            endcase
        end
    end
    
endmodule

 

 

< Source, HC_SR04_test_top >

module HC_SR04_top (
    input clk, reset_p, 
    input hc_sr04_echo,
    output hc_sr04_trig,
    output [3:0] com,
    output [7:0] seg_7, led_debug) ;
    
    wire [21:0] distance_cm;
    HC_SR04_cntr HC_SR04_cntr_0(.clk(clk), .reset_p(reset_p), .hc_sr04_echo(hc_sr04_echo), .hc_sr04_trig(hc_sr04_trig), .distance(distance_cm),  .led_debug(led_debug)); 
    
     wire [11:0] distance_cm_bcd;
    bin_to_dec bcd_humi(.bin(distance_cm[11:0]),  .bcd(distance_cm_bcd));
   
    fnd_cntr fnd (.clk(clk), .reset_p(reset_p), .value(distance_cm_bcd), .com(com), .seg_7(seg_7));    
endmodule