IIC读写EEFPROM

一、相关概念

1.IIC基础概念

①概念

IICInter-Integrated Circuit)其实是IICBus简称,所以中文应该叫集成电路总线,它是一种串行通信总线,主控制器和从器件间的主从通信,在小数据量场合使用,传输距离短,任意时刻只能有一个主机等特性。

IIC总线由数据线SDA和时钟线SCL构成通信线路,可用于收发数据,属于半双工通信协议,总线上的主从设备以字节为单位传输数据(8bit)。

②三种传输模式

标准模式:100Kbit/s

快速模式:400Kbit/s

高速模式:3.4Mbit/s

③物理层

在这里插入图片描述

(1) 它是一个支持多设备的总线(支持多主机多从机)。

(2) IIC总线只使用两条总线线路,一条双向串行数据线(SDA) 一条串行时钟线(SCL)。数据线即用来表示数据,时钟线用于数据收发同步。

(3) 每个连接到IIC总线的设备都有一个独立的地址,主机可以利用设备独立地址访问不同设备。

(4) IIC总线通过上拉电阻接到电源。当IIC设备空闲时,设备会输出高阻态,当所有设备都空闲,都输出高阻态时,由上拉电阻把IIC总线拉成高电平。

(5) IIC总线具有仲裁机制。

2.IIC协议时序

在这里插入图片描述

① IIC协议空闲状态

idle:在此状态下串口时钟信号 SCL 和串行数据信号 SDA 均保持高电平(都为1),此时无IIC设备工作。

**② IIC协议的起始信号 **

start:在IIC总线处于“空闲状态”时,时钟信号线SCL为高电平时,数据信号线SDA被拉低(由高电平变为低电平),出现下降沿,表示产生了一个起始信号。

起始信号是由主机主动建立的,在建立该信号之前IIC总线必须处于空闲状态。

③ IIC协议的数据传输(数据读写)状态及应答信号(ACK)

IIC通讯设备的通讯模式是主从通讯模式,通讯双方有主从之分。

数据信号线SDA更新数据时:在时钟信号线SCL为低电平时将数据信号线SDA上的数据进行数据更新。

即数据在时钟信号线SCL的上升沿到来之前就必须准备好,并在在SCL下降沿到来之前必须保持稳定。

IIC总线上的所有数据都是以字节(8bit)传送的,发送端(可以是主机也可以是从机)每发送一个字节(8bit),就必须在第9个SCL脉冲期间释放SDA,由接收端(发送端为主机时,接收端为从机/发送端为从机时,接收端为主机)反馈一个应答信号

应答信号为低电平(为0)时,称为有效应答位(ACK),表示接收端成功接收了该字节(8bit);应答信号为高电平(为1)时,称为非应答位(NACK),表示接收端接收该字节(8bit)失败。

数据接收失败或者数据传输结束都要发送 NACK

反馈有效应答位(ACK):接收端在第9个时钟脉冲之前的低电平期间将SDA拉低,并且确保在该时钟周期的高电平期间保持低电平。

④ IIC协议的停止信号

数据读写完成后,时钟信号线SCL为高电平时,数据信号线SDA被释放(由低电平转为高电平),出现上升沿,表示产生了一个停止信号,IIC总线跳转回“总线空闲状态”。

停止信号是由主机(本实验中为FPGA)主动建立的,建立该信号之后,IIC总线将返回空闲状态。

3.EEPROM概念

①概念

带电可擦可编程只读存储器。是一种掉电后数据不丢失的存储芯片。

(我使用的板子内部有两个Block)

读写格式

在IIC主从设备通讯时,主机在发送了起始信号后,接着会向从机发送控制命令。控制命令长度为1个字节,它的高7位为 IIC设备的器件地址,最低位为读写控制位。

在这里插入图片描述

4.EEPROM读写方式及时序

EEPROM的写操作有:

①单字节写(BYTE WRITE)

② 页写(PAGE WRITE)

EEPROM的读操作有:

①当前地址读(Current Address READ)

②随机地址读(RANDOM READ)

③顺序地址读(SEQUENTIAL READ)

在这里插入图片描述

①单字节写

字节写(BYTE WRITE)操作:在数据信号线SDA上,发起始位(START)->写写控制字(Control Byte)->接收ACK->写字节地址(Word Address)->接收ACK->写数据(Data) ->接收ACK->发停止位(STOP)。

②页写

一次写入16个字节(每字节为8bit)数据。

