ZYNQ-FPGA-IIC协议及eeprom读写

一、iic协议介绍以及eeprom

1.1iic协议

        IIC协议是传输中常见的协议,其包括了SCL 和SDA 两个引脚,它是一种半双工协议即可以收可以发但是不能同时进行。传输速度不高但较为简单。

        如上图,SCL与SDA 都连接着上拉电阻,因此当SCL、SDA空闲时候被上拉为高电平1,因此我们通常用0来表示有效。在SDA线上挂着许多元器件,这些元器件有自己的器件地址,因此主机可以通过器件地址找到设备。

IIC协议的状态:

1.空闲,SCL与SDA被上拉为高电平

2.开始,当SCL为高电平时候,SDA 出现第一个下降沿表示起始信号

3.数据发送,SCL为高电平的时候SDA 保持数据不变,SCL为低电平的时候SDA 改变到需要发送的数据

4.停止,当SCL为高电平的时候SDA 出现上升沿表示停止信号,即进入空闲状态

且SDA的前八bit是SDA 对从机进(主机)行输出,第9bit是从机(主机)发出的应答信号,应答信号为0表示有效。因此SDA 是应该既需要输入也需要输出的信号。

IIC协议写模式分为以下几步:

1.写入器件地址(操作哪个器件)

2.写入寄存器地址(操作该器件的哪个寄存器)

3.写入数据

下面分别介绍三步骤的过程

1.1.1写入器件地址

        首先IIC器件地址在一块板子上是固定的,是在设计板子的时候确定的,无法人为改变。对于EEPROM来说,我的板子(正点原子领航者和黑金ax7020)是1010000七位地址,其中前面四位是固定的,后面三位是通过eeprom在板子上的三个引脚决定的,其中eeprom的这三个引脚接的是GND因此为1010000,其中写入器件地址的第八位数据是读写控制信号,表示选择读模式还是写模式,其中0表示写  1表示读。因此整个数据如下:

1.1.2写入存储器地址

        该部分是对存储器地址写入,存储地址根据存储数量选择例如某些存储单元为2Kbit(256BYTE,一个BYTE是横着的8bit)选择八位即可2^8=256,某些存储是64kbit(8KBYTE)需要选择13位(2^13=8192=8kBYTE)。此次板子中选用16位(有效位是低13位)的存储器地址。

在选择时候可以定义一个信号,输入1即为16位输入0即为8位。

1.1.3写入数据

        写入数据分为单bit写与连续写,单bit写时候在应答结束后给出停止位,整个过程停止,下一个bit的数据需要从写入器件地址重新开始;连续写在写完第一bit数据响应后继续给出下一个数据而非停止位即可连续写。如下图:

单次写

连续写

(写命令当写完一页的时候再写会覆盖该页)        

1.1.4读模式

        读模式分为当前地址读(上一操作地址+1)、随机地址读(指定)

当前地址读较为简单,即再第一次发送器件地址的时候W/R给1即可开始再下一地址读

随即地址读较为复杂,其含义是从指定的一个地址开始读数据其流程是:

1.发送器件地址,且选择写命令(这一步被称为虚写)

2.发送虚写地址

3.再次发送器件地址,此时选择读

4.读出数据

(我个人认为进行虚写的目的是为了设置指定的读地址,如果不进行虚写操作将会直接进行当前地址读操作)

读也可以进行连续读模式,即最后主机回应0即可继续读下一bit,回应1 即是单bit读。

1.2 EEPROM

本开发板(正点原子领航者7020)上的eeprom是at24c64型号

这是他的原理图,eeprom是一个iic协议通信的存储空间,可以写入数据64kb

二、框架设计

        本次实验目标是将0-256写入eeprom对应的地址的0-256中,且加入验证模块,若写入正确即led常亮,若写入错误则led闪烁。

        对于本实验进行分析可得需要一下几个模块:eeprom驱动(编写iic协议对eeprom进行驱动),写入和读出数据封装模块(负责控制写什么数据以及解析读出的数据),led控制模块

        上图是总体的实验框图,类似于hdmi显示,i2c_dri模块是负责和eeprom模块完成驱动交互的,而e2prom_rw才是确定该系统功能的。

        IIC驱动模块较为负杂,采用三段式状态机。

        第一段状态机负责时序更新(时序)

        第二段状态机负责状态逻辑(组合逻辑)

        第三段状态机负责定义每个状态内做什么(时序)

状态机的流程图如下:

三、程序设计

