Verilog/Verilog 실습

Verilog basys3으로 DHT11 제어하기

오버헤드프레스 2023. 5. 3. 16:55

DHT11

 

Verilog 실습을 진행하면서 DHT11 온습도센서를 다루는 수업을 진행하고 있다.

보드는 basys3을 사용하였다.

 

스펙

DHT11은 20~90%의 습도와 0~50℃을 측정할 수 있으며 오차는 습도 ±5% 온도 ±2℃ 정도이다.

 

 

DHT11과 MCU 연결

 

1번 PIN으로 전원을 받고 2번 PIN으로 데이터를 3번 PIN은 GND와 연결한다.

DHT11은 3.3V ~ 5V에서 작동한다.

 

동작 전체

 

동작 초반

 

 

 

 

동작 중간
동작 후반

.

DHT11은 Signal 핀의 Level을 통해 통신을 위와 같이 수행한다.

 

1. MCU가 시작신호를 보내면 DHT11은 MCU가 시작신호를 완료하는걸 기다린다.

2. DHT11이 상대 습도 및 온도 정보를 포함하는 40bit 데이터의 응답 신호를 MCU에 보낸다.

3. MCU의 Start signal이 없다면 MCU는 응답 신호를 DHT로부터 받을 수 없다.

 

4. MCU와 DHT11이 서로 통신을 시작하면, MCU의 프로그램은 Data Single_bus 전압레벨을 high에서 low로 설정한다.

 

5. 이 과정은 최소 18ms이며, DHT11이 MCU의 신호를 감지한 후 MCU가 전압을 높이며 DHT11의 응답을 20~40us 기다린다.

6. DHT11이 Start signal을 감지하고, 최소 80us 동안 low-voltage-level 응답 신호를 보낸다. 

7. 그 후 DHT11프로그램은 Data Single-bus 전압을 low - high로 설정하고 데이터 전송을 위한 준비를 80us동안 유지한다.

 

8. MCU와 DHT11이 서로 통신을 시작하면, MCU의 프로그램은 Data Single_bus 전압레벨을 high에서 low로 설정한다.

 

9. 이 과정은 최소 18ms이며, DHT11이 MCU의 신호를 감지한 후 MCU가 전압을 높이며 DHT11의 응답을 20~40us 기다린다.

10. DHT11이 Start signal을 감지하고, 최소 80us 동안 low-voltage-level 응답 신호를 보낸다. 

11. 그 후 DHT11프로그램은 Data Single-bus 전압을 low - high로 설정하고 데이터 전송을 위한 준비를 80us동안 유지한다.

 

12. Data Single-bus가 low voltage level일 때, DHT11은 응답 신호를 보낸다. DHT11이 응답 신호를 보내면 전압을 끌어올려 80us동안 유지하고 데이터 전송을 준비한다.

13. 비트전송시 50us LOW후 26~28us HIGH는 0bit로 50us LOW후 70us HIGH는 1bit로 간주하여 데이터를 전송한다.

14. 데이터의 끝에서는 50us LOW후 계속 HIGH상태를 유지한다.

 

 

 

Verilog DHT11 코드

