온/습도 센서
이번에는 온습도 센서를 사용할려고자 한다.
온습도 센서는 DHT11 모듈을 사용하여 진행할 예정이며, 모듈의 회로 구성은 아래와 같다.
온/습도 센서의 총 동작도는 아래와 같다. MCU 시그널이 주어진 후 센서의 신호가 응답하는 방식으로 진행된다.
아래 그림은 MCU가 시작 신호를 보낸 후에 DHT 센서의 응답 과정을 나타낸다. 온습도센서가 MCU의 신호를 감지 후, MCU를 감지하려면 최소 18ms의 시간이 소요된다. 그러고 난 후 20~40us만큼 센서의 응답을 기다린다.
DHT가 시작 신호를 감지하면 저전압레벨의 응답신호를 80us 간 보낸다. 응답 신호를 보낸 후, 센서는 데이터 전송을 위해서 데이터 단일 버스 전압레벨을 저에서 고로 설정하고 80us동안 유지한다. 그다음 모듈은 MCU로 데이터를 전송할 때, 각 데이터 비트는 50us 동안의 저전압 레벨로 시작하며, 그 후 고전압 레벨 신호의 길이가 0 또는 1 데이터 비트를 결정한다.
마지막 데이터 비트를 전송 후 센서는 전압 레벨을 다시 내리고 50us동안 유지한 후, 저항을 통하여 단일 버스 전압 레벨을 자유 상태로 되돌린다.
위 그림을 통하여 센서가 MCU와 통신하기 위한 단계와 각 신호의 시간적 특성을 알 수 있었다.
아래는 온습도센서의 컨트롤러 코드를 나타낸다.
여섯 경우의 상태를 나타내기 위해 아래와 같이 parameter를 선언하였다.
parameter S_IDLE = 6'b00_0001;
parameter S_LOW_18MS = 6'b00_0010;
parameter S_HIGH_20US = 6'b00_0100;
parameter S_LOW_80US = 6'b00_1000;
parameter S_HIGH_80US = 6'b01_0000;
parameter S_READ_DATA = 6'b10_0000;
상태는 여섯 경우이기 때문에 6비트의 state를 선언하였고, 2비트의 읽음상태를 추가하였다.
reg [5:0] state, next_state;
reg [1:0] read_state;
다음은 스테이트 머신에 대한 동작을 의미한다. 리셋이 발생하였을 경우, 처음 상태인 IDLE상태로 되돌아가며, 그 외의 경우에는 다음 단계로 이동하는 것을 의미한다.
// State machine
always @(negedge clk or posedge reset_p)begin
if(reset_p) state = S_IDLE;
else state = next_state;
end
온도 데이터를 저장할 40비트의 temp를 선언하였다.
reg [39:0] temp_data;
다음은 온습도 센서의 동작을 나타내는 코드이다.
리셋 신호가 1일시 초기상태로 되돌아간다. 초기상태 경우에는 state는 IDLE상태이며, temp_data와 data_count를 0으로 초기화시킨다.
그 외의 경우는 현재 상태에 따라 동작을 수행한다.
IDLE상태인 경우, count_usec이 10,000 미만인 동안에는 count_usec_e 신호를 1로 설정하며 그 외의 경우에는 count_usec_e 신호를 0으로 설정하고 다음 상태인 LOW_18MS로 설정한다.
LOW_18MS 상태에는 count_usec이 20,000 미만인 경우에는 buffer를 0으로, count_usec_e 신호를 1로 설정한다.
그 외의 경우에는 count_usec_e 신호를 0으로 설정 후, 다음 단계로 넘어간다.
READ_DATA 상태 이전까지는 이전의 동작을 반복하지만, READ_DATA 상태에서는 데이터 읽기 상태에 따라 다른 동작을 수행한다.
WAIT_PEDGE 상태의 경우 dht_pedge가 발생한 경우, 다음 read_state를 WAIT_NEDGE로 설정한다.
dht_nedge가 발생한 경우, count_usec이 45 미만일 때에는 temp_data의 하위 비트에 1'b0를 추가한다. 그 외의 경우에는 temp_data 하위 비트에 1'b1을 추가한다.
그다음 data 카운트를 1씩 증가시키며, 다음 read_state를 WAIT_PEDGE로 설정한다. 그 외의 경우에는 count_usec_e를 1로 설정한다.
만일, data 카운트가 40 이상인 경우 data 카운트를 0으로 초기화한 후 초기상태로 설정하며, 습도 변수에 temp_data의 상위 비트를 할당하며, 온도 변수에는 중간 비트를 할당한다.
always @(posedge clk or posedge reset_p)begin
if(reset_p)begin
next_state = S_IDLE;
read_state = S_WAIT_PEDGE;
temp_data = 0;
data_count = 0;
end
else begin
case(state)
S_IDLE:begin
if(count_usec < 22'd10_000)begin //22'd3_000_000
count_usec_e = 1;
dht11_buffer = 'bz;
end
else begin
count_usec_e = 0;
next_state = S_LOW_18MS;
end
end
S_LOW_18MS:begin
if(count_usec < 22'd20_000)begin
dht11_buffer = 0;
count_usec_e = 1;
end
else begin
count_usec_e = 0;
next_state = S_HIGH_20US;
end
end
S_HIGH_20US:begin
if(count_usec < 22'd20)begin
count_usec_e = 1;
dht11_buffer = 'bz;
end
else if(dht_nedge)begin
count_usec_e = 0;
next_state = S_LOW_80US;
end
end
S_LOW_80US:begin
if(dht_pedge)begin
next_state = S_HIGH_80US;
end
end
S_HIGH_80US:begin
if(dht_nedge)begin
next_state = S_READ_DATA;
end
end
S_READ_DATA:begin
case(read_state)
S_WAIT_PEDGE:begin
if(dht_pedge) read_state = S_WAIT_NEDGE;
count_usec_e = 0;
end
S_WAIT_NEDGE:begin
if(dht_nedge) begin
if(count_usec < 45)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
end
endcase
if(data_count >= 40)begin
data_count = 0;
next_state = S_IDLE;
humidity = temp_data[39:32];
temperature = temp_data[23:16];
end
end
endcase
end
end
위의 코드들을 종합하며 아래와 같은 dht11 컨트롤러 코드를 구현하였다.
module dht11_cntr(
input clk, reset_p,
inout dht11_data,
output reg [7:0] humidity, temperature);
parameter S_IDLE = 6'b00_0001;
parameter S_LOW_18MS = 6'b00_0010;
parameter S_HIGH_20US = 6'b00_0100;
parameter S_LOW_80US = 6'b00_1000;
parameter S_HIGH_80US = 6'b01_0000;
parameter S_READ_DATA = 6'b10_0000;
parameter S_WAIT_PEDGE = 2'b01;
parameter S_WAIT_NEDGE = 2'b10;
reg [5:0] state, next_state;
reg [1:0] read_state;
wire clk_usec;
clock_div_100 usec_clk(.clk(clk), .reset_p(reset_p), .clk_div_100_nedge(clk_usec));
reg [21:0] count_usec;
reg count_usec_e;
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
// State machine
always @(negedge clk or posedge reset_p)begin
if(reset_p) state = S_IDLE;
else state = next_state;
end
reg dht11_buffer;
assign dht11_data = dht11_buffer;
wire dht_pedge, dht_nedge;
edge_detector_p ed(.clk(clk), .reset_p(reset_p), .cp(dht11_data), .n_edge(dht_nedge), .p_edge(dht_pedge));
reg [39:0] temp_data;
reg [5:0] data_count;
always @(posedge clk or posedge reset_p)begin
if(reset_p)begin
next_state = S_IDLE;
read_state = S_WAIT_PEDGE;
temp_data = 0;
data_count = 0;
end
else begin
case(state)
S_IDLE:begin
if(count_usec < 22'd10_000)begin //22'd3_000_000
count_usec_e = 1;
dht11_buffer = 'bz;
end
else begin
count_usec_e = 0;
next_state = S_LOW_18MS;
end
end
S_LOW_18MS:begin
if(count_usec < 22'd20_000)begin
dht11_buffer = 0;
count_usec_e = 1;
end
else begin
count_usec_e = 0;
next_state = S_HIGH_20US;
end
end
S_HIGH_20US:begin
if(count_usec < 22'd20)begin
count_usec_e = 1;
dht11_buffer = 'bz;
end
else if(dht_nedge)begin
count_usec_e = 0;
next_state = S_LOW_80US;
end
end
S_LOW_80US:begin
if(dht_pedge)begin
next_state = S_HIGH_80US;
end
end
S_HIGH_80US:begin
if(dht_nedge)begin
next_state = S_READ_DATA;
end
end
S_READ_DATA:begin
case(read_state)
S_WAIT_PEDGE:begin
if(dht_pedge) read_state = S_WAIT_NEDGE;
count_usec_e = 0;
end
S_WAIT_NEDGE:begin
if(dht_nedge) begin
if(count_usec < 45)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
end
endcase
if(data_count >= 40)begin
data_count = 0;
next_state = S_IDLE;
humidity = temp_data[39:32];
temperature = temp_data[23:16];
end
end
endcase
end
end
endmodule
테스트 벤치 코드를 통해 위의 코드가 정상적으로 실행이 되는지 확인해 보자.
테스트 벤치 코드는 아래의 코드와 같다.
습도와 온도의 값은 임의의 값으로 설정하여 진행하였다.
코드의 초반에서 tri1이라는 것을 확인할 수 있는데, 이는 pull up wire를 의미한다. 반대로 tri0인 경우에는 pull down wire를 의미한다.
module tb_dht11_cntr();
parameter [7:0] humi_data = 8'd80;
parameter [7:0] tmpr_data = 8'd30;
parameter [7:0] check_sum = humi_data + tmpr_data;
parameter [39:0] data = {humi_data, 8'b0, tmpr_data, 8'b0, check_sum};
reg clk, reset_p;
wire [7:0] humidity, temperature;
tri1 dht11_data; // tri1 = pull up wire, tri0 = pull down wire
reg data_buffer, wr_en;
assign dht11_data = wr_en ? data_buffer : 'bz;
dht11_cntr DUT(clk, reset_p, dht11_data, humidity, temperature);
initial begin
clk = 0;
reset_p = 1;
wr_en = 0;
end
always #5 clk = ~clk;
integer i;
initial begin
#10;
reset_p = 0; #10;
wait(!dht11_data); // "wait" is wait for to be true "()"
wait(dht11_data);
#20_000; // 20usec
data_buffer = 0; wr_en = 1; #80_000;
wr_en = 0; #80_000;
for(i = 0; i < 40; i = i + 1)begin
wr_en = 1; data_buffer = 0; #50_000;
data_buffer = 1;
if(data[39-i]) #70_000;
else #29_000;
end
wr_en = 1; data_buffer = 0; #10;
wr_en = 0; #10_000;
$finish;
end
endmodule
테스트 벤치 코드를 시뮬레이션하면 아래와 같은 파형이 나오는 것을 확인할 수 있다.
테스트 벤치 결과 출력이 잘 되는 것을 확인하였으니, 보드로 옮겨 실행을 해보자.
보드로 옮길 때에는 몇 줄의 코드를 수정하여 진행하였다.
임의로 설정한 온도와 습도의 값을 없앤 후, 코드의 마지막 부분에 온도와 습도 부분과 그에 대한 소수부분도 선언하여 진행하였다.
module dht11_cntr(
input clk, reset_p,
inout dht11_data,
output reg [7:0] humidity, temperature,
output [15:0] led_debug);
parameter S_IDLE = 6'b00_0001;
parameter S_LOW_18MS = 6'b00_0010;
parameter S_HIGH_20US = 6'b00_0100;
parameter S_LOW_80US = 6'b00_1000;
parameter S_HIGH_80US = 6'b01_0000;
parameter S_READ_DATA = 6'b10_0000;
parameter S_WAIT_PEDGE = 2'b01;
parameter S_WAIT_NEDGE = 2'b10;
reg [5:0] state, next_state;
reg [1:0] read_state;
assign led_debug[5:0] = state;
wire clk_usec;
clock_div_100 usec_clk(.clk(clk), .reset_p(reset_p), .clk_div_100_nedge(clk_usec));
reg [21:0] count_usec;
reg count_usec_e;
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
// State machine
always @(negedge clk or posedge reset_p)begin
if(reset_p) state = S_IDLE;
else state = next_state;
end
reg dht11_buffer;
assign dht11_data = dht11_buffer;
wire dht_pedge, dht_nedge;
edge_detector_p ed(.clk(clk), .reset_p(reset_p), .cp(dht11_data), .n_edge(dht_nedge), .p_edge(dht_pedge));
reg [39:0] temp_data;
reg [5:0] data_count;
always @(posedge clk or posedge reset_p)begin
if(reset_p)begin
next_state = S_IDLE;
read_state = S_WAIT_PEDGE;
temp_data = 0;
data_count = 0;
end
else begin
case(state)
S_IDLE:begin
if(count_usec < 22'd3_000_000)begin
count_usec_e = 1;
dht11_buffer = 'bz;
end
else begin
count_usec_e = 0;
next_state = S_LOW_18MS;
end
end
S_LOW_18MS:begin
if(count_usec < 22'd20_000)begin
dht11_buffer = 0;
count_usec_e = 1;
end
else begin
count_usec_e = 0;
next_state = S_HIGH_20US;
dht11_buffer = 'bz;
end
end
S_HIGH_20US:begin
count_usec_e = 1;
if(count_usec > 22'd100_000)begin
next_state = S_IDLE;
count_usec_e = 0;
end
if(dht_nedge)begin
count_usec_e = 0;
next_state = S_LOW_80US;
end
end
S_LOW_80US:begin
if(count_usec > 22'd100_000)begin
next_state = S_IDLE;
count_usec_e = 0;
end
if(dht_pedge)begin
next_state = S_HIGH_80US;
count_usec_e = 0;
end
end
S_HIGH_80US:begin
count_usec_e = 1;
if(count_usec > 22'd100_000)begin
next_state = S_IDLE;
count_usec_e = 0;
end
if(dht_nedge)begin
next_state = S_READ_DATA;
count_usec_e = 0;
end
end
S_READ_DATA:begin
count_usec_e = 1;
if(count_usec > 22'd100_000)begin
next_state = S_IDLE;
count_usec_e = 0;
data_count = 0;
read_state = S_WAIT_PEDGE;
end
case(read_state)
S_WAIT_PEDGE:begin
if(dht_pedge) read_state = S_WAIT_NEDGE;
end
S_WAIT_NEDGE:begin
if(dht_nedge) begin
if(count_usec < 95)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;
count_usec_e = 0;
end
else begin
count_usec_e = 1;
end
end
endcase
if(data_count >= 40)begin
data_count = 0;
next_state = S_IDLE;
read_state = S_WAIT_PEDGE;
count_usec_e = 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
endcase
end
end
endmodule
물리적으로 구현할 Top 모듈은 아래와 같이 구현하였다.
module dht11_test_top(
input clk, reset_p,
inout dht11_data,
output [3:0] com,
output [7:0] seg_7,
output [15:0] led_debug);
wire [7:0] humidity, temperature;
dht11_cntr(.clk(clk), .reset_p(reset_p), .dht11_data(dht11_data), .humidity(humidity), .temperature(temperature), .led_debug(led_debug));
wire [15:0] humidity_bcd, temperature_bcd;
bin_to_dec bcd_humi(.bin({4'b0, humidity}), .bcd(humidity_bcd));
bin_to_dec bcd_tmpr(.bin({4'b0, temperature}), .bcd(temperature_bcd));
wire [15:0] value;
assign value = {humidity_bcd[7:0], temperature_bcd[7:0]};
fnd_cntr fnd(.clk(clk), .reset_p(reset_p), .value(value), .com(com), .seg_7(seg_7));
endmodule
동작되는 모습은 아래의 영상과 같다.
온/습도 센서의 출력파형을 오실로 스코프로 확인해 보면 아래와 같은 파형이 출력이 되는 것을 확인할 수 있다.
위 파형을 보면 아래와 같은 2진수 값이 도출된다. 맨 앞 두 개의 Pulse는 온도와 습도의 데이터를 모듈에서 내보낼 때 Posedge에 의해 발생한 파형이다. 따라서 앞의 2개의 파형을 제외한 40개의 데이터 값만 가져온다.
0 0 1 0 0 1 1 0 / 0 0 0 0 0 1 0 0 / 0 0 0 1 1 1 0 0 / 0 0 0 0 0 1 1 0 / 0 1 0 0 1 1 0 0
2진수 값을 해석해 보면 아래와 같다.
우선, 맨 뒤 8비트 값은 앞의 32비트 값을 모두 다 합한 값을 의미한다.
100110 + 100 + 11100 + 110 = 01001100
8비트씩 4개의 묶음은 각각 습도/ 습도(소수점) / 온도 / 온도(소수점)를 의미한다. 따라서 첫 번째와 세 번째 묶음의 비트를 해석하면 온도와 습도 값을 도출해낼수 있다.
첫번째 묶음 00100110 = 38 = 습도 값
세번째 묶음 00011100 = 28
'Language > Verilog' 카테고리의 다른 글
Vivado : PWM 제어 (0) | 2024.07.31 |
---|---|
Vivado : Basys3 종합 시계 프로젝트 (타이머, 스톱워치, 시계) (2) | 2024.07.30 |
Vivado : 키패드 (0) | 2024.07.25 |
Vivado : 초음파 센서 (0) | 2024.07.25 |
Vivado : Basys3 Stop watch (0) | 2024.07.18 |