笔者使用的是黑金AX309的开发板,其板载了一块有一个 IIC 接口的 EEPROM 芯片 24LC04(data sheet),容量大小为 4Kbit。这里先简单总结下一般的存储器件,然后介绍IIC协议及其读写时序,最后板机验证。这里最后实现给EEPROM中写入数据,然后再讲写入的数据显示在数码管验证其正确性。本文主要介绍IIC协议,至于芯片的种类不影响协议本身以及相关问题的理解。(参考资料会在文末给出出处或在文中以超链接的形式给出)

基于FPGA的EEPRM读写(IIIC 接口协议)

  • 一,相关存储器件介绍
    • 1、ram
    • 2、rom
    • 3、prom
    • 4、eprom
    • 5、eeprom
  • 二,IIC通信协议介绍
    • 1,I2C(IIC) 基本概念
    • 2,I2C(IIC) 基本时序
  • 三,读写时序详解
    • 1,主机通过IIC总线往从机里面写数据
    • 2,主机通过IIC总线从从机里面读数据
  • 四,代码分析与仿真
    • 1,准备工作 SCL信号的产生
    • 2,准备工作 SCL信号的低电平正中间标志位和高电平正中间标志位产生
    • 3,代码分享
    • 4,代码仿真
    • 5,IIC写仿真分析(读仿真分析略)
    • 6,板机验证
  • 五,参考博文资料
  • 六,留坑(以后必填)

一,相关存储器件介绍

ROM Read Only Memory,意思是只读存储器,其中内容是固定的,无法更bai改,失电不丢失数据,比如光盘就是ROM的一种RAM RamdomAccessMemory随机存取存储器,读写速度快,但是无法保存信息,失电数据即丢失,比如计算机当中的内存就是EEPROM Electrically Erasable Programmable Read-Only Memory,这是一种可擦写可编程的只读存储器,失电数据不丢失,可以在电脑或专用设备上进行擦除信息并重新编程的存储器,现在计算机的主板上的BIOS就是EEPROM

1、ram

指的是“随机存取存储器”,即random access memory。它可以随时读写,而且速度很快,缺点是断电后信息丢失。

2、rom

指的是“只读存储器”,即read-only memory,不能进行修改。

3、prom

指的是“可编程只读存储器”既programmable red-only memory。这样的产品只允许写入一次。

4、eprom

指的是“可擦写可编程只读存储器”,即erasable programmable read-only memory。 它的特点是具有可擦除功能,擦除后即可进行再编程,但是缺点是擦除需要使用紫外线照射一定的时间。

5、eeprom

指的是“电可擦除可编程只读存储器”,即electrically erasable programmable read-only memory。它的最大优点是可直接用电信号擦除,也可用电信号写入。

二,IIC通信协议介绍

通信协议简单来讲其实就是一种形成规定的用来传输数据的方式,一般分为单工,半双工以及双工。可以参考一篇博文。简单来讲就是数据的允许传输方式,这里包括数据的传输方向以及允许的传输时间。接下来介绍IIC的简单背景与时序要求,最后简述下笔者的理解。

1,I2C(IIC) 基本概念

I2C 总线(I2C bus,Inter-IC bus)是一个双向的两线连续总线,提供集成电路(ICs)之间的通信线路。I2C 总线是一种串行扩展技术,最早由 Philips 公司推出,广泛应用于电视,录像机和音频设备。I2C 总线的意思是“完成集成电路或功能单元之间信息交换的规范或协议”。Philips 公司推出的 I2C 总线采用一条数据(SDA -串行数据线Serial data line),加一条时钟线(SCL-串行时间线Serial clock line)来完成数据的传输及外围器件的扩展。
本质上来讲任何一个设备都可以作为主机或者从机来对SDA以及SCL进行电压的控制达到控制数据线上其他设备的目的,此次实验我们选择FPGA芯片作为主机,EEPROM作为从机。
I2C总线数据传输速率在标准模式下可达100kbit/s,快速模式下可达400kbit/s,高速模式下可达 3.4Mbit/s。I2C 总线上的主设备与从设备之间以字节(8 位)为单位进行双向的数据传输。
这里有一个器件地址的概念:每个 I2C 器件都有一个器件地址,有的器件地址在出厂时地址就设置好了,
用户不可以更改(例如 OV7670 器件地址为固定的 0x42),有的确定了几位,剩下几位由硬件确定(比如常见的 I2C 接口的 EEPROM 存储器,留有 3 个控制地址的引脚,由用户自己在硬件设计时确定)。严格讲,主机不是直接向从机发送地址,而是主机往总线上发送地址,所有的从机都能接收到主机发出的地址,然后每个从机都将主机发出的地址与自己的地址比较,如果匹配上了,这个从机就会向总线发出一个响应信号。主机收到响应信号后,开始向总线上发送数据,与这个从机的通讯就建立起来了。如果主机
没有收到响应信号,则表示寻址失败。通常情况下,主从器件的角色是确定的,也就是说从机一直工作在从机模式。不同器件定义地址的方式是不同的,有的是软件定义,有的是硬件定义。例如某些单片机的 I2C 接口作为从机时,其器件地址是可以通过软件修改从机地址寄存器确定的。而对于一些其他器件,如 CMOS 图像传感器、EEPROM 存储器,其器件地址在出厂时就已经完全或部分设定好了,具体情况可以在对应器件的数据手册中查到。

