【FPGA】ds18b20温度传感器

一、ds18b20温度传感器

DS18B20 单线数字温度传感器,即“一线器件”,其具有独特的优点:

  • 采用单总线的接口方式 与微处理器连接时仅需要一条口线即可实现微处理器与 DS18B20 的双向通讯。单总线具有经济性好,抗干扰能力强,适合于恶劣环境的现场温度测量。
  • 测量温度范围宽,测量精度高 DS18B20 的测量范围为 -55 ℃ ~+ 125 ℃ ; 在 -10~+ 85°C范围内,精度为 ± 0.5°C 。
  • 在使用中不需要任何外围元件。
  • 持多点组网功能 多个 DS18B20 可以并联在惟一的单线上,实现多点测温。
  • 供电方式灵活 DS18B20 可以通过内部寄生电路从数据线上获取电源。因此,当数据线上的时序满足一定的要求时,可以不接外部电源,从而使系统结构更趋简单,可靠性更高。
  • 测量参数可配置 DS18B20 的测量分辨率可通过程序设定 9~12 位。
  • 负压特性电源极性接反时,温度计不会因发热而烧毁,但不能正常工作。
  • 掉电保护功能 DS18B20 内部含有 EEPROM ,在系统掉电以后,它仍可保存分辨率及报警温度的设定值

二、看ds18b20手册找关键

1.引脚说明

在这里插入图片描述

只有三个引脚,说明ds18b20是单总线

使用三态门的方式去实现单总线

  • dq_in

  • dq_out

  • dq_out_en

    // 三态门

    assign dq_in = dq;
    
    assign dq = dq_out_en?dq_out:1'bz;
    

    dq就是相对于主机来说的

    dq_in 就是ds18b20传感器发来的数据

    dq_out就是主机发给ds18b20的数据

    dq_out_en就是主机发送使能,1能发,0不能发

2.最高位字节和最低位字节数据

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-x2npUBZ5-1644454473357)(C:\Users\Jin\AppData\Roaming\Typora\typora-user-images\image-20220112191448421.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZMnkpxmS-1644454473357)(C:\Users\Jin\AppData\Roaming\Typora\typora-user-images\image-20220112190642462.png)]

数据[10:0]是温度数据

数据[15:11]是温度的正负,1代表负,0代表正

3.ds18b20暂存器数据

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-L8UeNqPJ-1644454473358)(C:\Users\Jin\AppData\Roaming\Typora\typora-user-images\image-20220113093821578.png)]

DS18B20的每个暂存器都有8bit存储空间,用来存储相应数据,

  • byte0和byte1分别为温度数据的低位和高位,用来储存测量到的温度值,且这两个字节都是只读的;

  • byte2和byte3为TH、TL告警触发值的拷贝,可以在从片内的电可擦可编程只读存储器EEPROM中读出,也可以通过总线控制器发出的[48H]指令将暂存器中TH、TL的值写入到EEPROM,掉电后EEPROM中的数据不会丢失;

  • byte4的配置寄存器用来配置温度转换的精确度(最大为12位精度);

  • byte5、6、7为保留位,禁止写入;

  • byte8亦为只读存储器,用来存储以上8字节的CRC校验码。

4.需要的命令

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vTt0Fo0q-1644454473358)(C:\Users\Jin\AppData\Roaming\Typora\typora-user-images\image-20220112191856182.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-euxRLjSA-1644454473358)(C:\Users\Jin\AppData\Roaming\Typora\typora-user-images\image-20220112191552364.png)]

命令Command命令数据CMD说明
SKIP ROM8’hCC跳过ROM命令,因为我们只有一个外设,不需要去搜索其他的外设,所以不需要其他的ROM命令
Convert T8’h44温度转换命令,将采集到的数据进 行温度转换
READ8’hBE温度读取命令,将温度转换后的数据进行读取

5.主状态机

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vpTBI4mt-1644454473359)(C:\Users\Jin\AppData\Roaming\Typora\typora-user-images\image-20220112192824609.png)]

主机的大致流程图

6.从状态机

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WSxNulxh-1644454473359)(C:\Users\Jin\AppData\Roaming\Typora\typora-user-images\image-20220113135215792.png)]

ds18b20温度传感器外设的大致流程图

