weiqi7777

FPGA之DS18B20

0
阅读(5802)

之前用单片机加proteus,来实现对DS18B20的驱动。也了解了DS18B20的驱动原理。然后就开始用FPGA来进行驱动了。

介绍这里就不介绍了,可以看看之前写的博文。

以下程序,只考虑外接一个DS18B20,因为开发板上就挂了一个。只读取温度16位值,没有读取其他寄存器值。需要其他操作,可以在程序的基础上改改就行了。也很简单的。主要是底层的驱动,发数据和读数据,复位和读取存在脉冲。

首先是底层的驱动,即复位,读取存在脉冲宽度,写数据,读数据。

首先是信号列表:


module DS18B02(
     input							clk,
	  input							rst_n,
	  
	  input	                  start,
	  input							mode,    //write or read  1 read 0 write
	  
	  //ds18b20 interface 
	  inout							DQ,
	  
	  input	[7:0]					rom_command,  //rom cpmmand data ,this only use cc
	  input	[7:0]             command,   //command data  this only use 44 and be
	  
	 // input	[7:0]					data,   //write 8-bits data
	  output	reg[15:0]			ds18b20_read_data,
	  
	  output	reg					read_finish,
	  output	reg					command_finish	,

     output 						look_dq_in	  
    );


说明一下。

Start: 启动DS18B20发数据或者读数据。

Mode: 控制模式,1表示读数据,0表示写数据。

DQ: DS18B20的数据端口。

rom_command: 器件命令。

Command: 操作命令。

ds18b20_read_data: 读取的8位值。

read_finish: 读取数据完成信号

command_finish: 发送命令完成信号。

至于最后一个是用chipscope调试用的,保存DQ作为输入时候的值。

首先介绍一下对DQ信号的处理,因为DQ是inout信号,所以不能直接进行操作的。需要进行一些处理。


reg DQ_reg;
reg enable_read;
assign DQ = enable_read ? 1'bz:DQ_reg;
assign look_dq_in = enable_read ? DQ:1'b0;


这里定义了一个enable_read,当为1,表示DQ为输入,否则为输出。然后定义一个DQ_reg,用来设定DQ作为输出时候的输出值。look_dq_in在读使能的时候,为DQ值,否则为0,这样就保存了DQ作为输入时候的值。

然后就是状态机的设计:


localparam idle_state ='d0;
localparam reset_state ='d1;
localparam read_pulse_state ='d2;
localparam write_rom_command_state ='d3;
localparam write_command_state ='d4;
localparam read_state ='d5;


总共5个状态。从名字也可以看出该状态是干嘛的。

第一个状态,idle_state:


idle_state: begin
			DQ_reg = 1'b1;
			enable_read = 1'b0; //default DQ is output
			delay_time_next  = 'd0;
			bit_counter_next =  'd0;
			read_data_next	  =  'd0;
			ack_next			  =  'b1;
			if(start)
				state_next = reset_state;
		 end


其实就是对一些变量清零。然后等待开始信号,一旦有开始信号,就跳转到复位状态,即复位总线。

这里设置enable_read为0,即DQ是作为输出的。输出的值由DQ_reg决定,所以为1.

这里说明一下上面一些变量作用:

delay_time_next和之后的delay_time是用来对状态的持续时间进行计数的。因为要满足要求时序,所以每个状态要需要制定的时间才能进行跳转。

bit_counter_next和bit_counter用来指示传输的第几位。

ack_next 和ack用来保存读取到的存在脉冲的响应。

read_data_next和read_data用来保存读取数据的中间移位的值。

第二个状态:复位状态

每次操作之前,需要对总线进行复位。

clip_image002

如上图,需要DQ发送低电平,时间为480us到960us之间。

如下程序:


