Opencores上的i2c controller core代码解析

本文深入解析Opencores上的I2C控制器代码,包括状态机设计、比特分类、时钟同步、SCL与SDA控制及信号滤波等关键技术。探讨了起始比特、停止比特、写比特与读比特的传输过程,以及如何通过状态转移图实现SCL的精确控制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、i2c_eeprom_test.v

//TO DO…

二、i2c_master_top.v

i2c_master_top.v文件比较简单,写了一个三段式状态机。状态转移图如下:
//TO DO…

三、i2c_master_byte_ctrl.v

//TO DO…

四、i2c_master_bit_ctrl.v

4.1 四类比特

  作者把传输过程中的比特分为四类:第一类是起始比特,第二类是停止比特,第三类是写比特,第四类是读比特。每一类比特都包含了SCL和SDA。按照这样分类的话,一次写寄存器的过程就是:
起始比特–写比特7–…--写比特0-读比特-写比特7–…--写比特0–读比特–写比特7–…--写比特0–读比特–停止比特。
  其中,第一串写比特是写器件地址,第二串写比特是写寄存器地址,第三串写比特是写8位寄存器的值,读比特是读从机的ACK信号。

4.2 比特时钟和SCL

  对于每一类比特,作者将它们的长度分为5个时钟周期,每个时钟周期内对SCL和SDA进行变化,也就是说作者为每一类比特设置了5个状态,时钟信号是状态转移的条件。而且这四类比特共用一个时钟。其中SDA的变化取决于输入信号din,也就是上层i2c_master_byte_ctrl.v的移位寄存器的最高位输入。
  假设晶振为50MHz,I2C的时钟SCL是400kHz,一个数据比特长度为一个SCL周期,也就是2.5us=2500ns(这样说并不准确,因为I2C中是SCL高电平时SDA保持稳定,SCL为低电平时SDA进行变化,并没有“传输一个数据比特需要多少个时钟周期”的说法。UART这种异步通信才有。但这里为了方便理解,先这么描述)。那么每一类比特的时间长度就是25000/5=500ns(2MHz),也就是说我需要将50MHz的系统时钟分频为2MHz的时钟,这个时钟姑且称为比特时钟,在i2c_master_bit_ctrl.v中对应变量clk_en。比特时钟是SCL的5倍,所以作者在i2c_master_top.v的模块端口声明中说明了分频系数的计算是(50,000,000/(5xSCL))-1。
  每一类比特包含SCL和SDA,SCL有两个周期为高电平,三个周期为低电平,所以这样得到的SCL的正负占空比是2:3。换个说法,作者把SCL的每个周期分为5份,通过每类比特中的状态转移来产生SCL的高低电平。这就实现了SCL的波形产生。

4.3 比特时钟clk_en的产生

  clk_en并不是简单的分频,代码如下。可以看到clk_en的生成包含两部分,一部分是拉高,另一部分是拉低。先说拉高的部分。rst和ena是输入信号,rst为0,ena为1。假设scl_sync为0,那么当cnt计数到0时,条件判断为真,clk_en拉高,cnt重新置数。再说拉低部分。两种情况下clk_en会被拉低,第一种情况是当从机还没准备好的时候,从机可以拉低SCL,让主机继续等待,直到从机释放SCL,这时slave_wait为1。另一种情况就是cnt递减的过程中clk_en保持为低。通过分析可以看出,clk_en只有一个系统时钟周期的时间为高电平,其余时间均为低电平。

always @(posedge clk or negedge nReset)
      if (~nReset)
      begin
          cnt    <= #1 16'h0;
          clk_en <= #1 1'b1;
      end
      else if (rst || ~|cnt || !ena || scl_sync)
      begin
          cnt    <= #1 clk_cnt;
          clk_en <= #1 1'b1;
      end
      else if (slave_wait)
      begin
          cnt    <= #1 cnt;
          clk_en <= #1 1'b0;    
      end
      else
      begin
          cnt    <= #1 cnt - 16'h1;
          clk_en <= #1 1'b0;
      end

