【FPGA】EEPROM+iic协议

EEPROM

电可擦除可编程只读存储器, 是一种常用的非易失性存储器(掉电后,数据不丢失)。

结构图:

SCL:iic的时钟引脚

SDA:双向数据引脚

WP:写保护引脚,低电平——可写,高电平——可读

A0、A1、A2地址引脚

iic

以下引用自原子哥:

IIC (I2CInterIntegrated Circuit)即集成电路总线,是一种两线式串行总线,由PHILIPS公司开发用于连接微控制器及其外围设备。多用于主机和从机在数据量不大且传输距离短的场合下的主从通信。

I2C总线由数据线SDA和时钟线SCL构成通信线路,既可用于发送数据,也可接收数据

标准模式:100Kbit/s  

快速模式:400kbit/s 

高速模式:3.4Mbit/s

IIC是半双工通信方式

空闲状态
开始信号
停止信号
数据的有效性
应答信号
数据传输

1.空闲状态

SCL:                1

SDA:                1

2.空闲状态

SCL:                1

SDA:        1 — — > 0

3.数据有效状态

SCL:                0— — — — —  >1 — — — — —  > 0

SDA:            稳定       准备    【数据】     稳定

                          采用                                                 采用

数据在SCL的上升沿到来之前就需准备好。并在在下降沿到来之前必须稳定

4.应答信号:

发送器每发送一个字节,就在时钟脉冲9期间释放数据线,由接收器反馈一个应答信号。 应答信号为低电平时,规定为有效应答位(ACK简称应答位),表示接收器已经成功地接收了该字节;应答信号为高电平时,规定为非应答位(NACK),一般表示接收器接收该字节没有成功。 

对于反馈有效应答位ACK的要求是,接收器在第9个时钟脉冲之前的低电平期间将SDA线拉低,并且确保在该时钟的高电平期间为稳定的低电平。 如果接收器是主控器,则在它收到最后一个字节后,发送一个NACK信号,以通知被控发送器结束数据发送,并释放SDA线,以便主控接收器发送一个停止信号P

5.停止状态

SCL:                1

SDA:        0 — — > 1

以下的EEPROM大小为:内部分成256页,一页32个字节

所以储存大小为:256*32*8 / 1024 = 64 Kbit

写时序为:

大概四个部分:

1、器件地址 + 应答位

2、EEPROM的内部高 5 地址 + 应答位

3、EEPROM内部低 8 位地址 + 应答位

4、8 位数据 + 应答位

为什么内部地址是13位的?

答:256*32字节 = 8192 。8191是13位的

注:AT24C64支持页写模式

读时序

完整时序表如下:

SCL SDA SCL SDA SCL SDA SCL SDA SCL SDA SCL SDA
1 1 起始态 0 1 1 寄存高8位地址 0 0 add【15】 寄存低8位地址 0 0 add【7】 读数据 0 0 写数据 0 0 data【7】
1 1 1 1 0 1 1 1 1 1 1 data【7】 1 1
写/读器件地址 2 1 0 2 1 2 1 2 1 2 1
3 0 0 3 0 3 0 3 0 3 0
4 0 add【6】 4 0 add【14】 4 0 add【6】 4 0 4 0 data【6】
5 1 5 1 5 1 5 1 data【6】 5 1
6 1 6 1 6 1 6 1 6 1
7 0 7 0 7 0 7 0 7 0
8 0 add【5】 8 0 add【13】 8 0 add【5】 8 0 8 0 data【5】
9 1 9 1 9 1 9 1 data【5】 9 1
10 1 10 1 10 1 10 1 10 1
11 0 11 0 11 0 11 0 11 0
12 0 add【4】 12 0 add【12】 12 0 add【4】 12 0 12 0 data【4】
13 1 13 1 13 1 13 1 data【4】 13 1
14 1 14 1 14 1 14 1 14 1
15 0 15 0 15 0 15 0 15 0
16 0 add【3】 16 0 add【11】 16 0 add【3】 16 0 16 0 data【3】
17 1 17 1 17 1 17 1 data【3】 17 1
18 1 18 1 18 1 18 1 18 1
19 0 19 0 19 0 19 0 19 0
20 0 add【2】 20 0 add【10】 20 0 add【2】 20 0 20 0 data【2】
21 1 21 1 21 1 21 1 data【2】 21 1
22 1 22 1 22 1 22 1 22 1
23 0 23 0 23 0 23 0 23 0
24 0 add【1】 24 0 add【9】 24 0 add【1】 24 0 24 0 data【1】
25 1 25 1 25 1 25 1 data【1】 25 1
26 1 26 1 26 1 26 1 26 1
27 0 27 0 27 0 27 0 27 0
28 0 add【1】 28 0 add【8】 28 0 add【0】 28 0 28 0 data【0】
29 1 29 1 29 1 29 1 data【0】 29 1
30 1 30 1 30 1 30 1 30 1
31 0 31 0 31 0 31 0 31 0
32 0 0/1 32 0 从机答 32 0 从机答 32 0 应答 32 0 应答
33 1 33 1 33 1 33 1 33 1
34 1 34 1 34 1 34 1 34 1
35 0 35 0 35 0 35 0 35 0
36 0 从机答
37 1
38 1
39 0 完成

