IIC协议及其工程【FPGA】

IIC协议及其工程【FPGA】

    • 一、IIC协议
      • 1、IIC简介
      • 2、IIC中相关的术语
        • 1、应答信号(ACK)
        • 2、无应答信号(NOACK)
        • 3、虚写
        • 3、IIC的时序
    • 二、EEPROM
      • 1、时钟频率
      • 2、起始位和停止位
      • 3、总线时序
      • 4、控制信号bit分配
      • 5、写操作
        • 1、字节写
        • 2、页写
      • 6、读操作
        • 1、随机读
        • 2、顺序读
    • 三、工程
      • 读写控制
      • IIC接口
      • 其它模块
    • 四、参考
    • 五、源码

一、IIC协议

1、IIC简介

同步半双工、飞利浦公司设计。

2、IIC中相关的术语

1、应答信号(ACK)

IIC总线上的所有数据都是以字节传输的,发送端每次发送一个字节,就必须在第9个脉冲周期释放SDA,等待接收端反馈一个应答信号。应答信号为低电平,称为有效应答位(ACK)。
通信双方的回应信号,在时钟高电平期间,数据引脚为低电平。

2、无应答信号(NOACK)

应答信号为高电平时,称为非应答位(NACK)。
通常用于主机发送给从机的信号,在时钟高电平期间,数据引脚为高电平。

3、虚写

使从机内的存储单元地址指针指向我们想要读取的存储单元地址处,首先发送了一次Dummy Write也就是虚写操作,只所以称为虚写,是因为我们并不是真的要写数据,而是通过这种虚写操作使地址指针指向虚写操作中字地址的位置,等从机应答后,就可以以当前地址读的方式读数据了

3、IIC的时序

sda在时钟低电平期间更新数据
在时钟高电平期间采集数据

二、EEPROM

1、时钟频率

支持最大400K的频率,跟芯片的供电电压相关,电压越大,支持的时钟频率也就越大

2、起始位和停止位

起始位分为高电平持续时间和低电平持续时间,两者的时间长度都为时钟周期的1/4。
在不同的工作电压下,需要维持的时间也是不同的。

停止位也是对应有不同的持续时间

3、总线时序

需要重点关注SDA引脚数据变化的位置,发生在时钟引脚SCL的低电平期间

4、控制信号bit分配

每个IIC设备的地址有一段地址是固定的,有一段是根据外围的引脚设置,最后一个bit(R/W)设置本次控制信号是读还是写,1表示读操作,0表示写操作。
发送控制字节的时候先发送地址的高位。
固定的地址信息“1010”,是IIC接口的EEPROM固定的地址头部信息。
摄像头的地址全都是固定的,没有引脚配置选项。

5、写操作

1、字节写

每次写入一个字节(8bit)的数据。
主机在数据信号发送完成之后,发送应答信号给从机,并且立即发送停止位信号。

2、页写

发送一次地址信息,一直传输数据存储数据,直到发送停止信号。

6、读操作

1、随机读

读取任意地址的数据,读取一次数据之后,主机发送无应答信号停止数据传输

2、顺序读

写入一个地址信号之后,连续获取该地址之后的数据,直到主机发送无应答信号(在随机读的基础上),从机才会停止发送数据。

三、工程

通过IIC协议完成对eeprom的读写

读写控制

