IIC读写EEPROM

本文详细介绍了IIC协议的基本概念和时序要求,并探讨了EEPROM的读写时序,包括写操作和不同类型的读操作。接着,文章展示了在FPGA项目中设计IIC状态机及代码实现的步骤,包括接口模块、控制模块和顶层设计的仿真波形,最终在板级验证中取得了成功的效果。
摘要由CSDN通过智能技术生成

一.协议介绍

1.IIC概念

IIC是一种两线式串行总线,由数据线SDA时钟线SCL构成通信线路,既可用于发送数据,也可接受数据,是一种半双工通信协议
总线上的主设备和从设备之间以字节为单位进行双向的数据传输。
多用于主机和从机在数据量不大且传输距离短的场合下的主从通信。主机启动总线,并产生时钟用于传送数据,此时任何接收数据的器件均被认为是从机。I2C器件一般采用开漏结构与总线相连,所以I2C_SCL和I2C_SDA均需接上拉电阻,也正因此,当总线空闲时,这两条线路都处于高电平状态,当连到总线上的任一器件输出低电平,都将使总线拉低。
总线上的每一个设备都可以作为主设备或从设备,而且每一个设备都会对应一个唯一的地址(可以从12C器件数据手册得知),主从设备之间就是通过这个地址来确定与哪个器件进行通信。

2.时序要求

  1. 空闲状态
    空闲状态,IIC两条总线被规定都处在高电平,此时各个器件的输出级场效应管均处在截止状态,即释放总线,由两条信号线各自的上拉电阻把电平拉高。

  2. 起始信号
    在SCL保持高电平期间,SDA的电平被拉低,称为 I2C总线的起始信号,标志着一次数据传输的开始。起始信号由主机主动建立,在建立该信号之前I2C总线必须处于空闲状态。

  3. 停止信号
    在SCL处在高电平期间。SDA被释放,被上拉到高电平,成为IIC总线停止信号,标志着一次数据传输终止。 停止信号由主机主动发送,结束后,IIC总线返回空闲状态。

  4. 数据传输
    在IIC总线传输数据时,必须在SCL处在低电平期间才允许SDA数据改变,在SCL处在高电平期间,SDA被要求必须保持稳定,在SCL高电平期间进行采样,如果SDA处于高电平则为1,为低电平则为0

  5. 应答信号
    12C总线上的所有数据都是以字节传送的,发送端每发送一个字节,就必须在第9个SCL脉冲期间释放SDA,由接收端反馈一个应答信号。应答信号为低电平时,称为有效应答位(ACK),表示接收端成功接收了该字节;应答信号为高电平时,称为非应答位(NACK),表示接收端接收该字节失败。对于反馈有效应答位ACK的要求是,接收端在第9个时钟脉冲之前的低电平期间将SDA拉低,并且确保在该时钟周期的高电平期间保持低电平。如果接收端是主控端,则在它收到最后一个字节后,发送一个NACK信号,以通知被控发送端结束数据发送,并释放SDA线,以便主控接收器发送停止信号。在这里插入图片描述

二.EEPROM

1.芯片简介

在这里插入图片描述
在这里插入图片描述

1.读写时序

1.写操作

在这里插入图片描述
byte写:起始位——>设备地址(4bit+3bit(2bit不关心+1bit块选择位)+1bit读/写)——>相应信号——>数据地址(8bits)——>相应信号——>8bits数据——>响应信号——>停止位
页写:起始位——>设备地址(4bit+3bit(2bit不关心+1bit块选择位)+1bit读/写)——>相应信号——>数据地址(8bits)——>相应信号——>8bits数据——>响应信号——>……——>>8bits数据——>响应信号——>停止位;
注意:页写中每一页最多写入16byte数据,如果超过的的内部指针会滚动,将之前写入的数据从头向后覆盖

2.读操作(当前地址读,随机读,顺序读)

在这里插入图片描述
当前地址读:byte写:起始位——>设备地址(4bit+3bit(2bit不关心+1bit块选择位)+1bit读/写)——>响应信号——>接收数据8bits——>不发送响应信号——>发送停止位
(注意当前地址读是在上一个读写操作的地址上+1,也就是上次读写地址的写一个地址)
随机读:起始位——>设备地址(4bit+3bit(2bit不关心+1bit块选择位)+1bit读/写)——>相应信号——>数据地址(8bits)——>起始位——>设备地址(4bit+3bit(2bit不关心+1bit块选择位)+1bit读/写)——>相应信号——>接收8bits数据——>不发送响应信号——>停止位
页读:起始位——>设备地址(4bit+3bit(2bit不关心+1bit块选择位)+1bit读/写)——>相应信号——>数据地址(8bits)——>起始位——>设备地址(4bit+3bit(2bit不关心+1bit块选择位)+1bit读/写)——>相应信号——>接收8bits数据——>发送响应信号——>……——>>8bits数据——>不发送响应信号——>停止位