3.1 iic_dri

        将IIC的驱动时钟设置在100KHZ-400KHZ即可,本次实验设计为250KHZ,这个时钟是用于该模块以及eeprom_rw模块的驱动时钟而非SCL,SCL应该是该时钟的四分频,具体解释如下:

        由于sda是既要输入也要输出的(例如主机写数据时候从机要应答),因此sda在定义的时候应该是inout类型,即既要输出也要输入模式,其具体使用如下

assign  sda        = sda_dir ?  sda_out : 1'bz   ;  //SDA数据输出或高阻
assign  sda_in     = sda                         ;  //SDA数据输入

        通过dir来控制是输入还是输出,如果dir是1即输出状态,sda的值由sda_out控制,实际中操作sda_out即可,如果dir是0则输入模式,这时候sda是高阻态,然后将sda输入的值给sda_in。即在下面使用过程中就不再直接使用sda,在输入时候使用sda_in,输出时候使用sda_out。

        再下面就是有一个分频操作(在没有使用PLL锁相环的时候人为进行分频)以及三段式状态机。

module        iic_dri(
        input             clk           ,
        input             rst           ,
        input             i2c_begin     ,
        input             addr_bit      ,//16or8
        input             i2c_rw        ,//0w   1r
        input     [15:0]  i2c_addr      ,
        input     [7:0 ]  i2c_wdata     ,
        output reg[7:0 ]  i2c_rdata     ,
        output reg        dri_clk       , //dri_clk是给其他模块的时钟,scl是给eeprom实际是时钟
        output reg        i2c_done      ,
        output reg        i2c_ack       ,
        output reg        scl           ,
        inout             sda               
);
    parameter            st_idle        =   8'b0000_0001  ;
    parameter            st_sladdr      =   8'b0000_0010  ;
    parameter            st_addrh       =   8'b0000_0100  ;
    parameter            st_addrl       =   8'b0000_1000  ;
    parameter            st_wdata       =   8'b0001_0000  ;
    parameter            st_addr_r      =   8'b0010_0000  ;
    parameter            st_rdata       =   8'b0100_0000  ;
    parameter            st_stop        =   8'b1000_0000  ;
    
    parameter            FPGA_FREQ      =   50000000      ;
    parameter            IIC_FREQ       =   250000        ;
    parameter            EEPROM_ADDR    =   7'b1010000    ;
    
    reg                  sda_dir   ;//三态门sda,sda_dir 1输出0输入
    reg                  sda_out   ;//dir1时候sda赋值为sdaout
    reg                  st_done   ;//st_done是每个环节完成标志位,i2cdone是整体完成
    reg                  wr_rd     ;  //锁存
    reg       [6:0]      cnt       ;
    reg       [7:0]      cur_state ;
    reg       [7:0]      nex_state ;
    reg      [15:0]      addr_t    ;//锁存
    reg       [7:0]      data_rd_t ; //读取的数据  锁存
    reg       [7:0]      data_wr_t ; //I2C需写的数据的临时寄存
    reg       [9:0]      clk_cnt   ; //分频时钟计数
    
    wire                 sda_in    ;
    wire      [8:0]      clk_divide;
    wire      [8:0]      clk_change;
    
    
    
    
assign        sda        =     sda_dir ? sda_out:1'bz;//输出状态就是out否则就是高阻态
assign        sda_in     =     sda                   ;//高组态时候输入给in,输出不用in就行
assign        clk_divide =     (FPGA_FREQ/IIC_FREQ)  >>  2'd2;//分频系数  右移两位其实就是÷2÷2
assign        clk_change =     clk_divide[8:1]    ;//周期是2的话1s就得跳变一次
//dri采用clk的四倍频时钟。因为iic只有scl低电平的时候sda可以改变
//dri是clk四倍频,那clk的t就是dri的四倍,所以dri的上升沿可以检测到clk低电平中间的点
//这里dri_clk就是四倍频的驱动时钟

