【Day24】I2C Master 的实现及验证(最终章)

今天,我们要来完成整个 I2C 的最後一个部份了!
先来看看这个 I2C Master write 模块该有哪些输入输出脚吧:

输入:

  • clk_sys
  • rst_n
  • en(外部给此模组的驱动信号(enable))
  • addr(8 bit)
  • data_i(8 bit)

(如果是 write 模式,那前 8 bit 传 address,而後 8 bit 传 data_i)

输出:

  • data_o(16 bit)
  • SCLK

(如果是 read 模式,那前 8 bit 传 address,而後面都传 "1" (高阻抗),因为要接收 data 所以断开,由 slave 去给信号)

输入+输出:

  • SDA
module usage_I2C_write_R_W(
  en, 
  clk_sys, 
  rst_n, 
  addr, 
  data_i, 
  SCLK, 
  SDA, 
  data_o
);
/*------ports declaration------*/
input         clk_sys;
input         rst_n;
input         en;
input   [7:0] addr;
input   [7:0] data_i;
inout         SDA;
output        SCLK;
output [15:0] data_o;
reg    [15:0] data_o;
wire          SCLK;

再来,因为我打算收资料也做个状态机,先来定义三个状态:

/*------parameter------*/
parameter IDLE  = 2'd0;
parameter SHIFT = 2'd1;
parameter STOP  = 2'd2;

宣告变数:

  • data_temp(蒐集 read data,於状态为 STOP 时输出给 data_o)
  • fstate(状态机变数)
  • count(计数 16 bit 的 data 的 counter)
  • 其他与状态机模组连接的变数

引用上次写好的模组

