一、简介
在上一篇文章中我们对于DS18B20的相关理论进行了详细的解释,同时也对怎样使用DS18B20进行了一个简单的叙述。在这篇文章我们通过工程来实现DS18B20的温度读取并且实现在数码管伤显示。
1、基本实现思路
根据不同时刻的操作,我们可以使用一个状态机来实现温度的读取。
如图所示,通过 初始化——>ROM指令写入——>温度转换命令写入——>二次初始化——>二次ROM指令写入——>温度读取命令写入——>温度读取等一系列操作就可以实现DS18B20的温度读取。
2、状态转换框图
将实现思路划分成6个状态,从而减少代码量。
二、工程实现
1、设计文件的编写
(1)DS18B20
新建一个ds18b20.v文件,如下:在这里需要用到的计数器非常多,如果对于每个状态都使用一个计数器的话,所占用的资源就会非常多,这里我们可以根据状态进行划分,实现同一个计数器在不同状态进行不同计数的方法,也就是分时复用的思路对于计数器进行带编写。并且在实现代码中,我们将跳过ROM指令和温度转换指令写在一起,将跳过ROM指令和温度读取指令写在一起,通过同一个位宽的bit计数器就可以进行命令的写入。
module ds18b20_driver(
input clk ,
input rst_n ,
//ds182b20 port
inout dq ,//单总线(双向口)
//user port
output reg sign ,//符号位,正数为0,负数为1
output reg [19:0] dout ,
output reg dout_vld
);
//-------------<参数定义>---------------------------------------------------------
//状态机参数定义
localparam INIT = 6'b0000_01,//主机初始化状态:主机发送复位脉冲-->主机释放总线-->主机接收存在脉冲(ds18b20接收到存在脉冲后拉低总线)
WRCMD = 6'b0000_10,//主机发送跳过ROM命令状态
WAIT = 6'b0001_00,//等待温度转换完成状态
INIT_AGAIN = 6'b0010_00,//二次初始化
RDCMD = 6'b0100_00,//主机发送读取温度命令状态
RDTEM = 6'b1000_00;//主机读取温度(2字节)状态
//时间参数定义
parameter TIME_1US = 50;//1us
parameter TM_INIT = 720 ,//初始化时间750us:主机发送复位脉冲至少480us(取500us)-->主机释放总线15-60us(取20us)-->主机接收存在脉冲60-240us(取200us)
TM_LOW = 2 ,//读写时隙,拉低总线至少1us(取2us)
TM_60US = 60 ,//读写时隙至少60us
TM_3US = 3 ,//连续两个读或写时隙至少间隔1us的恢复时间
TM_WAIT = 750_000 ;//温度转换最大750ms(精度12bits)
//命令参数定义
localparam CMD_SKROM = 8'hCC,//跳过ROM命令
CMD_CONVE = 8'h44,//温度转换命令
CMD_RDCMD = 8'hBE;//读取温度命令
//-------------<内部信号定义>-----------------------------------------------------
wire dq_in ;//双向端口输入
reg dq_out ;//双向端口输出
reg dq_out_en ;//双向端口输出使能
reg [5:0] state_c ;//状态机现态
reg [5:0] state_n ;//状态机次态
reg [7:0] cnt_1us ;//1us基准计数器
wire add_cnt_1us ;
wire end_cnt_1us ;
reg [19:0] xx ;
reg [19:0] cnt_xx ;//复用计数器
wire add_cnt_xx ;
wire end_cnt_xx ;
reg [4:0] cnt_bit ;//比特计数器
wire add_cnt_bit ;
wire end_cnt_bit ;
reg presen_pulse;//存在脉冲
reg flag ;//标志信号 发起温度转换,为0;读取温度,为1
reg [15:0] wr_data ;//写入数据(命令)
reg [15:0] rd_data ;//读出的温度数据
wire init2wrcmd ;
wire wrcmd2wait ;
wire wait2initagain ;
wire initagain2rdcmd ;
wire rdcmd2rdtem ;
wire rdtem2init ;
assign dq = dq_out_en ? dq_out : 1'bz;//输出使能为1时输出;为0时高阻态
assign dq_in = dq ;//高阻态时,将双向总线的数据赋值给输入
//--state_c(三段式状态机)
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
state_c <= INIT;
end
else begin
state_c <= state_n;
end
end
always @(*) begin
case(state_c)
INIT : begin
if(init2wrcmd)begin
state_n = WRCMD;
end
else begin
state_n = state_c;
end
end
WRCMD : begin
if(wrcmd2wait)begin
state_n = WAIT;
end
else begin
state_n = state_c;
end
end
WAIT : begin
if(wait2initagain)begin
state_n = INIT_AGAIN;
end
else begin
state_n = state_c;
end
end
INIT_AGAIN : begin
if(initagain2rdcmd)begin
state_n = RDCMD;
end
else begin
state_n = state_c;
end
end
RDCMD : begin
if(rdcmd2rdtem)begin
state_n = RDTEM;
end
else begin
state_n = state_c;
end
end
RDTEM : begin
if(rdtem2init)begin
state_n = INIT;
end
else begin
state_n = state_c;
end
end
default : state_n = INIT;
endcase
end
assign init2wrcmd = (state_c == INIT ) && end_cnt_xx && !presen_pulse;
assign wrcmd2wait = (state_c == WRCMD ) && end_cnt_bit ;
assign wait2initagain = (state_c == WAIT ) && end_cnt_xx;
assign initagain2rdcmd = (state_c == INIT_AGAIN) && end_cnt_xx && !presen_pulse;
assign rdcmd2rdtem = (state_c == RDCMD ) && end_cnt_bit;
assign rdtem2init = (state_c == RDTEM ) && end_cnt_bit;
//--cnt_1us
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_1us <= 'd0;
end
else if(add_cnt_1us)begin
if(end_cnt_1us)begin
cnt_1us <= 'd0;
end
else begin
cnt_1us <= cnt_1us + 1'b1;
end
end
else
cnt_1us <= cnt_1us;
end
assign add_cnt_1us = 1'b1;
assign end_cnt_1us = add_cnt_1us && cnt_1us == TIME_1US - 1;
//--cnt_xx
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_xx <= 'd0;
end
else if(add_cnt_xx)begin
if(end_cnt_xx)begin
cnt_xx <= 'd0;
end
else begin
cnt_xx <= cnt_xx + 1'b1;
end
end
end
assign add_cnt_xx = end_cnt_1us;
assign end_cnt_xx = add_cnt_xx && cnt_xx == xx - 1;
//xx
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
xx <= 'd0;
end
else if((state_c == INIT) ||(state_c == INIT_AGAIN))begin
xx <= TM_INIT;
end
else if(state_c == WAIT)begin
xx <= TM_WAIT;
end
else begin //除了INIT和WAIT两个状态,其它几个状态都在读或写(IDLE状态未考虑,计数器在IDLE状态下不工作)
xx <= TM_LOW + TM_60US + TM_3US;//读写1bit数据需要的时间
end
end
//--cnt_bit
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_bit <= 'd0;
end
else if(add_cnt_bit)begin
if(end_cnt_bit)begin
cnt_bit <= 'd0;
end
else begin
cnt_bit <= cnt_bit + 1'b1;
end
end
end
assign add_cnt_bit = end_cnt_xx && (state_c == WRCMD || state_c == RDCMD || state_c == RDTEM);
assign end_cnt_bit = add_cnt_bit && cnt_bit == 16-1;
//--presen_pulse
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
presen_pulse <= 'd0;
end
else if(((state_c == INIT) ||(state_c == INIT_AGAIN))&& (cnt_xx == 550) && (end_cnt_1us))begin
presen_pulse <= dq_in;
end
end
//--wr_data
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
wr_data <= 'd0;
end
else if(state_c == WRCMD)begin
wr_data <= 16'h44cc;
end
else if(state_c == RDCMD)begin
wr_data <= 16'hbecc;
end
else begin
wr_data <= 'd0;
end
end
//--dq_out、dq_out_en
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
dq_out_en <= 'd0;
dq_out <= 'd0;
end
else if((state_c == INIT ||state_c == INIT_AGAIN) && (cnt_xx < 20'd500))begin
dq_out_en <= 1'b1;
dq_out <= 1'b0;
end
else if(state_c == WRCMD || state_c == RDCMD)begin
if(cnt_xx <TM_LOW)begin
dq_out_en <= 1'b1;
dq_out <= 1'b0;
end
else if(cnt_xx >= TM_LOW && cnt_xx < (TM_LOW+TM_60US))begin
if(wr_data[cnt_bit] == 0)begin //写0时隙
dq_out_en <= 1'b1;
dq_out <= 1'b0;
end
else if(wr_data[cnt_bit] == 1)begin //写1时隙
dq_out_en <= 1'b0;
dq_out <= 1'b0;
end
end
else begin //记住不能省略
dq_out_en <= 1'b0;
dq_out <= 1'b0;
end
end
else if(state_c == RDTEM)begin
if(cnt_xx < TM_LOW)begin
dq_out_en <= 1'b1;
dq_out <= 1'b0;
end
else begin
dq_out_en <= 1'b0;
dq_out <= 1'b0;
end
end
else begin
dq_out_en <= 1'b0;
dq_out <= 1'd0;
end
end
//--rd_data
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
rd_data <= 'd0;
end
else if(state_c == RDTEM && cnt_xx == 20'd12 )begin
rd_data[cnt_bit] <= dq_in;
end
end
//--dout、dout_vld、sign
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
dout <= 'd0;
sign <= 'd0;
end
else if(state_c == RDTEM && end_cnt_bit)begin
if(rd_data[15] == 1'b0)begin //读出的温度数据机器码符号位为0,温度为正
dout <= rd_data[10:0]*20'd625;
sign <= 1'b0;
end
else begin //读出的温度数据机器码符号位为1,温度为负
dout <= (~rd_data[10:0] + 1'b1)*20'd625; //考虑位宽溢出的问题
sign <= 1'b1;
end
end
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
dout_vld <= 1'b0;
end
else
dout_vld <= rdtem2init;
end
endmodule
(2)数码管
新建一个seg_driver.v文件,如下:
module seg_driver(
input clk,
input rst_n,
input [23:0] din,
input sign,
output reg [5:0] sel,
output reg [7:0] dig
);
//参数定义
parameter TIME_1MS =50_000;
localparam ZERO =7'b100_0000,
ONE =7'b111_1001,
TWO =7'b010_0100,
TEREE =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,
P =7'b000_1100,
N =7'b100_1000,
NULL =7'b111_1111;
//内部信号定义
reg [15:0] cnt_1ms;
wire add_cnt_1ms;
wire end_cnt_1ms;
reg [5:0] seg_r;
reg flag;//小数点标志位
reg [23:0] dis_data;
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_1ms <= 'd0;
end
else if(add_cnt_1ms)begin
if(end_cnt_1ms)begin
cnt_1ms <= 'd0;
end
else begin
cnt_1ms <= cnt_1ms + 1'b1;
end
end
end
assign add_cnt_1ms =1'b1 ;
assign end_cnt_1ms = add_cnt_1ms && cnt_1ms == TIME_1MS-1;
//数码管位选
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
sel <= 6'b111_110;
end
else if(end_cnt_1ms)begin
sel <={sel[4:0],sel[5]};
end
end
//小数点位置 0为亮,1为灭
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
flag <= 'd1;
end
else begin
case (sel)
6'b110_111:flag <= 1'b0; //第三个数码管位置
default: flag<= 'd1;
endcase
end
end
//数码管显示数据分布
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
dis_data <= 'd0;
end
else begin
case (sel)
6'b111_110:dis_data <= din[7:4];
6'b111_101:dis_data <= din[11:8];
6'b111_011:dis_data <= din[15:12];
6'b110_111:dis_data <= din[19:16];
6'b101_111:dis_data <= din[23:20];
6'b011_111:dis_data <=(sign ?4'ha:4'hb) ;
default: ;
endcase
end
end
//数据显示
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
dig <= NULL;
end
else begin
case (dis_data)
0 :dig<={flag,ZERO };
1 :dig<={flag,ONE };
2 :dig<={flag,TWO };
3 :dig<={flag,TEREE};
4 :dig<={flag,FOUR };
5 :dig<={flag,FIVE };
6 :dig<={flag,SIX };
7 :dig<={flag,SEVEN};
8 :dig<={flag,EIGHT};
9 :dig<={flag,NINE };
4'ha:dig<={flag,N };
4'hb:dig<={flag,P };
default:dig<= NULL;
endcase
end
end
endmodule
2、数据处理文件的编写
当我们进行温度读取时,需要对原始数据进行处理之后采用将其显示在数码管中,所以这里还需要一个处理数据文件进行数据转换,新建一个data.v文件,如下:
module data(
input clk,
input rst_n,
input [19:0] din,
input din_vld,
output [23:0] dout
);
wire [7:0] data_r_int;
wire [15:0] data_r_float;
reg [3:0] data_r1;
reg [3:0] data_r2;
reg [3:0] data_r3;
reg [3:0] data_r4;
reg [3:0] data_g ;
reg [3:0] data_s ;
reg din_vld_r;
assign data_r_int=din/10000;
assign data_r_float=din%10000;
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
din_vld_r <= 'd0;
end
else
din_vld_r <= din_vld;
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
data_r1<='d0;
data_r2<='d0;
data_r3<='d0;
data_r4<='d0;
data_g <='d0;
data_s <='d0;
end
else if(din_vld_r)begin
data_s <=data_r_int/10;
data_g <=data_r_int%10;
data_r1<=data_r_float/1000;
data_r2<=data_r_float/100%10;
data_r3<=data_r_float/10%10;
data_r4<=data_r_float%10;
end
end
assign dout={data_s,data_g,data_r1,data_r2,data_r3,data_r4};
endmodule
3、顶层文件的编写
通过编写一个顶层文件将三个.v文件整合在一起,新建一个top.v文件,如下:
module top (
input clk ,
input rst_n ,
inout dq ,
output tx ,
output [5:0] sel,
output [7:0] dig
);
wire [23:0] dis_data;//温度数值
wire sign; //符号位
wire sign_out;
wire [19:0] dout ;
wire dout_vld;
seg_driver seg_driver_inst(
/*input */ .clk (clk) ,
/*input */ .rst_n (rst_n) ,
/*input [23:0] */ .din (dis_data ) ,
/*input */ .sign (sign) ,
/*output reg [5:0] */ .sel (sel) ,
/*output reg [7:0] */ .dig (dig)
);
data data_inst(
/*input */ .clk (clk),
/*input */ .rst_n (rst_n),
/*input [19:0] */ .din (dout),
/*input */ .din_vld(dout_vld),
/*output [23:0]*/ .dout (dis_data)
);
ds18b20_driver ds18b20_driver_inst(
/* input */. clk (clk ) ,
/* input */. rst_n (rst_n ) ,
/* input */. dq (dq ) ,//输入信号
/* output reg [19:0]*/. dout (dout ) ,//串行发送出去的数据
/* output */. dout_vld (dout_vld) ,
/* output */. sign (sign ) //发送一字节完成信号
);
endmodule
4、在线调试
因为这里需过用modelsim进行仿真的话需要编写大量的测试文件代码才能进行仿真,所以我们直接采取Signal Tap进行在线调试的方式进行波形抓取。
通过在线抓取,经过调试之后,我们可以直接看到温度的值,通过下板验证可以看到数码管显示的值和抓取过程的值是一样的。在数码管中因为我们要显示一个符号位,所以值保留3位小数。