reset_state: begin   //DQ output 0  600us
          enable_read = 0;  //DQ is output
			 DQ_reg = 0;       //DQ output 0
			 if(delay_time >= 30000 - 1 )  //delay 600us
				 begin
				    delay_time_next = 'd0;
				    state_next = read_pulse_state;
				 end
			 else
			    delay_time_next = delay_time + 1'b1;
		 end


还是设定enable_read为0,DQ作为输出。然后DQ_reg赋值为0。那么DQ就输出0了。规定时间在480us到960us之间。这里就设定600us。然后通过delay_time_next和delay_time计数就可以了。

计时到600us后,就跳转到读取脉冲响应状态。然后清零计数器。以便下一状态计数。

第三个状态:读取存在脉冲。

clip_image003

在复位之后,需要读取存在脉冲。即判断外部有DS18B20器件。时序就是复位之后,DQ作为输入。然后在最大60us之后,外部DS18B20会把DQ给置低。保持一段时间,然后在释放总线。(注意DQ是要接上拉电阻的,所以释放总线后,总线就输出高电平)。

然后这个读取过程至少要480us。

以下程序:


 read_pulse_state: begin
		    enable_read = 1;  //DQ is input
			 DQ_reg = 1;
			 if(delay_time == 3500 - 1) //delay 70us
			     ack_next = DQ;
			 if(delay_time >= 25000 - 1 ) //delay 500us
			   begin
				   delay_time_next = 0;
					//if receive pulse ack, write rom command, otherwise there is no ds18b20 is bus,go idle_state
				   if(ack == 0)
					    state_next = write_rom_command_state;
					else
					    state_next = idle_state;
				end
			 else
			   delay_time_next = delay_time + 1'b1;
 		 end


这里将enable_read置1,表示DQ为输入。DQ_reg 赋值1,这里给什么其实没有什么影响。但是考虑到后面如果DQ_reg为0,当enable_read为0,DQ作为输出,那么DQ会输出一个0,可能就会产生误操作了。

等待70us,读取DQ的值,这时候DQ的值应该为0.等待500us,跳转到写器件命令状态。满足设定的最小480us设定。

这里要注意的就是读取DQ值的时机,不能太早,也不能太晚。不然可能会读取不到低电平的ACK信号。

第四个状态:写器件命令状态

clip_image005

对于写数据,数据是1位1位的写。对于每一位,写1和写0的时序是不同的。这里操作就是,FPGA拉低DQ,保持2us时间。然后判断写的数据是0还是1,是0的话,继续拉低,是1的话,就拉高。然后总共的时间为100us。当写0的时候,在90us的时候,就要拉高DQ。

以上的操作是符合规定的时序的。其实只要符合规定的时序,想怎么操作就怎么操作。

以下代码:


