IIC协议详解

IIC(Inter-Integrated Circuit,集成电路总线)是一种广泛使用的​​同步、半双工、多主从串行通信协议​​,由飞利浦(现恩智浦)公司开发,主要用于短距离板级设备间通信。以下是IIC协议的详细解析:


​一、IIC协议基础​

  1. ​核心特点​​:

    • ​两根线通信​​:SDA(串行数据线)和SCL(串行时钟线)。
    • ​多主从架构​​:支持多个主设备和多个从设备共享总线。
    • ​地址寻址​​:每个从设备有唯一地址(7位或10位)。
    • ​开漏输出​​:SDA和SCL均需外接上拉电阻,避免总线冲突。
    • ​半双工​​:同一时间只能发送或接收数据。
    • ​低速到中速传输​​:标准模式(100 kbps)、快速模式(400 kbps)、高速模式(3.4 Mbps)等。
  2. ​物理层结构​​:

    • ​SDA​​:传输数据和地址信息。
    • ​SCL​​:主设备提供同步时钟信号。
    • ​上拉电阻​​:典型值1kΩ~10kΩ,具体取决于总线电容和传输速率。

​二、IIC协议帧格式​

IIC通信由​​起始信号、地址帧、数据帧、应答信号(ACK/NACK)、停止信号​​组成。

1. ​​起始条件(Start Condition)​
  • SCL为高电平时,SDA从高电平跳变到低电平。
  • 标志一次传输开始。
2. ​​地址帧(Address Frame)​
  • ​7位地址模式​​:
    • 主设备发送7位从设备地址 + 1位读写控制位(0:写,1:读)。
    • 例如:地址0x68(7位)的写操作帧为 0xD0(0x68 << 1 | 0)。
  • ​10位地址模式​​(扩展模式):
    • 分两次发送:前5位固定为11110,后接2位高位地址和读写位;第二字节为剩余8位地址。
3. ​​应答信号(ACK/NACK)​
  • 每传输完一个字节(8位)后,接收方需在第9个时钟周期发送应答信号:
    • ​ACK​​:SDA拉低,表示成功接收。
    • ​NACK​​:SDA保持高电平,表示接收失败或传输结束。
4. ​​数据帧(Data Frame)​
  • 数据按字节传输(8位),高位(MSB)在前。
  • 每个数据帧后必须跟随ACK/NACK信号。
5. ​​停止条件(Stop Condition)​
  • SCL为高电平时,SDA从低电平跳变到高电平。
  • 标志一次传输结束。

​三、IIC通信流程示例​

以主设备向从设备(地址0x50)写入数据0xAA为例:

  1. 主设备发送​​起始信号​​。
  2. 发送地址帧:0xA0(0x50 << 1 | 0,写操作)。
  3. 从设备返回​​ACK​​。
  4. 主设备发送数据0xAA
  5. 从设备返回​​ACK​​。
  6. 主设备发送​​停止信号​​。

​四、关键机制​

  1. ​时钟同步​​:

    • 多个主设备可通过SCL线的“线与”逻辑同步时钟,避免冲突。
  2. ​仲裁机制​​:

    • 当多个主设备同时发送数据时,通过SDA线电平检测冲突,优先级高的主设备继续通信。
  3. ​重复起始条件(Repeated Start)​​:

    • 在一次通信中不发送停止信号,直接发送新的起始信号,切换读写模式或切换从设备。

​五、传输模式​

模式速率应用场景
标准模式(Standard)100 kbps通用低速设备(如EEPROM)
快速模式(Fast)400 kbps传感器、实时时钟
高速模式(High-Speed)3.4 Mbps高速数据传输

​六、典型应用场景​

  • ​存储器​​:读写EEPROM(如AT24C02)。
  • ​传感器​​:获取温度、湿度数据(如BMP280、MPU6050)。
  • ​显示屏​​:控制OLED/LCD模块。
  • ​系统管理​​:SMBus(基于IIC的系统管理总线)。

​七、常见问题与解决​

  1. ​上拉电阻取值​​:根据总线电容和速率选择,通常3.3kΩ~10kΩ。
  2. ​地址冲突​​:确保从设备地址唯一,或使用10位地址模式。
  3. ​总线电容过大​​:导致信号边沿变缓,需降低上拉电阻值或使用缓冲器。
  4. ​多主竞争​​:依赖仲裁机制,设计时避免频繁多主操作。

