ZYNQ_project:IIC_EEPROM

EEPROM简介:

EEPROM(Electrically Erasable Progammable Read Only Memory, E2PROM)是指带电可擦可编程只读存储器,是一种常用的非易失性存储器(掉电数据不丢失), E2PROM 有多种类型的产品,我们领航者 ZYNQ开发板上使用的是 ATMEL 公司生产的 AT24C 系列的 AT24C64 这一型号。 AT24C64 具有高可靠性,可对所存数据保存 100 年,并可多次擦写,擦写次数达一百万次。
它的存储容量:256页每页32个字节。256 x 32 = 8192 (byte字节)

8192 x 8 = 65536(bit比特)

65536  /  1024 = 64 (Kbit)。。。。所以是AT24C系列,64Kbit存储容量。

AT24C64 采用两线串行接口的双向数据传输协议——I2C 协议实现读写操作,所以我们有必要了
解一下 I2C 协议。

IIC协议简介:

简单,双向,二线制总线标准。多用于主机与从机在数据量不大且短距离的(主从)通信。

主机启动总线,并产生时钟用于传送数据,此时任何接收数据的器件均被认为是从机。
IIC总线由数据线SDA和时钟线SCL构成通信线路,各种iic通信器件并联在总线上,通过器件地址识别器件(与器件通信前先要找到这个器件,发地址,等响应,找到对应要通信的从设备)。

IIC总线物理结构拓扑原理结构图:

 图中的 I2C_SCL 是串行时钟线, I2C_SDA 是串行数据线,由于 I2C 器件一般采用开漏结构与总线相连,
所以 I2C_SCL 和 I2C_SDA 均需接上拉电阻,也正因此,当总线空闲时,这两条线路都处于高电平状态,当
连到总线上的任一器件输出低电平,都将使总线拉低,即各器件的 SDA 及 SCL 都是“线与”关系。
I2C 总线支持多主和主从两种工作方式,通常工作在主从工作方式,我们的开发板就采用主从工作方式。
在主从工作方式中,系统中只有一个主机,其它器件都是具有 I2C 总线的外围从机。在主从工作方式中,主
机启动数据的发送(发出启动信号)并产生时钟信号,数据发送完成后,发出停止信号。

IIC的协议层

仔细观察图 33.1.2 可知 IIC 协议的整体时序由四个部分构成,上图中分别使用①、②、③、④进行区
分。
图中①: 总线空闲状态, 在 I2C 器件开始通信(传输数据)之前,串行时钟线 SCL 和串行数据线 SDA
线由于上拉的原因处于高电平状态,此时 I2C 总线处于空闲状态。
图中②: 起始信号, 如果主机(此处指 FPGA)想开始传输数据,只需在 SCL 为高电平时将 SDA 线拉
低,产生一个起始信号。
图中③: 数据传输状态, 主机可以向从机写数据,也可以读取从机输出的数据,数据的传输由双向数据
线(SDA)完成。
图中④: 停止状态, 当数据传输完成,主机只需产生一个停止信号,告诉从机数据传输结束,停止信号
的产生是在 SCL 为高电平时, SDA 从低电平跳变到高电平,从机检测到停止信号后,停止接收数据,并且
I2C 总线跳转回总线空闲状态。

数据传输的具体时序:

scl为高时,sda产生下降沿,产生起始信号。(告诉从机,要开始数据通信了)。

在scl为低时,sda可以改变数据;scl为高时,sda数据保持;

8bit数据一组,也就需要8个时钟周期;

第九个时钟周期:主机释放总线,从机控制总线产生应答位0.若为高,则说明无应答,这组数据是无效的传输。

scl为高时,sda产生上升沿,表示停止信号。(主机结束与从机的通信)

 器件地址:

当多个 I2C 器件挂接在总线上时,怎样才能与我们想要传输数据的器件进行通信。这就涉及到了器件地址(也称从机地址, SLAVE ADDRESS)。
有两种器件地址:7bit全都是固定的,与部分bit固定,部分bit可编程。

AT24C64,起始位+1010+A2A1A0.最低三位可编程。

WR位为0,表示主机要进行写操作,1表示读操作。

 IIC 读写时序

写时序:(字节写和页写)

字节写:

主机发送完字地址,从机正确应答后就把内部的存储单元地址指针指向该单元。如果读写控制位 R/W位为“0”即写命令,从机就处于接收数据的状态,此时,主机就开始写数据了,写数据可分为单次写(对于 E2PROM 而言,称为字节写)和连续写(对于 E2PROM 而言,称为页写)。下面我们先分别介绍 E2PROM的单次写与连续写。

从左上角开始按行往右看。

(1)主机先产生开始标志信号:scl == 1 sda产生下降沿。

(2)然后发送器件地址(MSB~LSB高位在前)最后一位是读写控制标志位,读写控制信号位,0写;1读。

(3)然后从机产生应答位。

 (4)然后发送字地址(存储器的存储空间的地址,相当于指针,相当于门牌号(学过C语言的都应该不陌生))由于AT24C64,存储空间为64Kbit,8bit只能表示256byte(2的8次方 = 256)(256x8/1024 == 2Kbit).

