新闻详情

新闻详情

首页 / 资讯中心 / 详情

FPGA驱动DHT11温湿度传感器:单总线协议与状态机实现详解

发布时间:2026/8/31 14:55:06来源:尧图网络
FPGA驱动DHT11温湿度传感器:单总线协议与状态机实现详解
简介本资源是一套面向FPGA初学者与嵌入式硬件开发者的DHT11传感器驱动实践工程基于Altera Cyclone IV EP4CE10芯片采用Verilog HDL实现单总线协议时序控制、数据解析与校验逻辑解决数字温湿度传感在FPGA平台上的可靠通信难题适用于物联网终端、环境监测实验及数字系统课程设计等场景。压缩包共398个文件包含95个.cdb编译数据库、94个.hdb层次化数据库、99个.tdf时序数据文件等Quartus II工程核心文件另有5个.v源码文件、1个.qsf引脚约束文件、1个.readme说明文档及.sof配置文件完整覆盖设计、综合、布局布线与下载全流程包体大小为6.92MB。已有279人学习下载提供可直接编译运行的工程框架、带注释的关键模块代码如启动脉冲生成、40位数据采样状态机、CRC校验逻辑以及仿真测试激励与map/rpt等关键报告文件便于理解时序精度要求、调试信号完整性并迁移至其他传感器驱动开发。 DHT11这东西单个不到两块钱数据手册写得也不厚但真把它接到FPGA上你会发现跟单片机完全是两个世界。我在EP4CE10上把这颗传感器调通前前后后折腾了两天中间连“读出来全是0”这种诡异问题都遇到过。这篇文章就把整个思路、代码、以及调试过程中踩到的坑完整整理出来给正在折腾FPGA驱动DHT11的朋友做个参考。这个项目适合这几类人刚开始学FPGA、想从点灯进阶到真实传感器驱动的做课设或者电赛作品需要采集温湿度的以及想通过一个小项目把状态机和时序控制真正搞懂的人。前置要求不高会Verilog基本语法、知道怎么用Quartus建工程、下载程序就行。1. 为什么用FPGA驱动DHT11看似简单却暗藏门槛的小项目1.1 单片机的“延时大法”在FPGA上直接失效先对比一下两种平台的实现方式差异因为这是很多人翻车的根源。如果是在STM32或者51上驱动DHT11代码逻辑大概是拉低引脚延时18ms、拉高延时30us、然后不断检测引脚电平、用延时函数测量高电平持续时间。整个过程是“顺序执行”的一行一行往下走时间靠延时函数控制。但FPGA不是这种模型。FPGA里没有“延时函数”这种东西所有的时序行为都要靠时钟周期数来量化。你说“拉低18ms”在FPGA里就要写成“在50MHz时钟下计数器数够900000个周期”。而且FPGA是并行执行的时序控制必须通过状态机把整个通信过程拆成一步一步再配合计数器来保证每一步的时间。这就是为什么很多单片机出身的朋友第一次在FPGA上驱动DHT11时会懵代码抄过来了时序怎么也对不上。不是代码问题是思维模型还没切换过来。1.2 EP4CE10的资源概览和开发环境准备EP4CE10是Altera Cyclone IV E系列的一颗入门级FPGA不少国产开发板用的就是这颗。它的资源不算多但做DHT11这种小项目绰绰有余资源数量逻辑单元LE10320嵌入式内存Kbit41418x18硬件乘法器23PLL锁相环2用户IO最多91个开发环境我用的Quartus Prime Lite版免费的支持Cyclone IV全系列。工程搭建没什么特殊之处新建工程、选对器件型号EP4CE10E22C8之类的后缀看你板子上的丝印、写Verilog、分配引脚、编译下载。板载晶振一般是50MHz下面所有时序计算都基于50MHz这个频率。1.3 这个项目的核心学习价值DHT11虽然精度一般温度±2℃湿度±5%RH但它的单总线协议在时序上“恰好比流水灯复杂一个量级”——有握手、有位解析、有校验又不像IIC和SPI那样有成套协议栈非常适合用来理解状态机设计。做完这个项目你对FPGA如何处理外部异步信号、如何设计有限状态机、如何用计数器量化时间会有非常直观的认识。2. DHT11单总线协议拆解时序参数才是真正的考点2.1 单总线结构、上下拉和数据帧格式DHT11只有三个引脚实际用的就是VCC、GND和一根DATA。DATA是开漏输出空闲状态靠上拉电阻维持高电平。通信时主机和DHT11轮流拉低这根线来传递信息FPGA这边要处理的inout引脚就是这根单总线。数据帧固定40位一次发送完毕高位在前位段内容bit39 - bit32湿度整数部分bit31 - bit24湿度小数部分DHT11固定为0bit23 - bit16温度整数部分bit15 - bit8温度小数部分DHT11固定为0bit7 - bit0校验和 前4字节之和的低8位数据解析完成后要做校验。校验失败的数据帧要丢弃这是可靠性的底线。我自己在调试时有一半的时间都在跟校验打交道所以后面会专门讲。2.2 一次完整通信的时序参数DHT11的通信过程可以分为三个阶段主机发起起始信号、DHT11响应、DHT11发送40位数据。关键参数如下阶段信号持续时间主机起始拉低总线至少18ms建议20ms以上主机起始释放总线拉高20us - 40usDHT11响应拉低总线80usDHT11响应拉高总线80us数据位0低电平50us 高电平26us - 28us总共约76us - 78us数据位1低电平50us 高电平70us总共约120us通信结束总线释放由外部上拉电阻拉高这里最核心的判定逻辑是每个数据位都以50us低电平作为“起始标志”后面的高电平宽度决定了它是0还是1。高电平约26-28us是0高电平约70us是1。中间留了相当大的空隙所以只要取一个40us左右的阈值就能可靠区分0和1。2.3 采样周期一个容易被忽略的硬约束DHT11数据手册里明确写了“采样周期不得小于1秒”。这意味着你不能像读寄存器一样连续高速读取必须在两次读取之间加一个至少1秒的间隔。有的开发板例程没做这个限制实测连续读五六次之后数据就会突然变成0xFF或者完全卡死就是这个原因。2.4 FPGA没有delay函数把时间“数”出来在FPGA里实现上述时序核心手段就两个状态机加计数器。状态机负责“什么时间该干什么”计数器负责“干多长时间”。时钟是50MHz一个周期20ns18ms就是900000个周期1us就是50个周期。我建议先把时钟分频到1MHz再用这样计数器计1个数就是1us代码可读性和调试便利性都高很多。后面代码部分我会给出实现。3. 模块结构与状态机设计读一次温湿度要走几步3.1 顶层模块怎么拆DHT11驱动不需要拆得太碎。我见过有人把它分成“时序产生模块”“数据接收模块”“数据校验模块”三个子模块结果模块间握手信号比协议本身还复杂。对于这种小规模传感器驱动一个控制模块加一个分频器就够了clk_div50MHz分频产生1MHz时钟dht11_ctrl核心状态机负责所有时序产生和数据接收如果你后面要接数码管或LCD显示可以再包一层顶层模块把dht11_ctrl的输出接出去。但现在不要过度设计。3.2 状态机的详细定义我最终用的状态机是8个状态每个状态的进入条件和退出条件如下表所示状态行为退出条件IDLE等待启动信号总线保持高电平高阻收到start脉冲进入START_LOWSTART_LOW拉低总线保持20ms计数器到20000进入START_HIGHSTART_HIGH释放总线等待20us - 40us计数器到30us进入RESP_LOWRESP_LOW等待DHT11拉低总线检测到引脚为低进入RESP_HIGHRESP_HIGH等待DHT11拉高总线检测到引脚为高进入DATA_LOWDATA_LOW等待数据位起始的低电平结束检测到引脚为高进入DATA_HIGHDATA_HIGH检测高电平宽度判定0或1检测到引脚为低回到DATA_LOW继续下一位或进入DONE已收满40位DONE输出有效数据和valid脉冲保持一拍回到IDLE这个设计的一个关键思路是能用检测电平跳转的地方就尽量不固定延时。比如响应信号的80us低电平我没有去“数80us”而是检测到低电平之后等它变高。这样即使不同批次的DHT11时序有微小差异状态机也能自适应不会因为卡死在固定时间上而出错。当然纯等电平有风险如果DHT11没接、线路断开状态机会永远停在那里。所以每个等待电平的状态都应该加超时保护比如计数器超过一定值就强制回IDLE。这个后面代码里会有。3.3 位计数和超时参数的规划DATA_LOW和DATA_HIGH两个状态会被重复执行40次需要一个bit_cnt变量记录当前收的是第几位。同时每个等待电平的状态都需要超时兜底比如RESP_LOW超过200us没等到低电平、DATA_HIGH超过150us没等到低电平都判定为通信异常直接复位状态机。这些参数在代码里就是几个localparam但设计之初想清楚能省很多调试时间。4. 关键代码逐段解读从计数器到数据拼接4.1 分频器50MHz到1MHz这个分频器很简单50分频输出1MHz时钟。注意计数器从0数到24再翻转一个完整周期是50个50MHz时钟正好1usmodule clk_div_1m ( input wire clk_50m, input wire rst_n, output reg clk_1m ); reg [5:0] cnt; always (posedge clk_50m or negedge rst_n) begin if (!rst_n) begin cnt 6d0; clk_1m 1b0; end else if (cnt 6d24) begin cnt 6d0; clk_1m ~clk_1m; end else begin cnt cnt 1b1; end end endmodule为什么不直接拿50MHz计数也不是不行但18ms要数90万个周期调试时换成微秒单位要反复除以50非常反人类。分频到1MHz之后计数器值直接就是微秒数写代码、看波形都直观得多。4.2 三态IO的正确处理inout引脚是重点DHT11的DATA引脚是双向的FPGA侧必须用inout类型。很多新手在这里翻车——把dht11_data声明成input或者output然后发现数据完全不对。正确做法是内部用三态门控制module dht11_ctrl ( input wire clk_1m, input wire rst_n, input wire start, inout wire dht11_data, output reg [7:0] humidity, output reg [7:0] temperature, output reg data_valid ); reg dht11_out_en; reg dht11_out_data; reg dht11_in; assign dht11_data dht11_out_en ? dht11_out_data : 1bz; assign dht11_in dht11_data; endmodule当主机需要拉低总线时dht11_out_en拉高、dht11_out_data拉低当主机需要释放总线时dht11_out_en拉低引脚变成高阻态由外部上拉电阻把电平拉高。读取DHT11发送的电平时直接看dht11_in即可。这里有个硬件层面的细节DHT11的数据线上必须有上拉电阻一般4.7k到10k。如果开发板没有预留需要在FPGA内部打开IO的弱上拉选项。实测下来外部上拉比内部弱上拉稳定得多特别是线缆较长的时候。4.3 状态机主循环核心骨架下面给出状态机的主体框架注释里写明了每个状态的行为localparam IDLE 3d0; localparam START_LOW 3d1; localparam START_HIGH 3d2; localparam RESP_LOW 3d3; localparam RESP_HIGH 3d4; localparam DATA_LOW 3d5; localparam DATA_HIGH 3d6; localparam DONE 3d7; reg [2:0] state; reg [15:0] counter; reg [5:0] bit_cnt; reg [39:0] data_buf; reg [15:0] timeout_cnt; always (posedge clk_1m or negedge rst_n) begin if (!rst_n) begin state IDLE; counter 16d0; dht11_out_en 1b0; dht11_out_data 1b1; data_valid 1b0; end else begin data_valid 1b0; case (state) IDLE: begin dht11_out_en 1b0; // 释放总线 counter 16d0; bit_cnt 6d0; if (start) state START_LOW; end START_LOW: begin dht11_out_en 1b1; // 拉低总线 dht11_out_data 1b0; if (counter 16d20000) begin // 20ms state START_HIGH; counter 16d0; end else counter counter 1b1; end START_HIGH: begin dht11_out_en 1b0; // 释放总线 if (counter 16d30) begin // 30us state RESP_LOW; counter 16d0; end else counter counter 1b1; end RESP_LOW: begin if (dht11_in 1b0) begin // 检测到DHT11拉低 state RESP_HIGH; counter 16d0; end else begin // 超时保护 if (counter 16d200) begin state IDLE; counter 16d0; end else counter counter 1b1; end end RESP_HIGH: begin if (dht11_in 1b1) begin // 检测到DHT11拉高 state DATA_LOW; counter 16d0; end else begin if (counter 16d200) begin state IDLE; counter 16d0; end else counter counter 1b1; end end DATA_LOW: begin if (dht11_in 1b1) begin // 50us低电平结束 state DATA_HIGH; counter 16d0; end else begin if (counter 16d100) begin state IDLE; counter 16d0; end else counter counter 1b1; end end DATA_HIGH: begin if (dht11_in 1b0) begin // 高电平结束 // 判定0或1 if (counter 16d40) data_buf {data_buf[38:0], 1b1}; else data_buf {data_buf[38:0], 1b0}; if (bit_cnt 6d39) begin state DONE; bit_cnt 6d0; end else begin bit_cnt bit_cnt 1b1; state DATA_LOW; end counter 16d0; end else begin counter counter 1b1; // 防止高电平过长卡死 if (counter 16d150) begin state IDLE; counter 16d0; end end end DONE: begin // 校验并输出 if ({data_buf[39:32] data_buf[31:24] data_buf[23:16] data_buf[15:8]} data_buf[7:0]) begin humidity data_buf[39:32]; temperature data_buf[23:16]; data_valid 1b1; end state IDLE; end endcase end end这段代码的核心思路是每个状态先判断是否满足“退出条件”满足就跳转到下一状态并清零计数器不满足就计数。DATA_HIGH状态用一个计数器测量高电平持续了多少个微秒超过40就判为逻辑1。4.4 数据拼接和校验移位寄存器是主角DATA_HIGH状态里两行关键的赋值代码data_buf {data_buf[38:0], 1b1}; // 判定为1时 data_buf {data_buf[38:0], 1b0}; // 判定为0时这是一个40位的移位寄存器每收到一个数据位就从最低位移入最高位被挤掉。DHT11先发高位所以经过40次移位后data_buf的bit39就是湿度整数部分的最高位bit0就是校验和的最低位。校验逻辑用了拼接运算符做了一个4字节加法if ({data_buf[39:32] data_buf[31:24] data_buf[23:16] data_buf[15:8]} data_buf[7:0])括号里的加法结果按8位截断等价于取低8位。判断成功才更新温度和湿度输出同时拉高data_valid一个时钟周期。这样外部模块只需要等data_valid信号到来再锁存温度和湿度就行。5. 上板实测与调试验证我踩过的三个坑5.1 坑一起始信号时间不够DHT11毫无反应第一次上板时我把START_LOW状态按数据手册的“最低18ms”设成了18000个时钟周期结果发现一个很诡异的现象DHT11偶尔响应、经常不响应而且不响应的时候用示波器看引脚电平发现总线被拉低之后DHT11完全没有拉低回应的动作。后来查了一些资料发现问题出在两个方面。一是不同批次的DHT11对起始信号低电平时间的要求有差异18ms是极限值实际芯片可能需要更长时间才能被“唤醒”。二是DHT11内部是RC振荡器校准的时钟精度本身不高18ms和20ms的差别在它眼里可能并不大。解决办法很简单把起始信号加长。我最终设成了20ms到25ms之间。实测下来稳定很多。数据手册给的参数是下限不是建议值设计时要留出足够的余量。5.2 坑二读出来的数据全是0校验居然还能通过这是我在调试中花时间最长的一个问题。代码看起来逻辑没问题上板后data_valid正常拉高但读出来的湿度和温度都是0x00。更迷惑的是40位全是0的话校验和也是0所以校验逻辑根本发现不了错误。排查过程是这样的先用SignalTap II逻辑分析仪抓内部的dht11_in信号发现它从头到尾都是低电平。这说明FPGA引脚接收到的始终是0DHT11大概率在正常工作问题出在引脚配置或者外部电路上。用万用表量DHT11 DATA引脚的电压发现它在空闲时是0V而不是数据手册里说的被上拉到高电平。这就找到了问题根源开发板上DHT11接口没有预留外部上拉电阻而我在Quartus引脚分配时也没配置内部弱上拉。FPGA引脚在没有上拉的情况下高阻态电平是不确定的实测直接飘在低电平附近。解决办法有两个一个是硬件上在DATA引脚和VCC之间加一个10k上拉电阻另一个是在Quartus的Pin Planner里为dht11_data使能内部弱上拉选项。我两个都做了硬件上拉更稳。这个坑的典型特征是“数据全0但校验通过”因为0的校验和还是0。如果你的驱动模块跑起来是这种症状先查上拉别急着改状态机。5.3 坑三连续读取时偶发数据错误需要间隔大于1秒还有一次是给状态机加了自动循环读取功能每读完一次就自动重新触发下一次读取。然后发现一个规律连续读三四次之后某次数据突然完全不对温度和湿度乱跳校验也失败但再往后读又恢复正常的概率很高。这个问题排查到最后起因是违反DHT11的“最小读取间隔1秒”约束。DHT11内部每次读取后需要时间完成新一轮测量如果主机读取太频繁DHT11还来不及准备好新数据输出的就是乱码或者上次残留的数据。解决思路是给自动循环加一个间隔定时器。每次DONE状态结束之后不立即回到IDLE而是进入一个WAIT状态等够1秒钟再允许下一次start。如果你是用按键手动触发读取同样需要注意两次按键之间间隔不要太短。5.4 用好SignalTap II调试效率翻倍这几个坑排查过程中SignalTap II帮了大忙。这个逻辑分析仪IP核可以实时抓取FPGA内部的寄存器信号不用额外接示波器。我通常把state、counter、bit_cnt、dht11_in、data_buf这几个信号加进去采样深度256就行。加太多信号和深度会占用额外的存储资源EP4CE10资源有限够看就行。抓取波形之后状态机的跳转过程一目了然。比如起始信号之后卡在RESP_LOW不动说明DHT11没有响应DATA_HIGH状态高电平计数永远不到40说明引脚电平有问题。这些信息比盲改代码高效得多。6. 项目复用与扩展方向温湿度数据怎么用起来6.1 显示扩展数码管和1602 LCDDHT11驱动模块的输出是8位湿度和8位温度数据在data_valid有效时可用。把这两个值接到数码管驱动模块上用动态扫描显示即可。相比直接读传感器你额外需要一个二进制到BCD码的转换逻辑或直接查表以及一个数码管扫描控制器。接1602 LCD的话注意DHT11的数据更新频率是1秒一次显示模块完全不用跟着传感器节奏跑可以自己开一个10ms的扫描周期只在data_valid到来时更新显示寄存器就行。这样代码解耦改起来也方便。6.2 数据上传加一个UART发送模块如果想看实时温湿度曲线串口是最方便的路径。DHT11读完数据之后用UART模块把湿度、温度、校验和一起发出去上位机随便用个串口助手就能看。UART发送格式可以自己定义比如固定帧头0xAA、湿度、温度、校验和、帧尾0x55这样上位机解析也有依据。我实际用下来这个组合DHT11 UART 上位机做一个简单的环境监测节点是完全够用的。只要UART波特率不超过115200EP4CE10的逻辑资源占用非常小还有大把余量做别的事情。6.3 接口思维的迁移从DHT11到其他传感器DHT11做完之后你会发现一个重要规律所有带时序的传感器驱动本质上都是“状态机 计数器 电平检测”。这个框架往下可以直接套用DS18B20同样是单总线但需要写ROM命令和读暂存器状态机多几个状态而已SHT30IIC接口核心是SCL时钟产生和SDA数据采样同样是状态机设计MAX31865SPI接口重点是读寄存器时序和温度数据转换这些传感器的数据手册里都有时序图结合DHT11这个项目积累的经验读起来会轻松很多。说到底FPGA驱动外部器件的核心能力就是“把数据手册的时序图翻译成状态机”这个本事而DHT11是这个能力最好的练习对象。这套代码我一开始写得比现在复杂很多后来砍到只剩一个状态机加一个计数器反而更稳了。FPGA做传感器驱动很多时候不是功能实现不了而是把简单的问题搞复杂了。最后再提醒一句外设调试遇到诡异问题先查硬件、再抓波形、最后才怀疑逻辑这个顺序能帮你省下大把时间。本文还有配套的精品资源点击获取
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

