IIC的介绍
IIC 协议(Inter-Integrated Circuit ,可简写为 I2C ),是一种用于各种电子设备之间进行通信和数据交换的同步串行通信协议 。它是由飞利浦(Philips)公司开发的两线式同步串行总线,只需要两根线就可在连接于总线上的器件之间传送信息。 IIC 多用于一主多从通信,通信速率较慢,传输距离短,任意时刻只能有一个主机。IIC是三大低速总线之一,通信速率:UART < IIC < SPI。
IIC的应用场景:
1.数据采集领域的串行 AD,IIC负责将模拟量转化为数字量后的结果"取回来"。
2.图像处理领域的摄像头配置
3.存储类芯片EEPROM配置
4.将传感器的数字量(TMP461温度传感器),通过IIC传输
IIC设备连接方式

当总线为高电平时,主从机的输入端均为高电平;当总线为低电平时,主从机的输入端均为低电平。
多设备的OC/OD门的器件,输出可以接到一起,均接到总线上,形成一个线与逻辑,同时这个总线只能输出低电平,所以我们需要在总线上接上拉电阻(2K-4.7K)接到电源1.7-3.6V,既可完成通信。
IIC通信总线接上拉电阻的原因
因为I2C总线的输出端采用的是"开漏(Open-Drain)/OC门"结构,它本身没有能力输出高电平,必须依靠上拉电阻将电平拉高。
OC/OD门
OC门(集电极开路)和OD门(漏极开路) 本质上是"同一种东西",只是分别用在双极性晶体管(BJT)和场效应管(MOSFET)两种不同的半导体上。 它们共同的核心特征是:只能把电平拉低,不能主动拉高,所以输出高电平必须依赖外部的上拉电阻。电阻值一般为2.2K~4.7K,然后接到电源1.7V~3.6V。
OC门(集电极开路)内部结构 :内部是一个NPN型三极管(BJT)。三极管的发射极(E) 接地,**集电极(C)**作为输出引脚直接引出,内部不接任何电源。所以叫"集电极开路"。
OD门(漏极开路)内部结构 :内部是一个N沟道场效应管(MOSFET)。MOS管的源极(S) 接地,**漏极(D)**作为输出引脚直接引出,内部不接任何电源。所以叫"漏极开路"。
OC/OD工作原理 :输入高电平(逻辑1)时:管子导通 ,输出引脚直接对地短路,输出低电平(0V) 。输入低电平(逻辑0)时:管子截止 ,输出引脚内部断开了与地的连接,处于高阻态(悬空) 。此时必须靠外部接的上拉电阻,才能把引脚拉到高电平(VCC)。在输入端接一个反相器就是逻辑1时,总线被上拉,逻辑0时总线输出0。
开漏(OD门)+ 上拉电阻实现的"线与"功能
OC/OD门能接成线与结构以及实现输出与输入之间的电平变换。

线与结构的好处:一个主机发送数据,多个从机可以同时接收到一样的数据。
IIC通信引脚
IIC通信只有两根通信线,分别为
SCL:串行时钟线,用于数据收发同步。
SDA:双向串行数据线,用于传输数据。
特点:1.支持一主多从通信和多主多从通信,通信总线可以挂载多个设备。我们一般使用一主多从通信。多主多从通信需要配合总线仲裁(Arbitration) 和时钟同步(Clock Synchronization) 机制。仲裁机制:如果两个主机同时试图发送数据,谁先发送"0"(低电平)(起始位)谁就赢,因为"0"会把"1"覆盖掉(线与功能)。输掉的主机会立刻检测到冲突并放弃总线,不会导致数据错乱或硬件烧毁。
2.每个连接到总线的设备都有一个独立的地址(查看芯片数据手册),主机可以利用这个地址进行不同设备之间的访问。
3.当 I2C 设备空闲时,会输出高阻态,而当所有设备都空闲,都输出高阻态时,由上拉电阻把总线拉成高电平。
4.IIC的通信速率由SCK决定,SCK频率要在400Khz以内,一般是200Khz或250Khz。
IIC通信时序
IIC写时序