2,I2C(IIC) 基本时序

I2C 协议整体时序说明如下:
总线空闲状态:SDA 为高电平,SCL 为高电平;
I2C 协议起始位:SCL 为高电平时,SDA 出现下降沿,产生一个起始位;
I2C 协议结束位:SCL 为高电平时,SDA 出现上升沿,产生一个结束位;

I2C 读写数据状态:在 I2C 总线上传送的每一位数据都有一个时钟脉冲相对应(或同步控制),即在 SCL 串行时钟的配合下,在 SDA 上逐位地串行传送每一位数据。进行数据传送时,在 SCL 呈现高电平期间,SDA 上的电平必须保持稳定,低电平为数据 0,高电平为数据 1。只有在 SCL 为低电平期间,才允许 SDA 上的电平改变状态。

数据应答位:I2C 总线上的所有数据都是以 8 位字节传送的,发送器(主机)每发送一个字节,就在第9个时钟脉冲期间释放数据线,由接收器(从机)反馈一个应答信号。应答信号为低电平时,规定为有效应答位(ACK简称应答位),表示接收器已经成功地接收了该字节;应答信号为高电平时,规定为非应答位(NACK),一般表示接收器接收该字节没有成功。对于反馈有效应答位 ACK 的要求是,接收器在第 9 个时钟脉冲之前的低电平期间将 SDA 线拉低,并且确保在该时钟的高电平期间为稳定的低电平。

三,读写时序详解

1,主机通过IIC总线往从机里面写数据

主机通过IIC总线往从机中写数据的时候,主机首先会发送一个起始信号,接着把IIC从机的7位设备地址后面添一个0(设备地址后面的0表示主机向从机写数据,1表示主机从从机中读数据)组成一个8位的数据,把这个8位的数据发给从机,发完这8位的数据以后主机马上释放SDA信号线等待从机的应答,如果从机正确收到这个数据,从机就会发送一个有效应答位0给主机告诉主机自己已经收到了数据,主机收到从机的有效应答位以后 ,接下来主机会发送想要写入的寄存器地址,寄存器发送完毕以后主机同样会释放SDA信号线等待从机的应答,从机如果正确收到了主机发过来的寄存器地址,从机会再次发送一个有效应答位给主机,主机收到从机的有效应答位0以后,接下来主机就会给从机发送想要写入从机的数据,从机正确收到这个数据以后仍然像之前两次一样会给主机发送一个有效应答位,主机收到这个有效应答位以后给从机发送一个停止信号,整个传输过程就结束了。下图是整个传输过程的示意图:
分别为单字节写与双地址字节写

2,主机通过IIC总线从从机里面读数据