4.4 SCL和SDA的高低控制

  作者在i2c_master_bit_ctrl.v中将输出scl_o和sda_o都接地了,将信号scl_oen和sda_oen作为选择信号提供给最顶层i2c_eeprom_test.v,对顶层最终的scl和sda输出进行控制。以时钟线为例,当scl_oen为高时,顶层的scl输出高阻态,由于实际硬件电路中时钟线通过上拉电阻接到电源端,所以顶层的scl会被拉高,也就是高电平。所以作者并不是通过给scl赋值0和1来产生最终的SCL,而是通过数据选择的方式,要么为0,要么为高阻(然后被上拉为1)来产生SCL的。这一点很特别。
在这里插入图片描述

4.5 该.v文件剩下的部分

  我觉得i2c_master_bit_ctrl.v可以分成两部分。第一部分就是前面四点提到的SCL的产生和SDA的变化。第二部分就是一组信号(cSCL/fSCL/sSCL/dSCL和cSDA/fSDA/sSDA/dSDA)及相应的功能。这部分我尝试用modelsim仿真观察波形,但是因为没有实际电路的上拉电阻的模型以及从机的响应,所有的高阻态都只能是高阻态,进而导致了诸多信号为不确定态,无法具体分析。所以我暂时把我理解的记录一下。
  首先是scl_i和sda_i信号,其实就是把顶层的SCL和SDA接进来,是相同的信号。然后是cSCL/cSDA和fSCL和fSDA。以cSCL和fSCL为例进行分析。它们构成的是一个五位的移位寄存器,如图
在这里插入图片描述
  其中clk可以看作125xSCL,clk2是125/7xSCL(具体的计算要看clk2的产生。clk2是clk的分频,分频系数是clk_en的分频系数减一后除以4,再加一得到的。以SCL=400kHz为例,clk_en的分频系数是50,000/(5x400)=25,频率是2MHz,那么clk2的分频系数就是(25-1)/4+1=6+1=7,所以频率是50/7MHz,约等于7.1MHz),或者说clk2是clk的7分频。
  假设t时刻(以ns为单位)clk和clk2的上升沿同时到达,则cSCL[0]存储的是t时刻的SCL电平,cSCL[1]存储的是t-20时刻的SCL电平,fSCL[0]存储t-2x20时刻的SCL电平,fSCL[1]存储t-2x20-7x20=t-9x20时刻的SCL电平,fSCL[2]存储t-2x20-2x7x20=t-16x20时刻的SCL电平。事实上clk2从clk分频出来并没有相移,所以理论上clk2的上升沿跟clk的上升沿是同时到达的。

cSCL[0]	t			ns
cSCL[1]	t-20		ns
fSCL[0]	t-40		ns
fSCL[1]	t-180		ns
fSCL[2]	t-320		ns

  通过以上分析可以知道,cSCL[1:0]和fSCL[2:0]相当于一个采样窗口,在这个窗口里对SCL进行间隔为140、140、20、20共5次采样,窗口每次向右(时间增大的方向)移动20。这个东西作者叫做滤波,怎么个滤波法呢

 // generate filtered SCL and SDA signals
 always @(posedge clk or negedge nReset)
   if (~nReset)
   begin
       sSCL <= #1 1'b1;
       sSDA <= #1 1'b1;

       dSCL <= #1 1'b1;
       dSDA <= #1 1'b1;
   end
   else if (rst)
   begin
       sSCL <= #1 1'b1;
       sSDA <= #1 1'b1;

       dSCL <= #1 1'b1;
       dSDA <= #1 1'b1;
   end
   else
   begin
       sSCL <= #1 &fSCL[2:1] | &fSCL[1:0] | (fSCL[2] & fSCL[0]);
       sSDA <= #1 &fSDA[2:1] | &fSDA[1:0] | (fSDA[2] & fSDA[0]);

       dSCL <= #1 sSCL;
       dSDA <= #1 sSDA;
   end

  sSCL是滤波后的信号。可以看到sSCL只取决于fSCL,fSCL总共3个位,有8个组合,只有当fSCL有两个1时sSCL才为1。而且它比较的顺序在写法上似乎有讲究(从功能上来讲无差别),因为fSCL三个比特有时间顺序之分,作者先比较fSCL[2]和fSCL[1],也就是320ns前的SCL和180ns前的SCL,如果它们为1,就说明当前的SCL为高,那么令sSCL为高;否则,比较fSCL[1]和fSCL[0],如果它们为1,说明当前时刻的SCL为高;再否则,比较SCL[2]和SCL[0],也就是320ns和40ns前的SCL,如果它们为1,则当前时刻的SCL为高。如果前面两个判断都为假,而最后一个判断为真,那么说明SCL依次出现了 高低高(101) 的情况,可以认为0是受噪声扰动导致的。