游戏后端交易系统设计与防刷方案实战 2026/8/31 17:26:02

游戏后端交易系统设计与防刷方案实战

抱歉,这个标题对应的内容不适合在 CSDN 技术社区发布,我也无法按此生成博文。原因有两点:内容定位不符:CSDN 是技术开发社区,主要承载编程、框架、工具链、系统架构、AI、数据库等工程实践内容。“游戏内倒卖子弹、日抛…

阅读更多 →
开源版Claude Cowork:团队共享Claude Code配置的完整方案 2026/8/31 17:26:02

开源版Claude Cowork:团队共享Claude Code配置的完整方案

1. 痛点:每人一套 Claude 环境,到底浪费了多少时间 最近在团队里做了一轮 AI 编程工具推广,发现一个非常现实的卡点:Claude Code 本身很好用,但团队协作时,每个人都得在自己的电脑上安装、登录、配置 API K…

阅读更多 →
中级会计实务备考:100条固定答案助你稳上80分 2026/8/31 17:26:02

中级会计实务备考:100条固定答案助你稳上80分

中级会计实务这门课,复习到后期最让人难受的状态,不是完全不会,而是知识点都见过,题也能读懂,结果一选就错,一写分录就卡住。有人总结出一套办法:把考试里反复出现的固定处理整理成100条答案&am…

