本文主要通过驱动DS18B20,获取实时温度,并将其显示在数码管上,然后通过按键调整温度报警阈值,从而实现功能。
一、DS18B20内部构成
1.序列号
DS18B20内部储着其独一无二的序列号64位ROM和单总线接口(1_wire总线)
2.高速缓冲区
高速缓存器包含了存储有数字温度结果的2个字节宽度的温度寄存器(LSB和MSB),以及提供了一个字节的高温(TH)和一个字节的低温(TL)温度报警寄存器和一个字节的配置寄存器。 配置寄存器允许用户自定义温度转换精度(9位,10位,11位,12位)。高温和低温温度报警器寄存是非易失性的(EEPROM),其可以在断电的情况下保存。

在配置寄存器(如下图)内如何对精度进行控制:通过修改R1,R2值来改变温度分辨率
无效位也就是LSB中的低位开始数,例如如果精度为0.125,那么在LSB中的第0位无效,后续不进入数据处理模块中。
| r1 | r0 | 分辨率 | 无效位 |
| 1 | 1 | 0.0625 | 无 |
| 1 | 0 | 0.125 | bit0 |
| 0 | 1 | 0.25 | bit0,bit1 |
| 0 | 0 | 0.5 | bit0,bit1,bit2 |

二、DS18B20数字温度传感器概述
2.1 基本特性
- 单总线(1-Wire)接口,仅需一根数据线实现通信与供电
- 独特的64位序列号,支持单总线上挂载多个器件
- 温度测量范围:-55°C 至 +125°C
- 可编程分辨率:9位至12位(0.5°C 至 0.0625°C)
- 内置非易失性报警上下限阈值寄存器
- 寄生供电模式,无需外部电源
2.2 ROM命令详解
ROM命令用于在单总线上识别和选择特定的DS18B20设备,适用于总线上挂载多个传感器的情况。
- 读ROM [33h]:读取总线上唯一一个DS18B20的64位ROM代码。仅当总线上只有一个设备时使用。
- 匹配ROM [55h]:后跟64位ROM代码,用于寻址并匹配总线上特定的DS18B20,后续命令仅对该设备生效。
- 跳过ROM [CCh]:跳过ROM寻址,对总线上的所有DS18B20同时发送后续功能命令。适用于单设备总线或需要广播操作的场景。
- 搜索ROM [F0h]:用于识别总线上所有DS18B20的64位ROM代码,是设备枚举过程的核心命令。
- 警报搜索[ECh]:仅搜索那些温度值超出报警阈值(触发报警标志位)的DS18B20设备,用于快速定位异常测温点。
2.3 功能命令详解
功能命令用于控制DS18B20的核心操作,如启动温度转换、读写数据等。
- 温度转换 [44h]:启动温度转换。在寄生供电模式下,主机需在命令发出后提供强上拉为转换供电。
- 写入暂存器 [4Eh]:向暂存器(地址为2、3、4的字节)写入数据,通常用于设置温度报警阈值(TH, TL)和配置寄存器。
- 读取暂存器 [BEh]:读取暂存器的全部9个字节,包括温度值(字节0,1)、报警阈值(字节2,3)、配置寄存器(字节4)及CRC(字节8)。
- 赋值暂存器 [48h]:将暂存器中的报警阈值(TH, TL)和配置寄存器复制到DS18B20的EEPROM中,实现掉电保存。
- 召回eeprom [B8h]:将EEPROM中保存的报警阈值和配置寄存器数据召回至暂存器。
- 读取供电模式 [B4h]:读取DS18B20的供电模式。总线主机读位:0表示寄生供电,1表示外部供电。
三.单总线(1_wire)时序控制
既然DS18B20是以单总线接口进行通讯的,那么就来讲一讲单总线上的一些时序控制。这里特别做一下声明,由于单总线是双线接口,既可以表示输入,也可以表示输出,所以在verilog中的声明为inout类型,后面提及到了释放总线是通过上拉电阻把总线拉到高阻态,
1.初始化(发送复位脉冲以及存在脉冲)
主机通过拉低总线至少480us后释放总线来表示复位脉冲,然后通过上拉电阻将总线拉高。然后DS18B20检测到总线上升后,等待15到60us后拉低总线60到240us来表示存在脉冲。通过官方文档可知,一个初始化周期至少为960us。

