DS18B20 FPGA
最近,写了好几个关于通信的,只要原理理解对了,你按照时序要求,写就好了。可是有些时候,我们对通讯的时序会理解的不对。这个时候多写几个通讯时序,就慢慢熟悉了。
你的DQ数据线要设置为:inout,千万别忘记了
写程序之前,还是先介绍一下ds18b20的时序图:
ds18b20的经典通讯过程如下:
我们就可以按照这个通讯方式去写程序:只要按照这个步骤,一步一步的写就可以了:
首先任何的通讯都需要一个初始化,就是互相告诉彼此,我们要准备通讯了,要准备传输数据了,ds18b20也不例外,
下面是初始化的时序过程:
主机先发送480—960us的低电平信号(你可以给个600us的低电平就可以),然后释放主机总线,交给从机,从机会响应60—240左右的低电平信号,之后,从机会把总线拉高。
然后就是写命令给ds18b20,写数据时,低位先传,然后是高位,写数据也分写0和写1
比如你写0,主机只要拉低总线60us以上就ok了,从机会在15us---60us之间,采集总线上的信号,这时采集的信号正好是0,这样就写完了一个数据,
比如你写1,这个稍微过程麻烦一下,首先主机要先拉低总线1us以上,然后在15us以内释放总线(包括之前的那个拉低1us的时间),从机会在15us---60us,采集总线上的数据,由于主机已经释放了总线,现在总线属于高电平,这时,从机就把1写进去了。
注意:写0和写1时间要大于60us以上,两次写的间隙,主机要释放总线1us以上
,现在,数据可以写入ds18b20中了,那如何从ds18b20中读取温度的值呢,
首先,我们要先发送读暂存器命令(BE),告诉从机要开始发送数据了,这时,我们要开始准备接受从机发过的数据,从机先发BYTE0,然后是BYTE1,....BYTE0先发低位,然后是高位。千万别搞错了。
如何读从机发来的数据呢,看看下面的时序图:
主机先拉低1us以上,然后释放总线、这个时候,从机就发送数据过来,从机会在15us,以内拉低总线(发送0),或者拉高总线(发送1)我们就要在15us以内的这段时间(包括之前的拉低1us),读这个值,我们可以在大概10us的时候读这个值,但是我们也要在15us以内释放总线。
下面是别人介绍的关于读写0,1,可以参考一下。
代码:每个人写的代码都不同,只要你原理懂了,用代码实现就可以了
按键按下,启动温度转换,下面输出的数据data就是温度值,按键和led是做测试的用的。要多用sigaltap去调试代码,看看自己错在那个步骤
module ds18b20_dri(
//module clock
input clk , // 时钟信号(50MHz)
input rst_n , // 复位信号
input [1:0] key,
output reg [1:0] led,
inout dq , // DS18B20的DQ引脚数据
output reg [19:0] data // 转换后得到的温度值
);
reg [15:0] temp_data;
reg sign;
reg [10:0] data1;
wire [19:0] data2;
//状态机
parameter IDLE = 5'b00000; //空闲状态
parameter RST_PULSE = 5'b00001; //主机复位脉冲
parameter DS18B20_WAIT = 5'b00010; //从机等待15--60us
parameter ANSWER_PULSE = 5'b00011; //从机应答
parameter SKIP_ROM_DATA = 5'b00100; //跳过ROM(CC)
parameter CONVERT_TEMP_DATA = 5'b00101; //温度转换(44)
parameter WAITING_500MS = 5'b00110; //等待温度转换500ms
parameter AGAIN_RST_PULSE = 5'b00111; //第二次主机再次复位
parameter DS18B20_WAIT1 = 5'b01000; //第二次从机等待15--60us
parameter AGAIN_ANSWER_PULSE = 5'b01001; //第二次从机应答
parameter AGAIN_SKIP_ROM_DATA = 5'b01010; //第二次跳过ROM(CC)
parameter READ_TEMP_DATA = 5'b01011; //读高速缓存器的值(BE)
parameter READ_DATA = 5'b01100; //读16位数据
reg [4:0] state_c;
reg [4:0] state_n;
wire idl2rst_pulse_start ; //空闲到复位脉冲
wire rst_pulse2ds18b20_wait_start; //复位脉冲到DS18B20从机等待
wire ds18b20_wait2answer_pluse_start; //DS18B20从机等待到应答信号
wire answer_pulse2skip_rom_start; //应答信号到跳过ROM
wire skip_rom2conv_temp_start; //跳过ROM到温度转换
wire conv_temp2wait_500ms_start; //温度转换到等待500ms
wire wait_500ms2again_rst_start; //500ms到再次复位
wire again_rst2ds18b20_wait_start ; //复位脉冲到DS18B20从机等待
wire ds18b20_wait2again_answer_start ; //从机等待到应答
wire again_answer2again_skip_rom_start; //应答信号到跳过rom
wire again_skip_rom2again_conv_temp_start;//跳过rom到读暂存器
wire again_conv_temp2read_data_start; //读暂存器到读数据
wire read_data2idle_start; //读16位数据到空闲
//parameter define
localparam ROM_SKIP_CMD = 8'hcc; // 跳过 ROM 命令 1100 1100
localparam CONVERT_CMD = 8'h44; // 温度转换命令 0100 0100
localparam READ_TEMP = 8'hbe; // 读 DS1820 温度暂存器命令 1011 1110
parameter TIME_500MS = 28'd3000_0000; //500ms
parameter TIME_600US = 28'd30000; //600us
parameter TIME_240US = 28'd12000; //240us
parameter TIME_72US = 28'd3600; //72us
parameter TIME_70US = 28'd3500; //72us
parameter TIME_60US = 28'd3000; //60us
parameter TIME_10US = 28'd500; //2us
parameter TIME_3US = 28'd150; //2us
parameter TIME_2US = 28'd100; //2us
parameter TIME_1US = 28'd50; //1us
//600ms 50*1000*600=3000_0000
//计数器,计算时间
reg [27:0] cnt;
reg flag_cnt;
wire add_cnt;
wire end_cnt;
reg [27:0] Time;
//计算器,计算吧第几个发送出去,一共8个
reg [3:0] cnt1;
wire add_cnt1;
wire end_cnt1;
reg write_flag;
//计算器,计算吧第几个读出去,一共16个
reg [4:0] cnt2;
wire add_cnt2;
wire end_cnt2;
reg read_flag;
//按键
wire neg_key_in1;
wire neg_key_in2;
reg key_in1_d0,key_in1_d1;
reg key_in2_d0,key_in2_d1;
reg start_flag1,start_flag2;
reg dq_dir ; // DQ数据(SDA)方向控制
reg dq_out ; // DQ输出信号
wire dq_in ; // DQ输入信号
wire pos_dq_in,neg_dq_in;
reg dq_in_d0,dq_in_d1;
//计时
reg [15:0] neg_cnt,pos_cnt;
//检测DQ的下降沿和上升沿
assign pos_dq_in = (~dq_in_d1) & dq_in_d0; //上升沿检测,如果检测到上升沿,有0到1,之后下个周期为0
assign neg_dq_in = dq_in_d1 & (~dq_in_d0); //下降沿
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
dq_in_d0<=1'b0;
dq_in_d1<=1'b0;
end
else begin
dq_in_d0<=dq_in;
dq_in_d1<=dq_in_d0;
end
end
//下降沿来了,开始计数,这个方法很好用,专门检测低电平的时间
//当低电平来了,低电平neg_cnt清零并开始自己计数,当上升沿来的那刻,去读neg_cnt这个值,就是低电平的时间
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
neg_cnt<=0;
end
else begin
if(neg_dq_in) //重新清零
neg_cnt<=0;
else
neg_cnt<=neg_cnt+1;
end
end
//上升沿来了,开始计数,
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
pos_cnt<=0;
end
else begin
if(pos_dq_in) //重新清零
pos_cnt<=0;
else
pos_cnt<=pos_cnt+1;
end
end
assign dq = dq_dir ? dq_out : 1'bz; // DQ数据输出或高阻
assign dq_in = dq ; // DQ数据输入
//状态机
aaa@qq.com(posedge clk or negedge rst_n)begin
if(!rst_n)begin
state_c <= IDLE;
end
else begin
state_c <= state_n;
end
end
//第二段:组合逻辑always模块,描述状态转移条件判断
aaa@qq.com(*)begin
case(state_c)
IDLE:begin
if(idl2rst_pulse_start)begin
state_n = RST_PULSE;
end
else begin
state_n = state_c;
end
end
RST_PULSE:begin
if(rst_pulse2ds18b20_wait_start)begin
state_n = DS18B20_WAIT;
end
else begin
state_n = state_c;
end
end
DS18B20_WAIT:begin
if(ds18b20_wait2answer_pluse_start)begin
state_n = ANSWER_PULSE;
end
else begin
state_n = state_c;
end
end
ANSWER_PULSE:begin
if(answer_pulse2skip_rom_start)begin
state_n = SKIP_ROM_DATA;
end
else begin
state_n = state_c;
end
end
SKIP_ROM_DATA:begin
if(skip_rom2conv_temp_start)begin
state_n = CONVERT_TEMP_DATA;
end
else begin
state_n = state_c;
end
end
CONVERT_TEMP_DATA:begin
if(conv_temp2wait_500ms_start)begin
state_n = WAITING_500MS;
end
else begin
state_n = state_c;
end
end
WAITING_500MS:begin
if(wait_500ms2again_rst_start)begin
state_n = AGAIN_RST_PULSE;
end
else begin
state_n = state_c;
end
end
AGAIN_RST_PULSE:begin
if(again_rst2ds18b20_wait_start)begin
state_n = DS18B20_WAIT1;
end
else begin
state_n = state_c;
end
end
DS18B20_WAIT1:begin
if(ds18b20_wait2again_answer_start)begin
state_n = AGAIN_ANSWER_PULSE;
end
else begin
state_n = state_c;
end
end
AGAIN_ANSWER_PULSE:begin
if(again_answer2again_skip_rom_start)begin
state_n = AGAIN_SKIP_ROM_DATA;
end
else begin
state_n = state_c;
end
end
AGAIN_SKIP_ROM_DATA:begin
if(again_skip_rom2again_conv_temp_start)begin
state_n = READ_TEMP_DATA;
end
else begin
state_n = state_c;
end
end
READ_TEMP_DATA:begin
if(again_conv_temp2read_data_start)begin
state_n = READ_DATA;
end
else begin
state_n = state_c;
end
end
READ_DATA:begin
if(read_data2idle_start)begin
state_n = IDLE;
end
else begin
state_n = state_c;
end
end
default:begin
state_n = IDLE;
end
endcase
end
//第三段:设计转移条件
assign idl2rst_pulse_start = state_c==IDLE && end_cnt;
assign rst_pulse2ds18b20_wait_start = state_c==RST_PULSE && end_cnt;
assign ds18b20_wait2answer_pluse_start = state_c==DS18B20_WAIT && end_cnt;
assign answer_pulse2skip_rom_start = state_c==ANSWER_PULSE && (pos_dq_in&&neg_cnt>TIME_60US&&neg_cnt<TIME_240US);
assign skip_rom2conv_temp_start = state_c==SKIP_ROM_DATA && end_cnt1;
assign conv_temp2wait_500ms_start = state_c==CONVERT_TEMP_DATA && end_cnt1;
assign wait_500ms2again_rst_start = state_c==WAITING_500MS && end_cnt;
assign again_rst2ds18b20_wait_start = state_c==AGAIN_RST_PULSE && end_cnt;
assign ds18b20_wait2again_answer_start = state_c==DS18B20_WAIT1 && end_cnt;
assign again_answer2again_skip_rom_start = state_c==AGAIN_ANSWER_PULSE && (pos_dq_in&&neg_cnt>TIME_60US&&neg_cnt<TIME_240US);
assign again_skip_rom2again_conv_temp_start = state_c==AGAIN_SKIP_ROM_DATA && end_cnt1;
assign again_conv_temp2read_data_start = state_c==READ_TEMP_DATA && end_cnt1;
assign read_data2idle_start = state_c==READ_DATA && end_cnt2;
//初始化过程
always @(posedge clk or negedge rst_n) begin
//复位初始化
if(rst_n == 1'b0) begin
dq_dir<=1; //设置DQ为输出
dq_out<=1; //设置DQ输出高电平
flag_cnt<=0;
Time<=1;
temp_data<=0;
end
else if(start_flag1)begin //当按键按下时,温度开始转换
case(state_c)
IDLE:begin //空闲先拉高1us,自己理解的
dq_dir<=1;
dq_out<=1;
Time<=TIME_1US;
flag_cnt<=1;
end
RST_PULSE:begin //主机拉低600us发送复位脉冲
dq_dir<=1;
dq_out<=0;
Time<=TIME_600US;
flag_cnt<=1;
end
DS18B20_WAIT:begin //释放总线,等着15-60us之后,从机把总线拉低
dq_dir<=0;
Time<=TIME_10US;
flag_cnt<=1;
end
ANSWER_PULSE:begin //从机把总线拉低60--240us,之后,从机又把总线拉高,结束应答信号
dq_dir<=0;
flag_cnt<=0;
end
SKIP_ROM_DATA:begin
if(ROM_SKIP_CMD[cnt1]==0) begin //判断第一位是0还是1
if(cnt<TIME_1US) begin
dq_dir<=1; //拉高
dq_out<=1;
end
else if(cnt<TIME_70US)begin
dq_dir<=1;
dq_out<=0;
end
else begin
dq_dir<=0; //释放总线2us,保证在两次写间隙时间大于1us
end
Time<=TIME_72US; //设置每个写间隙时间为70us,释放总线的时间2us,数据手册上:写间隙大于60us
flag_cnt<=1;
end
else begin //判断第一位是0还是1
if(cnt<TIME_1US) begin
dq_dir<=1;
dq_out<=1;
end
else if(cnt<TIME_3US)begin //拉低总线1us以上,在15us之内释放总线,我是拉低2us左右,释放总线
dq_dir<=1;
dq_out<=0;
end
else begin
dq_dir<=0; //包括释放总线
end
Time<=TIME_72US;
flag_cnt<=1;
end
end
CONVERT_TEMP_DATA:begin
if(CONVERT_CMD[cnt1]==0) begin
if(cnt<TIME_1US) begin
dq_dir<=1;
dq_out<=1;
end
else if(cnt<TIME_70US)begin
dq_dir<=1;
dq_out<=0;
end
else begin
dq_dir<=0;
end
Time<=TIME_72US;
flag_cnt<=1;
end
else begin
if(cnt<TIME_1US) begin
dq_dir<=1;
dq_out<=1;
end
else if(cnt<TIME_3US)begin
dq_dir<=1;
dq_out<=0;
end
else begin
dq_dir<=0;
end
Time<=TIME_72US;
flag_cnt<=1;
end
end
WAITING_500MS:begin
dq_dir<=1;
dq_out<=1;
Time<=TIME_500MS;
flag_cnt<=1;
end
AGAIN_RST_PULSE:begin
dq_dir<=1;
dq_out<=0;
Time<=TIME_600US;
flag_cnt<=1;
end
DS18B20_WAIT1:begin
dq_dir<=0;
Time<=TIME_10US;
flag_cnt<=1;
end
AGAIN_ANSWER_PULSE:begin
dq_dir<=0;
flag_cnt<=0;
end
AGAIN_SKIP_ROM_DATA:begin
if(ROM_SKIP_CMD[cnt1]==0) begin
if(cnt<TIME_1US) begin
dq_dir<=1;
dq_out<=1;
end
else if(cnt<TIME_70US)begin
dq_dir<=1;
dq_out<=0;
end
else begin
dq_dir<=0; //释放总线,2us
end
Time<=TIME_72US;
flag_cnt<=1;
end
else begin
if(cnt<TIME_1US) begin
dq_dir<=1;
dq_out<=1;
end
else if(cnt<TIME_3US)begin
dq_dir<=1;
dq_out<=0;
end
else begin
dq_dir<=0; //释放总线,2us
end
Time<=TIME_72US;
flag_cnt<=1;
end
end
READ_TEMP_DATA:begin
if(READ_TEMP[cnt1]==0) begin
if(cnt<TIME_1US) begin
dq_dir<=1;
dq_out<=1;
end
else if(cnt<TIME_70US)begin
dq_dir<=1;
dq_out<=0;
end
else begin
dq_dir<=0;
end
Time<=TIME_72US;
flag_cnt<=1;
end
else begin
if(cnt<TIME_1US) begin
dq_dir<=1;
dq_out<=1;
end
else if(cnt<TIME_3US)begin
dq_dir<=1;
dq_out<=0;
end
else begin
dq_dir<=0;
end
Time<=TIME_72US;
flag_cnt<=1;
end
end
READ_DATA:begin
if(cnt<TIME_70US) begin
if(cnt<TIME_2US) begin
dq_dir<=1;
dq_out<=1;
end
else if(cnt<TIME_3US) begin //拉低最少1us
dq_dir<=1;
dq_out<=0;
end
else begin
dq_dir<=0; //释放总线
if(cnt==TIME_10US) begin //在15us以内读取数据,我是在10us的时刻,读取从机发过来的数据
temp_data[cnt2]<=dq_in;
end
end
end
else begin
dq_dir<=0; //释放总线2us
end
Time<=TIME_72US;
flag_cnt<=1;
end
default:;
endcase
end
end
//计算时间
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt <= 0;
end
else if(add_cnt)begin
if(end_cnt)
cnt <= 0;
else
cnt <= cnt + 1;
end
end
assign add_cnt = flag_cnt;
assign end_cnt = add_cnt && cnt==Time-1;
//计算器
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt1 <= 0;
end
else if(add_cnt1)begin
if(end_cnt1)
cnt1 <= 0;
else
cnt1 <= cnt1 + 1;
end
end
assign add_cnt1 = end_cnt && write_flag ;
assign end_cnt1 = add_cnt1 && cnt1==8-1 ; //八个数据,发送出去了,结束标志
//计数器,
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt2 <= 0;
end
else if(add_cnt2)begin
if(end_cnt2)
cnt2 <= 0;
else
cnt2 <= cnt2 + 1;
end
end
assign add_cnt2 = end_cnt && read_flag;
assign end_cnt2 = add_cnt2 && cnt2==16-1 ; //16个数据读进来,结束标志
//写8位数据的标志位
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
write_flag<=0;
end
else if(state_c==SKIP_ROM_DATA||state_c==CONVERT_TEMP_DATA||state_c==AGAIN_SKIP_ROM_DATA||state_c==READ_TEMP_DATA) begin
write_flag<=1;
end
else begin
write_flag<=0;
end
end
//读8位数据的标志
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
read_flag<=0;
end
else if(state_c==READ_DATA) begin
read_flag<=1;
end
else begin
read_flag<=0;
end
end
//数据转换
//判断符号位
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
sign <= 1'b0;
data1 <= 11'b0;
end
else if(temp_data[15] == 1'b0) begin
sign <= 1'b0;
data1 <= temp_data[10:0];
end
else if(temp_data[15] == 1'b1) begin
sign <= 1'b1;
data1 <= ~temp_data[10:0] + 1'b1;
end
end
//对采集到的温度进行转换
assign data2 = (data1 * 11'd625)/ 7'd100;
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
data<=123;
end
else begin
data<=data2; //将读出的值显示到数码管上
end
end
//测试
assign neg_key_in1 = key_in1_d1 & (~key_in1_d0);
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
key_in1_d0<=1'b0;
key_in1_d1<=1'b0;
end
else begin
key_in1_d0<=key[0];
key_in1_d1<=key_in1_d0;
end
end
assign neg_key_in2 = key_in2_d1 & (~key_in2_d0);
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
key_in2_d0<=1'b0;
key_in2_d1<=1'b0;
end
else begin
key_in2_d0<=key[1];
key_in2_d1<=key_in2_d0;
end
end
//测试
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
start_flag1<=0;
end
else if(neg_key_in1) begin
start_flag1<=1;
end
end
//测试
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
start_flag2<=0;
end
else if(neg_key_in2) begin
start_flag2<=1;
end
end
//测试
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
led[0]<=1;
end
else if(start_flag1) begin
led[0]<=0;
end
end
//测试
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
led[1]<=1;
end
else if(start_flag2) begin
led[1]<=0;
end
end
endmodule
推荐阅读
-
fpga的EPCS 配置的2种方法是什么?FPGA下载程序的方法
-
Intel出货Stratix 10 DX FPGA:支持PCIe 4.0 x16
-
历史性第一次!国产FPGA打入日本市场
-
FPGA——给AI换个“大”动力“小”心脏
-
FPGA 云服务:30 倍效率,4 成费用,创业公司拥抱 AI 的计算选择
-
专访腾讯云FPGA团队:FPGA云服务器给产业链带来了哪些价值?
-
Intel发布全球容量最大FPGA:14nm 443亿晶体管超AMD 64核霄龙
-
FPGA云服务器如何推动人工智能落地发展?
-
数据大爆炸、智能要互联,菲数科技要以“FPGA+云"助力高性能计算
-
Intel发全新Agilex FPGA:10nm 3D封装、支持DDR5/PCIe 5.0