ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

FPGA驱动DHT11温湿度传感器:Verilog状态机设计与仿真

FPGA驱动DHT11温湿度传感器:Verilog状态机设计与仿真 简介一套完整的DHT11数字温湿度传感器FPGA示例工程面向FPGA开发学习者基于Zynq XC7Z020平台全部使用可综合Verilog编写支持自定义参考时钟频率默认50MHz便于移植到不同开发板适合快速掌握单总线传感器驱动与仿真验证流程。压缩包共30个文件包含Verilog/VHDL源码、Vivado工程配置与XDC约束、Modelsim仿真脚本do/tcl/bat、说明文档以及调试截图整体仅504KB结构紧凑清晰。工程不仅完整实现了DHT11的时序读写还加入了按键消抖等实用模块并配套可运行的Modelsim仿真工程仿真脚本与波形查看配置可一键调用直观观察数据建立/保持过程同时附带上板调试图与时序分析报告便于对照实测结果。目前已有541人学习浏览适合正在入门FPGA或需要在Zynq上接入温湿度传感器的开发者参考。1. 从一根数据线到完整时序为什么 DHT11 值得在 FPGA 上重写一遍DHT11 是嵌入式开发里最常见的温湿度传感器多数人用单片机 GPIO 模拟时序就能驱动但换到 FPGA 上事情会变得不一样。原因在于单片机是顺序执行指令延时可以用delay()函数“挤”出来而 FPGA 是并行硬件逻辑没有操作系统帮你排队所有的微秒级时序都要靠时钟周期精确计算和状态机调度。换句话说DHT11 的 18ms 起始信号、30μs 响应窗口、50μs 高低电平翻转在 FPGA 上不能靠“等”只能靠“数时钟”。这份工程基于 Xilinx Zynq XC7Z020 芯片用可综合 Verilog 编写默认参考时钟 50MHz附带 Modelsim 仿真工程和 Vivado 约束与调试文件。它解决的核心问题有两层一是把 DHT11 的单总线协议翻译成 FPGA 能理解的有限状态机二是提供一个可复现的验证环境让你在仿真里看到波形而不是直接上板盲调。适合刚入手 FPGA 但已经写过简单状态机的人也适合用 Zynq 做环境监测但不想自己从头抠时序的开发者。2. 单总线时序拆解与 Verilog 状态机设计DHT11 使用单总线1-Wire-like协议但比 Dallas 的 1-Wire 简单得多只有一根数据线主机主动发起通信从机被动应答。一个完整的读取周期分成三个阶段主机拉低起始、从机响应、数据逐位输出。理解这三个阶段的状态划分是写 Verilog 时序逻辑的前提。DHT11 的时序参数看起来不多但对时钟周期非常敏感。表 1 给出了本文工程默认 50MHz 时钟下需要用到的关键时间参数及对应的周期计数。表 1DHT11 通信时序关键参数与 50MHz 计数阶段时间参数周期数50MHz说明起始信号主机拉低 ≥18ms900000必须精确计数过短传感器不响应释放总线主机释放并延时 20-40μs1000-2000等待传感器拉低响应响应低电平从机拉低 80μs4000检测到这个低电平才能继续响应高电平从机拉高 80μs4000表示准备发送数据数据位 “0”低电平 50μs 高电平 26-28μs2500 1300采样点应落在高电平中段数据位 “1”低电平 50μs 高电平 70μs2500 3500高电平宽度是区分 0/1 的依据状态机的设计思路不是直接去测量每个高电平的绝对宽度而是利用 DHT11 的特性数据位总是以 50μs 低电平开始随后高电平的宽度决定逻辑值。因此状态机只需要关注当前是低电平还是高电平再用一个计数器来计时。localparam IDLE 3d0, START_LOW 3d1, START_REL 3d2, RESP_LOW 3d3, RESP_HIGH 3d4, DATA_LOW 3d5, DATA_HIGH 3d6, DONE 3d7;这段代码将状态定义为 3 位独热码的替代实现——使用的是二进制编码占用的寄存器更少。状态流转的核心逻辑是IDLE 收到外部触发信号trig后进入START_LOW计数到 900000 后释放总线进入START_REL在START_REL中检测到数据线被拉低说明传感器开始响应进入RESP_LOW之后每次电平翻转都切换到对应的状态直到 40 位数据全部收完。需要特别说明的是START_LOW的计数器不能复用其他状态的计数器否则状态切换时清零逻辑会变得混乱。我一般会为每个状态单独划分计数区间而不是在状态机外用统一的计数器这样调试时每个信号的波形一眼就能看出当前状态。3. 可综合 Verilog 代码结构与 Modelsim 仿真工程详解工程目录里最重要的三个文件是dht11.v、debouncer.sv和README.txt。其中dht11.v是传感器驱动核心debouncer.sv处理外部按键或触发信号的抖动防止误触发读取操作。README.txt描述了工程结构和引脚约束位置上板前应先读一遍。dht11.v的模块接口设计如下表所示表 2dht11.v 模块接口信号名方向位宽功能clkinput1参考时钟默认 50MHzrst_ninput1异步复位低有效triginput1读取触发信号上升沿有效dht11_datainout1连接 DHT11 单总线humi_dataoutput8湿度整数部分humi_decoutput8湿度小数部分temp_dataoutput8温度整数部分temp_decoutput8温度小数部分data_validoutput1读到的数据校验通过后置高一个周期关键在于dht11_data是 inout 类型这在 FPGA 里意味着要使用三态门控制输出方向。主机在发送起始信号时需要驱动总线拉低而读取从机响应和数据时必须释放总线让 DHT11 来控制电平。实现方式是使用一个输出使能信号dht11_oe高电平时数据线被强制拉低低电平时数据线进入高阻态。assign dht11_data dht11_oe ? 1b0 : 1bz;这一行代码是整个单总线通信的物理基础。dht11_oe拉高时传感器看到的是主机主动拉低拉低后数据线被释放外部上拉电阻通常 4.7kΩ 至 10kΩ将电平恢复到高这时从机才能控制总线。如果省略这个三态门逻辑直接赋值输出会导致总线冲突。仿真工程的核心是vsim.bat、wave.do和compile.tcl。在 Modelsim 中运行仿真的命令路径是vlib work vlog acc -f compile.tcl vsim -c work.dht11_tb -do wave.docompile.tcl里定义了需编译的源文件列表和仿真库路径。如果要在自己的环境中重新仿真需要先修改这个文件中的路径变量确保指向工程中的hdl目录。vsim -c表示以命令行模式运行仿真-do wave.do则自动加载波形配置把dht11_data、当前状态位宽、位计数器等关键信号添加到波形窗。仿真时最常踩的坑是初始复位时序。由于 DHT11 是异步时序仿真激励文件里的rst_n释放时间和trig触发时间需要按微秒级设置否则状态机可能一直在 IDLE 徘徊。建议激励中使用#100000;这样的延时语句模拟真实上电过程而不是在零时刻立即触发读取。4. Vivado 工程移植与上板实测调试仿真通过后就可以把工程搬到 Vivado 里做综合和上板验证。工程中包含xapp_dht11.xpr和demo.srcs目录但在实际使用时很多人会遇到芯片型号不匹配的问题。xapp_dht11.xpr绑定的是 XC7Z020如果手里的板子是其他型号需要直接创建新的 Vivado 工程手动添加hdl/dht11.v和hdl/debouncer.sv源文件。建立新工程后首先要做的是引脚约束。在 XC7Z020 上连接 DHT11 的数据引脚我一般选择 PL 侧的 IO并设置为 LVCMOS33 电平标准。约束文件里需要同时完成物理引脚绑定和 IO 标准设定set_property -dict { PACKAGE_PIN L15 IOSTANDARD LVCMOS33 } [get_ports dht11_data] set_property -dict { PACKAGE_PIN M14 IOSTANDARD LVCMOS33 } [get_ports clk] set_property -dict { PACKAGE_PIN N15 IOSTANDARD LVCMOS33 } [get_ports rst_n]需要注意的是dht11_data作为 inout 端口在约束中不需要额外配置三态属性Vivado 会根据 RTL 代码自动推断出 IOBUF 原语。但如果你在dht11.v中使用的是独立输入和输出端口的拼接方式就需要手动例化IOBUF原语。检查综合结果时如果发现端口类型不匹配应优先检查 RTL 中的 inout 声明。综合完成后打开demo.srcs中已有的timing.png可以看到工程的时序收敛情况。50MHz 约束下DHT11 驱动逻辑的路径延迟通常不是瓶颈真正要关心的是跨时钟域问题——如果外部触发信号trig来自按键或 UART 等其他时钟域必须做两级同步处理。debouncer.sv里实现的按键消抖本质上就是通过一个 20ms 窗口的计数器过滤毛刺同时输出同步后的脉冲信号。上板调试的硬件连接需要注意两点。一是 DHT11 模块的数据引脚必须外接上拉电阻部分廉价模块自带但如果是裸传感器就需要自行在数据线与 VCC 之间加 4.7kΩ 电阻。二是 VCC 供电要稳定DHT11 在通信瞬间的电流变化会在长导线上产生压降导致逻辑电平识别错误。使用面包板长跳线时最容易出现这个问题。调试过程中最有效的工具是 Vivado 内置的 ILA集成逻辑分析仪工程里的vivado_ila.png展示了抓取到的波形。ILA 的采样时钟选择系统 50MHz 时钟触发条件可以设置为data_valid 1这样就能在数据校验完成的瞬间截获完整的数据位波形。如果抓到的数据始终是 0xFF 或 0x00先检查总线电平是否在上拉范围内再检查状态机的时序计数是否按照 50MHz 计算。需要注意的是ILA 会增加少量 LUT 资源占用综合实现时如果出现时序违例可以适当缩小采样深度例如从 1024 减到 512。5. 还原 40 位数据帧与校验算法的边界处理DHT11 一次完整传输 40 位数据格式依次是湿度整数、湿度小数、温度整数、温度小数、校验和每个字段 8 位。校验和等于前四个字节之和的低 8 位。在dht11.v中数据位的接收通过一个位计数器bit_cnt实现每收到一个完整的数据位就把它移位寄存器的方式写入rx_data。数据位判断的时序逻辑是在DATA_HIGH状态下计时器从零开始计数高电平持续到DATA_HIGH_CNT时判定是否为逻辑 1。判定阈值不能单纯用绝对值而要考虑时钟偏差和温度漂移。通常的做法是设置一个中间阈值例如高电平计数大于 2000约 40μs判定为 1否则判定为 0。always (posedge clk or negedge rst_n) begin if (!rst_n) begin rx_byte 8b0; end else if (bit_cnt 8 data_phase RECEIVING) begin rx_byte {rx_byte[6:0], dht11_sync}; end end这一段代码实现的是逐位拼接。每接收到一个有效位rx_byte左移一位并把当前电平值填入最低位。dht11_sync是对dht11_data做的两级同步信号用于消除 IO 引脚上的亚稳态。实际工程中不能直接使用原始dht11_data作为状态机跳转的条件这一点在高频时钟下尤为重要虽然 50MHz 不算高频但养成同步的习惯能避免很多随机性故障。校验逻辑放在所有位接收完成后执行wire check_ok (humi_reg humi_dec_reg temp_reg temp_dec_reg) checksum_reg; assign data_valid (state DONE) check_ok;如果校验失败data_valid不会拉高上层模块可以据此判断这次读取无效。这里有一个比较常见的误用——有人会把校验失败当成传感器损坏实际上 DHT11 在每次读取间隔过短时小于 1 秒很容易出现数据线上的电平干扰导致校验失败。因此我一般会在上层状态机中加入最小读取间隔逻辑比如用 1ms 计数器保证两次触发之间至少有 500ms 的间隔而不是在 DHT11 驱动内部去限制。仿真的work目录里包含了 Modelsim 编译生成的库文件这部分内容不需要手动编辑但如果换用不同版本的 Modelsim建议删除work目录后重新执行vsim.bat。Modelsim 版本差异主要体现在对 SystemVerilog 文件的扩展名支持上debouncer.sv用.sv后缀如果编译时报告模块找不到检查compile.tcl中的文件列表是否吃掉了debouncer.sv或者将其改为.v后缀并删除其中的 SV 特有语法。最终的时序验证方法是把采样波形导出来逐位核对rx_data的每一位与 DHT11 数据手册的时序图对齐。打开wave.do配置好的波形窗展开dht11_data信号并显示为模拟值可以看到每个数据位的高电平宽度分布逻辑 1 和逻辑 0 的一半宽度差异会直观地落在阈值两侧。配合screenshot.png中预设的波形界面即使第一次接触这个工程按这个步骤也能在半小时内完成仿真验证并进入上板流程。本文还有配套的精品资源点击获取
返回列表