当前位置: 首页 > news >正文

【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 : 1'bz; // 使能时输出,否则高阻态释放总线

这里,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等待转换完成延时等待750ms(12位精度),让DS18B20完成测量。
M_RDCMD发送读取命令再次经过复位、存在脉冲后,发送0xBE命令。
M_RDTEM读取温度数据逐比特读取16位温度数据,每读一位都依赖从状态机配合。
M_DONE完成状态数据读取完毕,输出有效信号,并回到空闲状态。

主状态机的设计关键在于精确的计时明确的状态跳转条件。每一个状态都需要一个独立的计数器,来严格满足协议要求的时间。比如在M_RESET状态,计数器要数够480个1us的时钟周期。跳转条件通常是“当前状态 && 计数器达到规定值 && (可能还有其他条件)”。

3.2 从状态机:精细操作的“演员”

主状态机决定了“现在要干什么”(比如“现在要发送一个命令字节”),而从状态机则负责“具体怎么干好这一个比特的读写”。它被主状态机调用,专门处理最底层的读写时隙。

从状态机同样有自己的状态循环:

  1. S_IDLE:空闲,等待主状态机发出读写请求。
  2. S_LOW:主机拉低总线开始一个时隙,持续约2us。
  3. S_MASWRS_MASRD:根据主状态机指令,进入写时隙或读时隙的保持阶段,持续约60us。如果是写,在此期间根据要写的比特值控制总线电平;如果是读,则在中间时刻采样总线。
  4. S_RELEA:主机释放总线,结束当前比特操作,持续约2us。
  5. 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 = 10'b00000_00001; localparam M_RESET = 10'b00000_00010; // ... 其他状态 localparam M_DONE = 10'b10000_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_ack=0),说明传感器响应正常。

关键点2:命令发送与数据读取循环。在M_SKROMM_CTCMDM_RDCMDM_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 <= 1'b0; dq_out <= 1'b1; end else begin case(m_state_c) M_RESET: begin // 复位期间,主机强制拉低 dq_out_en <= 1'b1; dq_out <= 1'b0; end M_SKROM, M_CTCMD, M_RDCMD: begin // 发送命令阶段 // 当从状态机处于需要主机驱动的阶段时,使能输出 if(s_state_c inside {S_LOW, S_MASWR}) begin dq_out_en <= 1'b1; // 在S_MASWR状态,输出要发送的比特 command[cnt_bit] dq_out <= (s_state_c == S_MASWR) ? command[cnt_bit] : 1'b0; end else begin dq_out_en <= 1'b0; // 其他时间释放总线 end end M_RDTEM: begin // 读取数据阶段 // 只在起始拉低阶段驱动总线 if(s_state_c == S_LOW) begin dq_out_en <= 1'b1; dq_out <= 1'b0; end else begin dq_out_en <= 1'b0; // 采样阶段必须释放总线! end end default: begin dq_out_en <= 1'b0; dq_out <= 1'b1; end endcase end end // 数据采样逻辑(在读时隙) always @(posedge clk or negedge rst_n) begin if(!rst_n) begin temperature_data <= 16'b0; 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 <= 16'b0; else if(din_vld) raw_data <= din; end // 计算温度:绝对值 * 0.0625,然后转换为便于显示的格式 // 例如:abs_value=401 (0x0191), 401*0.0625=25.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 <= 20'b0; 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),主要做两件事:

  1. 模拟DS18B20的响应:在测试平台中,模拟一个“虚拟的”DS18B20。当看到FPGA发送的复位脉冲后,在恰当的时间拉低总线产生存在脉冲。在收到读命令后,按照协议返回一个预设的温度数据(比如0x0191代表25.0625℃)。
  2. 观察状态机跳转和时序:通过波形图,仔细查看m_state_cs_state_c是否按照我们设计的流程跳转。重点检查:
    • 复位脉冲的宽度是不是准确的480us?
    • 存在脉冲检测窗口是否在60-240us之间?
    • 写时隙和读时隙的波形是否符合协议图?特别是读时隙的采样点是否在正确位置?
    • 750ms的等待时间是否被正确计时?(仿真时可以把WAITC_TIME改小,比如750us,以加快仿真速度)。
