【Day23】I2C Master(Write)的实现

上一篇我们设计了 I2C Master 的状态机,那麽我们今天要来引用上次完成的状态机模块来实现 I2C Master 模块,但我们先一步一步来,先来完成 write 的部分,也就是先可以把输入的值成功以 I2C 协定传出去,再来完成读入的部分

先来看看这个 I2C Master write 模块该有哪些输入输出脚吧:

输入:

  • clk_sys
  • rst_n
  • en(外部给此模组的驱动信号(enable))
  • data_R(27 bit)
  • data_W(18 bit)
  • R_W(Read/Write)

输出:

  • ACK1, ACK2, ACK3(BUS 上的 ACK 接收後输出)
  • ACK(ACK = ACK1|ACK2|ACK3)
  • rstACK(连接状态机模组的脚)
  • SCLK
  • ldnACK1, ldnACK2, ldnACK3(连接状态机模组 ACK1, ACK2, ACK3 的脚 )
  • tick_I2C_neg(由於我读资料又会包在外面的模块,这个讯号是要传到外面并要在此讯号为"1"时取样资料)

输入+输出:

  • SDA
module I2C_write_R_W(
  clk_sys, 
  rst_n, 
  en,
  data_R,
  data_W,
  ACK, 
  ACK1,
  ACK2,
  ACK3,
  rstACK, 
  SCLK,
  SDA, 
  ldnACK1,
  ldnACK2, 
  ldnACK3,
  R_W, 
  tick_I2C_neg
);
/*---------ports declaration---------*/
input        clk_sys;
input        rst_n; 
input        en;
input        R_W;
input [26:0] data_R;//read  27bit
input [17:0] data_W;//write 18bit
output       ACK1;
output       ACK2;
output       ACK3;
output       tick_I2C_neg;
output       ACK; 
output       rstACK;
output       SCLK;
output       ldnACK1;
output       ldnACK2;
output       ldnACK3;
reg          tick_I2C_neg;    
reg          ACK1;
reg          ACK2;
reg          ACK3;
wire         ACK; 
wire         rstACK;
wire         SCLK;
wire         ldnACK1;
wire         ldnACK2;
wire         ldnACK3;
inout        SDA;

宣告变数:
需要用到的变数有:

  • count(计数 18 or 27 个 bit 用)
  • cnt_I2C(计数用(0~499),要做 tick_I2C)
  • regdata_R(load 进来的资料先暂存在这里(for Read))
  • regdata_W(load 进来的资料先暂存在这里(for Write))
  • 要做tick_I2C(100KHZ 的 tick)
  • 其他与状态机模组连接的变数
  • SEL(SDA 的暂存值(而 SDA 又是由 regdata 的 LSB 以及 SD0 做切换的),再多一层是因为,SDA 实际输出只会有高阻抗以及低电位,因此再多用一条线去用多工器选)
/*---------variables---------*/
reg  [4:0] count;
reg  [8:0] cnt_I2C;
reg        tick_I2C;
reg        SCLK_100k;
reg [26:0] regdata_R;
reg [17:0] regdata_W;
wire       SCLK_temp;
wire       SHEN;
wire       LDEN;
wire       SDO;
wire       countEN;
wire       rstcount;
wire       SEL;
/*---------assign wire---------*/
assign SEL = (SHEN)?((!R_W)?(regdata_W[17]):(regdata_R[26])):(SDO);
assign SDA = (SEL)?(1'bz):(1'b0);
assign ACK = ACK1|ACK2|ACK3;

引用状态机模组

/*---------module instantiate---------*/
I2C_control_R_W U0(
  .clk_sys(clk_sys),
  .SCLK_100k(SCLK_100k),
  .tick_I2C(tick_I2C),
  .rst_n(rst_n),
  .en(en),
  .count(count),
  .countEN(countEN),
  .rstcount(rstcount),
  .ACK1(ldnACK1),
  .ACK2(ldnACK2),
  .ACK3(ldnACK3),
  .rstACK(rstACK),
  .SCLK(SCLK),
  .SCLK_temp(SCLK_temp),
  .SHEN(SHEN),
  .LDEN(LDEN),
  .SDO(SDO),
  .R_W(R_W)
);

cnt_I2C

