FPGA开发:DTHT11温湿度模块底层驱动编写-FPGA常见问题社区-FPGA CPLD-ChipDebug

FPGA开发:DTHT11温湿度模块底层驱动编写

 

1.DTH11外形和参数

图片[1]-FPGA开发:DTHT11温湿度模块底层驱动编写-FPGA常见问题社区-FPGA CPLD-ChipDebug

2. DHT11 通信方式
1. 单总线说明
DHT11 器件采用简化的单总线通信。单总线即只有一根数据线,系统中的数据交换、
控制均由单总线完成。设备(主机或从机)通过一个漏极开路或三态端口连至该数据线,
以允许设备在不发送数据时能够释放总线。 单总线通常要求外接一个约 4.7kΩ 的上拉电
阻,这样,当总线闲置时,其状态为高电平。由于它们是主从结构,只有主机呼叫从机
时,从机才能应答,因此主机访问器件都必须严格遵循单总线序列,如果出现序列混乱,
器件将不响应主机。

2. 单总线传送数据位定义
主机与 DHT11 之间的通讯和同步采用单总线数据格式,一次传送 40 位数据,高位先
出。数据格式为: 8bit 湿度整数数据+8bit 湿度小数数据+8bit 温度整数数据+8bit 温度小数
数据+8bit 校验位。(其中湿度小数部分为 0)
校验位数据定义: “8bit 湿度整数数据+8bit 湿度小数数据+8bit 温度整数数据+8bit 温度
小数数据”8bit 校验位等于所得结果的末 8 位

图片[2]-FPGA开发:DTHT11温湿度模块底层驱动编写-FPGA常见问题社区-FPGA CPLD-ChipDebug

接收到的 40位数据为: 00110101(湿度高八位) +00000000(湿度低八位) +00011000(温度高八位)+00000100(温度低八位) +010110001(校验位)。通过计算: 00110101+00000000+00011000+00000100=01010001 与校验位相对应,结果正确。所以其湿度温度分别为:
湿度=整数+小数=00110101+0=35H=53.0%RH
温度=整数+小数=00011000+00000100=18H+04H=24℃+0.4℃=24.4℃。

3. 数据时序图
图片[3]-FPGA开发:DTHT11温湿度模块底层驱动编写-FPGA常见问题社区-FPGA CPLD-ChipDebug

4 .驱动文件:

module  dht11_ctrl
(
    input   wire        sys_clk     ,   //系统时钟,频率50MHz
    input   wire        sys_rst_n   ,   //复位信号,低电平有效
 
    inout   wire        dht11       ,   //控制总线
 
 
    output  reg [7:0]   temp_int    ,   //温度整数部分
    output  reg [7:0]   temp_float  ,   //温度小数部分
    output  reg [7:0]   humi_int    ,   //湿度整数部分
    output  reg         sign            //输出符号位,高电平显示负号
 
);
 
//********************************************************************//
//****************** Parameter and Internal Signal *******************//
//********************************************************************//
 
//parameter define
parameter   S_WAIT_1S  = 3'd1   ,   //上电等待1s状态
            S_LOW_18MS = 3'd2   ,   //主机拉低18ms,发送开始信号状态
            S_DLY1     = 3'd3   ,   //等待20-40us状态
            S_REPLY    = 3'd4   ,   //DHT11响应80us状态
            S_DLY2     = 3'd5   ,   //拉高等待80us状态
            S_RD_DATA  = 3'd6   ;   //接收数据状态
 
parameter   T_1S_DATA    = 999999 ; //1s时间计数值
parameter   T_18MS_DATA  = 17999  ; //18ms时间计数值
 
//reg define
reg         clk_1us     ;   //1us时钟,用于驱动整个模块
reg [4:0]   cnt         ;   //时钟分频计数器
reg [2:0]   state       ;   //状态机状态
reg [20:0]  cnt_us      ;   //us计数器
reg         dht11_out   ;   //总线输出数据
reg         dht11_en    ;   //总线输出使能信号
reg [5:0]   bit_cnt     ;   //字节计数器
reg [39:0]  data_tmp    ;   //读出数据寄存器
 
 
reg         dht11_d1    ;   //总线信号打一拍
reg         dht11_d2    ;   //总线信号打两拍
reg [31:0]  data        ;   //除校验位数据
reg [6:0]   cnt_low     ;   //低电平计数器
 
