VHDL语言编写DS18B20温度传感器程序详解

来源:互联网 发布:2018淘宝开店流程 编辑:程序博客网 时间:2024/04/28 21:39

        网上关于DS18B20的资料很多,但是光有程序,没有讲解,导致身边很多同学即使拿到源码也无从下手,故写此篇文章,一方面接收DB18B20,一方结合原理详细讲解源码的意义。

器件原理:

原理图:

        基本上所有的重要信息都在这张图上啦。很独特的一个点就是数据输入输出是共用一个管脚DQ的。
对于唯一的数据口,需要一定的执行顺讯:

执行序列


如图,每一次操作都必须满足上述顺序,若是缺少或者混乱,器件将不会返回值。

    初始化:

       通过单总线的所有执行操作处理都从一个初始化序列开始。初始化序列包括一个从总线控制器发出的复位脉冲和其后由从机发出的存在脉冲,存在脉冲让总线控制器知道DB18B20存在且已经做好操作准备。

    ROM指令:

       这里采用单从机模式(只有一个DB18B20),只挑取几个比较重要的指令做以说明

      READ ROM[33h](读取ROM指令):

         只有在总线上存在单只DS18B20的时候才能使用这条命令。该命令允许总线控制器在不使用搜索ROM指令的情况下读取从机的64位片序列码。如果总线上有不止一只从机,当所有从机试图同时传送信号时就会发生数据冲突。

       SKIP ROM[CCh](忽略ROM指令) :     

          这条指令允许总线控制器不用提供64位ROM编码就是用功能指令。例如,总线控制器可以先发出一条忽略ROM指令,然后发出温度转换指令[44h],从而完成温度转换操作。注意:当只有一直从机在总线上时,无论如何,忽略ROM指令之后只能跟着发出一条读取暂存器指令[BEh]。

        功能指令:

        在总线控制器发给欲连接的DS18B20一条ROM指令后,跟着可以发送一条DS18B20功能指令。这些命令允许总线控制器读写DS18B20的暂存器,发起温度转换和识别电源模式。

       CONVERT T[44h](温度转换指令):

          这条命令用以启动一次温度转换。温度转换指令被执行,产生的温度转换结果数据以2个字节的形式被存储在告诉暂存器中,而后DS18B20保存等待状态。如果DS18B20以外部电源供电,总线控制器在发出该命令后跟着发出读时序,DS18B20如果处于转换中,将在总线上返回0,若温度转换完成,则返回1。

        READ SCRATCHPAD(读取暂存器指令): 

        这条命令读取暂存器的内容。读取将从字节0开始,一直进行下去,直到第9字节(字节8)读完,如果不想读完所有字节,控制器可以在任何时间发出复位命令来终止读取。


时序:

        时序对于器件来说是很重要的,如果时序不对,器件就不能正常工作。 

      复位序列:复位和存在脉冲:

        和DS18B20间 的任何通讯都需要以初始化序列开始。一个复位脉冲跟着一个存在脉冲表明DS18B20已经准备好发送和接收数据。在初始化序列期间,总线控制器拉低总线并保持480us以发出(TX)一个复位脉冲,然后释放总线,进入接受状态(RX)。单总线由5K上拉电阻拉倒高电平。当DS18B20探测到I/O引脚上的上升沿后,等待15~60us,然后发出一个由60~240us低电平信号构成的存在脉冲。
        初始化时序见图:


      写时序:

        DS18B20的数据读写是通过时序处理位来确认信息交换的。有两种写时序:写1时序和写0时序。总线控制器通过写1时序写逻辑1到DS18B20,写0时序写逻辑0到DS18B20。所有写时序必须最少持续60us,包括两个写周期之间至少1us的恢复时间。当总线控制器把数据线从逻辑高电平拉到低电平的时候,写时序开始。
       总线控制器要生产一个写时序,必须把数据线拉到低电平然后释放,在写时序开始后的15us释放总线。当总线被释放的时候,5K的上拉电阻将拉高总线。总控制器要生成一个写0时序,必须把数据线拉到低电平并持续保持(至少60us)。
       总线控制器初始化写时序后,DA18B20在一个15us到60us的窗口内对I/O线采样。如果线上是高电平,就是写1。如果线上是低电平,就是写0。
       写时序如图:

      读时序:

       总线控制器发起读时序时,DS18B20仅被用来传输数据给控制器。因此,总线控制器在发出读暂存器指令[BEh]或读电源模式指令[B4H]后必须立刻开始读时序,DS18B20可以提供请求信息。除此之外,总线控制器在发出发送温度转换指令[44h]或召回EEPROM指令[B8h]之后读时序,详见DS18B20功能指令节。
      所有读时序必须最少60us,包括两个读周期间至少1us的恢复时间。当总线控制器把数据线从高电平拉到低电平时,读时序开始,数据线必须至少保持1us,然后总线被释放。在总线控制器发出读时序后,DS18B20通过拉高或拉低总线上来传输1或0.当传输逻辑0结束后,总线将被释放,通过上拉电阻回到上升沿状态。从DS18B20输出的数据在读时序的下降沿出现后15us内有效。因此,总线控制器在读时序开始后必须停止把I/O脚驱动为低电平15us,以读取I/O脚状态。
       读时序如图:

     操作举例:

          
        看了那么多的原理,肯定很多同学都忍不住要想知道如何具体操作的啦,下面就对源码详细解释。