发送完第一个温度转换的命令后,需要先回到初始化状态,然后再发送第二个读取温度数据的命令

7.初始化时序

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dEApr4Tb-1644454473359)(C:\Users\Jin\AppData\Roaming\Typora\typora-user-images\image-20220112194225637.png)]

初始化时序图

主机控制总线给从机发送复位脉冲480us,主机释放总线从机等待15-60us,从机控制总线给主机发送存在脉冲60-240us

8.写时隙

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sRdepNlN-1644454473360)(C:\Users\Jin\AppData\Roaming\Typora\typora-user-images\image-20220112195225955.png)]

写时隙(主机向从机传数据)

无论是写0还是写1,一开始都是主机控制总线拉低总线15us,如果是写0,需要在15us-60us内主机控制总线保持低电平,如果是写1,需要在15us-60us内主机释放总线,上拉电阻将总线拉高,

9.读时隙

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-p027W6UC-1644454473360)(C:\Users\Jin\AppData\Roaming\Typora\typora-user-images\image-20220112201149594.png)]

读时隙(从机向主机传数据)

无论是读0还是读1,一开始都是主机控制总线拉低>1us,如果是读0,在2us以后,主机释放总线,从机控制总线拉低,而且需要在2us< and <15us内,主机采集数据,如果是读1,

10.关键时间参数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Cx2SwqUD-1644454473360)(C:\Users\Jin\AppData\Roaming\Typora\typora-user-images\image-20220112203102150.png)]

这里有几个时间注意一下

用的是12位精准度,所以温度转换的时间750ms,还有几个时间根据时序图去对照着看

时间名称
Temperature Conversion Time750ms温度转换时间
Reset Time Low480us主机控制总线发送复位脉冲时间
Presence Detect High15-60us主机释放总线
Presence Detect Low60-240us从机控制总线方发存在脉冲

11.低字节先发

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pVY8yhlX-1644454473360)(C:\Users\Jin\AppData\Roaming\Typora\typora-user-images\image-20220115170231050.png)]

低字节(LSB FIRST)先发

根据图5,图6,图7,图8和图9的时序写出相应的状态图,可以分为主状态机和从状态机

三、状态机设计

这里可以使用一个状态机就可以实现了,只不过状态转移条件稍微多一点

如果使用主从状态去实现,对读写时隙可以细分,稍微方便一点

1.主状态机

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wma3r1QF-1644454473361)(C:\Users\Jin\AppData\Roaming\Typora\typora-user-images\image-20220113111051749.png)]

1.初始化阶段

红色是IDLE

黄色是RESET PULSE 主机控制总线发送复位脉冲

绿色是RELEASE 主机释放总线 上拉电阻

蓝色是PRESENCE PULSE 从机控制总线发送存在脉冲

2.发送命令阶段

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Y8fGq291-1644454473361)(C:\Users\Jin\AppData\Roaming\Typora\typora-user-images\image-20220113135807915.png)]

SKIP ROM COMMAND 主机向从机发送跳过ROM命令

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cXbnoDYC-1644454473361)(C:\Users\Jin\AppData\Roaming\Typora\typora-user-images\image-20220113135922877.png)]

CONVERT TEMPERATURE COMMAND主机向从机发送温度转换命令

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KQTrGsPj-1644454473361)(C:\Users\Jin\AppData\Roaming\Typora\typora-user-images\image-20220113140143184.png)]

WAIT TEMPERATURE COVERT 等待温度转换的时间

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OtmqZILJ-1644454473362)(C:\Users\Jin\AppData\Roaming\Typora\typora-user-images\image-20220113141042579.png)]

READ SCRATCHPAD COMMAND主机向从机发送读取存储器数据命令

3.读取数据阶段

READ TEMPERATURE 主机读从机发来的数据

DONE 主机读取完全部的数据

2.从状态机

从状态机就是细分了读写时隙

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vpBHJqEX-1644454473362)(C:\Users\Jin\AppData\Roaming\Typora\typora-user-images\image-20220113142448810.png)]

我们先找一下读写时隙的相同点,

红色是LOW 主机控制总线拉低

绿色是RELEASE 主机释放总线,上拉电阻拉高

然后是读写时隙的不同点

写时隙

紫色是MASTER WRITE 主机向从机发送1bit的0数据

