IIC (Inter‑Integrated Circuit) 集成电路总线
文章目录
- IIC (Inter‑Integrated Circuit) 集成电路总线
-
- 一、IIC 总线概述
- 二、物理层
- 三、设备寻址:7+1 字节格式
- 四、设备角色与功能
-
- 主设备
- 从设备
- 五、通信速率
- 六、协议层
-
- 6.1 起始信号 & 停止信号
- 6.2 数据位传输规则
- 6.3 ACK 应答信号
- 6.4 三种工作模式
- 6.5 多主机与总线仲裁
- 七、IIC 核心小结
- 八、Verilog 实现 IIC 协议
摘要:IIC(Inter‑Integrated Circuit)是一种两线式双向串行总线,仅需 SDA(数据线)与 SCL(时钟线)两根信号线,配合外部上拉电阻即可实现主从设备间的板内短距离通信。本文从物理层、设备寻址(7 位地址 + 1 位读写位)、设备角色、通信速率与协议层(起始/停止信号、数据位传输、ACK 应答、多主机仲裁)逐层讲解 IIC 核心机制,并给出基于三段式状态机的 Verilog 完整实现与仿真代码,帮助读者从原理到代码全面掌握 IIC 协议。
一、IIC 总线概述
IIC(Inter‑Integrated Circuit,集成电路总线),主要用于连接微控制器与各类外围器件,实现芯片与芯片之间的通信。 可以在 CPU 与被控 IC、IC 与 IC 之间完成双向数据传输,高速 IIC 总线速率可达 400kbps 以上。
适用场景:主控制器与从器件的主从通信,适合小数据量、短距离板内通信; 约束特性:任意时刻总线只允许存在一个有效主机;从机与从机之间不能直接交换数据,必须经过主机转发。
二、物理层
IIC 是两线式双向串行总线,只需要两根信号线:
总线硬件要求:SDA、SCL 两根信号线必须外接上拉电阻,阻值根据通信速率、总线容性负载选择,常用取值3.3kΩ ~ 10kΩ。总线空闲时两根线被上拉电阻拉为高电平;器件内部为开漏输出结构,依靠高阻态实现线与逻辑。
总线上挂载的每一个 IIC 从设备,都拥有唯一设备地址,主机依靠地址区分不同从机,保证访问准确。
三、设备寻址:7+1 字节格式
IIC 读写寻址字节一共 8bit:7bit从机设备地址 + 1bit读写方向位
- bit7~bit1:7 位从机设备地址,用来寻找总线上目标从机
- bit0:读写方向位
- 0:主机向从机写数据
- 1:主机从从机读数据
注意:IIC 器件内部 IO 为开漏结构,总线空闲时 SDA、SCL 处于高阻态,依靠外部上拉电阻拉高电平。
四、设备角色与功能
主设备
从设备
IIC 支持多主机模式(multimastering),一条总线可以挂多个主机; 重点:同一时间只允许一个主机掌握总线控制权。 多主机场景依靠总线仲裁机制,避免多个主机同时发送信号造成冲突。
五、通信速率
- 标准模式:最高 100kHz
- 快速模式:最高 400kHz
- 超高速模式:最高3.4MHz
通信属性:同步、串行、半双工
- 同步:通信时序完全跟随 SCL 时钟;
- 串行:数据按 bit 逐位传输;
- 半双工:同一时刻只能单向传输,不能同时收发。
六、协议层
6.1 起始信号 & 停止信号
- 起始信号 (S):SCL 为高电平时,SDA 由高电平跳变到低电平,表示通信开始。
- 口诀:sda先拉低,scl后拉低。

- 停止信号 §:SCL 为高电平时,SDA 由低电平跳变到高电平,表示通信结束,总线回到空闲状态。
- 口诀:sda后拉高,scl先拉高。

只有 SCL 处于高电平期间,SDA 的跳变才代表起始 / 停止;SCL 低电平时,允许 SDA 改变电平,用来传输数据 bit。
6.2 数据位传输规则
SCL 高电平阶段:SDA 电平必须保持稳定,接收端采样 SDA 得到数据; SCL 低电平阶段:允许 SDA 电平翻转,准备下一位数据。 每传输 1bit 数据,对应一个 SCL 时钟周期。 口诀:低电平切换数据,高电平保持。
6.3 ACK 应答信号
每发送完 8bit(1 字节)数据之后,接收方需要拉低 SDA 产生应答 ACK;
- ACK(应答):接收方将 SDA 拉低,告诉发送方接收正常;
- NACK(非应答):SDA 保持高电平,代表无应答,通常表示从机未收到、结束读操作。

