基于FPGA的ds18b20温度采集

基于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地址

ds18b20

六、总结

本文介绍了一个基于FPGA的DS18B20温度采集系统的设计与实现。DS18B20是一种高精度数字温度传感器,通过单总线协议与FPGA通信,能够提供9至12位的温度测量数据。系统的核心功能包括温度读取、精度配置、报警功能以及通过串口控制蜂鸣器的开关。

设计采用状态机架构,通过多个模块协同工作实现功能。ds18b20_driver模块负责与DS18B20通信,完成温度数据的读取和精度配置;ctrl模块对采集到的温度数据进行处理,支持不同精度下的数据转换和显示;beep模块实现温度报警功能,通过蜂鸣器发出警报;uart_loop模块用于接收串口指令,控制蜂鸣器的开关状态。

系统通过数码管显示温度值,并支持通过开关调整温度精度和符号位显示。通过串口通信,用户可以远程控制报警功能的开启与关闭。整个系统在FPGA上实现,具有高效、灵活的特点,适用于多种温度监测和报警场景。