深绿色也是MASTER WRITE 主机向从机发送1bit的1数据

读时隙

蓝色是MASTER SAMPLE 主机采样从机发送的1bit的0数据

橘色也是MASTER SAMPLE 主机采样从机发送的1bit的1数据

3.状态图

主状态机图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ztZOXwII-1644454473362)(C:\Users\Jin\AppData\Roaming\Typora\typora-user-images\image-20220113160659405.png)]

从状态图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-edUjmdgp-1644454473362)(C:\Users\Jin\AppData\Roaming\Typora\typora-user-images\image-20220113161227908.png)]

完整状态图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2PpvYvqF-1644454473363)(C:\Users\Jin\AppData\Roaming\Typora\typora-user-images\image-20220113154806498.png)]

四、代码部分

1.ds18b20_driver.v

// ds18b20驱动模块
module ds18b20_driver(
    input           clk,
    input           rst_n,
    // dq单总线
    input           dq_in,
    output  reg     dq_out,
    output  reg     dq_out_en,
    // 传出的数据
    output  [23:0]  dout,
    output          dout_vld
);

// 各个状态不同的时间
parameter   TIME_1US   = 50,// 1us
            RESET_TIME = 480,// 480us
            M_RELEA_TIME = 20,// 15-60us
            PRESE_TIME = 200,// 60-240us
            WAITC_TIME = 750000,// 750ms
            LOW_TIME   = 2,// >1us
            WRRD_TIME  = 60,//
            S_RELEA_TIME = 3;// > 1us


parameter   SKROM_CMD = 8'hCC,// 跳过ROM命令
            CONTEM_CMD = 8'h44,// 转换温度命令
            RDTEM_CMD = 8'hBE;// 读取温度命令

// 主状态机
localparam  M_IDLE  = 10'b00000_00001,// 默认状态
            M_RESET = 10'b00000_00010,// 主机发送复位脉冲
            M_RELEA = 10'b00000_00100,// 主机释放脉冲
            M_PRESE = 10'b00000_01000,// 从机发送存在脉冲
            M_SKROM = 10'b00000_10000,// 主机发送跳过ROM命令
            M_CTCMD = 10'b00001_00000,// 主机发送温度转换命令
            M_WAITC = 10'b00010_00000,// 温度转换等待
            M_RDCMD = 10'b00100_00000,// 主机发送温度读取命令
            M_RDTEM = 10'b01000_00000,// 主机读取温度数据
            M_DONE  = 10'b10000_00000;

// 从状态机
localparam  S_IDLE  = 6'b000_001,// 默认状态
            S_LOW   = 6'b000_010,// 无论读写总线拉低
            S_MASWR = 6'b000_100,// 写时隙发送1bit
            S_MASRD = 6'b001_000,// 读时隙采样1bit
            S_RELEA = 6'b010_000,// 无乱读写释放总线
            S_DONE  = 6'b100_000;// 1bit数据读写完


// 主状态机状态
reg     [9:0]       m_state_c;
reg     [9:0]       m_state_n;
// 状态转移条件
wire                m_idle2m_reset ;
wire                m_reset2m_relea;
wire                m_relea2m_prese;
wire                m_prese2m_skrom;
wire                m_skrom2m_ctcmd;
wire                m_skrom2m_rdcmd;
wire                m_ctcmd2m_waitc;
wire                m_waitc2m_reset;
wire                m_rdcmd2m_rdtem;
wire                m_rdtem2m_done ;

// 从状态机状态
reg     [5:0]       s_state_c;
reg     [5:0]       s_state_n;
// 状态转移条件
wire                s_idle2s_low   ;
wire                s_low2s_maswr  ;
wire                s_low2s_masrd  ;
wire                s_maswr2s_relea;
wire                s_masrd2s_relea;
wire                s_relea2s_done ;
wire                s_done2s_low   ;
wire                s_done2s_idle  ;

// 1us计数器作为时间基准
reg     [5:0]       cnt_1us;
wire                add_cnt_1us;
wire                end_cnt_1us;

// 各个状态不同时间
reg     [19:0]      cnt;
wire                add_cnt;
wire                end_cnt;
reg     [19:0]      X;

// 字节计数器
reg     [4:0]       cnt_bit;
wire                add_cnt_bit;
wire                end_cnt_bit;

