当前位置: 首页 > article >正文

驱动编写-DS18B20温度传感器

 经过阅读DS18B20温度传感器的datasheet,知道它的驱动主要是靠状态机的编写去控制。下面记录了一些基本知识。

因为DQ线旁边的4.7k上拉电阻的影响,单总线(1-WIRE)释放均保持高电平,受激发时为低电平。如下图所示

本次读数据只用读出缓存器9位数据中的前两位温度数据就可以了,如下图: 

数据的格式:

一、初始化序列:

主机和从机(DS18B20)间的任何通讯都需要以初始化序列开始。初始化时序如图:

默认情况下数据线是由一个上拉电阻拉高保持在高电平,主机需要先发送一个低电平的复位脉冲给数据线拉低,该复位脉冲需要保持在480us-960us之间,拉低后主机释放该单总线15-60us由上拉电阻把总线拉高,释放(等待延时)完成后等待18B20发出一个低电平脉冲响应信号,该响应信号响应时间维持在60-240us之间,如果主机检测到该响应信号,主机才可以继续后面的操作。

初始化代码(有注解)按照该时序图编写:

    //根据上述四个温度读取过程编写状态机
    always@(posedge clk_1us or negedge rst_n)begin
        if(!rst_n)begin
            flow_cnt   <= 4'b0;
            init_done  <= 4'b0;
            cnt_1us_en <= 1'b1;
            sta_done   <= 1'b0;
        end
        else begin
            sta_done   <= 1'b0;
            case(nxt_state)
                init:begin
                    init_done <= 1'b0;
                    case(flow_cnt)
                        4'd0:flow_cnt <= flow_cnt + 1;
                        4'd1:begin
                            clk_1us_en <= 1'b1;
                            if(cnt_1us < 20'd500)
                                dq_out <= 1'b0;
                            else begin
                                dq_out <= 1'bz;             //高阻则释放总线
                                clk_1us_en <= 1'b0;         //微秒使能计数器从1置为0时会清空cnt_1us
                                flow_cnt   <=  flow_cnt + 1;
                            end
                           end
                        4'd2:begin                          //释放总线15-60us,设置为30us
                             cnt_lus_en <= 1'b1;
                             if(cnt_1us < 20'd30)
                                 dq_out <= 1'bz; 
                             else begin 
                                 flow_cnt <= flow_cnt + 1'b1;
                                 //cnt_lus_en <= 1'b0;
                             end                                
                            end  
                        4'd3:begin
                            if(dq == 0 )  //dq为低表示主机检测到该响应信号
                                flow_cnt <= flow_cnt + 1'b1;
                             else 
                                flow_cnt <= flow_cnt;
                             end
                        4'd4:begin   
                            //cnt_lus_en <= 1'b1
                            if(cnt_1us == 20'd500)begin
                                cnt_1us_en <= 1'b0;
                                //dq_out <= 1'bz;
                                init_done <= 1'b0;
                                flow_cnt<= 4'd0;  
                             end
                             else 
                                flow_cnt <= flow_cnt;
                             end
                        default:flow_cnt <= 4'd0;                         
            endcase
        end

注意:最后的4’d4部分用于等待初始化完成,在这个阶段,总线的状态应该是由外部上拉电阻维持的高电平,因此主机不需要再次显式地将 dq_out 在if语句里设置为高阻态(1'bz)。

二、写数据到DS18B20

写、读数据时序(datasheet):

后续代码汇总:

`timescale 1ns / 1ps
//
// Company: 
// Engineer: 
// 
// Create Date: 2025/03/24 13:46:48
// Design Name: 
// Module Name: dss18b20
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//
module dss18b20(
    //module clock
    input              clk        ,          //产生50MHz,对应时钟周期0.02us
    input              rst_n      ,         // 复位信号

    //user interface
    inout              dq         ,         // DS18B20的DQ引脚数据
    output reg [19:0]  temp_data  ,         // 转换后的温度
    output reg         sign                 // 正负
);

//DS18B20的四个温度读取过程:
// 1'跳过ROM命令 2'发温度转换命令 3'跳过ROM命令 4'发读温度暂存器命令
localparam  ROM_SKIP_CMD = 8'hcc;     // 1'跳过ROM命令          
localparam  CONVERT_CMD  = 8'h44;     // 2'发温度转换命令          
localparam  READ_TEMP    = 8'hbe;     // 3'读 DS1820 温度暂存器命令 
 //整个的状态定义、流程
localparam  init         = 3'd1 ;       //初始化状态         
localparam  rom_skip     = 3'd2 ;      //跳过rom          
localparam  wr_byte      = 3'd3 ;       //写             
localparam  temp_convert = 3'd4 ;      //加载温度转换命令       
localparam  delay        = 3'd5 ;      //等温度转换完成        
localparam  rd_temp      = 3'd6 ;           
localparam  rd_byte      = 3'd7 ;           

//reg define
reg     [ 4:0]         cnt         ;        
reg                    clk_1us     ;        
reg     [19:0]         cnt_1us     ;  
      
reg     [ 2:0]         cur_state   ;        
reg     [ 2:0]         next_state  ;  
      
reg     [ 3:0]         flow_cnt    ;        
reg     [ 3:0]         wr_cnt      ;        
reg     [ 4:0]         rd_cnt      ;        
reg     [ 7:0]         wr_data     ;        //写入18b20数据  
reg     [ 4:0]         bit_width   ;        //读取的数据位宽
reg     [15:0]         rd_data     ;        //采集到的温度数据
reg     [15:0]         all_data    ;        //通过rd_data寄存器读取到的数据
reg     [10:0]         data1       ;  
      
reg     [ 3:0]         cmd_cnt     ;        
reg                    init_done   ;        
reg                    st_done     ;        
reg                    cnt_1us_en  ;        
reg                    dq_out      ;       //主机输出给DS18B20

wire    [19:0]         data2       ;        

assign dq = dq_out;

//时钟分频
always @ (posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        cnt     <= 5'b0;
        clk_1us <= 1'b0;
    end
    else if(cnt < 5'd24) begin      //25x0.02usx2=1us
        cnt     <= cnt + 1'b1;
        clk_1us <= clk_1us;
    end
    else begin
        cnt     <= 5'b0;
        clk_1us <= ~clk_1us;
    end
end

 //计数多少微秒
always @ (posedge clk_1us or negedge rst_n) begin
    if (!rst_n)
        cnt_1us <= 20'b0;
    else if (cnt_1us_en)
        cnt_1us <= cnt_1us + 1'b1;     //控制主机发送低电平的时间和发送高电平的时间
    else
        cnt_1us <= 20'b0;
end

always @ (posedge clk_1us or negedge rst_n) begin
    if(!rst_n)
        cur_state <= init;
    else 
        cur_state <= next_state;
end

always @( * ) begin
    case(cur_state)
        init: begin                             
                if (init_done)
                next_state = rom_skip;
            else
                next_state = init;
        end
        rom_skip: begin                         // 加载跳过ROM命令
            if(st_done)
                next_state = wr_byte;
            else
                next_state = rom_skip;
        end
        wr_byte: begin                          // 发送命令
            if(st_done)
                case(cmd_cnt)                   // 根据命令序号,判断下个状态
                    4'b1: next_state = temp_convert;
                    4'd2: next_state = delay;
                    4'd3: next_state = rd_temp;
                    4'd4: next_state = rd_byte;
                    default: 
                            next_state = temp_convert;
                endcase
            else
                next_state = wr_byte;
        end
        temp_convert: begin                     // 加载温度转换命令
            if(st_done)
                next_state = wr_byte;
            else
                next_state = temp_convert;
        end
        delay: begin                            // 延时等待温度转换结束
            if(st_done)
                next_state = init;
            else
                next_state = delay;
        end
        rd_temp: begin                          // 加载读温度命令
            if(st_done)
                next_state = wr_byte;
            else
                next_state = rd_temp;
        end
        rd_byte: begin                          // 读数据线上的数据
            if(st_done)
                next_state = init;
            else
                next_state = rd_byte;
        end
        default: next_state = init;
    endcase
end

 //根据上述四个温度读取过程编写状态机
always @ (posedge clk_1us or negedge rst_n) begin
    if(!rst_n) begin
        flow_cnt     <=  4'b0;
        init_done    <=  1'b0;
        cnt_1us_en   <=  1'b1;
        dq_out       <=  1'bZ;
        st_done      <=  1'b0;
        rd_data      <= 16'b0;
        rd_cnt       <=  5'd0;
        wr_cnt       <=  4'd0;
        cmd_cnt      <=  3'd0;
    end
    else begin
        st_done <= 1'b0;
        case (next_state)
            init:begin                              //初始化
                init_done <= 1'b0;
                case(flow_cnt)
                    4'd0:
                        flow_cnt <= flow_cnt + 1'b1;
                    4'd1: begin                 //主机发出500us复位脉冲
                        cnt_1us_en <= 1'b1;         
                        if(cnt_1us < 20'd500)
                            dq_out <= 1'b0;         
                        else begin
                            cnt_1us_en <= 1'b0;     //微秒使能计数器从1置为0时会清空cnt_1us
                            dq_out <= 1'bz;         //高阻则释放总线
                            flow_cnt <= flow_cnt + 1'b1;
                        end
                    end 
                    4'd2:begin                      //释放总线15-60us,设置为30us
                        cnt_1us_en <= 1'b1;
                        if(cnt_1us < 20'd30)
                            dq_out <= 1'bz;
                        else
                            flow_cnt <= flow_cnt + 1'b1;
                    end
                   //当主机发送复位脉冲后,DS18B20 会在释放总线后拉低 dq 引脚(60-240μs)作为应答
                    //dq_out 是主机的输出状态:此时 dq_out 已被主机释放总线,但实际引脚电平由 DS18B20 控制,因此必须读取物理引脚dq。
                    4'd3: begin                     
                        if(dq ==0 )         //dq为低表示主机检测到该响应信号
                            flow_cnt <= flow_cnt + 1'b1;
                        else
                            flow_cnt <= flow_cnt;
                    end
                    4'd4: begin                     //该部分用于等待初始化完成
                        if(cnt_1us == 20'd500) begin
                            cnt_1us_en <= 1'b0;
                         //在这个阶段,总线的状态应该是由外部上拉电阻维持的高电平,因此主机不需要再次显式地将 dq_out 设置为高阻态(1'bz)。
                         //dq_out <= 1'bz;
                            init_done  <= 1'b1;     
                            flow_cnt   <= 4'd0;
                        end
                        else
                            flow_cnt <= flow_cnt;
                    end
                    default: flow_cnt <= 4'd0;
                endcase
            end
            rom_skip: begin                         //加载跳过rom指令
                wr_data  <= ROM_SKIP_CMD;
                flow_cnt <= 4'd0;
                st_done  <= 1'b1;
            end
            wr_byte: begin                          //写数据有8个位宽
                if(wr_cnt <= 4'd7) begin
                    case (flow_cnt)
                        4'd0: begin
                            dq_out <= 1'b0;         //拉低数据线,开始写操作
                            cnt_1us_en <= 1'b1;     
                            flow_cnt <= flow_cnt + 1'b1;
                        end
                        4'd1: begin                 //数据线拉低1us
                            flow_cnt <= flow_cnt + 1'b1;
                        end
                        4'd2: begin
                            if(cnt_1us < 20'd60)    
                                dq_out <= wr_data[wr_cnt];
                            else if(cnt_1us < 20'd63)   
                                dq_out <= 1'bz;     //延时63微秒释放总线
                            else
                                flow_cnt <= flow_cnt + 1'b1;
                        end
                        4'd3: begin                 //发送1位数据完成
                            flow_cnt <= 0;
                            cnt_1us_en <= 1'b0;
                            wr_cnt <= wr_cnt + 1'b1;//写计数器加1
                        end
                        default : flow_cnt <= 0;
                    endcase
                end
                else begin                         
                    st_done <= 1'b1;
                    wr_cnt <= 4'd0;
                    cmd_cnt <= (cmd_cnt == 3'd4) ?  
                               3'd1 : (cmd_cnt+ 1'b1);
                end
            end
            temp_convert: begin                     //加载温度转换命令
                wr_data <= CONVERT_CMD;
                st_done <= 1'b1;
            end
            delay: begin                           //延时500ms等待温度转换结束
                cnt_1us_en <= 1'b1;
                if(cnt_1us == 20'd500000) begin
                    st_done <= 1'b1;
                    cnt_1us_en <= 1'b0;
                end 
            end 
            rd_temp: begin                          
                wr_data <= READ_TEMP;
                bit_width <= 5'd16;                 
                st_done <= 1'b1;
            end
            rd_byte: begin                          //接收16位温度数据
                if(rd_cnt < bit_width) begin
                    case(flow_cnt)
                        4'd0: begin
                            cnt_1us_en <= 1'b1;
                            dq_out <= 1'b0;         //主机拉低数据线
                            flow_cnt <= flow_cnt + 1'b1; 
                        end
//                  4'd1:begin
//                     flow_cnt   <= flow_cnt + 1'd1; //延迟1us
//                     end
                        4'd1: begin
                            dq_out <= 1'bz;         //释放总线并在15us内接收数据
                            if(cnt_1us == 20'd14) begin
                                rd_data <= {dq,rd_data[15:1]};//数据右移进来,dq脚先出低位数据
                                flow_cnt <= flow_cnt + 1'b1 ;
                            end
                        end
                        4'd2: begin
                            if (cnt_1us <= 20'd64)  //一位数据读取完成
                                dq_out <= 1'bz;     //15-60us接着释放总线
                            else begin
                                flow_cnt <= 4'd0;   
                                rd_cnt <= rd_cnt + 1'b1;//读计数器加1
                                cnt_1us_en <= 1'b0;
                            end
                        end
                        default : flow_cnt <= 4'd0;
                    endcase
                end
                else begin
                    st_done <= 1'b1;
                    all_data  <= rd_data;
                    rd_cnt <= 5'b0;
                end
            end
            default: ;
        endcase
    end 
end

//截位前11位数据
always @(posedge clk_1us or negedge rst_n) begin
    if(!rst_n) begin
        sign  <=  1'b0;
        data1 <= 11'b0;
    end
    else if(all_data[15] == 1'b0) begin
        sign  <= 1'b0;
        data1 <= all_data[10:0];
    end
    else if(all_data[15] == 1'b1) begin
        sign  <= 1'b1;
        data1 <= ~all_data[10:0] + 1'b1;
    end
end

//转换温度
assign data2 = (data1 * 11'd625)/ 7'd100;

 // reg [19:0] temp_data;
 //温度输出
always @(posedge clk_1us or negedge rst_n) begin
    if(!rst_n)
        temp_data <= 20'b0;
    else
        temp_data <= data2;
end

endmodule

上板验证:

利用ILA抓取数据:

记录将三段式状态机改为一段式状态机:

// 状态机部分改为一段式
always @ (posedge clk_1us or negedge rst_n) begin
    if(!rst_n) begin
        cur_state   <= init;
        flow_cnt    <= 4'b0;
        init_done   <= 1'b0;
        cnt_1us_en  <= 1'b1;
        dq_out      <= 1'bZ;
        st_done     <= 1'b0;
        rd_data     <= 16'b0;
        rd_cnt      <= 5'd0;
        wr_cnt      <= 4'd0;
        cmd_cnt     <= 3'd0;
    end
    else begin
        st_done <= 1'b0;
        case (cur_state)
            init: begin                              //初始化
                init_done <= 1'b0;
                case(flow_cnt)
                    4'd0:
                        flow_cnt <= flow_cnt + 1'b1;
                    4'd1: begin                 //主机发出500us复位脉冲
                        cnt_1us_en <= 1'b1;         
                        if(cnt_1us < 20'd500)
                            dq_out <= 1'b0;         
                        else begin
                            cnt_1us_en <= 1'b0;     //微秒使能计数器从1置为0时会清空cnt_1us
                            dq_out <= 1'bz;         //高阻则释放总线
                            flow_cnt <= flow_cnt + 1'b1;
                        end
                    end 
                    4'd2:begin                      //释放总线15-60us,设置为30us
                        cnt_1us_en <= 1'b1;
                        if(cnt_1us < 20'd30)
                            dq_out <= 1'bz;
                        else
                            flow_cnt <= flow_cnt + 1'b1;
                    end
                    4'd3: begin                     
                        if(dq ==0 )         //dq为低表示主机检测到该响应信号
                            flow_cnt <= flow_cnt + 1'b1;
                        else
                            flow_cnt <= flow_cnt;
                    end
                    4'd4: begin                     //该部分用于等待初始化完成
                        if(cnt_1us == 20'd500) begin
                            cnt_1us_en <= 1'b0;
                            init_done  <= 1'b1;     
                            flow_cnt   <= 4'd0;
                            cur_state  <= rom_skip;
                        end
                        else
                            flow_cnt <= flow_cnt;
                    end
                    default: flow_cnt <= 4'd0;
                endcase
            end
            rom_skip: begin                         //加载跳过rom指令
                wr_data  <= ROM_SKIP_CMD;
                flow_cnt <= 4'd0;
                st_done  <= 1'b1;
                cur_state <= wr_byte;
            end
            wr_byte: begin                          //写数据有8个位宽
                if(wr_cnt <= 4'd7) begin
                    case (flow_cnt)
                        4'd0: begin
                            dq_out <= 1'b0;         //拉低数据线,开始写操作
                            cnt_1us_en <= 1'b1;     
                            flow_cnt <= flow_cnt + 1'b1;
                        end
                        4'd1: begin                 //数据线拉低1us
                            flow_cnt <= flow_cnt + 1'b1;
                        end
                        4'd2: begin
                            if(cnt_1us < 20'd60)    
                                dq_out <= wr_data[wr_cnt];
                            else if(cnt_1us < 20'd63)   
                                dq_out <= 1'bz;     //延时63微秒释放总线
                            else
                                flow_cnt <= flow_cnt + 1'b1;
                        end
                        4'd3: begin                 //发送1位数据完成
                            flow_cnt <= 0;
                            cnt_1us_en <= 1'b0;
                            wr_cnt <= wr_cnt + 1'b1;//写计数器加1
                        end
                        default : flow_cnt <= 0;
                    endcase
                end
                else begin                         
                    st_done <= 1'b1;
                    wr_cnt <= 4'd0;
                    cmd_cnt <= (cmd_cnt == 3'd4) ? 3'd1 : (cmd_cnt + 1'b1);
                    
                    case(cmd_cnt)                   // 根据命令序号,判断下个状态
                        4'b1: cur_state <= temp_convert;
                        4'd2: cur_state <= delay;
                        4'd3: cur_state <= rd_temp;
                        4'd4: cur_state <= rd_byte;
                        default: cur_state <= temp_convert;
                    endcase
                end
            end
            temp_convert: begin                     //加载温度转换命令
                wr_data <= CONVERT_CMD;
                st_done <= 1'b1;
                cur_state <= wr_byte;
            end
            delay: begin                           //延时500ms等待温度转换结束
                cnt_1us_en <= 1'b1;
                if(cnt_1us == 20'd500000) begin
                    st_done <= 1'b1;
                    cnt_1us_en <= 1'b0;
                    cur_state <= init;
                end 
            end 
            rd_temp: begin                          
                wr_data <= READ_TEMP;
                bit_width <= 5'd16;                 
                st_done <= 1'b1;
                cur_state <= wr_byte;
            end
            rd_byte: begin                          //接收16位温度数据
                if(rd_cnt < bit_width) begin
                    case(flow_cnt)
                        4'd0: begin
                            cnt_1us_en <= 1'b1;
                            dq_out <= 1'b0;         //主机拉低数据线
                            flow_cnt <= flow_cnt + 1'b1; 
                        end
                        4'd1: begin
                            dq_out <= 1'bz;         //释放总线并在15us内接收数据
                            if(cnt_1us == 20'd14) begin
                                rd_data <= {dq,rd_data[15:1]};//数据右移进来,dq脚先出低位数据
                                flow_cnt <= flow_cnt + 1'b1 ;
                            end
                        end
                        4'd2: begin
                            if (cnt_1us <= 20'd64)  //一位数据读取完成
                                dq_out <= 1'bz;     //15-60us接着释放总线
                            else begin
                                flow_cnt <= 4'd0;   
                                rd_cnt <= rd_cnt + 1'b1;//读计数器加1
                                cnt_1us_en <= 1'b0;
                            end
                        end
                        default : flow_cnt <= 4'd0;
                    endcase
                end
                else begin
                    st_done <= 1'b1;
                    all_data  <= rd_data;
                    rd_cnt <= 5'b0;
                    cur_state <= init;
                end
            end
            default: cur_state <= init;
        endcase
    end 
end


http://www.kler.cn/a/614257.html

相关文章:

  • 基于CentOS7.0系统搭建FTP服务器
  • 数据结构(4)——带哨兵位循环双向链表
  • 「查缺补漏」巩固你的 RabbitMQ 知识体系
  • 【力扣刷题|第十七天】0-1 背包 完全背包
  • 测试cursor编辑器
  • 常用登录Linux系统的方法以及操作指南
  • Matlab安装tdms插件
  • spring-ai-starter-mcp-client小试牛刀
  • 【SQL性能优化】预编译SQL:从注入防御到性能飞跃
  • 从零实现3D自动标注:MS3D、MS3D++
  • GAMMA测试方法及分析
  • 微服务2.0
  • C++编程语言:抽象机制:一个矩阵的设计(Bjarne Stroustrup)
  • Blender多摄像机怎么指定相机渲染图像
  • CentOS 安装 zip
  • 金融级密码管理器——跨设备同步的端到端加密方案
  • C++11 -表达式/包装器
  • 质量工程:数字化转型时代的质量体系重构
  • Java NIO之FileChannel 详解
  • 每日一题之既约分数