在数据信号线SDA上,发起始位(START)->写写控制字(Control Byte)->接收ACK->写字节地址(Word Address)->接收ACK->写数据(Data(n)) ->接收ACK->写数据(Data(n+1))->接收ACK->(一共发送16字节(Byte)数据,中间数据省略)->写数据(Data(n+15))->接收ACK->发停止位(STOP)。

在这里插入图片描述

(个人理解:当前地址读是指定了一个block的第一个数据读取;其实随机地址读并不是真正的”随机“,而是指定了一个地址之后,再进行读取;而顺序地址读则是在随机地址读的基础上连续向后读取了几个数据)

①当前地址读

当前地址读(Current Address READ)操作:在数据信号线SDA上, 发起始位(START)->写读控制字(Control Byte)->接收ACK->接收读数据(Data)->发No ACK->发停止位(STOP)。

②随即地址读

随机地址读(RANDOM READ)操作:在数据信号线SDA上,发起始位(START)->写写控制字(Control Byte)->写读地址(Word Address)->接收ACK->再发起始位(START)->写读控制字(Control Byte)->接收读数据(Data)->发No ACK->发停止位(STOP)。

③顺序地址读

顺序地址读(SEQUENTIAL READ)操作:在数据信号线SDA上,发起始位(START)->写写控制字(Control Byte)->写读地址(Word Address)->接收ACK->再发起始位(START)->写读控制字(Control Byte)->接收读数据(Data(n)) ->接收ACK->接收读数据(Data(n+1))->接收ACK->…->接收读数据(Data(n+x)) ->发No ACK->发停止位(STOP)。

二、核心代码

1.代码思路

我们将IIC协议具体实现在一个IIC_master模块内,在此模块内部进行细分,将其分为接口模块(IIC_interface)以及控制模块(IIC_control);

其中接口模块主要负责数据的传输,接收来自控制模块以及EEPRAM的数据,本身并不具备储存数据的功能(内部不含有FIFO),控制模块不光要对接口模块进行操作,控制接口数据的收发,还要对数据进行短暂缓存(内部含有两个FIFO,分别负责存储读写数据)。

2.部分代码

IIC_control控制模块(不含状态机,可设计为6个状态,包括读写请求与等待)

