Menu Close

I2c 协议实现EEPROM 仿真

 

本文中以EEPROM为例, 通过i2c接口实现对EEPROM 的读写操作。本文中使用AT24c02 作为EEPROM 测试芯片, 同时AT24C02 芯片在PRA006 和PRX100t开发板中都有使用。 AT24C02是一颗价格便宜,使用广泛的I2C 接口的EEPROM 芯片。整个芯片有256 bytes 的存储单元, 支持页写(即每次最多可以写入8bytes)。每次I2C写入数据后需要等待5ms,用于芯片内部逻辑将数据写入的存储单元中。读操作没有这些限制, 可以从任意地址开始,连续读取数据。芯片地址可以通过外部地址pin 来进行配置。在本次实验中, 我们认为外部地址pin全部接地,即地址为 7’b0101_000。在实际具体项目中,需要查看硬件图纸,确定最终的芯片地址。{7’b0101_A2A1A0}

1 工程概述

     工程要求和内容

  •  本工程主要实现对AT24C02D 芯片的读写操作。
  • 产品定义:本工程实现对芯片的页写及随即地址读与连续读操作:对地址0-7依次写入8笔数据;然后依次读出。
  • 字符帧与命令关系:工程将iic帧结构分成一段一段的具体命令, 根据命令形成完整的读写iic 帧。具体命令对应的数据帧结构如下:

 

 

图1

  • 通信:iic 通讯,速率100k/400k

组织架构设计

图2

模块功能概述

底层iic_engine模块

负责将应用层模块传递的数据按照规定的速率写入到AT24C02D芯片的指定位置;然后从芯片指定位置读出数据发送到应用层模块,实现完整的iic phy功能,通过数据总线方式和外设通讯。

应用层iic_app模块

将需要写入芯片的数据发送给iic_cmd模块;然后接收iic_cmd模块从芯片指定位置读出的数据,读出的数据后期可根据实际需要使用。

时钟域pll模块

整个工程的时钟域模块。例化IP core, 生成工程需要的时钟。

动态配置cpu_test模块

本次实验中作为测试使用,实现cpu对iic通讯速率寄存器的读写功能,改变iic相关参数。后期可用cpu 总线模块所替代(包括axi 总线、riscv 总线等)。

2 模块功能详情

pll 模块

例化IP core,将晶振产生的50m时钟作为输入时钟,输出100m 时钟作为工程的系统时钟,!locked 作为整个工程的复位信号。

cpu_test 模块

模拟cpu总线,发送读写命令,寄存器地址,发送写外设数据。本工程模拟cpu配置div_in_r寄存器,同时可以读取这个寄存器内容

代码流程图如图3 所示:

图3

代码详解