主机通过IIC总线从从机中读数据的过程与写数据的过程有相似之处,但是读数据的过程还多了一些额外的步骤。主机从从机读数据时主机首先会发送一个起始信号,接着把IIC从机的7位设备地址后面添一个0(设备地址后面的0表示主机向从机写数据,1表示主机从从机中读数据),把这个8位的数据发给从机,发完这8位的数据以后主机马上释放SDA信号线等待从机的应答,如果从机正确收到这个数据,从机就会发送一个有效应答位0给主机告诉主机自己已经收到了数据,主机收到从机的有效应答位以后 ,接下来主机会发送想要读的寄存器地址,寄存器发送完毕以后主机同样会释放SDA信号线等待从机的应答,从机如果正确收到了主机发过来的寄存器地址,从机会再次发送一个有效应答位给主机,主机收到从机的有效应答位0以后,主机会给从机再次发送一次起始信号,接着把IIC从机的7位设备地址后面添一个1(设备地址后面的0表示主机向从机写数据,1表示主机从从机中读数据),注意,第一次是在设备地址后面添0,这一次是在设备地址后面添1,把这个8位的数据发给从机,发完这8位的数据以后主机马上释放SDA信号线等待从机的应答,如果从机正确收到这个数据,从机就会发送一个有效应答位0给主机告诉主机自己已经收到了数据,接着从机继续占用SDA信号线给主机发送寄存器中的数据,发送完毕以后,主机再次占用SDA信号线发送一个非应答信号1给从机,主机发送一个停止信号给从机结束整个读数据的过程。
读与写稍微有一点点区别就是需要再发送器件地址,读地址之后,需要再发一次器件地址。以及器件之后的0/1区别。笔者的理解是对于读而言是没有控制读的地址的,因此需要先写,然后不写入任何数据,将地址调整到需要读的地址,然后读数据,就会读出想要读的数据地址的数据
下图是整个读数据过程的示意图

四,代码分析与仿真

首先简单回顾下实验需求:向IIC地址中写入一个数据例如在地址0x01写入8’h55,然后再从改地址中读出该数据,显示在数码管上。

1,准备工作 SCL信号的产生

