【Day25】One-Wire protocol

One-Wire

One-Wire是一种只需要一条线即可传输资料的传输协定,而通常这种传输协定会用於与小型装置沟通,例如数位温度、湿度感测器。

那我们在这边会以 AM2302 的规格为主去撰写我们的程序


AM2302 Datasheet

注意:Tbe 为 master 拉低

图片出处:AM2302 Datasheet

这边我们由图一及图二可以看到在接收 AM2302 的资料时,一共会有一连串的 Start Signal 以及 40 bit 的 data,分别是湿度的前 8 bit、後 8 bit,温度的前 8 bit、後 8 bit,还有最後的 8 bit 较验位(较验位 = 前面四笔 8 bit 资料做相加)

而图三及图四则告诉了我们各种信号所会花费的时间长

而 datasheet 内容也有提到这里的 SDA 线也是有 pull-up 的~

先来第一步吧!

先定义状态机

//===== state machine =====//
localparam IDLE     = 3'd0;
localparam Be       = 3'd1;
localparam GO       = 3'd2;
localparam REL      = 3'd3;
localparam REH      = 3'd4;
localparam DATA     = 3'd5;
localparam CHECKOUT = 3'd6;

定义每个间隔时间

//===== during time =====//
localparam durBe       = 16'd50000;//1ms
localparam durGo       = 16'd1500; //30us
localparam durRel      = 16'd2555; //80us
localparam durReh      = 16'd2560; //80us
localparam durDataHIGH = 16'd2580; //70us
localparam durDataMax  = 16'd5000; //100us

这里的间隔周期以 datasheet 的 typical 为准,而这里的时脉是以 50M 为主,举个例子:如果要 1ms 那麽 counter 就要数 1ms/20ns = 50000,其他以此类推。

再来比较有趣的是这里可以不用精准定义 "0" 讯号以及 "1" 讯号的 HIGH 时间长,因为只要在资料线一变 LOW 时去检查在 HIGH 时数到的数字有没有超过一定的值就可以视为讯号 "1" 了(这里是抓 70us,因为 "0" HIGH 的 typical 值为 30us,"1" HIGH 的 typical 值为 75us)

再来定义输入输出
输入:

  • clk_sys
  • rst_n
  • en(状态机 enable)

输出:

  • dataReady(资料正确时升为一,表示资料可读取走)
  • data_o

inout

  • SDA
module AM2302_controller(
  clkSys, 
  en, 
  rst_n, 
  SDA, 
  dataReady, 
  data_o
);
/*---------Ports Declarations---------*/
input         clkSys;
input         en;
input         rst_n;
inout         SDA;
output        dataReady;
output [39:0] data_o;
reg           dataReady;
reg    [39:0] data_o;

宣告变数:

  • fstate(状态机用)
  • counter(计数用)
  • databuf(蒐集的资料暂存的地方)
  • counterEN(counter 的致能)
  • flag_get(以此 flag 抓负缘)
  • sdaReg(sda 的暂存)
/*---------variables---------*/
reg     [2:0] fstate;
reg    [15:0] counter;
reg    [39:0] databuf;
reg           counterEN;
reg           flag_get;
reg           sdaReg;

SDA 输出

/*---------assign wire---------*/
assign SDA = (sdaReg)?(1'bz):(1'b0);

counter 致能控制 counter 的计数

/*---------counter---------*/
always@(posedge clkSys or negedge rst_n)begin
  if(!rst_n)        counter <= 16'd0;
  else if(counterEN)counter <= counter + 16'd1;
  else              counter <= 16'd0;
end

状态机状态逻辑

  • 每个状态都数满周期後才往下个状态跑。
  • 特别的是,我这边的 DATA 状态是让它数到超过,才往下状态走(因为 "1" 的 HIGH 时间不会到 100us 这麽长,如果超过代表资料传送完毕了),如此一来可以省掉许多判断。
  • 最後一个状态是来检查校验码的。
/*---------fstate_state---------*/
always@(posedge clkSys or negedge rst_n)begin
  if(!rst_n)fstate <= IDLE;
  else begin
    case(fstate)
      IDLE:begin
        if(en)fstate <= Be;
        else  fstate <= IDLE;
      end
      Be:begin
        if(counter == durBe)fstate <= GO;
        else                fstate <= Be;
      end
      GO:begin
        if(counter == durGo)fstate <= REL;
        else                fstate <= GO;
      end
      REL:begin
        if(counter == durRel)fstate <= REH;
        else                 fstate <= REL;
      end
      REH:begin
        if(counter == durReh)fstate <= DATA;
        else                 fstate <= REH;
      end
      DATA:begin
        if(counter == durDataMax)fstate <= CHECKOUT;
        else                     fstate <= DATA;
      end
      CHECKOUT:fstate <= IDLE;
      default: fstate <= IDLE;
    endcase
  end
end