// 测试平台中模拟从机响应的部分代码片段 initial begin dq_bus = 1'bz; // 初始高阻 #(CLOCK_CYCLE * 1000); forever begin wait (dq_bus === 1'b0); // 等待主机拉低(复位开始) #480_000; // 等待480us复位结束 #30_000; // 等待30us后 dq_bus = 1'b0; // 从机拉低产生存在脉冲 #120_000; // 持续120us dq_bus = 1'bz; // 释放总线 // ... 后续模拟对命令的响应和数据返回 end end

通过仿真,你能提前发现90%以上的逻辑错误和时序问题。我当初就是在这里发现读时隙采样点设早了,导致数据采样不稳定。

5.2 上板调试:与真实世界对接

仿真通过后,就可以进行综合、布局布线,生成比特流文件下载到FPGA开发板了。上板调试是另一番景象。

首先检查硬件连接:DS18B20的三个引脚(VDD, DQ, GND)务必连接正确。DQ引脚需要接一个4.7kΩ的上拉电阻到VCC(3.3V或5V,视传感器型号和FPGA IO电压而定)。这是单总线正常工作的必要条件,没有上拉电阻,总线无法被拉高,通信必然失败。

使用逻辑分析仪或示波器:这是硬件调试的“眼睛”。将探头连接到DQ线,触发设置为下降沿。你应该能清晰地看到:

  • 一个长达480us的低电平复位脉冲。
  • 一个60-240us的低电平存在脉冲。
  • 后续一个个标准的写时隙和读时隙波形。 通过测量这些脉冲的宽度,可以最直接地验证你的时序控制是否精确。如果波形杂乱或宽度不对,回头检查你的计数器逻辑和状态机跳转条件。

利用FPGA的在线调试工具:像Xilinx的ChipScope(Vivado里叫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.cnnetsun.cn/news/1280789.html

相关文章:

  • 【嵌入式】树莓派上基于NCNN的YOLOv5模型优化与性能调优
  • 多平台直播效率提升指南:OBS Multi RTMP插件全方位应用
  • 基于GD32VW553的WS2812E彩灯驱动移植与SPI时序控制详解
  • 【ARMv8架构解析】NIC-400:芯片内部的AMBA高速公路
  • Docker 快速部署 CentOS7 开发环境指南
  • Kaggle训练模型不断连的终极配置指南
  • 2025CCPC河北省赛解题思路与实战技巧分享
  • 虚拟串口软件VSPD在串口调试中的实战应用
  • ecoRoute:纳米级ECO布线中的智能DRC修复与分层设计考量
  • ITK-SNAP实战指南:从二维切片到三维重建的医学影像分析
  • Phi-3 Mini开源镜像实操:GPU显存占用动态监控与告警设置
  • Verilog进阶:2001标准下模块端口的ANSI-C风格实践指南
  • 科研绘图自动化:让学术图表创作效率提升十倍的智能解决方案
  • 效率倍增:基于快马平台快速生成openclaw飞书自动化通知机器人
  • COMSOL Multiphysics 实战解析:电子芯片散热系统设计与优化
  • 从静态TLS内存耗尽到系统级修复:深度剖析libgomp与scikit-learn在ARM平台的兼容性困局
  • 【LDLTS】从原理到实践:解锁半导体缺陷分析的“高分辨率”密码
  • 计算机毕业设计springboot热点推荐个性化新闻系统 基于SpringBoot的个性化内容分发与热点聚合系统 SpringBoot驱动的用户兴趣建模与实时新闻推荐引擎
  • V免签二开实战:从源码到易支付接口的无缝集成指南
  • SAP物料主数据增强实战:BADI_MATERIAL_CHECK与BADI_MATERIAL_REF应用解析
  • 基于CW32F030的低成本电压电流双通道测量仪设计
  • 便携式三合一电源音频终端硬件设计详解
  • AudioSeal部署案例:教育机构AI语音课件自动水印+教师溯源管理系统
  • Stable-Diffusion-V1-5 保姆级部署:Windows系统C盘空间清理与GPU环境准备
  • 突破Mac NTFS读写限制:Nigate工具全方位实战指南
  • 《QGIS快速入门与应用基础》217:新建布局(名称/纸张大小设置)
  • SecGPT-14B开源可部署:无需API密钥的本地化网络安全大模型实践
  • 多语言+情感+事件检测:SenseVoice-Small ONNX镜像入门必看
  • 使用SolidWorks模型渲染图作为输入:Wan2.1-UMT5实现产品演示动画
  • Dify新手必看:如何用ollama插件快速搭建本地AI聊天应用(附详细截图)