IIC写时序分为:空闲状态,起始位,设备地址,读写位,应答位,寄存器地址,应答位,数据位,应答位,停止位。
PC:IIC通信是每发送1BYTE时,从机发一个ACK应答,然后再发1BYTE,从机ACK应答。且高位先发。
空闲状态:默认为高电平
起始位 :在SCL为高电平时,产生一个下降沿,代表起始位
7bit设备地址:在SCL为低电平时,更新bit位;在SCL为高电平时,保持不变。高位先发。
1bit读写位 :W操作为低电平,R操作为高电平。与7bit设备地址构成1BYTE。
ACK1:从机应答位,默认低电平。如若检测到为高,则没有该设备地址的从机。
寄存器地址:一般为8bit,高位先发。在SCL为低电平时,更新bit位;在SCL为高电平时,保持不变。
ACK2:从机应答位,默认低电平。如若检测到为高,则该设备地址的从机,没有收到寄存器地址数据或该从机没有这个寄存器地址。
**数据位:**一般为8bit,高位先发。在SCL为低电平时,更新bit位;在SCL为高电平时,保持不变。
**ACK3:**从机应答位,默认低电平。如若检测到为高,则该设备地址的从机,没有收到数据。
停止位: 在SCL为高电平时,产生一个上降沿,代表停止位。
从机ACK应答:意味着FPGA/单片机发送完1BYTE数据,释放了SDA数据总线,输出高阻态/悬空,此时SDA数据总线被上拉电阻拉高保持高电平。从机如果收到主机发来1BYTE数据,那么会将SDA数据线拉低,表示从机应答,FPGA/单片机检测到SDA被拉低,则表示从机的状态正常。
IIC写多字节数据时序

等价于

IIC读时序


IIC读时序分为:空闲状态、起始位、器件地址、写位、应答位、寄存器地址、应答位、起始位、设备地址、读位、从机应答、从机发送1BYTE、非应答、停止位。
IIC读多字节时序

从机不应答主控的排查方法
排查从机:1.检查从机供电是否正常
2.检查从机的设备地址引脚是否与我们发送的设备地址一致
3.检查从机电路工作是否正常
排查主机:1.使用示波器或SignalTap II抓一下主机发送的IIC波形是否正确
基于FPGA的IIC写模块
绘制模块框图及波形图


