Verilog_RTL 설계/Basys_3

[FSM] DTH11_Sensor

juniha 2025. 7. 14. 20:10

목적

  • DHT11 센서의 신호 규격(1-wire 프로토콜)을 FPGA 등 디지털 회로에서 순수 하드웨어 논리(FSM, 타이머 등)로 직접 처리하여 온도와 습도 값을 읽어내는 것을 목적

 

  • DHT11 센서에 필요한 초기 신호 시퀀스를 하드웨어적으로 생성
  • 센서가 응답하는 타이밍을 정확히 감지(엣지 디텍터, 타이머 활용)
  • 40비트 데이터(습도 상/하위, 온도 상/하위, 체크섬)를 차례로 수신
  • 유효성 검사(체크섬)까지 하드웨어적으로 처리해 병렬 신호로 온습도 값을 출력

FSM을 사용한 이유

  • FSM(Finite State Machine) : 시스템의 동작 단계를 상태(state)로 나누고, 입력 조건에 따라 상태를 전이시키며, 각 상태별로 출력을 결정하는 논리적 구조를 의미한다.
  • DHT11은 정해진 순서대로 신호를 주고, 정해진 순서대로 응답을 받아야만 동작하는 특성을 가진다.
  • 따라서, DHT11 센서와 같이 정밀 타이밍 및 다단계 통신 시퀀스를 요구하는 시스템을 FPGA 등 하드웨어로 구현할 때,
    FSM 구조를 적용함으로써 각 프로토콜 단계별 신호 제어, 상태 전이, 예외처리를 체계적이고 신뢰성 있게 수행할 수 있다.

사용 코드_ 상위 모듈

module dht11_top(
    input clk, reset_p,
    inout dht11_data,
    output [7:0] seg_7,
    output [3:0] com,
    output [15:0] debug_led
);
    wire [7:0] humidity, temperature;

    dht11_cntr dht11(
        .clk(clk),
        .reset_p(reset_p),
        .dht11_data(dht11_data),
        .humidity(humidity),
        .temperature(temperature),
        .debug_led(debug_led)
    );

    wire [7:0] humidity_bcd, temperature_bcd;
    bin_to_dec bcd_sec(.bin(humidity), .bcd(humidity_bcd));
    bin_to_dec bcd_min(.bin(temperature), .bcd(temperature_bcd));

    // 4자리 7세그먼트 [습도][온도]
    fnd_4digit_cntr fnd(
        .clk(clk), .reset_p(reset_p),
        .value({humidity_bcd, temperature_bcd}), .seg_7(seg_7), .com(com)
    );
endmodule

 

사용 코드_ 하위 모듈

