#今天也是咸鱼的一天~
因为参加Robei 杯 是要做一个机器人,先不说这个机器人具体怎么机器法,但是和外界互通的传感器肯定少不了,通过获取外界环境数据,进行处理,然后做出各种各样的功能。
先来讲个简单的模块,温湿度传感器DHT11。
DHT11介绍
DHT11是一款价格便宜,易于使用的温度湿度测量二合一传感器。它具有超小体积、极低功耗的特点。它使用单根总线与单片机进行双向的串行数据传输,信号传输距离可达20米以上。非常适用于对精度和实时性要求不高的温湿度测量场合。
本文将以DFRobot开源硬件平台的DHT11模块和DFRduino开发板来演示,讲解DHT11的驱动和使用。
DHT11电气参数
电源电压:3~5.5V(典型值:5V);
温度量程:0~50℃,误差 ±2℃;
湿度量程:20~90%RH,误差 ±5%RH;
采样周期:大于等于1秒/次。
这个传感器有三个脚,分别是VCC、GND、DATA。这是一个单数据口的传感器,数据口做双向传输使用,这个管脚在FPGA上要设置为inout
。
工作原理
DHT11使用单一总线通信,即DATA引脚和单片机连接的线。总线总是处于空闲状态和通信状态这个2个状态之间。
当单片机没有与DHT11交互时,总线处于空闲状态,在上拉电阻的作用下,处于高电平状态。
当单片机和DHT11正在通信时,总线处于通信状态,一次完整的通信过程如下:
①单片机将驱动总线的IO配置为输出模式。准备向DHT11发送数据。
②单片机将总线拉低至少18ms,以此来发送起始信号。再将总线拉高并延时20~40us,以此来代表起始信号结束。
③单片机将驱动总线的IO配置为输入模式,准备接收DHT11回传的数据。
④当DHT11检测倒单片机发送的起始信号后,就开始应答,回传采集到的传感器数据。DHT11先将总线拉低80us作为对单片机的应答(ACK),然后接着将总线拉高80us,准备回传采集到的温湿度数据。温湿度数据以固定的帧格式发送。
一帧为40个bit,而每一个bit的传输时序逻辑为:每一个bit都以50us的低电平(DHT11将总线拉低)为先导,然后紧接着DHT11拉高总线,如果这个高电平持续时间为26~28us,则代表逻辑0,如果持续70us则代表逻辑1。
看这张信号图,就可以知道模块和主控之间的通信方式。
- 上电后等待1s,以越过不稳定状态。
- IO为信号输出模式,低电平持续18ms以上。(建议20ms)
- IO电平拉高,持续20~40us。(建议30us)
- IO为信号输入模式,进入DHT响应时间。(就是模块受到了主控的信息,返回一个数据预发送信号)
- 低电平延时80us,电平拉高80us,完成DHT响应。
- 进入数据逻辑0/1发送,持续40位。
- 逻辑0,电平拉低持续50us,电平拉高持续25us。
- 逻辑1,电平拉低持续50us,电平拉高持续80us。
- 数据发送结束后,总线拉高。(此处可以至少延时1s,然后再进行下一次数据采集)
Verilog驱动代码:
// dht11
// made by 00
//time 2020.4.28
module dht11(
input clk,
input rst_n,
inout dht11,
output reg [31:0] data_valid
);
/**************parameter********************/
parameter POWER_ON_NUM = 1000_000;
parameter S_POWER_ON = 3'd0;
parameter S_LOW_20MS = 3'd1;
parameter S_HIGH_13US = 3'd2;
parameter S_LOW_83US = 3'd3;
parameter S_HIGH_87US = 3'd4;
parameter S_SEND_DATA = 3'd5;
parameter S_DEALY = 3'd6;
//reg define
reg[2:0] cur_state;
reg[2:0] next_state;
reg[20:0] count_1us;
reg[5:0] data_count;
reg[39:0] data_temp;
reg[4:0] clk_cnt;
reg clk_1M;
reg us_clear;
reg state;
reg dht_buffer;
reg dht_d0;
reg dht_d1;
wire dht_podge; //data posedge
wire dht_nedge; //data negedge
/*********************main codes*********************/
assign dht11 = dht_buffer;
assign dht_podge = ~dht_d1 & dht_d0; // catch posedge
assign dht_nedge = dht_d1 & (~dht_d0); // catch negedge
/*********************counters*****************************/
//clock with 1MHz
always @ (posedge clk or negedge rst_n) begin
if (!rst_n) begin
clk_cnt <= 5'd0;
clk_1M <= 1'b0;
end
else if (clk_cnt < 5'd24)
clk_cnt <= clk_cnt + 1'b1;
else begin
clk_cnt <= 5'd0;
clk_1M <= ~ clk_1M;
end
end
//counter 1 us
always @ (posedge clk_1M or negedge rst_n) begin
if (!rst_n)
count_1us <= 21'd0;
else if (us_clear)
count_1us <= 21'd0;
else
count_1us <= count_1us + 1'b1;
end
//change state
always @ (posedge clk_1M or negedge rst_n) begin
if (!rst_n)
cur_state <= S_POWER_ON;
else
cur_state <= next_state;
end
// state machine
always @ (posedge clk_1M or negedge rst_n) begin
if(!rst_n)
begin
next_state <= S_POWER_ON;
dht_buffer <= 1'bz;
state <= 1'b0;
us_clear <= 1'b0;
data_temp <= 40'd0;
data_count <= 6'd0;
end
else
begin
case (cur_state)
S_POWER_ON : //wait
begin
if(count_1us < POWER_ON_NUM)
begin
dht_buffer <= 1'bz;
us_clear <= 1'b0;
end
else
begin
next_state <= S_LOW_20MS;
us_clear <= 1'b1;
end
end
S_LOW_20MS: // send 20 ms
begin
if(count_1us < 20000)
begin
dht_buffer <= 1'b0;
us_clear <= 1'b0;
end
else
begin
next_state <= S_HIGH_13US;
dht_buffer <= 1'bz;
us_clear <= 1'b1;
end
end
S_HIGH_13US: // Hign 13 us
begin
if (count_1us < 20)
begin
us_clear <= 1'b0;
if(dht_nedge)
begin
next_state <= S_LOW_83US;
us_clear <= 1'b1;
end
end
else
next_state <= st_delay;
end
S_LOW_83US:
begin
if(dht_podge)
next_state <= S_HIGH_87US;
end
S_HIGH_87US: // ready to receive data signal
begin
if(dht_nedge)
begin
next_state <= S_SEND_DATA;
us_clear <= 1'b1;
end
else
begin
data_count <= 6'd0;
data_temp <= 40'd0;
state <= 1'b0;
end
end
S_SEND_DATA: // have 40 bit
begin
case(state)
0: begin
if(dht_podge)
begin
state <= 1'b1;
us_clear <= 1'b1;
end
else
us_clear <= 1'b0;
end
1: begin
if(dht_nedge)
begin
data_count <= data_count + 1'b1;
state <= 1'b0;
us_clear <= 1'b1;
if(count_1us < 60)
data_temp <= {data_temp[38:0],1'b0}; //0
else
data_temp <= {data_temp[38:0],1'b1}; //1
end
else //wait for high end
us_clear <= 1'b0;
end
endcase
if(data_cnt == 40) //check data bit
begin
next_state <= st_delay;
if(data_temp[7:0] == data_temp[39:32] + data_temp[31:24] + data_temp[23:16] + data_temp[15:8])
data_valid <= data_temp[39:8];
end
end
S_DELAY: // after data received delay 2s
begin
if(count_1us < 2000_000)
us_cnt_clr <= 1'b0;
else
begin
next_state <= S_LOW_20MS; // send signal again
us_cnt_clr <= 1'b1;
end
end
default :
cur_state <= cur_state;
endcase
end
end
//edge
always @ (posedge clk_1M or negedge rst_n) begin
if (!rst_n) begin
dht_d0 <= 1'b1;
dht_d1 <= 1'b1;
end
else begin
dht_d0 <= dht11;
dht_d1 <= dht_d0;
end
end
endmodule
亲测可用,里头注释都是英文的原因是,我写代码的时候开的英文键盘,写不了中文,这样可以避免中文符号的错误。
实物测试
这个是准备用在Robei 杯的比赛上的,但是现在还在纠结用Xilinx还是 Altera的板子,因为如果要用RISC-V架构的话,就一定得用Xilinx,调它的硬核。我感觉我还真的做不出来,所以打算先把结构搭出来,然后再试试看能不能用RISC-V 来实现。