状态机输出逻辑

  • sda 只有在 be 需要拉低(datasheet 的 Tbe 为 master 拉低)。
  • 当 counter 小於该状态应计数值的值时 counterEN = 1,这样是为了让 counter 可以在进入下个状态前归零。
/*---------fstate_output---------*/
always@(posedge clkSys or negedge rst_n)begin
  if(!rst_n)begin
    counterEN <= 1'b0;
    sdaReg    <= 1'b1;
  end 
  else begin
    case(fstate)
      IDLE:begin
        counterEN <= 1'b0;
        sdaReg    <= 1'b1;
      end
      Be:begin
        if(counter < durBe)counterEN  <= 1'b1;
        else               counterEN  <= 1'b0;
        sdaReg <= 1'b0;
      end
      GO:begin
        if(counter < durGo)counterEN  <= 1'b1;
        else               counterEN  <= 1'b0;
        sdaReg <= 1'b1;
      end
      REL:begin
        if(!SDA && counter < durRel)counterEN  <= 1'b1;
        else                        counterEN  <= 1'b0;
        sdaReg <= 1'b1;
      end
      REH:begin
        if(SDA && counter < durReh)counterEN  <= 1'b1;
        else                       counterEN  <= 1'b0;
        sdaReg <= 1'b1;
      end
      DATA:begin
        if(SDA && counter < durDataMax)counterEN  <= 1'b1;
        else                           counterEN  <= 1'b0;
        sdaReg <= 1'b1;
      end
      CHECKOUT:begin
        counterEN <= 1'b0;
        sdaReg    <= 1'b1;
      end 
      default:begin
        counterEN <= 1'b0;
        sdaReg    <= 1'b1;
      end 
    endcase
  end
end

flag_get

/*---------flag_get---------*/
always@(posedge clkSys or negedge rst_n)begin
  if(!rst_n)flag_get <= 1'b0;
  else      flag_get <= SDA;
end

把 SDA 延後一个 clk 给 flag_get,往後只要读到 SDA = 0;flag_get = 1,就知道 SDA 此时下降了(负缘)

蒐集资料

/*---------put data in databuf---------*/
always@(posedge clkSys or negedge rst_n)begin
  if(!rst_n)databuf <= 39'd0;
  else begin
    if(fstate > REH)begin
      if(!SDA && flag_get && (counter > durDataHIGH))     databuf <= {databuf[38:0],1'b1};    
      else if(!SDA && flag_get && (counter < durDataHIGH))databuf <= {databuf[38:0],1'b0};
      else                                                databuf <= databuf;
    end
    else databuf <= 39'd0;
  end 
end

在 SDA 刚下降时去检查刚刚在 SDA 为 HIGH 时维持了多少时间,如果大於 70us 则将 "1" 左移移入 databuf,反之则移入 "0"。

检查较验码

/*---------check data and sent dataReady---------*/
always@(posedge clkSys or negedge rst_n)begin
  if(!rst_n)begin
    data_o    <= 39'd0; 
    dataReady <= 1'b0;
  end 
  else begin
    if(fstate==CHECKOUT)begin
      if(databuf[7:0]==databuf[39:32]+databuf[31:24]+databuf[23:16]+databuf[15:8])begin
        data_o    <= databuf;
        dataReady <= 1'b1;
      end 
      else begin
        data_o    <= data_o;
        dataReady <= 1'b0;
      end 
    end
    else begin
      data_o    <= data_o;
      dataReady <= 1'b0;
    end
  end
end

endmodule

在 CHECKOUT 状态检查较验码,如果正确则输出至 data_o,并且将 dataReady 升为 "1"。

如此以来我们就完成了这个 AM2302 controller 模组瞜,是不是比上次的 I2C 更简单了一点呢~~~~


<<:  【Day 25】SwiftUI -Drawing Paths and Shapes

>>:  从容面对不如预期,把不爽留给命运

【Day01-资料】什麽才叫做资料?不就是资料吗还有什麽差别?

在这个演算法当道的时代 每一家网路公司在想办法尽量的搜集使用者的资讯 不论是苹果限制脸书获取使用者的...

【少女人妻的30天Elastic】Day 30 : App Search_API 介绍与应用_Synonyms

Aloha!我是少女人妻 Uerica!这篇是最後一篇了,虽然没有写到很深,但对搜寻引擎真的有多了...

EP 15: The Button of item in ListView binds Command to ViewModel

Hello, 各位 iT邦帮忙 的粉丝们大家好~~~ 本篇是 Re: 从零开始用 Xamarin 技...

19.MYSQL NOT指令

NOT与!的意思相同,它代表的意思是,运算结果为0时回传1,其他都回传1 WHERE NOT (AG...

Day 13 - .NET Core奇遇记

主导的第一个计画就是帮厂商开发一个平台并且包含3D模型模拟的功能,然後需要有一个後台给厂商能够上传图...