- IIC协议简介
通讯协议(Inter-Integrated Circuit)是由 Philips 公司开发的一种简单、双向二线制同步串行总线,只需要两根线即可在连接于总线上的器件之间传送信息。IIC 总线只使用两条总线:一条双向串行数据线(SDA),一条双向串行时钟线 (SCL)。数据线SDA用来表示数据,时钟线SCL用于同步数据收发。每个连接到总线的设备都有一个独立的地址,主机可以利用这个地址进行不同设备之间的访问。
总线通过上拉电阻接到电源。当 IIC 设备空闲时,会输出高阻态,而当所有设备都空闲,都输出高阻态时,由上拉电阻把总线拉成高电平。IIC总线是一个支持多设备的总线,所有设备都共享一根数据线SDA。所以需要每个设备都有一个唯一的地址,每个 I2C 设备都具备7位/10位器件地址。在7-bit地址格式中,在起始条件后,主机发送的第1个字节,该字节高7位为从机地址,最低位为R/W位-“0”表示发送(写),主机向从机写数据,“1”表示请求数据(读)主机读取从机数据。
主机在与从机建立通讯时,主机会将控制命令直接发送到串行数据线 SDA 上,与主机硬件相连的从机设备都会接收到主机发送的控制命令。所有从机设备在接收到主机发送的控制命令后会与自身器件地址做对比;若两者地址相同,该从机设备会回应一个应答信号告知主机设备,主机设备接收到应答信号后,主从设备建立通讯连接,两者即可开始进行数据通讯。
常见的IIC协议的传输速率有100kbps,400kbps,3.4Mbps。
- IIC协议时序图
IIC协议的写时序如下图所示。
图片来源:孤独的单刀
起始信号到达后,SDA数据线拉低,表示开始通信,然后发送7位器件地址的最高位+0(0表示写,1表示读),发送完成后释放总线,等待从机的应答信号,应答成功后开始依次发送8位寄存器地址的最高位与最低位,发送完成后再次等待从机应答,然后发送待写入的8位数据,等待应答,结束应答信后。将总线拉高,表示一次写入完成。
IIC协议的读时序如下图所示。
图片来源:孤独的单刀
读时序与写时序的不同在于发送完寄存器地址后需要再次发送7位器件地址+1(表示读操作),然后释放总线,等待从机发送数据。
- 设计思路
- 根据系统时钟频率与iic_clk驱动时钟频率计算出分频系数,以产生iic驱动时钟,通信频率就是SCL的频率,根据时序图可以看出,iic_clk的频率为SCL频率的四倍
- 利用计算出的分频系数生成计数器,从而产生iic_clk驱动时钟
- 对iic_clk驱动时钟进行计数,注意此时应该用iic_clk的上升沿,计数四次以产生SCL
- 编写状态机来实现主体功能,一定要注意状态跳转的条件还有每个状态的输出
- 设计一个8位计数器对传输数据进行计数
- 代码实现
IIC驱动的代码实现如下:
`timescale 1ns / 1ps
module IIC_drive(
input clk, //系统时钟
input rst_n, //系统复位
input iic_rw, //IIC读写控制信号
input [7:0] w_data, //写入从机的8位数据
input iic_start, //开始信号
input [7:0] iic_addr, //IIC字节地址
output reg iic_end, //结束信号
output reg[7:0] r_data, //读取8位数据
output reg scl, //IIC时钟
output reg iic_clk, //IIC驱动时钟
inout sda //IIC数据线
);
parameter SYS_CLK = 28'd100_000_000 ; //系统时钟频率
parameter IIC_FREQ = 20'd400_000 ; //IIC通信频率,即SCL频率
parameter DIV_CNT = SYS_CLK / IIC_FREQ >> 2'd3 ; //分频系数
parameter DEVICE_ADDR = 7'b1010_000;
localparam IDLE = 4'd0 , //空闲状态
IIC_START = 4'd1 , //开始状态
SEND_D_ADDR_W = 4'd2 , //发送7位器件码+写操作
ACK1 = 4'd3 , //从机响应
SEND_R_ADDR = 4'd4 , //发送8位地址码
ACK2 = 4'd5 , //从机响应
W_DATA = 4'd6 , //写入8位数据
ACK3 = 4'd7 , //从机响应
IIC_START2 = 4'd8 , //第二次开始状态
SEND_D_ADDR_R = 4'd9 , //发送7位器件码+读操作
ACK4 = 4'd10 , //从机响应
R_DATA = 4'd11 , //读取8位数据
NACK = 4'd12 , //非应答状态
STOP = 4'd13 ; //结束状态
reg [3:0] state , next_state ; //现态与次态
reg [4:0] iic_cnt; //产生IIC驱动时钟计数器
reg [1:0] iic_clk_cnt; //对驱动时钟计数以产生SCL
reg iic_clk_cnt_en; //驱动时钟计数使能信号