基于FPGA的ds18b20温度采集
文章目录
一、ds18b20简介
DS18B20数字温度计是一种提供9至12位摄氏温度测量器件,并具备带非易失性用户可编程上下触发点的报警功能。该器件通过单总线协议进行通信,根据定义仅需一根数据线(和地线)即可与中央微处理器通信。
优势特性
● 独特的单总线接口仅需一个通信端口引脚
● 集成温度传感器与EEPROM减少元件数量
● 测温范围-55°C至+125°C(-67°F至+257°F)
● -10°C至+85°C范围内精度达±0.5°C
● 可编程分辨率(9位至12位)
● 无需外部元件
● 支持用户自定义非易失性报警设置,通过报警搜索指令可识别超限温器件
● 提供8引脚SO(150密耳)、8引脚µSOP及3引脚TO-92封装
下图展示了DS18B20的模块框图,其中暂存存储器包含2字节温度寄存器,用于存储温度传感器的数字输出。此外,暂存器还提供对1字节上下限报警触发器寄存器(TH和TL)及1字节配置寄存器的访问。配置寄存器允许用户将温度-数字转换分辨率设置为9、10、11或12位。
如下图,DS18B20输出的温度数据以摄氏度校准;若需华氏度应用,必须使用查找表或转换程序。温度数据以16位符号扩展的二进制补码形式存储在温度寄存器中。符号位(S)指示温度正负:正数时S=0,负数时S=1。当DS18B20配置为12位分辨率时,温度寄存器所有位均包含有效数据;11位分辨率时,位0未定义;10位分辨率时,位1和位0未定义;9位分辨率时,位2、位1和位0未定义。
下表展示了12位分辨率转换时数字输出数据与对应温度读数的示例。
如下图,DS18B20完成温度转换后,系统会将测得温度值与用户自定义的二进制补码报警触发值进行比较,这些阈值存储在1字节大小的TH和TL寄存器中。符号位(S)用于指示数值正负:正数时S=0,负数时S=1。如存储器章节所述,可通过暂存器的第2、3字节访问TH和TL寄存器。
暂存器结构如下
如下图,暂存存储器的第4字节为配置寄存器。用户可通过该寄存器中的R0和R1位设置DS18B20的转换分辨率。这些位的上电默认值为R0=1和R1=1(12位分辨率)。需注意分辨率与转换时间存在直接权衡关系。配置寄存器中的第7位及第0至4位为设备内部保留位,不可被覆写。
二、设计思路
本次设计包括
- 基本的温度读取,并显示在数码管上
- 开关调整可加入符号位,区分零上和零下的温度
- 配置精度寄存器,使用开关能够切换温度精度
- 报警功能,当温度不在所设置的阈值内,则激活蜂鸣器报警
- 通过电脑用串口发送数据控制报警功能的开启与关闭
状态机设计如下图
- IDLE:基础默认状态
- SEND:FPGA向ds18b20发送复位脉冲状态
- RECV:接收ds18b20的复位脉冲状态
- SKIP:由于本次设计只有ds18b20一个器件,直接跳过ROM指令,这里为此时的状态
- CT:温度转换状态
- WAIT:温度转换需要的等待状态
- WS:配置寄存器命令状态
- WD:配置寄存器数据状态
- RC:读温度命令状态
- RD:都温度数据状态

上图为设计的RTL模块图,其中包括
- ds18b20驱动设计模块ds18b20_driver
- 数码管驱动模块sel_driver
- 数据处理模块ctrl
- 蜂鸣器模块beep
- uart模块uart_loop
- led灯模块led
- 顶层模块top
三、具体代码及思路
1、ds18b20_driver
1) 起始IDLE跳转SEND条件为延时2us,定义一个计数器实现
2) 进入SEND状态480us等待计数器开始计数,FPGA将总线拉低,发射复位脉冲;480us计数结束跳转RECV状态。
3) 进入RECV状态480us等待计数器开始计数,如果ds18b20存在,则其将总线拉低,发送存在脉冲;480us计数结束跳转SKIP状态。
此时涉及到SKIP跳转问题,在此设置一个跳转使能信号。
cpp
//skip跳转标志
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
skip_flag <= 2'd0; //0为跳转配置寄存器,1为跳转温度转换,2为跳转读温度
end
else if(WD_2_SEND)begin
skip_flag <= 2'd1;
end
else if(WAIT_2_SEND)begin
skip_flag <= 2'd2;
end
else if(RD_2_SEND)begin
skip_flag <= (sw_1 == sw_2) ? 2'b1:2'd0;
end
else begin
skip_flag <= skip_flag;
end
end

