FPGA—DS18B20数字温度传感器(附代码)

目录

1.理论学习

DS18B20概述

DS18B20特性

DS18B20内部结构

高速缓存器:

 64位光刻ROM:

1-Wire 总线协议:

温度测量原理

DS18B20操作步骤

1-Wire总线时序控制

2.实操

2.1 整体说明

2.2 DS18B20控制模块

2.2.1 模块框图和流程图

2.2.2 状态图

2.2.3 波形图        

2.2.4 RTL代码

2.3 顶层模块

2.3.1 模块框图

2.3.2 代码编写

3.上板验证

4.总结

1.理论学习

DS18B20概述

         DS18B20是DALLAS半导体公司出产的单总线数字温度传感器,其输出为数字信号,具有体积小、功耗低、抗干扰能力强、精度高等特点。本文学习DS18B20的工作原理以及如何使用单总线去控制其进行温度测量

       DS18B20 数字温度传感器提供 9-Bit 至 12-Bit(可配置)温度读数和一个用户可编程的非易失性且具有高温和低温触发报警的报警功能。 DS18B20 采用 1-Wire 通信即仅采用一根数据线与微控制器进行通信。该传感器的温度检测范围为-55℃至+125℃,在范围-10℃至+85℃之间具有± 0.5℃的精度。此外读取、写入和执行温度转换的电源可以从数据线本身获得,而不需要外部电源,当然使用外部电源供电也行。每个 DS18B20 都有一个独一无二的 64 位序列号,所以可以在一根总线上连接多个DS18B20 设备。

DS18B20特性

      1.采用单总线的接口方式。

      2.测温范围宽、精度高。

      3.无需外围电路。

      4.支持多点组网功能,可实现多个点测温。加下图

      5.可进行温度报警设计。

      6.供电方式灵活,寄生电源或外部电源。见下图

      7.温度采集精度可调节,精度转换时间短。

      8.具有掉电保护功能,可保存报警阈值以及采集精度位宽。

      9.负压特性,防止因接反导致烧毁传感器。

DS18B20内部结构

 DS18B20 内部结构图

高速缓存器:

        高速缓存器包含存储有数字温度结果的 2 个字节宽度的温度寄存器。另外,还提供了一个字节的高温(TH)和一个字节的低温(TL)温度报警寄存器和一个字节的配置寄存器。配置寄存器允许用户自定义温度转换精度 ( 9 位, 10 位 , 11 位 , 12 位) 。高温和低温温度报警器寄存器是非易失性的(EEPROM),其可以在断电的情况下保存。下面是高速缓存器的内部数据结构图。

配置寄存器:

       高速缓存器中第四个字节即为配置寄存器,如下图示。用户通过改变 R1 和 R0 的值来配置 DS18B20 的分辨率。上电默认为 R1=1 以及 R0=1(12 位分辨率)。需要注意的是转换时间与分辨率时间是有关系的,如下表所示。另外寄存器中最高位和低 5 位作为内部使用而保留使用,不可被写入。

 64位光刻ROM:

       每个 DS18B20 的片内 ROM 都存有一个独一无二的 64 位编码,如下图所示。在该ROM 编码的低 8 位保存有 DS18B20 的分类编码: 28h。中间的 48 位是独一无二的序列号。最高 8 位是前面 56 位的 CRC 循环冗余校验码( CRC=X8+X5+X4+1)。因为每个DS18B20 的序列号都不一样,所以一条总线上可以控制多个 DS18B20。

1-Wire 总线协议:

       总线协议仅需一个控制信号进行通信。在该总线系统中,微控制器(主设备)通过设备的 64 位序列号来识别该总线上的设备。 DS18B20 可以无需外部电源供电,当数据线 DQ 为高时由数据线为设备供电。当总线拉高时给内部电容( Cpp)充电,当总线拉低时由该电容给设备供电。这种由总线给设备供电的方式称为“寄生电源”。这种供电方式接线简单,但对于多点测温需要数据线上的更大的电流功能。这里使用的还是外部电源供电。

