FPGA读取DHT11模块数据

        记录初学fpga实现的一个功能。了解完DHT11所用的单总线协议后,其详细的读写规则有很多博客介绍,大概是主机发送低电平再发送高电平,然后从机发送低电平再高电平来响应(电平都得保持一定的时间),最后就是从机发数据给主机,用低电平做准备,高电平持续时间长短来表示0和1(低电平持续时间短,高电平持续时间长)。

        按照如下状态转移图编写verilog代码,最终实现的功能是从DHT11读取温湿度数据,一次读取的数据总共有40bit,并将读取的数据存储在data_reg寄存器中。其中data_reg[39:32]是湿度的整数部分,data_reg[31:24]是湿度的小数,data_reg[23:16]是温度的整数,data_reg[15:8]是温度的小数,data_reg[7:0]是校验和,也就是前面4个Byte一起加起来的结果。

        需要对状态图补充说明的是我认为从机一定会给响应信号过来,从而简化了问题,但是也有可能从机不给响应信号(坏了啥的),那样就卡在Waitlow状态下了。

         以上状态转移图中IDLE是空闲状态,当start信号变为高电平时进入Mlow状态,也就是主机发低电平,当低电平时间足够了进入Mhigh状态发高电平,发高电平结束后进入Waitlow状态检查从机是否发送了低电平来响应(代码中用检测下降沿方式来实现),最后就是从机发数据状态。

        代码如下:

// 读DHT11数据模块
// hqz 20230309
module DHT11
(
    input sys_clk,                      //系统时钟信号,50MHz,0.02us
    input sys_rst_n,                    //异步复位信号,低电平有效
    input start,                        //DHT11 driver module start signal 
    output data_end,                    //一次传输数据结束
    inout wire dat                      //双向数据线
);

//状态机定义
localparam IDLE = 3'd0,             //空闲状态
           Mlow = 3'd1,             //主机发送18ms低电平
           Mhigh = 3'd2,            //主机拉高电平30us
           Waitlow = 3'd3,          //检测从机响应低电平
           DHTdata = 3'd4;          //读取从机数据

//时间计数定义
localparam t1 = 20'd900_000,       
           t2 = 13'd1500,             
           t5 = 13'd2000,        //数据为0和1的分界,0的高电平持续26~28us,1的高电平持续116~118us
           t6 = 13'd1000;


reg         sda_en;   //双向口使能信号
reg         sda_out;   //iic sda输出寄存器
wire         sda_in;   //iic sda输入信号
reg datin_reg;                  //dat输入寄存器

reg [12:0] cnt_t2;         //t1,t2时间计数器
reg [19:0] cnt_t1;
reg [5:0] cnt_bit_num;                          //数据位传输的个数
reg [2:0] cur_state,next_state;                 //状态机的现态和次态

reg t1_end,t2_end;                 //t1,t2计满信号
reg dat_pre1,dat_pre2;                       //上一拍dat总线上的数据
reg ack;                           //从机应答有效信号
reg bit_num_full;                  //数据计数满40个
reg [12:0] cnt_01/* synthesis noprune */;                  //总线高电平时间计数器
reg data_0,data_1,cnt_01_en;       //计数总线上高电平持续时间


reg [5:0] data_bit;                //40bit寄存器的地址
reg [39:0] data_reg /* synthesis noprune */;                //从机发送的数据

//双向口处理
assign  sda_in = dat;
assign  dat = sda_en?sda_out:1'bz;

assign data_end=bit_num_full;

//状态机第一段
always @(posedge sys_clk or negedge sys_rst_n)
begin
    if(~sys_rst_n)
        cur_state <= IDLE;
    else
        cur_state <= next_state;
end 

//状态机第二段
always @(*)
begin
    case(cur_state)
    IDLE:  next_state <= (start?Mlow:IDLE);
    Mlow:  next_state <= (t1_end?Mhigh:Mlow);
    Mhigh: next_state <= (t2_end?Waitlow:Mhigh);
    Waitlow: next_state <= (ack?DHTdata:Waitlow);
    DHTdata: next_state <= (bit_num_full?IDLE:DHTdata);
    default: next_state <= IDLE;
    endcase
end

