驱动编写-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