我们能得出一个结论:

在SCL为0, 0时写入东西

在SCL为0, 1时读数据

代码:

module i2c_dri
    #(// slave address(器件地址),放此处方便参数传递
      parameter   SLAVE_ADDR =  7'b1010000   ,
      parameter   CLK_FREQ   = 26'd50_000_000,   // i2c_dri模块的驱动时钟频率(CLK_FREQ)
      parameter   I2C_FREQ   = 18'd250_000       // I2C的SCL时钟频率
     )(
          //global clock
          input                clk        ,      // i2c_dri模块的驱动时钟(CLK_FREQ)
          input                rst_n      ,      // 复位信号

          //i2c interface
          input                i2c_exec   ,      // I2C触发执行信号
          input                bit_ctrl   ,      // 字地址位控制(16b/8b)
          input                i2c_rh_wl  ,      // I2C读写控制信号
          input        [15:0]  i2c_addr   ,      // I2C器件内地址
          input        [ 7:0]  i2c_data_w ,      // I2C要写的数据
          output  reg  [ 7:0]  i2c_data_r ,      // I2C读出的数据
          output  reg          i2c_done   ,      // I2C一次操作完成
          output  reg          scl        ,      // I2C的SCL时钟信号
          inout                sda        ,      // I2C的SDA信号

          //user interface
          output  reg          dri_clk           // 驱动I2C操作的驱动时钟,1us
     );

//localparam define
localparam  st_idle     = 8'b0000_0001;          // 空闲状态
localparam  st_sladdr   = 8'b0000_0010;          // 发送器件地址(slave address)
localparam  st_addr16   = 8'b0000_0100;          // 发送16位字地址
localparam  st_addr8    = 8'b0000_1000;          // 发送8位字地址
localparam  st_data_wr  = 8'b0001_0000;          // 写数据(8 bit)
localparam  st_addr_rd  = 8'b0010_0000;          // 发送器件地址读
localparam  st_data_rd  = 8'b0100_0000;          // 读数据(8 bit)
localparam  st_stop     = 8'b1000_0000;          // 结束I2C操作

//reg define
reg            sda_dir     ;                     // I2C数据(SDA)方向控制
reg            sda_out     ;                     // SDA输出信号
reg            st_done     ;                     // 状态结束
reg            wr_flag     ;                     // 写标志
reg    [ 6:0]  cnt         ;                     // 计数
reg    [ 7:0]  cur_state   ;                     // 状态机当前状态
reg    [ 7:0]  next_state  ;                     // 状态机下一状态
reg    [15:0]  addr_t      ;                     // 地址
reg    [ 7:0]  data_r      ;                     // 读取的数据
reg    [ 7:0]  data_wr_t   ;                     // I2C需写的数据的临时寄存
reg    [ 9:0]  clk_cnt     ;                     // 分频时钟计数

//wire define
wire          sda_in       ;                     // SDA输入信号
wire   [8:0]  clk_divide   ;                     // 模块驱动时钟的分频系数

//*****************************************************
//**                    main code
//*****************************************************

