【Day22】I2C Master 状态机的实现

由於上一篇已经介绍过了 SPI 的 Timing Diagram,那麽今天就直接进入 I2C Master 状态机的程序喽~~

设计I2C Master的状态机

先来定义 8 个状态(下面会解释各个状态的行为)

  • IDLE
  • GO
  • START
  • WAIT
  • SHIFT
  • STOP
  • FINAL
  • END
/*---------parameter---------*/
parameter IDLE  = 3'd0;
parameter GO    = 3'd1;
parameter START = 3'd2;
parameter WAIT  = 3'd3;
parameter SHIFT = 3'd4;
parameter STOP  = 3'd5;
parameter FINAL = 3'd6;
parameter END   = 3'd7;

再来定义输入输出
输入:

  • clk_sys
  • rst_n
  • SCLK_100k(输入已经生成好的 SCLK)
  • en(状态机 enable)
  • tick_I2C
  • count(计数移位 18 or 27 次的计数器,由外模组来数所以这里是输入)
  • R_W(我这次状态机跑的次数没有固定,分别为 write 模式的 18 次((8+1)bit x 2),read 模式的 27 次((8+1)bit x 3),低电位是 write,高电位是 read)

输出:

  • ACK1, ACK2, ACK3(为一时,让外部模组知道此时要收 ACK)
  • SCLK_temp(负责除了传输时以外的 SCLK 讯号控制,会利用 SHEN 判断搭配 assign 去做切换)
  • LDEN(LoadEnable,告诉外部模块要 Load 资料)
  • SHEN(ShiftEnable,告诉外部模块要移位)
  • rstcount(reset count,告诉外部模块将计数器归零)
  • countEN(CountEnable,告诉外部模块将计数器往上计数)
  • SDO(除了资料的 SDA 以外的 SDA 控制,也是会利用 SHEN 判断搭配 assign 去做切换)
  • rstACK(因为是透过外部暂存器存放 ACK 状态,因此也需要 reset 这些暂存器的时机,因此需要 rstACK ,会在最後通讯结束时拉到 "1")
  • SCLK(最後总 SCLK 输出)
module I2C_control_R_W(
  clk_sys, 
  SCLK_100k, 
  tick_I2C, 
  rst_n, 
  en, 
  count, 
  countEN,
  rstcount, 
  ACK1,
  ACK2, 
  ACK3, 
  rstACK, 
  SCLK, 
  SCLK_temp,  
  SHEN, 
  LDEN, 
  SDO, 
  R_W
);
/*---------ports declaration---------*/
input       clk_sys;
input       rst_n;
input       en;
input       SCLK_100k;
input       tick_I2C;
input       R_W;// W---->0, R---->1
input [4:0] count;
output      ACK1; 
output      ACK2; 
output      ACK3; 
output      SCLK_temp; 
output      SHEN; 
output      LDEN; 
output      SDO; 
output      countEN; 
output      rstcount; 
output      rstACK;
output      SCLK;
reg         ACK1; 
reg         ACK2; 
reg         ACK3; 
reg         SCLK_temp; 
reg         SHEN; 
reg         LDEN; 
reg         SDO; 
reg         countEN; 
reg         rstcount; 
reg         rstACK;
wire        SCLK;

宣告跑状态的变数:

/*---------variables---------*/	
reg [2:0] fstate;

状态逻辑:

  • 重置时要能回到 IDLE 状态。
  • IDLE(收到 en(enable)讯号後开始到下一个状态)
  • GO(只有一个传输周期,用来 Load 资料用)
  • START(只有一个传输周期,用来 Load 资料用,但这里发生 START 讯号)
  • WAIT(等待一个传输周期)
  • SHIFT(count 数满 18 or 27 次後并且最後一次也要待满一个传输周期,状态才往 STOP)
  • STOP(在此状态会停止所有动作)
  • FINAL(这里开始要产生 STOP 讯号)
  • END(完成 STOP 讯号的产生,接着再回到 IDLE)
