欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

DS18B20 FPGA

程序员文章站 2022-06-08 22:05:33
...

最近,写了好几个关于通信的,只要原理理解对了,你按照时序要求,写就好了。可是有些时候,我们对通讯的时序会理解的不对。这个时候多写几个通讯时序,就慢慢熟悉了。

你的DQ数据线要设置为:inout,千万别忘记了

写程序之前,还是先介绍一下ds18b20的时序图:

ds18b20的经典通讯过程如下:

DS18B20 FPGA

我们就可以按照这个通讯方式去写程序:只要按照这个步骤,一步一步的写就可以了:

首先任何的通讯都需要一个初始化,就是互相告诉彼此,我们要准备通讯了,要准备传输数据了,ds18b20也不例外,

下面是初始化的时序过程:

主机先发送480—960us的低电平信号(你可以给个600us的低电平就可以),然后释放主机总线,交给从机,从机会响应60—240左右的低电平信号,之后,从机会把总线拉高。

DS18B20 FPGA

然后就是写命令给ds18b20,写数据时,低位先传,然后是高位,写数据也分写0和写1

比如你写0,主机只要拉低总线60us以上就ok了,从机会在15us---60us之间,采集总线上的信号,这时采集的信号正好是0,这样就写完了一个数据,

比如你写1,这个稍微过程麻烦一下,首先主机要先拉低总线1us以上,然后在15us以内释放总线(包括之前的那个拉低1us的时间),从机会在15us---60us,采集总线上的数据,由于主机已经释放了总线,现在总线属于高电平,这时,从机就把1写进去了。

注意:写0和写1时间要大于60us以上,两次写的间隙,主机要释放总线1us以上

DS18B20 FPGA

,现在,数据可以写入ds18b20中了,那如何从ds18b20中读取温度的值呢,

首先,我们要先发送读暂存器命令(BE),告诉从机要开始发送数据了,这时,我们要开始准备接受从机发过的数据,从机先发BYTE0,然后是BYTE1,....BYTE0先发低位,然后是高位。千万别搞错了。

DS18B20 FPGA

如何读从机发来的数据呢,看看下面的时序图:DS18B20 FPGA

 

主机先拉低1us以上,然后释放总线、这个时候,从机就发送数据过来,从机会在15us,以内拉低总线(发送0),或者拉高总线(发送1)我们就要在15us以内的这段时间(包括之前的拉低1us),读这个值,我们可以在大概10us的时候读这个值,但是我们也要在15us以内释放总线。

下面是别人介绍的关于读写0,1,可以参考一下。

DS18B20 FPGA

DS18B20 FPGA

代码:每个人写的代码都不同,只要你原理懂了,用代码实现就可以了

按键按下,启动温度转换,下面输出的数据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