`include "param.v"

module eeprom_rw #(parameter WR_LEN = 3,RD_LEN = 4)(
    input               clk     ,
    input               rst_n   ,
    
    input       [7:0]   din     ,
    input               din_vld ,
    input               rd_en   ,
    output      [7:0]   dout    ,//控制器输出数据
    output              dout_vld,
    input               busy    ,

    output              req     ,
    output      [3:0]   cmd     ,
    output      [7:0]   wr_data ,
    input       [7:0]   rd_data ,
    input               done     //传输完成标志
  
);


//状态机参数
    localparam      IDLE    = 6'b00_0001    ,
                    WR_REQ  = 6'b00_0010    ,//写传输 发送请求、命令、数据
                    WAIT_WR = 6'b00_0100    ,//等待一个字节传完
                    RD_REQ  = 6'b00_1000    ,//读传输 发送请求、命令、数据
                    WAIT_RD = 6'b01_0000    ,//等待一个自己传完
                    DONE    = 6'b10_0000    ;//一次读或写完成
//信号定义
    reg     [5:0]   state_c         ;
    reg     [5:0]   state_n         ;

    reg     [7:0]   cnt_byte        ;//数据传输 字节计数器
    wire            add_cnt_byte    ;
    wire            end_cnt_byte    ;

    reg             tx_req          ;//请求
    reg     [3:0]   tx_cmd          ;
    reg     [7:0]   tx_data         ;
    
    reg     [8:0]   wr_addr         ;//写eeprom地址
    reg     [8:0]   rd_addr         ;//读eeprom地址
    
    wire            wfifo_rd        ; 
    wire            wfifo_wr        ;
    wire            wfifo_empty     ;
    wire            wfifo_full      ;
    wire    [7:0]   wfifo_qout      ;
    wire    [5:0]   wfifo_usedw     ;
   
    wire            rfifo_rd        ;
    wire            rfifo_wr        ;
    wire            rfifo_empty     ;
    wire            rfifo_full      ;
    wire    [7:0]   rfifo_qout      ;
    wire    [5:0]   rfifo_usedw     ;

    reg             rd_flag         ;
    reg     [7:0]   user_data       ;
    reg             user_data_vld   ;

    wire            idle2wr_req     ; 
    wire            wr_req2wait_wr  ;
    wire            wait_wr2wr_req  ;
    wire            wait_wr2done    ;
    wire            idle2rd_req     ;
    wire            rd_req2wait_rd  ;
    wire            wait_rd2rd_req  ;
    wire            wait_rd2done    ;
    wire            done2idle       ;

//状态机设计
    always @(posedge clk or negedge rst_n) begin 
        if (rst_n==0) begin
            state_c <= IDLE ;
        end
        else begin
            state_c <= state_n;
       end
    end
    
    always @(*) begin 
        case(state_c)  
            IDLE :begin
                if(idle2wr_req)
                    state_n = WR_REQ ;
                else if(idle2rd_req)
                    state_n = RD_REQ ;
                else 
                    state_n = state_c ;
            end
            WR_REQ :begin
                if(wr_req2wait_wr)
                    state_n = WAIT_WR ;
                else 
                    state_n = state_c ;
            end
            WAIT_WR :begin
                if(wait_wr2wr_req)
                    state_n = WR_REQ ;
                else if(wait_wr2done)
                    state_n = DONE ;
                else 
                    state_n = state_c ;
            end
            RD_REQ :begin
                if(rd_req2wait_rd)
                    state_n = WAIT_RD ;
                else 
                    state_n = state_c ;
            end
            WAIT_RD :begin
                if(wait_rd2rd_req)
                    state_n = RD_REQ ;
                else if(wait_rd2done)
                    state_n = DONE ;
                else 
                    state_n = state_c ;
            end
            DONE :begin
                if(done2idle)
                    state_n = IDLE ;
                else 
                    state_n = state_c ;
            end
            default : state_n = IDLE ;
        endcase
    end
    
    assign idle2wr_req      = state_c==IDLE     && (wfifo_usedw > WR_LEN-2);
    assign wr_req2wait_wr   = state_c==WR_REQ   && (1'b1);
    assign wait_wr2wr_req   = state_c==WAIT_WR  && (done & cnt_byte < WR_LEN-1);
    assign wait_wr2done     = state_c==WAIT_WR  && (end_cnt_byte);
    assign idle2rd_req      = state_c==IDLE     && (rd_en);
    assign rd_req2wait_rd   = state_c==RD_REQ   && (1'b1);
    assign wait_rd2rd_req   = state_c==WAIT_RD  && (done & cnt_byte < RD_LEN-1);
    assign wait_rd2done     = state_c==WAIT_RD  && (end_cnt_byte);
    assign done2idle        = state_c==DONE     && (1'b1);
    
//cnt_byte  
    always @(posedge clk or negedge rst_n) begin 
        if (rst_n==0) begin
            cnt_byte <= 0; 
        end
        else if(add_cnt_byte) begin
            if(end_cnt_byte)
                cnt_byte <= 0; 
            else
                cnt_byte <= cnt_byte+1 ;
       end
    end
    assign add_cnt_byte = (state_c==WAIT_WR | state_c==WAIT_RD) & done;
    assign end_cnt_byte = add_cnt_byte  && cnt_byte == ((state_c==WAIT_WR)?
                                                        (WR_LEN-1):(RD_LEN-1));
//输出

    always  @(posedge clk or negedge rst_n)begin
        if(~rst_n)begin
            TX(1'b0,4'd0,8'd0);
        end
        else if(state_c==WR_REQ)begin
            case(cnt_byte)
                0           :TX(1'b1,{`CMD_START | `CMD_WRITE},{`I2C_ADR,wr_addr[8],`WR_BIT});//发起始位、写控制字
                1           :TX(1'b1,`CMD_WRITE,wr_addr[7:0]);   //发 写地址
                WR_LEN-1  :TX(1'b1,{`CMD_WRITE | `CMD_STOP},wfifo_qout);  //最后一个字节时 发数据、停止位
                default     :TX(1'b1,`CMD_WRITE,wfifo_qout);    //中间发数据(如果有)
            endcase 
        end
        else if(state_c==RD_REQ)begin
            case(cnt_byte)
                0           :TX(1'b1,{`CMD_START | `CMD_WRITE},{`I2C_ADR,rd_addr[8],`WR_BIT});//发起始位、写控制字
                1           :TX(1'b1,`CMD_WRITE,rd_addr[7:0]);   //发 读地址
                2           :TX(1'b1,{`CMD_START | `CMD_WRITE},{`I2C_ADR,rd_addr[8],`RD_BIT});//发起始位、读控制字
                RD_LEN-1    :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                   req     ;
        input       [3:0]       command ;
        input       [7:0]       data    ;
        begin 
            tx_req  = req;
            tx_cmd  = command;
            tx_data = data;
        end 
    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 + WR_LEN-2;
        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 + RD_LEN - 3;
        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 <= rfifo_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;

//fifo例化

wrfifo	u_wrfifo (
	.aclr   (~rst_n     ),
	.clock  (clk        ),
	.data   (din        ),
	.rdreq  (wfifo_rd   ),
	.wrreq  (wfifo_wr   ),
	.empty  (wfifo_empty),
	.full   (wfifo_full ),
	.q      (wfifo_qout ),
	.usedw  (wfifo_usedw)
	);

    assign wfifo_rd = state_c==WAIT_WR && done && cnt_byte > 1;
    assign wfifo_wr = ~wfifo_full & din_vld;

rdfifo	u_rdfifo (
	.aclr   (~rst_n     ),
	.clock  (clk        ),
	.data   (rd_data    ),
	.rdreq  (rfifo_rd   ),
	.wrreq  (rfifo_wr   ),
	.empty  (rfifo_empty),
	.full   (rfifo_full ),
	.q      (rfifo_qout ),
	.usedw  (rfifo_usedw)
	);

    assign rfifo_wr = ~rfifo_full && state_c==WAIT_RD && cnt_byte > 2 && done;
    assign rfifo_rd = rd_flag && ~busy;
endmodule

IIC接口

`include "param.v"

module i2c_master(
    input               clk         ,
    input               rst_n       ,

    input               req         ,
    input       [3:0]   cmd         ,
    input       [7:0]   din         ,

    output      [7:0]   dout        ,
    output              done        ,

    output              i2c_scl     ,
    input               i2c_sda_i   ,
    output              i2c_sda_o   ,
    output              i2c_sda_oe     
    );

//状态机参数定义

    localparam  IDLE  = 7'b000_0001,
                START = 7'b000_0010,
                WRITE = 7'b000_0100,
                RACK  = 7'b000_1000,
                READ  = 7'b001_0000,
                SACK  = 7'b010_0000,
                STOP  = 7'b100_0000;

//信号定义

    reg     [6:0]       state_c     ;
    reg     [6:0]       state_n     ;

    reg     [8:0]       cnt_scl     ;//产生i2c时钟
    wire                add_cnt_scl ;
    wire                end_cnt_scl ;
    reg     [3:0]       cnt_bit     ;//传输数据 bit计数器
    wire                add_cnt_bit ;
    wire                end_cnt_bit ;
    reg     [3:0]       bit_num     ;
    
    reg                 scl         ;//输出寄存器
    reg                 sda_out     ;
    reg                 sda_out_en  ;

    reg     [7:0]       rx_data     ;
    reg                 rx_ack      ;
    reg     [3:0]       command     ;
    reg     [7:0]       tx_data     ;//发送数据

    wire                idle2start  ; 
    wire                idle2write  ; 
    wire                idle2read   ; 
    wire                start2write ; 
    wire                start2read  ; 
    wire                write2rack  ; 
    wire                read2sack   ; 
    wire                rack2stop   ; 
    wire                sack2stop   ; 
    wire                rack2idle   ; 
    wire                sack2idle   ; 
    wire                stop2idle   ; 


//状态机
    always @(posedge clk or negedge rst_n) begin 
        if (rst_n==0) begin
            state_c <= IDLE ;
        end
        else begin
            state_c <= state_n;
       end
    end
    
    always @(*) begin 
        case(state_c)  
            IDLE :begin
                if(idle2start)
                    state_n = START ;
                else if(idle2write)
                    state_n = WRITE ;
                else if(idle2read)
                    state_n = READ ;
                else 
                    state_n = state_c ;
            end
            START :begin
                if(start2write)
                    state_n = WRITE ;
                else if(start2read)
                    state_n = READ ;
                else 
                    state_n = state_c ;
            end
            WRITE :begin
                if(write2rack)
                    state_n = RACK ;
                else 
                    state_n = state_c ;
            end
            RACK :begin
                if(rack2stop)
                    state_n = STOP ;
                else if(rack2idle)
                    state_n = IDLE ;
                else 
                    state_n = state_c ;
            end
            READ :begin
                if(read2sack)
                    state_n = SACK ;
                else 
                    state_n = state_c ;
            end
            SACK :begin
                if(sack2stop)
                    state_n = STOP ;
                else if(sack2idle)
                    state_n = IDLE ;
                else 
                    state_n = state_c ;
            end
            STOP :begin
                if(stop2idle)
                    state_n = IDLE ;
                else 
                    state_n = state_c ;
            end
            default : state_n = IDLE ;
        endcase
    end
    
    assign idle2start  = state_c==IDLE  && (req && (cmd&`CMD_START));
    assign idle2write  = state_c==IDLE  && (req && (cmd&`CMD_WRITE));
    assign idle2read   = state_c==IDLE  && (req && (cmd&`CMD_READ ));
    assign start2write = state_c==START && (end_cnt_bit && (command&`CMD_WRITE));
    assign start2read  = state_c==START && (end_cnt_bit && (command&`CMD_READ ));
    assign write2rack  = state_c==WRITE && (end_cnt_bit);
    assign read2sack   = state_c==READ  && (end_cnt_bit);
    assign rack2stop   = state_c==RACK  && (end_cnt_bit && ((command&`CMD_STOP ) || rx_ack));
    assign sack2stop   = state_c==SACK  && (end_cnt_bit && (command&`CMD_STOP ));
    assign rack2idle   = state_c==RACK  && (end_cnt_bit && ((command&`CMD_STOP ) == 0));
    assign sack2idle   = state_c==SACK  && (end_cnt_bit && (command&`CMD_STOP ) == 0);
    assign stop2idle   = state_c==STOP  && (end_cnt_bit);
    
//计数器
    always @(posedge clk or negedge rst_n) begin 
        if (rst_n==0) begin
            cnt_scl <= 0; 
        end
        else if(add_cnt_scl) begin
            if(end_cnt_scl)
                cnt_scl <= 0; 
            else
                cnt_scl <= cnt_scl+1 ;
       end
    end
    assign add_cnt_scl = (state_c != IDLE);
    assign end_cnt_scl = add_cnt_scl && cnt_scl == (`SCL_PERIOD)-1 ;

    always @(posedge clk or negedge rst_n) begin 
        if (rst_n==0) begin
            cnt_bit <= 0; 
        end
        else if(add_cnt_bit) begin
            if(end_cnt_bit)
                cnt_bit <= 0; 
            else
                cnt_bit <= cnt_bit+1 ;
       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 == WRITE | state_c == READ) 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
    always  @(posedge clk or negedge rst_n)begin
        if(~rst_n)begin
            scl <= 1'b1;
        end
        else if(idle2start | idle2write | idle2read)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 //若继续发送下一bit则继续拉低,否则保持高
            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总线
                sda_out <= 1'b1;
            end
            else if(cnt_scl == `HIGH_HALF)begin    //时钟高电平时拉低sda总线 
                sda_out <= 1'b0;                //保证从机能检测到起始位
            end 
        end 
        else if(state_c == WRITE && cnt_scl == `LOW_HLAF)begin  //scl低电平时发送数据   并串转换
            sda_out <= tx_data[7-cnt_bit];      
        end 
        else if(state_c == SACK && cnt_scl == `LOW_HLAF)begin  //发应答位
            sda_out <= (command&`CMD_STOP)?1'b1:1'b0;
        end 
        else if(state_c == STOP)begin //发停止位
            if(cnt_scl == `LOW_HLAF)begin       //时钟低电平时拉低sda总线
                sda_out <= 1'b0;
            end
            else if(cnt_scl == `HIGH_HALF)begin    //时钟高电平时拉高sda总线 
                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 | start2read | write2rack | 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 == `HIGH_HALF)begin
            rx_data[7-cnt_bit] <= i2c_sda_i;    //串并转换
        end
    end

//rx_ack
    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

//输出信号

    assign i2c_scl    = scl         ;
    assign i2c_sda_o  = sda_out     ;
    assign i2c_sda_oe = sda_out_en  ;
   
    assign dout = rx_data;
    assign done = rack2idle | sack2idle | stop2idle;

endmodule

其它模块

按键消抖
串口

四、参考

【FPGA】FPGA基于i2c的eeprom读写

五、源码

https://github.com/IvanXiang/FPGA_IIC_EEPROM

你可能感兴趣的:(fpga开发,单片机,嵌入式硬件)