模块代码编写
module IIC_wr(
input wire sys_clk ,//50Mhz
input wire sys_rst_n ,
input wire[6:0] device_addr ,
input wire[7:0] reg_addr ,
input wire[7:0] wr_data ,
input wire data_flag ,//数据更新标志
output reg done ,//发送完成标志
output reg SCL ,//250Khz
inout wire SDA //双向总线
);
//双向总线推荐使用组合逻辑wire型,reg型quartus不会报错,会多两个警告
//如果定义为reg型modelsim仿真会报错。
parameter CNT_MAX = 8'd100;
parameter BIT_CNT_MAX = 8'd8 ;
localparam IDEL = 8'd0;
localparam S0 = 8'd1;//BEGIN
localparam S1 = 8'd2;//D_ADDR
localparam S2 = 8'd3;//WR
localparam S3 = 8'd4;//ACK1
localparam S4 = 8'd5;//REG_ADDR
localparam S5 = 8'd6;//ACK2
localparam S6 = 8'd7;//DATA
localparam S7 = 8'd8;//ACK3
localparam S8 = 8'd9;//STOP
localparam S9 = 8'd10;//END
//寄存要发送的数据
reg [6:0] device_addr_reg;
reg [7:0] reg_addr_reg ;
reg [7:0] wr_data_reg ;
//////////////////////////
reg [7:0] state ;
reg [7:0] cnt ;
reg [7:0] bit_cnt ;
// 新增:SDA内部驱动寄存器+三态使能
reg sda_out;
reg sda_en;
//SDA
assign SDA = sda_en ? sda_out : 1'bz;
////////////////////////// 数据锁存 //////////////////////////
always@(posedge sys_clk or negedge sys_rst_n)
begin
if(!sys_rst_n)
begin
device_addr_reg <= 7'd0;
reg_addr_reg <= 8'd0;
wr_data_reg <= 8'd0;
end
else if(data_flag)
begin
device_addr_reg <= device_addr;
reg_addr_reg <= reg_addr ;
wr_data_reg <= wr_data ;
end
end
////////////////////////// 周期计数器cnt //////////////////////////
always@(posedge sys_clk or negedge sys_rst_n)
begin
if(!sys_rst_n)
cnt <= 8'd0;
else if(state == IDEL || state == S9 || cnt == CNT_MAX - 1'b1)
cnt <= 8'd0;
else
cnt <= cnt + 1'b1;
end
////////////////////////// bit位计数器bit_cnt //////////////////////////
always@(posedge sys_clk or negedge sys_rst_n)
begin
if(!sys_rst_n)
bit_cnt <= 8'd0;
else if(cnt == CNT_MAX/2 - 1'b1 && bit_cnt == BIT_CNT_MAX && !SCL || state == S7)
bit_cnt <= 8'd0;
else if(cnt == CNT_MAX/2 - 1'b1 && !SCL)
bit_cnt <= bit_cnt + 1'b1;
else
bit_cnt <= bit_cnt;
end
////////////////////////// SCL时钟生成 //////////////////////////
always@(posedge sys_clk or negedge sys_rst_n)
begin
if(!sys_rst_n)
SCL <= 1'b1;
else if(cnt == CNT_MAX - 1'b1 && state == S8)
SCL <= 1'b1;
else if(cnt == CNT_MAX - 1'b1)
SCL <= ~SCL;
else
SCL <= SCL;
end
/* //SDA
always@(posedge sys_clk or negedge sys_rst_n)
begin
if(!sys_rst_n)
SDA <= 1'b1;
else
case(state)
IDEL: SDA <= 1'b1;
S0 :
if(cnt == CNT_MAX/2 - 1'b1)
SDA <= 1'b0;
S1 : SDA <= device_addr_reg[8'd7 - bit_cnt];
S2 : SDA <= 1'b0;//W
S3 : SDA <= 1'bZ;//释放SDA
S4 : SDA <= reg_addr_reg[8'd8 - bit_cnt];
S5 : SDA <= 1'bZ;//释放SDA
S6 : SDA <= wr_data_reg[8'd8 - bit_cnt];
S7 : SDA <= 1'bZ;//释放SDA
S8 :
if(cnt == CNT_MAX/2 - 1'b1)
SDA <= 1'b1;
S9 : SDA <= 1'b1;
default:SDA <= 1'b1;
endcase
end */
////////////////////////// SDA内部输出逻辑 sda_out + sda_en //////////////////////////
always@(posedge sys_clk or negedge sys_rst_n)
begin
if(!sys_rst_n)
begin
sda_out <= 1'b1;
sda_en <= 1'b1;
end
else
begin
sda_out <= sda_out;
sda_en <= 1'b1; // 默认主机驱动SDA
case(state)
IDEL:
begin sda_out <= 1'b1; sda_en <= 1'b1; end
S0: // 起始信号:SCL高时拉低SDA
begin
sda_out <= (cnt == CNT_MAX/2 - 1'b1) ? 1'b0 : sda_out;
sda_en <= 1'b1;
end
S1: // 7位从地址
begin
sda_out <= device_addr_reg[8'd7 - bit_cnt];
sda_en <= 1'b1;
end
S2: // 写位0
begin sda_out <= 1'b0; sda_en <= 1'b1; end
S3: // 释放SDA,等待从机ACK
begin sda_out <= 1'b1; sda_en <= 1'b0; end
S4: // 寄存器地址8bit,修复索引 7-bit_cnt
begin
sda_out <= reg_addr_reg[8'd8 - bit_cnt];
sda_en <= 1'b1;
end
S5: // 释放SDA等ACK2
begin sda_out <= 1'b1; sda_en <= 1'b0; end
S6: // 写入数据,修复索引
begin
sda_out <= wr_data_reg[8'd8 - bit_cnt];
sda_en <= 1'b1;
end
S7: // 释放SDA等ACK3
begin sda_out <= 1'b0; sda_en <= 1'b0; end
S8: // 停止信号:SCL高时拉高SDA
begin
sda_out <= (cnt == CNT_MAX/2 - 1'b1) ? 1'b1 : sda_out;
sda_en <= 1'b1;
end
S9: // 空闲
begin sda_out <= 1'b1; sda_en <= 1'b1; end
default:
begin sda_out <= 1'b1; sda_en <= 1'b1; end
endcase
end
end
////////////////////////// done完成标志 //////////////////////////
always@(posedge sys_clk or negedge sys_rst_n)
begin
if(!sys_rst_n)
done <= 1'b0;
else if(state == S9)
done <= 1'b1;
else
done <= 1'b0;
end
//state
always@(posedge sys_clk or negedge sys_rst_n)
begin
if(!sys_rst_n)
begin
state <= IDEL;
end
else
case(state)
IDEL:
begin
if(data_flag)
begin
state <= S0;
end
else
begin
state <= IDEL;
end
end
S0 :
begin
if(cnt == CNT_MAX / 2 - 1'b1 && !SCL)
begin
state <= S1;
end
else
begin
state <= state;
end
end
S1 :
begin
if(cnt == CNT_MAX / 2 - 1'b1 && !SCL && bit_cnt == BIT_CNT_MAX-1'b1)
begin
state <= S2;
end
else
begin
state <= state;
end
end
S2 :
begin
if(cnt == CNT_MAX / 2 - 1'b1 && !SCL && bit_cnt == BIT_CNT_MAX)
begin
state <= S3;
end
else
begin
state <= state;
end
end
S3 :
begin
if(SCL && cnt == CNT_MAX/2 && SDA)//非应答
begin
state <= S0;
end
else if(cnt == CNT_MAX / 2 - 1'b1 && !SCL && bit_cnt == 0)
begin
state <= S4;
end
else
begin
state <= state;
end
end
S4 :
begin
if(cnt == CNT_MAX / 2 - 1'b1 && !SCL && bit_cnt == BIT_CNT_MAX)
begin
state <= S5;
end
else
begin
state <= state;
end
end
S5 :
begin
if(SCL && cnt == CNT_MAX/2 && SDA)//非应答
begin
state <= S0;
end
else if(cnt == CNT_MAX / 2 - 1'b1 && !SCL && bit_cnt == 0)
begin
state <= S6;
end
else
begin
state <= state;
end
end
S6 :
begin
if(cnt == CNT_MAX / 2 - 1'b1 && !SCL && bit_cnt == BIT_CNT_MAX)
begin
state <= S7;
end
else
begin
state <= state;
end
end
S7 :
begin
if(SCL && cnt == CNT_MAX/2 && SDA)//非应答
begin
state <= S0;
end
else if(cnt == CNT_MAX / 2 - 1'b1 && !SCL && bit_cnt == 0)
begin
state <= S8;
end
else
begin
state <= state;
end
end
S8 :
begin
if(cnt == CNT_MAX - 1'b1 && SCL && bit_cnt == 0)
begin
state <= S9;
end
else
begin
state <= state;
end
end
S9 :
begin
state <= IDEL;
end
default:
begin
state <= IDEL;
end
endcase
end
endmodule
仿真代码编写
`timescale 1ns/1ps
module IIC_wr_tb();
reg sys_clk ;
reg sys_rst_n ;
reg [6:0] device_addr ;
reg [7:0] reg_addr ;
reg [7:0] wr_data ;
reg data_flag ;
reg SDA_flag ;
wire done ;
wire SCL ;
wire SDA ;//双向总线,tb文件只能定义为wire型
initial
begin
sys_clk = 1'b0;
sys_rst_n = 1'b0;
device_addr = 7'b100_1100;
reg_addr = 8'b1010_0101;
wr_data = 8'b1110_0010;
data_flag = 1'b0;
#123
sys_rst_n = 1'b1;
#123456
data_flag = 1'b1;
#20
data_flag = 1'b0;
end
always #10 sys_clk = ~sys_clk;
always@(posedge sys_clk)
begin
case(IIC_wr_inst.state)
8'd4:SDA_flag <= 1'b0;
8'd6:SDA_flag <= 1'b0;
8'd8:SDA_flag <= 1'b0;
default:SDA_flag <= 1'b1;
endcase
end
assign SDA = (SDA_flag == 1'b0) ? 1'b1 : 1'bz;
IIC_wr IIC_wr_inst(
.sys_clk (sys_clk ),//50Mhz
.sys_rst_n (sys_rst_n ),
.device_addr(device_addr),
.reg_addr (reg_addr ),
.wr_data (wr_data ),
.data_flag (data_flag ),//数据更新标志
.done (done ),//发送完成标志
.SCL (SCL ),//250Khz
.SDA (SDA )
);
endmodule
仿真验证

仿真验证通过。
IIC读写模块的合体IIC_driver
绘制模块框图及波形图


编写模块代码
module IIC_driver(
input wire sys_clk ,//50Mhz
input wire sys_rst_n ,
input wire[6:0] device_addr ,
input wire[7:0] reg_addr ,
input wire[7:0] wr_data ,
input wire start ,//读/写数据开始标志
input wire[1:0] cmd ,
output reg SCL ,//250Khz
inout wire SDA ,//双向总线
output reg [7:0] rd_data ,
output reg done //发送/接收完成标志
);
//双向总线推荐使用组合逻辑wire型,reg型quartus不会报错,会多两个警告
//如果定义为reg型modelsim仿真会报错。
parameter CNT_MAX = 8'd100;
parameter BIT_CNT_MAX = 8'd8 ;
//cmd读写指令
localparam WR = 2'b01;
localparam RD = 2'b11;
//状态
localparam IDEL = 8'd0;
localparam S0 = 8'd1;//BEGIN
localparam S1 = 8'd2;//D_ADDR
localparam S2 = 8'd3;//WR
localparam S3 = 8'd4;//ACK1
localparam S4 = 8'd5;//REG_ADDR
localparam S5 = 8'd6;//ACK2
//写时序
localparam S6 = 8'd7;//DATA
localparam S7 = 8'd8;//ACK3
//读时序
localparam S8 = 8'd9;//BEGIN2
localparam S9 = 8'd10;//D_ADDR
localparam S10 = 8'd11;//RD
localparam S11 = 8'd12;//ACK3
localparam S12 = 8'd13;//RD_DATA
localparam S13 = 8'd14;//NACK
//停止位
localparam S14 = 8'd15;//STOP
localparam S15 = 8'd16;//END
//寄存要发送的数据
reg [6:0] device_addr_reg;
reg [7:0] reg_addr_reg ;
reg [7:0] wr_data_reg ;
//////////////////////////
reg [7:0] state ;
reg [7:0] cnt ;
reg [7:0] bit_cnt ;
// 新增:SDA内部驱动寄存器+三态使能
reg sda_out;
reg sda_en;
//SDA
assign SDA = sda_en ? sda_out : 1'bz;
////////////////////////// 数据锁存 //////////////////////////
always@(posedge sys_clk or negedge sys_rst_n)
begin
if(!sys_rst_n)
begin
device_addr_reg <= 7'd0;
reg_addr_reg <= 8'd0;
wr_data_reg <= 8'd0;
end
else if(start)
begin
device_addr_reg <= device_addr;
reg_addr_reg <= reg_addr ;
if(cmd == WR)
wr_data_reg <= wr_data ;
else
wr_data_reg <= 8'd0;
end
end
////////////////////////// 周期计数器cnt //////////////////////////
always@(posedge sys_clk or negedge sys_rst_n)
begin
if(!sys_rst_n)
cnt <= 8'd0;
else if(state == IDEL || state == S15 || cnt == CNT_MAX - 1'b1)
cnt <= 8'd0;
else
cnt <= cnt + 1'b1;
end
////////////////////////// bit位计数器bit_cnt //////////////////////////
always@(posedge sys_clk or negedge sys_rst_n)
begin
if(!sys_rst_n)
bit_cnt <= 8'd0;
else if(cnt == CNT_MAX/2 - 1'b1 && bit_cnt == BIT_CNT_MAX && !SCL || state == S7 || state == S13)
bit_cnt <= 8'd0;
else if(cnt == CNT_MAX/2 - 1'b1 && !SCL)
bit_cnt <= bit_cnt + 1'b1;
else
bit_cnt <= bit_cnt;
end
////////////////////////// SCL时钟生成 //////////////////////////
always@(posedge sys_clk or negedge sys_rst_n)
begin
if(!sys_rst_n)
SCL <= 1'b1;
else if(cnt == CNT_MAX - 1'b1 && state == S14)
SCL <= 1'b1;
else if(cnt == CNT_MAX - 1'b1)
SCL <= ~SCL;
else
SCL <= SCL;
end
////////////////////////// SDA内部输出逻辑 sda_out + sda_en //////////////////////////
always@(posedge sys_clk or negedge sys_rst_n)
begin
if(!sys_rst_n)
begin
sda_out <= 1'b1;
sda_en <= 1'b1;
end
else
begin
sda_out <= sda_out;
sda_en <= 1'b1; // 默认主机驱动SDA
case(state)
IDEL:
begin sda_out <= 1'b1; sda_en <= 1'b1; end
S0: // 起始信号:SCL高时拉低SDA
begin
sda_out <= (cnt == CNT_MAX/2 - 1'b1) ? 1'b0 : sda_out;
sda_en <= 1'b1;
end
S1: // 7位从地址
begin
sda_out <= device_addr_reg[8'd7 - bit_cnt];
sda_en <= 1'b1;
end
S2: // 写位0
begin sda_out <= 1'b0; sda_en <= 1'b1; end
S3: // 释放SDA,等待从机ACK
begin sda_out <= 1'b1; sda_en <= 1'b0; end
S4: // 寄存器地址8bit,修复索引 7-bit_cnt
begin
sda_out <= reg_addr_reg[8'd8 - bit_cnt];
sda_en <= 1'b1;
end
S5: // 释放SDA等ACK2
begin sda_out <= 1'b1; sda_en <= 1'b0; end
//写时序
S6: // 写入数据,修复索引
begin
sda_out <= wr_data_reg[8'd8 - bit_cnt];
sda_en <= 1'b1;
end
S7: // 释放SDA等ACK3
begin sda_out <= 1'b0; sda_en <= 1'b0; end
//读时序
S8://读操作第二个起始位
begin
sda_out <= (cnt == CNT_MAX/2 - 1'b1) ? 1'b0 : sda_out;
sda_en <= 1'b1;
end
S9://读操作第二次发7位从地址
begin
sda_out <= device_addr_reg[8'd7 - bit_cnt];
sda_en <= 1'b1;
end
S10://读位1
begin sda_out <= 1'b1; sda_en <= 1'b1; end
S11:// 释放SDA,等待从机ACK
begin sda_out <= 1'b1; sda_en <= 1'b0; end
S12://读数据,释放SDA,等待从机发完数据
begin sda_out <= 1'b1; sda_en <= 1'b0; end
S13://NACK,继续释放SDA
begin sda_out <= 1'b1; sda_en <= 1'b0; end
S14: // 停止信号:SCL高时拉高SDA
begin
sda_out <= (cnt == CNT_MAX/2 - 1'b1) ? 1'b1 : sda_out;
sda_en <= 1'b1;
end
S15: // 空闲
begin sda_out <= 1'b1; sda_en <= 1'b1; end
default:
begin sda_out <= 1'b1; sda_en <= 1'b1; end
endcase
end
end
//rd_data
always@(posedge sys_clk or negedge sys_rst_n)
begin
if(!sys_rst_n)
rd_data <= 8'd0;
else if(state == S12 && SCL && cnt == CNT_MAX/2 - 1'b1)
rd_data <= {rd_data[6:0],SDA};
else
rd_data <= rd_data;
end
////////////////////////// done完成标志 //////////////////////////
always@(posedge sys_clk or negedge sys_rst_n)
begin
if(!sys_rst_n)
done <= 1'b0;
else if(state == S15)
done <= 1'b1;
else
done <= 1'b0;
end
///////////////////////////state状态机////////////////////////
always@(posedge sys_clk or negedge sys_rst_n)
begin
if(!sys_rst_n)
begin
state <= IDEL;
end
else
case(state)
IDEL:
begin
if(start)
begin
state <= S0;
end
else
begin
state <= IDEL;
end
end
S0 :
begin
if(cnt == CNT_MAX / 2 - 1'b1 && !SCL)
begin
state <= S1;
end
else
begin
state <= state;
end
end
S1 :
begin
if(cnt == CNT_MAX / 2 - 1'b1 && !SCL && bit_cnt == BIT_CNT_MAX-1'b1)
begin
state <= S2;
end
else
begin
state <= state;
end
end
S2 :
begin
if(cnt == CNT_MAX / 2 - 1'b1 && !SCL && bit_cnt == BIT_CNT_MAX)
begin
state <= S3;
end
else
begin
state <= state;
end
end
S3 :
begin
if(SCL && cnt == CNT_MAX/2 && SDA)//非应答
begin
state <= S0;
end
else if(cnt == CNT_MAX / 2 - 1'b1 && !SCL && bit_cnt == 0)
begin
state <= S4;
end
else
begin
state <= state;
end
end
S4 :
begin
if(cnt == CNT_MAX / 2 - 1'b1 && !SCL && bit_cnt == BIT_CNT_MAX)
begin
state <= S5;
end
else
begin
state <= state;
end
end
S5 :
begin
if(SCL && cnt == CNT_MAX/2 && SDA)//非应答
begin
state <= S0;
end
else if(cmd == WR && cnt == CNT_MAX / 2 - 1'b1 && !SCL && bit_cnt == 0)
begin
state <= S6;
end
else if(cmd == RD && cnt == CNT_MAX / 2 - 1'b1 && !SCL && bit_cnt == 0)
begin
state <= S8;
end
else
begin
state <= state;
end
end
S6 :
begin
if(cnt == CNT_MAX / 2 - 1'b1 && !SCL && bit_cnt == BIT_CNT_MAX)
begin
state <= S7;
end
else
begin
state <= state;
end
end
S7 :
begin
if(SCL && cnt == CNT_MAX/2 && SDA)//非应答
begin
state <= S0;
end
else if(cnt == CNT_MAX / 2 - 1'b1 && !SCL && bit_cnt == 0)
begin
state <= S14;
end
else
begin
state <= state;
end
end
S8 :
begin
if(cnt == CNT_MAX / 2 - 1'b1 && !SCL)
begin
state <= S9;
end
else
begin
state <= state;
end
end
S9 :
begin
if(cnt == CNT_MAX / 2 - 1'b1 && !SCL && bit_cnt == BIT_CNT_MAX-1'b1)
begin
state <= S10;
end
else
begin
state <= state;
end
end
S10 :
begin
if(cnt == CNT_MAX / 2 - 1'b1 && !SCL && bit_cnt == BIT_CNT_MAX)
begin
state <= S11;
end
else
begin
state <= state;
end
end
S11 :
begin
if(SCL && cnt == CNT_MAX/2 && SDA)//非应答
begin
state <= S0;
end
else if(cnt == CNT_MAX / 2 - 1'b1 && !SCL && bit_cnt == 0)
begin
state <= S12;
end
else
begin
state <= state;
end
end
S12 :
begin
if(cnt == CNT_MAX / 2 - 1'b1 && !SCL && bit_cnt == BIT_CNT_MAX)
begin
state <= S13;
end
else
begin
state <= state;
end
end
S13 ://NACK
begin
if(SCL && cnt == CNT_MAX/2 && !SDA)//应答
begin
state <= S0;
end
else if(cnt == CNT_MAX / 2 - 1'b1 && !SCL && bit_cnt == 0)
begin
state <= S14;
end
else
begin
state <= state;
end
end
S14 :
begin
if(cnt == CNT_MAX - 1'b1 && SCL && bit_cnt == 0)
begin
state <= S15;
end
else
begin
state <= state;
end
end
S15 :
begin
state <= IDEL;
end
default:
begin
state <= IDEL;
end
endcase
end
endmodule
使用IIC_driver读取TMP461的温度
绘制模块框图及状态图



在读取TMP461时,要注意查看原理图和芯片手册,确定器件设备地址及寄存器地址。


编写模块代码
IIC_read_TMP461模块
module IIC_read_TMP461(
input wire clk ,
input wire rst_n ,
output wire[7:0] ch1_LT_high_byte,
output wire[7:0] ch1_LT_low_byte ,
output wire[7:0] ch2_LT_high_byte,
output wire[7:0] ch2_LT_low_byte ,
output wire SCL ,
inout wire SDA
);
wire done;
wire[7:0] rd_data;
wire[6:0] device_addr;
wire[7:0] reg_addr ;
wire start ;
wire[1:0] cmd ;
// wire[7:0] wr_data ;
fsm_ctrl fsm_ctrl_inst(
.clk (clk ),//250Khz
.rst_n (rst_n),
.done (done),//IIC发送完成标志
.rd_data (rd_data),//IIC读取的数据
.rd_data_valid (),//IIC读取的数据的有效标志
.start (start),//IIC读/写开始标志
.cmd (cmd),//cmd用于区分start是读还是写,WR:01,RD:11
.device_addr (device_addr),//设备地址
.reg_addr (reg_addr),//寄存器地址
.ch1_LT_high_byte(ch1_LT_high_byte),//第1个TMP461芯片本地温度的高字节
.ch1_LT_low_byte (ch1_LT_low_byte ),//第1个TMP461芯片本地温度的低字节
.ch2_LT_high_byte(ch2_LT_high_byte),//第2个TMP461芯片本地温度的高字节
.ch2_LT_low_byte (ch2_LT_low_byte ) //第2个TMP461芯片本地温度的低字节
);
IIC_driver IIC_driver_inst(
.sys_clk (clk ),//50Mhz
.sys_rst_n (rst_n),
.device_addr(device_addr),
.reg_addr (reg_addr ),
.wr_data (),
.start (start ),//读/写数据开始标志
.cmd (cmd ),
.SCL (SCL),//250Khz
.SDA (SDA),//双向总线
.rd_data (rd_data),
.done (done ) //发送/接收完成标志
);
endmodule
fsm_ctrl模块
module fsm_ctrl(
input wire clk ,//250Khz
input wire rst_n ,
input wire done ,//IIC发送完成标志
input wire[7:0] rd_data ,//IIC读取的数据
input wire rd_data_valid ,//IIC读取的数据的有效标志
output reg start ,//IIC读/写开始标志
output reg [1:0] cmd ,//cmd用于区分start是读还是写,WR:01,RD:11
output reg [6:0] device_addr ,//设备地址
output reg [7:0] reg_addr ,//寄存器地址
output reg [7:0] ch1_LT_high_byte,//第1个TMP461芯片本地温度的高字节
output reg [7:0] ch1_LT_low_byte ,//第1个TMP461芯片本地温度的低字节
output reg [7:0] ch2_LT_high_byte,//第2个TMP461芯片本地温度的高字节
output reg [7:0] ch2_LT_low_byte //第2个TMP461芯片本地温度的低字节
);
parameter TIME_1S = 250_000;
localparam WR = 2'b01;
localparam RD = 2'b11;
wire[6:0] device_addr_mem [1:0];//设备地址数组
assign device_addr_mem[0] = 7'h48;
assign device_addr_mem[1] = 7'h4E;
wire[7:0] reg_addr_mem [1:0];//寄存器地址数组
assign reg_addr_mem[0] = 8'h00;
assign reg_addr_mem[1] = 8'h15;
reg [31:0] delay_cnt ;
reg [7:0] state ;
localparam IDLE = 8'd0 ;
localparam S0 = 8'd1 ;
localparam S1 = 8'd2 ;
localparam S2 = 8'd3 ;
localparam S3 = 8'd4 ;
localparam S4 = 8'd5 ;
localparam S5 = 8'd6 ;
localparam S6 = 8'd7 ;
localparam S7 = 8'd8 ;
localparam S8 = 8'd9 ;
localparam S9 = 8'd10;
localparam S10 = 8'd11;
localparam S11 = 8'd12;
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
delay_cnt <= 32'd0 ;
state <= IDLE ;
start <= 1'b0 ;
cmd <= 2'b00 ;
device_addr <= 7'h00 ;
reg_addr <= 8'h00 ;
ch1_LT_high_byte <= 8'd0;
ch1_LT_low_byte <= 8'd0;
ch2_LT_high_byte <= 8'd0;
ch2_LT_low_byte <= 8'd0;
end
else
case(state)
IDLE:
if(delay_cnt == TIME_1S)
begin
state <= S0 ;
delay_cnt <= 32'd0 ;
end
else
begin
delay_cnt <= delay_cnt + 1'd1 ;
state <= IDLE ;
start <= 1'b0 ;
cmd <= 2'b00 ;
device_addr <= 7'h00 ;
reg_addr <= 8'h00 ;
end
S0 :
begin
state <= S1 ;
start <= 1'b1 ;//开始读的标志
cmd <= RD ;//写指令
device_addr <= device_addr_mem[0];
reg_addr <= reg_addr_mem[0];
end
S1 :
if(done)
begin
state <= S2 ;
end
else
begin
state <= state ;
start <= 1'b0 ;
cmd <= RD ;
device_addr <= device_addr_mem[0];
reg_addr <= reg_addr_mem[0];
end
S2 ://读取芯片1的本地温度的高字节
begin
state <= S3 ;
ch1_LT_high_byte <= rd_data;
end
S3 :
begin
state <= S4 ;
start <= 1'b1 ;//开始读的标志
cmd <= RD ;//写指令
device_addr <= device_addr_mem[0];
reg_addr <= reg_addr_mem[1];
end
S4 :
if(done)
begin
state <= S5 ;
end
else
begin
state <= state ;
start <= 1'b0 ;
cmd <= RD ;
device_addr <= device_addr_mem[0];
reg_addr <= reg_addr_mem[1];
end
S5 ://读取芯片1的本地温度的低字节
begin
state <= S6 ;
ch1_LT_low_byte <= rd_data;
end
S6 :
begin
state <= S7 ;
start <= 1'b1 ;//开始读的标志
cmd <= RD ;//写指令
device_addr <= device_addr_mem[1];
reg_addr <= reg_addr_mem[0];
end
S7 :
if(done)
begin
state <= S8 ;
end
else
begin
state <= state ;
start <= 1'b0 ;
cmd <= RD ;//写指令
device_addr <= device_addr_mem[1];
reg_addr <= reg_addr_mem[0];
end
S8 ://读取芯片2的本地温度的高字节
begin
state <= S9 ;
ch2_LT_high_byte <= rd_data;
end
S9 :
begin
state <= S10 ;
start <= 1'b1 ;//开始读的标志
cmd <= RD ;//写指令
device_addr <= device_addr_mem[1];
reg_addr <= reg_addr_mem[1];
end
S10 :
if(done)
begin
state <= S11 ;
end
else
begin
state <= state ;
start <= 1'b0 ;
cmd <= RD ;//写指令
device_addr <= device_addr_mem[1];
reg_addr <= reg_addr_mem[1];
end
S11 ://读取芯片2的本地温度的低字节
begin
state <= IDLE;
ch2_LT_low_byte <= rd_data;
end
default:
begin
delay_cnt <= 32'd0 ;
state <= IDLE ;
start <= 1'b0 ;
cmd <= 2'b00 ;
device_addr <= 7'h00 ;
reg_addr <= 8'h00 ;
ch1_LT_high_byte <= 8'd0;
ch1_LT_low_byte <= 8'd0;
ch2_LT_high_byte <= 8'd0;
ch2_LT_low_byte <= 8'd0;
end
endcase
end
endmodule