温度测量原理

       DS18B20中的温度传感器可以完成对温度的测量。其温度转换可由用户自定义(改变配置寄存器中R0与R1的值)为 9、 10、11、 12 位,精度分别为 0.5℃、 0.25℃、 0.125℃、 0.0625℃分辨率,若不设置则默认为 12位的转换精度。符号标志位(S)温度的正负极性:若 S=0,则为正数;若 S=1,则为负数。

       若要测量温度,主设备必须向 DS18B20 发送温度转换命令[44h]才能开始温度转换。温度转换后,转换的温度值将会保存在高速缓存器的温度寄存器中。只有通过读高速缓存器命令[BEh]才能将数据读出,数据通过 1-Wire 总线传输,传输顺序为低位到高位依次传输。如果 DS18B20 被定义为 12 位的转换精度,温度寄存器中所有位都将包含有效数据。若定义为 11 位转换精度,则 bit 0(最低位)为未定义的。若定义为 10 位转换精度,则 bit 0 和 bit 1 为未定义的。若定义 9 位转换精度,则 bit 0、 bit 1 和 bit 2 为未定义的。

 温度寄存器格式

 温度/数据对应关系

       待输出的数据是以二进制补码的形式存储在温度寄存器中的,所以要求温度值先得求原码再转化为十进制后乘以精度。正数的原码反码补码都是一样的,而负数的补码就是对反码加一。当温度为负数时,需先求得其原码(补码先减一再取反即为其原码)然后转换为十进制后乘以精度。例如数据输出为1111_1111_0101_1110,高六位全为1故判断此数为负数,将11_0101_1110减一后取反得到00_10100010,再将其转换为十进制得到162,乘以0.0625,填上负号最后算的温度为 -10.125摄氏度。 这里特别说明的是符号位只代表数据的正负,无论是取反还是求值它都是不算在里面的。另外上电复位时寄存器中的初始值为+85℃。

DS18B20操作步骤

       主机控制DS18B20完成温度转换必须经过三个步骤:每次读写之前都要对DS18B20进行初始化操作,操作完成后发送一条ROM命令,再发送一条RAM命令,这样才能对DS18B20进行预定的操作。

1.初始化

      1-Wire 总线上的所有事件都必须以初始化为开始。初始化序列由总线上的主设备发出的复位脉冲以及紧跟着从设备回应的存在脉冲构成。该存在脉冲是让总线主设备知道DS18B20 在总线上并准备好运行。

2.ROM命令

      ROM命令是对每个设备的 64 位 ROM 编码进行操作的,当总线上连接有多个设备时,可以通过这些命令识别各个设备。总共包含有 5 种 ROM 命令,每个命令的长度都是 8bit。

搜索ROM [F0h] :

       当系统上电初始化后,主设备可识别该总线上所有的从设备的 ROM 编码,这样就可以使得主设备确定总线上的从设备的类型以及数量。

读ROM [33h] :

        该命令允许主设备读取 DS18B20 的 64 位 ROM 编码,只有在总线上只有一个
DS18B20 时才能使用这个命令。如果总线上存在多个从设备,发送此命令,则当所有从设备都会回应时,将会引起数据冲突。

匹配ROM [55h] :

        该命令之后接着发出64位ROM编码,使主设备在多点总线上定位一个特定的 DS18B20。只有和 64 位 ROM 序列完全匹配的 DS18B20 才会做出响应。 总线上的其他从设备都将等待下一个复位脉冲。 此命令在总线上有单个或多个器件时都可以使用。

跳过ROM [CCh] :  

        这条命令在单点总线(一个DS18B20 传感器)情况下可以不用提供 64 位ROM 编码就进行下一步操作,可以节省时间。如果总线上不止一个从设备,在跳过 ROM 命令之后跟着发一条读命令, 则所有从设备将会同时执行温度转换,总线上就会发生数据冲突。

警报ROM [ECh] :

       该命令的操作与跳过 ROM 命令基本相同,但是不同的是只有温度高于 TH 或低于 TL(达到报警条件)的从设备才会响应。只要不掉电,警报状态将一直保持,直到温度不在
警报范围内为止。

3.RAM命令(功能命令)

        当总线上的主设备通过 ROM 命令确定了哪个 DS18B20 可以进行通信时,主设备就可以向其中一个从设备发送功能命令。这些命令可以使得主设备操控从设备进行一系列的操作。

温度转换 [44h] : 

         此命令为初始化单次温度转换,温度转换完后,转换的温度数据会寄存在高速缓存器的byte0(温度数据低八位)和 byte1(温度数据高八位)中,之后 DS18B20 恢复到低功耗的闲置状态。如果总线在该命令后发出读时隙,若 DS18B20 正在进行温度转换则会响应“0”,若完成了温度转换则响应“1”。

写入暂存器 [4Eh] 

       该命令使得主设备向高速缓存器写入 3 个字节的数据。第一个字节写入高速缓存器的byte2 中(TH 寄存器),第二个字节的数据写入 byte3 中(TL 寄存器),第三个字节的数据写入 byte4 中(配置寄存器)。所有的数据都是由低位到高位的顺序写入。复位可随时中断写入。

读取高速缓冲器 [BEh]

      该命名是读取高速缓存器里的值, 从 byte0(温度低八位)开始一直读到 byte8(CRC)校验),每个字节的数据从低位开始传送。若是不想读取这么多数据则在读取数据时随时可以通过复位来终止。