三.项目设计

1.状态机设计

这里还是采用主从状态机实现,通过接口模块,和控制接口模块来达到读写eeprom
在这里插入图片描述
控制模块(主状态机模块),仅考虑操作类型,读操作或者写操作,不关系数据如何发送,只需要发送操作指令,等待接口模块发送完成返回结束信号。
接口模块(从状态机),当操作指令到来后,只需要按照时序发送或者接收数据,之后返还一个结束信号

2.代码实现

1.接口模块

/**************************************功能介绍***********************************
Date	: 
Author	: WZY.
Version	: 
Description: 接口命令(cmd)
                                0       1
             bit0 (起始位)      NO      YES
             bit1 (写数据)      NO      YES
             bit2 (读数据)      NO      YES
             bit3 (停止位)      NO      YES
             bit4 (相应位)      ACK     NO_ACK
*********************************************************************************/
    
//---------<模块及端口声名>------------------------------------------------------
module iic_dirver#
(
    parameter T = "100k",
              SYSTEM_CLOCK = 50_000_000
)
( 
    input 	wire				clk		,
    input 	wire				rst_n	,

    //控制接口
    input   wire    [4:0]       cmd     ,
    input   wire                cmd_vld ,
    output  wire                done    ,

    //数据接口
    input   wire    [7:0]       wr_data ,
    output  wire    [7:0]       rd_data ,
    output  wire                rd_data_vld,

    output  wire                iic_scl ,
    inout   wire                iic_sda 
);								 
//---------<参数定义>--------------------------------------------------------- 
//状态机参数定义
localparam  IDLE    =   7'b0000001,
            START   =   7'b0000010,
            WRITE   =   7'b0000100,
            READ    =   7'b0001000,
            R_ACK   =   7'b0010000,
            S_ACK   =   7'b0100000,
            STOP    =   7'b1000000;  

//时间参数定义
parameter   IICT  = (T == "100k")?SYSTEM_CLOCK/100_000:
                   (T == "400k")?SYSTEM_CLOCK/400_000:
                   SYSTEM_CLOCK/3_400_000;
//  parameter   IICT = 500;
parameter   IICT_1_4 = IICT >> 2,
            IICT_1_2 = IICT >> 1,
            IICT_3_4 = IICT - IICT_1_4;