/*---------fstate state---------*/
always@(posedge clk_sys or negedge rst_n)begin
  if(!rst_n)begin
    fstate <= IDLE;
  end
  else begin
    case(fstate)
      IDLE:begin
        if(en) fstate <= GO;
        else   fstate <= IDLE;
      end
      GO:begin
        if(tick_I2C)fstate <= START;
        else        fstate <= GO;
      end
      START:begin
        if(tick_I2C)fstate <= WAIT;
        else        fstate <= START;
      end
      WAIT:begin
        if(tick_I2C)fstate <= SHIFT;
        else        fstate <= WAIT;
      end
      SHIFT:begin
        if(!R_W)begin//write-->18bit
        if(count==5'd16&&tick_I2C)fstate <= STOP;
        else                      fstate <= SHIFT;
      end
      else begin//read-->27bit
        if(count==5'd25&&tick_I2C)fstate <= STOP;
        else                      fstate <= SHIFT;
      end
      end
      STOP:begin
        if(tick_I2C)fstate <= FINAL;
        else        fstate <= STOP;
      end
      FINAL:begin
        if(tick_I2C)fstate <= END;
        else        fstate <= FINAL;
      end
      END:begin
        if(tick_I2C)fstate <= IDLE;
        else        fstate <= END;
      end
    endcase
  end
end

输出逻辑:

  • IDLE(闲置状态,SDA 及 SCLK 都为"1")
  • GO(在这里依旧 SDA 及 SCLK 都为 "1",但会在此状态 Load 资料)
  • START(这里发生 START 讯号,SDA 此时为 "0",SCLK 则维持)
  • WAIT(等待一个传输周期,SDA 及 SCLK 维持,与上个状态相同)
  • SHIFT(在此状态的 SDA 及 SCLK 值不影响,会 assign 到外部模块,主要做资料的移位以及在第 9、18、27 bit 接收 ACK)
  • STOP(在此状态 SDA 及 SCLK 都先拉为 "0",停止所有动作)
  • FINAL(这里开始要产生 STOP 讯号,因此 SCLK 先拉到 "1")
  • END(最後 SDA 拉为 "1",产生 STOP 讯号(完成一次通讯),接着再回到 IDLE)
/*---------fstate output---------*/
always@(posedge clk_sys or negedge rst_n)begin
  if(!rst_n)begin
    SCLK_temp <= 1'b1;
    LDEN      <= 1'b0;
    SHEN      <= 1'b0;
    ACK1      <= 1'b0;
    ACK2      <= 1'b0;
    ACK3      <= 1'b0;
    SDO       <= 1'b1;//SDIN_temp control data[26]/raising/falling
    countEN   <= 1'b0;
    rstcount  <= 1'b0;
    rstACK    <= 1'b0;
  end
  else begin
    if(tick_I2C)begin
      case(fstate)
        IDLE:begin
          SCLK_temp <= 1'b1;//high
          LDEN      <= 1'b0;
          SHEN      <= 1'b0;
          ACK1      <= 1'b0;
          ACK2      <= 1'b0;
          ACK3      <= 1'b0;
          SDO       <= 1'b1;//high
          countEN   <= 1'b0;
          rstcount  <= 1'b0;
          rstACK    <= 1'b0;
        end
        GO:begin
          SCLK_temp <= 1'b1;//high
          LDEN      <= 1'b1;//load data
          SHEN      <= 1'b0;
          ACK1      <= 1'b0;
          ACK2      <= 1'b0;
          ACK3      <= 1'b0;
          SDO       <= 1'b1;//high
          countEN   <= 1'b0;
          rstcount  <= 1'b0;
          rstACK    <= 1'b0;
        end
        START:begin
          SCLK_temp <= 1'b1;//high
          LDEN      <= 1'b0;
          SHEN      <= 1'b0;
          ACK1      <= 1'b0;
          ACK2      <= 1'b0;
          ACK3      <= 1'b0;
          SDO       <= 1'b0;//falling(start)
          countEN   <= 1'b0;
          rstcount  <= 1'b0;
          rstACK    <= 1'b0;
        end
        WAIT:begin
          SCLK_temp <= 1'b1;//high
          LDEN      <= 1'b0;
          SHEN      <= 1'b0;
          ACK1      <= 1'b0;
          ACK2      <= 1'b0;
          ACK3      <= 1'b0;
          SDO       <= 1'b0;//low
          countEN   <= 1'b0;
          rstcount  <= 1'b0;
          rstACK    <= 1'b0;
        end
        SHIFT:begin
          SCLK_temp <= 1'b0;//don't care
          LDEN      <= 1'b0;
          SHEN      <= 1'b1;//shifting
          //ACK1
          if(count==5'd7) ACK1 <= 1'b1;
          else            ACK1 <= 1'b0;
          //ACK2
          if(count==5'd16)ACK2 <= 1'b1;
          else            ACK2 <= 1'b0;
          //ACK3
          if(count==5'd25)ACK3 <= 1'b1;
          else            ACK3 <= 1'b0;
          SDO       <= 1'b1;//don't care(data[26])
          countEN   <= 1'b1;//counting
          //rstcount
          if(!R_W)begin//write-->18bit
            if(count==5'd16)rstcount  <= 1'b1;
            else            rstcount  <= 1'b0;
          end
          else begin//read-->27bit
            if(count==5'd25)rstcount  <= 1'b1;
            else            rstcount  <= 1'b0;
          end
          rstACK    <= 1'b0;
        end
        STOP:begin
          SCLK_temp <= 1'b0;//stop the clock
          LDEN      <= 1'b0;
          SHEN      <= 1'b0;
          ACK1      <= 1'b0;
          ACK2      <= 1'b0;
          ACK3      <= 1'b0;
          SDO       <= 1'b0;//low
          countEN   <= 1'b0;
          rstcount  <= 1'b0;
          rstACK    <= 1'b0;
        end
        FINAL:begin
          SCLK_temp <= 1'b1;//high
          LDEN      <= 1'b0;
          SHEN      <= 1'b0;
          ACK1      <= 1'b0;
          ACK2      <= 1'b0;
          ACK3      <= 1'b0;
          SDO       <= 1'b0;//low
          countEN   <= 1'b0;
          rstcount  <= 1'b0;
          rstACK    <= 1'b0;
        end
        END:begin
          SCLK_temp <= 1'b1;//high
          LDEN      <= 1'b0;
          SHEN      <= 1'b0;
          ACK1      <= 1'b0;
          ACK2      <= 1'b0;
          ACK3      <= 1'b0;
          SDO       <= 1'b1;//raising
          countEN   <= 1'b0;
          rstcount  <= 1'b0;
          rstACK    <= 1'b1;//reset ACK
        end
      endcase
    end
    else begin
      SCLK_temp <= SCLK_temp;
      LDEN      <= LDEN;
      SHEN      <= SHEN;
      ACK1      <= ACK1;
      ACK2      <= ACK2;
      ACK3      <= ACK3;
      SDO       <= SDO;
      countEN   <= countEN;
      rstcount  <= rstcount;
      rstACK    <= rstACK;
    end
  end
end

endmodule

这次的 I2C Master 看起来虽然好像复杂很多,但其实与前面几篇的 Uart 及 SPI 都有很大的相似之处欧~~

下一篇我们会继续来完成 I2C Master 的模组~!


<<:  【day22】FCM云端通讯测试

>>:  Swift纯Code之旅 Day27. 「画面最後的温柔 - 大胎头」

(後记) 突破一般视讯会议限制的视讯设备

当疫情不止,行销活动还是要想办法办下去,而举办视讯会议已经不能像以往只在一般的会议室,各种的场地都要...

Day 32 (Jq-UI)

JQ-UI使用步骤-1: (1)API Documentation > (左侧) Intera...

Day 21 : pillow套件,处理照片

在python中,有许多可以拿来处理影像的套件。今天就先稍微介绍一下,pillow这个影像处理套件。...

DAY11 - 前端网页怎麽处理档案?

前言 前几天我们聊的是串接API,在前端功能开发里,遇到的第二个主题就是档案处理。 档案处理也是在网...

Day 12 - 三朵云的入门(云端基础证照)

图片来源 接下来继续谈到Q2的目标是Azure Certificate, 云端相关证照, 因为我非...