Notice
Recent Posts
Recent Comments
Link
관리 메뉴

거북이처럼 천천히

Verilog RTL 설계(7월 23일 - 4, DHT11 구현 (3) ) 본문

RTL Design/Verilog RTL 설계

Verilog RTL 설계(7월 23일 - 4, DHT11 구현 (3) )

유로 청년 2024. 8. 10. 23:35

1. 예외 처리의 필요성

  • 위 그림은 MCU와 DHT11 간에 통신 과정을 설명하고 있다.
  • 만약 MCU와 DHT11 간에 신호를 주고받는 과정에서 신호를 제대로 받지 못할 경우, 부정확한 신호 및 데이터를 받거나 잘못된 온도, 습도 데이터를 받을 수 있다.
  • 따라서 각 상태에 대해서 수신측에서 데이터를 못 받는 경우에 대한 예외 처리가 필요하다.
  • 이번에는 수신측에서 데이터를 받지 못한 경우에 대한 예외 처리를 추가적으로 설계하도록 하겠으며, 전체 소스 코드에 대한 설명보다는 수정된 부분에 대해서 집중적으로 코드와 함께 설명하도록 하겠다.

 

 

 

2. 3단계) S_HIGH_20US, DHT11측에서 응답 신호가 없는 경우

// 3단계 : S_HIGH_20US
// 100ms 동안 기다려 보았는데, Negative edge가 발생하지 않으면
// 다시 S_IDLE 상태로 돌아가 첫 단계부터 시작한다.
S_HIGH_20US : begin
    enable_counter = 1;
    if (counter > 22'd100_000) begin
        enable_counter = 0;
        next_state = S_IDLE;
    end
    else if (dht11_data_nedge) begin
        enable_counter = 0;
        next_state = S_LOW_80US;
    end 
end
  • S_LOW_80US, S_HIGH_80US 상태 단계에서는 DHT11측에서 응답 신호를 dht11_data 와이어를 통해 전달한다.
  • 하지만, DHT11 측에서 MCU측에서 보낸 시작 신호를 받지 못해 응답 신호를 보내지 못한 상황이 발생할 수 있다.
  • 이를 위해 counter를 통해 100ms 동안 기다려 보고, 100ms 이전에 Negative edge가 발생하면 다음 상태인 S_LOW_80US로 전이되며, 100ms 넘어서 까지도 Negative edge가 발생하지 않으면 "dht11 측에서 응답 신호가 없다" 판단하여 첫 단계인 S_IDLE 상태로 넘어간다.

 

 

 

 

3. 4단계) S_LOW_80US , 응답 신호 과정에서 Positive edge가 발생하지 않는 경우

// 4단계 : S_LOW_80US
// 100ms 동안 기다려 보았는데, Positive edge가 발생하지 않으면
// 다시 S_IDLE 상태로 돌아가 첫 단계부터 시작한다.

S_LOW_80US : begin
    enable_counter = 1;
    if (counter > 22'd100_000) begin
        enable_counter = 0;
        next_state = S_IDLE;
    end
    else if (dht11_data_pedge) begin 
        next_state = S_HIGH_80US; 
        enable_counter = 0;
    end
end

 

  • counter를 통해 100ms 동안 기다려 보고, 100ms 이전에 Positive edge가 발생하면 다음 상태인 S_HIGH_80US로 전이된다.
  • 100ms 넘어서 까지도 Negative edge가 발생하지 않으면 "dht11 측에서 응답 신호가 없다" 판단하여 첫 단계인 S_IDLE 상태로 넘어간다.

 

 

 

 

4. 5단계) S_HIGH_80US , DHT11 측에서 온,습도 데이터 전송을 시작하지 않는 경우

