【FPGA】基于DS18B20的单总线温度监测系统设计与实现
1. 从零开始为什么选择FPGA和DS18B20来玩转温度监测如果你对电子DIY或者嵌入式开发感兴趣肯定听说过温度传感器。市面上温度传感器种类繁多有模拟的有数字的有复杂的也有简单的。但说到既好玩又好用还能让你深入理解硬件通信原理的DS18B20绝对是个绕不开的经典。而用FPGA来驱动它更是把这种“好玩”提升到了一个新的层次。我刚开始接触DS18B20的时候也被它那根“神奇的单总线”给迷住了。一根线既能供电又能通信还能挂多个设备这设计简直太巧妙了。但用单片机比如STM32去驱动它虽然网上例程一大堆但总觉得像是调用了一个黑盒子库函数时序是怎么精准控制的状态机是如何跳转的总有点隔靴搔痒的感觉。直到我用FPGA重新实现了一遍才真正有种“哦原来是这样”的通透感。FPGA是什么你可以把它想象成一块“数字乐高”。它内部有成千上万个逻辑门、触发器和布线资源你可以用硬件描述语言比如Verilog来定义它们之间的连接关系从而“搭建”出一个专用的数字电路。这个电路是并行的所有操作几乎可以同时发生速度极快控制时序可以精确到纳秒级别。这正是驱动DS18B20这类对时序要求苛刻的器件的绝佳平台。所以这个项目的核心目标就是用FPGA这块“数字乐高”亲手搭建一个能够与DS18B20传感器“对话”的专用电路实时读取温度并显示出来。整个过程你会清晰地看到每一个比特bit数据是如何在单总线上流动的每一个微妙us的延时是如何被精确生成的。这不仅仅是完成一个功能更是一次对数字逻辑设计和硬件通信协议的深度探险。2. 庖丁解牛彻底读懂DS18B20的“单总线语言”想要让FPGA和DS18B20顺畅交流第一步就是当好一个“翻译官”彻底弄懂DS18B20的“语言”——单总线协议。别被“协议”这个词吓到我们可以把它拆解成几个简单的“对话步骤”。2.1 单总线一根线的艺术DS18B20最核心的特点就是单总线1-Wire。这意味着数据输入输出、甚至供电在寄生供电模式下都通过同一根线完成。这根线通常被标记为DQ。为了实现双向通信主机我们的FPGA需要能够控制这根线输出高低电平也能释放这根线去读取从机DS18B20拉低或拉高的状态。在FPGA里我们通常用一个“三态门”结构来模拟这个功能。// 这是一个典型的三态门实现 assign dq_in dq; // 始终读取总线上的实际电平 assign dq dq_out_en ? dq_out : 1bz; // 使能时输出否则高阻态释放总线这里dq是连接物理引脚的双向线。dq_out_en是我们的控制信号。当它为1时FPGA掌控总线输出dq_out的值0或1当它为0时FPGA“松手”输出高阻态z总线由上拉电阻拉高此时FPGA可以通过dq_in读取DS18B20拉低总线时产生的低电平。这就是单总线双向通信的硬件基础。2.2 关键对话流程复位、写命令、读数据和DS18B20的每一次完整对话都遵循一个固定的三部曲初始化复位- 发送命令 - 读写数据。手册里的时序图是我们的“对话脚本”必须严格遵守。初始化时序这是每次通信前的“握手”。主机FPGA拉低总线至少480微秒us然后释放。DS18B20在等待15-60us后会主动拉低总线60-240us作为回应这个低电平脉冲就是“存在脉冲”Presence Pulse。FPGA需要在合适的窗口检测到这个低电平才能确认传感器在线并准备好通信。这个环节任何时间偏差都可能导致握手失败。写时隙主机向DS18B20写1个比特bit数据。无论是写0还是写1都以主机拉低总线至少1us开始。区别在于接下来的60us窗口期写0主机在拉低1us后继续保持总线为低电平直到60us时隙结束。写1主机在拉低1us后在15us内释放总线输出高阻态由上拉电阻将总线拉高直到60us时隙结束。读时隙主机从DS18B20读取1个比特数据。同样以主机拉低总线至少1us开始。随后主机释放总线并在拉低后的2us到15us这个黄金窗口期内采样总线电平。如果DS18B20想发送0它会持续拉低总线主机采样到低电平。如果DS18B20想发送1它会释放总线总线被上拉电阻拉高主机采样到高电平。所有命令和数据无论是8位的命令码还是16位的温度数据都是低位LSB先发。这一点在编程时顺序千万不能搞错。2.3 核心命令与数据解析对于基本的单点温度读取我们主要用到三个命令跳过ROM命令0xCC当总线上只有一个DS18B20时我们可以跳过复杂的ROM地址匹配过程直接对设备喊话。开始温度转换命令0x44告诉DS18B20“开始测量温度吧”发出这个命令后DS18B20需要一段时间进行模数转换对于12位精度这个时间最长是750毫秒ms。在这期间总线可以被释放去做其他事或者FPGA可以简单地等待。读取暂存器命令0xBE温度转换完成后用这个命令读取DS18B20内部暂存器的数据其中前两个字节就是我们需要的温度值。读回来的16位数据怎么理解呢它是一个有符号的整数以0.0625℃为步进。最低4位是小数部分。举个例子如果读到的16进制数是0x0191二进制0000 0001 1001 0001。高5位bit15-bit11是符号位0代表正温度。中间7位bit10-bit4是整数部分。低4位bit3-bit0是小数部分。 计算过程0x0191的十进制是401。401 * 0.0625 25.0625℃。在代码里我们通常先判断符号位如果是负数则取补码然后直接将这16位数据视为一个整数乘以625再除以10000就能得到带三位小数的温度值了25.0625℃。3. 核心设计用状态机为FPGA注入“灵魂”理解了协议接下来就要用Verilog在FPGA里构建一个能够自动执行上述“对话脚本”的智能模块。这里有限状态机FSM是我们的不二之选。它能让杂乱无章的时序控制变得条理清晰。我强烈建议将状态机分为主状态机和从状态机两层这样结构更清晰调试也更容易。3.1 主状态机掌控全局的“导演”主状态机负责整个通信流程的宏观调度。我们可以把它设计成一条清晰的流水线。下面这个表格概括了主状态机的核心状态和任务状态状态描述主要任务与跳转条件M_IDLE空闲状态系统起点收到启动信号后进入复位阶段。M_RESET发送复位脉冲拉低总线480us完成后跳转到释放状态。M_RELEA主机释放总线释放总线并等待15-60us完成后跳转。M_PRESE检测存在脉冲在60-240us窗口内检测从机回应的低电平确认后跳转。M_SKROM发送跳过ROM命令逐比特发送0xCC命令8位发完后根据标志跳转到温度转换或读取命令。M_CTCMD发送开始转换命令逐比特发送0x44命令启动温度转换。M_WAITC等待转换完成延时等待750ms12位精度让DS18B20完成测量。M_RDCMD发送读取命令再次经过复位、存在脉冲后发送0xBE命令。M_RDTEM读取温度数据逐比特读取16位温度数据每读一位都依赖从状态机配合。M_DONE完成状态数据读取完毕输出有效信号并回到空闲状态。主状态机的设计关键在于精确的计时和明确的状态跳转条件。每一个状态都需要一个独立的计数器来严格满足协议要求的时间。比如在M_RESET状态计数器要数够480个1us的时钟周期。跳转条件通常是“当前状态 计数器达到规定值 可能还有其他条件”。3.2 从状态机精细操作的“演员”主状态机决定了“现在要干什么”比如“现在要发送一个命令字节”而从状态机则负责“具体怎么干好这一个比特的读写”。它被主状态机调用专门处理最底层的读写时隙。从状态机同样有自己的状态循环S_IDLE空闲等待主状态机发出读写请求。S_LOW主机拉低总线开始一个时隙持续约2us。S_MASWR或S_MASRD根据主状态机指令进入写时隙或读时隙的保持阶段持续约60us。如果是写在此期间根据要写的比特值控制总线电平如果是读则在中间时刻采样总线。S_RELEA主机释放总线结束当前比特操作持续约2us。S_DONE一个比特操作完成。如果整个字节8位或16位没完成则回到S_LOW处理下一个比特如果已完成则回到S_IDLE通知主状态机。这种主从分离的设计好处非常明显主状态机逻辑干净只关心流程从状态机复用性高无论是发送命令的8个比特还是读取温度的16个比特都调用同一套精细的比特操作流程大大减少了代码冗余和出错概率。3.3 时间参数一切稳定的基石DS18B20通信的稳定性完全建立在精准的时序之上。在FPGA中我们通常用一个基准时钟比如50MHz周期20ns来产生所有需要的延时。下面是一些最关键的参数我强烈建议你把它们做成模块参数方便调整parameter CLK_FREQ 50_000_000; // 50MHz系统时钟 parameter TIME_1US CLK_FREQ / 1_000_000; // 计算1us需要的时钟周期数这里是50 parameter RESET_TIME 480 * TIME_1US; // 480us 复位脉冲 parameter M_RELEA_TIME 20 * TIME_1US; // 20us 主机释放等待在15-60us内即可 parameter PRESE_TIME 200 * TIME_1US; // 200us 检测存在脉冲的窗口 parameter WAITC_TIME 750_000 * TIME_1US; // 750ms 温度转换等待12位精度 parameter LOW_TIME 2 * TIME_1US; // 2us 读写时隙起始低电平时间 parameter WRRD_TIME 60 * TIME_1US; // 60us 读写时隙位周期 parameter S_RELEA_TIME 3 * TIME_1US; // 3us 读写时隙结束释放时间在代码里你需要一个1us的基准计数器然后用这个基准计数器在各个状态里数出对应的时长。特别注意750ms的转换等待时间很长对应的计数器会是一个很大的值50MHz下是37,500,000确保你的计数器位宽足够至少26位。4. 代码实战手把手搭建Verilog驱动模块理论说得再多不如一行代码来得实在。下面我们就基于上面的状态机设计来勾勒核心驱动模块ds18b20_driver.v的骨架。我会重点解释几个容易踩坑的地方。4.1 状态定义与跳转逻辑首先我们用独热码One-Hot或二进制码定义好主从状态机的所有状态。独热码虽然占用触发器多但译码简单在不太复杂的状态机中很常用。// 主状态机定义示例为独热码 localparam M_IDLE 10b00000_00001; localparam M_RESET 10b00000_00010; // ... 其他状态 localparam M_DONE 10b10000_00000; reg [9:0] m_state_c, m_state_n; // 当前状态和下一个状态 // 状态转移 always 块三段式风格推荐 always (posedge clk or negedge rst_n) begin if(!rst_n) m_state_c M_IDLE; else m_state_c m_state_n; end always (*) begin m_state_n m_state_c; // 默认保持 case(m_state_c) M_IDLE: if(start_en) m_state_n M_RESET; M_RESET: if(cnt_reset_done) m_state_n M_RELEA; M_RELEA: if(cnt_relea_done) m_state_n M_PRESE; M_PRESE: begin if(cnt_prese_done slave_ack 0) // 检测到存在脉冲低电平 m_state_n M_SKROM; else if(cnt_prese_done) // 超时未检测到 m_state_n M_IDLE; // 回到空闲可加入错误处理 end // ... 其他状态跳转 default: m_state_n M_IDLE; endcase end关键点1存在脉冲检测。在M_PRESE状态我们启动一个200us的计数器并在计数器计到约60us这个点处于DS18B20拉低脉冲的稳定期内时采样dq_in。如果采到低电平slave_ack0说明传感器响应正常。关键点2命令发送与数据读取循环。在M_SKROM、M_CTCMD、M_RDCMD和M_RDTEM这些状态主状态机需要调用从状态机来完成每个比特的操作。通常我们会设置一个位计数器cnt_bit当从状态机完成一个比特操作s_done2s_idle时cnt_bit加1。当cnt_bit计满8或16时说明一个字节或一个字操作完成主状态机才能跳转到下一个状态。4.2 三态门控制与数据采样这是与物理总线交互的核心。// 三态门控制逻辑 always (posedge clk or negedge rst_n) begin if(!rst_n) begin dq_out_en 1b0; dq_out 1b1; end else begin case(m_state_c) M_RESET: begin // 复位期间主机强制拉低 dq_out_en 1b1; dq_out 1b0; end M_SKROM, M_CTCMD, M_RDCMD: begin // 发送命令阶段 // 当从状态机处于需要主机驱动的阶段时使能输出 if(s_state_c inside {S_LOW, S_MASWR}) begin dq_out_en 1b1; // 在S_MASWR状态输出要发送的比特 command[cnt_bit] dq_out (s_state_c S_MASWR) ? command[cnt_bit] : 1b0; end else begin dq_out_en 1b0; // 其他时间释放总线 end end M_RDTEM: begin // 读取数据阶段 // 只在起始拉低阶段驱动总线 if(s_state_c S_LOW) begin dq_out_en 1b1; dq_out 1b0; end else begin dq_out_en 1b0; // 采样阶段必须释放总线 end end default: begin dq_out_en 1b0; dq_out 1b1; end endcase end end // 数据采样逻辑在读时隙 always (posedge clk or negedge rst_n) begin if(!rst_n) begin temperature_data 16b0; end else if(s_state_c S_MASRD) begin // 在读时隙的中间时刻例如拉低后13us采样此时数据稳定 if(cnt_sample 13 * TIME_1US) begin // 低位先收所以用位计数器作为索引 temperature_data[cnt_bit] dq_in; end end end最大的坑采样时刻读时隙中DS18B20在主机拉低总线后会在约15us内将有效数据放到总线上。主机必须在2us后、15us前完成采样。我个人的经验是选择在拉低后的13us左右采样这是一个比较稳妥的时间点。你需要根据你的TIME_1US精度来设置这个采样点计数器。4.3 数据处理与显示模块从DS18B20读出的原始数据需要转换。我们写一个简单的处理模块ds18b20_ctrl.v。module ds18b20_ctrl ( input clk, input rst_n, input [15:0] din, // 输入的16位原始温度数据 input din_vld, // 数据有效信号 output reg [19:0] dout // 输出为整数部分12位和小数部分8位代表0.xx ); reg [15:0] raw_data; wire sign_bit raw_data[15]; // 符号位 wire [10:0] abs_value sign_bit ? (~raw_data[10:0] 1) : raw_data[10:0]; // 取绝对值补码转换 always (posedge clk or negedge rst_n) begin if(!rst_n) raw_data 16b0; else if(din_vld) raw_data din; end // 计算温度绝对值 * 0.0625然后转换为便于显示的格式 // 例如abs_value401 (0x0191) 401*0.062525.0625 // 我们可以输出 250625或者分离整数和小数 wire [23:0] temp_scaled abs_value * 625; // 相当于放大了10000倍 // temp_scaled 401 * 625 250625 // 整数部分 250625 / 10000 25 // 小数部分 250625 % 10000 0625 wire [7:0] integer_part temp_scaled / 10_000; wire [15:0] decimal_part temp_scaled % 10_000; always (posedge clk or negedge rst_n) begin if(!rst_n) dout 20b0; else if(din_vld) begin // 组合输出例如高12位是整数低8位是小数*100 dout {integer_part, decimal_part[11:4]}; end end endmodule这个模块将带符号的16位原始数据转换为带符号的整数和小数部分方便后续送数码管或LCD显示。显示驱动模块seg_driver.v就是标准的数码管动态扫描将处理后的温度数据的每一位解码成7段码并循环点亮对应的数码管。这里要注意小数点的定位让显示看起来是“XX.X”度的格式。5. 仿真与上板让设计在现实中跑起来代码写完了千万别急着上板。在FPGA开发中仿真Simulation是拯救你头发的最重要一步。我用的是Modelsim你也可以用Vivado/Quartus自带的仿真工具。5.1 仿真验证在电脑里“预演”为驱动模块写一个测试平台Testbench主要做两件事模拟DS18B20的响应在测试平台中模拟一个“虚拟的”DS18B20。当看到FPGA发送的复位脉冲后在恰当的时间拉低总线产生存在脉冲。在收到读命令后按照协议返回一个预设的温度数据比如0x0191代表25.0625℃。观察状态机跳转和时序通过波形图仔细查看m_state_c和s_state_c是否按照我们设计的流程跳转。重点检查复位脉冲的宽度是不是准确的480us存在脉冲检测窗口是否在60-240us之间写时隙和读时隙的波形是否符合协议图特别是读时隙的采样点是否在正确位置750ms的等待时间是否被正确计时仿真时可以把WAITC_TIME改小比如750us以加快仿真速度。// 测试平台中模拟从机响应的部分代码片段 initial begin dq_bus 1bz; // 初始高阻 #(CLOCK_CYCLE * 1000); forever begin wait (dq_bus 1b0); // 等待主机拉低复位开始 #480_000; // 等待480us复位结束 #30_000; // 等待30us后 dq_bus 1b0; // 从机拉低产生存在脉冲 #120_000; // 持续120us dq_bus 1bz; // 释放总线 // ... 后续模拟对命令的响应和数据返回 end end通过仿真你能提前发现90%以上的逻辑错误和时序问题。我当初就是在这里发现读时隙采样点设早了导致数据采样不稳定。5.2 上板调试与真实世界对接仿真通过后就可以进行综合、布局布线生成比特流文件下载到FPGA开发板了。上板调试是另一番景象。首先检查硬件连接DS18B20的三个引脚VDD, DQ, GND务必连接正确。DQ引脚需要接一个4.7kΩ的上拉电阻到VCC3.3V或5V视传感器型号和FPGA IO电压而定。这是单总线正常工作的必要条件没有上拉电阻总线无法被拉高通信必然失败。使用逻辑分析仪或示波器这是硬件调试的“眼睛”。将探头连接到DQ线触发设置为下降沿。你应该能清晰地看到一个长达480us的低电平复位脉冲。一个60-240us的低电平存在脉冲。后续一个个标准的写时隙和读时隙波形。 通过测量这些脉冲的宽度可以最直接地验证你的时序控制是否精确。如果波形杂乱或宽度不对回头检查你的计数器逻辑和状态机跳转条件。利用FPGA的在线调试工具像Xilinx的ChipScopeVivado里叫ILA或Intel的SignalTap可以把FPGA内部的关键信号如状态机状态m_state_c、位计数器cnt_bit、原始温度数据temperature_data实时抓取出来显示在电脑上。这比看LED灯或者数码管要直观无数倍能帮你快速定位问题出在哪个状态。常见问题排查数码管无显示或显示乱码首先用ILA看din_vld信号有没有拉高以及temperature_data寄存器里有没有抓到正确的数据。如果数据不对问题出在通信链路。显示温度固定不变或为0很可能通信根本没成功。用示波器看初始化阶段有没有存在脉冲。如果没有检查复位脉冲宽度和上拉电阻。温度值跳变剧烈可能是电源噪声干扰确保电源稳定并在VDD和GND之间加一个0.1uF的旁路电容。也可能是采样时刻不准确导致读到的比特位出错。调试是一个需要耐心和经验的过程。每解决一个问题你对整个系统的理解就会加深一层。当我第一次在数码管上看到稳定、准确的室温显示时那种成就感是调通一个单片机库函数无法比拟的。整个系统从传感器、到FPGA内部的定制电路、再到显示输出完全在自己的掌控之中这种感觉就是硬件设计的魅力所在。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2410957.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!