复制高速缓存器 [48h] :

      该命令是将高速缓存器中的 TH(byte2)、 TL(byte3)以及配置寄存器(byte4)里的值拷贝到非易失性的存储器 EEPROM 里。 如果总线控制器在这条命令之后跟着发出读时隙,而 DS18B20 又正在忙于把暂存器拷贝到 EEPROM 存储器, DS18B20 就会输出一个“ 0” ,如果拷贝结束的话, DS18B20 则输出“ 1”。

召回 EEPROM [B8h]

     该命令将温度报警触发值(TH 和 TL)及配置寄存器的数据从 EEPROM 中召回至高速缓存器中。这个操作会在上电后自动执行一次,所以在上电期间暂存器中一直会存在有效的数据。若在召回命令之后启动读时隙,若 DS18B20 正在进行召回 EEPROM 则会响应“0”,若召回完成则响应“1”。

读取供电模式 [B4h]

     该命令可以读取总线上的 DS18B20 是否是由“寄生电源”供电。在读取数据时序中“0”表示“寄生电源供”模式供电,“1”表示外部电源供电。

1-Wire总线时序控制

     DS18B20 采用严谨的 1-Wire 总线通信协议来保证数据的完整性。该协议定义多个信号形式:复位脉冲,存在脉冲,写 0,写 1,读 0,读 1 。除了存在脉冲由从设备发出,其他信号都由主设备控制。

step 1: 初始化时序

       由上图可知,主机通过判断有无存在脉冲响应来判断有无传感器可以进行数据交互。

step 2: 写时序

       主设备通过写时隙将命令写入 DS18B20 中,写时隙有两种:写“1”和写“0”时隙。主设备通过写 1 时隙来向 DS18B20 中写入逻辑 1,通过写 0 时隙来向 DS18B20 中写入逻辑 0。

       当主设备将总线从高电平拉至低电平时,启动写时隙,所有的写时隙持续时间最少为 60us,每个写时隙间的恢复时间最少为 1us。

        如果要产生写 1 时隙,必须先将总线拉至逻辑低电平然后释放总线,允许总线在写时隙开始后 15us 内上拉至高电平。若要产生写 0 时隙,必须将总线拉至逻辑低电平并保持不变最少 60us。当总线(DQ)拉低后, DS18B20 在 15us 至 60us 之间对总线进行采样,如果采的 DQ为高电平则发生写 1,如果为低电平则发生写 0,如下图所示。

step 3: 读时序

       当我们发送完读取供电模式[B4h]或读高速缓存器[BEh]命令时,必须及时地生成读时隙,只有在读时隙 DS18B20 才能向主设备传送数据。

        每个读时隙最小必须有 60us 的持续时间以及每个读时隙间至少要有 1us 的恢复时间。当主设备将总线从高电平拉至低电平超过 1us,启动读时隙。

       当启动读时隙后, DS18B20 将会向主设备发送“0”或者“1”。 DS18B20 通过将 DQ 引脚上的上拉电阻将总线在开始的15us内拉高发送“1”,不拉高发送“0”。主设备在读时隙开始后的 15us 内必须释放总线,并且对总线进行采样。时序图如下。

2.实操

        实验目标:fpga控制DS18B20进行实时温度的转换并显示在数码管上。

2.1 整体说明

DS18B20 温度显示实验整体框图

由上图可知,本实验工程包括 3 个模块,各模块简介如下表。

        由上数码管动态显示模块前面学过,直接使用即可。故只需编写DS18B20控制模块的代码即可。

2.2 DS18B20控制模块

2.2.1 模块框图和流程图

        模块的功能是将实时的采集环境温度并转换。

 DS18B20 控制流程图

2.2.2 状态图

      根据流程图画状态图如下图。

        跳过 ROM 命令和温度跳转命令都是写 0,写 1 的时序,过程是一样的所以我们让其在同一个状态进行写入。同理可以让跳过 ROM 和读温度命令也在同一个状态写入,相关的状态描述,如下表所示。

2.2.3 波形图        

step 1: 产生1us的系统时钟,clk_us

        观察初始化时序、写时序以读时序可知,时间信号单位都是us级。故我们先产生单位时钟为1us的时钟信号来作为系统的驱动信号,方法就是“分频器”章节中提及到的分频法。

        sys_clk信号的频率为50 Mhz,一个周期为20ns,1us = 1000ns = 50个时钟周期。具体波形见下图。

       时钟波形分频图

  step 2:验证初始化状态

     初始化序列主设备发出的复位脉冲以及紧跟着的从设备回应的存在脉冲构成。根据前面提到的初始化时序要求画出相应波形图。

        注意 dq 为数据总线,其类型为输入输出型,定义为 wire 型变量,这里用组合逻辑对其进行赋值, 所以我们将借助 dq_out(总线输入)和 dq_en(总线使能信号)来给 dq 赋值,当 dq_en 为低时使 dq 的值为高阻态,当 dq_en 为高电平时,将 dq_out 的值赋给 dq(后面会不上信号波形)。还有一点验证是否有DS18B20响应除了有时间要求还有电平要求。所以我们需要借助一个标志信号来显示我们在 570us 采到低电平,然后使用这个标志信号标志是否检测到存在脉冲,故产生一个存在脉冲 flag。当采到存在脉冲后,拉高标志信号,让其持续到状态跳转时,作为状态跳