模块驱动时钟的分频系数的计算方法是先选定SCL的频率,为25KHz,然后得到一个clk_divide,使用它对SCL进行分频计算,其为4倍的SCL频率,因此为1MHZ,得到的这个1MHz的时钟。系统时钟为50MH(20ns)。因此计数值达到49即可。具体原因见下图仿真,下图是之前写的一个倍频电路的仿真。可见需要n倍的系统时钟,计数满翻转值就是n-1。

 always@(posedge clk or negedge rst_n)    begin  if(!rst_n)begincnt <= 0;   clk1 <= 0;endelse if(cnt== 'd9)//当cnt为9的时候,重新计数并且将clk1反转begincnt <= 0;    clk1 <= ~clk1;endelsecnt <= cnt + 1'b1;        end

2,准备工作 SCL信号的低电平正中间标志位和高电平正中间标志位产生

为了控制SDA信号,必须要产生SCL信号的低电平正中间标志位和高电平正中间标志位产生,这里使用如下的方法。

`timescale 1ps / 1ps
//-----------------------------------------------------------------------------------
// Copyright :This document is only for personal learning reference and research.
// Module:  time_test
// File:    time_test.v
// Author:  meng guodong
// E-mail:  823300630@qq.com
// Time :   2021-01-01 22:06:43
// Description: 时间分频,测试是否好用移位操作
//
//
//
//
// Revision: 1.0
//-------------------------------------------------------------------------------------------------
module time_test(//system singnalsinput                         clk             ,input                      rst_n           );reg     [9:0]   R_scl_cnt       ; // 用来产生IIC总线SCL时钟线的计数器
parameter   C_DIV_SELECT        =   10'd500 ; // 分频系数选择parameter   C_DIV_SELECT0       =   (C_DIV_SELECT >> 2)  -  1           , // 用来产生IIC总线SCL低电平最中间的标志位C_DIV_SELECT1       =   (C_DIV_SELECT >> 1)  -  1           ,C_DIV_SELECT2       =   (C_DIV_SELECT0 + C_DIV_SELECT1) + 1 , // 用来产生IIC总线SCL高电平最中间的标志位C_DIV_SELECT3       =   (C_DIV_SELECT >> 1)  +  1           ; // 用来产生IIC总线SCL下降沿标志位
always @(posedge clk or negedge rst_n)
beginif(!rst_n)R_scl_cnt   <=  10'd0 ; elsebeginif(R_scl_cnt == C_DIV_SELECT - 1'b1)R_scl_cnt <= 10'd0 ;elseR_scl_cnt <= R_scl_cnt + 1'b1 ;     end
endassign O_scl          = (R_scl_cnt <= C_DIV_SELECT1) ? 1'b1 : 1'b0 ; // 产生串行时钟信号O_scl
assign W_scl_low_mid  = (R_scl_cnt == C_DIV_SELECT2) ? 1'b1 : 1'b0 ; // 产生scl低电平正中间标志位
assign W_scl_high_mid = (R_scl_cnt == C_DIV_SELECT0) ? 1'b1 : 1'b0 ; // 产生scl高电平正中间标志位
endmodule


这里使用移位,移位操作其实等价于除2,这里简单记录:以3’b’100(为十进制的4)为例,右移一位变为2’b10(为十进制的2),左移一位变为4’b1000(为十进制的8)。

3,代码分享

这里测试方式是使用一个按键先写入单字节数据,然后再使用一个按键把该地址的数据读出来。具体工程以及详细的分析文档在公众号给出(关注公众号:果冻空间:回复 A-002-2 即可获得,里边有笔者自己做的状态跳转图,相关仿真资料,以及搜来的其他代码)这里只贴iic的驱动代码。
两个部分原因:1,公众号多点关注,2,博客分享在资源管理上确实不是很方便,笔者不太会用github(以后加油学)
在ACK1状态注意下一个状态的跳转,注意自己器件地址的位数自己选择跳转的状态。

`timescale 1ps / 1ps
//-----------------------------------------------------------------------------------
// Copyright :This document is only for personal learning reference and research.
// Module:  iic_dri
// File:    iic_dri.v
// Author:  meng guodong
// E-mail:  823300630@qq.com
// Time :   2021-01-02 14:39:39
// Description: 学习小梅哥IIC代码,参考基于芯航线的FPGA设计指导书
//
//
//
//
// Revision: 1.0
//-------------------------------------------------------------------------------------------------
module iic_dri (//system signalsinput                           clk50M          ,input                           reset           ,//IIC cintrol signalsinput                           iic_en          , //使能信号input           [2:0]           cs_bit          , //器件选择地址input           [12:0]          address         , //13 位数据读写地址,24LC64 有 13 位数据存储地址input                           write           , //写数据信号input           [7:0]           write_data      , //写数据input                           read            , //读数据信号output  reg     [7:0]           read_data       , //读数据output  reg                     scl             , //IIC 时钟信号inout                           sda             , //IIC 数据总线output  reg                     done              //一次 IIC 读写完成
);//========================================================================\
// =========== Define Parameter and Internal signals ===========
//========================================================================/
parameter   SYS_CLOCK = 50_000_000  ; //系统时钟采用 50MHz
parameter   SCL_CLOCK = 200_000     ; //scl 总线时钟采用 200kHz//状态
parameter   Idle        = 16'b0000_0000_0000_0001 ,Wr_start    = 16'b0000_0000_0000_0010 ,Wr_ctrl     = 16'b0000_0000_0000_0100 ,Ack1        = 16'b0000_0000_0000_1000 ,Wr_addr1    = 16'b0000_0000_0001_0000 ,Ack2        = 16'b0000_0000_0010_0000 ,Wr_addr2    = 16'b0000_0000_0100_0000 ,Ack3        = 16'b0000_0000_1000_0000 ,Wr_data     = 16'b0000_0001_0000_0000 ,Ack4        = 16'b0000_0010_0000_0000 ,Rd_start    = 16'b0000_0100_0000_0000 ,Rd_ctrl     = 16'b0000_1000_0000_0000 ,Ack5        = 16'b0001_0000_0000_0000 ,Rd_data     = 16'b0010_0000_0000_0000 ,Nack        = 16'b0100_0000_0000_0000 ,Stop        = 16'b1000_0000_0000_0000 ;//sda 数据总线控制位
reg sda_en ;
//sda 数据输出寄存器
reg sda_reg ;
assign  sda = sda_en ? sda_reg : 1'bz ;
//状态寄存器
reg [15:0] state ;
//读写数据标志位
reg W_flag ;
reg R_flag ;
//写数据到 sda 总线缓存器
reg [7:0]   sda_data_out ;
reg [7:0]   sda_data_in ;
reg [3:0]   bit_cnt ;
reg [7:0]   scl_cnt ;
parameter SCL_CNT_M = SYS_CLOCK/SCL_CLOCK ; //计数最大值
reg scl_cnt_state ;
//=============================================================================
//**************    Main Code   **************
//=============================================================================
//产生scl_cnt_state信号,为 1 表示 IIC 总线忙,为 0 表示总线闲,在iic_en信号到来时候拉高,done一次发送或写入结束时拉低
always @ (posedge clk50M or negedge reset)
begin  if(!reset)scl_cnt_state <= 1'b0 ;      else if(iic_en == 1'b1 )scl_cnt_state <= 1'b1 ;else if(done == 1'b1 )scl_cnt_state <= 1'b0 ;else scl_cnt_state <= scl_cnt_state ;
end
//scl 时钟总线产生计数器
always @ (posedge clk50M or negedge reset)
begin  if(!reset)scl_cnt <= 'd0 ;else if(scl_cnt_state == 1'b1 )beginif(scl_cnt == SCL_CNT_M - 1'b1 )scl_cnt <= 'd0 ;else scl_cnt <= scl_cnt + 1'b1 ;endelsescl_cnt <= 'd0 ;
end
//scl 时钟总线产生
always @ (posedge clk50M or negedge reset)
begin  if(!reset)scl <= 1'b1 ;else if(scl_cnt == (SCL_CNT_M >> 1) - 1'b1 )scl <= 1'b0 ;else if(scl_cnt == SCL_CNT_M - 1'b1 )scl <= 1'b1 ;else scl <= scl ;
end
//scl 时钟电平中部标志位  两者都可以吧
// reg   scl_high;
// reg   scl_low ;
//
// always@(posedge clk50M or negedge reset)
// begin// if(!reset)// begin// scl_high <= 1'b0;// scl_low <= 1'b0;// end// else if(scl_cnt == (SCL_CNT_M>>2))// scl_high <= 1'b1;// else if(scl_cnt == (SCL_CNT_M>>1)+(SCL_CNT_M>>2))// scl_low <= 1'b1;// else// begin// scl_high <= 1'b0;// scl_low <= 1'b0;// end
// end
wire   scl_high;
wire   scl_low ;
assign scl_low = (scl_cnt == ((SCL_CNT_M >> 1) + (SCL_CNT_M >> 2) - 'd2 )) ? 1'b1 : 1'b0;
assign scl_high = (scl_cnt == (SCL_CNT_M >> 2) - 1'b1 ) ? 1'b1 : 1'b0 ;
//主状态机
always @ (posedge clk50M or negedge reset)
begin  if(!reset)beginstate <= Idle ;sda_en <= 1'b1;sda_reg <= 1'b1;W_flag <= 1'b0;R_flag <= 1'b0;done <= 1'b0 ;endelse case(state)Idle:begindone <= 1'b0 ;W_flag <= 1'b0 ;R_flag <= 1'b0 ;sda_en <= 1'b1 ;sda_reg <= 1'b1 ;if(iic_en && write) //使能iic驱动并为写操作beginW_flag <= 1'b1; //写标志位置 1sda_en <= 1'b1; //设置 SDA 为输出模式sda_reg <= 1'b1; //SDA 输出高电平state <= Wr_start; //跳转到起始状态endelse if(iic_en && read) //使能 IIC 并且为读操作beginR_flag <= 1'b1; //读标志位置 1sda_en <= 1'b1; //设置 SDA 为输出模式sda_reg <= 1'b1; //SDA 输出高电平state <= Wr_start; //跳转到起始状态endelse    state <= state;endWr_start:beginif(scl_high == 1'b1)beginsda_reg <= 1'b0;state <= Wr_ctrl;sda_data_out <= {4'b1010, cs_bit,1'b0}; //跳转到下一个状态之前将需要传输的数据准备好bit_cnt <= 4'd8;endelse beginsda_reg <= 1'b1 ;state <= Wr_start ;endendWr_ctrl:        //写控制字节 4'b1010+3 位片选地址+1 位写控制beginif(scl_low == 1'b1)beginbit_cnt <= bit_cnt -4'b1;sda_reg <= sda_data_out[7];sda_data_out <= {sda_data_out[6:0],1'b0};  if(bit_cnt == 'd0)beginstate <= Ack1;      //等待从机响应sda_en <= 1'b0;     //释放SDA线endelse state <= Wr_ctrl;endelse state <= Wr_ctrl;endAck1:beginif(scl_high == 1'b1)if(sda == 1'b0)         //接收到相应,跳转到下一个状态beginstate <= Wr_addr2;    //使用的是8位地址的sda_data_out <= address[7:0];//state <= Wr_addr1;    //使用的是13位地址的//sda_data_out <= {3'bxxx,address[12:8]};bit_cnt <= 4'd8;endelse    state <= Ack1;endWr_addr1: //写2字节地址中高地址字节中的第五位beginif(scl_low)//scl低电平的时候改变数据,高电平的时候数据保持稳定由从机接收beginsda_en <= 1'b1; //主机控制SDA线bit_cnt <= bit_cnt -4'b1; sda_reg <= sda_data_out[7];sda_data_out <= {sda_data_out[6:0],1'b0};if(bit_cnt == 0)beginstate <= Ack2; //在等待响应时候,需要释放SDA总线由从机控制sda_en <= 1'b0;endelse state <= Wr_addr1;endelse state <= Wr_addr1 ;endAck2:beginif(scl_high == 1'b1)if(sda == 1'b0)beginstate <= Wr_addr2 ;sda_data_out <= address[7:0] ;bit_cnt <= 4'd8;endelse    state <= Idle;else    state <= Ack2;endWr_addr2:beginif(scl_low == 1'b1)beginsda_en <= 1'b1;bit_cnt <= bit_cnt - 'd1;sda_reg <= sda_data_out[7];         //使用移位,每次将最高位送出去sda_data_out <= {sda_data_out[6:0],1'b0};if(bit_cnt == 'd0)beginstate <= Ack3;sda_en <= 1'b0;endelse state <= Wr_addr2;endelse state <= Wr_addr2;endAck3:beginif(scl_high == 1'b1)if(sda == 1'b0)beginif(W_flag == 1'b1)beginsda_data_out <= write_data;bit_cnt <= 'd8;state <= Wr_data;endelse if(R_flag == 1'b1)beginstate <= Rd_start;sda_reg <= 1'b1;       //sda输出高电平end endelse state <= Idle;else state <= Ack3;endWr_data: //向e2prom中写数据beginif(scl_low == 1'b1)beginsda_en <= 1'b1;bit_cnt <= bit_cnt - 4'd1;sda_reg <= sda_data_out[7];sda_data_out <= {sda_data_out[6:0],1'b0};if(bit_cnt == 'd0)beginstate <= Ack4;sda_en <= 1'b0;endelse state <= Wr_data;endelse    state <= Wr_data;endAck4://响应写数据结束beginif(scl_high == 1'b1)if(sda == 1'b0)beginsda_reg <= 1'b0;state <= Stop ;endelse state <= Idle;else state <= Ack4;endRd_start://读数据的开始 ??beginif(scl_low == 1'b1)sda_en <= 1'b1;//总线控制,为1时由主机控制SDA总线else if(scl_high == 1'b1)beginsda_reg <= 1'b0;state <= Rd_ctrl;sda_data_out <= {4'b1010,cs_bit,1'b1};//读数据为1bit_cnt <= 4'd8; end else    beginsda_reg <= 1'b1;state <= Rd_start;endendRd_ctrl://发送控制字节片选与读控制位beginif(scl_low == 1'b1)//改变数据beginbit_cnt <= bit_cnt - 1'b1;sda_reg <= sda_data_out[7];sda_data_out <= {sda_data_out[6:0],1'b0};if(bit_cnt == 4'd0)beginstate <= Ack5;sda_en <= 1'b0;endelse state <= Rd_ctrl;endelse state <= Rd_ctrl;endAck5://判断读状态从机的响应beginif(scl_high == 1'b1)if(sda == 1'b0)beginstate <= Rd_data;sda_en <= 1'b0;bit_cnt <= 4'd8;endelse    state <= Idle;else state <= Ack5;endRd_data:beginif(scl_high == 1'b1)beginsda_data_in <= {sda_data_in[6:0],sda};bit_cnt <= bit_cnt - 4'd1;state <= Rd_data;endelse if(scl_low == 1'b1 && bit_cnt == 4'd0)state <= Nack;else state <= Rd_data;endNack://不做应答响应beginread_data <= sda_data_in;if(scl_high == 1'b1)beginstate <= Stop;sda_reg <= 1'b0;endelse state <= Nack;endStop:beginif(scl_low == 1'b1)sda_en <= 1'b1;else if(scl_high == 1'b1)beginsda_en <= 1'b1;sda_reg <= 1'b1;state <= Idle;done <= 1'b1;endelse state <= Stop;enddefault:beginstate <= Idle;sda_en <= 1'b1;sda_reg <= 1'b1;W_flag <= 1'b0;R_flag <= 1'b0;done <= 1'b0;endendcase
end endmodule

4,代码仿真

代码使用仿真有使用镁光提供的两个仿真模板,也有使用夏宇闻 Verilog 数字系统设计教程一书中的仿真,这里笔者使用的是镁光提供的仿真。第一个24Lc04B是13地址,第二个是24LC64是8地址的。笔者使用的是8地址进行仿真。向地址中写入数据即可。

`timescale 1ns / 1ps
module tb_iic_dri;// Inputsreg clk50M;reg reset;reg iic_en;reg [2:0] cs_bit;reg [12:0] address;reg write;reg [7:0] write_data;reg read;// Outputswire [7:0] read_data;wire scl;wire done;// Bidirswire sda;
always #10 clk50M = ~clk50M;// Instantiate the Unit Under Test (UUT)iic_dri uut (.clk50M(clk50M), .reset(reset), .iic_en(iic_en), .cs_bit(cs_bit), .address(address), .write(write), .write_data(write_data), .read(read), .read_data(read_data), .scl(scl), .sda(sda), .done(done));initial begin// Initialize Inputscs_bit = 3'b000;address = {5'b00000,8'h54};clk50M = 0;reset = 0;iic_en = 0;write = 0;write_data = 0;read = 0;// Wait 100 ns for global reset to finish#100;reset = 1'b1;#100;iic_en = 1'b1;write = 1'b1;write_data = 8'h55;#20;iic_en = 1'b0;write = 1'b0;#(20*100000);iic_en = 1'b1;read = 1'b1;#20;iic_en = 1'b0;read = 1'b0;// Add stimulus hereend
M24LC64 M24LC64 (.A0(1'b0), .A1(1'b0), .A2(1'b0), .WP(1'b0), .SDA(sda), .SCL(scl), .RESET(!reset));endmodule

5,IIC写仿真分析(读仿真分析略)

写数据,在写使能与iic使能同时到来时,打开计数器,计数到高电平中点时候,拉低SDA信号,产生一个起始位。



6,板机验证


至于数字的显示可以参考原工程。我会在文末给出获取方式。

五,参考博文资料

参考1,【接口时序】6、IIC总线的原理与Verilog实现(强烈安利)
参考2,芯航线FPGA设计参考指南(强烈安利)

六,留坑(以后必填)

1,对于代码时序跳转的练习(单独开一篇)
2,对于正点原子思路IIC的分析与验证
笔者的时候是参考的正点原子的思路,但是这个东西吧,怎么说,仿真比较麻烦,网上关于ISE里的chipscope使用又很少,不知道怎么,时序都可以,板机验证就是出不来,以后有时间填坑
3,对于代码中移位操作以及状态跳转中进行数据的加减与赋值。
(最后,聊一点小思路,能看到这里的也真的不容易。其实我们写代码,开始的时候就是抄,抄各种各样的代码,然后把抄的背过,学会其中的思路,在一点一点慢慢的修改,就形成了自己的思路了所以需要多抄,多总结)
4,对于IIC的时序图绘制与代码的重新修改

(关注公众号:果冻空间:回复 A-002-2 即可获得,里边有笔者自己做的状态跳转图,相关仿真资料,以及搜来的其他代码)

基于FPGA的EEPROM读写(IIIC 接口协议)相关推荐

  1. 基于FPGA实现IIC接口(EEPROM)

    1 IIC应用领域 在嵌入式系统开发过程中,IIC占据非常重要的地位.IIC通讯接口能到搭载较多的从设备,从而实现与多个从设备进行通讯,在板级通讯中是一种比较常用的通讯接口.笔者通过IIC接口实现FP ...

  2. 基于FPGA的UART接口协议设计

    一.PC终端概述 PC终端,Personal Computer 智能终端,通俗的讲,就是利用电脑GUI界面控制我们的外部硬件电路. 因此设计到了PC与外部硬件电路的通信接口.对于台式电脑.个人笔记本, ...

  3. 基于FPGA的SSI接口协议实现

    基于FPGA的SSI接口协议实现 SSI 是一种主机和从机点对点的通信接口,其中从机可以是具有 SSI 协议的各种传感器,例如磁致伸缩位移传感器.编码器等. SSI协议采用主机主动式读取方式,从机根据 ...

  4. 【接口协议】基于 FPGA 的 HMDI 彩条显示实验

    目录 HDMI 介绍 HDMI 引脚定义 TMDS 介绍 编码模块 代码实现 并转串模块 视频时序标准 传输通道顶层 顶层模块 工程搭建 HDMI 介绍 HDMI,高清晰度多媒体接口(High Def ...

  5. altera fpga 型号说明_基于FPGA的USB2.0接口通信

    欢迎FPGA工程师加入官方微信技术群 点击蓝字关注我们FPGA之家-中国最好最大的FPGA纯工程师社群 概述 本文主要介绍一种基于FPGA的FT232H接口通信开发方案.传统的USB通信开发对工程人员 ...

  6. 基于FPGA的PCIe接口实现(具体讲解了数据流向)

    转载自:https://www.cnblogs.com/chengqi521/p/7094544.html 时间:2014-12-09 来源:西安电子科技大学电子工程学院 作者:姜 宁,陈建春,王 沛 ...

  7. 基于FPGA的USB接口控制器设计(VHDL)(中)

    今天给大侠带来基于 FPGA 的 USB 接口控制器设计(VHDL),由于篇幅较长,分三篇.今天带来第二篇,中篇,USB通信原理.USB 系统开发以及设计实例.话不多说,上货. 之前有关于 Veril ...

  8. python fpga chips_基于FPGA实现JESD204B高速接口设计

    曹鹏飞 摘 要:JESD204B接口是高速ADC和DAC芯片采用的数据通信接口之一,具有传输速率高,抗干扰能力强,芯片间同步方便等优点.目前国内JESD204B 接口应用多由国外集成芯片提供,缺乏自主 ...

  9. 基于FPGA实现PCI-E接口和DMA控制器设计

    随着网络的飞速发展,人们可获取的信息量日益增长,数据的处理及存储速率的要求也越来越高.万兆网(10Gb以太网)的普及,高速存储设备的应用(如DDR2,传输速率可达800M)对系统带宽带来极大的挑战. ...

最新文章

  1. /etc/rc.d 与 /etc/profile或者./.bash_profile的区别
  2. error LNK2038: 检测到“RuntimeLibrary”的不匹配项: 值“MTd_StaticDebug”不匹配值“MDd_DynamicDebug...
  3. java php python 高并发_关于php如何调用Python快速发送高并发邮件的示例代码
  4. asp.net与JAVASCRIPT函数的相互调用
  5. python中、文件最重要的功能是( )和接收数据_Python基础语法14个知识点大串讲
  6. 【AI视野·今日CV 计算机视觉论文速览 第180期】Wed, 26 Feb 2020
  7. android的应用组件,跟我学android-Android应用基本组件介绍(五)
  8. android 饿了么地图,饿了么送餐位置地图定位代码
  9. linux shell写日志,Linux shell编程之文件内容写入和日志记录
  10. 题目1544:数字序列区间最小值
  11. 小米php架构图,小米商城基本框架部分
  12. 怎么把动图分解成图片?gif怎么拆分成几张?
  13. 电信屏蔽了80端口,利用80端口映射解决web网站应用发布问题
  14. 中创向心力:如何把思想政治教育贯穿职业教育全过程?
  15. 学计算机有那些方向,计算机专业的研究生研究方向有哪些
  16. OpenGL入门示例8——图形平移、旋转、缩放
  17. ExoPlayer官方中文使用文档
  18. EasyNVR+人工智能分析算法,赋能AI行业应用
  19. 最新论文笔记(+21):Privacy-Preserving Byzantine-Robust Federated Learning via Blockchain Systems/ TIFS2022
  20. 建设网站制作公司的选择标准是什么?

热门文章

  1. ora-00119和ora-00132解决方案
  2. JQuery的Ajax跨域请求的解决方案
  3. Sqlserver2012 评估期已过解决问题
  4. 如何重置/删除chrome的输入突出显示/焦点边框? [重复]
  5. 正坐标系及矢量知识,点乘与差乘,旋转
  6. activiti启动流程实例
  7. 全向轮机器人左下轮运动学分析
  8. jmeter 导出聚合报告_使用Jmeter聚合报告生成对比图表
  9. pyinstaller安装_如何打包Python Web项目,实现免安装一键启动?
  10. 洛谷——P1179 [NOIP2010 普及组] 数字统计