`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: 
// Engineer: 
// 
// Create Date: 2022/07/02 14:12:28
// Design Name: 
// Module Name: clk_en
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//////////////////////////////////////////////////////////////////////////////////


module scl_en #
(
    parameter DIV_ADDR = 32'hFFFF_0001,       //寄存器地址
    parameter CLK_F = 100_000_000,            //默认输入频率值
    parameter SPEED = 100_000                 //默认的通讯速率
)
(
    input         sys_clk,
    
    input [31:0]  div_addr,
    input [31:0]  div_in,
    input         div_en,
    input         div_rd,
    output [31:0] div_dout,
    
    output reg    clk_en,
    output reg    clk_en_half,
    
    input         rst
);

localparam DIV_COE = CLK_F/(SPEED * 2) - 1;                    //分频系数


reg [31:0] cnt;
reg [31:0] div_in_r;
reg        rst_r;                                         //使用动态可编程分频系数时,对分频器进行复位

//在特定寄存器地址选通情况下,由div_en 将动态可编程分频系数div_in 写入分频寄存器div_in_r,并产生复位信号对计数器复位。
always@(posedge sys_clk or posedge rst)
if(rst)
begin
   div_in_r <= DIV_COE;
   rst_r <= 1'b1;
end
else
begin
    rst_r <= 1'b0;
    
    if( div_en && (div_addr == DIV_ADDR) )
    begin
        div_in_r <= div_in;
        rst_r <= 1'b1;                     
    end
end

//分频计数器,每次分频值变更计数器都需要复位,从0 开始计数。
always@(posedge sys_clk or posedge rst_r)
if(rst_r)
begin
    clk_en <= 0;
    clk_en_half <= 0;
    cnt <= 0; 
end
else
begin
    
    clk_en <= 1'b0;
    clk_en_half <= 1'b0;
    
    if(cnt == div_in_r[31:1])
       clk_en_half <= 1'b1;
    
    if(cnt == div_in_r)
    begin
        cnt <= 0;
        clk_en <= 1'b1;
    end
    
    else
       cnt <= cnt + 1;
end


// read register
reg [31:0] div_dout_r = 0;
always @ (posedge sys_clk or posedge rst)
if(rst) 
   div_dout_r <= 0;
else if( div_rd &&(div_addr == DIV_ADDR) )
   div_dout_r <= div_in_r;

assign div_dout = div_dout_r;


endmodule

 

端口信号解释:

        • sys_clk:时钟域模块传递进来的系统时钟
        • cpu_addr:需要对其他模块操作的寄存器地址
        • cpu_wr:寄存器写使能(每个bit控制cpu_din的每个字节,为1表示对应字节有效)
        • cpu_rd:读寄存器使能
        • cpu_din:写数据
        • cpu_dout:从其他模块读入的数据
        • rst:时钟域模块传递进来的复位信号

状态机详解:

初始化:rst对模块进行初始化操作。

状态0:等待20ms,给出写寄存器地址,写使能,写数据,调整到状态1

状态1:各种变量清0,停留在状态1。

scl_en 模块

端口信号解释:

      • sys_clk:时钟域模块传递进来的系统时钟
      • div_addr:需要对其他模块操作的寄存器地址
      • div_in:cpu 配置模块传递进来的写寄存器数据
      • div_en:写使能
      • div_rd:读寄存器使能
      • div_dout:从寄存器读出的数据,需要输出给cpu 模块
      • clk_en:iic 速率时钟使能信号
      • clk_en_half:对齐两个clk_en使能信号中心的使能信号(写数据时使用此信号)
      • rst:时钟域模块传递进来的复位信号

代码详解:

【1】写寄存器:

在特定寄存器地址选通情况下,由div_en 将动态可编程分频系数div_in 写入分频寄存器div_in_r,并产生复位信号对计数器复位。

【2】分频计数器:产生iic_phy需要的数据发送使能信号。每次分频值变更计数器都需要复位,从0 开始计数。同时生成1/4 相位的 clk_en_half 信号。这个信号用于控制输出SDA 信号。

iic_phy模块

本模块主要根据iic_cmd模块不同命令完成iic 通讯中数据帧不同部分的具体发送和读取

流程图

 

图4

代码详解

`timescale 1ns / 1ps


module iic_phy(
   input            sys_clk,
   
   input      [2:0] op_cmd,
   input            op_cmd_en,
   output reg       op_cmd_done,
   output reg       dev_ack,
   input            master_ack,
   input      [7:0] data_in,
   output reg [7:0] data_out,
   
   input            clk_en,
   input            clk_en_half,
   
   output reg       sda_t,
   output           sda_o,
   input            sda_i,
   
   output reg       scl_t,
   output           scl_o,
   input            scl_i,
   
   input            rst
);

/*
op_cmd[2:0] : 000 send start flag
               001 send dev address + wr/rd (data_in)
               010 send data
               011 receive device ack /nack
               111 send stop flag
               100 receive data (data_out)
               101 send master ack/nack
               110 reserved
               111 send stop flag
*/


assign scl_o = 1'b0;
assign sda_o = 1'b0;


reg [7:0] i2c_st;
reg [2:0] sft_count;
reg [7:0] i2c_wr_data;             //将需要写的数据存入本地寄存器中


always@(posedge sys_clk or posedge rst)
if(rst)
begin
    op_cmd_done <= 0;
    dev_ack <= 0;
    data_out <= 0;
    
    scl_t <= 1'b1;
    sda_t <= 1'b1;
    
    sft_count <= 7;
    i2c_wr_data <= 0;
    
    i2c_st <= 0;
end
else 
case(i2c_st)
0:
begin
    sft_count <= 7;
    op_cmd_done <= 1'b0;
    
    dev_ack <= 0;
    
    i2c_wr_data <= data_in;
    
    case({op_cmd_en,op_cmd[2:0]})
    4'b1000: i2c_st <= 1;     // start flag
    4'b1001: i2c_st <= 4;     // write device addr + wr/rd
    4'b1010: i2c_st <= 4;     // write data
    4'b1011: i2c_st <= 7;     // read ack/nack from device
    4'b1100: i2c_st <= 9;     // read data
    4'b1101: i2c_st <= 12;    // send ack/nack to device
    4'b1111: i2c_st <= 15;    // stop flag
    default: i2c_st <= 0;
    endcase 
