【FPGA】EEPROM+iic protocol

EEPROM

Electrically Erasable Programmable Read-Only Memory is a commonly used non-volatile memory (data will not be lost after power failure).

Structure diagram:

SCL: the clock pin of iic

SDA: bidirectional data pin

WP: write protection pin, low level - can be written, high level - can be read

A0, A1, A2 address pins

iic

The following is quoted from Brother Atomic:

IIC ( I2C , Inter - Integrated Circuit) is an integrated circuit bus, which is a two-wire serial bus developed by PHILIPS to connect microcontrollers and their peripherals. It is mostly used for master-slave communication between the master and the slave in the case of small data volume and short transmission distance.

The I2C bus consists of a data line SDA and a clock line SCL to form a communication line, which can be used to send data or receive data

Standard mode: 100Kbit /s  

Fast Mode: 400kbit /s 

High speed mode: 3.4Mbit /s

IIC is a half-duplex communication method

idle state
②Start signal
③Stop signal
Validity of data
Response signal
⑥Data transmission

1. Idle state

SCL:                1

SDA:                1

2. Idle state

SCL:                1

SDA:         1 — — > 0

3. Valid data status

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

SDA:             Stable Ready [Data] Stable

                          adopt adopt

Data needs to be ready before the rising edge of SCL arrives. and must be stable before the falling edge of the

4. Answer signal:

Every time the transmitter sends a byte, it releases the data line during the clock pulse 9 , and the receiver feeds back a response signal. When the response signal is low, it is defined as an effective response bit ( ACK for short), indicating that the receiver has successfully received the byte; when the response signal is high, it is specified as a non-acknowledgment bit ( NACK ), which generally indicates that the receiver has not successfully received the byte. 

The requirement for feeding back the valid acknowledgment bit ACK is that the receiver pulls the SDA line low during the low level period before the ninth clock pulse , and ensures that it is a stable low level during the high level period of the clock. If the receiver is the master, after it receives the last byte, it sends a NACK signal to inform the controlled transmitter to end data transmission, and releases the SDA line so that the master receiver sends a stop signal P.

5. Stop state

SCL:                1

SDA:         0 — — > 1

The following EEPROM size is: internally divided into 256 pages, 32 bytes per page

So the storage size is: 256*32*8 / 1024 = 64 Kbit

The write timing is:

About four parts:

1. Device address + response bit

2. Internal high 5 address of EEPROM + response bit

3. Low 8-bit address + response bit inside EEPROM

4. 8-bit data + response bit

Why is the internal address 13 bits?

Answer: 256*32 bytes = 8192. 8191 is 13 bits

Note: AT24C64 supports page write mode

read timing

The complete timing table is as follows:

SCL SDA SCL SDA SCL SDA SCL SDA SCL SDA SCL SDA
1 1 initial state 0 1 1 Register high 8-bit address 0 0 add【15】 Register the lower 8-bit address 0 0 add【7】 read data 0 0 write data 0 0 data【7】
1 1 1 1 0 1 1 1 1 1 1 data【7】 1 1
Write/Read Device Address 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 Answer from the machine 32 0 Answer from the machine 32 0 answer 32 0 answer
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 Answer from the machine
37 1
38 1
39 0 Finish

We can draw a conclusion:

Write something while SCL is 0, 0

Read data when SCL is 0, 1

code:

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

 Here are some tutorials for using signal ii:

Quartus Ⅱ 12.1 tutorial (5) eeprom read and write test

Guess you like

Origin blog.csdn.net/qq_42792802/article/details/127060929