always   @(posedge clk or negedge rst ) begin
    if (rst == 1'b0) begin 
        dri_clk <= 1'b0 ;
        clk_cnt <= 10'b0;
    end
    else if (clk_cnt == clk_change - 1'b1) begin 
        clk_cnt <= 10'b0;
        dri_clk <= ~dri_clk;
    end
    else 
        clk_cnt <= clk_cnt + 1'b1;
end

//三段式状态机 第一段时序逻辑 状态转移 (状态机以及其他模块使用dri时钟)
//状态转移,复位进入等待,否则进入下一周期
always @(posedge dri_clk or negedge rst) begin 
    if (rst == 1'b0) 
        cur_state <= st_idle;
    else 
        cur_state <= nex_state ;
end

//第二段,组合逻辑判断使用阻塞赋值(电平触发)  描述这几个状态之间的转移过程
always @(*) begin      //*代表用到的所有电平
    nex_state = st_idle;//取消锁存(可以删除)
    case(cur_state)
        st_idle : begin
            if (i2c_begin == 1'b1)
                nex_state = st_sladdr;
            else 
                nex_state = st_idle;
        end
        
        st_sladdr : begin
            if(st_done == 1'b1)   begin//环节完成拉高
                if (addr_bit == 1'b1)
                    nex_state = st_addrh;
                else 
                    nex_state = st_addrl;
            end
            else
                nex_state = st_sladdr;
        end
        
        st_addrh : begin
            if(st_done == 1'b1)
                nex_state = st_addrl;
            else
                nex_state = st_addrh;
        end
        
        st_addrl : begin
            if (st_done == 1'b1) begin
                if (wr_rd == 1'b1)
                    nex_state = st_addr_r ;
                else 
                    nex_state = st_wdata;
            end
            else
                nex_state = st_addrl;
        end
        
        st_wdata : begin
            if (st_done == 1'b1)
                nex_state = st_stop ;
            else 
                nex_state = st_wdata;
        end
        
        st_addr_r : begin
            if (st_done == 1'b1)
                nex_state = st_rdata ;
            else
                nex_state = st_addr_r;
        end
        
        st_rdata : begin
            if (st_done == 1'b1 )
                nex_state = st_stop;
            else 
                nex_state = st_rdata;
        end
        
        st_stop : begin
            if (st_done == 1'b1)
                nex_state = st_idle;
            else
                nex_state = st_stop;
        end
        
        default nex_state = st_idle;
    endcase

end

//第三段****描述状态作用 时序逻辑电路 ****

always @( posedge dri_clk or negedge rst ) begin
    if (rst == 1'b0 ) begin
        scl           <= 1'b1;//由于有上拉电阻,无电平的时候被上拉为1
        sda_out       <= 1'b1;//由于有上拉电阻,无电平的时候被上拉为1
        sda_dir       <= 1'b1;
        i2c_done      <= 1'b0;//整体结束
        i2c_ack       <= 1'b0;
        cnt           <= 7'b0;//利用该计数器完成数据sda以及clk时钟
        st_done       <= 1'b0;
        addr_t        <= 16'b0;
        data_rd_t     <= 8'b0;
        data_wr_t     <= 8'b0;
        wr_rd         <= 1'b0;
        i2c_rdata     <= 8'b0;
    end
    else begin 
        st_done <= 1'b0;
        cnt     <= cnt +1'b1; //利用该计数器完成数据sda以及clk时钟
        case (cur_state )
            st_idle : begin
                scl           <= 1'b1;      //初始化
                sda_out       <= 1'b1;      //初始化
                sda_dir       <= 1'b1;      //初始化
                i2c_done      <= 1'b0;      //初始化
                cnt           <= 7'b0;      //初始化
                if (i2c_begin == 1'b1) begin
                    wr_rd     <= i2c_rw   ;     //锁存响应
                    addr_t    <= i2c_addr ;     //锁存响应
                    data_wr_t <= i2c_wdata;     //锁存响应
                    i2c_ack   <= 1'b0     ;     //锁存响应
                end
            end
            
            st_sladdr : begin
                case ( cnt )
                    7'd1 : sda_out <= 1'b0 ;//下降沿开始触发iic
                    7'd3 : scl     <= 1'b0 ;
                    7'd4 : sda_out <= EEPROM_ADDR[6];
                    7'd5 : scl     <= 1'b1 ;
                    7'd7 : scl     <= 1'b0 ;
                    7'd8 : sda_out <= EEPROM_ADDR[5];
                    7'd9 : scl     <= 1'b1 ;
                    7'd11: scl     <= 1'b0 ;
                    7'd12: sda_out <= EEPROM_ADDR[4];
                    7'd13: scl     <= 1'b1 ;
                    7'd15: scl     <= 1'b0 ;
                    7'd16: sda_out <= EEPROM_ADDR[3];
                    7'd17: scl     <= 1'b1 ;
                    7'd19: scl     <= 1'b0 ;
                    7'd20: sda_out <= EEPROM_ADDR[2];
                    7'd21: scl     <= 1'b1 ;
                    7'd23: scl     <= 1'b0 ;
                    7'd24: sda_out <= EEPROM_ADDR[1];
                    7'd25: scl     <= 1'b1 ;
                    7'd27: scl     <= 1'b0 ;
                    7'd28: sda_out <= EEPROM_ADDR[0];
                    7'd29: scl     <= 1'b1 ;
                    7'd31: scl     <= 1'b0 ;
                    7'd32: sda_out <= 1'b0 ;          //无论是读还是写模块第一次都是输入写
                    7'd33: scl     <= 1'b1 ;
                    7'd35: scl     <= 1'b0 ;
                    7'd36: begin
                        sda_dir <= 1'b0    ;          //高阻态进入输入
                        sda_out <= 1'b1    ;          //空闲
                    end
                    7'd37: scl     <= 1'b1 ;
                    7'd38: begin                      //由于sda每次站的时间大概为4个计数单位因此在sda输入四个计数内都可以检测到应答信号
                        st_done    <= 1'b1 ; 
                        if (sda_in == 1'b1 )          //1是没有应答
                            i2c_ack <= 1'b1 ;   //原本是0如果没有回应则拉高到1
                    end 
                    7'd39: begin
                        scl   <= 1'b0;
                        cnt   <= 7'b0;
                    end
                    default : ;
                endcase
            end
            
            st_addrh : begin 
                case (cnt) 
                    7'd0 : begin            //如果操作正确,这里应该是继续了上一步的scl=0
                        sda_dir <= 1'b1 ; 
                        sda_out <= addr_t[15] ;
                    end
                    7'd1 : scl     <= 1'b1 ;
                    7'd3 : scl     <= 1'b0 ;
                    7'd4 : sda_out <= addr_t[14] ; 
                    7'd5 : scl     <= 1'b1 ;
                    7'd7 : scl     <= 1'b0 ;
                    7'd8 : sda_out <= addr_t[13] ;
                    7'd9 : scl     <= 1'b1 ;
                    7'd11: scl     <= 1'b0 ;
                    7'd12: sda_out <= addr_t[12] ; 
                    7'd13: scl     <= 1'b1 ;
                    7'd15: scl     <= 1'b0 ;
                    7'd16: sda_out <= addr_t[11] ;
                    7'd17: scl     <= 1'b1 ;
                    7'd19: scl     <= 1'b0 ;
                    7'd20: sda_out <= addr_t[10] ;
                    7'd21: scl     <= 1'b1 ;
                    7'd23: scl     <= 1'b0 ;
                    7'd24: sda_out <= addr_t[9]  ;
                    7'd25: scl     <= 1'b1 ;
                    7'd27: scl     <= 1'b0 ;
                    7'd28: sda_out <= addr_t[8]  ;
                    7'd29: scl     <= 1'b1 ;
                    7'd31: scl     <= 1'b0;
                    7'd32: begin
                        sda_dir <= 1'b0 ;
                        sda_out <= 1'b1 ;
                    end
                    7'd33: scl     <= 1'b1;
                    7'd34:begin 
                        st_done <= 1'b1 ;
                        if (sda_in == 1'b1 )
                            i2c_ack <= 1'b1 ;
                        end
                    7'd35: begin
                        scl     <= 1'b0 ;         //35凑不凑都行,因为如果凑了的话刚好满足sda在下一周期第一个就跳变。
                        cnt     <= 7'b0 ;         //如果这里没有凑35 那么下面一步0就是变沿,1才是数据改变
                    end
                    default :      ;
                endcase            
            end
            
            st_addrl : begin 
                case (cnt)
                    7'd0 : begin 
                        sda_dir <= 1'd1 ;
                        sda_out <= addr_t[7] ;
                    end
                    7'd1 : scl     <= 1'd1;
                    7'd3 : scl     <= 1'd0;
                    7'd4 : sda_out <= addr_t[6 ];
                    7'd5 : scl     <= 1'd1;
                    7'd7 : scl     <= 1'd0;
                    7'd8 : sda_out <= addr_t[5 ];
                    7'd9 : scl     <= 1'd1;
                    7'd11: scl     <= 1'd0;
                    7'd12: sda_out <= addr_t[4 ];
                    7'd13: scl     <= 1'd1;
                    7'd15: scl     <= 1'd0;
                    7'd16: sda_out <= addr_t[3 ];
                    7'd17: scl     <= 1'd1;
                    7'd19: scl     <= 1'd0;
                    7'd20: sda_out <= addr_t[2 ];
                    7'd21: scl     <= 1'd1;
                    7'd23: scl     <= 1'd0;
                    7'd24: sda_out <= addr_t[1 ];
                    7'd25: scl     <= 1'd1;
                    7'd27: scl     <= 1'd0;
                    7'd28: sda_out <= addr_t[0 ];
                    7'd29: scl     <= 1'd1;
                    7'd31: scl     <= 1'd0;
                    7'd32: begin 
                        sda_dir    <= 1'd0 ;
                        sda_out    <= 1'd1 ;
                    end
                    7'd33:scl <= 1'd1;
                    7'd34: begin 
                        st_done <= 1'd1;
                        if (sda_in == 1'd1 )
                            i2c_ack <= 1'd1 ;
                    end
                    7'd35: begin 
                            scl <= 1'd0 ;
                            cnt <= 7'd0 ;     //35凑不凑都行,因为如果凑了的话刚好满足sda在下一周期第一个就跳变。
                    end                       //如果这里没有凑35 那么下面一步0就是变沿,1才是数据改变
                    default : ;
                endcase                     
            end
            
            st_wdata :begin
                case (cnt)
                    7'd0 : begin
                        sda_dir <= 1'd1  ;
                        sda_out <= data_wr_t[7]   ;//所存的输入数据
                    end
                    7'd1 : scl     <= 1'd1;
                    7'd3 : scl     <= 1'd0;
                    7'd4 : sda_out <= data_wr_t[6 ];
                    7'd5 : scl     <= 1'd1;
                    7'd7 : scl     <= 1'd0;
                    7'd8 : sda_out <= data_wr_t[5 ];
                    7'd9 : scl     <= 1'd1;
                    7'd11: scl     <= 1'd0;
                    7'd12: sda_out <= data_wr_t[4 ];
                    7'd13: scl     <= 1'd1;
                    7'd15: scl     <= 1'd0;
                    7'd16: sda_out <= data_wr_t[3 ];
                    7'd17: scl     <= 1'd1;
                    7'd19: scl     <= 1'd0;
                    7'd20: sda_out <= data_wr_t[2 ];
                    7'd21: scl     <= 1'd1;
                    7'd23: scl     <= 1'd0;
                    7'd24: sda_out <= data_wr_t[1 ];
                    7'd25: scl     <= 1'd1;
                    7'd27: scl     <= 1'd0;
                    7'd28: sda_out <= data_wr_t[0 ];
                    7'd29: scl     <= 1'd1;
                    7'd31: scl     <= 1'd0;
                    7'd32:begin
                        sda_dir <= 1'd0 ;
                        sda_out <= 1'd1 ;
                    end
                    7'd33: scl  <= 1'd1;
                    7'd34: begin
                        st_done <= 1'd1;
                        if (sda_in == 1'd1 )
                            i2c_ack <= 1'd1;
                    end
                    7'd35: begin 
                           scl     <= 1'd0;
                           cnt     <= 7'd0;
                    end
                    default: ;
                endcase    
            end
            
            st_addr_r : begin
                case (cnt)
                    7'd0 : begin
                        sda_dir     <= 1'd1   ; 
                        sda_out     <= 1'd1   ;
                    end                    
                    7'd1 : scl     <= 1'd1;
                    7'd2 : sda_out <= 1'd0;    //下降沿触发信号
                    7'd3 : scl     <= 1'd0;
                    7'd4 : sda_out <= EEPROM_ADDR[6 ];                    
                    7'd5 : scl     <= 1'd1;
                    7'd7 : scl     <= 1'd0;
                    7'd8 : sda_out <= EEPROM_ADDR[5 ]; 
                    7'd9 : scl     <= 1'd1;
                    7'd11: scl     <= 1'd0;
                    7'd12: sda_out <= EEPROM_ADDR[4 ];
                    7'd13: scl     <= 1'd1;
                    7'd15: scl     <= 1'd0;
                    7'd16: sda_out <= EEPROM_ADDR[3 ];
                    7'd17: scl     <= 1'd1;
                    7'd19: scl     <= 1'd0;
                    7'd20: sda_out <= EEPROM_ADDR[2 ];
                    7'd21: scl     <= 1'd1;
                    7'd23: scl     <= 1'd0;
                    7'd24: sda_out <= EEPROM_ADDR[1 ];
                    7'd25: scl     <= 1'd1;
                    7'd27: scl     <= 1'd0;
                    7'd28: sda_out <= EEPROM_ADDR[0 ];
                    7'd29: scl     <= 1'd1;
                    7'd31: scl     <= 1'd0;
                    7'd32: sda_out <= 1'd1;                  //读信号
                    7'd33: scl     <= 1'd1;
                    7'd35: scl     <= 1'd0;
                    7'd36: begin
                        sda_dir <= 1'd0;
                        sda_out <= 1'd1;
                    end
                    7'd37: scl     <= 1'd1;
                    7'd38: begin
                        st_done <= 1'd1 ;
                        if (sda_in == 1'd1)
                            i2c_ack <= 1'd1 ;
                    end
                    7'd39: begin
                        cnt <= 7'd0 ;
                        scl <= 1'd0 ;
                    end  
                    default : ;
                endcase
            end
            
            st_rdata : begin 
                case (cnt )
                    7'd0 :  sda_dir    <= 1'd0   ;
                    7'd1 : begin
                        data_rd_t[7]   <= sda_in ;
                        scl            <= 1'd1   ;
                    end
                    7'd3 :  scl        <= 1'd0   ;
                    7'd5 :begin 
                        data_rd_t[6]   <= sda_in ;
                        scl            <= 1'd1   ;
                    end
                    7'd7 :  scl        <= 1'd0   ;
                    7'd9 :begin 
                        data_rd_t[5]   <= sda_in ;
                        scl            <= 1'd1   ;
                    end
                    7'd11:  scl        <= 1'd0   ;
                    7'd13:begin 
                        data_rd_t[4]   <= sda_in ;
                        scl            <= 1'd1   ;
                    end
                    7'd15:  scl        <= 1'd0   ;
                    7'd17:begin 
                        data_rd_t[3]   <= sda_in ;
                        scl            <= 1'd1   ;
                    end
                    7'd19:  scl        <= 1'd0   ;
                    7'd21:begin 
                        data_rd_t[2]   <= sda_in ;
                        scl            <= 1'd1   ;
                    end
                    7'd23:  scl        <= 1'd0   ;
                    7'd25:begin 
                        data_rd_t[1]   <= sda_in ;
                        scl            <= 1'd1   ;
                    end
                    7'd27:  scl        <= 1'd0   ;
                    7'd29:begin 
                        data_rd_t[0]   <= sda_in ;
                        scl            <= 1'd1   ;
                    end
                    7'd31:  scl        <= 1'd0   ;
                    7'd32: begin
                        sda_dir        <= 1'd1   ;
                        sda_out        <= 1'd1   ;
                    end
                    7'd33:  scl        <= 1'd1   ;
                    7'd34:  st_done    <= 1'd1   ;     //未应答
                    7'd35:begin 
                            scl        <= 1'd0   ;
                            cnt        <= 7'd0   ;
                            i2c_rdata  <= data_rd_t ;
                    end
                    default :     ;
                endcase            
            end
            
            st_stop : begin 
                case (cnt )
                    7'd0: begin 
                        sda_dir <= 1'd1 ;
                        sda_out <= 1'd0 ;
                    end
                    7'd1 : scl     <= 1'b1;
                    7'd3 : sda_out <= 1'b1;
                    7'd15: st_done <= 1'b1;
                    7'd16: begin
                        cnt      <= 7'b0;
                        i2c_done <= 1'b1; 
                    end
                    default  : ;
                endcase   
            end
        endcase
    end    
end          
endmodule

3.2 eeprom_rw

        该模块可以认为是iic_dir的上层模块,因为iic_dri是完成iic协议,而iic协议真正怎么使用是看该模块。

module         e2prom_rw(
    input                  dri_clk        ,
    input      [7:0]       i2c_data_r     ,
    input                  i2c_done       ,
    input                  i2c_ack        ,
    input                  rst            ,
    output      reg        i2c_begin      ,
    output                 addr_bit       ,
    output      reg        i2c_rw         ,
    output      reg [15:0] i2c_addr       ,
    output      reg [7:0 ] i2c_data_w     ,
    output      reg        rw_done        , //done 和结果输出给灯
    output      reg        rw_result       //用在后面灯环节
);
        reg     [1:0]      flow_cnt   ;     //环节计数
        reg     [13:0]      wait_cnt   ;
assign             addr_bit  =  1'b1  ;
parameter          DATA_MAX   =  256   ; //写入数据0-256一共257个数
parameter          WAIT_MAX   =  1250   ; //dri 频率是100_000则5ms是500个计数单位

always   @(posedge  dri_clk or negedge  rst) begin
    if (rst == 1'b0 ) begin 
        flow_cnt     <= 2'b00 ;
        i2c_rw       <= 1'b0  ;
        i2c_begin    <= 1'b0  ;
        i2c_addr     <= 16'b0 ;
        i2c_data_w   <= 8'b0  ;
        wait_cnt     <= 14'b0  ;
        rw_done      <= 1'b0  ;
        rw_result    <= 1'b0  ;        
    end
    else begin
        i2c_begin <= 1'b0 ; //拉低00时候的开始信号
        rw_done  <= 1'b0 ;
        case (flow_cnt)
            2'b00 : begin //写1模块  (用于判断是否写完257次)
                wait_cnt <= wait_cnt + 1'b1 ;
                if (wait_cnt == WAIT_MAX - 1'b1) begin //每次写中间需要5ms间隔
                    wait_cnt <= 14'b0 ;
                    if (i2c_addr == DATA_MAX) begin    //写完257次
                        flow_cnt <= 2'b10;
                        i2c_rw   <= 1'b1; 
                        i2c_addr <= 16'b0;     //写完去读,没写完去写(读前清零)
                    end
                    else begin
                        i2c_begin<= 1'b1    ;
                        flow_cnt <= 2'd01   ;
                    end
                end                
            end
            
            2'b01 :begin 
                if (i2c_done == 1'b1 ) begin
                    flow_cnt   <= 2'b00           ;
                    i2c_addr   <= i2c_addr + 1'b1 ;
                    i2c_data_w <=i2c_data_w +1'b1 ;
                end       
            end
            
            2'b10 : begin
                flow_cnt <= flow_cnt + 1'b1 ;
                i2c_begin<= 1'b1 ;           
            end
            
            2'b11: begin 
                if(i2c_done) begin
                    if ((i2c_addr[7:0] != i2c_data_r)||(i2c_ack == 1'b1)) begin
                        rw_done    <=   1'b1 ;
                        rw_result  <=   1'b0 ;//读的不对
                    end
                    else if (i2c_addr == (DATA_MAX - 16'b1)) begin
                        rw_done    <=   1'b1 ;
                        rw_result  <=   1'b1 ;
                    end
                    else  begin 
                        flow_cnt   <=   2'b10  ;
                        i2c_addr   <=   i2c_addr + 16'b1;
                    end
                end    
            end
            default  : ;
        endcase    
    end
end
endmodule

该模块执行的内容是:

1.判断是否写完256次?如果写完了进行读操作3,反之继续进行写操作且准备再次开始一次iic2。

2.判断这次写iic是否完成,完成的话地址和数据都加一

3.写操作开始一次iic传输,判断是否传输正确或得到应答信号,如果没有结束传输,结果为错误;如果正确则判断是否督导最大地址,如果是则结束,结果是正确,返回读下一位

3.3 rw_result_led

        首先定义了一个done_flag锁存eeprom_rw模块输出的判断结果。再实现想要的结果。

module            rw_result_led(
    input               dri_clk,
    input                rst   ,
    input             rw_done  ,
    input             rw_result,
    output    reg        led   
);
parameter       LED_CNT    =    100000  ;
    //灯反转计数器
    reg      [16:0]    led_cnt   ;
    //寄存done信号
    reg                done_flag ;
    
    
always @(posedge dri_clk or negedge  rst ) begin 
    if (!rst)
        done_flag <= 1'b0 ;
    else if (rw_done)
        done_flag <= 1'b1 ;
    else 
        done_flag <= done_flag ;
end

always @(posedge dri_clk or negedge  rst ) begin 
    if (!rst) begin 
        led <= 1'b1      ;
        led_cnt <= 17'b0 ;
    end
    else if (done_flag) begin 
        if (!rw_result) begin
            led_cnt <= led_cnt + 17'b1 ;
            if (led_cnt == LED_CNT - 17'b1) begin 
                led_cnt <= 17'b0 ;
                led <= ~led      ;
            end
        end
        else 
            led <= 1'b0 ;
    end
end
endmodule

3.4 顶层模块

调用四个模块即可

module e2prom_top(
    input        sys_clk,
    input        sys_rst,
    output       led    ,
    output       scl    ,
    inout        sda   
);
   wire           i2c_begin   ;
   wire           addr_bit    ;
   wire           i2c_rw      ;
   wire  [15:0]  i2c_addr    ;
   wire  [7 : 0]  i2c_data_w  ;
   wire  [7 :0]  i2c_rdata   ;
   wire  [7 : 0]  i2c_data_r  ;
   wire           dri_clk     ;
   wire           i2c_done    ;
   wire           i2c_ack     ;
   wire           rw_done     ;
   wire           rw_result   ;

iic_dri u_iic_dri (
    .clk                (     sys_clk        ),
    .rst                (     sys_rst        ),
    .i2c_begin          (     i2c_begin      ),
    .addr_bit           (     addr_bit       ),
    .i2c_rw             (     i2c_rw         ),
    .i2c_addr           (     i2c_addr       ),
    .i2c_wdata          (     i2c_data_w     ),
    .i2c_rdata          (     i2c_rdata      ),
    .dri_clk            (     dri_clk        ),
    .i2c_done           (     i2c_done       ),
    .i2c_ack            (     i2c_ack        ),
    .scl                (     scl            ),
    .sda                (     sda            )
);

e2prom_rw u_e2prom_rw(
        
     .dri_clk           (      dri_clk                 ),
     .i2c_data_r        (      i2c_rdata               ),
     .i2c_done          (      i2c_done                ),
     .i2c_ack           (      i2c_ack                 ),
     .rst               (      sys_rst                 ),
     .i2c_begin         (      i2c_begin               ),
     .addr_bit          (      addr_bit                ),
     .i2c_rw            (      i2c_rw                  ),
     .i2c_addr          (      i2c_addr                ),
     .i2c_data_w        (      i2c_data_w              ),
     .rw_done           (      rw_done                 ),
     .rw_result         (      rw_result               )

);

rw_result_led u_rw_result_led(


     .dri_clk         (   dri_clk        ),
     .rst             (   sys_rst        ),
     .rw_done         (   rw_done        ),
     .rw_result       (   rw_result        ),
     .led             (   led        )
);
ila_0 u_ila_0 (
	.clk(sys_clk), // input wire clk


	.probe0(led), // input wire [0:0]  probe0  
	.probe1(rw_result), // input wire [255:0]  probe1 
	.probe2(done_flag), // input wire [255:0]  probe2
    .probe3(i2c_addr), // input wire [255:0]  probe3 
	.probe4(i2c_data_r) // input wire [255:0]  probe4
);

endmodule

ila是我调用的探针

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: UG585-Zynq-7000-TRM是一份关于Xilinx Zynq-7000 SoC处理器的技术参考手册。Zynq-7000 SoC处理器是一款配备ARM Cortex-A9双核处理器和Xilinx可编程逻辑部分(PL)的可扩展平台,旨在为嵌入式系统开发者提供卓越的灵活性和可编程性。 TRM代表Technical Reference Manual(技术参考手册),其中包含了该处理器的体系结构和功能特性的详细描述,包括处理器内部的各种模块、外设、总线结构等等。该手册的主要目的是为开发者提供全面的指导和支持,从而加速其系统设计和开发应用,减少错误和风险。 UG585-Zynq-7000-TRM手册的内容包括Zynq-7000的基本特征、处理器硬件和软件架构、可编程逻辑PL和PS之间的通信、外设接口和寄存器映射、中断控制等内容。开发者可以根据手册中的详细说明,了解Zynq-7000的构成和功能,从而基于此设计和实现自己的应用。 总之,UG585-Zynq-7000-TRM手册是一份非常重要的技术参考资料,为开发者提供全面的指导和支持,促进了Zynq-7000处理器的应用和拓展,也为未来的嵌入式系统设计提供了参考和借鉴。 ### 回答2: UG585是Xilinx公司发布的Zynq-7000系列技术手册,全称为“Zynq-7000 All Programmable SoC Technical Reference Manual”。Zynq-7000系列是Xilinx公司推出的一款功能强大的FPGA芯片,集成了双核ARM Cortex-A9处理器和可编程逻辑资源,支持高性能中间件、操作系统和外围设备的支持。该手册详细介绍了Zynq-7000系列SoC的架构、功能、性能、测试和验证、软件和硬件开发等方面的知识。他对于学习、使用和开发Zynq-7000 SoC具有非常重要的意义,使得开发人员能够深入了解这个芯片的细节,掌握它的特性和功能,以便更加高效地使用它进行开发。如果你想要学习和使用Zynq-7000 SoC,UG585是一个非常重要的参考文献,值得认真阅读和研究。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值