module dht11_cntr(
    input clk, reset_p,               // 시스템 클록, 비동기 리셋(Active-high)
    inout dht11_data,                 // DHT11 센서 데이터 입출력 (양방향)
    output reg [7:0] humidity, temperature,   // 습도, 온도(8비트)
    output [15:0] debug_led                  // 디버깅용 LED 출력
);

    // --- 상태(State) 파라미터 ---
    parameter S_IDLE      = 6'b00_0001;   // 3초 대기
    parameter S_LOW_18MS  = 6'b00_0010;   // MCU -> DHT11, 18ms LOW 신호
    parameter S_HIGH_20US = 6'b00_0100;   // 20us HIGH 신호
    parameter S_LOW_80US  = 6'b00_1000;   // DHT11 -> MCU, 80us LOW 응답
    parameter S_HIGH_80US = 6'b01_0000;   // DHT11 -> MCU, 80us HIGH 응답
    parameter S_READ_DATA = 6'b10_0000;   // 40비트 데이터 수신

    // 데이터비트 수신용 서브 FSM 상태 (엣지 감지)
    parameter S_WAIT_PEDGE = 2'b01;       // 양의 엣지 대기
    parameter S_WAIT_NEDGE = 2'b10;       // 음의 엣지 대기

    // --- 1us 클록 생성 (100MHz -> 1us, 10ns x 100) ---
    wire clk_usec;
    clock_div_100 us_clk(
        .clk(clk),
        .reset_p(reset_p),
        .nedge_div_100(clk_usec)       // 1us마다 nedge 발생
    );

    reg [21:0] count_usec;             // us 단위 타이머 (최대 약 4초까지 표현)
    reg count_usec_e;                  // 타이머 Enable

    // --- us 타이머: Enable 시에만 증가, 아니면 클리어 ---
    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;
        else if (!count_usec_e)
            count_usec = 0;
    end

    // --- DHT11 데이터 엣지 검출기 (p_edge: 상승, n_edge: 하강) ---
    wire dht_nedge, dht_pedge;
    edge_detector_p ed(
        .clk(clk), .reset_p(reset_p), .cp(dht11_data),
        .p_edge(dht_pedge), .n_edge(dht_nedge)
    );

    // --- 메인 FSM 상태 ---
    reg [5:0] state, next_state;
    reg [1:0] read_state;          // 데이터 수신용 서브 FSM 상태

    assign debug_led[5:0] = state; // 하위 6비트로 현재 상태 모니터링

    // --- 메인 FSM 상태 레지스터 ---
    always @(negedge clk or posedge reset_p) begin
        if (reset_p)
            state = S_IDLE;
        else
            state = next_state;
    end

    // --- dht11_data를 inout으로 쓰기 위한 버퍼 선언 ---
    reg dht11_buffer; // 출력 시 사용
    assign dht11_data = dht11_buffer; // 입력 모드(=Z), 출력모드(=0 or 1)

    // --- 데이터 수신용 레지스터 ---
    reg [39:0] temp_data;      // 40비트 시프트 레지스터 (전체 데이터 수신)
    reg [5:0] data_count;      // 데이터 비트 수 카운트

    // --- 상태/동작/출력 제어 ---
    always @(posedge clk or posedge reset_p) begin
        if (reset_p) begin
            // 모든 상태/변수 초기화
            next_state = S_IDLE;
            data_count = 0;
            count_usec_e = 0;
            dht11_buffer = 'bz; // 입력모드(Z)
            read_state = S_WAIT_PEDGE;
            temp_data = 0;
            humidity = 0;
            temperature = 0;
        end else begin
            case (state)
                // --- 1. S_IDLE: 3초 대기(초기화 및 반복 주기 맞춤) ---
                S_IDLE: begin
                    if (count_usec < 22'd3_000_000) begin // 3,000,000us = 3s
                        count_usec_e = 1;        // 타이머 Enable
                        dht11_buffer = 'bz;      // 입력모드(Z)
                    end else begin
                        next_state = S_LOW_18MS; // 대기 후 스타트 비트 출력
                        count_usec_e = 0;
                    end
                end
                // --- 2. S_LOW_18MS: DHT11 시작 신호(18ms LOW) ---
                S_LOW_18MS: begin
                    if (count_usec < 22'd18_000) begin // 18,000us = 18ms
                        dht11_buffer = 0;       // 출력 LOW (시작 신호)
                        count_usec_e = 1;
                    end else begin
                        next_state = S_HIGH_20US;
                        dht11_buffer = 'bz;     // 입력모드(Z)
                        count_usec_e = 0;
                    end
                end
                // --- 3. S_HIGH_20US: 20us HIGH 후 응답 대기 ---
                S_HIGH_20US: begin
                    count_usec_e = 1;
                    // (예외처리) 만약 100ms를 초과하면 에러로 강제 종료
                    if (count_usec > 22'd100_000) begin
                        next_state = S_IDLE;
                        count_usec_e = 0;
                    end
                    // 20us 지나고 DHT11이 응답(L→H) 시작하면 다음 상태로
                    if (dht_nedge) begin
                        next_state = S_LOW_80US;
                        count_usec_e = 0;
                    end
                end
                // --- 4. S_LOW_80US: DHT11 80us LOW 응답 ---
                S_LOW_80US: begin
                    count_usec_e = 1;
                    if (count_usec > 22'd100_000) begin // 100ms 초과시 에러
                        next_state = S_IDLE;
                        count_usec_e = 0;
                    end
                    // LOW → HIGH(80us)로 변할 때 다음 상태로
                    if (dht_pedge) begin
                        next_state = S_HIGH_80US;
                        count_usec_e = 0;
                    end
                end
                // --- 5. S_HIGH_80US: DHT11 80us HIGH 응답 ---
                S_HIGH_80US: begin
                    count_usec_e = 1;
                    if (count_usec > 22'd100_000) begin // 100ms 초과시 에러
                        next_state = S_IDLE;
                        count_usec_e = 0;
                    end
                    // HIGH → LOW(데이터 전송 시작)로 변할 때 데이터 수신상태 진입
                    if (dht_nedge) begin
                        next_state = S_READ_DATA;
                    end
                end
                // --- 6. S_READ_DATA: 데이터 40비트 수신 FSM ---
                S_READ_DATA: begin
                    case (read_state)
                        // a. 데이터 비트의 시작: 상승엣지(HIGH) 대기
                        S_WAIT_PEDGE: begin
                            if (dht_pedge)
                                read_state = S_WAIT_NEDGE;
                            count_usec_e = 0;   // 엣지 대기 중에는 카운터 정지
                        end
                        // b. 데이터 비트의 끝: 하강엣지(LOW) 대기
                        S_WAIT_NEDGE: begin
                            if (dht_nedge) begin
                                // LOW 레벨 지속시간에 따라 0/1 판별 (DHT11 프로토콜)
                                // 26~28us면 '0', 70us면 '1' (실제 50us 기준)
                                if (count_usec < 50) begin
                                    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
                            // (타임아웃) 데이터 신호가 0.7s 이상 안 오면 에러처리
                            if (count_usec > 22'd700_000) begin
                                count_usec_e = 0;
                                data_count = 0;
                                next_state = S_IDLE;
                                read_state = S_WAIT_PEDGE;
                            end
                        end
                    endcase
                    // 40비트 다 받으면 검증 및 값 저장
                    if (data_count >= 40) begin
                        data_count = 0;
                        next_state = S_IDLE;
                        // Checksum 검사: [39:32]+[31:24]+[23:16]+[15:8] == [7:0]
                        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
endmodule

사용 코드_ Testbench

module tb_dht11_cntr( );

    // --- 클록 및 리셋 신호 ---
    reg clk, reset_p;

    // --- dht11_data 신호선 선언(tri-state; 풀업 1) ---
    tri1 dht11_data;

    // --- DHT11에서 읽어올 값들 ---
    wire [7:0] humidity, temperature;

    // --- 시뮬레이션을 위한 내부 데이터(드라이브 신호) ---
    reg dout, wr;
    // wr=1이면 dout값을 dht11_data에 출력, wr=0이면 Z로 만듦(입력모드)
    assign dht11_data = wr ? dout : 'bz;

    // --- DUT 인스턴스 (테스트 대상) ---
    dht11_cntr DUT(
        .clk(clk),
        .reset_p(reset_p),
        .dht11_data(dht11_data),
        .humidity(humidity),
        .temperature(temperature)
    );

    // --- 시뮬레이션용 데이터 패턴 선언 ---
    parameter [7:0] humi_value = 8'd80; // 습도(상위 8비트, 예: 80%)
    parameter [7:0] tmpr_value = 8'd25; // 온도(상위 8비트, 예: 25도)
    parameter [7:0] check_sum = humi_value + tmpr_value; // 체크섬 계산
    
    // [습도][습도소수][온도][온도소수][체크섬] (총 40비트)
    parameter [39:0] data = {humi_value, 8'd0, tmpr_value, 8'd0, check_sum};

    // --- 초기화 및 클록 생성 ---
    initial begin
        clk = 0;
        reset_p = 1;
        wr = 0;
    end

    always #5 clk = ~clk;  // 100MHz 클록 생성 (10ns 주기)

    integer i;
    initial begin
        #10;
        reset_p = 0; // 리셋 해제
        
        // 1. DUT가 DHT11에 start 신호를 내릴 때까지 대기
        
        wait(!dht11_data);   // LOW 신호(18ms) 들어올 때까지 대기 (시작 신호)
        wait(dht11_data);    // HIGH(20us) 상태가 되길 대기

        // 2. DHT11 응답 시퀀스 시뮬레이션
        
        #20_000;             // 20us HIGH 후
        dout = 0; wr = 1;    // 80us LOW 출력 (DHT11 응답)
        #80_000;
        wr = 0;              // high impedance(입력)
        #80_000;             // 80us HIGH(입력모드; DUT에서 읽기 준비)
        wr = 1;              // 다시 출력모드로 변경

        // 3. 40비트 데이터 전송 (각 비트: LOW→HIGH→(길이에 따라 0/1 판별))
        
        for (i=0;i<40;i=i+1)begin
            dout = 0; #50000; // 50us LOW
            dout = 1;
            if(data[39-i]) #70000; // '1'이면 70us HIGH
            else #27000;           // '0'이면 27us HIGH
        end

        // 4. 데이터 송신 끝 (마지막 신호)
        dout = 0; wr = 1; #10
        wr = 0; #1000;
        $stop;   // 시뮬레이션 정지
    end

endmodule

사용 코드_ XDC

## This file is a general .xdc for the Basys3 rev B board
## To use it in a project:
## - uncomment the lines corresponding to used pins
## - rename the used ports (in each line, after get_ports) according to the top level signal names in the project

## Clock signal
set_property -dict { PACKAGE_PIN W5   IOSTANDARD LVCMOS33 } [get_ports clk]
create_clock -add -name sys_clk_pin -period 10.00 -waveform {0 5} [get_port clk]                                                                                   s clk]

## Switches
set_property -dict { PACKAGE_PIN V16   IOSTANDARD LVCMOS33 } [get_ports reset_p]

## LEDs
set_property -dict { PACKAGE_PIN U16   IOSTANDARD LVCMOS33 } [get_ports {debug_led[0]}]
set_property -dict { PACKAGE_PIN E19   IOSTANDARD LVCMOS33 } [get_ports {debug_led[1]}]
set_property -dict { PACKAGE_PIN U19   IOSTANDARD LVCMOS33 } [get_ports {debug_led[2]}]
set_property -dict { PACKAGE_PIN V19   IOSTANDARD LVCMOS33 } [get_ports {debug_led[3]}]
set_property -dict { PACKAGE_PIN W18   IOSTANDARD LVCMOS33 } [get_ports {debug_led[4]}]
set_property -dict { PACKAGE_PIN U15   IOSTANDARD LVCMOS33 } [get_ports {debug_led[5]}]
set_property -dict { PACKAGE_PIN U14   IOSTANDARD LVCMOS33 } [get_ports {debug_led[6]}]
set_property -dict { PACKAGE_PIN V14   IOSTANDARD LVCMOS33 } [get_ports {debug_led[7]}]
set_property -dict { PACKAGE_PIN V13   IOSTANDARD LVCMOS33 } [get_ports {debug_led[8]}]
set_property -dict { PACKAGE_PIN V3    IOSTANDARD LVCMOS33 } [get_ports {debug_led[9]}]
set_property -dict { PACKAGE_PIN W3    IOSTANDARD LVCMOS33 } [get_ports {debug_led[10]}]
set_property -dict { PACKAGE_PIN U3    IOSTANDARD LVCMOS33 } [get_ports {debug_led[11]}]
set_property -dict { PACKAGE_PIN P3    IOSTANDARD LVCMOS33 } [get_ports {debug_led[12]}]
set_property -dict { PACKAGE_PIN N3    IOSTANDARD LVCMOS33 } [get_ports {debug_led[13]}]
set_property -dict { PACKAGE_PIN P1    IOSTANDARD LVCMOS33 } [get_ports {debug_led[14]}]
set_property -dict { PACKAGE_PIN L1    IOSTANDARD LVCMOS33 } [get_ports {debug_led[15]}]


##7 Segment Display
set_property -dict { PACKAGE_PIN W7   IOSTANDARD LVCMOS33 } [get_ports {seg_7[7]}]
set_property -dict { PACKAGE_PIN W6   IOSTANDARD LVCMOS33 } [get_ports {seg_7[6]}]
set_property -dict { PACKAGE_PIN U8   IOSTANDARD LVCMOS33 } [get_ports {seg_7[5]}]
set_property -dict { PACKAGE_PIN V8   IOSTANDARD LVCMOS33 } [get_ports {seg_7[4]}]
set_property -dict { PACKAGE_PIN U5   IOSTANDARD LVCMOS33 } [get_ports {seg_7[3]}]
set_property -dict { PACKAGE_PIN V5   IOSTANDARD LVCMOS33 } [get_ports {seg_7[2]}]
set_property -dict { PACKAGE_PIN U7   IOSTANDARD LVCMOS33 } [get_ports {seg_7[1]}]

set_property -dict { PACKAGE_PIN V7   IOSTANDARD LVCMOS33 } [get_ports seg_7[0]]

set_property -dict { PACKAGE_PIN U2   IOSTANDARD LVCMOS33 } [get_ports {com[0]}]
set_property -dict { PACKAGE_PIN U4   IOSTANDARD LVCMOS33 } [get_ports {com[1]}]
set_property -dict { PACKAGE_PIN V4   IOSTANDARD LVCMOS33 } [get_ports {com[2]}]
set_property -dict { PACKAGE_PIN W4   IOSTANDARD LVCMOS33 } [get_ports {com[3]}]


##Buttons
set_property -dict { PACKAGE_PIN U18   IOSTANDARD LVCMOS33 } [get_ports btn[0]]
set_property -dict { PACKAGE_PIN T18   IOSTANDARD LVCMOS33 } [get_ports btn[1]]
set_property -dict { PACKAGE_PIN W19   IOSTANDARD LVCMOS33 } [get_ports btn[2]]
set_property -dict { PACKAGE_PIN T17   IOSTANDARD LVCMOS33 } [get_ports btn[3]]
set_property -dict { PACKAGE_PIN U17   IOSTANDARD LVCMOS33 } [get_ports btn[4]]


##Pmod Header JA
set_property -dict { PACKAGE_PIN J1   IOSTANDARD LVCMOS33 } [get_ports {dht11_data}];#Sch name = JA1

## SPI configuration mode options for QSPI boot, can be used for all designs
set_property BITSTREAM.GENERAL.COMPRESS TRUE [current_design]
set_property BITSTREAM.CONFIG.CONFIGRATE 33 [current_design]
set_property CONFIG_MODE SPIx4 [current_design]

Simulation_ Testbench

  1. 초기 시퀀스
    • 초기 구간에서는 DHT11 프로토콜에 따라 start, response, data 전송 순으로 신호가 나타납니다.
    • dout, wr 신호를 통해 테스트벤치가 DHT11 역할을 하며 신호를 내보냄.
  2. 데이터 수신
    • 여러 번의 0/1 신호(0은 짧은 High, 1은 긴 High)를 40번 반복하며
      실제 DHT11 프로토콜로 값을 FPGA에 전달.
  3. 결과 확인
    • 전송이 끝난 후, humidity(습도)와 temperature(온도) 신호가 목표값(50, 19)을 확인 할 수 있다.
    • Radix 설정에서 10진수로 바꿔주면 humidity(습도)와 temperature(온도) 신호가 목표값(80, 25)으로 갱신됨.
    • 이 값들은 원래 테스트 데이터(data 파라미터)와 일치해야 정상임을 확인할 수 있다.
  4. Bitstream 실행
    • Testbench가 정상임을 확인한 이후, Top module을 bitstream 실행시켜 보드의 동작을 확인한다.


Schematic_RTL_Analysis

Schematic_Synthesis

보드 연결

동작 영상

결과

   FPGA가 DHT11에 데이터 요청

  • Top 모듈(예: dht11_top)이 일정 주기로 dht11_cntr 하위 모듈을 구동힌디.
  • 하위 FSM(dht11_cntr)이 18ms LOW, 20us HIGH 신호를 발생시켜 DHT11 센서에 데이터 요청한다.

   DHT11 센서가 응답

  • DHT11 센서가 응답 신호(80us LOW → 80us HIGH)를 보낸 뒤,
    총 40비트(습도 상위 8, 하위 8, 온도 상위 8, 하위 8, 체크섬 8비트)를 전송한다.

   FPGA가 데이터 수신 및 해석

  • dht11_cntr FSM이 센서로부터 40비트 데이터를 타이밍에 맞춰 읽는다.
  • 수신한 데이터는 내부 shift register(temp_data)에 저장된다.
  • 데이터 수신이 끝나면 체크섬을 계산하여 데이터 유효성을 확인한다.
  • 정상적인 데이터라면 humidity, temperature 신호에 각각 습도(상위 8비트), 온도(상위 8비트) 값을 출력하는 것을 확인 할 수 있다.

   출력 및 모니터링

  • 이 값들은 top 모듈에서 7세그먼트, LED, 또는 디버깅 신호로 출력되어 Basys_3 fnd로 습도와 온도의 값을 확인 할 수 있다.

동작 결과

  • 센서가 3초마다 습도와 온도의 값을 받아 7Segment로 출력되며, DHT11 FSM의 상태가 바뀔 떄마다 해당 상태를 led로 확인 할 수 있다.
  • 손으로 센서를 감쌌을 때 온도와 습도가 증가 하는 것을 확인 할 수 있다.
  • 측정 후 Switch 조작으로  reset을 시킬 수 있다.