/*------module I2C_write instantiate------*/
I2C_write_R_W U0(
  .clk_sys(clk_sys),
  .rst_n(rst_n),
  .en(en), 
  .data_R({addr, 1'b1, 8'hff, 1'b0, 8'hff, 1'b1}),//27bit
  .data_W({addr, 1'b1, data_i, 1'b1}),//18bit
  .ACK(ACK),
  .ACK1(),
  .ACK2(),
  .ACK3(), 
  .rstACK(rstACK),
  .SCLK(SCLK),
  .SDA(SDA),
  .ldnACK1(ldnACK1), 
  .ldnACK2(ldnACK2),
  .ldnACK3(ldnACK3),
  .R_W(addr[0]),
  .tick_I2C_neg(tick_I2C_neg)
);

状态逻辑:

  • 重置时要能回到 IDLE 状态。
  • IDLE( ldnACK1 && (addr[0]) && tick_I2C_neg 讯号後开始到下一个状态)(代表第九 bit 以及目前是 read 模式并且在讯号正中间开始)
  • SHIFT(蒐集完 16 bit 资料後结束)
  • STOP(回到 IDLE)
/*------fstate state------*/
always@(posedge clk_sys or negedge rst_n)begin
  if(!rst_n)fstate <= IDLE;
  else begin
    case(fstate)
      IDLE:begin
        if(ldnACK1&&(addr[0])&&tick_I2C_neg)fstate <= SHIFT;
        else                                fstate <= IDLE;
      end
      SHIFT:begin
        if(count==4'd15&&tick_I2C_neg)fstate <= STOP;
        else                          fstate <= SHIFT;
      end
      STOP:begin
        fstate <= IDLE;
      end
      default:begin
        fstate <= IDLE;
      end
    endcase
  end
end

输出逻辑:

  • IDLE(变数都为 0)
  • SHIFT( !ldnACK2&&tick_I2C_neg 此条件成立才 count 加一以及存入资料)(第 18 bit 资料不收,因为是 ACK,而 tick_I2C_neg 则是为了在资料正中间取样,确保资料正确)
  • STOP(将蒐集完的 16 bit 资料输出给 data_o,并将 count 归零)
/*------fstate output------*/
always@(posedge clk_sys or negedge rst_n)begin
  if(!rst_n)begin
    count     <= 4'd0;
    data_temp <= 16'd0;
    data_o    <= 16'd0;
  end
  else begin
    case(fstate)
      IDLE:begin
        count     <= 4'd0;
        data_temp <= data_temp;
      end
      SHIFT:begin
        if(!ldnACK2&&tick_I2C_neg)begin
          count     <= count + 4'd1;
          data_temp <= {data_temp[14:0], SDA};
        end 
        else begin
          count     <= count;
          data_temp <= data_temp;
        end
      end
      STOP:begin
        count  <= 4'd0;
        data_o <= data_temp;
      end
      default:begin
        count     <= 4'dx;
        data_temp <= 16'hxx;
        data_o    <= 16'hxx;
      end
    endcase
  end
end

endmodule

这样就结束了我们的 I2C 整个模块

再来是 TestBench 的部分~~~~


TestBench

`timescale 1ns / 10ps

module i2c_tb();

localparam durTime = 5*1000;
reg sysClk, rst_n, tick_tx, sda_i;
reg [7:0]addr_i, data_i;
wire [15:0]data_o;
wire SCL;
wire SDA;
integer i;
assign SDA = (sda_i==1'b1)?1'bz:1'b0;
pullup(SDA);

usage_I2C_write_R_W UUT(
  .en(tick_tx), 
  .clk_sys(sysClk), 
  .rst_n(rst_n), 
  .addr(addr_i),
  .data_i(data_i),
  .SCLK(SCL), 
  .SDA(SDA),
  .data_o(data_o)
);

always@(negedge SCL, negedge rst_n)begin
  if(!rst_n)sda_i <= 1'b1;
  else if(i==8||i==17||i==27||i==28||i==36||i==35||i==43)#2000 sda_i <= 1'b0;//<27 for write
  else#2000 sda_i <= 1'b1;
end

always@(posedge SCL, negedge rst_n)begin
  if(!rst_n)i <= 0;
  else i <= i + 1;
end

always #10 sysClk = ~sysClk;

initial begin
  sysClk  = 0;
  rst_n   = 1;
  tick_tx = 0;
  sda_i   = 1'b1;
  addr_i  = 8'hA6;
  data_i  = 8'h81;
  #50 rst_n   = 0;
  #50 rst_n   = 1;

  #10 tick_tx = 1;
  #10 tick_tx = 0;

  repeat(50) #durTime;

  addr_i  = 8'hA7;
  #10 tick_tx = 1;
  #10 tick_tx = 0;

  repeat(100) #durTime;
  
  $stop;

end

endmodule 

值得注意的是 Verilog 本身就有 "pullup" 语法可以模拟上拉电阻的效果

Wave

可以看到上半部的部分:

前半部是 write 模式,先传送 addr_i 的 10100110(最後一 bit 是 "0" ,所以是 write)再来是 ACK,再接着 data_i 的 10000001,最後也是 1 bit 的 ACK。

後半部是 read 模式,先传送 addr_i 的 10100111(最後一 bit 是 "1" ,所以是 read ),再来是 ACK,接着的 16 bit 是由 tb 发送的 01111110 和 11111101,而最後再搭配一个 master 端的 non-ACK 完成通讯。

而由此波形也可以看见 SDA 确实有在 SCLK 为 LOW 时才改变值。

注:虚线是因为 pullup 的关系,所以 HIGH 才变成虚线

而这里可以看到 tick_I2C_neg(红色那条)位於 SDA 的正中间,以此来取样 SDA 信号可以确保值的正确性~~~~

来看看 State Machine Viewer 是否如预期

状态确实都有照着我们的想法去跑呢~

那麽 I2C 的教学也到这边结束喽~~~~


<<:  Day26:救世主

>>:  视觉化KBARS(2)-python api

【JavaScript】阵列方法之filter()

【前言】 本系列为个人前端学习之路的学习笔记,在过往的学习过程中累积了很多笔记,如今想藉着IT邦帮忙...

第二十天:Gradle task graph

Gradle 的其中一个强大特点,就是它了解任务间的相依性,可以在核心建立出图或树。这对於开发者来说...

NIST通用风险模型(The NIST Generic Risk Model)

-具有关键风险因素的通用风险模型(NIST SP 800-30 R1) 关键风险因素(Key Ri...

django新手村14-----添加资料

之前在添加资料时,都是手动去资料库添加,这样很不合理,也没有效率 如果只是为了方便,当然可以使用以下...

Proxmox VE 资料中心选项实用设定

前面我们介绍了非常多关於节点、客体机的相关设定与管理,在铁人赛的最後一篇,我们回头来看最上层的资料...