//读写命令数据输出(数据,地址,读写控制字,开始停止位),使用task
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            TX<={1'b0,4'b0,8'b0} ;
        end
        else if (state_n==WR_REQ) begin
            case (cnt_byte)
                0:TX(1'b1,{`CMD_START | `CMD_WRITE},{`I2C_ADR,wr_addr[8],`WR_BIT});//发地址,BLOCK地址,写控制字
                1:TX(1'b1, `CMD_WRITE,wr_addr[7:0]);    //发 写地址
                2:TX(1'b1,{`CMD_WRITE | `CMD_STOP},wfifo_qout);     //最后一个字节,发数据及停止位
                default : TX(1'b1,`CMD_WRITE,wfifo_qout);       //中间发数据
            endcase
        end
        else if (state_n==RD_REQ) begin
            case (cnt_byte)
                0:TX(1'b1,{`CMD_START |`CMD_WRITE},{`I2C_ADR,rd_addr[8],`WR_BIT});//发地址,BLOCK地址,写控制字,
                                                    //并不是真正的要在这个地方写,而是要先找到这个地方再进行读操作
                1:TX(1'b1,`CMD_WRITE,rd_addr[7:0]);  //发送读地址
                2:TX(1'b1,{`CMD_START |`CMD_WRITE},{`I2C_ADR,rd_addr[8],`WR_BIT});  //发起始位、读控制字
                3:TX(1'b1,{`CMD_READ | `CMD_STOP},0);   //最后一个字节时,读数据、发停止位
                default:TX(1'b1,`CMD_READ,0)
            endcase
        end
        else begin
            TX(1'b0,tx_cmd,tx_data);
        end
    end
//用task发送请求、命令、数据(地址+数据)
    task TX;
        input  wire         req;
        input  wire [3:0]   command;
        input  wire [7:0]   data;

        tx_req=req;
        tx_cmd=command;
        tx_data=data;
    endtask
//wr_addr,rd_addr,读写地址
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            wr_addr<=0 ;
        end
        else if (wait_wr2done) begin
            wr_addr<=wr_addr+1;
        end
    end
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            rd_addr<=0 ;
        end
        else if (wait_rd2done) begin
            rd_addr<=rd_addr+1;
        end
    end
//rd_flag,读数据开启信号
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            rd_flag<=1'b0 ;
        end
        else if (~rfifo_empty) begin
            rd_flag<=1'b1;
        end
        else begin
            rd_flag<=1'b0 ;
        end
    end
//user_data,user_data_vld数据输出及有效
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            user_data<= 0;
            user_data_vld<=0;
        end
        else begin
            user_data<= rfifo_qout;
            user_data_vld<= wfifo_rd;
        end
    end
//数据输出同步
    assign req     = tx_req ; 
    assign cmd     = tx_cmd ; 
    assign wr_data = tx_data; 

    assign dout    = user_data;//控制器输出数据
    assign dout_vld= user_data_vld;

IIC_interface接口模块(不含状态机,可设计为7个状态包括读写状态与读写应答)

//IIC时钟计数器,在发送数据和接收数据的时候需要
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
           cnt_scl <= 0;
        end
        else if (add_cnt_scl) begin
             if (end_cnt_scl) begin
               cnt_scl <= 0;
            end
            else begin
               cnt_scl <= cnt_scl+1;
            end
        end
        else begin
           cnt_scl <= cnt_scl;
        end
    end
    assign add_cnt_scl=(state_c!=IDLE);
    assign end_cnt_scl=add_cnt_scl&&cnt_scl==`SCL_PERIOD-1;
//bit计数器
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
           cnt_bit <= 0;
        end
        else if (add_cnt_bit) begin
             if (end_cntbit) begin
               cnt_bit <= 0;
            end
            else begin
               cnt_bit <= cnt_bit+1;
            end
        end
        else begin
           cnt_bit <= cnt_bit;
        end
    end
    assign add_cnt_bit=end_cnt_scl;
    assign end_cnt_bit=add_cnt_bit&&cnt_bit==(bit_num-1);

    always @(*) begin
        if (state_c==READ|state_c==WRITE) begin
            bit_num<= 8;
        end
        else begin
            bit_num<=1;
        end
    end
//command,命令接收,请求到达时锁存命令,防止丢失
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            command<= 0;
        end
        else if(req)begin
            command<=cmd ;
        end
    end
//tx_data,数据接收(锁存)
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            tx_data<= 0;
        end
        else if(req)begin
            tx_data<= din;
        end
    end
//scl,时钟总线控制,产生IIC时钟周期
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            scl <= 1'b1;
        end
        else if (idle2start | idle2read | idle2write) begin
            scl<=1'b0;
        end
        else if(add_cnt_scl && cnt_scl == `SCL_HALF-1)begin
            scl <= 1'b1;
        end
        else if (end_cnt_scl && ~stop2idle) begin
            scl <= 1'b0;
        end
    end
//sda_out数据总线控制,主要是写状态下发送出去数据
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            sda_out <= 1'b1;
        end
        else if (state_c == START) begin
            if (cnt_scl == `LOW_HLAF) begin
                sda_out <= 1'b1;
            end
            else if (cnt_scl == `HIGH_HALF) begin
                sda_out <= 1'b0;            //确保开始信号采样成功
            end
        end
        else if(state_c == WRITE && cnt_scl == `LOW_HLAF) begin
            sda_out <= tx_data[7-cnt_bit];
        end
        else if (cnt_scl == SACK && cnt_scl == `LOW_HLAF) begin//发应答位
            sda_out <= (command & `CMD_STOP)?1'b1:1'b0;
        end
        else if (cnt_scl == STOP) begin
            if (cnt_scl == `LOW_HLAF) begin
                sda_out <= 1'b0;
            end
            else if (cnt_scl ==`HIGH_HALF) begin
                sda_out <= 1'b1;            //确保结束信号采样成功
            end
        end
    end

//sda_out_en,总线输出数据使能
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            sda_out_en<= 1'b0;
        end
        else if (idle2start | idle2write | read2sack | rack2stop) begin
             sda_out_en <= 1'b1;
        end
        else if(idle2read | write2rack | start2read | stop2idle)begin
            sda_out_en <= 1'b0;
        end
    end
//rx_data,接收读入的数据
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            rx_data<= 0;
        end
        else if (state_c==READ && cnt_scl == `LOW_HLAF) begin
             rx_data[cnt_bit] <= i2c_sda_i;
        end
    end
//rx_ack,IIC从机应答位,写应答
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            rx_ack<= 1'b1;
        end
        else if (state_c == RACK && cnt_scl == `HIGH_HALF) begin
            rx_ack <= i2c_sda_i;
        end
    end

三、总结

IIC协议作为国际通用协议有其独到的优点:用很轻盈的架构实现了多主设备仲裁和设备路由。但是也确实更加难以理解。它常用于系统内各芯片之间的通信,仍然具有无法替代的优势。


参考链接

嵌入式学习之EEPROM

终于看懂了!通信协议 IIC 与 SPI 最全对比

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值