write_rom_command_state: begin
		    enable_read = 0; //DQ is output
		    if(delay_time < 99)  //DQ output 0 keep  2us
				 DQ_reg = 0;
			 else if(delay_time < 4500 - 1) //delay 90us
			    if(rom_command[bit_counter] == 1)
				     DQ_reg = 1;
				 else
				     DQ_reg = 0;
			 else
			    DQ_reg = 1;
			 if(delay_time >= 5000 - 1) //delay 100us
			    begin
				    delay_time_next = 0;
					 if(bit_counter == 'd7)
					   begin
						   bit_counter_next = 'd0;
							 state_next = write_command_state;
						end
					 else
					   bit_counter_next = bit_counter + 1'b1;
				 end
			 else
			    delay_time_next = delay_time + 1'b1;
		 end


首先,设定enable_read为0.表示DQ为输出。在2us时间内,DQ输出0.在2us到90us时间内,判断写的数据为0还是1,从而判断DQ的值。90us到100us时间,DQ输出1.整个操作时间为100us。100us时间到,就写了1位。然后就判断是不是写了8位数据,如果写了,就状态跳转。否则将计数器清零,再次执行这个状态。

第五个状态:写命令状态

这里时序和写器件命令时序一样的,只是写的数据不一样而已。这个时候写的数据为command。

以下代码:


write_command_state: begin
			 enable_read = 0; //DQ is output
		    if(delay_time < 99)  //DQ output 0 keep  2us
				 DQ_reg = 0;
			 else if(delay_time < 4500 - 1) //delay 90us
			    if(command[bit_counter] == 1)
				     DQ_reg = 1;
				 else
				     DQ_reg = 0;
			 else
			    DQ_reg = 1;
			 if(delay_time >= 5000 - 1) //delay 100us
			    begin
				    delay_time_next = 0;
					 if(bit_counter == 'd7)
					   begin
						   bit_counter_next = 'd0;
							command_finish = 1'b1;
							//if write, write finish ,go idle_state
							//if read, go read_state
							if(mode == 0)
							   state_next = idle_state;
						   else
							   state_next = read_state;
						end
					 else
					   bit_counter_next = bit_counter + 1'b1;
				 end
			 else
			    delay_time_next = delay_time + 1'b1;
		 end


这里要注意的是状态跳转,在写模式下,即写命令,写完后,跳转回idle状态。但是在读模式下,接着就要进行数据读取了,所以要跳转到读模式状态。

然后就是读数据了。读数据要稍微麻烦点。

第六个状态,读数据。

clip_image007

这里操作是:DQ首先作为输出,拉低保持2us。然后DQ作为输入,在10us时间读取DQ数据,90us时间后,DQ作为输出。操作时间为100us。也是符合规定的时序的。


 read_state: begin
		    if(delay_time < 99)  //DQ output 0 keep  2us
				 begin
				   enable_read = 0; // DQ is output
					DQ_reg = 0;           //DQ output 0
				 end
			 else if(delay_time < 4500 - 1) //delay 90us
			    begin
				   enable_read = 1;
					if(delay_time == 500 -1) //at 10us read DQdata
					   read_data_next = {DQ,read_data[15:1]};
				 end
			 else
			    begin
				   enable_read = 0; // DQ is output
					DQ_reg = 1;
				 end					 
			 if(delay_time >= 5000 - 1) //delay 100us
			    begin
				    delay_time_next = 0;
					 if(bit_counter == 'd15)
					   begin
						   bit_counter_next = 'd0;
							state_next = idle_state;
							read_finish  = 1'b1;
						end
					 else
					   bit_counter_next = bit_counter + 1'b1;
				 end
			 else
			    delay_time_next = delay_time + 1'b1;
		 end


这里在2us时间内,enable_read为0,DQ输出,DQ输出0。 2us时间到90us时间内,enable_read为1,DQ作为输入,在10us时间的时候,读取DQ值,存在移位寄存器的高位中。

时间到100us后判断是否读取了16个数据。因为温度的值是以16位数据保存的。读了16个数据,就状态跳转。否则计数器清零,再次执行这个状态。

以上就是核心的底层驱动。剩下就是在顶层例化这个模块,然后发对应命令即可。

在外部另外编写代码,进行命令发送和数据读取。

信号列表:


module DS18B20_control(
		input						clk,
		input						rst_n,
		
		input						start_ds18b20,
		
		inout						DQ,
		
		output [15:0] 			read_data,
		output					read_finish,
		
		output					look_dq_in
    );


这里的信号就不用介绍了,看名字就知道这些信号是干啥的。

然后就是状态机的设计,


localparam idle_state = 'd0;
localparam write_command_state = 'd1;
localparam wait_convert_state = 'd2;
localparam read_temperature_state = 'd3;


定义了4个状态:

第一个状态:idle状态。

这个状态就是不操作DS18B20的时候,各个信号值的状态是怎么样的。


idle_state: begin
		   delay_time_next = 'd0;
		   if(start_ds18b20)
			   state_next = write_command_state;
		end


代码也比较简单,判断是否有使能操作DS18B20操作信号,有就状态跳转,否则就不跳转。

第二个状态:write_command_state,写命令状态

这个状态,其实就是写命令0xcc和0x44。开启DS18B20转化温度。


write_command_state: begin
		   rom_command = 8'hcc;
		   command		= 8'h44;
			start = 1;
			if(command_finish == 1)
			   begin
				   start = 0;
					state_next = wait_convert_state;
				end
		end


器件命令赋值cc,命令赋值44。然后开启DS18B20,等待command_finish信号。当两个命令发送完成后。会反馈command_finish为高。检测到为高后,就状态跳转。

第三个模块:wait_convert_state。等待转换

DS18B20转换温度是需要时间的。不能说你发完命令后,就可以直接读取值。因为这个时候值还没有变化。所以需要等待时间。这里是等待1s。


wait_convert_state: begin
		   if(delay_time >= 50_000_000 - 1) //delay 1s
			//if(delay_time >= 50_0 - 1) //delay 1s
			   state_next = read_temperature_state;
			else
			   delay_time_next = delay_time + 1'b1;
		end


注意,这里状态跳转的时候,没有对计数器清零。因为在idle_state的时候,有对计数器清零。

第四个状态:read_temperature_state,读取温度状态。

这个状态,就要读取温度了。


read_temperature_state: begin
		   rom_command = 8'hcc;
		   command		= 8'hBE;
			start = 1;
			mode			= 'd1;
			if(read_finish)
			    begin
				    start = 0;
					 state_next = idle_state;
				 end
		end


首先,器件命令为cc,操作命令为BE。表示读暂存寄存器。然后就开始读值。当read_finish为1的时候,表示16位温度数据读取结束。然后就可以进行状态的跳转了。

以上就是DS18B20的驱动了。将这个模块封装。给外部调用。外部调用的时候,给start_ds18b20信号。然后等待read_finish。当这个信号为1,读取温度结束,然后就可以从read_data读取16位温度值了。

这里为了能下到开发板中,看到实验现象。在外部写了一个顶层代码,调用上述模块。


`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: 
// Engineer: 
// 
// Create Date:    18:09:53 11/27/2014 
// Design Name: 
// Module Name:    DS18B20_top 
// Project Name: 
// Target Devices: 
// Tool versions: 
// Description: 
//
// Dependencies: 
//
// Revision: 
// Revision 0.01 - File Created
// Additional Comments: 
//
//////////////////////////////////////////////////////////////////////////////////
module DS18B20_top(
		input					clk,
		input					rst_n,
		
		inout					DS18B20,
		
		output	[7:0]    LED,
		
		output				look_dq_in
    );
	 
	 wire [15:0]  read_data;
	 reg start_ds18b20;
	 wire  read_finish;
	 
    
	 localparam  idle_state = 'd0;
	 localparam  read_state	= 'd1;
	 
	 reg state;
	 always@(posedge clk or negedge rst_n) begin
	   if(!rst_n)
		    begin
	          state <= idle_state;
				 start_ds18b20 <= 1'b0;
			 end
		else
		  case(state)
		  idle_state:
		      state <= read_state;
		  read_state: begin
			  if(read_finish == 1)
			     begin
				     state <= idle_state;
					  start_ds18b20 <= 1'b0;
				  end
			  else
			     start_ds18b20 <= 1'b1;
		  end
		  endcase
	 end
    
	 
	 DS18B20_control DS18B20_control_1(
    .clk(clk), 
    .rst_n(rst_n), 
    .start_ds18b20(start_ds18b20), 
    .DQ(DS18B20), 
    .read_data(read_data[15:0]), 
    .read_finish(read_finish),
	 .look_dq_in(look_dq_in)
    );

    assign LED = ~read_data[11:4];


endmodule


代码也很简单。最后读到的温度值。将11位到4位值赋给8位LED。这样看LED灯就知道读取温度值为多少了。

最后,将代码综合,分配管脚,生成bit。下载到芯片中。可以看到LED灯亮了。指示当前检测到温度为多少。