//wire  define
wire            dht11_fall; //总线下降沿
wire            dht11_rise; //总线上升沿
 
//********************************************************************//
//***************************** Main Code ****************************//
//********************************************************************//
 
//当使能信号为1是总线的值为DATA_out的值,为0时值为高阻态
assign  dht11  =   (dht11_en == 1 ) ? dht11_out : 1'bz;
 
//检测总线信号的上升沿下降沿
assign  dht11_rise =   (~dht11_d2) & (dht11_d1)    ;
assign  dht11_fall =   (dht11_d2)  & (~dht11_d1)   ;
 
//对dht11信号打拍
always@(posedge clk_1us or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        begin
            dht11_d1  <=  1'b0 ;
            dht11_d2  <=  1'b0 ;
        end
    else
        begin
            dht11_d1  <=  dht11    ;
            dht11_d2  <=  dht11_d1 ;
        end
 
//cnt:分频计数器
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt <=  5'b0;
    else    if(cnt == 5'd24)
        cnt <=  5'b0;
    else
        cnt <=  cnt + 1'b1;
 
//clk_1us:产生单位时钟为1us的时钟
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        clk_1us <=  1'b0;
    else    if(cnt == 5'd24)
        clk_1us <=  ~clk_1us;
    else
        clk_1us <=  clk_1us;
 
//bit_cnt:读出数据bit位数计数器
always@(posedge clk_1us or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        bit_cnt <=  6'b0;
    else    if(bit_cnt == 40 && dht11_rise == 1'b1)
        bit_cnt <=  6'b0;
    else    if(dht11_fall == 1'b1 && state == S_RD_DATA)
        bit_cnt <=  bit_cnt + 1'b1;
 
 
        
        
//状态机状态跳转
always@(posedge clk_1us or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        state   <=  S_WAIT_1S   ;
    else
        case(state)
        S_WAIT_1S:
            if(cnt_us == T_1S_DATA) //上电1s后跳入起始状态
                state   <=  S_LOW_18MS  ;
            else
                state   <=  S_WAIT_1S   ;
        S_LOW_18MS:
            if(cnt_us == T_18MS_DATA)
                state   <=  S_DLY1     ;
            else
                state   <=  S_LOW_18MS  ;
        S_DLY1:
            if(cnt_us == 10)    //等待10us后进入下一状态
                state   <=  S_REPLY     ;
            else
                state   <=  S_DLY1     ;
        S_REPLY:  //上升沿到来且低电平保持时间大于70us,则跳转到下一状态
            if(dht11_rise == 1'b1 && cnt_low >= 70)
                state   <=  S_DLY2     ;
                 //若1ms后,dht11还没响应,则回去继续发送起始信号
            else    if(cnt_us >= 1000)
                state   <=  S_LOW_18MS ;
            else
                state   <=  S_REPLY    ;
        S_DLY2: //下降沿到来且计数器值大于70us,则跳转到下一状态
            if(dht11_fall == 1'b1 && cnt_us >= 70)
                state   <=  S_RD_DATA   ;
            else
                state       <=  S_DLY2  ;
        S_RD_DATA:  //读完数据后,回到起始状态
            if(bit_cnt == 40 && dht11_rise == 1'b1)
                state   <=  S_LOW_18MS  ;
            else
                state   <=  S_RD_DATA   ;
        default:
                state   <=  S_WAIT_1S   ;
        endcase
 
//各状态下的计数器赋值
//cnt_us:每到一个新的状态就让该计数器重新计数
always@(posedge clk_1us or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        begin
            cnt_low <=  7'd0      ;
            cnt_us   <=  21'd0    ;
        end
    else
        case(state)
        S_WAIT_1S:
            if(cnt_us == T_1S_DATA) 
                cnt_us   <=  21'd0  ;
            else
                cnt_us   <=  cnt_us + 1'b1;
        S_LOW_18MS:
            if(cnt_us == T_18MS_DATA)
                cnt_us   <=  21'd0  ;
            else
                cnt_us   <=  cnt_us + 1'b1;
        S_DLY1:
            if(cnt_us == 10)
                cnt_us   <=  21'd0  ;
            else
                cnt_us   <=  cnt_us + 1'b1;
        S_REPLY:
            if(dht11_rise == 1'b1 && cnt_low >= 70)
                begin
                    cnt_low <=  7'd0    ;
                    cnt_us   <=  21'd0  ;
                end
            //当dht11发送低电平回应时,计算其低电平的持续时间
            else    if(dht11 == 1'b0)
                begin
                    cnt_low  <=  cnt_low + 1'b1 ;
                    cnt_us   <=  cnt_us + 1'b1  ;
                end
            //若1ms后,dht11还没响应,则回去继续发送起始信号
            else    if(cnt_us >= 1000)
                begin
                    cnt_low <=  7'd0   ;
                    cnt_us  <=  21'd0  ;
                end
            else
                begin
                    cnt_low <=  cnt_low        ;
                    cnt_us  <=  cnt_us + 1'b1  ;
                end
        S_DLY2:
            if(dht11_fall == 1'b1 && cnt_us >= 70)
                cnt_us   <=  21'd0  ;
            else
                cnt_us   <=  cnt_us + 1'b1;
        S_RD_DATA:
            if(dht11_fall == 1'b1 || dht11_rise == 1'b1)
                cnt_us   <=  21'd0  ;
            else
                cnt_us   <=  cnt_us + 1'b1;
        default:
            begin
                cnt_low  <=  7'd0   ;
                cnt_us   <=  21'd0  ;
            end
        endcase
 
//各状态下的单总线赋值
always@(posedge clk_1us or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        begin
               dht11_out <=  1'b0    ;
               dht11_en  <=  1'b0    ;
        end
    else
        case(state)
        S_WAIT_1S:
                begin
                    dht11_out    <=  1'b0    ;
                    dht11_en     <=  1'b0    ;
                end
        S_LOW_18MS: //拉低总线18ms
                begin
                    dht11_out    <=  1'b0    ;
                    dht11_en     <=  1'b1    ;
                end
    //后面状态释放总线即可,由DHT11操控总线
        S_DLY1:
                begin
                    dht11_out    <=  1'b0    ;
                    dht11_en     <=  1'b0    ;
                end
        S_REPLY:
                begin
                    dht11_out    <=  1'b0    ;
                    dht11_en     <=  1'b0    ;
                end
        S_DLY2:
                begin
                    dht11_out    <=  1'b0    ;
                    dht11_en     <=  1'b0    ;
                end
        S_RD_DATA:
                begin
                    dht11_out    <=  1'b0    ;
                    dht11_en     <=  1'b0    ;
                end
        default:;
        endcase
 
//data_tmp:将读出的数据寄存在data_tmp中
always@(posedge clk_1us or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        data_tmp    <=  40'b0;
    else    if(state == S_RD_DATA && dht11_fall == 1'b1 && cnt_us<=50)
        data_tmp[39-bit_cnt]   <=  1'b0;
    else    if(state == S_RD_DATA && dht11_fall == 1'b1 && cnt_us>50)
        data_tmp[39-bit_cnt]   <=  1'b1;
    else
        data_tmp    <=  data_tmp;
 
//data_out:输出数据显示,按一次按键切换一次数据
always@(posedge clk_1us or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        data    <=  32'b0;
    else    if(data_tmp[7:0] == data_tmp[39:32] + data_tmp[31:24] +
                                      data_tmp[23:16] + data_tmp[15:8])
        data    <=  data_tmp[39:8];   //若检验位正确,则数据值有效
     else
        data    <=  data;
 
//data_out:对数码管显示的湿度和温度进行赋值
always@(posedge clk_1us or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
    begin
        temp_int    =0;
        temp_float  =0;
        humi_int    =0;
        end
    else   
    begin
    //温度低四位显示温度小数数据
        humi_int    <=  data[31:24]; //湿度小数位为0
        temp_int    <=  data[15:8] ;
        temp_float  <=  data[3:0];
        end
 
//sign:符号位的显示
always@(posedge clk_1us or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        sign    <=  1'b0;
    else    if(data[7] == 1'b1)
    //当温度低八位最高位为1时,显示负号
        sign    <=  1'b1;
    else
        sign    <=  1'b0;
 
endmodule

 

请登录后发表评论

    没有回复内容