​八、调试技巧​

  • 用示波器或逻辑分析仪抓取SDA和SCL波形,检查时序是否符合规范。
  • 确认从设备地址正确(数据手册标注的地址通常是7位)。
  • 检查硬件连接,排除短路或接触不良。

九、Verilog代码示例  

///代码说明/
///1.此代码仅通过modelsim仿真,无实际上板调试///
///2.此代码可能存在与芯片时序不匹配,如需使用请根据芯片数据手册时序图调整///

module i2c_driver (
    input  wire        clk            ,      // 系统时钟(如50MHz)
    input  wire        rst            ,      // 异步复位(高电平有效)
    // I2C物理接口                    
    output reg         i2c_scl        ,      // I2C时钟线
    inout  wire        i2c_sda        ,      // I2C数据线
    // 用户控制接口                   
    input  wire        start          ,      // 启动传输信号(上升沿触发)
	input  wire [7:0]  restart_num    ,      // 重发起始信号位置设置,读数据时需要在发送地址后重新发送start信号
	input  wire [7:0]  wr_num         ,      // IIC待发送数据字节个数
	input  wire [7:0]  rd_num         ,      // IIC接收字节个数
	input  wire        wr_en          ,      // IIC待发送数据写入使能
	input  wire [7:0]  wr_data        ,      // IIC待发送数据
	input  wire        rd_en          ,      // IIC接收数据fifo读取
	output wire [7:0]  rd_data        ,      // IIC接收数据
	output wire        rd_data_ready  ,      // IIC接收数据准备完成
	output wire        i2c_busy              // IIC忙状态
);

    parameter              CLK_DIV   = 500                    ;  // 50MHz / (100kHz * 2) = 250 → 实际SCL为50MHz/(2*CLK_DIV)=50kHz
    parameter              START_DIV = 200                    ;  // 50MHz / (100kHz * 2) = 250 → 实际SCL为50MHz/(2*CLK_DIV)=50kHz

    parameter              IDLE          =  8'b00000000     ; ///空闲状态	
    parameter              START         =  8'b00000001     ; ///起始位发送
    parameter              SEND_DATA     =  8'b00000010     ; ///发送数据
    parameter              SD_ACK        =  8'b00000100     ; ///发送数据等待ACK
    parameter              READ_DATA     =  8'b00001000     ; ///接收数据
    parameter              RD_ACK        =  8'b00010000     ; ///接收数据返回ACK 
    parameter              STOP          =  8'b00100000     ; ///发送停止位	
    parameter              RESTART_DLY   =  8'b01000000     ; ///重新发送起始位延时
	
	reg       [7:0]     cur_state          ;
	reg       [7:0]     next_state         ;
	reg                 start_d            ;
	reg                 start_d1           ;
	wire                start_p            ; //IIC流程开始信号
	reg                 start_done         ; //IIC起始位发送完成
	reg                 send_done          ; //IIC单字节数据发送完成
	reg                 ack_sdone0         ; //IIC发送数据接收ACK正常,发送字节未完成
	reg                 ack_sdone1         ; //IIC发送数据接收ACK正常,发送字节完成
	reg                 ack_sdone2         ; //IIC发送数据接收ACK正常,需重新发送起始位
	reg                 ack_sdone3         ; //IIC发送数据接收ACK正常,发送字节完成,无读取数据操作
	reg                 read_done          ; //IIC单字节数据接收完成
	reg                 ack_rdone0         ; //IIC单字节数据接收未达到目标字节数,同时返回ACK完毕
	reg                 ack_rdone1         ; //IIC单字节数据接收达到目标字节数,同时返回ACK完毕
	reg                 stop_done          ; //IIC发送接收字节完毕后发送停止位完成
	reg          [7:0]  send_bit_cnt       ; //发送bit数据计数
	reg          [7:0]  send_byte_cnt      ; //发送字节数据计数
	reg          [7:0]  read_bit_cnt       ; //接收bit数据计数
	reg          [7:0]  read_byte_cnt      ; //接收字节数据计数
	reg                 rd_sdata           ; //读取发送数据使能
	wire         [8:0]  send_data          ; //发送字节数据
	reg                 wr_rdata           ; //写入接收数据使能
	reg          [7:0]  read_data          ; //接收字节数据
	reg                 sda_out            ; //SDA管脚IO状态,0:输入;1:输出
	reg                 sda_reg            ; //SDA输出值
	
	wire                clk_middle         ; //分频中间态
	reg          [31:0] clk_cnt            ; //分频计数
	reg          [31:0] start_cnt          ; //起始位保持时间计数
	reg                 restart_done       ; //重新发送起始位等待完成
	
	
	
	send_data_fifo u_send_data_fifo(//发送数据fifo,需使用First_Word Fall_Through模式
    .Data                           (wr_data                ),              //input [7:0] Data
    .Clk                            (clk                    ),              //input Clk
    .WrEn                           (wr_en                  ),              //input WrEn
    .RdEn                           (rd_sdata               ),              //input RdEn
    .Reset                          (rst                    ),              //input Reset
    .Q                              (send_data[7:0]         ),              //output [7:0] Q
    .Empty                          (                       ),              //output Empty
    .Full                           (                       )               //output Full
    );
	assign send_data[8] = 1'b0 ;
	assign start_p = ~start_d1 && start_d ;
	assign i2c_busy = (cur_state == IDLE) ? 1'b0 : 1'b1 ;
	assign clk_middle = (clk_cnt == CLK_DIV/2) ? 1'b1 : 1'b0 ;
	assign i2c_sda =  sda_out ? sda_reg : 1'bz ;
	
	always @(posedge clk or posedge rst) begin
	    if(rst) begin
		   start_d <= 1'b0  ; 
		   start_d1<= 1'b0  ;
		end
		else begin
		   start_d <= start  ;  
		   start_d1<= start_d;
		end
	end
	/主状态机
	always @(posedge clk or posedge rst) begin
        if (rst)
            cur_state <= IDLE;
        else
            cur_state <= next_state;
    end
	
	always @(*) begin
		if(rst)
		  next_state <=  IDLE ;
		else begin
			case (cur_state)
				IDLE      : 
				   next_state <= start_p ? START : IDLE ;
				START     :
				   next_state <= start_done ? SEND_DATA : START ;
				SEND_DATA :
				   next_state <= send_done ? SD_ACK : SEND_DATA ;
				SD_ACK    :
				   next_state <= ack_sdone0 ? SEND_DATA   :
								 ack_sdone1 ? READ_DATA   : 
								 ack_sdone2 ? RESTART_DLY : 
								 ack_sdone3 ? STOP        : SD_ACK ;			
				READ_DATA :
				   next_state <= read_done ? RD_ACK : READ_DATA ;
				RD_ACK    :
				   next_state <= ack_rdone0 ? READ_DATA :
								 ack_rdone1 ? STOP      : RD_ACK ;
				STOP      :
				   next_state <= stop_done ? IDLE : STOP ;
				RESTART_DLY :
				   next_state <= restart_done ? START : RESTART_DLY ;
				default   :
				   next_state <= IDLE  ;
			endcase		
		end
	end
	重新发送起始位延时计数
	always @(posedge clk or posedge rst) begin
        if (rst)
            start_cnt <= 32'd0;
        else if(cur_state == RESTART_DLY)
		    start_cnt <= start_cnt + 1'b1;		
		else 
		    start_cnt <= 32'd0;
    end
	重新发送起始位延时完成
	always @(posedge clk or posedge rst) begin
        if (rst)
            restart_done <= 1'b0;
        else if(start_cnt == (CLK_DIV - 1'b1))
		    restart_done <= 1'b1;		
		else 
		    restart_done <= 1'b0;	
    end
	起始位发送完成
	always @(posedge clk or posedge rst) begin
        if (rst)
            start_done <= 1'b0;
        else if((clk_cnt == (CLK_DIV - 1'b1))&&(cur_state == START))
		    start_done <= 1'b1;		
		else 
		    start_done <= 1'b0;	
    end
	分频处理
	always @(posedge clk or posedge rst) begin
        if (rst)
            clk_cnt <= 32'd0;
        else if(cur_state == IDLE)
            clk_cnt <= 32'd0;
		else if(clk_cnt < (CLK_DIV - 1'b1))
		    clk_cnt <= clk_cnt + 1'b1;		
		else 
		    clk_cnt <= 32'd0;
    end
	时钟处理
	always @(posedge clk or posedge rst) begin
        if (rst)
            i2c_scl <= 1'b1;
        else if((cur_state == IDLE)||(cur_state == RESTART_DLY))
            i2c_scl <= 1'b1;
		else if(clk_cnt == (CLK_DIV - 1'b1))
            i2c_scl <= ~ i2c_scl;
		else
            i2c_scl <=   i2c_scl;		   
    end
	SDA方向处理
	always @(posedge clk or posedge rst) begin
        if (rst)
            sda_out <= 1'b1;
        else if(cur_state == SD_ACK) 
            sda_out <= 1'b0;
		else 
		    sda_out <= 1'b1;
    end
	SDA输出数据处理
	always @(posedge clk or posedge rst) begin
        if (rst)
            sda_reg <= 1'b1;
        else if(cur_state == START)
            sda_reg <= 1'b0;
		else if(cur_state == SEND_DATA)
            sda_reg <= send_data[send_bit_cnt];		
		else if((cur_state == RD_ACK)&&(read_byte_cnt <rd_num))
            sda_reg <= 1'b0;
		else if((cur_state == STOP)&&(clk_cnt > (CLK_DIV/2)) && i2c_scl)
            sda_reg <= 1'b0;	
		else 
		    sda_reg <= 1'b1;
    end
	SDA输出bit计数
	always @(posedge clk or posedge rst) begin
        if (rst)
            send_bit_cnt <= 8'd8;
		else if((cur_state == SEND_DATA) && ~i2c_scl && clk_middle )
            send_bit_cnt <= send_bit_cnt - 1'b1 ;		
		else if((cur_state == SEND_DATA) &&(next_state == SD_ACK))
            send_bit_cnt <= 8'd8;		
		else 
		    send_bit_cnt <= send_bit_cnt;
    end
	SDA输出字节完成
	always @(posedge clk or posedge rst) begin
        if (rst)
            send_done <= 1'b0;
		else if((cur_state == SEND_DATA) && i2c_scl && (clk_cnt == (CLK_DIV - 1'b1)) && (send_bit_cnt == 8'd0))
            send_done <= 1'b1 ;
		else 
		    send_done <= 1'b0;
    end	
	SDA输出byte计数
	always @(posedge clk or posedge rst) begin
        if (rst)
            send_byte_cnt <= 8'd0;
		else if((cur_state == SEND_DATA) &&(next_state == SD_ACK) )
            send_byte_cnt <= send_byte_cnt + 1'b1 ;		
		else if(cur_state == STOP)
            send_byte_cnt <= 8'd0;		
		else 
		    send_byte_cnt <= send_byte_cnt;
    end
	SDA输出数据切换使能
	always @(posedge clk or posedge rst) begin
        if (rst)
            rd_sdata <= 1'b0 ;
		else if((cur_state == SEND_DATA) &&(next_state == SD_ACK) )
            rd_sdata <= 1'b1 ;			
		else 
		    rd_sdata <= 1'b0 ;
    end	
	
	SDA输入bit计数
	always @(posedge clk or posedge rst) begin
        if (rst)
            read_bit_cnt <= 8'd0;
		else if((cur_state == READ_DATA) && i2c_scl && clk_middle )
            read_bit_cnt <= read_bit_cnt + 1'b1 ;		
		else if((cur_state == READ_DATA) &&(next_state == RD_ACK))
            read_bit_cnt <= 8'd0;		
		else 
		    read_bit_cnt <= read_bit_cnt;
    end
	SDA输入bit计数
	always @(posedge clk or posedge rst) begin
		if (rst)
			read_byte_cnt <= 8'd0;
		else if((cur_state == READ_DATA)&&(next_state == RD_ACK))
			read_byte_cnt <= read_byte_cnt + 8'd1;
		else if(cur_state == STOP)
			read_byte_cnt <= 8'd0;
		else
			read_byte_cnt <= read_byte_cnt;
	end
	SDA输出bit计数
	always @(posedge clk or posedge rst) begin
        if (rst)
            read_done <= 1'b0;
		else if((cur_state == READ_DATA) && ~i2c_scl && (clk_cnt == (CLK_DIV - 1'b1)) && (read_bit_cnt == 8'd8))
            read_done <= 1'b1 ;			
		else 
		    read_done <= 1'b0;
    end
	
	SDA输入数据处理
	always @(posedge clk or posedge rst) begin
        if (rst)
            read_data <= 8'h0;
		else if((cur_state == READ_DATA)&& i2c_scl && clk_middle)
            read_data <= {read_data[6:0],i2c_sda};
		else 
		    read_data <= read_data;
    end
	SDA输入数据写入使能
	always @(posedge clk or posedge rst) begin
        if (rst)
            wr_rdata <= 1'b0 ;
		else if((cur_state == READ_DATA) &&(next_state == RD_ACK) )
            wr_rdata <= 1'b1 ;			
		else 
		    wr_rdata <= 1'b0 ;
    end	
	sack输入数据处理
	reg  ack_signle;
	always @(posedge clk or posedge rst) begin//ACK信号提取
        if (rst)
            ack_signle <= 1'b0;
		else if((cur_state == SD_ACK)&& i2c_scl && clk_middle && ~i2c_sda)
            ack_signle <= 1'b1;
		else if(cur_state != SD_ACK)
		    ack_signle <= 1'b0;
		else 
		    ack_signle <= ack_signle;
    end
	always @(posedge clk or posedge rst) begin//当前发送字节数小于设定字节数,同时不等于重新发送起始位字节数
        if (rst)
            ack_sdone0 <= 1'b0;
		else if((cur_state == SD_ACK)&& ack_signle && i2c_scl && (clk_cnt == (CLK_DIV - 1'b1)) && (send_byte_cnt < wr_num) && (send_byte_cnt != restart_num))
            ack_sdone0 <= 1'b1;
		else 
		    ack_sdone0 <= 1'b0;
    end
	always @(posedge clk or posedge rst) begin//当前发送字节数等于设定字节数
        if (rst)
            ack_sdone1 <= 1'b0;
		else if((cur_state == SD_ACK)&& ack_signle && i2c_scl && (clk_cnt == (CLK_DIV - 1'b1)) && (send_byte_cnt == wr_num) && (rd_num != 8'd0))
            ack_sdone1 <= 1'b1;
		else 
		    ack_sdone1 <= 1'b0;
    end
	always @(posedge clk or posedge rst) begin//当前发送字节数等于重新发送起始位字节数
        if (rst)
            ack_sdone2 <= 1'b0;
		else if((cur_state == SD_ACK)&& ack_signle && i2c_scl && (clk_cnt == (CLK_DIV - 1'b1)) && (send_byte_cnt == restart_num))
            ack_sdone2 <= 1'b1;
		else 
		    ack_sdone2 <= 1'b0;
    end
	always @(posedge clk or posedge rst) begin//当前发送字节数等于设定字节数,同时无读取操作
        if (rst)
            ack_sdone3 <= 1'b0;
		else if((cur_state == SD_ACK)&& ack_signle && i2c_scl && (clk_cnt == (CLK_DIV - 1'b1)) && (send_byte_cnt == wr_num) && (rd_num == 8'd0))
            ack_sdone3 <= 1'b1;
		else 
		    ack_sdone3 <= 1'b0;
    end
	rack输入数据处理
	always @(posedge clk or posedge rst) begin//当前接收字节数小于设置值
        if (rst)
            ack_rdone0 <= 1'b0;
		else if((cur_state == RD_ACK)&& i2c_scl && (clk_cnt == (CLK_DIV - 1'b1)) && (read_byte_cnt < rd_num))
            ack_rdone0 <= 1'b1;
		else 
		    ack_rdone0 <= 1'b0;
    end
	always @(posedge clk or posedge rst) begin//当前接收字节数等于设置值
        if (rst)
            ack_rdone1 <= 1'b0;
		else if((cur_state == RD_ACK)&& i2c_scl && (clk_cnt == (CLK_DIV - 1'b1)) && (read_byte_cnt == rd_num))
            ack_rdone1 <= 1'b1;
		else 
		    ack_rdone1 <= 1'b0;
    end
	///STOP状态处理///
	always @(posedge clk or posedge rst) begin//当前接收字节数等于设置值
        if (rst)
            stop_done <= 1'b0;
		else if((cur_state == STOP) && (clk_cnt == (CLK_DIV -1'b1)) && i2c_scl)
            stop_done <= 1'b1;
		else 
		    stop_done <= 1'b0;
    end
	
	
	
	read_data_fifo u_read_data_fifo(//接收数据fifo
    .Data                           (read_data              ),              //input [7:0] Data
    .Clk                            (clk                    ),              //input Clk
    .WrEn                           (wr_rdata               ),              //input WrEn
    .RdEn                           (rd_en                  ),              //input RdEn
    .Reset                          (rst                    ),              //input Reset
    .Q                              (rd_data                ),              //output [7:0] Q
    .Empty                          (rd_data_ready          ),              //output Empty
    .Full                           (                       )               //output Full
    );
	
endmodule 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值