IIC(Inter-Integrated Circuit,集成电路总线)是一种广泛使用的同步、半双工、多主从串行通信协议,由飞利浦(现恩智浦)公司开发,主要用于短距离板级设备间通信。以下是IIC协议的详细解析:
一、IIC协议基础
-
核心特点:
- 两根线通信:SDA(串行数据线)和SCL(串行时钟线)。
- 多主从架构:支持多个主设备和多个从设备共享总线。
- 地址寻址:每个从设备有唯一地址(7位或10位)。
- 开漏输出:SDA和SCL均需外接上拉电阻,避免总线冲突。
- 半双工:同一时间只能发送或接收数据。
- 低速到中速传输:标准模式(100 kbps)、快速模式(400 kbps)、高速模式(3.4 Mbps)等。
-
物理层结构:
- 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位地址。
- 分两次发送:前5位固定为
3. 应答信号(ACK/NACK)
- 每传输完一个字节(8位)后,接收方需在第9个时钟周期发送应答信号:
- ACK:SDA拉低,表示成功接收。
- NACK:SDA保持高电平,表示接收失败或传输结束。
4. 数据帧(Data Frame)
- 数据按字节传输(8位),高位(MSB)在前。
- 每个数据帧后必须跟随ACK/NACK信号。
5. 停止条件(Stop Condition)
- SCL为高电平时,SDA从低电平跳变到高电平。
- 标志一次传输结束。
三、IIC通信流程示例
以主设备向从设备(地址0x50)写入数据0xAA
为例:
- 主设备发送起始信号。
- 发送地址帧:
0xA0
(0x50 << 1 | 0,写操作)。 - 从设备返回ACK。
- 主设备发送数据
0xAA
。 - 从设备返回ACK。
- 主设备发送停止信号。
四、关键机制
-
时钟同步:
- 多个主设备可通过SCL线的“线与”逻辑同步时钟,避免冲突。
-
仲裁机制:
- 当多个主设备同时发送数据时,通过SDA线电平检测冲突,优先级高的主设备继续通信。
-
重复起始条件(Repeated Start):
- 在一次通信中不发送停止信号,直接发送新的起始信号,切换读写模式或切换从设备。
五、传输模式
模式 | 速率 | 应用场景 |
---|---|---|
标准模式(Standard) | 100 kbps | 通用低速设备(如EEPROM) |
快速模式(Fast) | 400 kbps | 传感器、实时时钟 |
高速模式(High-Speed) | 3.4 Mbps | 高速数据传输 |
六、典型应用场景
- 存储器:读写EEPROM(如AT24C02)。
- 传感器:获取温度、湿度数据(如BMP280、MPU6050)。
- 显示屏:控制OLED/LCD模块。
- 系统管理:SMBus(基于IIC的系统管理总线)。
七、常见问题与解决
- 上拉电阻取值:根据总线电容和速率选择,通常3.3kΩ~10kΩ。
- 地址冲突:确保从设备地址唯一,或使用10位地址模式。
- 总线电容过大:导致信号边沿变缓,需降低上拉电阻值或使用缓冲器。
- 多主竞争:依赖仲裁机制,设计时避免频繁多主操作。
八、调试技巧
- 用示波器或逻辑分析仪抓取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