//SDA控制
assign  sda     = sda_dir ?  sda_out : 1'bz;     // SDA数据输出或高阻
assign  sda_in  = sda ;                          // SDA数据输入
assign  clk_divide = (CLK_FREQ/I2C_FREQ) >> 2'd3;// 模块驱动时钟的分频系数,25

//生成I2C的SCL的四倍频率的驱动时钟用于驱动i2c的操作
always @(posedge clk or negedge rst_n) begin
    if(rst_n == 1'b0) begin
        dri_clk <=  1'b1;
        clk_cnt <= 10'd0;
    end
    else if(clk_cnt == clk_divide - 1'd1) begin
        clk_cnt <= 10'd0;
        dri_clk <= ~dri_clk;
    end
    else
        clk_cnt <= clk_cnt + 1'b1;
end

//(三段式状态机)同步时序描述状态转移
always @(posedge dri_clk or negedge rst_n) begin
    if(rst_n == 1'b0)
        cur_state <= st_idle;
    else
        cur_state <= next_state;
end

//组合逻辑判断状态转移条件
always @( * ) begin
//    next_state = st_idle;
    case(cur_state)
        st_idle: begin                            // 空闲状态
           if(i2c_exec) begin
               next_state = st_sladdr;
           end
           else
               next_state = st_idle;
        end
        st_sladdr: begin
            if(st_done) begin
                if(bit_ctrl)                      // 判断是16位还是8位字地址
                   next_state = st_addr16;
                else
                   next_state = st_addr8 ;
            end
            else
                next_state = st_sladdr;
        end
        st_addr16: begin                          // 写16位字地址
            if(st_done) begin
                next_state = st_addr8;
            end
            else begin
                next_state = st_addr16;
            end
        end
        st_addr8: begin                           // 8位字地址
            if(st_done) begin
                if(wr_flag==1'b0)                 // 读写判断
                    next_state = st_data_wr;
                else
                    next_state = st_addr_rd;
            end
            else begin
                next_state = st_addr8;
            end
        end
        st_data_wr: begin                        // 写数据(8 bit)
            if(st_done)
                next_state = st_stop;
            else
                next_state = st_data_wr;
        end
        st_addr_rd: begin                         // 写地址以进行读数据
            if(st_done) begin
                next_state = st_data_rd;
            end
            else begin
                next_state = st_addr_rd;
            end
        end
        st_data_rd: begin                        // 读取数据(8 bit)
            if(st_done)
                next_state = st_stop;
            else
                next_state = st_data_rd;
        end
        st_stop: begin                           // 结束I2C操作
            if(st_done)
                next_state = st_idle;
            else
                next_state = st_stop ;
        end
        default: next_state= st_idle;
    endcase
end

//时序电路描述状态输出
always @(posedge dri_clk or negedge rst_n) begin
    //复位初始化
    if(rst_n == 1'b0) begin
        scl        <= 1'b1;
        sda_out    <= 1'b1;
        sda_dir    <= 1'b1;
        i2c_done   <= 1'b0;
        cnt        <= 1'b0;
        st_done    <= 1'b0;
        data_r     <= 1'b0;
        i2c_data_r <= 1'b0;
        wr_flag    <= 1'b0;
        addr_t     <= 1'b0;
        data_wr_t  <= 1'b0;
    end
    else begin
        st_done <= 1'b0 ;
        cnt     <= cnt +1'b1 ;
        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_exec) begin
                    wr_flag   <= i2c_rh_wl ;
                    addr_t    <= i2c_addr  ;
                    data_wr_t <= i2c_data_w;
                end
            end
            st_sladdr: begin                            // 写地址(器件地址和字地址)
                case(cnt)
                    7'd1 : sda_out <= 1'b0;             // 开始I2C
                    7'd3 : scl <= 1'b0;
                    7'd4 : sda_out <= SLAVE_ADDR[6];    // 传送器件地址
                    7'd5 : scl <= 1'b1;
                    7'd7 : scl <= 1'b0;
                    7'd8 : sda_out <= SLAVE_ADDR[5];
                    7'd9 : scl <= 1'b1;
                    7'd11: scl <= 1'b0;
                    7'd12: sda_out <= SLAVE_ADDR[4];
                    7'd13: scl <= 1'b1;
                    7'd15: scl <= 1'b0;
                    7'd16: sda_out <= SLAVE_ADDR[3];
                    7'd17: scl <= 1'b1;
                    7'd19: scl <= 1'b0;
                    7'd20: sda_out <= SLAVE_ADDR[2];
                    7'd21: scl <= 1'b1;
                    7'd23: scl <= 1'b0;
                    7'd24: sda_out <= SLAVE_ADDR[1];
                    7'd25: scl <= 1'b1;
                    7'd27: scl <= 1'b0;
                    7'd28: sda_out <= SLAVE_ADDR[0];
                    7'd29: scl <= 1'b1;
                    7'd31: scl <= 1'b0;
                    7'd32: sda_out <= 1'b0;              // 0:写
                    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: st_done <= 1'b1;
                    7'd39: begin
                        scl <= 1'b0;
                        cnt <= 1'b0;
                    end
                    default :  ;
                endcase
            end
            st_addr16: begin
                case(cnt)
                    7'd0 : begin
                        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: st_done <= 1'b1;
                    7'd35: begin
                        scl <= 1'b0;
                        cnt <= 1'b0;
                    end
                    default :  ;
                endcase
            end
            st_addr8: begin
                case(cnt)
                    7'd0: begin
                       sda_dir <= 1'b1 ;
                       sda_out <= addr_t[7];            // 字地址
                    end
                    7'd1 : scl <= 1'b1;
                    7'd3 : scl <= 1'b0;
                    7'd4 : sda_out <= addr_t[6];
                    7'd5 : scl <= 1'b1;
                    7'd7 : scl <= 1'b0;
                    7'd8 : sda_out <= addr_t[5];
                    7'd9 : scl <= 1'b1;
                    7'd11: scl <= 1'b0;
                    7'd12: sda_out <= addr_t[4];
                    7'd13: scl <= 1'b1;
                    7'd15: scl <= 1'b0;
                    7'd16: sda_out <= addr_t[3];
                    7'd17: scl <= 1'b1;
                    7'd19: scl <= 1'b0;
                    7'd20: sda_out <= addr_t[2];
                    7'd21: scl <= 1'b1;
                    7'd23: scl <= 1'b0;
                    7'd24: sda_out <= addr_t[1];
                    7'd25: scl <= 1'b1;
                    7'd27: scl <= 1'b0;
                    7'd28: sda_out <= addr_t[0];
                    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: st_done <= 1'b1;
                    7'd35: begin
                        scl <= 1'b0;
                        cnt <= 1'b0;
                    end
                    default :  ;
                endcase
            end
            st_data_wr: begin                          // 写数据(8 bit)
                case(cnt)
                    7'd0: begin
                        sda_out <= data_wr_t[7];        // I2C写8位数据
                        sda_dir <= 1'b1;
                    end
                    7'd1 : scl <= 1'b1;
                    7'd3 : scl <= 1'b0;
                    7'd4 : sda_out <= data_wr_t[6];
                    7'd5 : scl <= 1'b1;
                    7'd7 : scl <= 1'b0;
                    7'd8 : sda_out <= data_wr_t[5];
                    7'd9 : scl <= 1'b1;
                    7'd11: scl <= 1'b0;
                    7'd12: sda_out <= data_wr_t[4];
                    7'd13: scl <= 1'b1;
                    7'd15: scl <= 1'b0;
                    7'd16: sda_out <= data_wr_t[3];
                    7'd17: scl <= 1'b1;
                    7'd19: scl <= 1'b0;
                    7'd20: sda_out <= data_wr_t[2];
                    7'd21: scl <= 1'b1;
                    7'd23: scl <= 1'b0;
                    7'd24: sda_out <= data_wr_t[1];
                    7'd25: scl <= 1'b1;
                    7'd27: scl <= 1'b0;
                    7'd28: sda_out <= data_wr_t[0];
                    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: st_done <= 1'b1;
                    7'd35: begin
                        scl  <= 1'b0;
                        cnt  <= 1'b0;
                    end
                    default  :  ;
                endcase
            end
            st_addr_rd: begin                           // 写地址以进行读数据
                case(cnt)
                    7'd0 : begin
                        sda_dir <= 1'b1;
                        sda_out <= 1'b1;
                    end
                    7'd1 : scl <= 1'b1;
                    7'd2 : sda_out <= 1'b0;             // 重新开始
                    7'd3 : scl <= 1'b0;
                    7'd4 : sda_out <= SLAVE_ADDR[6];    // 传送器件地址
                    7'd5 : scl <= 1'b1;
                    7'd7 : scl <= 1'b0;
                    7'd8 : sda_out <= SLAVE_ADDR[5];
                    7'd9 : scl <= 1'b1;
                    7'd11: scl <= 1'b0;
                    7'd12: sda_out <= SLAVE_ADDR[4];
                    7'd13: scl <= 1'b1;
                    7'd15: scl <= 1'b0;
                    7'd16: sda_out <= SLAVE_ADDR[3];
                    7'd17: scl <= 1'b1;
                    7'd19: scl <= 1'b0;
                    7'd20: sda_out <= SLAVE_ADDR[2];
                    7'd21: scl <= 1'b1;
                    7'd23: scl <= 1'b0;
                    7'd24: sda_out <= SLAVE_ADDR[1];
                    7'd25: scl <= 1'b1;
                    7'd27: scl <= 1'b0;
                    7'd28: sda_out <= SLAVE_ADDR[0];
                    7'd29: scl <= 1'b1;
                    7'd31: scl <= 1'b0;
                    7'd32: sda_out <= 1'b1;             // 1:读
                    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: st_done <= 1'b1;
                    7'd39: begin
                        scl <= 1'b0;
                        cnt <= 1'b0;
                    end
                    default : ;
                endcase
            end
            st_data_rd: begin                          // 读取数据(8 bit)
                case(cnt)
                    7'd0: sda_dir <= 1'b0;
                    7'd1: begin
                        data_r[7] <= sda_in;
                        scl       <= 1'b1;
                    end
                    7'd3: scl  <= 1'b0;
                    7'd5: begin
                        data_r[6] <= sda_in ;
                        scl       <= 1'b1   ;
                    end
                    7'd7: scl  <= 1'b0;
                    7'd9: begin
                        data_r[5] <= sda_in;
                        scl       <= 1'b1  ;
                    end
                    7'd11: scl  <= 1'b0;
                    7'd13: begin
                        data_r[4] <= sda_in;
                        scl       <= 1'b1  ;
                    end
                    7'd15: scl  <= 1'b0;
                    7'd17: begin
                        data_r[3] <= sda_in;
                        scl       <= 1'b1  ;
                    end
                    7'd19: scl  <= 1'b0;
                    7'd21: begin
                        data_r[2] <= sda_in;
                        scl       <= 1'b1  ;
                    end
                    7'd23: scl  <= 1'b0;
                    7'd25: begin
                        data_r[1] <= sda_in;
                        scl       <= 1'b1  ;
                    end
                    7'd27: scl  <= 1'b0;
                    7'd29: begin
                        data_r[0] <= sda_in;
                        scl       <= 1'b1  ;
                    end
                    7'd31: scl  <= 1'b0;
                    7'd32: begin
                        sda_dir <= 1'b1;              // 非应答
                        sda_out <= 1'b1;
                    end
                    7'd33: scl     <= 1'b1;
                    7'd34: st_done <= 1'b1;
                    7'd35: begin
                        scl <= 1'b0;
                        cnt <= 1'b0;
                        i2c_data_r <= data_r;
                    end
                    default  :  ;
                endcase
            end
            st_stop: begin                            // 结束I2C操作
                case(cnt)
                    7'd0: begin
                        sda_dir <= 1'b1;              // 结束I2C
                        sda_out <= 1'b0;
                    end
                    7'd1 : scl     <= 1'b1;
                    7'd3 : sda_out <= 1'b1;
                    7'd15: st_done <= 1'b1;
                    7'd16: begin
                        cnt      <= 1'b0;
                        i2c_done <= 1'b1;             // 向上层模块传递I2C结束信号
                    end
                    default  : ;
                endcase
            end
        endcase
    end
end

endmodule

 下面是使用signal ii的一些教程:

quartus Ⅱ 12.1 使用教程(5) eeprom 读写测试_虚无缥缈vs威武的博客-CSDN博客

猜你喜欢

转载自blog.csdn.net/qq_42792802/article/details/127060929