64Kbit : 64x1024/8= 8192

2的13次方,才能表示完8192个数据。

iic以字节传数据,所以AT24C64要用两字节的字地址。

从机指针指向对应的字地址,准备接收数据。

每传递一字节数据后从机会在下一个时钟周期产生应答位。(主机释放总线,从机拉低总线)

(5)然后从机产生应答位。

(6)然后主机开始发送数据:8bit数据位,

(7)然后从机产生应答位。

(8)然后主机产生停止信号,结束位。

页写:

接下来我们开始介绍 E2PROM 的连续写(页写)时序:

 (1)主机产生起始信号。

(2)主机发送器件地址+指令bit。

(3)从机应答。

(4)主机发送第一个字节,从机应答。第二个字节,从机应答。

(5)传递一字节的数据(8bit),高位在前。从机产生应答位。

(6)主机发送停止信号。

两者的区别在于发送完一字节数据后,是发送结束信号还是继续发送下一字节数据,如果发送的是结束信号,就称为单次写,如果继续发送下一字节数据,就称为连续写。

要注意的是, 所有 I2C 设备均支持单字节数据写入操作,但只有部分 I2C 设备支持页写操作,对于
AT24C64 的页写,是不能发送超过一页的单元容量的数据的,而 AT24C64 的一页的单元容量为 32Byte,当
写完一页的最后一个单元时,地址指针指向该页的开头,如果再写入数据,就会覆盖该页的起始数据。

读时序:

I2C 写时序介绍完毕后,接下来我们开始 I2C 读时序部分的介绍。根据一次读操作读取数据量的多少,
读操作可分为随机读操作和顺序读操作。

读数据有三种方式:当前地址读,随机读,连续读。

当前地址读方式:

当前地址读是指在一次读或写操作后发起读操作。由于 I2C 器件在读写操作后,其内部的地址指针自动加一,因此当前地址读可以读取下一个字地址的数据。也就是说上次读或写操作的单元地址为 02 时,当前地址读的内容就是地址 03 处的单元数据。

 (1)主机发送起始信号。

(2)主机发送器件地址+读命令(1).

(3)从机发送响应信号,从机应答。

(4)从机发送数据8bit,高位在前。

(5)主机非应答,高电平无应答信号.

(6)主机发送停止信号。

随机读取方式:

并不随机,指定地址并读取一字节数据。

(1)主机发送起始信号。

(2)主机发送器件地址+写指令(0).原因是,接下来是要写入字地址(存储器的地址),所以是写指令。从机产生应答位。

(3)主机发送第一字节地址,从机产生应答位。

(4)主机发送第二字节地址,从机产生应答位。

(5)主机发送起始信号。又一次发送起始信号。

(6)主机发送期间地址+读指令(1).从机产生应答位。

(7)从机发送8bit数据。主机产生无应答位。

(8)主机发送停止信号。

需要注意理解的是随机地址读在发送完器件地址和字地址后,竟然又发送起始信号和器件地址,而且第一次发送器件地址时后面的读写控制位为“ 0”,也就是写命令,第二次发送器件地址时后面的读写控制位为“1”,也就是读。为什么会有这样奇怪的操作呢?这是因为我们需要使从机内的存储单元地址指针指向我们想要读取的存储单元地址处,所以首先发送了一次Dummy Write 也就是虚写操作,只所以称为虚写,是因为我们并不是真的要写数据,而是通过这种虚写操作使地址指针指向虚写操作中字地址的位置,等从机应答后,就可以以当前地址读的方式读数据了。

当前地址读下的连续读:

当前地址读和随机读都是一次读取一个字节,连续读是将当前地址读或随机读的主机非应答改成应答,表示继续读取数据。

(1)主机发送起始信号。

(2)主机发送器件地址+读命令(1),从机产生应答信号。

(3)从机发送8bit数据,主机产生应答信号(0).

(4)从机发送下一字节数据,主机产生应答信号(0).

.................

(5)从机发送下一字节数据,主机产生无应答信号(1).

(6)主机发送停止信号。

随机地址连续读:

有当前地址连续读,当然也有随机地址连续读,随机地址连续读是在图 33.1.10 随机地址读时序的基础上,主机非应答改成应答,表示继续读取数据。

(1)主机发送起始信号。

(2)主机发送器件地址+写指令(0).原因是”虚写“。从机产生应答位。

(3)主机发送第一字节地址,从机产生应答位。

(4)主机发送第二字节地址,从机产生应答位。

(5)主机发送起始信号。又一次发送起始信号。

(6)主机发送期间地址+读指令(1).从机产生应答位。

(7)从机发送8bit数据。主机产生应答位(0)。

(8)从机发送下一字节的8bit数据。主机产生应答位(0)。

................

(10)从机发送8bit数据。主机产生无应答位。

(11)主机发送停止信号。

总结:

所以器件地址为1010_000

实验任务:

本节的实验任务是先向 E2PROM(AT24C64)的存储器地址 0 至 255 分别写入数据 0~255;写完之后再读取存储器地址 0~255 中的数据,把读到的数据通过串口发送给上位机,验证数据正确否。