源码详解:

           无论给出什么样的源码,同学们都不可能直接使用,肯定要对其中关键的端口或者信号量加以修改,所以这里就给出核心代码,只要在理解原理的基础上适当的加以裁剪就可以使用。
library IEEE;  use IEEE.STD_LOGIC_1164.ALL;  use IEEE.STD_LOGIC_ARITH.ALL;  use IEEE.STD_LOGIC_UNSIGNED.ALL;  entity ds18B20 is  port(clk : in std_logic;   ---50MHz      dq  : inout std_logic;  --DQ数据输出输入端     rst: in std_logic;      LED : out std_logic;    --指示灯,标志程序进行到哪一步LED2 : out std_logic; LED3 : out std_logic;      dataout1,dataout2,dataout3 : out std_logic_vector(6 downto 0 ));  --数据输出端end ds18B20;  
architecture Behavioral of ds18B20 is  TYPE  STATE_TYPE  is (RESET,CMD_CC,WRITE_BYTE,WRITE_LOW,WRITE_HIGH,READ_BIT, <span style="white-space:pre"></span>CMD_44,CMD_BE,WAIT800MS,GET_TMP,WAIT4MS);   --状态机  signal STATE: STATE_TYPE:=RESET;   --初始化状态机
signal clk_temp : std_logic:='0';  --监测总线上的数据signal clk1m : std_logic; --分频后得到的1M时钟 
signal cp: std_logic;  -- 为时序而产生的1ms时钟
begin  ----------分频程序,分到1MHz----------------ClkDivider:process (clk,clk_temp)  begin  if rising_edge(clk) then     if (count = 24) then        count <= 0;        clk_temp<= not clk_temp;     else        count <= count +1;     end if;  end if;      clk1m<=clk_temp;  end Process;  ----------为时序产生1ms时钟----------------process (clk1m)  variable n: integer range 0 to 12000:=0;begin             -----cp 1ms if rising_edge(clk1m) then     n:=n+1;   if (n>12000) then n:=0;  cp<=not cp;  end if;    end if;  end Process;  
STATE_TRANSITION:process(STATE,clk1m)    --重头戏,状态机开始啦begin if rising_edge(clk1m) then    if(rst='0') then      STATE<=RESET;   else     case STATE is       when RESET=>  --如果处在复位状态     LED2<='0';      LED3<='0';      if (cnt>=0 and cnt<500) then -- 500μs的复位低电平       dq<='0';   --dq作为输出       cnt<=cnt+1;         STATE<=RESET;  --在一定时序内保持复位状态       elsif (cnt>=500 and cnt<510) then        dq<='Z';  --高阻态再输入下一级电路的话,对下级电路无任何影响,和没接一样,高阻态可以应用在inout端口里面,这样在inout没有输出的时候就弄个高阻态,这样就其电平就可以由外面的输入信号决定了       cnt<=cnt+1;        STATE<=RESET;  --拉高dq     elsif (cnt>=510 and cnt<750) then   -- 240μs       temp<=dq;  --dq作为输入(对于控制器来说是输入,对于DS18b20来说是输出,这个道理大家应该都明白吧)       if(cnt=580) then          temp<=dq;          if(temp='1') then  --如果temp为1说明DS18B20存在(因为检测到了存在脉冲)           LED<='0';          else 
           LED<='1';          end if;        end if;        cnt<=cnt+1;         STATE<=RESET;       elsif (cnt>=750) then  --初始化时序结束       cnt<=0; --计数器清零        STATE<=CMD_CC;  --复位过程伴随着忽略rom指令“CC”     end if;       when CMD_CC=>  --忽略rom指令“CC”       LED2<='1';        LED3<='0';        write_temp<="11001100";  --将write_temp设为“11001100”       STATE<=WRITE_BYTE;      when WRITE_BYTE=>        case WRITE_BYTE_CNT is           when 0 to 7=>             if (write_temp(WRITE_BYTE_CNT)='0') then  --判断当前write_temp第WRITE_BYTE_CNT上是否为'0'             STATE<=WRITE_LOW;  --如果当前write_temp第WRITE_BYTE_CNT上是'0',进入WRITE_LOW状态(即对DS18b20写低)             LED3<='1';                 else              STATE<=WRITE_HIGH;  --如果当前write_temp第WRITE_BYTE_CNT上是'1',进入WRITE_HIGH状态(即对DS18b20写高)           end if;             WRITE_BYTE_CNT<=WRITE_BYTE_CNT+1;  --判断write_temp的下一位         when 8=>            if (WRITE_BYTE_FLAG=0) then -- 第一次写0XCC完毕               STATE<=CMD_44;  --开启温度转换指令             WRITE_BYTE_FLAG<=1;             elsif (WRITE_BYTE_FLAG=1) then --写0X44完毕 (写温度转换指令后没有读数据?)              STATE<=RESET;               WRITE_BYTE_FLAG<=2;             elsif (WRITE_BYTE_FLAG=2) then --第二次写0XCC完毕               STATE<=CMD_BE;               WRITE_BYTE_FLAG<=3;             elsif (WRITE_BYTE_FLAG=3) then --写0XBE完毕               STATE<=GET_TMP;               WRITE_BYTE_FLAG<=0;             end if;             WRITE_BYTE_CNT<=0;          when others=>STATE<=RESET;        end case;       when WRITE_LOW=>  进入写0时序,参看前面       LED3<='1';        case WRITE_LOW_CNT is           when 0=>             dq<='0';             if (cnt=70) then  --等待时序             cnt<=0;               WRITE_LOW_CNT<=1;             else               cnt<=cnt+1;             end if;           when 1=>             dq<='Z';             if (cnt=5) then               cnt<=0;               WRITE_LOW_CNT<=2;             else               cnt<=cnt+1;             end if;           when 2=>             STATE<=WRITE_BYTE;             WRITE_LOW_CNT<=0;           when others=>WRITE_LOW_CNT<=0;         end case;       when WRITE_HIGH=>    --进入写1时序,参看前面       case WRITE_HIGH_CNT is           when 0=>             dq<='0';             if (cnt=8) then               cnt<=0;               WRITE_HIGH_CNT<=1;             else               cnt<=cnt+1;             end if;           when 1=>             dq<='Z';             if (cnt=72) then               cnt<=0;               WRITE_HIGH_CNT<=2;             else               cnt<=cnt+1;             end if;           when 2=>             STATE<=WRITE_BYTE;             WRITE_HIGH_CNT<=0;          when others=>WRITE_HIGH_CNT<=0;        end case;       when CMD_44=>         write_temp<="01000100";  --写指令44h       STATE<=WRITE_BYTE;       when CMD_BE=>         write_temp<="10111110";  --写指令BEh       STATE<=WRITE_BYTE;     when READ_BIT=>         case READ_BIT_CNT is           when 0=>             dq<='0';  --4μs的低电平           if (cnt=4) then               READ_BIT_CNT<=1;               cnt<=0;             else               cnt<=cnt+1;             end if;           when 1=>             dq<='Z'; --4μs的高电平            if (cnt=4) then              READ_BIT_CNT<=2;               cnt<=0;             else               cnt<=cnt+1;             end if;           when 2=>             dq<='Z';            TMP_BIT<=dq; --12μs读出数据 ,就是最后一次赋值的结果。           if (cnt=4) then               READ_BIT_CNT<=3;               cnt<=0;             else               cnt<=cnt+1;             end if;           when 3=>              dq<='Z';    --控制器拉高总线            if (cnt=50) then --读出数据后,等待50us              cnt<=0;               READ_BIT_CNT<=0;               STATE<=GET_TMP;             else               cnt<=cnt+1;             end if;           when others=>READ_BIT_CNT<=0;         end case;       when GET_TMP=>  --读数据       case GET_TMP_CNT is           when 0 =>            STATE<=READ_BIT;            GET_TMP_CNT<=GET_TMP_CNT+1;          when 1 to 12=>             STATE<=READ_BIT;             TMP(GET_TMP_CNT-1)<=TMP_BIT;--将读出的每一位数据按顺序存进 TMP(0 to 11)里面             GET_TMP_CNT<=GET_TMP_CNT+1; --存的是读出的0到11位,第十二位没有存          when 13=>             GET_TMP_CNT<=0;             STATE<=WAIT4MS;         end case;       when WAIT4MS=>  --等待4ms       if (cnt>=4000) then           --STATE<=WAIT4MS;          STATE<=RESET;           cnt<=0;         else           cnt<=cnt+1;           STATE<=WAIT4MS;         end if;       when others=>STATE<=RESET;        LED<='0';        LED2<='0';         LED3<='0';    end case;  end if; end if; end process;  
end;
        其中LED只是为了方便观察程序到了哪一步,所有的LED都可以不要。

       这样就完成了,当然肯定还存在一些问题,希望大家不吝赐教。或者还有什么其他的疑惑,可以联系QQ:3463758699





0 0
原创粉丝点击