// 6단계 : S_READ_DATA
S_READ_DATA : begin
    enable_counter = 1;    
    if (counter > 22'd100_000) begin
        enable_counter = 0;
        next_state = S_IDLE;
    end
    else begin
        case (sub_state)                          
            S_WAIT_PEDGE : begin
                if (dht11_data_pedge) begin
                    enable_counter = 1;
                    sub_state = S_WAIT_NEDGE;
                end
            end        
            S_WAIT_NEDGE : begin
                if (dht11_data_nedge) begin
                    if (counter < 95) temp_data = {temp_data[38:0], 1'b0};
                    else temp_data = {temp_data[38:0], 1'b1};
                    enable_counter = 0;
                    sub_state = S_WAIT_PEDGE;
                    count_data = count_data + 1;
                end
            end
        endcase
    end 
end
  • DHT11 측에서 데이터를 전송하기 시작하면 전압 레벨은 Low-level로 변하며, Negative edge를 발생시킨다.
  • 따라서 counter를 통해 100ms 동안 기다려 보고, 100ms 이전에 Negative edge가 발생하면 다음 상태인 S_READ_DATA로 전이된다.
  • 100ms 넘어서 까지도 Negative edge가 발생하지 않으면 "dht11 측에서 문제가 발생하여 데이터를 전송하지 못한다." 판단하여 첫 단계인 S_IDLE 상태로 넘어간다.

 

 

 

5. 6단계) S_DHT_DATA , 40bit 데이터 전송 간에 문제 발생하는 경우

// 6단계 : S_READ_DATA
S_READ_DATA : begin
    enable_counter = 1;    
    if (counter > 22'd100_000) begin
        enable_counter = 0;
        next_state = S_IDLE;
    end
    else begin
        case (sub_state)                          
            S_WAIT_PEDGE : begin
                if (dht11_data_pedge) begin
                    enable_counter = 1;
                    sub_state = S_WAIT_NEDGE;
                end
            end        
            S_WAIT_NEDGE : begin
                if (dht11_data_nedge) begin
                    if (counter < 95) temp_data = {temp_data[38:0], 1'b0};
                    else temp_data = {temp_data[38:0], 1'b1};
                    enable_counter = 0;
                    sub_state = S_WAIT_PEDGE;
                    count_data = count_data + 1;
                end
            end
        endcase
    end 
end
    • Q) 40bit 데이터를 받는 과정에서 최대 얼마만큼의 시간이 소요되는가?
    • A) 데이터 0은 50us + 28us = 78us가 소요되며, 데이터 1은 50us + 70us = 120us가 소요된다. 따라서 40bit 데이터가 모두 1인 경우가 가장 The Worst Case라고 볼 수 있고, 40bit 데이터를 받는데, 걸릴 수 있는 최대 시간은 120us * 40bit = 4800usec = 4.8msec 가 소요된다.

      따라서 100msec 동안 기다린 뒤, 100msec 넘어서 까지도 40bit 데이터를 다 받지 못한다면  "dht11 측에서 문제가 발생하여 데이터를 전송하지 못한다." 판단하여 첫 단계인 S_IDLE 상태로 넘어간다.
  • Q) 왜 95usec 을 기준으로 데이터를 0, 1을 구분하는가?
  • A) 이전 설계에서는 Positive edge에서 counting을 하였기 때문에 "High-level이 얼마만큼 지속하는가?" 를 기준으로 데이터를 0과 1로 구분하였다. 하지만, 이번에는 Negative ede에서부터 counting을 하기 때문에 "1bit 데이터를 받는 데, 얼마만큼의 시간이 소요되는가?"를 기준으로 데이터를 구분하였다.

    데이터 0은 78usec, 데이터 1은 120us 가 최대 소요되기 때문에 95usec를 기준으로 이보다 작으면 데이터 0, 크면 데이터 1로 구분할 수 있는 것이다.

 

 

 

 

6. 전체 소스 코드

// Top module
module top_module_dht11 (
    input clk, reset_p,
    inout dht11_data, 
    output [3:0] com,
    output [7:0] seg_7,
    output [5:0] led_state );
    
    wire [7:0] temperature, humidity;
    dht11_cntr control_dht11 (.clk(clk), .reset_p(reset_p), .dht11_data(dht11_data), .temperature(temperature), .humidity(humidity), .led_state(led_state));
    
    // Convert from binary to decimal
    wire [15:0] temperature_bcd, humidity_bcd;
    bin_to_dec convert_temperature(.bin({4'b0, temperature}), .bcd(temperature_bcd));
    bin_to_dec convert_humidity(.bin({4'b0, humidity}), .bcd(humidity_bcd));
     
    // FND Control
    wire [15:0] hex_value;
    assign hex_value = {temperature_bcd[7:0], humidity_bcd[7:0]};
    
    fnd_cntr fnd_control (.clk(clk), .reset_p(reset_p), .hex_value(hex_value), .com(com), .seg_7(seg_7));
endmodule

// Control DHT11
module dht11_cntr(
    input clk, reset_p,
    inout dht11_data,
    output reg [7:0] temperature,
    output reg [7:0] humidity,
    output [5:0] led_state );
    
    // State LED
    assign led_state = state;
    
    // Declare State 
    parameter S_IDLE                = 6'b00_0001;
    parameter S_LOW_18MS            = 6'b00_0010;
    parameter S_HIGH_20US           = 6'b00_0100;
    parameter S_LOW_80US            = 6'b00_1000;
    parameter S_HIGH_80US           = 6'b01_0000;
    parameter S_READ_DATA           = 6'b10_0000;
    
    // Declare Sub-State.
    parameter S_WAIT_PEDGE          = 2'b01;
    parameter S_WAIT_NEDGE          = 2'b10;
    
    // Declare state, next state variable
    reg [5:0] state, next_state;
    reg [1:0] sub_state;
    
    
    // Get 1usec one cycle pulse
    wire clk_1usec;
    clk_div_100 clk_div_1usec(.clk(clk), .reset_p(reset_p), .clk_div_100_nedge(clk_1usec));
    
    // Making usecond counter.
    reg [21:0] counter;
    reg enable_counter;
    always @ (posedge clk or posedge reset_p) begin
        if(reset_p) begin counter = 0; end
        else if(clk_1usec && enable_counter) counter = counter + 1;
        else if(!enable_counter) counter = 0;
    end
    
    // 언제 next_state로 넘어가는가?
    always @(posedge clk or posedge reset_p) begin
        if(reset_p) state = S_IDLE;
        else state = next_state;
    end
    
    // inout 키워드는 register가 아닌 wire형이기 때문에 
    // reg형으로 자료형 변환 불가.
    // 따라서 임시 레지스터를 선언한 뒤, 임시 레지스터를 통한 dht11_data wire로 
    // 데이터를 MCU에서 dht11로 전송한다.
    reg temp_dht11;
    assign dht11_data = temp_dht11;
    
    // Get positive edge or negative edge of dht11_data wire.
    wire dht11_data_nedge, dht11_data_pedge;
    edge_detector edge_detector_0 (.clk(clk), .reset_p(reset_p), .cp(dht11_data), .n_edge(dht11_data_nedge), .p_edge(dht11_data_pedge));
    
    // 온도, 습도 데이터를 받아서 임시 저장한다.
    // 왜냐하면 40bit를 전부 받은 후, check sum과 비교하여 전송 간에 데이터 손실이 있었는지 여부 확인하기 위해서
    // 만약 손실이 없음을 확인하면 FND로 출력하며, 손실이 있으면 출력하지 않고, 다음 데이터를 받는다.
    reg [39:0] temp_data;
    reg [5:0] count_data;
    
    // 각 상태의 동작 및 다음 상태로 전이될 조건을 정의
    always @(negedge clk or posedge reset_p) begin
        if(reset_p) begin
            next_state = S_IDLE;
            sub_state = S_WAIT_PEDGE; 
            temperature = 4'b0;
            humidity = 4'b0;
            temp_dht11 = 0;
            temp_data = 0;
            count_data = 0;
            enable_counter = 0;
        end
        else begin
            case(state) 
                   // 1단계 : S_IDLE
                   S_IDLE : begin
                      if(counter < 22'd3_000_000) begin
                            enable_counter = 1;
                            temp_dht11 = 'bz;
                      end
                      else begin
                            enable_counter = 0;
                            next_state = S_LOW_18MS;
                      end
                   end                 
                   
                   // 2단계 : S_LOW_18MS
                   S_LOW_18MS : begin
                      if(counter < 22'd18_000) begin
                            enable_counter = 1;
                            temp_dht11 = 'b0;
                      end
                      else begin
                            enable_counter = 0;
                            next_state = S_HIGH_20US;
                            temp_dht11 = 'bz;
                      end   
                   end
                   
                   // 3단계 : S_HIGH_20US
                   // 100ms 동안 기다려 보았는데, Negative edge가 발생하지 않으면
                   // 다시 S_IDLE 상태로 돌아가 첫 단계부터 시작한다.
                   S_HIGH_20US : begin
                        enable_counter = 1;
                        
                        if(counter > 22'd100_000) begin
                            enable_counter = 0;
                            next_state = S_IDLE;
                        end
                        else if(dht11_data_nedge) begin
                            enable_counter = 0;
                            next_state = S_LOW_80US;
                        end 
                   end
                   
                   // 4단계 : S_LOW_80US
                   // 100ms 동안 기다려 보았는데, Positive edge가 발생하지 않으면
                   // 다시 S_IDLE 상태로 돌아가 첫 단계부터 시작한다.
                   S_LOW_80US : begin
                        enable_counter = 1;
                        
                        if(counter > 22'd100_000) begin
                            enable_counter = 0;
                            next_state = S_IDLE;
                        end
                        else if(dht11_data_pedge) begin 
                            next_state = S_HIGH_80US; 
                            enable_counter = 0;
                        end
                   end
                                              
                   // 5단계 : S_HIGH_80US
                   // 100ms 동안 기다려 보았는데, Negative edge가 발생하지 않으면
                   // 다시 S_IDLE 상태로 돌아가 첫 단계부터 시작한다.
                   S_HIGH_80US : begin
                        enable_counter = 1;
                        
                        if(counter > 22'd100_000) begin
                            enable_counter = 0;
                            next_state = S_IDLE;
                        end
                        else if(dht11_data_nedge) begin  
                            next_state = S_READ_DATA; 
                            enable_counter = 0;
                        end
                   end 
                        
                   // 6단계 : S_READ_DATA
                   
                   S_READ_DATA : begin
                        // 100ms 동안 기다려 보았는데, Positive edge가 발생하지 않으면
                        // 다시 S_IDLE 상태로 돌아가 첫 단계부터 시작한다.
                        enable_counter = 1;    
                            
                        if(counter > 22'd100_000) begin
                           enable_counter = 0;
                           next_state = S_IDLE;
                        end
                        else begin
                        
                        end 
                            case (sub_state)                          
                                S_WAIT_PEDGE : begin
                                    if(dht11_data_pedge) begin
                                        enable_counter = 1;
                                        sub_state = S_WAIT_NEDGE;
                                    end
                                end        
                                    
                                S_WAIT_NEDGE : begin
                                    if(dht11_data_nedge) begin
                                        if(counter < 95) temp_data = {temp_data[38:0], 1'b0};
                                        else temp_data = {temp_data[38:0], 1'b1};
                                        
                                        enable_counter = 0;
                                        sub_state = S_WAIT_PEDGE;
                                        count_data = count_data + 1;
                                    end
                                end
                        endcase
                        
                        // 40bit 데이터를 받았는지 여부 확인
                        if(count_data >= 40) begin
                             // Check Sum과 비교하여 데이터 손실 없음을 확인하면 FND로 출력한다.
                             if(temp_data[39:32] + temp_data[31:24] + temp_data[23:16] + temp_data[15: 8] == temp_data[7:0]) begin
                                     humidity = temp_data[39:32];
                                     temperature = temp_data[23:16];
                             end
                             
                             // count_data 변수 값을 0으로 초기화
                             count_data = 0;
                             enable_counter = 0;
                             
                             // state, sub-state 초기화
                             next_state = S_IDLE;
                             sub_state = S_WAIT_PEDGE;
                       end
                   end   
  
            endcase
        end
    end
endmodule

// FND Control
module fnd_cntr (
    input clk, reset_p,
    input [15:0] hex_value,
    output [3:0] com,
    output [7:0] seg_7 );
    
    // Ring Counter of com
    ring_counter ring_counter_com (.clk(clk), .reset_p(reset_p), .com(com));
    
    // Select value
    reg [3:0] value;
    always @(com) begin
        case(com)
            4'b1110 : value = hex_value[3:0];
            4'b1101 : value = hex_value[7:4];
            4'b1011 : value = hex_value[11:8];
            4'b0111 : value = hex_value[15:12];
            default : value = value;
        endcase
    end
    
    // Show value to FND
    decoder_seg7 decoder_7_segment (.hex_value(value), .seg_7(seg_7));
endmodule

// Ring Counter of com
module ring_counter (
    input clk, reset_p,
    output reg [3:0] com );
    
    wire clk_1usec, clk_1msec;
    clk_div_100 clk_div_1usec(.clk(clk), .reset_p(reset_p), .clk_div_100(clk_1usec));
    clk_div_n #(.n(1000)) clk_div_1msec (.clk(clk), .reset_p(reset_p), .clk_source(clk_1usec), .clk_div_n_nedge(clk_1msec));
    
    always @(posedge clk or posedge reset_p) begin
        if(reset_p) com = 4'b1110;
        else if(clk_1msec) com = {com[2:0], com[3]};
    end
endmodule

// Decoder of 7-segment.
module decoder_seg7 (
    input [3:0] hex_value,
    output reg [7:0] seg_7);
    
    always @(hex_value) begin
        case(hex_value)
            0  : seg_7 = 8'b0000_0011; //common anode, 0이 켜지는거임 
            1  : seg_7 = 8'b1001_1111;  // 1
            2  : seg_7 = 8'b0010_0101;  // 2
            3  : seg_7 = 8'b0000_1101;  // 3
            4  : seg_7 = 8'b1001_1001;  // 4
            5  : seg_7 = 8'b0100_1001;  // 5
            6  : seg_7 = 8'b0100_0001;  // 6
            7  : seg_7 = 8'b0001_1111;  // 7
            8  : seg_7 = 8'b0000_0001;  // 8
            9  : seg_7 = 8'b0000_1001;  // 9
            10 : seg_7 = 8'b0001_0001;  // A
            11 : seg_7 = 8'b1100_0001;  // b
            12 : seg_7 = 8'b0110_0011;  // C
            13 : seg_7 = 8'b1000_0101;  // d
            14 : seg_7 = 8'b0110_0001;  // E
            15 : seg_7 = 8'b0111_0001;  // F
        endcase
    end
    
endmodule

// Clock Divider 100
module clk_div_100 (
    input clk, reset_p,
    output clk_div_100,
    output clk_div_100_nedge, clk_div_100_pedge );
    
    reg [6:0] counter;
    always @(posedge clk or posedge reset_p) begin
        if(reset_p) counter = 0;
        else begin
            if(counter >= 99) counter = 0;
            else counter = counter + 1;
        end
    end
    
    assign clk_div_100 = (counter < 50)? 0 : 1;
    
    edge_detector edge_detector_0 (.clk(clk), .reset_p(reset_p), .cp(clk_div_100), .n_edge(clk_div_100_nedge), .p_edge(clk_div_100_pedge));
endmodule

// Clock Divider N 
module clk_div_n #(parameter n = 100) (
    input clk, reset_p,
    input clk_source,
    output clk_div_n,
    output clk_div_n_nedge, clk_div_n_pedge );
    
    wire clk_source_nedge;
    edge_detector edge_detector_0 (.clk(clk), .reset_p(reset_p), .cp(clk_source), .n_edge(clk_source_nedge));
    
    integer counter;
    always @(posedge clk or posedge reset_p) begin
        if(reset_p) counter = 0;
        else if(clk_source_nedge) begin
            if(counter >= n - 1) counter = 0;
            else counter = counter + 1;
        end
    end
    
    assign clk_div_n = (counter < n/2) ? 0 : 1;
    edge_detector edge_detector_1 (.clk(clk), .reset_p(reset_p), .cp(clk_div_n), .n_edge(clk_div_n_nedge), .p_edge(clk_div_n_pedge));
endmodule

// Edge detector
module edge_detector (
    input clk, reset_p,
    input cp,
    output n_edge, p_edge );
    
    reg flip_flop_current, flip_flop_old;
    
    always @(posedge clk or posedge reset_p) begin
        if(reset_p) begin flip_flop_current <= 0; flip_flop_old <= 0; end
        else begin 
            flip_flop_current <= cp;
            flip_flop_old <= flip_flop_current;
        end
    end
    
    assign p_edge = ({flip_flop_current, flip_flop_old} == 2'b10)? 1 : 0;
    assign n_edge = ({flip_flop_current, flip_flop_old} == 2'b01)? 1 : 0;
endmodule


module bin_to_dec(
        input [11:0] bin,
        output reg [15:0] bcd
    );

    reg [3:0] i;

    always @(bin) begin
        bcd = 0;
        for (i=0;i<12;i=i+1)begin
            bcd = {bcd[14:0], bin[11-i]};
            if(i < 11 && bcd[3:0] > 4) bcd[3:0] = bcd[3:0] + 3;
            if(i < 11 && bcd[7:4] > 4) bcd[7:4] = bcd[7:4] + 3;
            if(i < 11 && bcd[11:8] > 4) bcd[11:8] = bcd[11:8] + 3;
            if(i < 11 && bcd[15:12] > 4) bcd[15:12] = bcd[15:12] + 3;
        end
    end
endmodule