模块框图:

时序图:

就是使用前面给的时序图。

代码:

写代码,组合逻辑不要过长,输出端口尽量用时序逻辑。

只放接口模块代码,拱大家参考。

/*
    iic协议接口模块
    输入端:时钟,复位,写时序控制标志信号,读时序控制标志信号
    输出端:二线,读写时序完成标志信号,接收数据与标志信号
    在该模块例化伪双端口ram,用于存储接收到的数据,读出后传给tx模块
    // 先实现写入一页的数据,然后再拓展。每页32个字节
*/
`include "para.v"
module iic_interface (
    input		wire				sys_clk		    ,
    input		wire				sys_rst_n	    ,
    input       wire                iic_wr_flag     ,
    input       wire                iic_rd_flag     ,

    inout       wire                sda             ,

    output		reg                 scl             ,
    output      wire                iic_wr_done     ,
    output      wire                iic_rd_done     ,
    output      reg     [7:0]       po_data         ,
    output      reg                 po_flag         
);
    parameter   DEVICE_W = 8'b1010_0000 ;
    parameter   DEVICE_R = 8'b1010_0001 ;
    // localparam 
    localparam  IDLE                    =   4'b0000     ,
                START                   =   4'b0001     ,
                ADDR_DEVICE             =   4'b0011     ,
                SLAVE_ACK               =   4'b0010     ,
                ADDR_FIRST              =   4'b0110     ,
                ADDR_SECOND             =   4'b0111     ,
                START2                  =   4'b0101     ,
                ADDR_DEVICE_R           =   4'b0100     ,
                ACK_SLAVE2_R            =   4'b1100     ,
                READ_DATA               =   4'b1101     ,
                ACK_MASTER              =   4'b1111     ,
                NOACK_MASTER            =   4'b1110     ,
                END_R                   =   4'b1010     ,
                WRITE_DATA              =   4'b1011     ,
                ACK_SLAVE2_W            =   4'b1001     ,
                END_W                   =   4'b1000     ;
                
    // reg signal
    reg     [ 3:0]      state_c             ;
    reg     [ 3:0]      state_n             ;
    reg     [31:0]      cnt_scl             ;
    reg     [ 2:0]      cnt_bit             ;
    reg     [31:0]      cnt_data            ;
    reg                 master_ack_ok       ; // 用来检测主机是否正常产生响应。
    reg                 addr_first_req      ;
    reg                 addr_second_req     ;
    reg                 start2_req          ;
    reg                 write_data_req      ;
    reg                 slave_ack_ok        ; // 从机在规定时刻有响应
    reg                 ack_master_req      ;
    reg                 noack_master_req    ;
    reg                 end_w_req           ;
    reg                 read_work           ; // 根据输入端口的flag信号,控制工作状态。
    reg                 write_work          ; // 不能有同时拉高的时候。
    reg                 sda_out             ;
    reg     [ 7:0]      addr_h = 8'h00      ; // at24c64的高低字节地址。
    reg     [ 7:0]      addr_l = 8'h00      ; 
    reg     [31:0]      generate_data       ;
    reg                 write_work_r        ;
    reg                 read_work_r         ;
    reg                 po_flag_r           ;
    reg     [ 7:0]      po_data_r           ;
    reg                 iic_en  ;
    // wire signal
    // wire                iic_en              ;
    wire                end_cnt_bit         ;
    wire                add_cnt_bit         ;
    wire                end_cnt_scl         ;
    wire                add_cnt_scl         ;
    wire                add_cnt_data        ;
    wire                end_cnt_data        ;
    wire                keep_cnt_data       ;
    // 状态转移条件
    wire        IDLEtoSTART                 ;
    wire        STARTtoADDR_DEVICE          ;
    wire        ADDR_DEVICEtoSLAVE_ACK      ;
    wire        SLAVE_ACKtoIDLE             ;
    wire        SLAVE_ACKtoADDR_FIRST       ;
    wire        SLAVE_ACKtoADDR_SECOND      ;
    wire        SLAVE_ACKtoSTART2           ;
    wire        SLAVE_ACKtoWRITE_DATA       ;
    wire        ADDR_FIRSTtoSLAVE_ACK       ;
    wire        ADDR_SECONDtoSLAVE_ACK      ;
    wire        START2toADDR_DEVICE_R       ;
    wire        ADDR_DEVICE_RtoACK_SLAVE2_R ;
    wire        ACK_SLAVE2_RtoREAD_DATA     ;
    wire        READ_DATA_ACK_MASTER        ;
    wire        READ_DATAtoNOACK_MASTER     ;
    wire        ACK_MASTERtoREAD_DATA       ;
    wire        NOACK_MASTERtoEND_R         ;
    wire        END_RtoIDLE                 ;
    wire        WRITE_DATAtoACK_SLAVE2_W    ;
    wire        ACK_SLAVE2_WtoEND_W         ;
    wire        ACK_SLAVE2_WtoWRITE_DATA    ;
    wire        END_WtoIDLE                 ;
/******************************************************************************************
********************************************main code**************************************
*******************************************************************************************/
    // reg     [ 3:0]      state_c           ;
    always @(posedge sys_clk or negedge sys_rst_n) begin
        if(~sys_rst_n) 
            state_c <= IDLE ;
        else
            state_c <= state_n ;
    end
    // reg     [ 3:0]      state_n           ;
    always @(*) begin
        case(state_c)
        IDLE         :  if(IDLEtoSTART)
                            state_n = START ;
                        else 
                            state_n = IDLE ;
        START        :  if(STARTtoADDR_DEVICE)
                            state_n = ADDR_DEVICE ;
                        else 
                            state_n = START ;
        ADDR_DEVICE  :  if(ADDR_DEVICEtoSLAVE_ACK)
                            state_n = SLAVE_ACK ;
                        else 
                            state_n = ADDR_DEVICE ;
        SLAVE_ACK    :  if(SLAVE_ACKtoADDR_FIRST)
                            state_n = ADDR_FIRST ;
                        else if(SLAVE_ACKtoADDR_SECOND)
                            state_n = ADDR_SECOND ;
                        else if(SLAVE_ACKtoSTART2) // 进入读数据相关状态
                            state_n = START2 ;
                        else if(SLAVE_ACKtoWRITE_DATA) // 进入写数据相关状态
                            state_n = WRITE_DATA ;
                        else if(SLAVE_ACKtoIDLE) // 新增
                            state_n = IDLE ; // 在该进行跳转的时候,从机没有任何响应,跳回IDLE状态。
                        else 
                            state_n = SLAVE_ACK ;
        ADDR_FIRST   :  if(ADDR_FIRSTtoSLAVE_ACK)
                            state_n = SLAVE_ACK ;
                        else 
                            state_n = ADDR_FIRST ;
        ADDR_SECOND  :  if(ADDR_SECONDtoSLAVE_ACK)
                            state_n = SLAVE_ACK ;
                        else 
                            state_n = ADDR_SECOND ;
        START2       :  if(START2toADDR_DEVICE_R)
                            state_n = ADDR_DEVICE_R ;
                        else 
                            state_n = START2 ;
        ADDR_DEVICE_R:  if(ADDR_DEVICE_RtoACK_SLAVE2_R)
                            state_n = ACK_SLAVE2_R ;
                        else 
                            state_n = ADDR_DEVICE_R ;
        ACK_SLAVE2_R :  if(ACK_SLAVE2_RtoREAD_DATA)
                            state_n = READ_DATA ;
                        else 
                            state_n = ACK_SLAVE2_R ;
        READ_DATA    :  if(READ_DATA_ACK_MASTER)
                            state_n = ACK_MASTER ;
                        else if(READ_DATAtoNOACK_MASTER)
                            state_n = NOACK_MASTER ;
                        else 
                            state_n = READ_DATA ;
        ACK_MASTER   :  if(ACK_MASTERtoREAD_DATA)
                            state_n = READ_DATA ;
                        else
                            state_n = ACK_MASTER ; // 在该进行跳转前,主机无任何响应,跳转回idle
        NOACK_MASTER :  if(NOACK_MASTERtoEND_R)
                            state_n = END_R ;
                        else 
                            state_n = NOACK_MASTER ; // 主机无响应,跳转回idle
        END_R        :  if(END_RtoIDLE)
                            state_n = IDLE ;
                        else 
                            state_n = END_R ; // 正常来讲就是一个scl周期就会跳回idle
        WRITE_DATA   :  if(WRITE_DATAtoACK_SLAVE2_W)
                            state_n = ACK_SLAVE2_W ;
                        else 
                            state_n = WRITE_DATA ;
        ACK_SLAVE2_W :  if(ACK_SLAVE2_WtoWRITE_DATA) // 写数据状态优先跳转。
                            state_n = WRITE_DATA ;
                        else if(ACK_SLAVE2_WtoEND_W)
                            state_n = END_W ;
                        else 
                            state_n = ACK_SLAVE2_W ; // 从机无应答,跳回idle
        END_W        :  if(END_WtoIDLE)
                            state_n = IDLE ;
                        else 
                            state_n = END_W ;
        default      :  state_n = IDLE ;
        endcase
    end

    assign  IDLEtoSTART                     = (state_c == IDLE          ) && (iic_wr_flag || iic_rd_flag) ;
    assign  STARTtoADDR_DEVICE              = (state_c == START         ) && (end_cnt_scl && scl) ;
    assign  ADDR_DEVICEtoSLAVE_ACK          = (state_c == ADDR_DEVICE   ) && (end_cnt_scl && scl) && (end_cnt_bit) ;
    assign  SLAVE_ACKtoIDLE                 = (state_c == SLAVE_ACK     ) && (end_cnt_scl && scl) && (!slave_ack_ok) ;
    assign  SLAVE_ACKtoADDR_FIRST           = (state_c == SLAVE_ACK     ) && (end_cnt_scl && scl) && (addr_first_req  ) && (slave_ack_ok) ;
    assign  SLAVE_ACKtoADDR_SECOND          = (state_c == SLAVE_ACK     ) && (end_cnt_scl && scl) && (addr_second_req ) && (slave_ack_ok) ;
    assign  SLAVE_ACKtoSTART2               = (state_c == SLAVE_ACK     ) && (end_cnt_scl && scl) && (start2_req      ) && (slave_ack_ok) ;
    assign  SLAVE_ACKtoWRITE_DATA           = (state_c == SLAVE_ACK     ) && (end_cnt_scl && scl) && (write_data_req  ) && (slave_ack_ok) ;
    assign  ADDR_FIRSTtoSLAVE_ACK           = (state_c == ADDR_FIRST    ) && (end_cnt_scl && scl) && (end_cnt_bit) ;
    assign  ADDR_SECONDtoSLAVE_ACK          = (state_c == ADDR_SECOND   ) && (end_cnt_scl && scl) && (end_cnt_bit) ;
    assign  START2toADDR_DEVICE_R           = (state_c == START2        ) && (end_cnt_scl && scl) ;
    assign  ADDR_DEVICE_RtoACK_SLAVE2_R     = (state_c == ADDR_DEVICE_R ) && (end_cnt_scl && scl) && (end_cnt_bit) ;
    assign  ACK_SLAVE2_RtoREAD_DATA         = (state_c == ACK_SLAVE2_R  ) && (end_cnt_scl && scl) && (slave_ack_ok) ;
    assign  READ_DATA_ACK_MASTER            = (state_c == READ_DATA     ) && (end_cnt_scl && scl) && (end_cnt_bit) && (ack_master_req) ;
    assign  READ_DATAtoNOACK_MASTER         = (state_c == READ_DATA     ) && (end_cnt_scl && scl) && (end_cnt_bit) && (noack_master_req) ;
    assign  ACK_MASTERtoREAD_DATA           = (state_c == ACK_MASTER    ) && (end_cnt_scl && scl) && (master_ack_ok) ;
    assign  NOACK_MASTERtoEND_R             = (state_c == NOACK_MASTER  ) && (end_cnt_scl && scl) && (master_ack_ok) ;
    assign  END_RtoIDLE                     = (state_c == END_R         ) && (end_cnt_scl && scl) ;
    assign  WRITE_DATAtoACK_SLAVE2_W        = (state_c == WRITE_DATA    ) && (end_cnt_scl && scl) && (end_cnt_bit) ;
    assign  ACK_SLAVE2_WtoEND_W             = (state_c == ACK_SLAVE2_W  ) && (end_cnt_scl && scl) && (end_w_req       ) && (slave_ack_ok) ;
    assign  ACK_SLAVE2_WtoWRITE_DATA        = (state_c == ACK_SLAVE2_W  ) && (end_cnt_scl && scl) && (write_data_req  ) && (slave_ack_ok) ;
    assign  END_WtoIDLE                     = (state_c == END_W         ) && (end_cnt_scl && scl) ;

    // reg                 po_flag_r           ;
    always @(posedge sys_clk or negedge sys_rst_n) begin
        if(~sys_rst_n) 
            po_flag_r <= 1'b0 ;
        else if((state_c == READ_DATA) && (end_cnt_scl && end_cnt_bit && scl))
            po_flag_r <= 1'b1 ;
        else 
            po_flag_r <= 1'b0 ;
    end
    // reg     [ 7:0]      po_data_r           ;
    always @(posedge sys_clk or negedge sys_rst_n) begin
        if(~sys_rst_n) 
            po_data_r <= 8'd0 ;
        else if(state_c == READ_DATA && (scl) && (cnt_scl == (`MAX_CNT_SCL >> 1)))
            po_data_r[7 - cnt_bit] <= sda ;
            // po_data_r <= {po_data[6:0],sda} ;
        else if(state_c == IDLE)
            po_data_r <= 8'd0 ;
        else 
            po_data_r <= po_data_r ;
    end
    // reg                 write_work_r        ;
    // reg                 read_work_r         ;
    always @(posedge sys_clk or negedge sys_rst_n) begin
        if(~sys_rst_n) begin
            write_work_r <= 1'b0 ;
            read_work_r  <= 1'b0 ;
        end
        else begin
            write_work_r <= write_work ;
            read_work_r  <= read_work  ;
        end  
    end
    // reg                 generate_data       ;
    always @(posedge sys_clk or negedge sys_rst_n) begin
        if(~sys_rst_n) 
            generate_data <= 32'd0 ;
        else if(add_cnt_data && generate_data == `MAX_CNT_DATA - 1)
            generate_data <= 32'd0 ;
        else if(add_cnt_data)
            generate_data <= generate_data + 1'b1 ;
        else    
            generate_data <= generate_data ;
    end
    // reg     [31:0]      cnt_scl             ;
    always @(posedge sys_clk or negedge sys_rst_n) begin
        if(~sys_rst_n) 
            cnt_scl <= 32'd0 ;
        else if(add_cnt_scl) begin
            if(end_cnt_scl)
                cnt_scl <= 32'd0 ;
            else
                cnt_scl <= cnt_scl + 1'b1 ;
        end
        else 
            cnt_scl <= 32'd0 ;     
    end
    // wire                add_cnt_scl         ;
    assign  add_cnt_scl = (state_c != IDLE) ;
    // wire                end_cnt_scl         ; 
    assign  end_cnt_scl = (add_cnt_scl) && (cnt_scl == (`MAX_CNT_SCL - 1)) ;

    // reg     [ 2:0]      cnt_bit             ; 一共就计数0 ~ 7,其实可以自己归零。111 -> 000
    always @(posedge sys_clk or negedge sys_rst_n) begin
        if(~sys_rst_n) 
            cnt_bit <= 3'd0 ;
        else if(add_cnt_bit) begin
            if(end_cnt_bit && (end_cnt_scl && scl))
                cnt_bit <= 3'd0 ;
            else if(end_cnt_scl && scl)
                cnt_bit <= cnt_bit + 1'b1 ;
            else 
                cnt_bit <= cnt_bit ;
        end
        else 
            cnt_bit <= 3'd0 ;
    end
    // wire                add_cnt_bit         ;
    assign  add_cnt_bit = ((state_c == (ADDR_DEVICE)) || (state_c == (ADDR_FIRST)) || (state_c == (ADDR_SECOND)) || (state_c == (READ_DATA)) || state_c == (WRITE_DATA) || state_c == (ADDR_DEVICE_R)) ;
    // wire                end_cnt_bit         ; // cnt_bit == 7
    assign  end_cnt_bit = add_cnt_bit && (cnt_bit == (`MAX_CNT_BIT - 1)) ;

    // reg     [31:0]      cnt_data            ;
    always @(posedge sys_clk or negedge sys_rst_n) begin
        if(~sys_rst_n) 
            cnt_data <= 3'd0 ;
        else if(add_cnt_data) begin
            if(end_cnt_data)
                cnt_data <= 3'd0 ;
            else 
                cnt_data <= cnt_data + 1'b1 ;
        end
        else if(keep_cnt_data)
            cnt_data <= cnt_data ;
        else 
            cnt_data <= 3'd0 ;
    end

    assign    add_cnt_data  = (state_c == (READ_DATA) || state_c == (WRITE_DATA))
                            && (end_cnt_scl && scl && end_cnt_bit) ;
    assign    end_cnt_data  = add_cnt_data && (cnt_data == `MAX_CNT_DATA - 1);
    assign    keep_cnt_data = ((state_c == (READ_DATA)) || (state_c == (WRITE_DATA)) || (state_c == (ACK_MASTER)) || (state_c == (ACK_SLAVE2_W)));

    // reg                 master_ack_ok       ; // 用来检测主机是否正常产生响应。
    always @(posedge sys_clk or negedge sys_rst_n) begin
        if(~sys_rst_n) 
            master_ack_ok <= 1'b0 ;
        else if(state_c == ACK_MASTER) begin
            if(cnt_scl == (`MAX_CNT_SCL >> 1) && scl && ~sda)
                master_ack_ok <= 1'b1 ;
            else 
                master_ack_ok <= master_ack_ok ;
        end
        else if(state_c == NOACK_MASTER) begin
            if(cnt_scl == (`MAX_CNT_SCL >> 1) && scl && sda)
                master_ack_ok <= 1'b1 ;
            else 
                master_ack_ok <= master_ack_ok ;
        end
        else 
            master_ack_ok <= 1'b0 ;
    end
    // reg                 slave_ack_ok        ; // 从机在规定时刻有响应
    always @(posedge sys_clk or negedge sys_rst_n) begin
        if(~sys_rst_n) 
            slave_ack_ok <= 1'b0 ;
        else if(state_c == (SLAVE_ACK) || state_c == (ACK_SLAVE2_R) || state_c == (ACK_SLAVE2_W)) begin
            if(cnt_scl == (`MAX_CNT_SCL >> 1) && scl && ~sda) // && ~sda
                slave_ack_ok <= 1'b1 ;
            else 
                slave_ack_ok <= slave_ack_ok ;
        end
        else 
            slave_ack_ok <= 1'b0 ;
    end
    // reg                 addr_first_req      ;
    always @(posedge sys_clk or negedge sys_rst_n) begin
        if(~sys_rst_n) 
            addr_first_req <= 1'b0 ;
        else if(state_c == START) 
            addr_first_req <= 1'b1 ;
        else if(state_c == ADDR_FIRST)
            addr_first_req <= 1'b0 ;
        else 
            addr_first_req <= addr_first_req ;
    end
    // reg                 addr_second_req     ;
    always @(posedge sys_clk or negedge sys_rst_n) begin
        if(~sys_rst_n) 
            addr_second_req <= 1'b0 ;
        else if(state_c == START) 
            addr_second_req <= 1'b1 ;
        else if(state_c == ADDR_SECOND)
            addr_second_req <= 1'b0 ;
        else 
            addr_second_req <= addr_second_req ;
    end
    // reg                 start2_req          ;
    always @(posedge sys_clk or negedge sys_rst_n) begin
        if(~sys_rst_n) 
            start2_req <= 1'b0 ;
        else if((state_c == START) && (read_work)) 
            start2_req <= 1'b1 ;
        else if(state_c == START2)
            start2_req <= 1'b0 ;
        else 
            start2_req <= start2_req ;
    end

    // reg         read_work   ; // 根据输入端口的flag信号,控制工作状态。
    always @(posedge sys_clk or negedge sys_rst_n) begin
        if(~sys_rst_n) 
            read_work <= 1'b0 ;
        else if(state_c == END_R)
            read_work <= 1'b0 ;
        else if(!write_work && iic_rd_flag)
            read_work <= 1'b1 ;
        else 
            read_work <= read_work ;
    end
    // reg         write_work  ; // 不能有同时拉高的时候。
    always @(posedge sys_clk or negedge sys_rst_n) begin
        if(~sys_rst_n) 
            write_work <= 1'b0 ;
        else if(state_c == END_W)
            write_work <= 1'b0 ;
        else if(!write_work && iic_wr_flag)
            write_work <= 1'b1 ;
        else 
            write_work <= write_work ;
    end
    // reg                 write_data_req      ;
    always @(posedge sys_clk or negedge sys_rst_n) begin
        if(~sys_rst_n) 
            write_data_req <= 1'b0 ;
        else if(state_c == WRITE_DATA && (end_cnt_scl && scl && end_cnt_bit) && cnt_data == (`MAX_CNT_DATA - 1))
                write_data_req <= 1'b0 ;
        else if(state_c == START && (end_cnt_scl && scl && write_work))
            write_data_req <= 1'b1 ;
        else 
            write_data_req <= write_data_req ;
    end
    // reg                 ack_master_req      ;
    always @(posedge sys_clk or negedge sys_rst_n) begin
        if(~sys_rst_n) 
            ack_master_req <= 1'b0 ;
        else if(state_c == READ_DATA && (end_cnt_scl && scl && end_cnt_bit) && cnt_data == (`MAX_CNT_DATA - 2))
                ack_master_req <= 1'b0 ;
        else if(state_c == START && (end_cnt_scl && scl && read_work))
            ack_master_req <= 1'b1 ;
        else 
            ack_master_req <= ack_master_req ;
    end
    // reg                 noack_master_req    ;
    always @(posedge sys_clk or negedge sys_rst_n) begin
        if(~sys_rst_n) 
            noack_master_req <= 1'b0 ;
        else if(state_c == NOACK_MASTER)
                noack_master_req <= 1'b0 ;
        else if(state_c == START && (end_cnt_scl && scl && read_work))
            noack_master_req <= 1'b1 ;
        else 
            noack_master_req <= noack_master_req ;
    end
    // reg                 end_w_req           ;
    always @(posedge sys_clk or negedge sys_rst_n) begin
        if(~sys_rst_n) 
            end_w_req <= 1'b0 ;
        else if(state_c == END_W)
                end_w_req <= 1'b0 ;
        else if(state_c == START && (end_cnt_scl && scl && write_work))
            end_w_req <= 1'b1 ;
        else 
            end_w_req <= end_w_req ;
    end

    // reg              sda_out ;
    always @(posedge sys_clk or negedge sys_rst_n) begin
        if(~sys_rst_n) 
            sda_out <= 1'b1 ;
        else 
        case (state_c)
        IDLE           :    sda_out <= 1'b1 ;
        START          :    if(cnt_scl == (`MAX_CNT_SCL >> 1) && scl) 
                                sda_out <= 1'b0 ;
                            else 
                                sda_out <= sda_out ;
        ADDR_DEVICE    :    if(cnt_scl == (`MAX_CNT_SCL >> 1) && !scl) 
                                sda_out <= DEVICE_W[7 - cnt_bit] ;
                            else 
                                sda_out <= sda_out ;
        SLAVE_ACK      :    sda_out <= 1'b1 ;
        ADDR_FIRST     :    if(cnt_scl == (`MAX_CNT_SCL >> 1) && !scl) 
                                sda_out <= addr_h[7 - cnt_bit] ;
                            else 
                                sda_out <= sda_out ;
        ADDR_SECOND    :    if(cnt_scl == (`MAX_CNT_SCL >> 1) && !scl) 
                                sda_out <= addr_l[7 - cnt_bit] ;
                            else 
                                sda_out <= sda_out ;
        START2         :    if(cnt_scl == (`MAX_CNT_SCL >> 1) && !scl) 
                                sda_out <= 1'b1 ;
                            else if(cnt_scl == (`MAX_CNT_SCL >> 1) && scl) 
                                sda_out <= 1'b0 ;
                            else 
                                sda_out <= sda_out ;
        ADDR_DEVICE_R  :    if(cnt_scl == (`MAX_CNT_SCL >> 1) && !scl) 
                                sda_out <= DEVICE_R[7 - cnt_bit] ;
                            else 
                                sda_out <= sda_out ;
        ACK_SLAVE2_R   :    sda_out <= 1'b1 ;
        READ_DATA      :    sda_out <= 1'b1 ;
        ACK_MASTER     :    sda_out <= 1'b0 ;
        NOACK_MASTER   :    sda_out <= 1'b1 ;
        END_R          :    if(cnt_scl == (`MAX_CNT_SCL >> 1) && !scl) 
                                sda_out <= 1'b0 ;
                            else if(cnt_scl == (`MAX_CNT_SCL >> 1) && scl)
                                sda_out <= 1'b1 ;
                            else 
                                sda_out <= sda_out ;
        WRITE_DATA     :    if(cnt_scl == (`MAX_CNT_SCL >> 1) && !scl) 
                                sda_out <= generate_data[7 - cnt_bit] ;
                            else 
                                sda_out <= sda_out ;
        ACK_SLAVE2_W   :    sda_out <= 1'b1 ;
        END_W          :    if(cnt_scl == (`MAX_CNT_SCL >> 1) && !scl) 
                                sda_out <= 1'b0 ;
                            else if(cnt_scl == (`MAX_CNT_SCL >> 1) && scl)
                                sda_out <= 1'b1 ;
                            else 
                                sda_out <= sda_out ;
        default        :        sda_out <= 1'b1 ;
        endcase
    end                                                                                
    // wire            iic_en ;
    // reg                 iic_en  ;
    always @(posedge sys_clk or negedge sys_rst_n) begin
        if(~sys_rst_n) 
            iic_en <= 1'b0 ;
        else if((state_c == IDLE) || (state_c == START) || (state_c == ADDR_DEVICE) || (state_c == ADDR_FIRST) 
             || (state_c == ADDR_SECOND) || (state_c == START2) || (state_c == ADDR_DEVICE_R) || (state_c == ACK_MASTER) 
             || (state_c == NOACK_MASTER) || (state_c == END_R) || (state_c == WRITE_DATA) || (state_c == END_W))
            iic_en <= 1'b1 ;
        else 
            iic_en <= 1'b0 ;
    end
    reg             sda_out_r ;
    always @(posedge sys_clk or negedge sys_rst_n) begin
        if(~sys_rst_n) 
            sda_out_r <= 1'b1 ;
        else 
            sda_out_r <= sda_out ;
    end
    // assign  iic_en = ~((state_c == SLAVE_ACK) || (state_c == ACK_SLAVE2_R) || (state_c == ACK_SLAVE2_W) || (state_c == READ_DATA)) ;
    // assign  iic_en = ((state_c == IDLE) || (state_c == START) || (state_c == ADDR_DEVICE) || (state_c == ADDR_FIRST) 
    // || (state_c == ADDR_SECOND) || (state_c == START2) || (state_c == ADDR_DEVICE_R) || (state_c == ACK_MASTER) 
    // || (state_c == NOACK_MASTER) || (state_c == END_R) || (state_c == WRITE_DATA) || (state_c == END_W)) ? 1'b1 : 1'b0 ;
    // output signal
    // inout       wire                sda             ,
    assign  sda = (iic_en) ? sda_out_r : 1'bz ;
    // output		reg                 scl             ,
    always @(posedge sys_clk or negedge sys_rst_n) begin
        if(~sys_rst_n) 
            scl  <= 1'b1 ;
        else if(end_cnt_scl && (state_c != IDLE))
            scl  <= ~scl ;
        else if(state_c == IDLE)
            scl  <= 1'b1 ;
        else    
            scl  <=  scl ;
    end
    // output      wire                iic_wr_done     ,
    assign  iic_wr_done = ~write_work && write_work_r ; 
    // output      wire                iic_rd_done     ,
    assign  iic_rd_done = ~read_work && read_work_r ; 
    // output      reg     [7:0]       po_data         ,
    // output      reg                 po_flag     
    always @(posedge sys_clk or negedge sys_rst_n) begin
        if(~sys_rst_n) begin
            po_data <= 8'd0  ;
            po_flag <= 1'b0  ;
        end
        else begin
            po_data <= po_data_r ;
            po_flag <= po_flag_r ;
        end
    end

ila_0 ila_0_inst (
	.clk(sys_clk), // input wire clk

	.probe0(state_c), // input wire [3:0]  probe0  
	.probe1(write_work), // input wire [0:0]  probe1 
	.probe2(read_work), // input wire [0:0]  probe2 
	.probe3(sda_out), // input wire [0:0]  probe3 
	.probe4(iic_en), // input wire [0:0]  probe4 
	.probe5(scl), // input wire [0:0]  probe5 
	.probe6(sda), // input wire [0:0]  probe6 
	.probe7(iic_wr_flag) // input wire [0:0]  probe7
);

endmodule

仿真:

上板验证: 

可优化的地方:

 1,存储地址,1字节,2字节。

2,数据的产生,是自己产生的,可以优化成总线的形式。或者数据流的形式。

反思:

1,第一次设计的时候,状态机设计的很烂。要多观察,找状态上的异同。

2,组合逻辑不要过长,多用时序逻辑。防止竞争与冒险。

3,尽量不画完整时序图,写代码锻炼想象力,加强代码与波形之间转换的能力。

  • 22
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值