首先是底层的驱动,即复位,读取存在脉冲宽度,写数据,读数据。
首先是信号列表:
+ 查看代码
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用来保存读取数据的中间移位的值。
第二个状态:复位状态
每次操作之前,需要对总线进行复位。
如上图,需要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后,就跳转到读取脉冲响应状态。然后清零计数器。以便下一状态计数。
第三个状态:读取存在脉冲。
在复位之后,需要读取存在脉冲。即判断外部有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信号。
第四个状态:写器件命令状态
对于写数据,数据是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状态。但是在读模式下,接着就要进行数据读取了,所以要跳转到读模式状态。
然后就是读数据了。读数据要稍微麻烦点。
第六个状态,读数据。
这里操作是: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