end
1:      //start flag
begin
    if(clk_en)
    begin
        scl_t <= 1'b1;
        sda_t <= 1'b1;
        i2c_st <= 2;
    end
end
2:
begin
    scl_t <= 1'b1;
    
    if(clk_en)
    begin
        sda_t <= 1'b0;
        i2c_st <= 3;
    end
end
3:
begin
    if(clk_en)
    begin
        scl_t <= 1'b0;
        op_cmd_done <= 1'b1;
        i2c_st <= 0;
    end
end

4:  //write 
begin
    if(clk_en)
    begin
        scl_t <= 1'b0;
        i2c_st <= 5;
    end
end
5:
begin
    if(clk_en_half)
       sda_t <= i2c_wr_data[sft_count];
       
    if(clk_en)
    begin
        scl_t <= 1'b1;
        sft_count <= sft_count - 1;
        i2c_st <= 4;
        
        if( sft_count == 0 )
            i2c_st <= 6;
    end
end
6:
begin
    if(clk_en)
    begin
        scl_t <= 1'b0;
        op_cmd_done <= 1'b1;
        i2c_st <= 0;
    end
end
7:     //read ack/nack from device
begin
    if( clk_en_half )
        sda_t <= 1'b1;
        
    if(clk_en)
    begin
        scl_t <= 1'b1;
        dev_ack <= sda_i;
        i2c_st <= 8; 
    end
end
8:                           
begin
    if(clk_en)
    begin
        scl_t <= 1'b0;
        op_cmd_done <= 1'b1;
        i2c_st <= 0;
    end
end

9:  //read data 
begin
    sda_t <= 1'b1;
    if(clk_en)
    begin 
        scl_t <= 1'b0;
        i2c_st <= 10;
    end
end
10:
begin
    if(clk_en)
    begin
        scl_t <= 1'b1;
        data_out[sft_count] <= sda_i;
        
        sft_count <= sft_count - 1;
        
        i2c_st <= 9;
        
        if(sft_count == 0)
           i2c_st <= 11;
    end
end
11:
begin
    if( clk_en )
    begin
        scl_t <= 1'b0;
        op_cmd_done <= 1'b1;
        i2c_st <= 0;
    end
end

12:  //send ack/nack to device
begin
    if( clk_en_half )
        sda_t <=  master_ack;
    if( clk_en )
    begin
        scl_t <= 1'b1;
        i2c_st <= 13;
    end
end
13:
begin
    if( clk_en )
    begin
        scl_t <= 1'b0;
        i2c_st <= 14;
    end
end
14:
begin
    if(clk_en_half) 
    begin
        scl_t <= 1'b0;
        sda_t <= 1'b1;                   //兼容性设计, 防止pcb layout 时, sda 延迟,造成事实上的start 信号
        op_cmd_done <= 1'b1;
        i2c_st <=0;
    end
end

15: //stop flag
begin
    sda_t <= 1'b0;
    
    if(clk_en)
    begin
        scl_t <= 1'b1;
        i2c_st <= 16;
    end
end
16:
begin
    if(clk_en)
    begin
        sda_t <= 1'b1;
        i2c_st <= 17;
    end
end
17:
begin
    if(clk_en)
    begin
        sda_t <= 1'b1;
        scl_t <= 1'b1;
        op_cmd_done <= 1'b1;
        i2c_st <= 0;
    end
end
default:i2c_st <= 0;
endcase




endmodule

 

端口信号解释:

      • sys_clk:时钟域模块传递进来的系统时钟
      • op_cmd:数据帧结构命令(不同命令处理数据帧不同部分)
      • op_cmd_en:命令使能信号(系统时钟单脉冲高电平信号)
      • op_cmd_done:完成命令所指的任务后的完成指示信号
      • dev_ack:从设备(本工程指AT24C02D芯片)返回的ack信号
      • master_ack:主设备反馈的ack信号
      • data_in:写入从设备的数据
      • data_out:从从设备读出的数据
      • clk_en:iic 数据发送时钟使能信号
      • clk_en_half:对齐两个clk_en使能信号中心的使能信号(写数据时使用此信号)
      • sda_t:sda总线上三态门输入输出的选择信号
      • sda_o:sda总线上三态门的输出信号
      • sda_i:sda总线上三态门的输入信号
      • scl_t:scl总线上三态门输入输出的选择信号
      • scl_o:scl总线上三态门的输出信号
      • scl_i:scl总线上三态门的输入信号
      • rst:时钟域模块传递进来的复位信号