fSCL[2]		fSCL[1]			fSCL[0]			sSCL
0				0				0			0
0				0				1			0
0				1				0			0
0				1				1			1
1				0				0			0
1				0				1			1
1				1				0			1
1				1				1			1

  但是我比较疑惑的是 001 和 110 这两组。001说明320ns前、180ns前的SCL为低,40ns前的SCL为高,那么为什么认为此时的SCL为低而不为高呢?同理 110 的情况下为什么认为SCL是高非低呢?这是我一直想不通的地方。作者把这个叫做滤波,我又完全匹配不上计算机控制系统课程中学习的任何离散时间域的滤波算法,迷惑。
  通过上面的分析可以知道滤波后得到的sSCL相比实际的SCL会有延迟,不过因为这一路下来我们是以SCL为400kHz来分析,也就是时钟周期为2500ns,这些延迟带来的误差应该是在允许范围内的。
  dSCL就没有理解难度了,就是上一个滤波后的SCL的电平。
  作者用sSCL和dSCL来干嘛呢?第一个是用来判断起始沿和停止沿。一开始我没懂这个判断有什么意义:起始和结束信号是自己产生的,自己还反过来去检测什么时候起始和结束。但后来想了一下,起始和停止信号被当作起始比特和停止比特划分成了5个状态来实现,当处在这5个状态中的任何一个状态时,是无法对沿进行判断的(SCL和SDA只有00/01/10/11四种状态,无法检测“SCL为高时SDA由高变低”),所以作者才需要在这个状态机之外又重新进行判断,而且还是经过滤波后才判断的。得到起始沿和停止沿就可以对外输出busy信号。
  剩下的还有一些信号我不理解,比如scl_sync信号,al信号和sda_chk信号。

五、其他

//TO DO…

最近一个项目需要做I2C的slave,在opencores.org上面找到了一个I2C代码,不过是master的。 下载来看看,发现里面有一个I2C slave的行为级代码。 于是自己根据这个代码改写了一个I2C slave RTL的代码,并修改了原来那个设计的testbench,将rtl的Slave替换了原来的behavior的Slave,在modelsim里面作了前仿,完全通过。还有一个myram.v文件,是一个register file,和slave相连,存储数据用的。 用synplify做综合,使用x3s400-4的器件,占用LUT<100,速度接近200MHz。性能比较优化。 代码做了详尽的注释,语言采用verilog,并且写了仿真的脚本。解压了直接运行simbehav.bat就可以了。如果modelsim安装的时候注册了环境变量(path),脚本调用modelsim,输入run -all即可看到仿真结果。 虽然不是很复杂,不过对于广大需要做I2C的RTL slave的工程师来说,还是很有参考价值的。 1、 设计流程 将I2C slave的行为模型改为rtl模型。 进行等效仿真,直到波形一致,通过timing check,数据正确。 再进行rtl优化设计 2、 注意要点 a) 时钟的设计 b) 对于restart condition的时序是否正确 c) 3、 进度 a) 11-12:initial状态的bitcnt不对,需要认真比对/设计 b) 11-13:initial基本解决(sda_in的问题)。Sda三态冲突,原因不明。比对原设计 c) 11-14:sda三态冲突解决,原因为sda在初始化时没有将sda_oen赋值(由sm赋值,但是sm没有做async reset)。同时注意verilog的大小写敏感。 d) 11-15:仿真出现错误:read出来的数据非期望值。写入逻辑完全正确。Read时由于sda_oe在sm中有一个cycle_pulse的延迟,导致了mem_do[7]串行移出时错位。在更改了sm的代码风格后再研究解决方法。 e) 11-16:仿真完全匹配波形。计划:优化结构,提高稳定性sm改为每个时钟打一下。 关于I2C的SDA三态转换: Master在发送完第8个bit后随后将sda释放(posedge后大概1/4 scl周期),此时slave需要在第九个bit对应的scl的posedge拉低sda。
评论 18
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值