告别时序图恐惧!用Verilog在FPGA上驱动DS18B20的保姆级实战(附完整代码)

张开发
2026/4/16 23:02:18 15 分钟阅读

分享文章

告别时序图恐惧!用Verilog在FPGA上驱动DS18B20的保姆级实战(附完整代码)
告别时序图恐惧用Verilog在FPGA上驱动DS18B20的保姆级实战第一次接触DS18B20温度传感器时我被它复杂的单总线协议吓到了。那些微妙级的时序要求、精确的延时控制还有状态跳转的逻辑让我这个FPGA新手望而却步。直到我亲手用Verilog实现了一个完整的驱动控制器才发现原来魔鬼都在细节里——而细节完全可以用代码驯服。本文将带你从零开始用状态机的方式建模DS18B20的所有操作时序。不同于单纯讲解协议原理的理论文章我们会聚焦在如何用Verilog代码实现那些看似复杂的时序逻辑。你将得到一个完整可用的DS18B20控制器模块带注释的Verilog源码精确控制微妙级延时的两种实用方案基于Xilinx ILA的实时波形调试技巧可复用的IP核封装方法1. 单总线协议的精髓与Verilog建模要点DS18B20的通信完全依赖一根双向数据线DQ这种单总线设计节省了IO资源但也带来了严格的时序要求。协议层的关键在于初始化序列主设备发送复位脉冲480-960us低电平从设备回应存在脉冲60-240us低电平读写时序写0需要保持60us以上的低电平写1则在15us内释放总线读时序需要在15us内完成采样// 单总线状态机基本框架 localparam [3:0] IDLE 4d0, RESET_LOW 4d1, RESET_WAIT 4d2, PRESENCE_DETECT 4d3, WRITE_0 4d4, WRITE_1 4d5, READ_BIT 4d6;提示单总线协议的所有操作都基于精确的延时控制FPGA实现时需要特别注意时钟计数器的位宽选择2. 精确延时控制的两种实现方案2.1 时钟周期计数器法假设系统时钟为50MHz周期20ns要实现480us的复位低电平// 计算需要的时钟周期数 parameter RESET_LOW_CYCLES 480_000 / 20; // 24000个周期 reg [15:0] delay_cnt; // 足够计数到480us always (posedge clk) begin if (state RESET_LOW) begin if (delay_cnt RESET_LOW_CYCLES-1) delay_cnt delay_cnt 1; else begin delay_cnt 0; state RESET_WAIT; end end end2.2 时钟分频法对于需要重复使用的固定延时可以预先分频生成特定频率的使能信号// 生成1us间隔的使能脉冲 reg [5:0] us_cnt; wire us_tick (us_cnt 49); // 50MHz/501MHz (1us) always (posedge clk) begin us_cnt (us_tick) ? 0 : us_cnt 1; end // 在状态机中使用 always (posedge clk) begin if (us_tick) begin case(state) RESET_LOW: if (reset_cnt 480) reset_cnt reset_cnt 1; else state RESET_WAIT; endcase end end两种方法对比方法精度资源占用适用场景周期计数高较高单一长延时时钟分频中低重复短延时3. 完整的状态机实现3.1 初始化序列always (posedge clk or posedge rst) begin if (rst) begin state IDLE; dq_out 1b1; // 默认释放总线 end else begin case(state) IDLE: if (start_init) begin state RESET_LOW; delay_cnt 0; end RESET_LOW: begin dq_out 1b0; // 拉低总线 if (delay_cnt RESET_LOW_CYCLES-1) begin state RESET_WAIT; dq_out 1b1; // 释放总线 delay_cnt 0; end else delay_cnt delay_cnt 1; end RESET_WAIT: if (delay_cnt PRESENCE_WAIT_CYCLES-1) begin if (!dq_in) // 检测到存在脉冲 presence 1b1; state IDLE; end else delay_cnt delay_cnt 1; endcase end end3.2 读写时序实现写时序的关键在于精确控制低电平持续时间WRITE_0: begin dq_out 1b0; if (delay_cnt WRITE_0_CYCLES-1) begin dq_out 1b1; state WRITE_RECOVERY; delay_cnt 0; end else delay_cnt delay_cnt 1; end WRITE_1: begin dq_out 1b0; if (delay_cnt WRITE_1_LOW_CYCLES-1) begin dq_out 1b1; state WRITE_RECOVERY; delay_cnt 0; end else delay_cnt delay_cnt 1; end读时序则需要主设备在15us内完成采样READ_BIT: begin dq_out 1b0; // 启动读时序 if (delay_cnt READ_SLOT_CYCLES-1) begin bit_data dq_in; // 在15us时刻采样 dq_out 1b1; state READ_RECOVERY; delay_cnt 0; end else delay_cnt delay_cnt 1; end4. 完整驱动模块设计与封装4.1 顶层模块接口module ds18b20_driver ( input wire clk, // 50MHz系统时钟 input wire rst, // 异步复位 inout wire dq, // 双向数据线 input wire start, // 启动温度转换 output reg [15:0] temp_data, // 温度数据输出 output reg temp_valid, // 数据有效标志 output reg error // 通信错误标志 ); // 三态总线控制 reg dq_out_en; reg dq_out_val; assign dq dq_out_en ? dq_out_val : 1bz; // 内部状态机实现... endmodule4.2 温度读取流程控制完整的温度获取需要遵循特定命令序列初始化 → 跳过ROMCCh→ 启动温度转换44h等待转换完成典型750ms12位分辨率初始化 → 跳过ROMCCh→ 读暂存器BEh读取前两个字节的温度数据// 温度转换状态机片段 localparam [3:0] CONV_START 4d0, CONV_WAIT 4d1, READ_START 4d2, READ_DATA 4d3; always (posedge clk) begin case(temp_state) CONV_START: if (init_done cmd_done) begin send_byte(8h44); // 启动转换命令 temp_state CONV_WAIT; wait_cnt 0; end CONV_WAIT: if (wait_cnt CONV_TIME_12BIT) begin temp_state READ_START; end else wait_cnt wait_cnt 1; READ_START: if (init_done cmd_done) begin send_byte(8hBE); // 读暂存器命令 temp_state READ_DATA; byte_cnt 0; end endcase end5. 实战调试技巧用ILA抓取波形当通信出现问题时实时波形分析是最直接的调试手段。以Xilinx Vivado的ILA为例设置触发条件通常在初始化序列的开始位置设置触发添加关键信号主设备DQ输出从设备DQ响应状态机当前状态时间基准设置为us级便于观察时序关系# Vivado ILA添加探针示例 create_debug_core u_ila ila set_property C_DATA_DEPTH 8192 [get_debug_cores u_ila] # 添加监测信号 set_property port_width 1 [get_debug_ports u_ila/clk] connect_debug_port u_ila/clk [get_nets clk] set_property port_width 4 [get_debug_ports u_ila/probe0] connect_debug_port u_ila/probe0 [get_nets state]典型问题诊断无存在脉冲响应检查上拉电阻通常4.7kΩ、电源电压、硬件连接读写数据错误核对时序参数特别是15us的采样窗口状态机卡死添加超时机制确保异常时能自动恢复6. 进阶封装可复用IP核将DS18B20驱动封装为IP核可以方便地在不同项目中复用添加AXI-Lite接口用于寄存器配置实现中断机制通知温度数据就绪支持多传感器并联通过ROM匹配// AXI-Lite接口示例 module ds18b20_axi_wrapper ( input wire s_axi_aclk, input wire s_axi_aresetn, // AXI-Lite接口信号... inout wire dq ); // 寄存器映射 reg [15:0] temp_value; reg temp_valid; reg start_conv; // AXI寄存器读写逻辑... // 实例化底层驱动 ds18b20_driver u_driver ( .clk(s_axi_aclk), .rst(~s_axi_aresetn), .dq(dq), .start(start_conv), .temp_data(temp_value), .temp_valid(temp_valid) ); // 中断生成逻辑... endmodule在Vivado中打包IP时记得定义清晰的接口文档提供时序约束文件包含测试工程作为验证参考7. 性能优化与异常处理7.1 低功耗设计技巧动态调整采样频率非关键应用可降低采样率电源门控完成测量后切断传感器供电时钟门控通信间隙暂停相关逻辑时钟// 时钟门控示例 reg measure_en; wire gated_clk clk measure_en; always (posedge clk) begin if (idle_state) measure_en 1b0; else if (start_measure) measure_en 1b1; end7.2 鲁棒性增强添加CRC校验DS18B20的64位ROM和9字节暂存器都包含CRC实现超时重试机制总线竞争检测与恢复// 超时计数器 reg [31:0] timeout_cnt; always (posedge clk) begin if (state ! next_state) timeout_cnt 0; else if (timeout_cnt TIMEOUT_MAX) timeout_cnt timeout_cnt 1; else state IDLE; // 超时复位 end8. 实测案例温室监控系统集成在一个实际农业监测项目中我们使用Cyclone 10 LP FPGA驱动了8个DS18B20传感器。关键实现细节采用跳过ROM→启动转换→匹配ROM→读取数据的流程每个传感器有独立的校准偏移量存储在FPGA的ROM中通过UART上传数据到上位机// 多传感器读取调度 genvar i; generate for (i0; i8; ii1) begin : sensor_array always (posedge clk) begin if (sensor_select i) begin rom_match(rom_codes[i]); // 发送匹配ROM命令 read_temperature(); temp_data[i] corrected_temp; end end end endgenerate遇到的典型问题及解决方案问题1长导线引入噪声导致通信失败解决方案缩短导线长度在传感器端添加100nF去耦电容问题2多个传感器响应冲突解决方案严格遵循初始化→ROM匹配→功能命令的流程问题3温度转换期间功耗突增解决方案错开各传感器的转换启动时间

更多文章