`timescale 1ns / 1ps


module DHT11(
    input clk,
    input reset_n,
    output reg [7:0] humidity,
    output reg [7:0] temperature,
    inout dht11_data,
    output reg [7:0] led
    );
    
    //베릴로그는 회로기 때문에 순서가 있는 송수신 같은것은 못함
    //스타트비트를 보내고 데이터를 읽고 이런 순서. 이럴 때 fsm을 사용한다.
    
    parameter S_IDLE = 3'b000;
    parameter S_LOW_18MS = 3'b001;
    parameter S_HIGH_20US = 3'b010;
    parameter S_LOW_80US = 3'b011;
    parameter S_HIGH_80US = 3'b100;
    parameter S_READ_DATA = 3'b101;
    
    parameter S_WAIT_PEDGE = 3'b000;
    parameter S_WAIT_NEDGE = 3'b001;
    
    reg [21:0] count_usec;
    reg count_usec_e = 0;
    reg [2:0] state = S_IDLE, next_state = S_IDLE, read_state = S_WAIT_PEDGE;
    reg dht_buffer;
    reg [39:0] temp_data;
    reg [5:0] data_count = 0;
    
    wire dht_nedge, dht_pedge;
    
    assign dht11_data = dht_buffer;
    
    clock_usec usec_clk(.clk(clk), .reset_n(1), .clk_usec(clk_usec));
    
    always @(negedge clk_usec or negedge reset_n)begin
        if(!reset_n) count_usec = 0;
        else if (count_usec_e) count_usec = count_usec + 1;
        else count_usec = 0;
    end
    
    always @(negedge clk_usec or negedge reset_n)begin
        if (!reset_n) state = S_IDLE;
        else state = next_state;
    end
    
    always @(negedge clk_usec or negedge reset_n)begin
        if(!reset_n)begin
            count_usec_e = 0;
            
        end
        else begin
            case (state)
                S_IDLE : begin
                    
                    if (count_usec < 22'd3000000)begin  //22'd3000000
                        dht_buffer = 1'bz;
                        count_usec_e = 1;
                        led[0] = 1;
                    end
                    else begin
                        next_state = S_LOW_18MS;
                        count_usec_e = 0;
                        led = 0;
                    end
                end
                S_LOW_18MS : begin
                    led[1] = 1;
                    if (count_usec < 19999)begin
                        count_usec_e = 1;
                        dht_buffer = 0;
                    end
                    else begin
                        count_usec_e = 0;
                        next_state = S_HIGH_20US;
                        dht_buffer = 1'bz;
                    end
                end
                S_HIGH_20US : begin
                    led[2] = 1;
                    if (count_usec < 3)begin
                        dht_buffer = 1'bz;
                        count_usec_e = 1;                                                                                                                                                                                                                            
                    end
                    else if(count_usec < 40)begin
                        count_usec_e = 1;
                        dht_buffer = 1'bz;
                        if(dht_nedge)begin    //nedge가 들어오면 다음 state로 넘어갈 수 있게
                            next_state = S_LOW_80US;
                            count_usec_e = 0;
                        end
                        else begin
                            next_state = S_HIGH_20US;
                            count_usec_e = 1;
                        end
                    end
                    else begin
                        next_state = S_IDLE;  
                        count_usec_e = 0;
                    end
                end
                S_LOW_80US : begin
                led[3] = 1;
                    if (count_usec < 83)begin
                        if(dht_pedge)begin
                            next_state = S_HIGH_80US;
                            count_usec_e = 0;
                        end
                        else begin
                            next_state = S_LOW_80US;
                            count_usec_e = 1;
                        end
                    end
                    else begin
                        next_state = S_IDLE;
                        count_usec_e = 0;
                    end
                end
                S_HIGH_80US: begin
                led[4] =1;
                    if (count_usec < 100) begin
                        if (dht_nedge) begin
                            next_state = S_READ_DATA;
                            count_usec_e = 0;
                        end
                        else begin
                            next_state = S_HIGH_80US;
                            count_usec_e = 1;
                        end
                    end
                    else begin
                        next_state = S_IDLE;
                        count_usec_e = 0;
                    end
                end
                S_READ_DATA : begin
                led[5] = 1;
                    case (read_state)
                        S_WAIT_PEDGE : begin
                            if(dht_pedge)begin
                                read_state = S_WAIT_NEDGE;
                                count_usec_e = 1;
                            end
                            else begin
                                count_usec_e = 0;
                            end
                        end
                        S_WAIT_NEDGE : begin
                            if (dht_nedge)begin
                                data_count = data_count + 1;
                                read_state = S_WAIT_PEDGE;
                                if(count_usec < 50)begin
                                    temp_data = {temp_data[38:0], 1'b0}; //시프트 레지스터 0을 밀어넣는다
                                end
                                else begin
                                    temp_data = {temp_data[38:0], 1'b1}; //50보다 크면 1을 저장한다
                                end
                            end
                            else begin
                                count_usec_e = 1;
                                read_state = S_WAIT_NEDGE;
                            end
                        end
                        default : read_state = S_WAIT_PEDGE;
                    endcase
                    if(data_count >= 40)begin
                        data_count = 0;
                        next_state = S_IDLE;
                        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
                    end
                end
                default : next_state = S_IDLE;
            endcase
        end
    end
    
    edge_detect ed(.clk(clk_usec), .cp_in(dht11_data), .reset_n(1), .p_edge(dht_pedge), .n_edge(dht_nedge));
    
    
    
endmodule

 

 

LED를 추가한 이유는 신호가 어디까지 가는지 확인하기 위함이다.

 

 

7세그먼트 출력을 위한 코드

`timescale 1ns / 1ps