状态机解释:

帧起始操作

图5

状态0:根据命令使能及具体命令,跳转到对应的状态

状态1:来一个clk_en信号,将scl_t与sda_t置位,跳转到状态2

状态2:,首先将scl_t置位,来一个clk_en信号,将sda_t复位,跳转到状态3

状态3:来一个clk_en信号,将scl_t复位,同时op_cmd_done信号置位,表示本命令任务完成,跳转到状态0。

写数据/ 写地址+WR/RD 操作

 

图6

状态4:来一个clk_en信号,将scl_t复位,跳转到状态5

状态5:来一个clk_en_half信号,将需要写的数据由高到低逐位发送到sda_t,来一个clk_en信号,将发送计数器减1,跳转到状态4。如果此时计数器为0,说明8bit发送完毕,跳转到状态6。

状态6:来一个clk_en信号,将scl_t复位,同时op_cmd_done信号置位,表示本命令任务完成,跳转到状态0。

从设备中读取ACK/NACK 操作

图7

状态7:来一个clk_en_half信号,将sda_t置位,来一个clk_en信号,scl_t置位,同时将总线上数据接收到dev_ack,跳转到状态8

状态8:来一个clk_en信号,将scl_t复位,同时op_cmd_done信号置位,表示本命令任务完成,跳转到状态0。

从设备中读取数据操作

图8

状态9:首先将sda_t置位,来一个clk_en_half信号,将scl_t复位,跳转到状态10

状态10:来一个clk_en信号,将总线上的数据sda_i接收到数据data_out对应bit位,同时计数器减1,跳转到状态9,如果计数器计数到0,说明接收了8bit数据,跳转到状态11。

状态11:来一个clk_en信号,将scl_t复位,同时op_cmd_done信号置位,表示本命令任务完成,跳转到状态0。

控制器发送ACK/NACK 到设备 操作

 

图9

状态12:来一个clk_en_half信号,将主设备ack通过sda_t发送到总线上,来一个clk_en信号,将scl_t置位,跳转到状态13

状态13:来一个clk_en信号,将scl_t复位跳转到状态14

状态14:来一个clk_en_half信号,将scl_t复位,sda_t置位,同时op_cmd_done信号置位,表示本命令任务完成,跳转到状态0。

发送STOP位 操作

 

图10

状态15:首先将sda_t复位,来一个clk_en信号,将scl_t置位,跳转到状态16

状态16:来一个clk_en信号,将sda_t置位,跳转到状态17

状态17:来一个clk_en_half信号,将scl_t置位,sda_t置位,同时op_cmd_done信号置位,表示本命令任务完成,跳转到状态0。

iic_phy 模块主要实现i2c 物理层信号传输, 根据接收到的不同命令,实现start,stop ,读,写,发送ack/nack,接收ack/nack 等信息。

iic_cmd 模块

根据产品定义,将应用层需要写入的数据和对应的命令,命令使能发送给底层iic_phy。

流程图

图11

代码详解