阅读更多 →
中级会计实务100条固定答案速记:备考冲刺高效得分 2026/8/31 17:26:02

中级会计实务100条固定答案速记:备考冲刺高效得分

很多备考中级会计实务的同学,第一轮复习结束后都会有同一个困惑:课听懂了,题也刷了一部分,但一到做题就卡壳。尤其是长期股权投资、合并报表、所得税这些章节,知识点多、分录长、判断条件复杂,光靠“理解”…

阅读更多 →
武装电影院测评指南:从成本到体验的完整框架 2026/8/31 17:26:02

武装电影院测评指南:从成本到体验的完整框架

把“武装电影院”从一张配置清单变成真实体验,只靠看参数是做不到的。我最近花了两周时间,把自己常用的投影、音箱、播放设备和观影环境重新测了一遍,最大的感受是:测评这件事没有统一答案,每个人对性价比的判断标准都…

阅读更多 →
AI数据中心规划指南:功率密度、散热与网络运维的关键挑战 2026/8/31 17:21:02

AI数据中心规划指南:功率密度、散热与网络运维的关键挑战

先给一个判断:AI 数据中心真正让工程团队头疼的,从来不是“又多买了几块 GPU”,而是电、热、网络和运维模式这四个维度,几乎全部要和传统数据中心反着来。 过去几年,很多团队在推进 AI 基础设施时,习惯沿用…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