목적
- 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



- 초기 시퀀스
- 초기 구간에서는 DHT11 프로토콜에 따라 start, response, data 전송 순으로 신호가 나타납니다.
- dout, wr 신호를 통해 테스트벤치가 DHT11 역할을 하며 신호를 내보냄.
- 데이터 수신
- 여러 번의 0/1 신호(0은 짧은 High, 1은 긴 High)를 40번 반복하며
실제 DHT11 프로토콜로 값을 FPGA에 전달.
- 여러 번의 0/1 신호(0은 짧은 High, 1은 긴 High)를 40번 반복하며
- 결과 확인
- 전송이 끝난 후, humidity(습도)와 temperature(온도) 신호가 목표값(50, 19)을 확인 할 수 있다.
- Radix 설정에서 10진수로 바꿔주면 humidity(습도)와 temperature(온도) 신호가 목표값(80, 25)으로 갱신됨.
- 이 값들은 원래 테스트 데이터(data 파라미터)와 일치해야 정상임을 확인할 수 있다.
- 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을 시킬 수 있다.
'Verilog_RTL 설계 > Basys_3' 카테고리의 다른 글
| [PWM]LED_128_Step (2) | 2025.07.17 |
|---|---|
| [FSM] Ultrasonic_sensor_(HC_SR04) (0) | 2025.07.16 |
| [Sequential_Circuit] edge_detector (0) | 2025.07.13 |
| [Shift_Register] PIPO_(Parallel-In Parallel-Out) (0) | 2025.07.12 |
| [Shift_Register] PISO_(Parallel-In Serial-Out) (0) | 2025.07.12 |