`timescale 1ns / 1ps


module iic_cmd#
(
   parameter DEV_ADDR = 7'b101_0000
)
(
   input            sys_clk,
                    
   input            op_cmd_done,
   input            dev_ack,
   input      [7:0] data_out,
   
   output reg [2:0] op_cmd,
   output reg       op_cmd_en,
   output reg       master_ack,
   output reg [7:0] data_in,
   
   input      [7:0] wr_data,
   input            wr_data_valid,
   input            wr_data_start,
   input            wr_data_end,
   output reg       wr_data_done,
   output reg [7:0] rd_data,
   output reg       rd_data_valid,
    
   output reg       i2c_error,
   
   input            rst

);
 
localparam I2C_OP_START    = 3'b000;
localparam I2C_OP_DEV_ADDR = 3'b001;
localparam I2C_OP_WR_DATA  = 3'b010;
localparam I2C_OP_RD_ACK   = 3'b011;
localparam I2C_OP_RD_DATA  = 3'b100;
localparam I2C_OP_WR_ACK   = 3'b101;
localparam I2C_OP_RESVED   = 3'b110;
localparam I2C_OP_STOP     = 3'b111;

reg [31:0] cnt;
reg [3:0] data_count;
reg [7:0]  st;

reg [7:0] wr_data_r;
//=====================
always@(posedge sys_clk or posedge rst)
if(rst)
begin
   wr_data_r <= 0;
end
else
begin
    if((op_cmd == I2C_OP_RD_ACK) && wr_data_valid )
        wr_data_r <= wr_data;
end

//======================
always@(posedge sys_clk or posedge rst)
if(rst)
begin
    cnt <= 0;
    data_count <= 0;
    op_cmd <= 0;
    op_cmd_en <= 0;
    master_ack <= 0;
    data_in <= 0;
    
    wr_data_done <= 1'b0;
    rd_data <= 1'b0;
    rd_data_valid <= 1'b0;
    
    i2c_error <= 0;
    st <= 0;
end
else
case(st)
0: 
begin
    op_cmd_en <= 1'b0;
    i2c_error <= 0;
    
    rd_data_valid <= 1'b0;
    wr_data_done <= 1'b0;
    
    if(wr_data_start)                    //写数据开始信号
    begin
        op_cmd_en <= 1'b1;
        op_cmd <= I2C_OP_START;                           
        st <= 1;
    end
end
//=====================================write
1:                //write device addr + wr
begin
    op_cmd_en <= 1'b0;
    
    if(op_cmd_done)
    begin
        op_cmd_en <= 1'b1;
        op_cmd <= I2C_OP_DEV_ADDR;
        data_in <= {DEV_ADDR,1'b0};         
        st <= 2;
    end
end
2:               // read ack/nack from device
begin
    op_cmd_en <= 1'b0;
    
    if(op_cmd_done)
    begin
        op_cmd_en <= 1'b1;
        op_cmd <= I2C_OP_RD_ACK;
        st <= 3;
    end
end
3:               // write memory addr
begin
    op_cmd_en <= 1'b0;
    
    if(op_cmd_done)
    begin
        if(!dev_ack)
        begin
            op_cmd_en <= 1'b1;
            op_cmd <= I2C_OP_WR_DATA;                  
            data_in <= 8'h00;
            wr_data_done <= 1'b1;
            st <= 4;
        end
        else
        begin
            op_cmd_en <= 1'b1;
            op_cmd <= I2C_OP_STOP;
            i2c_error <= 1'b1;
            st <= 19;                          // 从设备ack/nack 错误跳转状态
        end
    end
end
4:              // read ack/nack from device
begin
    op_cmd_en <= 1'b0;
    wr_data_done <= 1'b0;
    
    if(op_cmd_done)
    begin
        op_cmd_en <= 1'b1;
        op_cmd <= I2C_OP_RD_ACK;                
        st <= 5;
        if(wr_data_end)
        begin
           st <= 6;
           cnt <= 0;
        end
    end
end
5:            // write data
begin
    op_cmd_en <= 1'b0; 

    if(op_cmd_done)
    begin
        if(!dev_ack)
        begin
            wr_data_done <= 1'b1;
            op_cmd_en <= 1'b1;
            op_cmd <= I2C_OP_WR_DATA;               
            data_in <= wr_data_r;               //data_in <= cnt;
            data_count <= data_count + 1;
            st <= 4;
        end
        else
        begin
            op_cmd_en <= 1'b1;
            op_cmd <= I2C_OP_STOP;
            i2c_error <= 1'b1;
            st <= 19;                          // 从设备ack/nack 错误跳转状态
        end
    end
end
6:              //stop flag
begin
    op_cmd_en <= 1'b0;
    
    if(op_cmd_done)
    begin
        op_cmd_en <= 1'b1;
        op_cmd <= I2C_OP_STOP;              
        st <= 7;
    end
end
7:
begin
    op_cmd_en <= 1'b0;
    
    if(op_cmd_done)
       st <= 8;
end
//=========================read
8:            // start flag
begin
    cnt <= cnt + 1;
    if(cnt == 500_000)
    begin
        cnt <= 0;
        op_cmd_en <= 1'b1;
        op_cmd <= I2C_OP_START;                    
        st <= 9;                              
    end                                       
end                                           
9:           // write device addr + wr/rd                                    
begin                                         
    op_cmd_en <= 1'b0;                        
                                              
    if(op_cmd_done)                           
    begin                                     
        op_cmd_en <= 1'b1;                    
        op_cmd <= I2C_OP_DEV_ADDR;                     
        data_in <= {DEV_ADDR,1'b0};            
        st <= 10;                             
    end                                       
end                                           
10:         // read ack/nack from device              
begin                                         
    op_cmd_en <= 1'b0;                        
                                              
    if(op_cmd_done)                           
    begin                                     
        op_cmd_en <= 1'b1;                    
        op_cmd <= I2C_OP_RD_ACK;                      
        st <= 11;
    end
end
11:           // write memory addr
begin
    op_cmd_en <= 1'b0;
    
    if(op_cmd_done)
    begin
        if(!dev_ack)
        begin
            op_cmd_en <= 1'b1;
            op_cmd <= I2C_OP_WR_DATA;                     
            data_in <= 8'h00;
            st <= 12;
        end
        else
        begin
            op_cmd_en <= 1'b1;
            op_cmd <= I2C_OP_STOP;
            i2c_error <= 1'b1;
            st <= 19;                          // 从设备ack/nack 错误跳转状态
        end
    end
end
12:            // read ack/nack from device
begin
    op_cmd_en <= 1'b0;
    
    if(op_cmd_done)
    begin
        op_cmd_en <= 1'b1;
        op_cmd <= I2C_OP_RD_ACK;                    
        st <= 13;
    end
end
13:          //restart
begin
    op_cmd_en <= 1'b0;
    
    if(op_cmd_done)
    begin
        if(!dev_ack)
        begin
            op_cmd_en <= 1'b1;
            op_cmd <= I2C_OP_START;                      
            st <= 14;
        end
        else
        begin
            op_cmd_en <= 1'b1;
            op_cmd <= I2C_OP_STOP;
            i2c_error <= 1'b1;
            st <= 19;                          // 从设备ack/nack 错误跳转状态
        end
    end
end
14:           // write device addr + rd
begin
    op_cmd_en <= 1'b0;
    
    if(op_cmd_done)
    begin
        op_cmd_en <= 1'b1;
        op_cmd <= I2C_OP_DEV_ADDR;
        data_in <= {DEV_ADDR,1'b1};            
        st <= 15;
    end
end
15:         // read ack/nack from device
begin
    op_cmd_en <= 1'b0;
    
    if(op_cmd_done)
    begin
        op_cmd_en <= 1'b1;
        op_cmd <= I2C_OP_RD_ACK;                       
        st <= 16;
    end
end
16:         // read data
begin
    op_cmd_en <= 1'b0;
    rd_data_valid <= 1'b0;
    
    if(op_cmd_done)
    begin
        if(!dev_ack)
        begin
            op_cmd_en <= 1'b1;
            op_cmd <= I2C_OP_RD_DATA;                    
            st <= 17;
        end
        else
        begin
            op_cmd_en <= 1'b1;
            op_cmd <= I2C_OP_STOP;
            i2c_error <= 1'b1;
            st <= 19;                          // 从设备ack/nack 错误跳转状态
        end
    end
end
17:            // send ack/nack to device
begin
    op_cmd_en <= 1'b0;
    master_ack <= 1'b0;
    
    if(op_cmd_done)
    begin
        rd_data <= data_out;
        rd_data_valid <= 1'b1;
        
        op_cmd_en <= 1'b1;
        op_cmd <= I2C_OP_WR_ACK;                     
        st <= 16;
        
        cnt <= cnt + 1;
        
        if(cnt[3:0] == data_count - 1)
        begin
            cnt <= 0;
            master_ack <= 1'b1;
            st <= 18;
        end
    end
end
18:            //stop
begin
    op_cmd_en <= 1'b0;
    rd_data_valid <= 1'b0;
    
    if(op_cmd_done)
    begin
        op_cmd_en <= 1'b1;
        op_cmd <= I2C_OP_STOP; 
        data_count <= 1'b0;        
        st <= 19;
    end
end
19:
begin
    master_ack <= 1'b0;   
    i2c_error <= 1'b0;
    op_cmd_en <= 1'b0;
    
    if(op_cmd_done)
    begin
       wr_data_done <= 1'b1; 
       st <= 0;
    end
end
default:st <= 0;
endcase


endmodule

 

端口信号解释:

      • sys_clk:时钟域模块传递进来的系统时钟
      • op_cmd_done:完成命令所指的任务后的完成指示信号
      • dev_ack:从设备(本工程指AT24C02D芯片)返回的ack信号
      • data_out:
      • op_cmd:数据帧结构命令(不同命令处理数据帧不同部分)
      • op_cmd_en:命令使能信号(系统时钟单脉冲高电平信号)
      • master_ack:主设备反馈的ack信号
      • data_in:写入从设备的数据
      • wr_data:应用层传递进来需要写入从设备的数据
      • wr_data_valid:应用层传递进来与数据对应的数据有效信号
      • wr_data_start:应用层传递进来表示iic_cmd模块状态机可以开始的开始信号
      • wr_data_end:应用层传递进来表示已经无数据需要写入的结束信号
      • wr_data_done:表示收到从设备ack,应用层可以准备需要写入的数据了/同时用作数据读取完成指示信号
      • rd_data:从总线上读取的从设备反馈的数据
      • rd_data_valid:读取数据有效信号
      • i2c_error:从设备反馈ack 错误指示
      • rst:时钟域模块传递进来的复位信号

状态机解释:

状态0:接收到应用层的START信号后,发送命令使能及开始命令,跳转到状态1

1) write device addr + wr

状态1:当phy层操作完毕,发送命令使能及写设备地址+写数据,写设备地址命令,跳转到状态2

2) read ack/nack from device

状态2:当phy层操作完成,发送命令使能及读从设备ack命令,跳转到状态3

3) write 24C02 memory addr

状态3:当phy层操作完成,并且收到的从设备ack为低表示应答正确,发送写数据命令,命令使能,需要写入的数据,跳转到状态4;如果应答为1,则应答错误,跳转到状态19,同时给出错误指示,结束命令及命令使能

4) read ack/nack from device

状态4:当phy层操作完成,发送命令使能及读从设备ack命令,跳转到状态5;如果此时收到数据发送结束标志,则发送命令使能及读从设备应答命令,跳转到状态6

5) write data

状态5:当phy层操作完成,并且收到的从设备ack为低表示应答正确,发送写数据命令,命令使能,需要写入的数据,同时给应用层wr_data_done信号表示可以准备下一笔数据了,跳转到状态4;如果应答为1,则应答错误,跳转到状态19,同时给出错误指示,结束命令及命令使能.

6) stop flag

状态6:当phy层操作完成,发送命令使能,结束命令,跳转到状态7

7) 写操作完成

状态7:当phy层操作完成,跳转到状态8

 

8) 读操作, 发送start flag

状态8:等待5ms(每次写操作从设备需要5ms的时间才能真正将数据写入到芯片内部),给命令使能,开始命令,跳转到状态9

9) write device addr + wr/rd

状态9:当phy层操作完成,给命令使能,写设备地址命令,写设备地址+写位跳转到状态10

10) read ack/nack from device

状态10:当phy层操作完成,发送命令使能及读从设备ack命令,跳转到状态11;

11) write 24c02 memory addr

状态11:当phy层操作完成,并且收到的从设备ack为低表示应答正确,发送写数据命令,命令使能,需要写入的数据,跳转到状态12;如果应答为1,则应答错误,跳转到状态19,同时给出错误指示,结束命令及命令使能.

12) read ack/nack from device

状态12:当phy层操作完成,发送命令使能及读从设备ack命令,跳转到状态13;

13) restart

状态13:当phy层操作完成,并且收到的从设备ack为低表示应答正确,发送命令使能及开始命令,跳转到状态14;如果应答为1,则应答错误,跳转到状态19,同时给出错误指示,结束命令及命令使能。

14) write device addr + rd

状态14:当phy层操作完成,发送命令使能及写设备地址命令,给出设备地址+读位,跳转到状态15。

15) read ack/nack from device

状态15:当phy层操作完成,发送命令使能及读从设备ack命令,跳转到状态16;

16) read data

状态16: 当phy层操作完成,并且收到的从设备ack为低表示应答正确,发送命令使能及读命令,跳转到状态17;如果应答为1,则应答错误,跳转到状态19,同时给出错误指示,结束命令及命令使能.。

17) send ack/nack to device

状态17:当phy层操作完成,表示数据已经拿到,将读取数据输出同时给出数据有效信号,如果数据未读取完毕则继续给命令使能及写主设备应答命令,跳转到状态16,否则给命令使能,写主设备应答命令,主设备应答设置为1,跳转到状态18。

18) stop

状态18:当phy层操作完成,给命令使能及结束命令,应用层可以开始新的读写操作了,跳转到状态19。

状态19:从设备应答错误指示状态,便于错误查找, 等待iic_phy 操作结束,同时给出读写完成指示信号。跳转到状态0

iic_app 模块

代码详解

`timescale 1ns / 1ps