/*---------100k counter---------*/
always@(posedge clk_sys or negedge rst_n)begin
  if(!rst_n)cnt_I2C <= 9'd0;
  else begin
    if(cnt_I2C<9'd499)cnt_I2C <= cnt_I2C + 9'd1;//0-499
    else              cnt_I2C <= 9'd0;
  end
end

tick_I2C

/*---------I2C tick---------*/
always@(posedge clk_sys or negedge rst_n)begin
  if(!rst_n)tick_I2C <= 1'b0;
  else begin
    if(cnt_I2C==9'd498)tick_I2C <= 1'b1;
    else               tick_I2C <= 1'b0;
  end
end
/*---------I2C tick_neg---------*/
always@(posedge clk_sys or negedge rst_n)begin
  if(!rst_n)tick_I2C_neg <= 1'b0;
  else begin
    if(cnt_I2C==9'd249)tick_I2C_neg <= 1'b1;
    else               tick_I2C_neg <= 1'b0;
  end
end

SCLK_100k

/*---------SCLK_100k---------*/
always@(posedge clk_sys or negedge rst_n)begin
  if(!rst_n)SCLK_100k <= 1'b0;
  else begin
    if(cnt_I2C==9'd100)     SCLK_100k <= 1'b0;
    else if(cnt_I2C==9'd400)SCLK_100k <= 1'b1;
    else                    SCLK_100k <= SCLK_100k;
  end
end

这里来解释一下为什麽是 100 跟 400:

还记得前几天的 I2C 的介绍内有提到,SDA 只能在 SCLK 为 "0" 时更动值,因此我们的 SCLK 高电位的部分要抓短一点,所以才会出现 100 时下去 400 上来的情况(咦?这样不是反过来吗?因为我状态机模组里面有反向了,你们也可以 100 上去 400 下来,然後不要反向,当然你想用其他的高电位宽度也都可以!),而 SDA 只会在 cnt_I2C 等於 498 时才更动值,如此一来就可以达到 SDA 只会在 SCLK 等於 "0" 时才更动值的效果!

藉由 rstcount&countEN 来控制 count

/*---------count---------*/
always@(posedge clk_sys or negedge rst_n)begin
  if(!rst_n)begin
    count <= 5'd0;
  end
  else begin
    if(tick_I2C)begin
      if(rstcount)    count <= 5'd0;
      else if(countEN)count <= count + 5'd1;
      else            count <= count;
    end
    else count <= count;
  end
end

藉由 LDEN&SHEN&R_W 来控制我们的 regdata_W&regdata_R

/*---------load data---------*/
always@(posedge clk_sys or negedge rst_n)begin
  if(!rst_n)begin
    regdata_R <= 27'd0;
    regdata_W <= 18'd0;
  end
  else begin
    if(!R_W)begin
      if(tick_I2C)begin
        if(LDEN)     regdata_W <= data_W;
        else if(SHEN)regdata_W <= {regdata_W[16:0],1'b0};
        else         regdata_W <= regdata_W;
      end
      else regdata_W <= regdata_W;
    end 
    else begin
      if(tick_I2C)begin
        if(LDEN)     regdata_R <= data_R;
        else if(SHEN)regdata_R <= {regdata_R[25:0],1'b0};
        else         regdata_R <= regdata_R;
      end
      else regdata_R <= regdata_R;
    end
  end
end

读取 BUS 上的 ACK

/*---------ACK---------*/
always@(posedge clk_sys or negedge rst_n)begin
  if(!rst_n)begin
    ACK1 <= 1'b0;
    ACK2 <= 1'b0;
    ACK3 <= 1'b0;
  end
  else if(rstACK&&tick_I2C)begin
    ACK1 <= 1'b0;
    ACK2 <= 1'b0;
    ACK3 <= 1'b0;
  end
  else begin
    if(ldnACK1&&tick_I2C)ACK1 <= SDA;
    else                 ACK1 <= ACK1;
    if(ldnACK2&&tick_I2C)ACK2 <= SDA;
    else                 ACK2 <= ACK2;
    if(ldnACK3&&tick_I2C)ACK3 <= SDA;
    else                 ACK3 <= ACK3;
  end
end

endmodule

由於这篇的内容也算偏多,可以先让你们消化一下,下一篇我们会继续完成 Read 的部分,并且撰写 TestBench 来验证电路的正确性~~


<<:  Day23 - this&Object Prototypes Ch3 Objects - Review

>>:  Day 23 来验证一下路由吧

【Day 14】jQuery基本语法

jQuery的基本语法: 1.前面都会有美元 $ 的符号 2. $(CSS选择器).执行的动作() ...

【Day 11】分类(Classification)(下)

昨天提到了生成模型(Generative Model),要去计算事前机率(Prior Probabi...

一些事件

在操作元件那天,已经大略谈到在Activity 上架上元件就像在写Windows Form 上的元件...

[Part 8 ] Vue.js 的精随- 自订事件

前言 $emit 让我们可以发送出自订的事件,例如: 触发特定的事件(关闭 popup) 或是 子元...

Day13: HTTP服务器

哈罗~~大家好!!星期五好愉快~~马上进入主题吧!! 今天要介绍的是使用NodeJs建立後端的服务器...