//状态机第三段
always @(posedge sys_clk or negedge sys_rst_n)
begin
    if(~sys_rst_n)
    begin
        sda_en <= 1'b0;
        cnt_bit_num <= 6'b0;
        cnt_01_en <= 1'b0;
        bit_num_full <= 1'b0;
        ack <= 1'b0;
        dat_pre1 <= 1'b1;
        dat_pre2 <= 1'b1;
    end
    else    
        case(cur_state)
        IDLE:
            begin
                sda_en <= 1'b1;     //dat输出
                sda_out <= 1'b1;    //拉高电平
            end
        Mlow: sda_out <= 1'b0;    //拉低电平t1时间
        Mhigh: sda_out <= 1'b1;    //拉高电平t2时间
        Waitlow:    
            begin
                sda_en <= 1'b0;         //总线输入
                dat_pre1 <= sda_in;      //检测sda_in下降沿
                dat_pre2 <= dat_pre1;
                if((~dat_pre1)&dat_pre2)   ack <= 1'b1;
                else ack <= 1'b0;   
            end
        DHTdata:
            begin
                dat_pre1 <= sda_in;      //检测sda_in上升沿
                dat_pre2 <= dat_pre1;
                if(dat_pre1&(~dat_pre2))  
                    begin
                        cnt_01_en <= 1'b1;
                        cnt_bit_num <= cnt_bit_num + 6'd1;
                    end
                else if((~dat_pre1)&dat_pre2)  //检测sda_in下降沿
                    begin
                        cnt_01_en <= 1'b0;
                        if(cnt_bit_num == 6'd40) bit_num_full <= 1'b1;
                        else bit_num_full <= 1'b0;
                    end
                else 
                    begin    
                        cnt_bit_num <= cnt_bit_num;
                        bit_num_full <= 1'b0;   
                    end
            end
        endcase 
end

always@(posedge sys_clk, negedge sys_rst_n)begin
    if(~sys_rst_n) begin
        data_bit <= 6'd40;
        data_reg <= 40'd0;
    end
    else begin
        if(data_bit != 6'd0)
            if(data_0) begin
                data_bit <= data_bit - 6'd1;
                data_reg[data_bit] <= 1'b0;
            end
            else if(data_1)begin
                data_bit <= data_bit - 6'd1;
                data_reg[data_bit] <= 1'b1;
            end
            else data_bit <= data_bit;
        else data_bit <= 6'd40;
    end
end

//判断从机发送的电平,并对应的输出高低电平标志信号
always@(posedge sys_clk, negedge sys_rst_n)begin
    if(~sys_rst_n) begin
        cnt_01 <= 12'd0;
        data_0 <= 1'b0;
        data_1 <= 1'b0;
    end
    else begin
        if(cnt_01_en) cnt_01 <= cnt_01 + 12'd1;
        else begin
            if(cnt_01>t5) data_1 <= 1'b1;           //从机发送的高电平
            else if(cnt_01>t6) data_0 <= 1'b1;      //从机发送的低电平
            else begin
                data_0 <= 1'b0;
                data_1 <= 1'b0;
            end
            cnt_01 <= 12'd0;                        //清空计数器
        end
    end
end

//主机拉低电平时间计数器,18ms,计数t1个
always @(posedge sys_clk or negedge sys_rst_n)
begin
    if(~sys_rst_n)begin
        cnt_t1 <= 20'd0;
        t1_end<=1'b0;
    end
    else 
        if(next_state == Mlow)
            begin
                if(cnt_t1 == t1)
                    begin
                        cnt_t1 <= 20'd0;
                        t1_end <= 1'b1;
                    end
                else
                    cnt_t1 <= cnt_t1 + 20'd1;   
                    
            end
        else 
            begin
                cnt_t1 <= 20'd0;
                t1_end <= 1'b0;
            end
end

//主机拉高电平时间计数器, 30us,计数t2个
always@(posedge sys_clk,negedge sys_rst_n)begin
    if(~sys_rst_n)begin
        cnt_t2 <= 13'd0;
        t2_end <= 1'b0;
    end
    else 
        if(next_state == Mhigh)
            begin
                if(cnt_t2 == t2)
                    begin
                        cnt_t2 <= 13'd0;
                        t2_end <= 1'b1;
                    end
                else 
                    cnt_t2 <= cnt_t2 + 13'd1;
            end
        else 
            begin
                cnt_t2 <= 13'd0;
                t2_end <= 1'b0;
            end
end

endmodule

使用SignalTap Logical Analyzer抓取的波形如下:

         波形中data_reg数据是4E00140668h,先看校验和4E+00+14+06=68(十六进制),湿度是78.0%,温度是20.6°C。总结下来,设计的比较简陋,有一些情况没有考虑,但是第一步能跑就行了哈哈哈,还有上板调试用好逻辑分析仪非常有用,帮我调了很多bug。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值