上电复位默认状态为0,下一步跳转为配置寄存器命令状态。
4) 进入SKIP状态,开始发送指令操作,对于写指令的规则如上图,这里设置的发送时间为100us,位于60与120之间,因为要发一位8bit数据,所以还需要一个8bit计数器,
cpp
SKIP : begin //发送rom指令 跳过rom cc 1100 1100
if(cnt_8bit == 3'd2 || cnt_8bit == 3'd3 || cnt_8bit == 3'd6 || cnt_8bit == 3'd7)begin //写1
if(cnt_100us <= 100)begin //拉低2us
dq_en <= 1'b1;
dq_out <= 1'b0;
end
else if(cnt_100us > 100 && cnt_100us < 3100)begin //拉高60us
dq_en <= 1'b1;
dq_out <= 1'b1;
end
else begin //释放总线 写0写1之间需要释放总线大于1us
dq_en <= 1'b0;
dq_out <= 1'b0;
end
end
else begin //写0
if(cnt_100us < 3100)begin //拉低62us
dq_en <= 1'b1;
dq_out <= 1'b0;
end
else begin //释放总线
dq_en <= 1'b0;
dq_out <= 1'b0;
end
end
由于为串行发送,且为低位先发,跳过ROM指令为 CCh 为 1100_1100,当记到第2、3、6、7个bit时发送1,发1时FPGA要先拉低总线,这里设置拉低2us,之后拉高,写1,这里设置拉高60us(可以更接近所设置的100us),拉高后要释放总线,写0与写1之间释放总线的时间要大于1us,小于所设置的写时间(100us),这里将未记入的时间全部释放;发0也是开始100us的计数,这里也设置的拉低60us,由于有2us的拉低延时,总共未62us,然后释放总线。当8bit计完,根据SKIP跳转使能进行状态跳转操作,此时使能为0,跳转配置寄存器WS状态。
5) 进入WS状态,发送一位8bit配置寄存器命令 4Eh 为 0100_1110 ,8bit发送完毕跳转到WD状态。

6) 进入WD状态,发送三位8bit配置寄存器数据,要再定义一个三位的计数器,如上图所示,要想配置精度寄存器,Th与Tl寄存器也要进行配置,所以智能定义三位8bit数据,将三个寄存器全部进行配置。