6.4 三种工作模式
- 写模式:开始信号 →主机发从机地址 + 写位 → 从机 ACK → 主机发送寄存器地址/或者直接从头开始写数据 → 从机 ACK → 主机发送数据 → 从机 ACK… → 停止信号

- 读模式:开始信号 → 主机发从机地址 + 读位 → 从机ACK → 主机读取数据(变现为不工作,高阻态,通过上拉电阻输入数据)→ 主机ACK → 读取下一个数据 →主机ACK…→主机NACK→停止信号

- 先写后读模式:开始信号 → 主机发从机地址 + 写位 → 指定寄存器或先从头写入数据→…→从机ACK → 重新发起起始→发送从机地址 + 读位 → 从机 ACK → 从机输出数据 → 主机ACK…→ 主机 NACK → 发出停止信号结束读取。

6.5 多主机与总线仲裁
多个主机同时抢占总线时,仲裁逻辑: 主机一边发送数据,一边实时监听 SDA 总线电平; 如果自己输出高电平,但总线被别的主机拉为低电平,代表仲裁失败,该主机立刻停止发送,让出总线,等待总线空闲后重新发起通信。
七、IIC 核心小结
八、Verilog 实现 IIC 协议
说明:为本篇初稿实现,支持 IIC 写、读、组合读写(重复起始),内部采用三段式状态机,使用三态门处理 SDA/SCL 双向端口。
module top (
input wire clk ,
input wire rst_n ,
input wire start_iic ,//协议开始信号
input wire worr ,//方向信号
input wire [7:0] sendnum ,//器件规定主机的写入数据量
input wire [7:0] recvnum ,//规定主机的读出数据量
input wire [7:0] data_i_iic ,//外部给到的数据
output wire [7:0] data_o_iic ,//从机里面读到、输出的数据
output wire done_w ,//完成一次写数据的握手信号
output wire done_r ,//完成一次读数据的握手信号
output wire done_iic ,//完成一次iic通信的握手信号
output wire wrong ,//报警信号
inout wire scl ,//时钟总线
inout wire sda //数据总线
);
//参数
parameter sysclk = 50_000_000 ,//系统时钟
iiclk = 100_000 ,//iic速率
delay = sysclk/iiclk ,//发一比特所用周期数10us
MID = delay/2 ,//5us
Q_MID = delay/4 ,//2.5us
TQ_MID = MID + Q_MID ,//12.5us
ID_W = 8'b1010_0000 ,//设备ID+方向
ID_R = 8'b1010_0001 ;//设备ID+方向
//状态定义
localparam IDLE = 'd1 ,
START1 = 'd2 ,
IDW = 'd3 ,
ACK1 = 'd4 ,
WDATA = 'd5 ,
ACK2 = 'd6 ,
STOP = 'd7 ,
IDR = 'd8 ,
ACK3 = 'd9 ,
RDATA = 'd10 ,
ACK4 = 'd11 ,
START2 = 'd12 ;
reg [3:0] state_c,state_n;
//状态转移
wire IDLE_START1 ,
START1_IDW ,
IDW_ACK1 ,
ACK1_WDATA ,
WDATA_ACK2 ,
ACK2_WDATA ,
ACK2_STOP ,//第一种工作模式:IDLE -> START1 -> IDW -> ACK1 -> WDATA <–> ACK2 ->STOP ->ILDE
START1_IDR ,
IDR_ACK3 ,
ACK3_RDATA ,
RDATA_ACK4 ,
ACK4_RDATA ,
ACK4_STOP ,//第二种工作模式:IDLE -> START1 -> IDR -> ACK3 -> RDATA <–> ACK4 ->STOP ->ILDE
ACK2_START2 ,
START2_IDR ,//第三种工作方式:IDLE -> START1 -> IDW -> ACK1 -> WDATA <–> ACK2 -> IDR -> ACK3 -> RDATA <–> ACK4 ->STOP ->ILDE
ACK1_STOP ,//从机错误应答情况
ACK3_STOP ,//从机错误应答情况
STOP_IDLE ;
//内部信号
//计数器
reg [9:0] cnt_time ;//时间计数
reg [3:0] cnt_bit ;//比特计数
//寄存信号
reg [1:0] start_reg ;//协议开始,两个位宽是为了后续根据需求选择长短信号
reg worr_reg ;//工作模式1/2选择
reg [7:0] sendnum_reg ;
reg [7:0] recvnum_reg ;
reg [7:0] data_reg ;
//数据量计数
reg [7:0] sendcnt ;//发送计数
reg [7:0] recvcnt ;//接收计数
//数据暂存和应答信号
reg [7:0] data_temp ;
reg ack_flag ;//应答信号
//三态门信号
reg scl_en ;
reg scl_out ;
wire scl_in ;
reg sda_en ;
reg sda_out ;
wire sda_in ;
//三态门
assign scl = scl_en ? scl_out : 1'bz;
assign scl_in = scl;
assign sda = sda_en ? sda_out : 1'bz;
assign sda_in = sda;
//输入数据寄存
always @(posedge clk or negedge rst_n) begin
if (rst_n == 0) begin
start_reg <= 2'b00;
end
else begin
start_reg <= {start_reg[0],start_iic};
end
end
//状态机第一段
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
if (rst_n == 0) begin
state_n = IDLE;
end
else begin
case (state_c)
IDLE : state_n = (IDLE_START1) ? START1: IDLE ;
START1 : state_n = (START1_IDW ) ? IDW : (START1_IDR) ? IDR : START1;
IDW : state_n = (IDW_ACK1 ) ? ACK1 : IDW ;
ACK1 : state_n = (ACK1_STOP ) ? STOP : (ACK1_WDATA) ? WDATA : ACK1;
WDATA : state_n = (WDATA_ACK2 ) ? ACK2 : WDATA ;
ACK2 : state_n = (ACK2_STOP ) ? STOP : (ACK2_START2)?START2 :(ACK2_WDATA ) ? WDATA : ACK2;
STOP : state_n = (STOP_IDLE ) ? IDLE : STOP ;
IDR : state_n = (IDR_ACK3 ) ? ACK3 : IDR ;
ACK3 : state_n = (ACK3_STOP ) ? STOP : (ACK3_RDATA ) ? RDATA : ACK3;
RDATA : state_n = (RDATA_ACK4 ) ? ACK4 : RDATA ;
ACK4 : state_n = (ACK4_STOP ) ? STOP : (ACK4_RDATA ) ? RDATA : ACK4;
START2 : state_n = (START2_IDR ) ? IDR : START2 ;
default: state_n = IDLE;
endcase
end
end
assign IDLE_START1 = (state_c == IDLE ) && start_reg == 2'b01;
assign START1_IDW = (state_c == START1) && cnt_time == delay + Q_MID – 1 && worr_reg == 0;
assign START1_IDR = (state_c == START1) && cnt_time == delay + Q_MID – 1 && worr_reg == 1;
assign IDW_ACK1 = (state_c == IDW ) && cnt_time == delay – 1 && cnt_bit == 7;
assign ACK1_STOP = (state_c == ACK1 ) && cnt_time == delay – 1 && ack_flag == 1;
assign ACK1_WDATA = (state_c == ACK1 ) && cnt_time == delay – 1 && ack_flag == 0;
assign WDATA_ACK2 = (state_c == WDATA ) && cnt_time == delay – 1 && cnt_bit == 7;
assign ACK2_STOP = (state_c == ACK2 ) && cnt_time == delay – 1 && (sendcnt == sendnum && recvnum == 0 && ack_flag == 0)||ack_flag == 1;
assign ACK2_WDATA = (state_c == ACK2 ) && cnt_time == delay – 1 && sendcnt < sendnum && ack_flag == 0;
assign IDR_ACK3 = (state_c == IDR ) && cnt_time == delay – 1 && cnt_bit == 7;
assign ACK3_STOP = (state_c == ACK3 ) && cnt_time == delay – 1 && ack_flag == 1;
assign ACK3_RDATA = (state_c == ACK3 ) && cnt_time == delay – 1 && ack_flag == 0;
assign RDATA_ACK4 = (state_c == RDATA ) && cnt_time == delay – 1 && cnt_bit == 7;
assign ACK4_STOP = (state_c == ACK4 ) && cnt_time == delay – 1 && recvcnt == recvnum;
assign ACK2_START2 = (state_c == ACK2 ) && cnt_time == delay – 1 && sendcnt == sendnum && recvnum != 0;
assign ACK4_RDATA = (state_c == ACK4 ) && cnt_time == delay – 1 && recvcnt < recvnum;
assign START2_IDR = (state_c == START2) && cnt_time == delay + Q_MID – 1 ;
assign STOP_IDLE = (state_c == STOP ) && cnt_time == delay + Q_MID – 1 ;
//状态机第三段
always @(posedge clk or negedge rst_n) begin
if (rst_n == 0) begin
//计数
cnt_time <= 0;
cnt_bit <= 0;
//寄存
worr_reg <= 0;
sendnum_reg <= 0;
recvnum_reg <= 0;
data_reg <= 0;
//数据量
sendcnt <= 0;
recvcnt <= 0;
data_temp <= 0;
ack_flag <= 0;
//三态门
scl_en <= 0;
sda_en <= 0;
scl_out <= 0;
sda_out <= 0;
end
else begin
case (state_c)
IDLE :begin//寄存信号,主机不工作
//计数
cnt_time <= 0;
cnt_bit <= 0;
//寄存
worr_reg <= worr;
sendnum_reg <= sendnum;
recvnum_reg <= recvnum;
data_reg <= data_i_iic;
//数据量计数
sendcnt <= 0;
recvcnt <= 0;
data_temp <= 0;
ack_flag <= 0;
//三态门
scl_en <= 0;
sda_en <= 0;
scl_out <= 1;//象征为高阻,但实际上不会被输出
sda_out <= 1;//象征为高阻,但实际上不会被输出
end
START1,START2:begin//开始时序:主机工作,sda先拉低,scl后拉低
//计数
if (cnt_time == delay + Q_MID – 1) begin//计数12.5us
cnt_time <= 0;
end
else begin
cnt_time <= cnt_time + 1;
end
cnt_bit <= 0;
//三态门
scl_en <= 1;
sda_en <= 1;
if (cnt_time >= delay) begin
scl_out <= 0;
end
else begin
scl_out <= 1;
end
if (cnt_time >= MID) begin
sda_out <= 0;
end
else begin
sda_out <= 1;
end
end
IDW :begin//主机工作:发送从机地址
//计数
if (cnt_time == delay – 1) begin//周期计数
cnt_time <= 0;
end
else begin
cnt_time <= cnt_time + 1;
end
if (cnt_time == delay – 1) begin
if (cnt_bit == 7) begin
cnt_bit <= 0;
end
else begin
cnt_bit <= cnt_bit + 1;
end
end
//三态门
scl_en <= 1;
sda_en <= 1;
if (cnt_time >=Q_MID && cnt_time <= TQ_MID-1) begin//两边高中间低
scl_out <= 1;
end
else begin
scl_out <= 0;
end
if (cnt_time == 1) begin
sda_out <= ID_W[7 – cnt_bit];//高位优先
end
end
ACK1,ACK3:begin//采集应答信号、维持时钟输出
//计数
if (cnt_time == delay – 1) begin//周期计数
cnt_time <= 0;
end
else begin
cnt_time <= cnt_time + 1;
end
cnt_bit <= 0;
//寄存
if (cnt_time == MID) begin//采样点
ack_flag <= sda_in;
end
//三态门
scl_en <= 1;
sda_en <= 0;
if (cnt_time >=Q_MID && cnt_time <= TQ_MID-1) begin//两边高中间低
scl_out <= 1;
end
else begin
scl_out <= 0;
end
sda_out <= 1;
end
WDATA :begin//主机工作:写数据、数据量计数保持
//计数
if (cnt_time == delay – 1) begin//周期计数
cnt_time <= 0;
end
else begin
cnt_time <= cnt_time + 1;
end
if (cnt_time == delay – 1) begin
if (cnt_bit == 7) begin
cnt_bit <= 0;
end
else begin
cnt_bit <= cnt_bit + 1;
end
end
//三态门
scl_en <= 1;
sda_en <= 1;
if (cnt_time >=Q_MID && cnt_time <= TQ_MID-1) begin//两边高中间低
scl_out <= 1;
end
else begin
scl_out <= 0;
end
if (cnt_time == 0) begin
sda_out <= data_reg[7 – cnt_bit];//高位优先
end
end
ACK2 :begin//采集应答信号、维持时钟输出、对数据量进行计数
//计数
if (cnt_time == delay – 1) begin//周期计数
cnt_time <= 0;
end
else begin
cnt_time <= cnt_time + 1;
end
cnt_bit <= 0;
//寄存
if (cnt_time == 0) begin//完成一次数据写入,计数+1
sendcnt <= sendcnt + 1;
end
//更新数据
data_reg <= data_i_iic;
if (cnt_time == MID) begin//采样点
ack_flag <= sda_in;
end
//三态门
scl_en <= 1;
sda_en <= 0;
if (cnt_time >=Q_MID && cnt_time <= TQ_MID-1) begin//两边高中间低
scl_out <= 1;
end
else begin
scl_out <= 0;
end
sda_out <= 1;
end
STOP :begin//停止时序:主机工作,sda后拉高,scl先拉高
//计数
if (cnt_time == delay + Q_MID – 1) begin//计数12.5us
cnt_time <= 0;
end
else begin
cnt_time <= cnt_time + 1;
end
cnt_bit <= 0;
//三态门
scl_en <= 1;
sda_en <= 1;
if (cnt_time >= Q_MID) begin
scl_out <= 1;
end
else begin
scl_out <= 0;
end
if (cnt_time >= TQ_MID) begin
sda_out <= 1;
end
else begin
sda_out <= 0;
end
end
IDR :begin//主机工作:发送从机地址
//计数
if (cnt_time == delay – 1) begin//周期计数
cnt_time <= 0;
end
else begin
cnt_time <= cnt_time + 1;
end
if (cnt_time == delay – 1) begin
if (cnt_bit == 7) begin
cnt_bit <= 0;
end
else begin
cnt_bit <= cnt_bit + 1;
end
end
//三态门
scl_en <= 1;
sda_en <= 1;
if (cnt_time >=Q_MID && cnt_time <= TQ_MID-1) begin//两边高中间低
scl_out <= 1;
end
else begin
scl_out <= 0;
end
if (cnt_time == 1) begin
sda_out <= ID_R[7 – cnt_bit];//高位优先
end
end
RDATA :begin//从机工作,主机读数据、数据量计数保持
//计数
if (cnt_time == delay – 1) begin//周期计数
cnt_time <= 0;
end
else begin
cnt_time <= cnt_time + 1;
end
if (cnt_time == delay – 1) begin
if (cnt_bit == 7) begin
cnt_bit <= 0;
end
else begin
cnt_bit <= cnt_bit + 1;
end
end
if (cnt_time == MID) begin
data_temp[7-cnt_bit] <= sda_in;
end
//三态门
scl_en <= 1;
sda_en <= 0;
if (cnt_time >=Q_MID && cnt_time <= TQ_MID-1) begin//两边高中间低
scl_out <= 1;
end
else begin
scl_out <= 0;
end
sda_out <= 1;
end
ACK4 :begin//发出应答信号
//计数
if (cnt_time == delay – 1) begin//周期计数
cnt_time <= 0;
end
else begin
cnt_time <= cnt_time + 1;
end
cnt_bit <= 0;
//寄存
if (cnt_time == 0) begin//完成一次数据写入,计数+1
recvcnt <= recvcnt + 1;
end
//三态门
scl_en <= 1;
sda_en <= 1;
if (cnt_time >=Q_MID && cnt_time <= TQ_MID-1) begin//两边高中间低
scl_out <= 1;
end
else begin
scl_out <= 0;
end
if (recvcnt == recvnum) begin
sda_out <= 1;
end
else begin
sda_out <= 0;
end
end
default: begin
//计数
cnt_time <= 0;
cnt_bit <= 0;
//寄存
worr_reg <= 0;
sendnum_reg <= 0;
recvnum_reg <= 0;
data_reg <= 0;
//数据量
sendcnt <= 0;
recvcnt <= 0;
data_temp <= 0;
ack_flag <= 0;
//三态门
scl_en <= 0;
sda_en <= 0;
scl_out <= 0;
sda_out <= 0;
end
endcase
end
end
//输出信号
assign data_o_iic = (state_c == ACK4) ? data_temp : 0;
assign done_r = (state_c == ACK4 && cnt_time == MID) ? 1 : 0;
assign done_w = (state_c == ACK2 && cnt_time == MID) ? 1 : 0;
assign done_iic = (state_c == STOP && cnt_time == MID) ? 1 : 0;
assign wrong = (state_c == STOP && cnt_time == MID && (sendcnt!=sendnum || recvcnt!=recvnum ));
endmodule
- 仿真代码和波形
module tb ();
reg clk ;
reg rst_n ;
reg start_iic ;
reg [7:0] data_i_iic ;
wire [7:0] data_o_iic ;
wire done_w ;
wire done_r ;
wire done_iic ;
wire wrong ;
tri0 scl ;
tri0 sda ;
initial begin
clk <= 0;
rst_n <= 0;
start_iic <= 0;
#20
rst_n <= 1;
#100
start_iic <= 1;
#100
start_iic <= 0;
end
always #10 clk = ~clk;
always @(posedge clk or negedge rst_n) begin
if (rst_n == 0) begin
data_i_iic <= 8'h22;
end
else if(done_w)begin
data_i_iic <=data_i_iic + 1;
end
end
top top_inst(
.clk (clk ),
.rst_n (rst_n ),
.start_iic (start_iic ),
.worr (0 ),
.sendnum (4 ),
.recvnum (3 ),
.data_i_iic (data_i_iic),
.data_o_iic (data_o_iic),
.done_w (done_w ),
.done_r (done_r ),
.done_iic (done_iic ),
.wrong (wrong ),
.scl (scl ),
.sda (sda )
);
endmodule

网硕互联帮助中心


评论前必须登录!
注册