// 跳过ROM命令到温度转换命令还是温度读取命令的标志
reg                 flag;
// 从机发送的存在脉冲
reg                 slave_ack;
// 不同状态的command
reg     [7:0]       command;
// 16bit的原始温度数据
reg     [15:0]      origin_data;
// 原始温度数据处理
reg     [10:0]      temper_data;
// 最后接收的温度数据
wire    [23:0]      rx_data;
// 主状态机
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        m_state_c <= M_IDLE; 
    end 
    else begin 
        m_state_c <= m_state_n;
    end 
end
    
always @(*)begin 
    case (m_state_c)
        M_IDLE      :begin 
                    if(m_idle2m_reset)begin
                        m_state_n = M_RESET;
                    end
                    else begin
                        m_state_n = m_state_c;
                    end 
                   end
        M_RESET      :begin 
                    if(m_reset2m_relea)begin
                        m_state_n = M_RELEA;
                    end
                    else begin
                        m_state_n = m_state_c;
                    end 
                   end
        M_RELEA      :begin 
                    if(m_relea2m_prese)begin
                        m_state_n = M_PRESE;
                    end
                    else begin
                        m_state_n = m_state_c;
                    end 
                   end
        M_PRESE      :begin 
                    if(m_prese2m_skrom)begin
                        m_state_n = M_SKROM;
                    end
                    else begin
                        m_state_n = m_state_c;
                    end 
                   end
        M_SKROM      :begin 
                    if(m_skrom2m_ctcmd)begin
                        m_state_n = M_CTCMD;
                    end
                    else if(m_skrom2m_rdcmd)begin
                        m_state_n = M_RDCMD;
                    end
                    else begin
                        m_state_n = m_state_c;
                    end 
                   end
        M_CTCMD      :begin 
                    if(m_ctcmd2m_waitc)begin
                        m_state_n = M_WAITC;
                    end
                    else begin
                        m_state_n = m_state_c;
                    end 
                   end
        M_WAITC      :begin 
                    if(m_waitc2m_reset)begin
                        m_state_n = M_RESET;
                    end
                    else begin
                        m_state_n = m_state_c;
                    end 
                   end
        M_RDCMD      :begin 
                    if(m_rdcmd2m_rdtem)begin
                        m_state_n = M_RDTEM;
                    end
                    else begin
                        m_state_n = m_state_c;
                    end 
                   end
        M_RDTEM      :begin 
                    if(m_rdtem2m_done)begin
                        m_state_n = M_IDLE;
                    end
                    else begin
                        m_state_n = m_state_c;
                    end 
                   end
        M_DONE      : m_state_n = M_IDLE;
        default: m_state_n = M_IDLE;
    endcase
end
    
