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