module iic_app
(
   input             sys_clk,

   output reg  [7:0] wr_data,
   output reg        wr_data_valid,
   output reg        wr_data_start,
   output reg        wr_data_end,
   input             wr_data_done,
   input       [7:0] rd_data,
   input             rd_data_valid,

   input             rst
);

reg [7:0] app_st;
reg [31:0] cnt;

always@(posedge sys_clk or posedge rst)
if(rst)
begin
    wr_data <= 0;
    wr_data_valid <= 0;
    wr_data_start <= 0;
    wr_data_end <= 0;
    
    cnt <= 1;
    app_st <= 0;
end
else
case(app_st)
0:
begin
    wr_data <= 0;
    wr_data_valid <= 0;
    wr_data_start <= 0;
    wr_data_end <= 0;

    cnt <= cnt + 1;
    
    if(&cnt[15:0])
    begin
        cnt <= 1;
        wr_data_start <= 1'b1;
        app_st <= 1;
    end
end
1:
begin
    wr_data_start <= 1'b0;
    
    if(wr_data_done)
    begin
        wr_data_valid <= 1'b1;
        wr_data <= cnt;
        
        cnt <= cnt + 1;
        
        if(cnt == 9)
        begin
            wr_data_end <= 1'b1;
            wr_data_valid <= 1'b0;
            wr_data <= 0;

            app_st <= 2;
        end
    end
