本节将和大家一起使用FPGA驱动底板上的DS18B20Z单总线温度传感器进行温度数据的采集。


硬件说明

DS18B20是我们日常设计中常用的一款温度传感器芯片,只需要一根总线就可以实现通信,非常的方便,我们的STEP-BaseBoard底板上就集成了温度传感器DS18B20Z,下面我们就一起来学习一下它的硬件链接及驱动方法。
DS18B20Z只有一根总线,硬件电路非常简单,但是一定记得总线需要做上拉处理,如下图总线上连接了10K(上拉电阻取值可以一定范围内自行调整)的上拉电阻,另外我们使用FPGA驱动,一定记得将FPGA对应的管脚同样作上拉配置,重要的事情说三遍,总线上拉,总线上拉,总线上拉。
聊完硬件连接,接下来简要介绍如何驱动(更加详细的信息需要大家参考数据手册),不同的功能需求对应不同寄存器配置,本设计执行的操作案例如下。

下面为大家展示上述案例中每个环节的时序要求:






Verilog代码

// --------------------------------------------------------------------
// >>>>>>>>>>>>>>>>>>>>>>>>> COPYRIGHT NOTICE <<<<<<<<<<<<<<<<<<<<<<<<<
// --------------------------------------------------------------------
// Module:DS18B20Z 
// 
// Author: Step
// 
// Description: Drive DS18B20Z to get temperature code
// 
// Web: www.stepfpga.com
// 
// --------------------------------------------------------------------
// Code Revision History :
// --------------------------------------------------------------------
// Version: |Mod. Date:   |Changes Made:
// V1.0     |2015/11/11   |Initial ver
// --------------------------------------------------------------------
module DS18B20Z
(
	input				clk_in,			//系统时钟
	input				rst_n_in,		//系统复位,低有效
	inout				one_wire,		//DS18B20Z传感器单总线,双向管脚
	output	reg	[15:0]	data_out		//DS18B20Z有效温度数据输出
);
 
	/*
	本设计通过驱动DS18B20Z芯片获取温度数据,
	需要了解inout类型的接口如何实现双向通信,
	中间涉及各种不同的延时和寄存器指令操作,注释部分以作简要说明,更多详情需参考数据手册
	*/
 
	localparam	IDLE	=	3'd0;
	localparam	MAIN	=	3'd1;
	localparam	INIT	=	3'd2;
	localparam	WRITE	=	3'd3;
	localparam	READ	=	3'd4;
	localparam	DELAY	=	3'd5;
 
	//计数器分频产生1MHz的时钟信号
	reg					clk_1mhz;
	reg		[2:0]		cnt_1mhz;
	always@(posedge clk_in or negedge rst_n_in) begin
		if(!rst_n_in) begin
			cnt_1mhz <= 3'd0;
			clk_1mhz <= 1'b0;
		end else if(cnt_1mhz >= 3'd5) begin
			cnt_1mhz <= 3'd0;
			clk_1mhz <= ~clk_1mhz;	//产生1MHz分频
		end else begin
			cnt_1mhz <= cnt_1mhz + 1'b1;
		end
	end
 
	reg		[2:0]		cnt;
	reg					one_wire_buffer;
	reg		[3:0]		cnt_main;
	reg		[7:0]		data_wr;
	reg		[7:0]		data_wr_buffer;
	reg		[2:0]		cnt_init;
	reg		[19:0]		cnt_delay;
	reg		[19:0]		num_delay;
	reg		[3:0]		cnt_write;
	reg		[2:0]		cnt_read;
	reg		[15:0]		temperature;
	reg		[7:0]		temperature_buffer;
	reg		[2:0] 		state = IDLE;
	reg		[2:0] 		state_back = IDLE;
	//使用1MHz时钟信号做触发完成下面状态机的功能
	always@(posedge clk_1mhz or negedge rst_n_in) begin
		if(!rst_n_in) begin
			state <= IDLE;
			state_back <= IDLE;
			cnt <= 1'b0;
			cnt_main <= 1'b0;
			cnt_init <= 1'b0;
			cnt_write <= 1'b0;
			cnt_read <= 1'b0;
			cnt_delay <= 1'b0;
			one_wire_buffer <= 1'bz;
			temperature <= 16'h0;
		end else begin
			case(state)
				IDLE:begin		//IDLE状态,程序设计的软复位功能,各状态异常都会跳转到此状态
						state <= MAIN;	//软复位完成,跳转之MAIN状态重新工作
						state_back <= MAIN;
						cnt <= 1'b0;
						cnt_main <= 1'b0;
						cnt_init <= 1'b0;
						cnt_write <= 1'b0;
						cnt_read <= 1'b0;
						cnt_delay <= 1'b0;
						one_wire_buffer <= 1'bz;
					end
				MAIN:begin		//MAIN状态控制状态机在不同状态间跳转,实现完整的温度数据采集
						if(cnt_main >= 4'd11) cnt_main <= 1'b0;
						else cnt_main <= cnt_main + 1'b1;
						case(cnt_main)
							4'd0: begin state <= INIT; end	//跳转至INIT状态进行芯片的复位及验证
							4'd1: begin data_wr <= 8'hcc;state <= WRITE; end	//主设备发出跳转ROM指令
							4'd2: begin data_wr <= 8'h44;state <= WRITE; end	//主设备发出温度转换指令
							4'd3: begin num_delay <= 20'd750000;state <= DELAY;state_back <= MAIN; end	//延时750ms等待转换完成
 
							4'd4: begin state <= INIT; end	//跳转至INIT状态进行芯片的复位及验证
							4'd5: begin data_wr <= 8'hcc;state <= WRITE; end	//主设备发出跳转ROM指令
							4'd6: begin data_wr <= 8'hbe;state <= WRITE; end	//主设备发出读取温度指令
 
							4'd7: begin state <= READ; end	//跳转至READ状态进行单总线数据读取
							4'd8: begin temperature[7:0] <= temperature_buffer; end	//先读取的为低8位数据
 
							4'd9: begin state <= READ; end	//跳转至READ状态进行单总线数据读取
							4'd10: begin temperature[15:8] <= temperature_buffer; end	//后读取的为高8为数据
 
							4'd11: begin state <= IDLE;data_out <= temperature; end	//将完整的温度数据输出并重复以上所有操作
							default: state <= IDLE;
						endcase
					end
				INIT:begin		//INIT状态完成DS18B20Z芯片的复位及验证功能
						if(cnt_init >= 3'd6) cnt_init <= 1'b0;
						else cnt_init <= cnt_init + 1'b1;
						case(cnt_init)
							3'd0: begin one_wire_buffer <= 1'b0; end	//单总线复位脉冲拉低
							3'd1: begin num_delay <= 20'd500;state <= DELAY;state_back <= INIT; end	//复位脉冲保持拉低500us时间
							3'd2: begin one_wire_buffer <= 1'bz; end	//单总线复位脉冲释放,自动上拉
							3'd3: begin num_delay <= 20'd100;state <= DELAY;state_back <= INIT; end	//复位脉冲保持释放100us时间
							3'd4: begin if(one_wire) state <= IDLE; else state <= INIT; end	//根据单总线的存在检测结果判定是否继续
							3'd5: begin num_delay <= 20'd400;state <= DELAY;state_back <= INIT; end	//如果检测正常继续保持释放400us时间
							3'd6: begin state <= MAIN; end	//INIT状态操作完成,返回MAIN状态
							default: state <= IDLE;
						endcase
					end
				WRITE:begin		//按照DS18B20Z芯片单总线时序进行写操作
						if(cnt <= 3'd6) begin	//共需要发送8bit的数据,这里控制循环的次数
							if(cnt_write >= 4'd6) begin cnt_write <= 1'b1; cnt <= cnt + 1'b1; end
							else begin cnt_write <= cnt_write + 1'b1; cnt <= cnt; end
						end else begin
							if(cnt_write >= 4'd8) begin cnt_write <= 1'b0; cnt <= 1'b0; end	//两个变量都恢复初值
							else begin cnt_write <= cnt_write + 1'b1; cnt <= cnt; end
						end
						//对于WRITE状态中cnt_write来讲,执行过程为:0;[1~6]*8;7;8;
						case(cnt_write)
							//lock data_wr
							4'd0: begin data_wr_buffer <= data_wr; end	//将需要写出的数据缓存
							//发送 1bit 数据的用时在60~120us之间,参考数据手册
							4'd1: begin one_wire_buffer <= 1'b0; end	//总线拉低
							4'd2: begin num_delay <= 20'd2;state <= DELAY;state_back <= WRITE; end	//延时2us时间,保证15us以内
							4'd3: begin one_wire_buffer <= data_wr_buffer[cnt]; end	//先发送数据最低位
							4'd4: begin num_delay <= 20'd80;state <= DELAY;state_back <= WRITE; end	//延时80us时间
							4'd5: begin one_wire_buffer <= 1'bz; end	//总线释放
							4'd6: begin num_delay <= 20'd2;state <= DELAY;state_back <= WRITE; end	//延时2us时间
							//back to main
							4'd7: begin num_delay <= 20'd80;state <= DELAY;state_back <= WRITE; end	//延时80us时间
							4'd8: begin state <= MAIN; end	//返回MAIN状态
							default: state <= IDLE;
						endcase
					end
				READ:begin		//按照DS18B20Z芯片单总线时序进行读操作
						if(cnt <= 3'd6) begin	//共需要接收8bit的数据,这里控制循环的次数
							if(cnt_read >= 3'd5) begin cnt_read <= 1'b0; cnt <= cnt + 1'b1; end
							else begin cnt_read <= cnt_read + 1'b1; cnt <= cnt; end
						end else begin
							if(cnt_read >= 3'd6) begin cnt_read <= 1'b0; cnt <= 1'b0; end	//两个变量都恢复初值
							else begin cnt_read <= cnt_read + 1'b1; cnt <= cnt; end
						end
						case(cnt_read)
							//读取 1bit 数据的用时在60~120us之间,总线拉低后15us时间内读取数据,参考数据手册
							3'd0: begin one_wire_buffer <= 1'b0; end	//总线拉低
							3'd1: begin num_delay <= 20'd2;state <= DELAY;state_back <= READ; end	//延时2us时间
							3'd2: begin one_wire_buffer <= 1'bz; end	//总线释放
							3'd3: begin num_delay <= 20'd5;state <= DELAY;state_back <= READ; end	//延时5us时间
							3'd4: begin temperature_buffer[cnt] <= one_wire; end	//读取DS18B20Z返回的总线数据,先收最低位
							3'd5: begin num_delay <= 20'd60;state <= DELAY;state_back <= READ; end	//延时60us时间
							//back to main
							3'd6: begin state <= MAIN; end	//返回MAIN状态
							default: state <= IDLE;
						endcase
					end
				DELAY:begin		//延时控制
						if(cnt_delay >= num_delay) begin	//延时控制,延时时间由num_delay指定
							cnt_delay <= 1'b0;
							state <= state_back; 	//很多状态都需要延时,延时后返回哪个状态由state_back指定
						end else cnt_delay <= cnt_delay + 1'b1;
					end
			endcase
		end
	end
 
	assign	one_wire = one_wire_buffer;
 
endmodule



小结

本节主要为大家讲解了DS18B20Z的驱动方法及软件实现,需要大家掌握的同时自己创建工程,通过整个设计流程,生成FPGA配置文件加载测试。
如果你对Diamond软件的使用不了解,请参考这里:Diamond的使用


相关资料


使用STEP-MXO2第二代的基于DS18B20Z的温度计设计程序: 后续会有下载连接 待更新
使用STEP-MAX10的基于DS18B20Z的温度计设计程序: 后续会有下载连接 待更新