特别说明:以下所涉及代码参考网络、相关开发板厂家。只用于自己学习、理解和感悟。
I2C总线是由Philips公司开发的一种简单、双向二进制串行总线。只需要两根线即可在连接于总线上的器件之间信息传递。
I2C的标准速率为100kbit/s,快速模式时速率为400kbit/s,支持多机通讯,支持多主控模块,但同一时刻只允许有一个主控。由数据线SDA和时钟线SCL构成串行总线。每个电路和模块都有唯一的地址。
以下以AT24C04为例说明I2C的基本读写操作,I2C设备的操作可分为读写单个存储字节和多个存储字节。读写时序示意图如下所示:
如上图所示,I2C通信分为空闲状态、开始态、信息传输态、停止态。传输信息分为:设备地址、读写标志、应答信号、存储地址、数据。
空闲态:
SDA和SCL同时处于高电平(硬件电路中一般SDA和SCL线通过电阻上拉),进行总线释放。
开始态:
开始态时,SDA和SCL处于高电平状,当数据线SDA由高电平跳变到低电平后进入开始态。开始态代表一次数据传输的开始。
信息传输态:
I2C总线上,每一位数据都与一个时钟脉冲对应,即在每个数据位在相对应的时钟沿进行串行传送。在I2C总线上,时钟的高电平时,数据线上的数据保持稳定;时钟的低电平时,才允许电平改变状态。 数据信息传输过程中伴随着应答信号的发生。应答分位ACK和NACK。I2C总线上的数据都是以8位字节传送的。发送器每发送一个字节后,在第9个时钟脉冲期间释放数据线,同时等待接收器所反馈的应答信号。如果应答信号为低电平时,表示为有效应答(ACK);否则为无效应答(NACK)。无应答表示接收失败。如果主控制器在接收到最后一个字节后,发送一个NACK信号通知从设备结束数据发送,并释放SDA线,以便主设备发送一个停止信号。
停止态:
在时钟线保持高电平期间,数据线返回高电平即数据线被释放,称为I2C总线上的停止信号。接收到停止信号后I2C总线进入空闲状态。
读写时序如下所示:
1、写时序:
2、读时序:
在FPGA中,代码结构如下所示:
代码如下:
1、timescale.v代码
`timescale 1ns / 10ps
2、i2c_master_defines.v代码
`define I2C_CMD_NOP 4'b0000
`define I2C_CMD_START 4'b0001
`define I2C_CMD_STOP 4'b0010
`define I2C_CMD_WRITE 4'b0100
`define I2C_CMD_READ 4'b1000
3、i2c_master_bit_ctrl.v代码
`include "timescale.v"
`include "i2c_master_defines.v"
//位控制部分
//发送简单命令到SCL/SDA转换器
//每个命令有5个状态,A/B/C/D/idle。
module i2c_master_bit_ctrl(
input clk, //系统时钟
input rst, //同步使能高复位
input nReset, //异步使能低复位
input ena, //内核使能信号
input[15:0] clk_cnt, //时钟预分频值
input[3:0] cmd, //从控制器来的命令
output reg cmd_ack, //应答命令
output reg busy, //i2c总线忙
output reg a1, //i2c总线仲裁丢失
input din, //数据输入
output reg dout, //数据输出
input scl_i, //i2c时钟线输入
output scl_o, //i2c时钟线输出
output reg scl_oen, //i2c时钟线输出使能(低使能)
input sda_i, //i2c数据线输入
output sda_o, //i2c数据线输出
output reg sda_oen //i2c数据线输出使能(第使能)
);
reg[1:0] cSCL,cSDA; //采集SCL,SDA
reg[2:0] fSCL,fSDA; //SCL,SDA滤波器输入
reg sSCL,sSDA; //滤波和同步后的SCL,SDA输入
reg dSCL,dSDA; //延时变化后的dSCL,dSDA
reg dscl_oen; //延时变化后的时钟使能信号
reg sda_chk; //检查SDA输出(多主机仲裁)
reg clk_en; //时钟产生信号
reg slave_wait; //从设备就绪等待
reg[15:0] cnt; //时钟分频计数器(同步)
reg[13:0] filter_cnt; //滤波器时钟分频
//状态机变量
reg [17:0] c_state; //逻辑综合枚举状态
//在从设备没有准备好的时候,通过拉低SCL来达到延时的目的
//延时使能SCL
always @(posedge clk)
dscl_oen <= #1 scl_oen;
// slave_wait is asserted when master wants to drive SCL high,
//but the slave pulls it low
// slave_wait remains asserted until the slave releases SCL
always @(posedge clk or negedge nReset)
if(!nReset) slave_wait <= 1'b0;
else slave_wait <= (scl_oen & ~dscl_oen & ~sSCL)
| (slave_wait & ~sSCL);
//主设备SCL为高,但另一个主设备为低
//主设备开始倒计时(进行时钟同步)
wire scl_sync = dSCL & ~sSCL & scl_oen;
//产生时钟使能信号
always @(posedge clk or negedge nReset)
if(~nReset)
begin
cnt <= #1 16'h0;
clk_en <= 1'b1;
end
else if(rst || ~|cnt || !ena || scl_sync)
begin
cnt <= #1 clk_cnt;
clk_en <= #1 1'b1;
end
else if(slave_wait)
begin
cnt <= #1 cnt;
clk_en <= #1 1'b0;
end
else
begin
cnt <= #1 cnt - 16'h1;
clk_en <= #1 1'b0;
end
//信号采集
//降低亚稳态风险
always @(posedge clk or negedge nReset)
if(!nReset)
begin
cSCL <= #1 2'b00;
cSDA <= #1 2'b00;
end
else if(rst)
begin
cSCL <= #1 2'b00;
cSDA <= #1 2'b00;
end
else
begin
cSCL <= {cSCL[0],scl_i};
cSDA <= {cSDA[0],sda_i};
end
//滤波SCL和SDA信号,(尝试)排除故障
always @(posedge clk or negedge nReset)
if(!nReset) filter_cnt <= 14'h0;
else if(rst||!ena) filter_cnt <= 14'h0;
else if(~|filter_cnt) filter_cnt <= clk_cnt >> 2;
else filter_cnt <= filter_cnt - 1;
//滤波,filter_cnt频率是fSCLK频率的四倍
always @(posedge clk or negedge nReset)
if(!nReset)
begin
fSCL <= 3'b111;
fSDA <= 3'b111;
en