[온습도 센서의 개요 및 Data Sheet]
DHT11 온습도 센서로 온습도를 센싱하여 FND로 출력하는 기능을 구현해보도록 하자.
📌 Typical Application
📌 DHT11의 통신 프로토콜
처음에 MCU가 Low high보내주면, 받았다는 신호로 DHT가 Low High하나 보내고 데이터를 보내기 시작한다.
DHT의 응답이 완료되면 Data Transmit이 시작되고 총 40bit를 보낸 뒤 종료되고
1회의 통신 프로세스는 약 4ms이며, 데이터는 정수와 소수 부분으로 구성된다.
Process와 Process 사이의 IDLE 구간은 약 3초 정도의 대기 시간을 줘야 DHT11이 무리없이 통신할 수 있다.
<데이터 구성>
"RH 정수 부분(8bit) + RH 소수 부분(8bit)" + "T 정수 부분(8bit) + T 소수 부분(8bit)" + "check sum(8bit)"
위와 같이 데이터 부분 + check sum으로 구성되고
정상적으로 데이터가 전송되었다면, "데이터 부분의 합 = check sum" 이 된다.
- check sum : Error가 있는지 확인하기 위해서 보내는 data(8bit)
- RH : 습도
- T : 온도
FSM(유한 상태 머신)
링카운터같은 것.
실제로 회로는 병렬적인데
통신같은 기능을 할 때(start bit나오고 data받고 stop bit나가서 종료)는 순차적으로 동작해야한다.
예를 들어,
링카운터 0000주면 A회로 동작, 0001주면 B 회로 동작
즉, 회로의 sequential한 동작을 부여해줄 때, FSM을 사용한다.
우리는 링카운터 대신, parameter로 define한 뒤 case문으로 순서를 부여할 것이다.
Start Signal from MCU to DHT
Response Signal from DHT to MCU
※ 주황색 마킹(○)
: High를 주는 방식을 임피던스를 주는 방식을 사용했고,
풀업으로 연결되어 있기때문에 둥글게 High가 된것.
-> 임피던스를 주면, 신호와는 단절되지만 풀업저항으로 연결되어 있기 때문에
연결 끊으면 High(1)가 연결된다.
① State는 IDLE 상태로 유지되고 있다가
MCU가 Low(18ms), High(20~40us)의 Start Signal을 보낸다.
② Start Signal을 받는 DHT는 Low(80us), High(80us)의
Response Signal을 보내고 Data Transmit이 시작된다.
Data Transmit
Data Transmit 이 시작된 뒤,
Pos edge가 한 번 발생하고 Neg edge를 기다리는 모드로 진입한다.
이떄, Neg edge를 기다리는 시간이 26~28us이면 0을, 70us이면 1을 Shift Register에 저장한다.
러프하게 usec_count > 50us 이면, 1 / usec_count < 50us 이면, 0을 저장한다.
cf) Data transmit의 기간은 시간으로 받을 수는 없다
Data에 0이 많으면 짧게 걸릴 것이고 1이 많으면 길게 걸릴 것이다.
📌 Data Sheet
[통신 프로토콜 Coding]
module DHT11(
input clk, reset_p,
inout dht11_data,// 선 하나로 입출력을 다 수행해야 한다.
output reg [7:0] humidity, temperature,
output [7:0] LED_bar // For Debuging
);
// 파라미터는 상수 선언 시, 사용한다. C언어의 DEFINE처럼 쓰면 된다.
// MCU의 Start bit 출력 단계
parameter S_IDLE = 6'b000_001; // C언어랑은 달리, 다른 값으로 바꾸면 에러난다.
parameter S_LOW_18MS = 6'b000_010; // 6가지 상태가 필요하므로 6bit
parameter S_HIGH_20US = 6'b000_100; // 임피던스로 줘서 풀업으로 High 출력
// DHT의 응답 단계
parameter S_LOW_80US = 6'b001_000; // 80us동안 low를 유지.
parameter S_HIGH_80US = 6'b010_000; // 80us동안 High를 유지.
// Data 40bit 받는 단계
parameter S_READ_DATA = 6'b100_000;
// 얘네는 State machine을 따로 쓸 것이다.
// 총 40번의 아래 State가 반복되면 data 전송 끝
// 50us보다 크면 1, 작으면 0
parameter S_WAIT_PEDGE = 2'b01; // 얘가 뜨면, 카운트가 시작될 것이다. // pedge가 발생할때까지 기다리는 상태
parameter S_WAIT_NEDGE = 2'b10; // nedge가 발생할 때까지 기다리는 상태
// usec clock
reg [21:0] count_usec = 0; // reg를 0으로 초기화하면 무시되고 회로가 안 만들어져셔 상관없지만(시뮬레이션때만 적용됨), wire는 0으로 초기화하면 접지가 되어버린다.
wire clk_usec;
reg count_usec_e;
clock_usec usec_clk(.clk(clk), .reset_p(reset_p), .clk_usec(clk_usec));
// usec Counter
always @(posedge clk, posedge reset_p) begin
if(reset_p) count_usec = 0; // 리셋 누르면 출력이 0되도록 설계
else begin
// data가 들어올 때 동안만 count
if(clk_usec && count_usec_e) count_usec = count_usec + 1; // enable이 1이고, clk_usec가 들어올 때만 count++
else if(!count_usec_e) count_usec = 0;
end
end
//DHT11 data의 edge를 잡아보자
wire dht_pedge, dht_nedge;
edge_detector_n edg_dht(.clk(clk), .cp_in(dht11_data), .rst(reset_p), .p_edge(dht_pedge), .n_edge(dht_nedge)); // Edge Detector
// 상태를 기록할 상태 변수
reg [5:0] state, next_state;
reg [1:0] read_state;
// State Machine
always @(posedge clk, posedge reset_p) begin
if(reset_p) state = S_IDLE;
else state = next_state; // 매 클락마다 state를 next_state로 바꿔준다.
end
// inout은 reg로 선언하면 안된다.
reg dht11_buffer;
assign dht11_data = dht11_buffer; // 이렇게 reg 버퍼 선언해주고 assign으로 연결해주면 된다.
// DHT11의 통신 프로토콜
reg [39:0] temp_data; // 여기다 데이터를 저장할 것이다.
reg [5:0] data_count; // 40회 반복을 위한 카운트 변수
always @(posedge clk, posedge reset_p) begin
if(reset_p) begin
count_usec_e = 0;
next_state = S_IDLE;
dht11_buffer = 1'bz; // 자신이 속한 always문 안에서 초기화해줘야된다.
read_state = S_WAIT_PEDGE; // 초기 상태는 Pos edge를 기다리는 상태이다.
data_count = 0; // data_count를 초기화해주지 않으면, Z에 계속 1을 더하는 꼴이 된다.
end
else begin
case(state) // Verilog에서는 case로 시작하고 endcase로 끝난다.
S_IDLE : begin // 3초 흘려보내기(프로세스와 프로세스 사이에 약 3초의 대기 시간을 주어야 원활한 통신이 가능하다.)
if(count_usec < 22'd3_000_000) begin
// if(count_usec < 22'd3_0) begin // 시뮬레이션용으로 대기 시간 줄임
count_usec_e = 1; // 매 usec마다 count_usec++됨, 3초를 세야 하니까 1준다.
dht11_buffer = 1'bz; // 3초 동안 아무것도 안 할 것이기 때문에, inout로 Z를 내보내줘야 한다.
end
else begin // 3초가 지났다면,
count_usec_e = 0; // count_usec reset
next_state = S_LOW_18MS; // 끝났으면 다음 상태로 넘어간다.
end
end
S_LOW_18MS: begin // Low로 18ms 유지
if(count_usec < 22'd19_999) begin // 22'd17_999 = 18ms, 하지만 최소 18ms이므로 여유있게 20ms주자
count_usec_e = 1;
dht11_buffer = 0; // 0을 18ms동안 출력해야 하므로.
end
else begin
count_usec = 0;
next_state = S_HIGH_20US; // 끝났으면 다음 상태로 넘어간다.
dht11_buffer = 1'bz;
end
end
S_HIGH_20US : begin // 응답을 20~40us 동안 기다려라~
if(count_usec < 20) begin
dht11_buffer = 1'bz; // 응답할 때까지 z준다.(풀업 연결되어있으므로 실제로는 1주는 꼴)
count_usec_e = 1;
end
else if(count_usec < 40) begin
dht11_buffer = 1'bz;
if(dht_nedge) begin
next_state = S_LOW_80US;
count_usec_e = 0;
end
end
else begin // 40us가 지났는데도 falling edge가 안 나오면,
next_state = S_IDLE; // 처음으로 다시 돌아가고
count_usec_e = 0; // counting을 멈춘다.
end
end
S_LOW_80US : begin // 약 80us동안 High를 기다려라.(여유를 좀 줘도 된다.)
// if(count_usec < 80) begin
if(count_usec < 90) begin // 여유있게 좀 더 준다.
if(dht_pedge) begin // 1이 들어오면
next_state = S_HIGH_80US; // 다음 단계로
count_usec_e = 0; // 카운팅 중단
end
else begin // 80us 이내지만 1이 안 뜨면
next_state = S_LOW_80US; // 현재 state를 유지
count_usec_e = 1; // count는 계속
end
end
else begin // 80us가 지나도 응답이 없다면,
next_state = S_IDLE; // 처음으로 돌아가서 Start bit 다시 보내고 통신을 재시작해라.
count_usec_e = 0;
end
end
S_HIGH_80US : begin
if(count_usec < 80) begin // 80us 안에
if(dht_nedge) begin // nedge가 발생했다면,
next_state = S_READ_DATA; 다음 단계로 이동
count_usec_e = 0; // 카운팅 중단
end
else begin // 아직 nedge 발생 안 했다면,
next_state = S_HIGH_80US; // 일단 계속 기다려
count_usec = 1; // 카운팅하면서 기다려
end
end
else begin // 80us가 넘었는데도 nedge 발생 안 하면,
next_state = S_IDLE; // 처음으로 돌아가서 start bit 다시 받는다.
count_usec = 0; 카운팅 중단
end
end
S_READ_DATA : begin // 이거 40번 반복하여 Data Transmit
case(read_state)
S_WAIT_PEDGE : begin // Reponse signal 이후 pedge 기다리는 상태
if(dht_pedge) begin // pedge 받으면,
read_state = S_WAIT_NEDGE; 다음 단계로 이동
count_usec_e = 1; // pedge 들어오면서부터 count 시작
end
else begin // pedge안 나오면,
count_usec_e = 0; // counting 안 하고 있는 상태 유지
end
end
// 여기서 데이터 저장
S_WAIT_NEDGE : begin // pedge가 들어올 때까지 시간을 count하다가 50ms보다 크면 1, 50ms보다 작으면 0이며, 이 값을 시프트 레지스터에 저장할 것이다.
if(dht_nedge) begin
data_count = data_count + 1;
read_state = S_WAIT_PEDGE;
if(count_usec < 50) begin // 0 저장
temp_data = {temp_data[38:0], 1'b0}; // 최하위비트에 하나씩 밀어넣는다.
end
else begin // 1 저장
temp_data = {temp_data[38:0], 1'b1}; // 최하위비트에 하나씩 밀어넣는다.
end
end
else begin
read_state = S_WAIT_NEDGE; // 안들어오면 현 상태 유지
count_usec_e = 1; // 유지하면서 계속 count
end
end
default : read_state = S_WAIT_PEDGE; // 디폴트는 pedge 대기 상태
endcase
if(data_count >= 40) begin // 40개의 data를 다 받으면
data_count = 0;
next_state = S_IDLE; // 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; // 디폴트는 IDLE
endcase
end
end
endmodule