2.写时隙
写入时隙分为两种类型:“写1”时隙与“写0”时隙。总线主控单元使用“写1”时隙向DS18B20写入逻辑1,使用“写0”时隙向DS18B20写入逻辑0。所有写时隙的持续时间均不得少于60us,且相邻写时隙之间应至少留有1us的恢复时间,这里以官方文档为参考,

要生成“写1”时隙,在将1-Wire总线拉低后,总线主控单元必须在15秒内释放该总线;总线释放后,5 kΩ的上拉电阻将使总线电平变为高电平。要生成“写0”时隙,在将1-Wire总线拉低后,总线主控单元必须在该时隙的整个持续时间内保持总线处于低电平状态(至少60us)。
为什么写1时必须在15us内释放总线,写0时必须拉低总线至少60us的原因: DS18B20 在主设备发起写时隙后的 15 us至 60 us时间窗内对单线总线进行采样。若采样期间总线电平为高,则向 DS18B20 写入“1”;若总线电平为低,则向 DS18B20 写入“0”。
3.读时隙
DS18B20 仅在主设备发出读取时隙时才能向其传输数据。因此,主设备必须在发出“读取暂存器[BEh]”或“读取电源 [B4h]”命令后立即生成一个读取时隙,以便 DS18B20 能够提供所请求的数据。此外,主设备亦可在发出“转换温度 [44h]”或“召回 eeprom [B8h]”命令后生成读取时隙,以查询操作状态。所有读取时隙的持续时间均不得短于 60 秒,且相邻时隙之间应至少有 1 秒的恢复时间。读取时隙由主设备发起:该设备将 1-Wire 总线拉低至少 1 us,随后马上释放总线。主设备发起读取时隙后,DS18B20 将开始在总线上传输“1”或“0”。DS18B20 通过保持总线为高电平来传输“1”,通过将总线拉低来传输“0”。在传输“0”时,DS18B20 将在时隙结束时释放总线;随后,上拉电阻将使总线恢复至高电平空闲状态。

来自 DS18B20 的数据在触发读取时隙的下降沿之后的有效期为 15 秒。因此,主设备必须释放总线,然后在时隙开始后 15 秒内对总线状态进行采样。
下图中的Tint,Trc,Tsamples分别表示主机将总线拉低发起读操作的时间,恢复时间和主机采样时间。为了能够得到ds18b20的稳定的数据,尽可能将Tint,Trc拉短(也就是主机拉低总线时间越短越好),Tsamples尽可能靠近15us,

四:DS18B20工作流程
因为我的单总线上只有一个ds18b20,所以我只需要使用rom命令中的跳过rom命令直接锁定我的ds18b20设备。其次就是功能命令,在这个模块中只需要使用到温度转换指令和读取温度指令。最重要的就是初始化,是建立主机与从机交互的桥梁,每完成一次通信,就必须再次初始化。
操作流程:
1. 复位初始化(拉低总线,等待存在脉冲):与芯片建立通信,确认芯片在线
2. 发送ROM命令(匹配ROM/跳过ROM 0xCC ):选中总线上的DS18B20
3. 发送功能命令 0x44 :启动温度转换
4. 等待转换完成
5. 再次复位初始化
6. 发送ROM命令
7. 发送功能命令 0xBE :读取温度寄存器,
8.生成读时隙读取温度。
五.如何在FPGA温度报警
1.设计思路
在设计时,我总共分为了5个模块,温度采集模块,处理数据模块,数码管显示模块,BCD模块,温度报警模块

