Verilog 실습을 진행하면서 DHT11 온습도센서를 다루는 수업을 진행하고 있다.
보드는 basys3을 사용하였다.
DHT11은 20~90%의 습도와 0~50℃을 측정할 수 있으며 오차는 습도 ±5% 온도 ±2℃ 정도이다.
1번 PIN으로 전원을 받고 2번 PIN으로 데이터를 3번 PIN은 GND와 연결한다.
DHT11은 3.3V ~ 5V에서 작동한다.
.
DHT11은 Signal 핀의 Level을 통해 통신을 위와 같이 수행한다.
1. MCU가 시작신호를 보내면 DHT11은 MCU가 시작신호를 완료하는걸 기다린다.
2. DHT11이 상대 습도 및 온도 정보를 포함하는 40bit 데이터의 응답 신호를 MCU에 보낸다.
3. MCU의 Start signal이 없다면 MCU는 응답 신호를 DHT로부터 받을 수 없다.
4. MCU와 DHT11이 서로 통신을 시작하면, MCU의 프로그램은 Data Single_bus 전압레벨을 high에서 low로 설정한다.
5. 이 과정은 최소 18ms이며, DHT11이 MCU의 신호를 감지한 후 MCU가 전압을 높이며 DHT11의 응답을 20~40us 기다린다.
6. DHT11이 Start signal을 감지하고, 최소 80us 동안 low-voltage-level 응답 신호를 보낸다.
7. 그 후 DHT11프로그램은 Data Single-bus 전압을 low - high로 설정하고 데이터 전송을 위한 준비를 80us동안 유지한다.
8. MCU와 DHT11이 서로 통신을 시작하면, MCU의 프로그램은 Data Single_bus 전압레벨을 high에서 low로 설정한다.
9. 이 과정은 최소 18ms이며, DHT11이 MCU의 신호를 감지한 후 MCU가 전압을 높이며 DHT11의 응답을 20~40us 기다린다.
10. DHT11이 Start signal을 감지하고, 최소 80us 동안 low-voltage-level 응답 신호를 보낸다.
11. 그 후 DHT11프로그램은 Data Single-bus 전압을 low - high로 설정하고 데이터 전송을 위한 준비를 80us동안 유지한다.
12. Data Single-bus가 low voltage level일 때, DHT11은 응답 신호를 보낸다. DHT11이 응답 신호를 보내면 전압을 끌어올려 80us동안 유지하고 데이터 전송을 준비한다.
13. 비트전송시 50us LOW후 26~28us HIGH는 0bit로 50us LOW후 70us HIGH는 1bit로 간주하여 데이터를 전송한다.
14. 데이터의 끝에서는 50us LOW후 계속 HIGH상태를 유지한다.
Verilog DHT11 코드
`timescale 1ns / 1ps
module DHT11(
input clk,
input reset_n,
output reg [7:0] humidity,
output reg [7:0] temperature,
inout dht11_data,
output reg [7:0] led
);
//베릴로그는 회로기 때문에 순서가 있는 송수신 같은것은 못함
//스타트비트를 보내고 데이터를 읽고 이런 순서. 이럴 때 fsm을 사용한다.
parameter S_IDLE = 3'b000;
parameter S_LOW_18MS = 3'b001;
parameter S_HIGH_20US = 3'b010;
parameter S_LOW_80US = 3'b011;
parameter S_HIGH_80US = 3'b100;
parameter S_READ_DATA = 3'b101;
parameter S_WAIT_PEDGE = 3'b000;
parameter S_WAIT_NEDGE = 3'b001;
reg [21:0] count_usec;
reg count_usec_e = 0;
reg [2:0] state = S_IDLE, next_state = S_IDLE, read_state = S_WAIT_PEDGE;
reg dht_buffer;
reg [39:0] temp_data;
reg [5:0] data_count = 0;
wire dht_nedge, dht_pedge;
assign dht11_data = dht_buffer;
clock_usec usec_clk(.clk(clk), .reset_n(1), .clk_usec(clk_usec));
always @(negedge clk_usec or negedge reset_n)begin
if(!reset_n) count_usec = 0;
else if (count_usec_e) count_usec = count_usec + 1;
else count_usec = 0;
end
always @(negedge clk_usec or negedge reset_n)begin
if (!reset_n) state = S_IDLE;
else state = next_state;
end
always @(negedge clk_usec or negedge reset_n)begin
if(!reset_n)begin
count_usec_e = 0;
end
else begin
case (state)
S_IDLE : begin
if (count_usec < 22'd3000000)begin //22'd3000000
dht_buffer = 1'bz;
count_usec_e = 1;
led[0] = 1;
end
else begin
next_state = S_LOW_18MS;
count_usec_e = 0;
led = 0;
end
end
S_LOW_18MS : begin
led[1] = 1;
if (count_usec < 19999)begin
count_usec_e = 1;
dht_buffer = 0;
end
else begin
count_usec_e = 0;
next_state = S_HIGH_20US;
dht_buffer = 1'bz;
end
end
S_HIGH_20US : begin
led[2] = 1;
if (count_usec < 3)begin
dht_buffer = 1'bz;
count_usec_e = 1;
end
else if(count_usec < 40)begin
count_usec_e = 1;
dht_buffer = 1'bz;
if(dht_nedge)begin //nedge가 들어오면 다음 state로 넘어갈 수 있게
next_state = S_LOW_80US;
count_usec_e = 0;
end
else begin
next_state = S_HIGH_20US;
count_usec_e = 1;
end
end
else begin
next_state = S_IDLE;
count_usec_e = 0;
end
end
S_LOW_80US : begin
led[3] = 1;
if (count_usec < 83)begin
if(dht_pedge)begin
next_state = S_HIGH_80US;
count_usec_e = 0;
end
else begin
next_state = S_LOW_80US;
count_usec_e = 1;
end
end
else begin
next_state = S_IDLE;
count_usec_e = 0;
end
end
S_HIGH_80US: begin
led[4] =1;
if (count_usec < 100) begin
if (dht_nedge) begin
next_state = S_READ_DATA;
count_usec_e = 0;
end
else begin
next_state = S_HIGH_80US;
count_usec_e = 1;
end
end
else begin
next_state = S_IDLE;
count_usec_e = 0;
end
end
S_READ_DATA : begin
led[5] = 1;
case (read_state)
S_WAIT_PEDGE : begin
if(dht_pedge)begin
read_state = S_WAIT_NEDGE;
count_usec_e = 1;
end
else begin
count_usec_e = 0;
end
end
S_WAIT_NEDGE : begin
if (dht_nedge)begin
data_count = data_count + 1;
read_state = S_WAIT_PEDGE;
if(count_usec < 50)begin
temp_data = {temp_data[38:0], 1'b0}; //시프트 레지스터 0을 밀어넣는다
end
else begin
temp_data = {temp_data[38:0], 1'b1}; //50보다 크면 1을 저장한다
end
end
else begin
count_usec_e = 1;
read_state = S_WAIT_NEDGE;
end
end
default : read_state = S_WAIT_PEDGE;
endcase
if(data_count >= 40)begin
data_count = 0;
next_state = S_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;
endcase
end
end
edge_detect ed(.clk(clk_usec), .cp_in(dht11_data), .reset_n(1), .p_edge(dht_pedge), .n_edge(dht_nedge));
endmodule
LED를 추가한 이유는 신호가 어디까지 가는지 확인하기 위함이다.
7세그먼트 출력을 위한 코드
`timescale 1ns / 1ps
module DHT11_top(
input clk,
input btn,
inout dht11_data,
output [3:0] com_an,
output [6:0] seg,
output [7:0] led
);
wire [7:0] humidity, temperature;
wire [15:0] bcd_humi, bcd_tempr;
wire reset_n;
wire clk_160ns, clk_2560ns, clk_40us, debounced_btn;
clock_divider cd_16(.clk(clk), .sel(3), .clk_out(clk_160ns)); //160ns
clock_divider cd_256(.clk(clk_160ns), .sel(3), .clk_out(clk_2560ns)); //2560ns
clock_divider cd_4096(.clk(clk_2560ns), .sel(3), .clk_out(clk_40us)); //40us
D_flip_flop_posedge dff(.D(btn), .E(), .Q(debounced_btn));
assign reset_n = ~debounced_btn;
DHT11 dht(.clk(clk), .reset_n(reset_n), .humidity(humidity), .temperature(temperature), .dht11_data(dht11_data), .led(led));
bin_to_dec btd_humi (.bin({4'd0, humidity}), .bcd(bcd_humi));
bin_to_dec btd_tempr (.bin({4'd0, temperature}), .bcd(bcd_tempr));
FND_4digit_switcher fnd(
.value_1(bcd_tempr[3:0]),
.value_10(bcd_tempr[7:4]),
.value_100(bcd_humi[3:0]),
.value_1000(bcd_humi[7:4]),
.clk(clk),
.com_an(com_an),
.seg(seg)
);
endmodule
시뮬레이션을 위한 테스터 벤치 코드
`timescale 1ns / 1ps
module TB_DHT11();
reg clk, reset_n;
wire [7:0] humidity, temperature;
tri1 dht11_data;
//inout을 선언할 땐 reg로 받고 wire로 출력하면 돼서 괜찮다.
reg wr = 0;
reg din;
integer i;
parameter [7:0] humi_value = 8'd80;
parameter [7:0] temp_value = 8'd25;
parameter [7:0] checksum = humi_value + temp_value;
parameter [39:0] data = {humi_value, {8{1'b0}}, temp_value, {8{1'b0}}, checksum};
assign dht11_data = (wr == 1) ? din : 1'bz; //? : 조건연산자 wr을 1주면 reg(input), wr을 0주면 임피던스 (output)
//integer : 32비트 signed variable, real : 32bit 소수 variable
//time : 64bit unsigned variable, realtime : 64bit 소수 variable
//wand : 연결되어있는 값들의 and 값
//tri1 : 저항성 pullup에 의해 전원으로 연결되는 net
DHT11 DUT(.clk(clk), .reset_n(reset_n),
.humidity(humidity), .temperature(temperature),
.dht11_data(dht11_data));
initial begin
clk = 0;
reset_n = 0; #10;
end
always #5 clk = ~clk;
initial begin
#100 reset_n = 0; wr = 0; //시간 단위를 맞춰서 입력.
#200 reset_n = 1; wr = 0; //DHT11 에서 us사용해서 1000
wait(!dht11_data); //wait 은 while 문, 1일 때 아무것도 안하고 기다림 0이 될때까지
wait(dht11_data)begin din = 1;wr = 1; end //인스턴스명 . 을하면 모듈 사용(?)
#4000; din = 0; //din = 0주면 falling edge 준거, 10ns씩이니까 40us
#4000; din = 1;
#4000; din = 0;
for (i = 0; i < 40; i = i + 1)begin
if (data[39-i]) begin
#2000 din = 1;
#70000 din = 0;
end
else begin
#2000 din = 1;
#26000 din = 0;
end
end
#10000;
$finish;
end
endmodule
시뮬레이션 결과
humidity, temperature 는 설정한대로 80, 25가 나왔다.
16진수여서 50, 19가 나옴
10진수로 바꿨을 때
DHT11을 이용해 온습도를 7세그먼트를 통해 출력하였다.
1000의 자리 100의 자리는 습도, 10의 자리 1의 자리는 온도.
'Verilog > Verilog 실습' 카테고리의 다른 글
Verilog 다목적 시계 (0) | 2023.05.16 |
---|---|
Verilog HC-SR04 초음파센서 제어 (0) | 2023.05.16 |
Verilog [CPU만들기] ACC (2) | 2023.05.16 |
Verilog [CPU 만들기] PC, ALU (0) | 2023.05.09 |
Verilog ADC로 전압값을 BCD로 변환 (0) | 2023.05.04 |