//指令定义
`define     START_BIT  5'b00001
`define     WRITE_BIT  5'b00010
`define     READ_BIT   5'b00100
`define     STOP_BIT   5'b01000
`define     ACK_BIT    5'b10000
//响应信号定义
`define     ACK        0
`define     NO_ACK     1
//---------<内部信号定义>-----------------------------------------------------
reg 	[6:0]	cstate     ;//现态
reg	    [6:0]	nstate     ;//次态

wire            idle2start  ;
wire            idle2write  ;
wire            idle2read   ;
wire            start2write ;
wire            write2r_ack ;
wire            read2s_ack  ;
wire            r_ack2idle  ;
wire            r_ack2stop  ;
wire            s_ack2idle  ;
wire            s_ack2stop  ;
wire            stop2idle   ;


//计数器参数定义
reg			[8:0]	cnt_1bit	   	;
wire				add_cnt_1bit	;
wire				end_cnt_1bit	;

reg			[2:0]	cnt_num	   	;
wire				add_cnt_num	;
wire				end_cnt_num	;

reg         [3:0]   num;

//命令信号寄存
reg         [4:0]   cmd_r           ;

//存在信号
reg                 rev_ack         ;

//三态门参数
reg                 OE;
wire                din;
reg                 dout;

//数据寄存
reg         [7:0]   rd_data_r       ;
reg         [7:0]   wr_data_r       ;

//****************************************************************
//                  数据寄存
//****************************************************************
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        wr_data_r <= 8'd0;
    end
    else if (cmd_vld) begin
        wr_data_r <= wr_data;
    end
end

//****************************************************************
//                  状态机
//****************************************************************
//第一段:时序逻辑描述状态转移
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        cstate <= IDLE;
    end 
    else begin 
        cstate <= nstate;
    end 
end

//第二段:组合逻辑描述状态转移规律和状态转移条件
always @(*) begin
    case(cstate)
        IDLE  : begin
                    if (idle2start) begin
                        nstate = START;
                    end
                    else if (idle2write) begin
                        nstate = WRITE;
                    end
                    else if (idle2read) begin
                        nstate = READ;
                    end
                    else begin
                      nstate = cstate;
                    end
                end
        START : begin
                    if (start2write) begin
                        nstate = WRITE;
                    end
                    else begin
                      nstate = cstate;
                    end
                end
        WRITE : begin
                    if (write2r_ack) begin
                        nstate = R_ACK;
                    end
                    else begin
                      nstate = cstate;
                    end
                end
        READ  : begin
                    if (read2s_ack) begin
                        nstate = S_ACK;
                    end
                    else begin
                      nstate = cstate;
                    end
                end
        R_ACK : begin
                    if (r_ack2idle) begin
                        nstate = IDLE;
                    end
                    else if (r_ack2stop) begin
                        nstate = STOP;
                    end
                    else begin
                      nstate = cstate;
                    end
                end
        S_ACK : begin
                    if (s_ack2stop) begin
                        nstate = STOP;
                    end
                    else if (s_ack2idle) begin
                        nstate = IDLE;
                    end
                    else begin
                      nstate = cstate;
                    end
                end
        STOP  : begin
                    if (stop2idle) begin
                        nstate = IDLE;
                    end
                    else begin
                      nstate = cstate;
                    end
                end
        default : ;
    endcase
end
assign  idle2start  = cstate == IDLE    && cmd_vld && (cmd & `START_BIT);//接收到命令信号并且命令中包含开始命令
assign  idle2write  = cstate == IDLE    && cmd_vld && (cmd & `WRITE_BIT);//接收到命令信号并且命令中没有开始命令,有写命令
assign  idle2read   = cstate == IDLE    && cmd_vld && (cmd & `READ_BIT );//接收到命令信号并且命令中没有开始命令,有读命令
assign  start2write = cstate == START   && end_cnt_num && (cmd_r & `WRITE_BIT);
assign  write2r_ack = cstate == WRITE   && end_cnt_num;
assign  read2s_ack  = cstate == READ    && end_cnt_num;
assign  r_ack2idle  = cstate == R_ACK   && end_cnt_num && !(cmd_r & `STOP_BIT);//接收到响应,并且命令中没有停止命令
assign  r_ack2stop  = cstate == R_ACK   && end_cnt_num &&  (cmd_r & `STOP_BIT);//接收到相应信号,并且命令中有停止命令
assign  s_ack2idle  = cstate == S_ACK   && end_cnt_num && !(cmd_r & `STOP_BIT);//等待1bit时间并且没有停止命令
assign  s_ack2stop  = cstate == S_ACK   && end_cnt_num &&  (cmd_r & `STOP_BIT);//等待1bit时间并且有停止命令
assign  stop2idle   = cstate == STOP    && end_cnt_num;
            
//第三段:描述输出,时序逻辑或组合逻辑皆可
            
//****************************************************************
//                  命令寄存
//****************************************************************
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        cmd_r <= 0;
    end
    else if (cmd_vld) begin//每次接收到命令使能更新命令
        cmd_r <= cmd;
    end
end

//****************************************************************
//              计数器复用
//**************************************************************** 
//1bit计数器


always @(posedge clk or negedge rst_n)begin 
   if(!rst_n)begin
        cnt_1bit <= 9'd0;
    end 
    else if(add_cnt_1bit)begin 
        if(end_cnt_1bit)begin 
            cnt_1bit <= 'd0;
        end
        else begin 
            cnt_1bit <= cnt_1bit + 1'b1;
        end 
    end
end 

assign add_cnt_1bit = cstate != IDLE;
assign end_cnt_1bit = add_cnt_1bit && cnt_1bit == IICT-1;

//计数器复用


always @(posedge clk or negedge rst_n)begin 
   if(!rst_n)begin
        cnt_num <= 'd0;
    end 
    else if(add_cnt_num)begin 
        if(end_cnt_num)begin 
            cnt_num <= 'd0;
        end
        else begin 
            cnt_num <= cnt_num + 1'b1;
        end 
    end
end 

assign add_cnt_num = end_cnt_1bit;
assign end_cnt_num = add_cnt_num && cnt_num == num - 1;


always @(*) begin
    case (cstate)
        START   : num = 1 ;
        WRITE   : num = 8 ;
        READ    : num = 8 ;
        R_ACK   : num = 1 ;
        S_ACK   : num = 1 ;
        STOP    : num = 1 ;
        default: num = 1;
    endcase
end
  
//****************************************************************
//                  三态门
//****************************************************************
//OE使能控制
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        OE <= 0;
    end
    else if (idle2start||idle2write||start2write||r_ack2stop||read2s_ack||s_ack2stop) begin
            OE <= 1;
        end
        else if (idle2read||write2r_ack||stop2idle||s_ack2idle||r_ack2idle) begin
            OE <= 0;
        end
end
assign iic_sda = OE?dout:1'bz;
assign din        = iic_sda;
assign iic_scl = (cnt_1bit >= (IICT >>1))?1:0;//发送周期波形

// SDA信号控制
always @(*) begin
    if (!rst_n) begin
        dout = 1;
    end
    else case (cstate)
        IDLE : begin
                dout = 1;
            end
        START : begin
                if (cnt_1bit == IICT_3_4) begin
                    dout = 0;
                end
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值