module DHT11_top(
    input clk,
    input btn,
    inout dht11_data,
    output [3:0] com_an,
    output [6:0] seg,
    output [7:0] led
    );
    
    wire [7:0] humidity, temperature;
    wire [15:0] bcd_humi, bcd_tempr;
    wire reset_n;
    wire clk_160ns, clk_2560ns, clk_40us, debounced_btn;
    
    clock_divider cd_16(.clk(clk), .sel(3), .clk_out(clk_160ns));  //160ns
    clock_divider cd_256(.clk(clk_160ns), .sel(3), .clk_out(clk_2560ns));  //2560ns
    clock_divider cd_4096(.clk(clk_2560ns), .sel(3), .clk_out(clk_40us));  //40us
    
    D_flip_flop_posedge dff(.D(btn), .E(), .Q(debounced_btn));
    
    assign reset_n = ~debounced_btn;
    
    DHT11 dht(.clk(clk), .reset_n(reset_n), .humidity(humidity), .temperature(temperature), .dht11_data(dht11_data), .led(led));
    
     bin_to_dec btd_humi   (.bin({4'd0, humidity}), .bcd(bcd_humi));
     bin_to_dec btd_tempr  (.bin({4'd0, temperature}), .bcd(bcd_tempr));

    FND_4digit_switcher fnd(
    .value_1(bcd_tempr[3:0]), 
    .value_10(bcd_tempr[7:4]), 
    .value_100(bcd_humi[3:0]), 
    .value_1000(bcd_humi[7:4]),
    .clk(clk),
    .com_an(com_an),
    .seg(seg)
    );
    
    
endmodule

 

 

시뮬레이션을 위한 테스터 벤치 코드

`timescale 1ns / 1ps




module TB_DHT11();

reg clk, reset_n;
wire [7:0] humidity, temperature;
tri1 dht11_data;
//inout을 선언할 땐 reg로 받고 wire로 출력하면 돼서 괜찮다.
reg wr = 0;
reg din;

integer i;

parameter [7:0] humi_value = 8'd80;
parameter [7:0] temp_value = 8'd25;
parameter [7:0] checksum = humi_value + temp_value;
parameter [39:0] data = {humi_value, {8{1'b0}}, temp_value, {8{1'b0}}, checksum};

assign dht11_data = (wr == 1) ? din : 1'bz;  //? : 조건연산자 wr을 1주면 reg(input), wr을 0주면 임피던스 (output)

//integer : 32비트 signed variable, real : 32bit 소수 variable
//time : 64bit unsigned variable, realtime : 64bit 소수 variable
//wand : 연결되어있는 값들의 and 값
//tri1 : 저항성 pullup에 의해 전원으로 연결되는 net


DHT11 DUT(.clk(clk), .reset_n(reset_n),
        .humidity(humidity), .temperature(temperature),
        .dht11_data(dht11_data));
        
        initial begin
            clk = 0;
            reset_n = 0; #10;
        end
        always #5 clk = ~clk;
        initial begin
            #100 reset_n = 0; wr = 0;  //시간 단위를 맞춰서 입력. 
            #200 reset_n = 1; wr = 0;  //DHT11 에서 us사용해서 1000
            wait(!dht11_data); //wait 은 while 문, 1일 때 아무것도 안하고 기다림 0이 될때까지
            wait(dht11_data)begin din = 1;wr = 1; end //인스턴스명 . 을하면 모듈 사용(?)
            #4000; din = 0; //din = 0주면 falling edge 준거, 10ns씩이니까 40us
            #4000; din = 1;
            #4000; din = 0;
            for (i = 0; i < 40; i = i + 1)begin
                if (data[39-i]) begin
                    #2000 din = 1;
                    #70000 din = 0;
                end
                else begin
                    #2000 din = 1;
                    #26000 din = 0;
                end
            end
            #10000;
            $finish;
            
        end
endmodule

 

시뮬레이션 결과

 

humidity, temperature 는 설정한대로 80, 25가 나왔다.

16진수여서 50, 19가 나옴

 

10진수로 바꿨을 때

 

 

결과물

DHT11을 이용해 온습도를 7세그먼트를 통해 출력하였다.

1000의 자리 100의 자리는 습도, 10의 자리 1의 자리는 온도.

 

'Verilog > Verilog 실습' 카테고리의 다른 글

Verilog 다목적 시계  (0) 2023.05.16
Verilog HC-SR04 초음파센서 제어  (0) 2023.05.16
Verilog [CPU만들기] ACC  (2) 2023.05.16
Verilog [CPU 만들기] PC, ALU  (0) 2023.05.09
Verilog ADC로 전압값을 BCD로 변환  (0) 2023.05.04