如上图所示,精度寄存器的低5位和第7位是不可被覆写的,能够配置的只有第5、6位;配置时将前两位寄存器全部配置为1,在第三位精度寄存器配置时,要根据swich开关控制的精度来配置寄存器,如下图所示。
cpp
WD : begin //发送配置寄存器的数据
if((cnt_3num==2)&&(cnt_8bit == 3'd7 || ((sw[3]==0)&&cnt_8bit==3'd6)||((sw[4]==0)&&cnt_8bit==3'd5))) begin
if(cnt_100us < 3100)begin //写0
dq_en <= 1'b1;
dq_out <= 1'b0;
end
else begin
dq_en <= 1'b0;
dq_out <= 1'b0;
end
end
else begin //写1
if(cnt_100us <= 100)begin//拉低2us
dq_en <= 1'b1;
dq_out <= 1'b0;
end
else if(cnt_100us > 100 && cnt_100us < 3100)begin//拉高60us
dq_en <= 1'b1;
dq_out <= 1'b1;
end
else begin
dq_en <= 1'b0;
dq_out <= 1'b0;
end
end
当三位计数器计完,跳回SEND,且SKIP跳转使能变化为1.
7) 进入SEND状态,到SKIP状态之前与第 2)、3) 步骤相同;跳到SKIP时使能为1,发送完跳过ROM指令后跳转到CT状态。
8) 进入CT状态,开始写8bit温度转换命令 44h 为 0100_0100,写规则与上述相同;计完8bit跳转WAIT状态。
9) 进入WAIT状态,等待温度转换完成,需要等待所设精度的等待时间,添加一个计数器,但最大值要根据所设精度改变;等待时间结束,跳回SEND状态,同时SKIP跳转使能变为2。
10) 进入SEND状态,到SKIP状态之前过程与第 2)、3) 步骤相同;跳转到SKIP时,跳转使能为2,发送完跳过ROM命令后跳转到RC状态。
11) 进入RC状态,发送读取温度命令 BEh 1011_1110,8bit发送完毕,跳转到RD状态。
12) 进入RD状态,开始读取温度数据,读数据规则如上图,由于所读数据有16bit,要再定义一个16bit计数器;
cpp
RD : begin //读取16bit温度数据
if(cnt_100us < 100)begin //拉低2us
dq_en <= 1'b1;
dq_out <= 1'b0;
end
else begin //释放总线 由从机控制总线传数据到主机
dq_en <= 1'b0;
dq_out <= 1'b0;
if(cnt_100us == TIME_10us)begin //10us的时候进行采样
temp_data[cnt_16bit] <= dq_in; //串并转换
end
end
end
进入后FPGA先拉底总线2us,之后释放总线,开始接收数据;如图所示,读数据要在包括拉低2us的15us内将数据读出,这里设置的为10us,在此时采样,依次把数据读出(读数据的总时间与写数据共用一个100us计数器);16bit读完后跳回SEND,此时要对输入的sw[4:3]进行延时打两个拍操作,判断sw的数据是否改变,如改变SKIP使能变回0,为改变跳为1。
cpp
//延时打拍,判断精度设置有没有改变,改变则进行配置,不改变则不配置
reg [1:0] sw_1;
reg [1:0] sw_2;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
sw_1 <= 2'b00;
sw_2 <= 2'b00;
end
else begin
sw_1 <= sw[4:3];
sw_2 <= sw_1;
end
end
这样一个周期的操作就完成了。
2、ctrl
此模块为采集到的数据处理模块,ds18b20采集到的16bit数据传入
16bit数据结构如上图,高5为是符号位,0为+,1为-;低4为是小数位,根据精度的设置,值与数据的取舍会有所改变;中间7为是整数位。
为了方便处理,将小数位单独取出作为一个4bit的数据
cpp
always @(*)begin //小数位的处理
case(sw[4:3])
2'b11:data_b = t_data[3:0];
2'b01:data_b = t_data[3:1];
2'b10:data_b = t_data[3:2];
2'b00:data_b = t_data[3:3];
default:data_b = 4'd12;
endcase
end
当精度位0.0625时后四位全部取,精度为0.125时bit0丢弃,精度为0.25时bit0与bit1丢弃,精度为0.5时bit0、bit1与bit2丢弃。(这里default所设值为方便数码管显示,所设置的默认值)
cpp
//精度描述
always @(*) begin
if(sw[4:3]==2'b11)begin
pre <= 13'd625;
end
else if(sw[4:3]==2'b01)begin
pre <= 13'd1250;
end
else if(sw[4:3]==2'b10)begin
pre <= 13'd2500;
end
else if(sw[4:3]==2'b00)begin
pre <= 13'd5000;
end
else begin
pre <= pre;
end
end
小数位取出的数位2进制数,以默认精度0.0625为例,取出小数位数值为0001时,正常所代表的数为1,但其真正含义时0.0625,所以要对其进行处理,就要将小数位数值扩大10000倍,再乘以精度,在进行后续处理,精度描述如上图。
cpp
data_b0001; //小数点后四位
data_b001; //小数点后三位
data_b01; //小数点后第二位
data_b1; //小数点后第一位
data_1; //个位
data_10; //十位
data_fu; //符号位
cpp
assign data_b0001 = (sw[4:3]==2'b11) ? (data_b*pre%10) : 4'd12;
assign data_b001 =((sw[4:3]==2'b11)||(sw[4:3]==2'b01)) ? (data_b*pre/10%10) : 4'd12;
assign data_b01 =((sw[4:3]==2'b11)||(sw[4:3]==2'b01)||(sw[4:3]==2'b10)) ? (data_b*pre/100%10) : 4'd12;
assign data_b1 =data_b*pre/1000%10;
assign data_1 =t_data[10:4]%10;
assign data_10 =t_data[10:4]/10%10;
assign data_fu =t_data[11] ? 4'hB : 4'hA;//数码管上A显示正号 B显示负号
如此数据位的处理完成,所设置的功能中,可以添加符号位,由于数码管显示位数有限,当精度位0.0625时,舍弃最后的小数位,加入符号;当精度改变时,不需要的数处理完为数码管默认值,不进行显示。
cpp
always @(*)begin
case(sw[2])
1'b0:dis_data ={data_10,data_1,data_b1,data_b01,data_b001,data_b0001};
1'b1:dis_data ={data_fu,data_10,data_1,data_b1,data_b01,data_b001};
default:dis_data ={data_fu,data_fu,data_fu,data_fu,data_fu,data_fu};
endcase
end
由于有阈值报警,需要进行值的设定,以及报警信号的设定
cpp
parameter temp_max = 30,
temp_min = 23;
assign en=(data_1>=(temp_max%10)&&data_10>=(temp_max/10))?1'b1:1'b0;
assign dn=(data_1<=(temp_min%10)&&data_10<=(temp_min/10))?1'b1:1'b0;
当大于30摄氏度小于23摄氏度时,进行报警
3、sel_driver
详见数码管详解
4、uart_loop
电脑通过串口发送开关指令,控制蜂鸣器模块的开关;详见UART设计
5、beep
当uart发送的开关数据有效,开启beep功能,此时,如果温度超出所设阈值,进行报警。
6、led
主要功能为显示beep的功能是否开启,开启时led0亮,关闭时led0灭。
四、实现效果
上电开关位置所代表数据为2'b11精度0.0625,显示温度26.6250
拨动开关改变为2'b01精度变为0.125,舍弃最后一位显示温度26.750
拨动开关改变为2'b10精度变为0.25,舍弃后两位显示温度27.25

拨动开关改变为2'b00精度变为0.5,显示温度为27.5

拨动另一个所设置开关,调整为显示符号位,u字开口向上表示(+)零上

电脑通过串口发送"开"指令led0亮起蜂鸣器功能开启,到达阈值进行报警
电脑通过串口发送"关"指令led0灭蜂鸣器功能关闭。
五、代码
1、top.v
cpp
module top (
input clk,
input rst_n,
input [4:0] sw,
input rx,
// input dq_in,
// output dq_out,
// output dq_en,
inout dq,
output tx,
output [0:0] led,
output [5:0] sel,
output [7:0] dig,
output pwm
);
wire en;
wire dn;
wire [15:0] temp_data;
wire [23:0] dis_data;
wire [7:0] data ;
reg din;
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
din <= 1'b0;
end
else if(data == 8'h80)begin
din <= 1'b1;
end
else if(data == 8'hb3)begin
din <= 1'b0;
end
else begin
din <= din;
end
end
ds18b20_driver inst_ds18b20_driver (
.clk (clk),
.rst_n (rst_n),
.sw (sw),
// .dq_in (dq_in),
// .dq_out (dq_out),
// .dq_en (dq_en),
.dq (dq),
.temp_data(temp_data)
);
ctrl inst_ctrl (
.t_data (temp_data),
.dis_data (dis_data),
.sw (sw),
.en (en),
.dn (dn)
);
sel_driver inst_sel_driver (
.clk (clk),
.rst_n (rst_n),
.dis_data (dis_data),
.sw (sw),
.sel (sel),
.dig (dig)
);
beep inst_beep (
.clk (clk),
.rst_n (rst_n),
.en (en),
.dn (dn),
.din (din),
.pwm (pwm)
);
uart_loop inst_uart_loop (
.clk (clk),
.rst_n (rst_n),
.rx (rx),
.sw (sw),
.data (data),
.tx (tx)
);
led inst_led (
.clk (clk),
.rst_n (rst_n),
.din (din),
.led (led)
);
endmodule
2、ds18b20_driver.v
cpp
module ds18b20_driver (
input clk,
input rst_n,
input [4:0] sw,
// input dq_in,
// output reg dq_out,
// output reg dq_en,
inout dq,
output reg [15:0] temp_data
);
//状态机数据
reg [3:0] state_c; //现态
reg [3:0] state_n; //次态
reg [2:0] s_state_c;
reg [2:0] s_state_n;
//主状态定义
localparam IDLE = 4'b0000,
SEND = 4'b0001,
RECV = 4'b0010,
SKIP = 4'b0011,
CT = 4'b0100,
WS = 4'b1000, //配置精度命令状态
WD = 4'b1001, //配置精度数据状态
WAIT = 4'b0101,
RC = 4'b0110,
RD = 4'b0111;
wire IDLE_2_SEND,
SEND_2_RECV,
RECV_2_SKIP,
SKIP_2_CT,
CT_2_WAIT,
WAIT_2_SEND,
SKIP_2_RC,
RC_2_RD,
RD_2_SEND,
SKIP_2_WS,
WS_2_WD,
WD_2_SEND;
//从状态机定义
// localparam S_IDLE = 3'b000,
// S_SLOW = 3'b001,
// S_READ = 3'b010,
// S_SEND = 3'b011,
// S_RELS = 3'b100,
// S_DOWN = 3'b101;
// wire S_IDLE_2_SLOW,
// S_SLOW_2_READ,
// S_SLOW_2_SEND,
// S_SEND_2_RELS,
// S_READ_2_RELS,
// S_RELS_2_SLOW,
// S_RELS_2_DOWN,
// S_DOWN_2_IDLE;
//三态门数据
reg dq_en;
wire dq_in;
reg dq_out;
//计2us
reg [6:0] cnt_2us;
wire add_cnt_2us;
wire end_cnt_2us;
parameter TIME_2us = 100;
//读数据的10us
// reg [9:0] cnt_10us;
// wire add_cnt_10us;
// wire end_cnt_10us;
parameter TIME_10us = 500;
//写计数100us
reg [12:0] cnt_100us;
wire add_cnt_100us;
wire end_cnt_100us;
parameter TIME_100us = 5_000;
parameter TIME_1us = 50;
//复位脉冲和存在脉冲的480us计数
reg [14:0] cnt_480us;
wire add_cnt_480us;
wire end_cnt_480us;
parameter TIME_480us = 24_000;
//等待750ms计数
reg [25:0] cnt_750ms;
wire add_cnt_750ms;
wire end_cnt_750ms;
parameter TIME_750ms = 37_500_000,
TIME_375ms = 18_750_000,
TIME_187_5ms = 9_375_000,
TIME_93_75ms = 4_687_500;
reg [25:0] time_ms;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
time_ms <= 37_500_000;
end
else begin
case(sw[4:3])
2'b00: time_ms <= TIME_93_75ms;
2'b01: time_ms <= TIME_187_5ms;
2'b10: time_ms <= TIME_375ms;
2'b11: time_ms <= TIME_750ms;
default: time_ms = 0;
endcase
end
end
//ROM指令与功能命令的8bit计数
reg [2:0] cnt_8bit;
wire add_cnt_8bit;
wire end_cnt_8bit;
parameter MAX_8bit = 8;
//配置寄存器的三个字节的8bit计数
reg [1:0] cnt_3num;
wire add_cnt_3num;
wire end_cnt_3num;
parameter MAX_3num = 3;
//读取到的16bit计数
reg [3:0] cnt_16bit;
wire add_cnt_16bit;
wire end_cnt_16bit;
parameter MAX_16bit = 16;
reg idle_flag; //存在脉冲标志
reg [1:0] skip_flag; //跳转标志
//三态门
assign dq_in = dq;
assign dq = dq_en ? dq_out : 1'bz;
//2us计数器
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cnt_2us <= 7'd0;
end
else if(add_cnt_2us)begin
if (end_cnt_2us) begin
cnt_2us <= 7'd0;
end
else begin
cnt_2us <= cnt_2us + 7'd1;
end
end
else begin
cnt_2us <= cnt_2us;
end
end
assign add_cnt_2us = (state_c == IDLE); //(s_state_c == S_IDLE)||(s_state_c == S_DOWN);
assign end_cnt_2us = add_cnt_2us && (cnt_2us == TIME_2us-1);
//10us计数器
// always @(posedge clk or negedge rst_n) begin
// if (!rst_n) begin
// cnt_10us <= 10'b0;
// end
// else if(add_cnt_10us)begin
// if (end_cnt_10us) begin
// cnt_10us <= 10'b0;
// end
// else begin
// cnt_10us <= cnt_10us + 10'b1;
// end
// end
// else begin
// cnt_10us <= cnt_10us;
// end
// end
// assign add_cnt_10us = (s_state_c == S_READ);
// assign end_cnt_10us = add_cnt_10us && (cnt_10us == (TIME_10us-1));
//100us计数器
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cnt_100us <= 13'd0;
end
else if(add_cnt_100us)begin
if (end_cnt_100us) begin
cnt_100us <= 13'd0;
end
else begin
cnt_100us <= cnt_100us + 13'd1;
end
end
else begin
cnt_100us <= cnt_100us;
end
end
assign add_cnt_100us = (state_c == SKIP)||(state_c == CT)||(state_c == WS)||(state_c == WD)||(state_c == RC)||(state_c == RD);
assign end_cnt_100us = add_cnt_100us && (cnt_100us == (TIME_100us-1));
//480us计数器
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cnt_480us <= 15'd0;
end
else if(add_cnt_480us)begin
if (end_cnt_480us) begin
cnt_480us <= 15'd0;
end
else begin
cnt_480us <= cnt_480us + 15'd1;
end
end
else begin
cnt_480us <= cnt_480us;
end
end
assign add_cnt_480us = (state_c == SEND)||(state_c == RECV);
assign end_cnt_480us = add_cnt_480us && (cnt_480us == TIME_480us-1);
//等待750ms计数器
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cnt_750ms <= 26'd0;
end
else if(add_cnt_750ms)begin
if (end_cnt_750ms) begin
cnt_750ms <= 26'd0;
end
else begin
cnt_750ms <= cnt_750ms + 26'd1;
end
end
else begin
cnt_750ms <= cnt_750ms;
end
end
assign add_cnt_750ms = (state_c == WAIT);
assign end_cnt_750ms = add_cnt_750ms &&(cnt_750ms == (time_ms-1));
//ROM指令与功能命令的8bit计数器
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cnt_8bit <= 3'd0;
end
else if(add_cnt_8bit)begin
if (end_cnt_8bit) begin
cnt_8bit <= 3'd0;
end
else begin
cnt_8bit <= cnt_8bit + 3'd1;
end
end
else begin
cnt_8bit <= cnt_8bit;
end
end
assign add_cnt_8bit = (end_cnt_100us) && ((state_c == CT)||(state_c == SKIP)||(state_c == RC)||(state_c == WS)||(state_c == WD));
assign end_cnt_8bit = add_cnt_8bit && (cnt_8bit == (MAX_8bit-1));
//配置寄存器的三个字节的8bit计数器
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cnt_3num <= 2'd0;
end
else if(add_cnt_3num)begin
if (end_cnt_3num) begin
cnt_3num <= 2'd0;
end
else begin
cnt_3num <= cnt_3num + 2'd1;
end
end
else begin
cnt_3num <= cnt_3num;
end
end
assign add_cnt_3num = (end_cnt_8bit) && (state_c == WD);
assign end_cnt_3num = add_cnt_3num && (cnt_3num == (MAX_3num-1));
//16bit 计数器
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cnt_16bit <= 4'd0;
end
else if(add_cnt_16bit)begin
if (end_cnt_16bit) begin
cnt_16bit <= 4'd0;
end
else begin
cnt_16bit <= cnt_16bit + 4'd1;
end
end
else begin
cnt_16bit <= cnt_16bit;
end
end
assign add_cnt_16bit = (end_cnt_100us) &&(state_c == RD) ;
assign end_cnt_16bit = add_cnt_16bit && (cnt_16bit == (MAX_16bit-1));
//存在脉冲描述
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
idle_flag <=0;
end
else if((cnt_480us == 5000) && (dq_in == 0))begin //100us时检测存在脉冲
idle_flag <= 1;
end
else if(state_c == SKIP)begin
idle_flag <= 0;
end
else begin
idle_flag <= idle_flag;
end
end
//skip跳转标志
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
skip_flag <= 2'd0; //0为跳转温度转换,1为跳转配置寄存器,2为跳转读温度
end
else if(WAIT_2_SEND)begin
skip_flag <= 2'd1;
end
else if(WD_2_SEND)begin
skip_flag <= 2'd2;
end
else if(RD_2_SEND)begin
skip_flag <= 2'd0;
end
else begin
skip_flag <= skip_flag;
end
end
//主状态机一段
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
state_c <= IDLE;
end
else begin
state_c <= state_n;
end
end
//从状态机一段
// always @(posedge clk or negedge clk ) begin
// if(!rst_n)begin
// s_state_n <= S_IDLE;
// end
// else begin
// s_state_n <= s_state_c;
// end
// end
//主状态机二段
always @(*) begin
case(state_c)
IDLE : begin state_n <= IDLE_2_SEND ? SEND : state_c; end
SEND : begin state_n <= SEND_2_RECV ? RECV : state_c; end
RECV : begin state_n <= RECV_2_SKIP ? SKIP : state_c; end
SKIP : begin if(SKIP_2_CT) begin state_n <= CT ;end
else if (SKIP_2_RC) begin state_n <= RC ;end
else if (SKIP_2_WS) begin state_n <= WS ;end
else begin state_n <= state_c; end
end
CT : begin state_n <= CT_2_WAIT ? WAIT : state_c; end
WAIT : begin state_n <= WAIT_2_SEND ? SEND : state_c; end
WS : begin state_n <= WS_2_WD ? WD : state_c; end
WD : begin state_n <= WD_2_SEND ? SEND : state_c; end
RC : begin state_n <= RC_2_RD ? RD : state_c; end
RD : begin state_n <= RD_2_SEND ? SEND : state_c; end
endcase
end
//描述跳转条件
assign IDLE_2_SEND = (state_c == IDLE) && end_cnt_2us;
assign SEND_2_RECV = (state_c == SEND) && end_cnt_480us;
assign RECV_2_SKIP = (state_c == RECV) && end_cnt_480us && (idle_flag == 1);
assign SKIP_2_CT = (state_c == SKIP) && end_cnt_8bit && (skip_flag == 0);
assign CT_2_WAIT = (state_c == CT) && end_cnt_8bit;
assign WAIT_2_SEND = (state_c == WAIT) && end_cnt_750ms;
assign SKIP_2_WS = (state_c == SKIP) && end_cnt_8bit && (skip_flag == 1);
assign WS_2_WD = (state_c == WS) && end_cnt_8bit;
assign WD_2_SEND = (state_c == WD) && end_cnt_3num;
assign SKIP_2_RC = (state_c == SKIP) && end_cnt_8bit && (skip_flag == 2);
assign RC_2_RD = (state_c == RC) && end_cnt_8bit;
assign RD_2_SEND = (state_c == RD) && end_cnt_16bit;
//第三段状态机
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
dq_en <= 0;
dq_out <= 0;
temp_data <= 0;
end
else begin
case(state_c)
IDLE : begin //释放总线
dq_en <= 0;
dq_out <= 0;
end
SEND : begin //初始化拉低总线
dq_en <= 1;
dq_out <= 0;
end
RECV : begin //释放总线检测存在脉冲
dq_en <= 0;
dq_out <= 0;
end
SKIP : begin //发送rom指令 跳过rom cc 1100 1100
if(cnt_8bit == 3'd2 || cnt_8bit == 3'd3 || cnt_8bit == 3'd6 || cnt_8bit == 3'd7)begin //写1
if(cnt_100us <= 100)begin //拉低2us
dq_en <= 1'b1;
dq_out <= 1'b0;
end
else if(cnt_100us > 100 && cnt_100us < 3100)begin //拉高60us
dq_en <= 1'b1;
dq_out <= 1'b1;
end
else begin //释放总线 写0写1之间需要释放总线大于1us
dq_en <= 1'b0;
dq_out <= 1'b0;
end
end
else begin //写0
if(cnt_100us < 3100)begin //拉低62us
dq_en <= 1'b1;
dq_out <= 1'b0;
end
else begin //释放总线
dq_en <= 1'b0;
dq_out <= 1'b0;
end
end
end
CT : begin //发送功能命令 温度转换 44 0100_0100
if(cnt_8bit == 3'd2 || cnt_8bit == 3'd6)begin //写1
if(cnt_100us <= 100)begin
dq_en <= 1'b1;
dq_out <= 1'b0;
end
else if(cnt_100us > 100 && cnt_100us < 3100)begin
dq_en <= 1'b1;
dq_out <= 1'b1;
end
else begin
dq_en <= 1'b0;
dq_out <= 1'b0;
end
end
else begin //写0
if(cnt_100us < 3100)begin
dq_en <= 1'b1;
dq_out <= 1'b0;
end
else begin
dq_en <= 1'b0;
dq_out <= 1'b0;
end
end
end
WS : begin //发送功能命令 配置寄存器 4e 0100_1110
if(cnt_8bit == 3'd1 || cnt_8bit == 3'd2|| cnt_8bit == 3'd3|| cnt_8bit == 3'd6)begin //写1
if(cnt_100us <= 100)begin//拉低2us
dq_en <= 1'b1;
dq_out <= 1'b0;
end
else if(cnt_100us > 100 && cnt_100us < 3100)begin//拉高60us
dq_en <= 1'b1;
dq_out <= 1'b1;
end
else begin
dq_en <= 1'b0;
dq_out <= 1'b0;
end
end
else begin //写0
if(cnt_100us < 3100)begin
dq_en <= 1'b1;
dq_out <= 1'b0;
end
else begin
dq_en <= 1'b0;
dq_out <= 1'b0;
end
end
end
WD : begin //发送配置寄存器的数据
if((cnt_3num==2)&&(cnt_8bit == 3'd7 || ((sw[3]==0)&&cnt_8bit==3'd6)||((sw[4]==0)&&cnt_8bit==3'd5))) begin //写0
if(cnt_100us < 3100)begin
dq_en <= 1'b1;
dq_out <= 1'b0;
end
else begin
dq_en <= 1'b0;
dq_out <= 1'b0;
end
end
else begin //写1
if(cnt_100us <= 100)begin//拉低2us
dq_en <= 1'b1;
dq_out <= 1'b0;
end
else if(cnt_100us > 100 && cnt_100us < 3100)begin//拉高60us
dq_en <= 1'b1;
dq_out <= 1'b1;
end
else begin
dq_en <= 1'b0;
dq_out <= 1'b0;
end
end
end
WAIT : begin //等待转换完成 750ms
dq_en <= 1'b0;
dq_out <= 1'b0;
end
RC : begin //发送读取温度指令 BE 1011_1110
if(cnt_8bit == 3'd0 || cnt_8bit == 3'd6)begin //写0
if(cnt_100us < 3100)begin
dq_en <= 1'b1;
dq_out <= 1'b0;
end
else begin
dq_en <= 1'b0;
dq_out <= 1'b0;
end
end
else begin //写1
if(cnt_100us <= 100)begin
dq_en <= 1'b1;
dq_out <= 1'b0;
end
else if(cnt_100us > 100 && cnt_100us < 3100)begin
dq_en <= 1'b1;
dq_out <= 1'b1;
end
else begin
dq_en <= 1'b0;
dq_out <= 1'b0;
end
end
end
RD : begin //读取16bit温度数据
if(cnt_100us < 100)begin //拉低2us
dq_en <= 1'b1;
dq_out <= 1'b0;
end
else begin //释放总线 由从机控制总线传数据到主机
dq_en <= 1'b0;
dq_out <= 1'b0;
if(cnt_100us == TIME_10us)begin //10us的时候进行采样
temp_data[cnt_16bit] <= dq_in; //串并转换
end
end
end
default : begin
dq_en <= 1'b0;
dq_out <= 1'b0;
temp_data <= 16'h0000;
end
endcase
end
end
endmodule
3、ctrl.v
cpp
module ctrl(
input [15:0] t_data,
input [4:0] sw,
output reg [23:0] dis_data,
output en,
output dn
);
reg [3:0] data_b;
wire [3:0] data_b0001; //小数点后四位
wire [3:0] data_b001; //小数点后三位
wire [3:0] data_b01; //小数点后第二位
wire [3:0] data_b1; //小数点后第一位
wire [3:0] data_1; //个位
wire [3:0] data_10; //十位
wire [3:0] data_fu; //符号位
reg [12:0] pre; //精度
//精度描述
always @(*) begin
if(sw[4:3]==2'b11)begin
pre <= 13'd625;
end
else if(sw[4:3]==2'b01)begin
pre <= 13'd1250;
end
else if(sw[4:3]==2'b10)begin
pre <= 13'd2500;
end
else if(sw[4:3]==2'b00)begin
pre <= 13'd5000;
end
else begin
pre <= pre;
end
end
always @(*)begin //小数位的处理
case(sw[4:3])
2'b11:data_b = t_data[3:0];
2'b01:data_b = t_data[3:1];
2'b10:data_b = t_data[3:2];
2'b00:data_b = t_data[3:3];
default:data_b = 4'd12;
endcase
end
parameter temp_max = 30,
temp_min = 23;
assign data_b0001 = (sw[4:3]==2'b11) ? (data_b*pre%10) : 4'd12;
assign data_b001 =((sw[4:3]==2'b11)||(sw[4:3]==2'b01)) ? (data_b*pre/10%10) : 4'd12;
assign data_b01 =((sw[4:3]==2'b11)||(sw[4:3]==2'b01)||(sw[4:3]==2'b10)) ? (data_b*pre/100%10) : 4'd12;
assign data_b1 =data_b*pre/1000%10;
assign data_1 =t_data[10:4]%10;
assign data_10 =t_data[10:4]/10%10;
assign data_fu =t_data[11] ? 4'hB : 4'hA;//数码管上A显示正号 B显示负号
always @(*)begin
case(sw[2])
1'b0:dis_data ={data_10,data_1,data_b1,data_b01,data_b001,data_b0001};
1'b1:dis_data ={data_fu,data_10,data_1,data_b1,data_b01,data_b001};
default:dis_data ={data_fu,data_fu,data_fu,data_fu,data_fu,data_fu};
endcase
end
assign en=(data_1>=(temp_max%10)&&data_10>=(temp_max/10))?1'b1:1'b0;
assign dn=(data_1<=(temp_min%10)&&data_10<=(temp_min/10))?1'b1:1'b0;
endmodule
4、beep.v
cpp
module beep#(parameter CLK_PRE = 50_000_000, TIME_300MS = 15_000_000)(
input clk ,
input rst_n ,
input en ,
input dn ,
input din ,
output reg pwm
);
//频率控制音色 ,占空比控制音量 ,占空比越大,低电平越少,音量越小
parameter DO = CLK_PRE / 523, // DO的周期所需要的系统时钟周期个数
RE = CLK_PRE / 587,
MI = CLK_PRE / 659,
FA = CLK_PRE / 698,
SO = CLK_PRE / 784,
LA = CLK_PRE / 880,
SI = CLK_PRE / 988;
reg [16:0] cnt1 ; //计数频率
wire add_cnt1;
wire end_cnt1;
reg [16:0] X ; //cnt1最大值
reg [23:0] cnt2 ; //计数每个音符发声300ms
wire add_cnt2;
wire end_cnt2;
reg [5:0] cnt3 ; //计数乐谱48
wire add_cnt3;
wire end_cnt3;
reg [5:0] cnt4 ; //计数乐谱48
wire add_cnt4;
wire end_cnt4;
reg ctrl ; //后25%消音
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt1 <= 17'b0;
end
else if(end_cnt2)begin
cnt1 <= 17'b0;
end
else if(add_cnt1)begin
if(end_cnt1)begin
cnt1 <= 17'b0;
end
else begin
cnt1 <= cnt1 + 1'b1;
end
end
else begin
cnt1 <= cnt1;
end
end
assign add_cnt1 = (en||dn)&&din;
assign end_cnt1 = add_cnt1 && cnt1 == X - 1;
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt2 <= 24'b0;
end
else if(add_cnt2)begin
if(end_cnt2)begin
cnt2 <= 24'b0;
end
else begin
cnt2 <= cnt2 + 1'b1;
end
end
else begin
cnt2 <= cnt2;
end
end
assign add_cnt2 = (en||dn)&&din;
assign end_cnt2 = add_cnt2 && cnt2 == TIME_300MS -1;
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt3 <= 6'b0;
end
else if(add_cnt3)begin
if(end_cnt3)begin
cnt3 <= 24'b0;
end
else begin
cnt3 <= cnt3 + 1'b1;
end
end
else begin
cnt3 <= cnt3;
end
end
assign add_cnt3 = end_cnt2;
assign end_cnt3 = add_cnt3 && cnt3 == 48 - 1;
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt4 <= 6'b0;
end
else if(add_cnt4)begin
if(end_cnt4)begin
cnt4 <= 24'b0;
end
else begin
cnt4 <= cnt4 + 1'b1;
end
end
else begin
cnt4 <= cnt4;
end
end
assign add_cnt4 = end_cnt2;
assign end_cnt4 = add_cnt4 && cnt4 == 48 - 1;
always @(*)begin
case(cnt3)
0 : X = SI;
1 : X = 1;
2 : X = SI;
3 : X = 1;
4 : X = SI;
5 : X = 1;
6 : X = SI;
7 : X = 1;
8 : X = SI;
9 : X = 1;
10 : X = SI;
11 : X = 1;
12 : X = SI;
13 : X = 1;
14 : X = SI;
15 : X = 1;
16 : X = SI;
17 : X = 1;
18 : X = SI;
19 : X = 1;
20 : X = SI;
21 : X = 1;
22 : X = SI;
23 : X = 1;
24 : X = SI;
25 : X = 1;
26 : X = SI;
27 : X = 1;
28 : X = SI;
29 : X = 1;
30 : X = SI;
31 : X = 1;
32 : X = SI;
33 : X = 1;
34 : X = SI;
35 : X = 1;
36 : X = SI;
37 : X = 1;
38 : X = SI;
39 : X = 1 ;
40 : X = SI;
41 : X = 1;
42 : X = SI;
43 : X = 1;
44 : X = SI;
45 : X = 1;
46 : X = SI;
47 : X = 1 ;
default : X = 1;
endcase
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
ctrl <= 1'b0;
end
else if(cnt2 >= ((TIME_300MS >> 1) + (TIME_300MS >>2)))begin
ctrl <= 1'b1;
end
else if(X == 1)begin
ctrl <= 1'b1;
end
else begin
ctrl <= 1'b0;
end
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
pwm <= 1'b1;
end
else if(ctrl)begin
pwm <= 1'b1;
end
else if((en||dn)&&din && (cnt1 < (X >> 5)))begin
pwm <= 1'b0;
end
else begin
pwm <= 1'b1;
end
end
endmodule
5、led.v
cpp
module led(
input clk,
input rst_n,
input din,
output reg [0:0] led=1'b0
);
always @(*)begin
if(din==1'b0)begin
led<=1'b0;
end
else if(din==1'b1)begin
led<=1'b1;
end
else begin
led<=led;
end
end
endmodule
6、uart_loop
详见UART设计
7、gitee地址
六、总结
本文介绍了一个基于FPGA的DS18B20温度采集系统的设计与实现。DS18B20是一种高精度数字温度传感器,通过单总线协议与FPGA通信,能够提供9至12位的温度测量数据。系统的核心功能包括温度读取、精度配置、报警功能以及通过串口控制蜂鸣器的开关。
设计采用状态机架构,通过多个模块协同工作实现功能。ds18b20_driver模块负责与DS18B20通信,完成温度数据的读取和精度配置;ctrl模块对采集到的温度数据进行处理,支持不同精度下的数据转换和显示;beep模块实现温度报警功能,通过蜂鸣器发出警报;uart_loop模块用于接收串口指令,控制蜂鸣器的开关状态。
系统通过数码管显示温度值,并支持通过开关调整温度精度和符号位显示。通过串口通信,用户可以远程控制报警功能的开启与关闭。整个系统在FPGA上实现,具有高效、灵活的特点,适用于多种温度监测和报警场景。