5.1.1温度采集模块包括
温度采集模块是FPGA与DS18B20通信的核心,负责生成1-Wire总线时序、控制状态机转换、发送命令并读取温度数据。
时序生成:精确产生1μs基准时钟,满足DS18B20的时序要求
状态控制:8状态状态机完整实现DS18B20通信流程
命令发送:正确发送跳过ROM(0xCC)、温度转换(0x44)、读取温度(0xBE)命令
数据采集:在STATE7状态通过读时隙采样16位温度数据
数据处理:将原始二进制数据转换为实际温度值
循环采集:完成一次采集后自动回到初始状态,实现连续温度监测
代码实现:
module ds18b20_1wire (
input wire clk,
input wire rst_n,
inout dq,
output [15:0] data
);
localparam TGrom =8'hcc,//跳过rom
WD_ZH=8'h44,//温度转换命令
read_WD=8'hbe ;//读取温度命令
reg clk_1us;
reg [5:0]cnt_1us;//1us计数器
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
clk_1us<=0;
cnt_1us<=0;
end
else if(cnt_1us==24)begin
cnt_1us<=0;
clk_1us<=~clk_1us;
end
else
cnt_1us<=cnt_1us+1;
end
//初始化(复位脉冲(500us)和存在脉冲(检测到上升沿后等待15到60us将总线拉低60到120us))
//总周期1000us=500+60+240
reg [11:0] cnt;//初始化计数器
always @(posedge clk_1us or negedge rst_n) begin
if(!rst_n)begin
cnt<=0;
end
else if(state==STATE0||state==STATE4)begin
if(cnt==1000-1)
cnt<=0;
else
cnt<=cnt+1;
end
end
reg [6:0]cnt_data;//发送指令计数器/写读时隙周期65;
reg [2:0]cnt_bit;//发送数据位数索引号
always @(posedge clk_1us or negedge rst_n) begin
if(!rst_n)begin
cnt_data<=0;
cnt_bit<=0;
end
else if(state==STATE1||state==STATE2||state==STATE5||state==STATE6) begin
if(cnt_data==65-1)begin
cnt_data<=0;
if(cnt_bit==8-1)
cnt_bit<=0;
else
cnt_bit<=cnt_bit+1;
end
else
cnt_data<=cnt_data+1;
end
end
reg [19:0]cnt_wait;//等待温度转换时间
always @(posedge clk_1us or negedge rst_n ) begin
if(!rst_n)
cnt_wait<=0;
else if(state==STATE3)begin
if(cnt_wait==750000-1)
cnt_wait<=0;
else
cnt_wait<=cnt_wait+1;
end
end
//采样数据存储
reg [3:0] bit_index;
reg [6:0] cnt_data_index;
always @(posedge clk_1us or negedge rst_n) begin
if(!rst_n)begin
bit_index<=0;
cnt_data_index<=0;
end
else if(state==STATE7) begin
if(cnt_data_index==65-1)begin
cnt_data_index<=0;
if(bit_index==16-1)
bit_index<=0;
else
bit_index<=bit_index+1;
end
else
cnt_data_index<=cnt_data_index+1;
end
end
//
reg[3:0] state;
localparam [3:0] STATE0=0,//初始化
STATE1=1,//发送跳过ROM指令
STATE2=2,//发送温度转换指令
STATE3=3,//等待温度转换750ms
STATE4=4,//初始化指令
STATE5=5,//跳过ROM指令
STATE6=6,//读取温度指令
STATE7=7;//读取温度
reg dq_out;
reg en;
always @(posedge clk_1us or negedge rst_n) begin
if(!rst_n)begin
dq_out<=0;
en<=0;
end
else begin
case(state)
STATE0:begin//发送复位脉冲
if(cnt<500-1)begin
dq_out<=0;
en<=1;
end
else begin
dq_out<=0;
en<=0;
end
end
STATE1:begin
if(TGrom[cnt_bit]==0)begin
if(cnt_data<60)begin
en<=1;
dq_out<=0;
end
else begin
en<=0;
dq_out<=0;
end
end
if(TGrom[cnt_bit]==1)begin
if(cnt_data<2)begin//只要在15us内释放总线即可
en<=1;
dq_out<=0;
end
else begin
en<=0;
dq_out<=0;
end
end
end
STATE2:begin
if(WD_ZH[cnt_bit]==0)begin
if(cnt_data<60)begin
en<=1;
dq_out<=0;
end
else begin
en<=0;
dq_out<=0;
end
end
if(WD_ZH[cnt_bit]==1)begin
if(cnt_data<2)begin
en<=1;
dq_out<=0;
end
else begin
en<=0;
dq_out<=0;
end
end
end
STATE3:begin
en <= 0;
dq_out <= 0;
end
STATE4:begin
if(cnt<500-1)begin
dq_out<=0;
en<=1;
end
else
begin
dq_out<=0;
en<=0;
end
end
STATE5:begin
if(TGrom[cnt_bit]==0)begin
if(cnt_data<60)begin
en<=1;
dq_out<=0;
end
else begin
en<=0;
dq_out<=0;
end
end
if(TGrom[cnt_bit]==1)begin
if(cnt_data<2)begin
en<=1;
dq_out<=0;
end
else begin
en<=0;
dq_out<=0;
end
end
end
STATE6:begin
if(read_WD[cnt_bit]==0)begin
if(cnt_data<60)begin
en<=1;
dq_out<=0;
end
else begin
en<=0;
dq_out<=0;
end
end
if(read_WD[cnt_bit]==1)begin
if(cnt_data<2)begin
en<=1;
dq_out<=0;
end
else begin
en<=0;
dq_out<=0;
end
end
end
STATE7:begin
if(cnt_data_index<2)begin
en<=1;
dq_out<=0;
end
else begin
en<=0;
dq_out<=0;
end
end
endcase
end
end
reg [15:0] data_reg;
always @(posedge clk_1us or negedge rst_n) begin
if(!rst_n)
data_reg<=0;
else if(cnt_data_index==13)
data_reg<={dq,data_reg[15:1]};
else
data_reg<=data_reg;
end
reg [15:0] data_out;
always @(posedge clk_1us or negedge rst_n) begin
if(!rst_n)
data_out<=0;
else if(cnt_data_index==65-1&&bit_index==16-1)
data_out<=data_reg;
else
data_out<=data_out;
end
//检测是否有存在脉冲
reg flag;
always @(posedge clk_1us or negedge rst_n) begin
if(!rst_n)
flag<=0;
else if(cnt==600&&dq==0)
flag<=1;
else if(state==STATE2||state==STATE6)
flag<=0;
end
//状态跳转
always @(posedge clk_1us or negedge rst_n) begin
if(!rst_n)
state<=STATE0;
else
case(state)
STATE0:begin
if(cnt==1000-1&&flag==1)
state<=STATE1;
end
STATE1:begin
if(cnt_data==65-1&&cnt_bit==8-1)
state<=STATE2;
end
STATE2:begin
if(cnt_data==65-1&&cnt_bit==8-1)
state<=STATE3;
end
STATE3:begin
if(cnt_wait==750000-1)
state<=STATE4;
end
STATE4:begin
if(cnt==1000-1&&flag==1)
state<=STATE5;
end
STATE5:begin
if(cnt_data==65-1&&cnt_bit==8-1)
state<=STATE6;
end
STATE6:begin
if(cnt_data==65-1&&cnt_bit==8-1)
state<=STATE7;
end
STATE7:begin
if(cnt_data_index==65-1&&bit_index==16-1)
state<=STATE0;
end
endcase
end
assign dq = (en == 1) ? dq_out : 1'bz;
assign data=data_out;
endmodule
5.1.2数据处理模块:
由于开发板只有6个数码管,所以选择使用4个数码管来显示温度,剩下两个数码管用来调节阈值。
所以经过处理后的温度只能有4位,由于在使用ds18b20时,没有特意去控制配置寄存器,所以数据有效位为16位,其中前5位表示符号位,11位是真实的数据位。
必须值得注意的是,ds18b20模块中温度数据(不包括符号位)是以二进制补码存储的,所以进行温度数据处理时,必须要确认温度是否大于0,正数的补码,原码,反码都是其本身,而负数的补码等于是反码加1。
由于reg类型变量是无法存储小数位的,所以采取将原数据温度进行放大100倍(4个数码管正好足够,只要温度不超过99.99)
代码实现:
module data(
input wire clk,
input wire rst_n,
input wire [15:0] data,
output wire [19:0] data_out
);
reg [10:0] data_z;
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
data_z<=0;
else if(data[15]==0)
data_z<=data[10:0];
else if(data[15]==1)begin
data_z<=~(data[10:0]-1);
end
end
assign data_out=data_z*100>>4;//温度放大100,右移操作也就是×0.0625
endmodule
5.1.3 BCD模块(可不采用,直接使用除法计算每一个数码管该显示的数字即可)
代码实现:
module BCD (
input [19:0] data,
output[15:0] data_out
);
reg [35:0] temp;
integer i;
always @(*) begin
temp={16'b0,data};
for(i=0;i<20;i=i+1)begin
if(temp[23:20]>=5)begin
temp[23:20]=temp[23:20]+3;
end
if(temp[27:24]>=5)begin
temp[27:24]=temp[27:24]+3;
end
if(temp[31:28]>=5)begin
temp[31:28]=temp[31:28]+3;
end
if(temp[35:32]>=5)begin
temp[35:32]=temp[35:32]+3;
end
// if(temp[39:36]>=5)begin
// temp[39:36]=temp[39:36]+3;
// end
// if(temp[43:40]>=5)begin
// temp[43:40]=temp[43:40]+3;
// end
temp=temp<<1;
end
end
assign data_out=temp[35:20];
endmodule
5.1.4 温度报警模块
这里我选择了使用3个按键,一个按键控制阈值增加,一个按键控制阈值减少,还有一个按键用来确认阈值。(阈值设置默认30),
温度超过阈值,进行报警,也就是蜂鸣器叫。(使用的是无源蜂鸣器)
代码如下:
module temp_ctrl (
input wire clk,
input wire rst_n,
input wire [2:0] key,
input wire [15:0]data_BCD,
output wire [5:0] data_yuzhi,
output wire buzz
);
wire key_stable1,key_stable2,key_stable3;
key_de u1(
.clk(clk),
.rst_n(rst_n),
.key(key[0]),
.key_stable(key_stable1)
);
key_de u2(
.clk(clk),
.rst_n(rst_n),
.key(key[1]),
.key_stable(key_stable2)
);
key_de u3(
.clk(clk),
.rst_n(rst_n),
.key(key[2]),
.key_stable(key_stable3)
);
reg [5:0]data;
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
data<=30;
end
else if(key_stable1)
data<=data+1;
else if(key_stable2)
data<=data-1;
else
data<=data;
end
reg [5:0] data_out;//按下按键后的真正的阈值;
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
data_out<=30;
end
else if(key_stable3)
data_out<=data;
else
data_out<=data_out;
end
wire[5:0] data_temp;
assign data_temp=data_BCD[15:12]*10+data_BCD[11:8];
reg [15:0] cnt;
reg buzz_z;
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
cnt <= 0;
buzz_z <= 0;
end else if(data_temp >=data_out) begin
cnt <= cnt + 1;
if(cnt == 16'd50000) begin //
cnt <= 0;
buzz_z <= ~buzz_z;
end
end else begin
cnt <= 0;
buzz_z <= 0;
end
end
assign data_yuzhi=data;
assign buzz=buzz_z;
endmodule
按键消抖:
module key_de (
input wire clk,
input wire rst_n,
input wire key,
output wire key_stable
);
//亚稳态
parameter cnt_max=999999;
reg key_z1,key_z2;
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
key_z1<=0;
key_z2<=0;
end
else begin
key_z1<=key;
key_z2<=key_z1;
end
end
wire key_posedge;
wire key_negedge;
assign key_posedge=!key_z1&&key_z2;
assign key_negedge=key_z1&&!key_z2;
reg [19:0] cnt ;//20ms计时
reg flag;
reg cnt_en;
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
cnt<=0;
flag<=1;
cnt_en<=0;
end
else begin
if(key_negedge||key_posedge) begin
cnt<=0;
cnt_en<=1;
end
else if(cnt_en&&cnt<cnt_max)
cnt<=cnt+1;
else if(cnt_en&&cnt==cnt_max)begin
flag=key_z2;
cnt_en<=0;
end
end
end
reg key_z_z1,key_z_z2;
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
key_z_z1<=0;
key_z_z2<=0;
end
else begin
key_z_z1<=flag;
key_z_z2<=key_z_z1;
end
end
wire key_negedge_stable;
assign key_negedge_stable=!key_z_z1&&key_z_z2;
assign key_stable =key_negedge_stable;
endmodule
5.1.5数码管显示模块
将需要显示的阈值和实时温度传到这个模块中进行显示。
代码实现:
module seven_seg (
input wire clk,
input wire rst_n,
input wire [15:0] data,
input wire [5:0] data_yuzhi,
output wire [5:0] sel,
output wire [7:0] seg
);
parameter cnt_qiehuan_max=5000;
reg [12:0]cnt_qiehuan;
reg flag;
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
cnt_qiehuan<=0;
flag<=0;
end
else if(cnt_qiehuan==cnt_qiehuan_max-1)begin
cnt_qiehuan<=0;
flag<=1;
end
else
begin
cnt_qiehuan<=cnt_qiehuan+1;
flag<=0;
end
end
reg [2:0]state;
parameter [2:0] STATE0=0,
STATE1=1,
STATE2=2,
STATE3=3,
STATE4=4,
STATE5=5;
reg [5:0] sel_out;
reg [3:0]data_out;
//位选
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
state<=STATE0;
sel_out<=6'b111111;
data_out<=4'b0000;
end
else if(flag)begin
case(state)
STATE0:begin
sel_out<=6'b111110;
state<=STATE1;
data_out<=data[3:0];
end
STATE1:begin
sel_out<=6'b111101;
state<=STATE2;
data_out<=data[7:4];
end
STATE2:begin
sel_out<=6'b111011;
state<=STATE3;
data_out<=data[11:8];
end
STATE3:begin
sel_out<=6'b110111;
state<=STATE4;
data_out<=data[15:12];
end
STATE4:begin
sel_out<=6'b101111;
state<=STATE5;
data_out<=data_yuzhi%10;
end
STATE5:begin
sel_out<=6'b011111;
state<=STATE0;
data_out<=data_yuzhi/10;
end
default:;
endcase
end
end
//段选
reg [7:0] seg_out;
always@(*)begin
case(data_out)
0:seg_out=8'b11000000;
1:seg_out=8'b11111001;
2:seg_out=8'b10100100;
3:seg_out=8'b10110000;
4:seg_out=8'b10011001;
5:seg_out=8'b10010010;
6:seg_out=8'b10000010;
7:seg_out=8'b11111000;
8:seg_out=8'b10000000;
9:seg_out=8'b10010000;
default: seg_out = 8'b11111111;
endcase
if(state==3)
seg_out[7]=0;
end
assign sel=sel_out;
assign seg=seg_out;
endmodule
5.1.6顶层模块
代码实现:
module top (
input wire clk,
input wire rst_n,
inout wire dq,
input wire [2:0] key,
output wire buzz,
output wire [5:0] sel,
output wire [7:0] seg
);
// 内部信号声明
wire [15:0] data_ds18; // DS18B20 原始温度数据
wire [19:0] data_scaled; // 经过 data 模块放大100倍的温度值
wire [15:0] data_out_BCD; // BCD 转换后的 4 位十进制数
wire [5:0] data_yuzhi;
// 实例化 DS18B20 控制器
ds18b20_1wire ds18b20_1wire_tb (
.clk (clk),
.rst_n(rst_n),
.dq (dq),
.data (data_ds18)
);
// 实例化温度处理模块
data data_tb (
.clk (clk),
.rst_n (rst_n),
.data (data_ds18), // 输入:16位原始温度
.data_out(data_scaled) // 输出:20位温度×100
);
// 实例化 BCD 转换模块
BCD BCD_tb (
.data (data_scaled), // 输入:20位二进制温度
.data_out(data_out_BCD) // 输出:16位BCD码
);
// 实例化数码管显示模块
seven_seg seven_seg_tb (
.clk (clk),
.rst_n(rst_n),
.data (data_out_BCD), // 输入:16位BCD码
.data_yuzhi(data_yuzhi), //输入阈值温度
.sel (sel),
.seg (seg)
);
//实例化温度报警模块
temp_ctrl temp_ctrl_tb(
.clk(clk),
.rst_n(rst_n),
. key(key),
. data_BCD(data_out_BCD),
. data_yuzhi(data_yuzhi),
. buzz(buzz)
);
endmodule
六.实验效果:
由于图片展示不了蜂鸣器效果,在实际操作中也可以使用led作为报警。