转的判断条件。

 step3: 跳过 ROM 和温度转换命令状

    这里只需对应命令编码进行写时序即可,跳过ROM 和读取温度命令状态同理。

step4:等待温度转换完成状态

     使用计数器进行750ms的等待即可。

 

step5:再次初始化(同step1)

step6:写跳过ROM和读取温度命令状态命令

step7:读取温度状态

      fpga向ds18b20发送了读取温度命令状态命令后,  fpga需立即生成读时隙,DS18B20 才会向总线发送温度数据。读时隙时序图可知,每个读时隙之间都需要最少 60us 的读时隙持续时间以及最少 1us 的恢复时间,无论是读 1 时隙还是读 0 时隙,都需要拉低总线超过 1us来启动读时隙当读时隙开始后 DS18B20 会发送高速缓存器的值,从低位开始发送,读 1和读 0 的数据有效时间都为 15us,所以当读时隙开始后我们需要在 15us 内采集数据。

    data_tmp:记录读取到的温度值(二进制补码形式)

   data:对读到的温度值进行正负温度的判断,最高位为1,则该数为负数,对其求原码(取反加一操作),最高位为0,则原码等于补码。

   data_out: 温度乘以精度,原式 data * 0.0625 因位宽为整数故无法正确读出。

   写成 data * (625 / 1000)又因为在数码管显示上进行了小数显示(除以了1000),故这里要乘回来,最终形式 data_out = (data * 10'd625)/ 4'd10。
    sign:用于数码管正负符号的显示 。

2.2.4 RTL代码

`timescale  1ns/1ns

module  ds18b20_ctrl
(
    input   wire        sys_clk     ,   
    input   wire        sys_rst_n   ,   

    inout   wire        dq          ,   //数据总线

    output  wire [19:0] data_out    ,   //输出温度
    output  reg         sign            //输出温度符号位

);

parameter   S_INIT          =   3'd1, 
            S_WR_CMD        =   3'd2, 
            S_WAIT          =   3'd3, 
            S_INIT_AGAIN    =   3'd4, 
            S_RD_CMD        =   3'd5, 
            S_RD_TEMP       =   3'd6; 

parameter   WR_44CC_CMD =   16'h44cc; //跳过ROM及温度转换命令,低位在前
parameter   WR_BECC_CMD =   16'hbecc; //跳过ROM及读取温度命令,低位在前

parameter   S_WAIT_MAX  =   750000  ; //750ms

reg         clk_1us     ;   //分频时钟,单位时钟1us
reg [4:0]   cnt         ;   //分频计数器
reg [2:0]   state       ;   //状态机状态
reg [19:0]  cnt_1us     ;   //微秒计数器
reg [3:0]   bit_cnt     ;   //字节计数器
reg [15:0]  data_tmp    ;   //读取ds18b20的温度
reg [19:0]  data        ;   //判断完正负后的温度
reg         flag_pulse  ;   //初始化存在脉冲标志信号
reg         dq_out      ;   //输出总线数据,即FPGA给的总线数据值
reg         dq_en       ;   //输出总线数据使能信号

//温度转换,由于数码管位数有限,在这里保留小数点后三位
assign data_out = (data * 10'd625)/ 4'd10;

//当使能信号为1是总线的值为dq_out的值,为0时为高阻态
assign  dq  =   (dq_en ==1 ) ? dq_out : 1'bz;

//cnt:分频计数器
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt <=  5'b0;
    else    if(cnt == 5'd24)
        cnt <=  5'b0;
    else
        cnt <=  cnt + 1'b1;

//clk_1us:产生单位时钟为1us的时钟
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        clk_1us <=  1'b0;
    else    if(cnt == 5'd24)
        clk_1us <=  ~clk_1us;
    else
        clk_1us <=  clk_1us;

//cnt_1us:1us时钟计数器,用于状态跳转
always@(posedge clk_1us or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_1us <=  20'b0;
    else    if(((state==S_WR_CMD || state==S_RD_CMD || state==S_RD_TEMP)
       && cnt_1us==20'd64) || ((state==S_INIT || state==S_INIT_AGAIN) &&
       cnt_1us==20'd999) || (state==S_WAIT && cnt_1us==S_WAIT_MAX))
        cnt_1us <=  20'b0;
    else
        cnt_1us <=  cnt_1us +   1'b1;

//bit_cnt:bit计数器,写1bit或读1bit加1,一次写完之后清零
always@(posedge clk_1us or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        bit_cnt  <=  4'b0;
    else    if((state == S_RD_TEMP || state == S_WR_CMD ||
         state == S_RD_CMD) && (cnt_1us == 20'd64 && bit_cnt == 4'd15))
        bit_cnt  <=  4'b0;
    else    if((state == S_WR_CMD || state == S_RD_CMD ||
                              state == S_RD_TEMP) && cnt_1us == 20'd64)
        bit_cnt  <=  bit_cnt + 1'b1;

//初始化存在脉冲标志信号:初始化状态时,当总线发来存在脉冲才能初始化成功
always@(posedge clk_1us or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        flag_pulse  <=  1'b0;
    else    if(cnt_1us == 20'd570 && dq == 1'b0 && (state == S_INIT ||
                                                state == S_INIT_AGAIN))
        flag_pulse  <=  1'b1;
    else    if(cnt_1us == 999)
        flag_pulse  <=  1'b0;
    else
        flag_pulse  <=  flag_pulse;

//状态跳转
always@(posedge clk_1us or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        state   <=  S_INIT;
    else
        case(state)
    //初始化最小时间为960us
       S_INIT:  //收到存在脉冲且时间需大于960us跳转
            if(cnt_1us == 20'd999  && flag_pulse == 1'b1 )
                state   <=  S_WR_CMD;
            else
                state   <=  S_INIT;
        S_WR_CMD:   //发送完跳过ROM和温度转换命令后跳转
            if(bit_cnt == 4'd15 && cnt_1us == 20'd64 )
                state   <=  S_WAIT;
            else
                state   <=  S_WR_CMD;
        S_WAIT: //等待750ms后跳转
            if(cnt_1us == S_WAIT_MAX)
                state   <=  S_INIT_AGAIN;
            else
                state   <=  S_WAIT;
        S_INIT_AGAIN:   //再次初始化后跳转
            if(cnt_1us == 20'd999  && flag_pulse == 1'b1 )
                state   <=  S_RD_CMD;
            else
                state   <=  S_INIT_AGAIN;
        S_RD_CMD:   //发送完跳过ROM和读取温度命令后跳转
            if(bit_cnt == 4'd15 && cnt_1us == 20'd64)
                state   <=  S_RD_TEMP;
            else
                state   <=  S_RD_CMD;
        S_RD_TEMP:  //读完2字节的温度后跳转
            if(bit_cnt == 4'd15 && cnt_1us == 20'd64)
                state   <=  S_INIT;
            else
                state   <=  S_RD_TEMP;
        default:
                state   <=  S_INIT;
        endcase

//给各状态下的总线相应的时序
always@(posedge clk_1us or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        begin
            dq_out  <=  1'b0;
            dq_en   <=  1'b0;
        end
    else
        case(state)
    //初始化是最小480us低电平,然后释放总线
        S_INIT:
            if(cnt_1us < 20'd499)
                begin
                    dq_out  <=  1'b0;
                    dq_en   <=  1'b1;
                end
            else
                begin
                    dq_out  <=  1'b0;
                    dq_en   <=  1'b0;
                end
    //每一个写时段最少有60us的持续时间和最少1us的恢复时间
    //写0:总线拉低后一直拉低,最少60us
    //写1:总线拉低后必须在15us内释放总线
        S_WR_CMD:
            if(cnt_1us > 20'd62)
                begin
                    dq_out  <=  1'b0;
                    dq_en   <=  1'b0;
                end
            else    if(cnt_1us <= 20'b1)
                begin
                    dq_out  <=  1'b0;
                    dq_en   <=  1'b1;
                end
            else    if(WR_44CC_CMD[bit_cnt] == 1'b0)
                begin
                    dq_out  <=  1'b0;
                    dq_en   <=  1'b1;
                end
            else    if(WR_44CC_CMD[bit_cnt] == 1'b1)
                begin
                    dq_out  <=  1'b0;
                    dq_en   <=  1'b0;
                end
    //为适应寄生电源,温度转换命令后将总线拉高
        S_WAIT:
            begin
                dq_out  <=  1'b1;
                dq_en   <=  1'b1;
            end
    //与第一次初始化时序一致
        S_INIT_AGAIN:
            if(cnt_1us < 20'd499)
                begin
                    dq_out  <=  1'b0;
                    dq_en   <=  1'b1;
                end
            else
                begin
                    dq_out  <=  1'b0;
                    dq_en   <=  1'b0;
                end
    //与发送跳过ROM和读取温度命的时序一致
        S_RD_CMD:
            if(cnt_1us > 20'd62)
                begin
                    dq_out  <=  1'b0;
                    dq_en   <=  1'b0;
                end
            else    if(cnt_1us <= 20'b1)
                begin
                    dq_out  <=  1'b0;
                    dq_en   <=  1'b1;
                end
            else    if(WR_BECC_CMD[bit_cnt] == 1'b0)
                begin
                    dq_out  <=  1'b0;
                    dq_en   <=  1'b1;
                end
            else    if(WR_BECC_CMD[bit_cnt] == 1'b1)
                begin
                    dq_out  <=  1'b0;
                    dq_en   <=  1'b0;
                end
    //拉低总线超过1us后释放总线
        S_RD_TEMP:
            if(cnt_1us <=1)
                begin
                    dq_out  <=  1'b0;
                    dq_en   <=  1'b1;
                end
            else
                begin
                    dq_out  <=  1'b0;
                    dq_en   <=  1'b0;
                end
        default:;
        endcase

//data_tmp:读出温度,寄存在data_tmp里
always@(posedge clk_1us or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        data_tmp    <=  12'b0;
        //总线拉低后数据有效时间为15us
    else    if(state == S_RD_TEMP && cnt_1us == 20'd13)
        data_tmp   <=  {dq,data_tmp[15:1]};
    else
        data_tmp    <=  data_tmp;

//温度判断,输出温度
always@(posedge clk_1us or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        data    <=  20'b0;
    else    if(data_tmp[15] == 1'b0 && state == S_RD_TEMP &&
                            cnt_1us == 20'd60 && bit_cnt == 4'd15)
        data    <=  data_tmp[10:0];
    else    if(data_tmp[15] == 1'b1 && state == S_RD_TEMP &&
                            cnt_1us == 20'd60 && bit_cnt == 4'd15)
        data    <=  ~data_tmp[10:0] + 1'b1;

//温度判断,输出符号位
always@(posedge clk_1us or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        sign    <=  1'b0;
    else    if(data_tmp[15] == 1'b0 && state == S_RD_TEMP &&
                            cnt_1us == 20'd60 && bit_cnt == 4'd15)
        sign    <=  1'b0;
    else    if(data_tmp[15] == 1'b1 && state == S_RD_TEMP &&
                            cnt_1us == 20'd60 && bit_cnt == 4'd15)
        sign    <=  1'b1;

endmodule 

2.3 顶层模块

2.3.1 模块框图

2.3.2 代码编写

`timescale  1ns/1ns

module  ds18b20
(
    input   wire            sys_clk     ,  
    input   wire            sys_rst_n   ,  
    inout   wire            dq          ,  

    output  wire    [5:0]   sel,   
    output  wire    [7:0]   seg    
);

wire    [19:0]  data_out ;
wire            sign     ;

ds18b20_ctrl    ds18b20_ctrl_inst
(
    .sys_clk     (sys_clk  ),   
    .sys_rst_n   (sys_rst_n),   

    .dq          (dq       ),   

    .data_out    (data_out ),   
    .sign        (sign     )    

);
seg_dynamic seg_dynamic_inst
(
    .sys_clk     (sys_clk  ),   
    .sys_rst_n   (sys_rst_n),   
    .data        (data_out ),
    .point       (6'b001000),   
    .seg_en      (1'b1     ),   
    .sign        (sign     ),   

    .sel         (sel      ),   
    .seg         (seg      )    

);

endmodule

3.上板验证

下图是2023.03.23 17:30依次测量室温、手指以及手腕的华氏温度。

4.总结

*1. 重点学习如何看懂初始化、写时序和读时序。

*2. 理解单总线与fpga和ds8b20的关系。

*3  理解如何读取温度并进行转化。

说明:

本人使用的是野火家Xilinx Spartan6系列开发板及配套教程,以上内容如有疑惑或错误欢迎评论区指出,或者移步B站观看野火家视频教程。

开发软件:ise14.7     仿真:modelsim 10.5 

如需上述资料私信或留下邮箱

  • 17
    点赞
  • 97
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 17
    评论
#include<reg52.h> #define ui unsigned int #define uc unsigned char //宏定义 sbit SET=P3^1; //定义调整键 sbit DEC=P3^2; //定义减少键 sbit ADD=P3^3; //定义增加键 sbit BEEP=P3^6; //定义蜂鸣器 sbit ALAM=P1^2; //定义灯光报警 sbit ALAM1=P1^4; sbit DQ =P3^7; //定义DS18B20总线I/O sbit SCL=P1^6; sbit SDA=P1^7; sbit DIAN=P0^5; //小数点 bit bdata shanshuo_st; //闪烁间隔标志 bit bdata beep_st; //蜂鸣器间隔标志 uc x=0; //计数器 ui bai,shi,ge; uc set_st=0; //状态标志 char shangxian,xiaxian; code LEDData[]={0x5F,0x44,0x9D,0xD5,0xC6,0xD3,0xDB,0x47,0xDF,0xD7,0xCF,0xDA,0x9B,0xDC,0x9B,0x8B}; //====================================DS18B20========================================= /*****延时子程序*****/ void Delay_DS18B20(int num) { while(num--) ; } void delay()//5微秒延时函数 { ;; } void start() //开始信号 { SDA=1; delay(); SCL=1; delay(); SDA=0; delay(); } void stop() //终止信号 { SDA=0; delay(); SCL=1; delay(); SDA=1; delay(); } void respons() //应答 { uc i; SCL=1; delay(); while((SDA==1)&&(i<250))i++;//如果SDA为低应答有效,或者超过一定时间默认应答有效 SCL=0; delay(); } void init24c04()//I2C总线初始化 { SDA=1; delay(); SCL=1; delay(); } void write_byte(uc date)//写操作 { uc i,temp; temp=date; for(i=0;i<8;i++) { temp=temp<<1; SCL=0; delay(); SDA=CY; delay(); SCL=1; delay(); } SCL=0; delay(); SDA=1; delay(); } uc read_byte()//读操作 { uc i,k; SCL=0; delay(); SDA=1; delay(); for(i=0;i<8;i++) { SCL=1; delay(); k=(k<<1)|SDA; SCL=0; delay(); } return k; } void write_add(uc address,uc date)//往任意地址存数据 { start(); write_byte(0xa0);//0xa0代表写入 respons(); write_byte(address); respons(); write_byte(date); respons(); stop(); } uc read_add(uc address)//读随意地址内容 { uc date; start(); write_byte(0xa0);//0xa0代表写入 respons(); write_byte(address); respons(); start(); write_byte(0xa1);//0xa1代表读出 respons(); date=read_byte(); stop(); return date;//返回值 } /*****初始化DS18B20*****/ void Init_DS18B20(void) { uc w=0; DQ = 1; //DQ复位 Delay_DS18B20(8); //稍做延时 DQ = 0; //单片机将DQ拉低 Delay_DS18B20(80); //精确延时,大于480us DQ = 1; //拉高总线 Delay_DS18B20(14); W=DQ //稍做延时后,如果x=0则初始化成功,x=1则初始化失败 Delay_DS18B20(20); } /*****读一个字节*****/ unsigned char ReadOneChar(void) { uc i=0; uc dat = 0; for (i=8;i>0;i--) { DQ = 0; // 给脉冲信号 dat>>=1; DQ = 1; // 给脉冲信号 if(DQ) dat|=0x80; Delay_DS18B20(4); } return(dat); } /*****写一个字节*****/ void WriteOneChar(uc dat) { uc i=0; for (i=8; i>0; i--) { DQ = 0; DQ = dat&0x01; Delay_DS18B20(5); DQ = 1; dat>>=1; } } /*****读取温度*****/ ui ReadTemperature(void) { ui b=0; float tt=0; Init_DS18B20(); WriteOneChar(0xCC); //跳过读序号列号的操作 WriteOneChar(0x44); //启动温度转换 Init_DS18B20(); WriteOneChar(0xCC); //跳过读序号列号的操作 WriteOneChar(0xBE); //读取温度寄存器 a=ReadOneChar(); //读低8位 b=ReadOneChar(); //读高8位 t=b; t<<=8; t=t|a; tt=t*0.0625; t= tt*10+0.5; //放大10倍输出并四舍五入 return(t); } /*****延时子程序*****/ void Delay(ui num) { while( --num ); } /*****初始化定时器0*****/ void InitTimer(void) { TMOD=0x01; TH0=0x3c; TL0=0xb0; //50ms(晶振12M) } /*****读取温度*****/ void check_wendu(void) { ui f; f=ReadTemperature()-5; //获取温度值并减去DS18B20的温漂误差 if(f<0)f=0; if(f>999)f=999; bai=f/100; //计算得到十位数字 shi=(f0)/10; //计算得到个位数字 ge=(f0); //计算得到整数位 } /*****显示开机初始化等待画面*****/ void Disp_init(void) { P0= ~0x80; //显示---- P2= 0x7F; Delay(200); P2=0XDF; Delay(200); P2 = 0xF7; Delay(200); P2= 0xFD; Delay(200); P2= 0xFF; //关闭显示 } /*****显示温度子程序*****/ void Disp_Temperature(void) //显示温度 { P0= ~0x98; //显示C P2= 0x7F; Delay(400); P0=LEDData[ge]; //显示个位 P2 = 0xDF; Delay(400); P0 =LEDData[shi]; //显示十位 DIAN = 0; //显示小数点 P2= 0xF7; Delay(400); P0 =~LEDData[bai]; //显示百位 P2 = 0xFD; Delay(400); P2 = 0xff; //关闭显示 } /*****显示报警温度子程序*****/ void Disp_alarm(uc baojing) { p0 =~0x98; //显示C p2 = 0x7F; Delay(200); p0 =~LEDData[baojing]; //显示十位 P0 =~LEDData[baojing/10]; //显示百位 P2 = 0xF7; Delay(200); if(set_st==1)P0 =~0xCE; else if(set_st==2)P0 =~0x1A; //上限H、下限L标示 P2= 0xFD; Delay(200); P2 = 0xff; //关闭显示 } /*****报警子程序*****/ void Alarm() { if(x>=10){beep_st=~beep_st;x=0;} if((bai*10+shi)>=shangxian&&beep;_st==1) { BEEP=0; ALAM1=0; } else if((bai*10+shi)>=shangxian&&beep;_st==0) { BEEP=1; ALAM1=0; } if((bai*10+shi)<xiaxian&&beep;_st==1) { BEEP=0; ALAM=0; } else if((bai*10+shi)<xiaxian&&beep;_st==0) { BEEP=1; ALAM=0; } if(((bai*10+shi)<shangxian)&&((bai*10+shi)>=xiaxian)) { BEEP=1; ALAM1=1; ALAM=1; } } /*****主函数*****/ void main(void) { ui z; InitTimer(); //初始化定时器 EA=1; //全局中断开关 TR0=1; ET0=1; //开启定时器0 check_wendu(); check_wendu(); shangxian=read_add(10); xiaxian=read_add(20); for(z=0;z<300;z++) { Disp_init(); } while(1) { if(SET==0) { Delay(2000); do{} while(SET==0); set_st++;x=0;shanshuo_st=1; if(set_st>2)set_st=0; } if(set_st==0) { check_wendu(); Disp_Temperature(); Alarm(); //报警检测 } else if(set_st==1) { BEEP=1; //关闭蜂鸣器 ALAM=1; ALAM1=1; if(x>=10){shanshuo_st=~shanshuo_st;x=0;} if(shanshuo_st) {Disp_alarm(shangxian);} if(ADD==0) { do{Disp_alarm(shangxian);} shangxian++; if(shangxian>99)shangxian=99; write_add(10,shangxian); } else if(DEC==0) { do{Disp_alarm(shangxian);} while(DEC==0); shangxian--; if(shangxian<xiaxian) shangxian=xiaxian; write_add(10,shangxian); } } else if(set_st==2) { BEEP=1; //关闭蜂鸣器 ALAM=1; ALAM1=1; if(x>=10){shanshuo_st=~shanshuo_st;x=0;} if(shanshuo_st) {Disp_alarm(xiaxian);} if(ADD==0) { do{Disp_alarm(xiaxian);} while(ADD==0); xiaxian++; if(xiaxian>shangxian) xiaxian=shangxian; write_add(20,xiaxian); } else if(DEC==0) { do{Disp_alarm(xiaxian);} while(DEC==0); xiaxian--; if(xiaxian<0) xiaxian=0; write_add(20,xiaxian); } } } } /*****定时器0中断服务程序*****/ void timer0(void) interrupt 1 { TH0=0x3c; TL0=0xb0; x++; }
DS18B20温度传感器可以与FPGA进行连接和通信。DS18B20采用单总线的接口方式,只需要一条口线即可实现与FPGA的双向通讯。这种接口方式具有经济性好,抗干扰能力强的特点,非常适合于恶劣环境的现场温度测量。因此,您可以通过将DS18B20FPGA连接,实现对温度的测量和读取。 在与FPGA的通信过程中,可以使用DS18B20的命令来控制和读取温度数据。其中,一些常用的命令包括跳过ROM命令(SKIP ROM),温度转换命令(Convert T)和温度读取命令(READ)。通过这些命令,您可以实现对DS18B20温度转换和读取功能的控制。 在读取温度数据时,DS18B20会将测量到的温度值存储在字节0和字节1中,其中字节0为低位,字节1为高位。这两个字节都是只读的,您可以通过读取这两个字节来获取测量到的温度值。 综上所述,通过将DS18B20FPGA连接并使用相应的命令,您可以实现对温度传感器的控制和读取操作。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [【FPGAds18b20温度传感器](https://blog.csdn.net/weixin_45888898/article/details/122853459)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 17
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

咖啡0糖

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值