assign m_idle2m_reset  = m_state_c == M_IDLE  && (1'b1);
assign m_reset2m_relea = m_state_c == M_RESET && (end_cnt);// 主机控制总线发送复位脉冲480us
assign m_relea2m_prese = m_state_c == M_RELEA && (end_cnt);// 主机释放总线20us
assign m_prese2m_skrom = m_state_c == M_PRESE && (end_cnt) && (~slave_ack);// 从机控制总线发送存在脉冲200us并且接收到低电平的存在脉冲
assign m_skrom2m_ctcmd = m_state_c == M_SKROM && (end_cnt_bit) && (flag == 0);// 8bit跳过ROM命令发送完,并且温度转换
assign m_skrom2m_rdcmd = m_state_c == M_SKROM && (end_cnt_bit) && (flag == 1);// 8bit跳过ROM命令发送完,并且温度读取
assign m_ctcmd2m_waitc = m_state_c == M_CTCMD && (end_cnt_bit);// 8bit温度转换命令发送完
assign m_waitc2m_reset = m_state_c == M_WAITC && (end_cnt);// 等待温度转换400us
assign m_rdcmd2m_rdtem = m_state_c == M_RDCMD && (end_cnt_bit);// 8bit温度读取命令发送完
assign m_rdtem2m_done  = m_state_c == M_RDTEM && (end_cnt_bit);// 16bit温度数据读取完

// 从状态机
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        s_state_c <= S_IDLE; 
    end 
    else begin 
        s_state_c <= s_state_n;
    end 
end
    
always @(*)begin 
    case (s_state_c)
        S_IDLE      :begin 
                    if(s_idle2s_low)begin
                        s_state_n = S_LOW;
                    end
                    else begin
                        s_state_n = s_state_c;
                    end 
                   end
        S_LOW      :begin 
                    if(s_low2s_maswr)begin
                        s_state_n = S_MASWR;
                    end
                    else if(s_low2s_masrd)begin
                        s_state_n = S_MASRD;
                    end
                    else begin
                        s_state_n = s_state_c;
                    end 
                   end
        S_MASWR      :begin 
                    if(s_maswr2s_relea)begin
                        s_state_n = S_RELEA;
                    end
                    else begin
                        s_state_n = s_state_c;
                    end 
                   end
        S_MASRD      :begin 
                    if(s_masrd2s_relea)begin
                        s_state_n = S_RELEA;
                    end
                    else begin
                        s_state_n = s_state_c;
                    end 
                   end
        S_RELEA      :begin 
                    if(s_relea2s_done)begin
                        s_state_n = S_DONE;
                    end
                    else begin
                        s_state_n = s_state_c;
                    end 
                   end
        S_DONE      :begin 
                    if(s_done2s_low)begin
                        s_state_n = S_LOW;
                    end
                    else if(s_done2s_idle)begin
                        s_state_n = S_IDLE;
                    end
                    else begin
                        s_state_n = s_state_c;
                    end 
                   end
        default: s_state_n = S_IDLE;
    endcase
end
    
assign s_idle2s_low     = s_state_c == S_IDLE  && (((m_state_c == M_SKROM) || (m_state_c == M_CTCMD) || (m_state_c == M_RDCMD) || (m_state_c == M_RDTEM)));// 到读写时候
assign s_low2s_maswr    = s_state_c == S_LOW   && (((m_state_c == M_SKROM) || (m_state_c == M_CTCMD) || (m_state_c == M_RDCMD)) && (end_cnt));// 发送命令先拉低总线2us
assign s_low2s_masrd    = s_state_c == S_LOW   && ((m_state_c == M_RDTEM) && (end_cnt));// 读取数据先拉低总线2us
assign s_maswr2s_relea  = s_state_c == S_MASWR && (end_cnt);// 写1bit数据60us
assign s_masrd2s_relea  = s_state_c == S_MASRD && (end_cnt);// 读1bit数据60us
assign s_relea2s_done   = s_state_c == S_RELEA && (end_cnt);// 1bit数据读写完释放总线2us
assign s_done2s_low     = s_state_c == S_DONE  && (~end_cnt_bit);// 8bit或者16bite没有全部读写完
assign s_done2s_idle    = s_state_c == S_DONE  && (end_cnt_bit);// 8bit或者16bit全部读写完
// 1us计数器
always @(posedge clk or negedge rst_n)begin 
   if(!rst_n)begin
        cnt_1us <= 0;
    end 
    else if(add_cnt_1us)begin 
            if(end_cnt_1us)begin 
                cnt_1us <= 0;
            end
            else begin 
                cnt_1us <= cnt_1us + 1;
            end 
    end
   else  begin
       cnt_1us <= cnt_1us;
    end
end 

assign add_cnt_1us = 1'b1;
assign end_cnt_1us = add_cnt_1us && cnt_1us == TIME_1US - 1;

// 各个状态不同时间计数器
always @(posedge clk or negedge rst_n)begin 
   if(!rst_n)begin
        cnt <= 0;
    end 
    else if(add_cnt)begin 
            if(end_cnt)begin 
                cnt <= 0;
            end
            else begin 
                cnt <= cnt + 1;
            end 
    end
   else  begin
       cnt <= cnt;
    end
end 

assign add_cnt = end_cnt_1us;
assign end_cnt = add_cnt && cnt == X - 1;

// 各个状态不同时间
always @(*)begin 
    if(m_state_c == M_RESET)begin
        X = RESET_TIME;
    end
    else if(m_state_c == M_RELEA)begin
        X = M_RELEA_TIME;
    end
    else if(m_state_c == M_PRESE)begin
        X = PRESE_TIME;
    end
    else if(m_state_c == M_WAITC)begin
        X = WAITC_TIME;
    end
    else if(s_state_c == S_LOW)begin
        X = LOW_TIME;
    end
    else if((s_state_c == S_MASWR) || (s_state_c == S_MASRD))begin
        X = WRRD_TIME;
    end
    else if(s_state_c == S_RELEA)begin
        X = S_RELEA_TIME;
    end
end

// 字节计数器
always @(posedge clk or negedge rst_n)begin 
   if(!rst_n)begin
        cnt_bit <= 0;
    end 
    else if(add_cnt_bit)begin 
            if(end_cnt_bit)begin 
                cnt_bit <= 0;
            end
            else begin 
                cnt_bit <= cnt_bit + 1;
            end 
    end
   else  begin
       cnt_bit <= cnt_bit;
    end
end 

assign add_cnt_bit = s_relea2s_done;// 1bit数据度写完,并且释放总线时间结束
assign end_cnt_bit = add_cnt_bit && cnt_bit == ((m_state_c == M_RDTEM)?(16 - 1):(8 - 1));

// 跳过ROM命令到温度转换命令还是温度读取命令的标志
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        flag <= 0;
    end 
    else if(m_waitc2m_reset)begin 
        flag <= 1'b1;
    end 
    else if(m_rdtem2m_done)begin 
        flag <= 1'b0;
    end 
end

// 单总线dq
// dq_in从机向主机发送存在脉冲
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        slave_ack <= 1'b1;
    end 
    else if(m_state_c == M_PRESE && cnt == 60 && end_cnt_1us)begin 
        slave_ack <= dq_in;
    end 
    else if(m_prese2m_skrom)begin
        slave_ack <= 1'b1;
    end
end


// dq_out_en主机向从机发送使能
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        dq_out_en <= 0;
    end 
    else if(m_idle2m_reset || m_waitc2m_reset || s_idle2s_low || s_done2s_low || m_prese2m_skrom || m_skrom2m_ctcmd || m_skrom2m_rdcmd || s_low2s_maswr)begin 
        dq_out_en <= 1'b1; 
    end 
    else if(m_reset2m_relea || s_maswr2s_relea || s_low2s_masrd || m_rdcmd2m_rdtem || s_low2s_masrd)begin 
        dq_out_en <= 1'b0;
    end 
end

// dq_out主机向从机发送的数据
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        dq_out <= 0;
    end 
    else if(m_idle2m_reset || m_waitc2m_reset || s_idle2s_low || s_done2s_low)begin 
        dq_out <= 0;
    end 
    else if(s_low2s_maswr)begin 
        dq_out <= command[cnt_bit];
    end 
end

// 不同状态的command
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        command <= 0;
    end 
    else if(m_prese2m_skrom)begin 
        command <= SKROM_CMD;
    end 
    else if(m_skrom2m_ctcmd)begin 
        command <= CONTEM_CMD;
    end 
    else if(m_skrom2m_rdcmd)begin
        command <= RDTEM_CMD;
    end
end

// dq_in从机向主机发送16bit的温度数据
// 1bit数据在2-15us以内采集
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        origin_data <= 0;
    end 
    else if(s_state_c == S_MASRD && cnt == 13 && end_cnt_1us)begin 
        origin_data[cnt_bit] <= dq_in;
    end 
end

// 接收到的数据进行处理
// 是一个10进制的数据
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        temper_data <= 0;
    end 
    else if(m_rdtem2m_done)begin
        if(origin_data[15])begin 
            temper_data <= ~origin_data[10:0] + 1'b1;
        end 
        else begin 
            temper_data <= origin_data[10:0];
        end 
    end
end

// 得到的数据带有3位小数
assign rx_data = temper_data * 625;

assign dout = rx_data;
assign dout_vld = m_rdtem2m_done;

endmodule

2.ds18b20_ctrl.v

module ds18b20_ctrl(
    input           clk,
    input           rst_n,
    input   [23:0]  din,
    input           din_vld,
    output  [23:0]  dout
);

reg     [23:0]      rx_data;
wire    [3:0]       rx_data_r0;
wire    [3:0]       rx_data_r1;
wire    [3:0]       rx_data_r2;
wire    [3:0]       rx_data_r3;
wire    [3:0]       rx_data_r4;
wire    [3:0]       rx_data_r5;

always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        rx_data <= 0;
    end 
    else if(din_vld)begin 
        rx_data <= din;
    end 
end

assign rx_data_r0 = rx_data%10;
assign rx_data_r1 = (rx_data/10)%10;
assign rx_data_r2 = (rx_data/100)%10;
assign rx_data_r3 = (rx_data/1000)%10;
assign rx_data_r4 = (rx_data/10000)%10;
assign rx_data_r5 = (rx_data/100000)%10;

assign dout ={rx_data_r5,rx_data_r4,rx_data_r3,rx_data_r2,rx_data_r1,rx_data_r0};


endmodule

3.top.v

module top(
    input           clk,
    input           rst_n,
    inout           dq,
    output  [7:0]   seg_dig,
    output  [5:0]   seg_sel,
    output          uart_tx  
);
wire            dq_in;
wire            dq_out;
wire            dq_out_en;
wire    [23:0]  tem_data;
wire            tem_data_vld;
wire    [23:0]  seg_data;
wire    [7:0]   uart_tx_data;

// 三态门
assign dq_in = dq;
assign dq = dq_out_en?dq_out:1'bz;

assign uart_tx_data = seg_data[23:16];


// ds18b20驱动模块
ds18b20_driver u_ds18b20_driver(
    /* input            */.clk          (clk      ),
    /* input            */.rst_n        (rst_n    ),
    /* input            */.dq_in        (dq_in    ),
    /* output           */.dq_out       (dq_out   ),
    /* output           */.dq_out_en    (dq_out_en),
    /* output  [23:0]   */.dout         (tem_data ),
    /* output           */.dout_vld     (tem_data_vld )
);

// 串口发送模块
uart_tx u_uart_tx(
    /* input            */.clk          (clk     ),
    /* input            */.rst_n        (rst_n   ),
    /* input            */.baud_sel     (0),// 波特率的选择
    /* input   [7:0]    */.din          (uart_tx_data),// 串并转换的数据
    /* input            */.din_vld      (tem_data_vld ),// 串并转换的数据有效
    /* output           */.dout         (uart_tx   ),// 发送模块发送的1bit数据
    /* output           */.busy         (busy    ) // 发送模块忙标志
);
// ds18b20控制模块 
ds18b20_ctrl u_ds18b20_ctrl(
    /* input            */.clk          (clk     ),
    /* input            */.rst_n        (rst_n   ),
    /* input   [23:0]   */.din          (tem_data),
    /* input            */.din_vld      (tem_data_vld ),
    /* output  [23:0]   */.dout         (seg_data )
);

seg_driver u_seg_driver(
    /* input                        */.clk          (clk    ),
    /* input                        */.rst_n        (rst_n  ),
    /* input           [23:0]       */.data         (seg_data),
    /* output   reg    [7:0]        */.seg_dig      (seg_dig),
    /* output   reg    [5:0]        */.seg_sel      (seg_sel)
);

endmodule

4.seg_driver

// 数码管驱动
module seg_driver(
    input                       clk,
    input                       rst_n,
    input           [23:0]      data,
    output   reg    [7:0]       seg_dig,
    output   reg    [5:0]       seg_sel
);

localparam  ZERO  = 7'b100_0000,
            ONE   = 7'b111_1001,
            TWO   = 7'b010_0100,
            THREE = 7'b011_0000,
            FOUR  = 7'b001_1001,
            FIVE  = 7'b001_0010,
            SIX   = 7'b000_0010,
            SEVEN = 7'b111_1000,
            EIGHT = 7'b000_0000,
            NINE  = 7'b001_0000;

parameter SCAN_TIME = 50_000;

// 扫描计数器1ms
reg     [17:0]      scan_cnt;
wire                add_scan_cnt;
wire                end_scan_cnt;

reg     [3:0]       num;

// 小数点
reg                 point;

// 扫描计数器1ms
always @(posedge clk or negedge rst_n)begin 
   if(!rst_n)begin
        scan_cnt <= 0;
    end 
    else if(add_scan_cnt)begin 
            if(end_scan_cnt)begin 
                scan_cnt <= 0;
            end
            else begin 
                scan_cnt <= scan_cnt + 1;
            end 
    end
   else  begin
       scan_cnt <= scan_cnt;
    end
end 

assign add_scan_cnt = 1'b1;
assign end_scan_cnt = add_scan_cnt && scan_cnt == SCAN_TIME - 1;

// 计数器扫描位选
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        seg_sel <= 6'b111110;
    end 
    else if(end_scan_cnt)begin 
        seg_sel <= {seg_sel[4:0],seg_sel[5]};
    end 
    else begin 
        seg_sel <= seg_sel;
    end 
end

// 根据位选来给数据
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        num <= 4'b0;
        point <= 1'b0;
    end 
    else begin 
        case (seg_sel)
            6'b111110   :   begin 
                                num <= data[3:0];
                                point <= 1'b1;
                            end
            6'b111101   :   begin 
                                num <= data[7:4];
                                point <= 1'b1;
                            end
            6'b111011   :   begin 
                                num <= data[11:8];
                                point <= 1'b1;
                            end
            6'b110111   :   begin 
                                num <= data[15:12];
                                point <= 1'b1;
                            end
            6'b101111   :   begin 
                                num <= data[19:16];
                                point <= 1'b0;
                            end
            6'b011111   :   begin 
                                num <= data[23:20];
                                point <= 1'b1;
                            end                                                 
            default: ;
        endcase
    end 
end


// 根据num来对段选赋值
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        seg_dig <= 8'hff;
    end 
    else begin 
        case (num)
            0   :   seg_dig <={point,ZERO};
            1   :   seg_dig <={point,ONE};
            2   :   seg_dig <={point,TWO};
            3   :   seg_dig <={point,THREE};
            4   :   seg_dig <={point,FOUR};
            5   :   seg_dig <={point,FIVE};
            6   :   seg_dig <={point,SIX};
            7   :   seg_dig <={point,SEVEN};
            8   :   seg_dig <={point,EIGHT};
            9   :   seg_dig <={point,NINE};
            default: ;
        endcase
    end 
end
endmodule

五、仿真验证

仿真验证只是看看ds18b20的驱动的状态转移是否正确

`timescale 1ns/1ns
                
module ds18b20_driver_tb();
//激励信号定义 
reg				tb_clk  	;
reg				tb_rst_n	;
reg				tb_dq_in	;

//输出信号定义	 
wire			tb_dq_out	;
wire			tb_dq_out_en;
wire	[7:0]	tb_dout     ;
wire			tb_dout_vld ;
                                          
//时钟周期参数定义					        
    parameter		CLOCK_CYCLE = 20;    
                                          
ds18b20_driver u_ds18b20_driver(
    /* input            */.clk          (tb_clk      ),
    /* input            */.rst_n        (tb_rst_n    ),
    /* input            */.dq_in        (tb_dq_in    ),
    /* output  reg      */.dq_out       (tb_dq_out   ),
    /* output  reg      */.dq_out_en    (tb_dq_out_en),   
    /* output  [7:0]    */.dout         (tb_dout     ),
    /* output           */.dout_vld     (tb_dout_vld )
);
//产生时钟							       		
initial 		tb_clk = 1'b0;		       		
always #(CLOCK_CYCLE/2) tb_clk = ~tb_clk;  		

integer i;                      
//产生激励							       		
initial  begin						       		
    tb_rst_n = 1'b1;								
    tb_dq_in = 0;								
    #(CLOCK_CYCLE*2);				            
    tb_rst_n = 1'b0;							
    #(CLOCK_CYCLE*20);				            
    tb_rst_n = 1'b1;	
    // #(CLOCK_CYCLE*1000);
    repeat(5)begin
        for(i=0;i<10000;i=i+1)begin
            tb_dq_in = {$random};
            #(CLOCK_CYCLE*10);
        end
        #(CLOCK_CYCLE*10);
    end
    #(CLOCK_CYCLE*500);					
    $stop;                      
                                                                                
end 									       	
endmodule 		

                                                   

在这里插入图片描述
我这里750ms改成了75ns

六、上板验证

在这里插入图片描述

七、总结

这个ds18b20不需要什么协议,算是第二个做出来的成就,这个ds18b20让我学会了如何看手册,一步步的去找关键点。
注意一下符号的优先级,我就死在&& 和 || 上 调试了半天。

  • 29
    点赞
  • 179
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
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 ]
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值