7/23 DHT11 온습도 센서

정유석·2024년 7월 23일

교육 - 베릴로그

목록 보기
14/28



보드에서 임피던스 상태로 있는것은 센서가 Vcc를 읽는것, 보드에서 출력0 상태로 있는것은 접지와 연결한것이고 센서가 데이터선의 전위0을 읽는 것

DHT11 컨트롤 모듈

module dht11_cntr(
    input clk, reset_p,
    inout dht11_data,   //단일 데이터선이라 inout으로 선언
    output reg [7:0] humidity, temperature);
    
    parameter S_IDLE = 6'b00_0001; //상태천이도에있는 6개의 상태 설정
    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;
    
    parameter S_WAIT_PEDGE = 2'b01; //read_data에서의 40번의 데이터 교환을위한 상태로써 따로 설정
    parameter S_WAIT_NEDGE = 2'b10;
    
    reg [5:0] state, next_state;
    reg [1:0] read_state;
    
    wire clk_usec; //정확한 클락을 위해 clk_div가 아니라 분주 사용
    clock_div_100 usec_clk(.clk(clk), .reset_p(reset_p), .clk_div_100_nedge(clk_usec)); //클락이 아니라 엣지 사용
    
    reg [21:0] count_usec; //3초 대기를 위함
    reg count_usec_e;
    always @(negedge clk or posedge reset_p)begin
        if(reset_p)count_usec = 0;
        else if(clk_usec && count_usec_e)count_usec = count_usec + 1; //S_IDLE에서 count_usec_e를 1주면 카운트 시작
        else if(!count_usec_e)count_usec = 0; //count_usec_e가 0이되면 리셋
    end
            
    always @(negedge clk or posedge reset_p)begin //next_state와 state를 겹쳐서 동시에 바뀌면 한클락 뒤에 읽히게되서 
        if(reset_p)state = S_IDLE;                //하나는 상승엣지 다른하나는 하강엣지
        else state = next_state;
    end
    
    reg dht11_buffer; //input, inout에는 reg선언 안됨, 새로운 변수 만들어서 대입
    assign dht11_data = dht11_buffer;
    
    wire dht_pedge, dht_nedge;
    edge_detector_p ed(
        .clk(clk), .reset_p(reset_p), .cp(dht11_data),
        .p_edge(dht_pedge), .n_edge(dht_nedge));
    
    reg [39:0] temp_data; //데이터시트 5번에 최상위비트부터 보낸다고 되어있음, 쉬프트레지스터 사용
    reg [5:0] data_count;
    
    always @(posedge clk or posedge reset_p)begin
        if(reset_p)begin
            next_state = S_IDLE;
            read_state = S_WAIT_PEDGE;
            temp_data = 0;
            data_count = 0;
        end
        else begin
            case(state)
                S_IDLE:begin //최초 풀업에의해 high신호
                    if(count_usec < 22'd10_000)begin //22'd3_000_000
                        count_usec_e = 1;
                        dht11_buffer = 'bz; //풀업저항 회로에의해 임피던스가 있으면 Vcc
                    end
                    else begin
                        count_usec_e = 0;
                        next_state = S_LOW_18MS;                
                    end
                end
                S_LOW_18MS:begin //low신호를 주고난후에 임피던스로 신호를 끊음 그러면 풀업에의해 다시 high
                    if(count_usec <22'd20_000)begin //18ms에서 여유시간 추가
                        dht11_buffer = 0;
                        count_usec_e = 1;
                    end
                    else begin
                        count_usec_e = 0;
                        next_state = S_HIGH_20US;
                    end
                end
                S_HIGH_20US:begin
                    if(count_usec < 22'd20)begin
                        count_usec_e = 1;
                        dht11_buffer = 'bz;
                    end
                    else if(dht_nedge)begin //센서가 응답할때 1->0으로 신호가 변하면서 하강엣지 발생
                        count_usec_e = 0;
                        next_state = S_LOW_80US;                    
                    end
                end
                S_LOW_80US:begin
                    if(dht_pedge)begin
                        next_state = S_HIGH_80US;
                    end
                end
                S_HIGH_80US:begin //for80us는 대략 80us 정확하진 않지만 엣지는 들어온다
                if(dht_nedge)begin
                        next_state = S_READ_DATA;
                    end
                end
                S_READ_DATA:begin
                    case(read_state)
                        S_WAIT_PEDGE:begin
                            if(dht_pedge)read_state = S_WAIT_NEDGE;
                            count_usec_e = 0;
                        end
                        S_WAIT_NEDGE:begin
                            if(dht_nedge)begin             //count_usec은 us단위 카운트
                                if(count_usec < 45)begin //45보다 짧으면 신호0 크면1//데이터시트에 high신호 26~28us이면0, 70us이면1
                                    temp_data = {temp_data[38:0], 1'b0};
                                end
                                else begin
                                    temp_data = {temp_data[38:0], 1'b1};
                                end
                                data_count = data_count + 1;
                                read_state = S_WAIT_PEDGE;
                            end
                            else begin
                                count_usec_e = 1;
                            end
                        end
                    endcase
                    if(data_count >= 40)begin //10진수40 따로 설정하지않으면 기본 32비트, 저장공간은 6비트라 하위6비트까지만 저장
                        data_count = 0;
                        next_state = S_IDLE;
                        humidity = temp_data[39:32];
                        temperature = temp_data[23:16];
                    end
                end                    
            endcase
        end
    end
        
endmodule

컨트롤 모듈 최종

BCD변환 모듈까지 들어가있고 BCD변환 모듈은 맨 아래에 있음

module dht11_cntr(
    input clk, reset_p,
    inout dht11_data,   //단일 데이터선이라 inout으로 선언
    output reg [7:0] humidity, temperature,
    output [15:0] led_debug); //보드 동작시 디버깅용
    
    parameter S_IDLE = 6'b00_0001; //상태천이도에있는 6개의 상태 설정
    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;
    
    parameter S_WAIT_PEDGE = 2'b01; //read_data에서의 40번의 데이터 교환 따로 설정
    parameter S_WAIT_NEDGE = 2'b10;
    
    reg [5:0] state, next_state;
    reg [1:0] read_state;
    
    assign led_debug[5:0] = state;    
    
    wire clk_usec; //정확한 클락을 위해 clk_div가 아니라 분주 사용
    clock_div_100 usec_clk(.clk(clk), .reset_p(reset_p), .clk_div_100_nedge(clk_usec)); //클락이 아니라 엣지 사용
    
    reg [21:0] count_usec; //3초 대기를 위함
    reg count_usec_e;
    always @(negedge clk or posedge reset_p)begin
        if(reset_p)count_usec = 0;
        else if(clk_usec && count_usec_e)count_usec = count_usec + 1; //S_IDLE에서 count_usec_e를 1주면 카운트 시작
        else if(!count_usec_e)count_usec = 0; //count_usec_e가 0이되면 리셋
    end
            
    always @(negedge clk or posedge reset_p)begin //next_state와 state를 겹쳐서 동시에 바뀌면 한클락 뒤에 읽히게되서 
        if(reset_p)state = S_IDLE;                //하나는 상승엣지 다른하나는 하강엣지
        else state = next_state;
    end
    
    reg dht11_buffer; //input, inout에는 reg선언 안됨, 새로운 변수 만들어서 대입
    assign dht11_data = dht11_buffer;
    
    wire dht_pedge, dht_nedge;
    edge_detector_p ed(
        .clk(clk), .reset_p(reset_p), .cp(dht11_data),
        .p_edge(dht_pedge), .n_edge(dht_nedge));
    
    reg [39:0] temp_data; //데이터시트 5번에 최상위비트부터 보낸다고 되어있음, 쉬프트레지스터 사용
    reg [5:0] data_count;
    
    always @(posedge clk or posedge reset_p)begin
        if(reset_p)begin
            next_state = S_IDLE;
            read_state = S_WAIT_PEDGE;
            temp_data = 0;
            data_count = 0;
//            count_usec_e = 0;
        end
        else begin
            case(state)
                S_IDLE:begin //최초 풀업에의해 high신호
                    if(count_usec < 22'd3_000_000)begin //
                        count_usec_e = 1;
                        dht11_buffer = 'bz; //풀업저항 회로에의해 임피던스가 있으면 Vcc
                    end
                    else begin
                        count_usec_e = 0;
                        next_state = S_LOW_18MS;                
                    end
                end
                S_LOW_18MS:begin //low신호를 주고난후에 임피던스로 신호를 끊음 그러면 풀업에의해 다시 high
                    if(count_usec <22'd20_000)begin //18ms에서 여유시간 추가
                        dht11_buffer = 0;
                        count_usec_e = 1;
                    end
                    else begin
                        count_usec_e = 0;
                        next_state = S_HIGH_20US;
                        dht11_buffer = 'bz;
                    end
                end
                S_HIGH_20US:begin //어짜피 다음 하강엣지만 감지하면 되서 주석처리
//                    if(count_usec < 22'd3)begin
//                        count_usec_e = 1;
                        
//                    end
//                    else 
                    if(dht_nedge)begin
                        count_usec_e = 0;
                        next_state = S_LOW_80US;                    
                    end
                end
                S_LOW_80US:begin
                    if(dht_pedge)begin
                        next_state = S_HIGH_80US;
                    end
                end
                S_HIGH_80US:begin //for80us는 대략 80us 정확하진 않지만 엣지는 들어온다
                if(dht_nedge)begin
                        next_state = S_READ_DATA;
                    end
                end
                S_READ_DATA:begin
                    case(read_state)
                        S_WAIT_PEDGE:begin
                            if(dht_pedge)read_state = S_WAIT_NEDGE;
                            count_usec_e = 0;
                        end
                        S_WAIT_NEDGE:begin
                            if(dht_nedge)begin             //count_usec은 us단위 카운트
                                if(count_usec < 45)begin //45보다 짧으면 신호0 크면1//데이터시트에 high신호 26~28us이면0, 70us이면1
                                    temp_data = {temp_data[38:0], 1'b0};
                                end
                                else begin
                                    temp_data = {temp_data[38:0], 1'b1};
                                end
                                data_count = data_count + 1;
                                read_state = S_WAIT_PEDGE;
                            end
                            else begin
                                count_usec_e = 1;
                            end
                        end
                    endcase
                    if(data_count >= 40)begin //10진수40 따로 설정하지않으면 기본 32비트, 저장공간은 6비트라 하위6비트까지만 저장
                        data_count = 0;
                        next_state = S_IDLE;
                        humidity = temp_data[39:32];
                        temperature = temp_data[23:16];
                    end
                end                    
            endcase
        end
    end
        
endmodule

DHT11 동작 모듈 test_top

module dht11_test_top(
    input clk, reset_p,
    inout dht11_data,
    output [3:0] com,
    output [7:0] seg_7, led_debug);
    
    wire [7:0] humidity, temperature;
    dht11_cntr dht11(.clk(clk), .reset_p(reset_p),
        .dht11_data(dht11_data),
        .humidity(humidity), .temperature(temperature), .led_debug(led_debug));
    
    wire [15:0] humidity_bcd, temperature_bcd;
    bin_to_dec bcd_humi(.bin({4'b0, humidity}), .bcd(humidity_bcd));
    bin_to_dec bcd_tmpr(.bin({4'b0, temperature}), .bcd(temperature_bcd));
    
    wire [15:0] value;
    assign value = {humidity_bcd[7:0], temperature_bcd[7:0]};        
    fnd_cntr fnd (.clk(clk), .reset_p(reset_p), 
        .value(value), .com(com), .seg_7(seg_7));

endmodule

테스트 벤치

//dht11의 관점
module tb_dht11_cntr();
    
    //보드에 보내줄 온습도 데이터 임의로 생성
    parameter humi_data = 8'h27;
    parameter tmpr_data = 8'h23;
    parameter check_sum = humi_data + tmpr_data; //앞의 8비트 4개 모두 더한 값, 보낸쪽과 받은쪽에서 모두 값이 같아야 온전한 값
    parameter [39:0] data = {humi_data, 8'b0, tmpr_data, 8'b0, check_sum};
    
    //입력이였던건 reg, 출력이였던건 wire 선언
    reg clk, reset_p;
    wire [7:0] humidity, temperature;
    tri1 dht11_data; //wire타입이고 풀업이 달린 와이어, 시뮬레이션에서만 사용가능한 변수, 회로만들때는 못만듬//tri0은 풀다운
    reg data_buffer, wr_en;
    assign dht11_data = wr_en ? data_buffer : 'bz; //출력안할때는 임피던스, 할때는 출력선 사용
    
    dht11_cntr DUT(clk, reset_p, dht11_data, humidity, temperature);
    
    initial begin
        clk = 0;
        reset_p = 1;
        wr_en = 0;
    end
    
    always #5 clk = ~clk;
    
    integer i; //변수선언은 always문이나 initial문 밖에서
    initial begin
        #10; //잠깐 리셋
        reset_p = 0; #10
        wait(!dht11_data); //괄호안이 참이 될때까지 기다림, 시작신호 18ms동안의 0신호
        wait(dht11_data);
        #20_000;
        data_buffer = 0; wr_en = 1; #80_000; //응답신호 보내는중
        wr_en = 0; #80_000;
        
        for(i=0; i<40; i=i+1)begin //언더플로우가 되기때문에 i=39부터 시작해서 줄어들면 안됨
            wr_en = 1; data_buffer = 0; #50_000;
            data_buffer = 1;
            if(data[39-i]) #70_000; //신호1을 의미하는 신호 길이
            else #29_000; //신호0을 의미하는 신호 길이
        end
        wr_en = 1; data_buffer = 0; #10;
        wr_en = 0; #10_000;
        $finish;
    end
endmodule

테스트 벤치 최종

//dht11의 관점
module tb_dht11_cntr();
    
    //보드에 보내줄 온습도 데이터 임의로 생성
    parameter humi_data = 8'h27;
    parameter tmpr_data = 8'h23;
    parameter check_sum = humi_data + tmpr_data; //앞의 8비트 4개 모두 더한 값, 보낸쪽과 받은쪽에서 모두 값이 같아야 온전한 값
    parameter [39:0] data = {humi_data, 8'b0, tmpr_data, 8'b0, check_sum};
    
    reg clk, reset_p;
    wire [7:0] humidity, temperature;
    tri1 dht11_data; //wire타입이고 풀업이 달린 와이어, 시뮬레이션에서만 사용가능한 변수, 회로만들때는 못만듬//tri0은 풀다운
    reg data_buffer, wr_en;
    assign dht11_data = wr_en ? data_buffer : 'bz; //출력안할때는 임피던스, 할때는 출력선 사용
    
    dht11_cntr DUT(clk, reset_p, dht11_data, humidity, temperature);
    
    initial begin
        clk = 0;
        reset_p = 1;
        wr_en = 0;
    end
    
    always #5 clk = ~clk;
    
    integer i; //변수선언은 always문이나 initial문 밖에서
    initial begin
        #10; //잠깐 리셋
        reset_p = 0; #10
        wait(!dht11_data); //괄호안이 참이 될때까지 기다림, 시작신호 18ms동안의 0신호
        wait(dht11_data);
        #20_000;
        data_buffer = 0; wr_en = 1; #80_000; //응답신호 보내는중
        wr_en = 0; #80_000;
        
        for(i=0; i<40; i=i+1)begin //언더플로우가 되기때문에 i=39부터 시작해서 줄어들면 안됨
            wr_en = 1; data_buffer = 0; #50_000;
            data_buffer = 1;
            if(data[39-i]) #70_000;
            else #29_000;
        end
        wr_en = 1; data_buffer = 0; #10;
        wr_en = 0; #10_000;
        $finish;
    end
endmodule

테스트 벤치 결과


온습도 결과가 처음 임의로 정해준 값으로 나옴

XDC

실제 보드에서의 동작확인과 디버깅을 위한 LED
# LEDs
set_property -dict { PACKAGE_PIN U16 IOSTANDARD LVCMOS33 } [get_ports {led_debug[0]}]
set_property -dict { PACKAGE_PIN E19 IOSTANDARD LVCMOS33 } [get_ports {led_debug[1]}]
set_property -dict { PACKAGE_PIN U19 IOSTANDARD LVCMOS33 } [get_ports {led_debug[2]}]
set_property -dict { PACKAGE_PIN V19 IOSTANDARD LVCMOS33 } [get_ports {led_debug[3]}]
set_property -dict { PACKAGE_PIN W18 IOSTANDARD LVCMOS33 } [get_ports {led_debug[4]}]
set_property -dict { PACKAGE_PIN U15 IOSTANDARD LVCMOS33 } [get_ports {led_debug[5]}]
set_property -dict { PACKAGE_PIN U14 IOSTANDARD LVCMOS33 } [get_ports {led_debug[6]}]
set_property -dict { PACKAGE_PIN V14 IOSTANDARD LVCMOS33 } [get_ports {led_debug[7]}]
set_property -dict { PACKAGE_PIN V13 IOSTANDARD LVCMOS33 } [get_ports {led_debug[8]}]
set_property -dict { PACKAGE_PIN V3 IOSTANDARD LVCMOS33 } [get_ports {led_debug[9]}]
set_property -dict { PACKAGE_PIN W3 IOSTANDARD LVCMOS33 } [get_ports {led_debug[10]}]
set_property -dict { PACKAGE_PIN U3 IOSTANDARD LVCMOS33 } [get_ports {led_debug[11]}]
set_property -dict { PACKAGE_PIN P3 IOSTANDARD LVCMOS33 } [get_ports {led_debug[12]}]
set_property -dict { PACKAGE_PIN N3 IOSTANDARD LVCMOS33 } [get_ports {led_debug[13]}]
set_property -dict { PACKAGE_PIN P1 IOSTANDARD LVCMOS33 } [get_ports {led_debug[14]}]
set_property -dict { PACKAGE_PIN L1 IOSTANDARD LVCMOS33 } [get_ports {led_debug[15]}]
연결 포트
##Pmod Header JA
set_property -dict { PACKAGE_PIN J1 IOSTANDARD LVCMOS33 } [get_ports {dht11_data}];#Sch name = JA1

BCD 변환

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

1의 자리에서 A에서 부터 변환을 해줘야 하는데 이때 6(0110)을 더해주면 된다, 이때 1의자리 상위 3비트만을 보고 3(011)을 더해주고 좌쉬프트 한번하고 하위 첫번째 비트를 그대로 내려준다. 이렇게 하면 보다 적은 비트수를 계산하게 된다.

"Double Dabble" 또는 "Shift and Add-3" 알고리즘

  • 2진수를 BCD로 변환시켜주는 알고리즘이다.
  • bcd로 변환하려면 0~9까지 표현하기위해 4비트가 각 자릿수마다 필요하다.
  • 2진수를 bcd로 변환했을때 저장할 수 있을만한 충분한 크기의 비트수를 준비하고 0으로 초기화 한다.
  • 2진수를 최상위 비트부터 bcd의 최하위 비트로 저장한다(좌쉬프트 과정).
  • 이때 bcd의 1의자리를 표현하는 하위 4비트가 0101(5)이상이라면 0011(3)을 더한다.
    (2진수 데이터가 1010이상일때(10진수로 10이상, 16진수로 A이상) 최상위 비트부터 좌쉬프트 하므로 bcd에서는 0101이상의 비트로 저장된다.
  • 0011(3)을 더해주면 1000이상으로 데이터가 변환되는데 이때 마지막에 한번더 좌쉬프트 해주면 다음 자릿수로 1이 넘어가서 해당 자릿수를 표현할수있게된다.
    (이렇기 때문에 코드의 for문 조건은 i<12이고 for문 안의 if문 조건은 i<11이여서 마지막에는 쉬프트만 한번 더 할수있게된다)
  • 위 과정을 반복하면 각 자릿수 마다 10진수로 9를 넘어가는 2진수 데이터를 다음 자릿수로 넘겨서 표현하는 bcd변환이 가능하다.
profile
개인 기록공간

0개의 댓글