end
2:
begin
    if(wr_data_done)
    begin
       app_st <= 0;
       wr_data_end <= 1'b0;
    end
end
default:app_st <= 0;
endcase

endmodule

 

端口信号解释:

      • sys_clk:时钟域模块传递进来的系统时钟
      • wr_data:需要写入从设备的数据
      • wr_data_valid:数据对应的数据有效信号
      • wr_data_start:表示iic_cmd模块状态机可以开始的开始信号
      • wr_data_end:表示已经无数据需要写入的结束信号
      • wr_data_done:表示收到从设备ack,应用层可以准备需要写入的数据了/同时用作数据读取完成指示信号
      • rd_data:从总线上读取的从设备反馈的数据
      • rd_data_valid:读取数据有效信号
      • rst:时钟域模块传递进来的复位信号

状态机解释:

状态0:等待一段时间跳转到状态1,每次的读写做一个分隔,便于观察

状态1:如果iic_cmd模块准备好接收写数据了,就开始给写数据及数据有效使能,发送8笔数据,发完后给数据发送完毕结束信号,跳转到状态2

状态2:等待iic_cmd模块读取完成,然后跳转到状态0等待下一次读写

3 仿真下板调试

仿真代码

`timescale 1ns / 1ps



module tb_iic();

parameter PERIOD = 20;
parameter DIV_ADDR = 32'hFFFF_0001;
parameter DEV_ADDR = 7'b1010_000;

reg inclk;
initial
begin
   inclk = 0;
   #20;
   forever #(PERIOD / 2 ) inclk = ~ inclk;
end


wire SDA;
wire SCL;

PULLUP SDA_inst ( .O(SDA)  );
PULLUP SCL_inst ( .O(SCL)  );


top top_inst
(

 .inclk     ( inclk ),
 .SDA       ( SDA ),
 .SCL       ( SCL )
);

AT24C02D  #
(.SLAVE_ADDRESS(DEV_ADDR))
AT24C02D_inst
(
    .SDA    (SDA),
    .SCL    (SCL),
    .WP     (0)
);


endmodule

 

本实验主要测试对芯片的0-7地址依次写入01,02,03,04,05,06,07,08 数据,然后再依次读出(随机地址读+连续读)。采用芯片模型仿真,用原语将输出高电平与高阻进行转换。前20ms iic 速率采用100k, 之后速率变为400k。从最后仿真结果看写入数据与读出数据一致,并且可以重复读写,与设计初衷一致。

图12

 

图13

图14

图15

图16

 

vivado 参考代码:

Posted in FPGA, Verilog, 教材与教案, 文章

发表回复

相关链接