目录
一、IIC简介
二、IIC传输方式
三、IIC时序
3.1 IIC整体时序
3.2 IIC数据传输和应答
3.3 器件地址
3.4 寄存器地址
四、IIC读写
4.1 IIC单次写时序
4.2 IIC连续写时序(页写时序)
4.3 IIC单次读时序
4.4 IIC连续读时序
五、IIC控制器设计
IIC即Inter-Integrated Circuit(集成电路总线),是一种双向、半双工、二线制串行通信总线。多用于主机和从机在数据量不大且传输距离短的场合下的主从通信。主机启动总线,并产生时钟用于传送数据,此时任何接收数据的器件均被认为是从机。
IIC总线由数据线SDA和时钟线SCL构成通信线路,可用于收发数据。
IIC中控制SCL的电平高低变换的为主设备,主要产生时钟、起始信号和停止信号
IIC从设备主要进行地址、停止位检测
IIC支持多主控, 任何一个能够进行发送和接收的设备都可以成为主设备,但同时只能有一个主控。
主设备和从设备之间以8bit即一个字节(先发送字节的最高位)进行双向数据传送,各种被控器件均并联在总线上,通过器件地址识别(每个器件都有唯一的地址识别)。
IIC在物理上由SDA和SCL及上拉电阻组成。
由于IIC器件一般采用开漏结构与总线相连(为了避免通信设备和空闲设备之间出现总线信号混乱),所以当总线空闲时,SCL和SDA均需接上拉电阻处于高电平状态
在IIC器件开始通信(传输数据)之前,串行时钟线SCL和串行数据线SDA 上拉处于高电平状态,此时IIC总线处于空闲状态。
如果主机开始传输数据,只需在SCL为高电平时将SDA线拉低,产生一个起始信号,从机检测到起始信号后,准备接收数据(主机可以向从机写数据,也可以读取从机输出的数据,数据的传输由双向数据线(SDA) 完成)。
当数据传输完成,主机产生一个停止信号,告诉从机数据传输结束,停止信号的产生是在SCL为高电平时,SDA 从低电平跳变到高电平,从机检测到停止信号后,停止接收数据,总线再次处于空闲状态。
总结:
总线空闲状态:SDA 为高电平,SCL 为高电平
IIC协议起始位:SCL为高电平时,SDA 出现下降沿,产生一个起始位
IIC协议结束位:SCL为高电平时,SDA 出现上升沿,产生一个结束位
在起始信号之后,主机开始发送传输的数据:在串行时钟线SCL为低电平状态时,SDA允许改变传输的数据位,在 SCL 为高电平状态时,SDA 要求保持稳定,相当于一个时钟周期传输 1bit 数据,经过 8个时钟周期后,传输了 8bit数据,即一个字节。第8个时钟周期末,主机释放SDA以使从机应答,在第 9个时钟周期,从机将 SDA 拉低以应答;如果第 9 个时钟周期,SCL 为高电平时,SDA 未被检测到为低电平,视为非应答,表明此次数据传输失败,主机可以决定是否放弃写入或者重新发起写入。第 9个时钟周期末,从机释放 SDA 以使主机继续传输数据,如果主机发送停止信号,此次传输结束。
总结:
数据传输时SCL为低电平时,SDA允许改变;SCL为高电平时,SDA保持稳定
主机往总线上发送地址,所有的从机都能接收到主机发出的地址,然后每个从机都将主机发出的地址与自己的地址比较,如果匹配上了,这个从机就会向总线发出一个响应信号。主机收到响应信号后,与从机建立通信兵向总线上发送数据。如果主机没有收到响应信号,则表示寻址失败。
通常情况下,主从器件的角色是确定的,也就是说从机一直工作在从机模式。每个IIC器件都有一个器件地址,有些器件的器件地址是固定的,而有些IIC器件的器件地址由个固定部分和一个可编程的部分构成,这是因为很可能在一个系统中有几个同样的器件,器件地址的可编程能最大数量的使这些器件连接到IIC总线上。
进行数据传输时,主机首先向总线上发出开始信号,对应开始位S,然后按照从高到低的位序发送器件地址,一般为7bit,第8bit位为读写控制位R/W,该位为0时表示主机对从机进行写操作,当该位为1时表示主机对从机进行读操作,然后接收从机响应。
某些器件内部会有一些可供读写的寄存器或存储器,要对某个器件的存储单元进行读写时,就需要指定存储单元的地址即字地址,然后再向该地址写入内容。
该地址为一个或两个字节长度,在主机收到从机返回的器件响应后由主机发出,具体长度由器件内部的存储单元的数量决定,当存储单元数量不超过一个字节所能表示的最大数量(2^8=256)时,用一个字节表示,超过一个字节所能表示的最大数量时,就需要用两个字节来表示。
1.单字节字地址
2.双字节字地址
写时序可概括为:start + 器件地址 + 写命令(0) + 字地址 + 数据 (+ 数据 + ... ) + stop
读时序可概括为:start + 器件地址 + 写命令(0) + 字地址 + start + 器件地址 + 读命令(1) + 接收从机的数据 + 主机非应答(1) + stop
单次写入数据过程如下:
主机拉低SDA发送起始信号;
主机传输器件地址字节,其中最低位为 0,表明为写操作;
主机读取从机应答信号;
读取应答信号成功,主机传输1字节字寄存器地址数据;
主机读取从机应答信号;
读取应答信号成功,对于1字节寄存器地址器件,主机传输待写入的数据;对于两字节字地址器件,传输地址数据低字节,主机读取从机应答信号,读取应答信号成功,主机传输待写入的数据;
主机读取从机应答信号;
读取应答信号成功,主机产生 STOP 位,终止传输。
连续写(也称页写,IIC连续写时序仅部分器件支持) 是主机连续写多个字节数据到从机,连续多字节写操作也是分为1字节地址段器件和2字节地址段器件的写操作。
连续写入多字节数据过程如下:
主机拉低SDA发送起始信号;
主机传输器件地址字节,其中最低位为0,表明为写操作;
主机读取从机应答信号;
读取应答信号成功,主机传输1字节地址数据;
主机读取从机应答信号;
读取应答信号成功,对于1字节地址段器件,传输待写入的第1个数据;对于两字节地址段器件,传输低字节地址数据,主机读取从机应答信号,读取应答信号成功,主机传输待写入的数据;
主机读取从机应答信号;
读取应答信号成功,传输待写入的下一个数据;
直到写完N个数据,主机读取从机应答信号;
读取应答信号成功后,主机产生 STOP 位,终止传输。
读时序发送完器件地址和字地址后又发送起始信号和器件地址,第一次发送器件地址后面的读写控制位为“0”,第二次发送器件地址时后面的读写控制位为“1”。
因为需要使从机内的存储单元地址指针指向想要读取的存储单元地址处,所以首先发送了一次Dummy Write也就是虚写操作,并不是真的要写数据,而是通过这种虚写操作使地址指针指向字地址,等从机应答后,就可以以当前地址读的方式读数据。
单次读数据过程如下:
主机拉低SDA发送起始信号;
主机传输器件地址字节,其中最低位为0,表明为写操作:
主机取从机应答信号;
读取应答信号成功,主机传输1字节字地址数据;
主机读取从机应答信号;
读取应答信号成功,对于1字节地址段器件,无此步骤;对于两字节地址段器件,主机传输低字节地址数据,主机读取从机应答信号,读取应答信号成功;
主机发起起始信号;
主机传输器件地址字节,其中最低位为1,表明为读操作;
主机读取从机应答信号;
读取应答信号成功,主机读取SDA总线上的一个字节的数据;
产生无应答1信号(无需设置为输出高电平,因为总线会被自动拉高);
主机产生STOP位,终止传输。
连续读将单次读中的主机非应答改为主机应答,表示继续读取数据,直到数据读完发送主机非应答信号。
状态机的状态跳转总共有 8个状态,一开始状态机处于空闲状态idle,当IIC触发执行信号触发 (i2c exec=1)时,状态机进入发送控制命令状态sladdr;发送完控制命令后就发送字地址。通过bit_ctl 信号判断是单字节还是双字节字地址。对于双字节的字地址先发送高8位即第一个字节,发送完高8位后进入发送 8 位字地址状态addr8也就是发送双字节地地址的低 8 位;对于单字节的字地址直接进入发送 8 位字地址状态addr8。发送完字地址后,根据读写判断标志来判断是读操作还是写操作。如果是写 (wr_flag=0) 就进入写数据状态data_wr,开始发送数据;如果是读 (wr_flag=1)就进入发送器件地址读状态addr_rd 发送器件地址,此状态结束后就进入读数据状态data_rd 接受数据。读或写数据结束后就进入结束IIC操作状态done并发送结束信号,此时,IIC总线再次进入空闲状态idle。
参考自正点原子
module i2c_dri
#(
parameter SLAVE_ADDR = 7'b1010000 , //从机器件地址
parameter CLK_FREQ = 26'd50_000_000, //模块输入的时钟频率
parameter I2C_FREQ = 18'd250_000 //IIC_SCL的时钟频率
)
(
input clk , //时钟
input rst_n , //复位,低电平有效
//IIC control
input i2c_exec , //I2C触发执行信号
input bit_ctrl , //字地址位控制,为1时字地址16位、为0时字地址8位
input i2c_rh_wl , //I2C读写控制信号
input [15:0] i2c_addr , //I2C器件字地址
input [ 7:0] i2c_data_w , //I2C要写的数据
inout sda , //I2C的SDA信号
//IIC output
output reg [ 7:0] i2c_data_r , //I2C读出的数据
output reg i2c_done , //I2C一次操作完成
output reg i2c_ack , //I2C应答标志 0:应答 1:未应答
output reg scl , //I2C的SCL时钟信号
//user interface
output reg dri_clk //驱动I2C操作的驱动时钟
);
//localparam define
localparam idle = 8'b0000_0001; //空闲状态
localparam sladdr = 8'b0000_0010; //发送器件地址(slave address)
localparam addr16 = 8'b0000_0100; //发送字地址高8位
localparam addr8 = 8'b0000_1000; //发送字地址低8位
localparam data_wr = 8'b0001_0000; //写数据(8bit)
localparam addr_rd = 8'b0010_0000; //发送器件地址
localparam data_rd = 8'b0100_0000; //读数据(8bit)
localparam stop = 8'b1000_0000; //结束I2C操作
//reg/wire define
reg sda_dir ; //I2C数据(SDA)方向控制
wire sda_in ; //SDA输入信号
reg sda_out ; //SDA输出信号
reg st_done ; //状态结束
reg wr_flag ; //读写标志
reg [ 6:0] cnt ; //计数
reg [ 7:0] cur_state ; //状态机当前状态
reg [ 7:0] next_state; //状态机下一状态
reg [15:0] addr_t ; //字地址临时寄存
reg [ 7:0] data_r ; //读取的数据
reg [ 7:0] data_wr_t ; //I2C需写的数据的临时寄存
reg [ 9:0] clk_cnt ; //分频时钟计数
wire [ 8:0] clk_divide; //模块驱动时钟的分频系数
//*****************************************************
//** main code
//*****************************************************
//SDA控制
assign sda = sda_dir?sda_out : 1'bz; //SDA信号处于数据输出或高阻态
assign sda_in = sda ; //SDA数据输入
assign clk_divide = (CLK_FREQ/I2C_FREQ) >> 2'd2; //模块驱动时钟的分频系数
//时钟信号SCL通过计数器方法产生,生成I2C的SCL的四倍频率的驱动时钟用于驱动i2c的操作
//将输入时钟clk分频为clk_divide倍频率,并输出到dri_clk信号上。这样可以产生一个驱动I2C操作的时钟信号,其频率是I2C_SCL时钟频率的clk_divide倍
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
dri_clk <= 1'b0;
clk_cnt <= 10'd0;
end
else if(clk_cnt == clk_divide[8:1] - 1'd1) begin
clk_cnt <= 10'd0;
dri_clk <= ~dri_clk; //在每个分频周期结束时,切换 dri_clk 的电平状态(从高到低或从低到高),实现分频时钟的频率。
end
else
clk_cnt <= clk_cnt + 1'b1;
end
//(三段式状态机)同步时序描述状态转移
always @(posedge dri_clk or negedge rst_n) begin
if(!rst_n)
cur_state <= idle;
else
cur_state <= next_state;
end
//组合逻辑判断状态转移条件
always @(*) begin
next_state = idle; //一开始处于空闲状态
case(cur_state)
idle: begin
if(i2c_exec) begin //当IIC触发执行信号i2c_exec
next_state = sladdr; //状态机进入发送从机器件地址
end
else
next_state = idle;
end
sladdr: begin
if(st_done) begin //如果上一状态结束
if(bit_ctrl) //判断是16位还是8位字地址
next_state = addr16; //双字节地址就进入高8位字地址状态
else
next_state = addr8 ; //单字节地址就进入低8位字地址状态
end
else
next_state = sladdr;
end
addr16: begin
if(st_done) begin
next_state = addr8; //发完高8位再进入低8位字地址状态
end
else begin
next_state = addr16;
end
end
addr8: begin //8位字地址
if(st_done) begin
if(wr_flag==1'b0) //读写判断
next_state = data_wr; //wr_flag为0则进入写数据状态
else
next_state = addr_rd; //wr_flag为1则进入读数据状态
end
else begin
next_state = addr8;
end
end
data_wr: begin //写数据(8 bit)
if(st_done)
next_state = stop; //写完就停止
else
next_state = data_wr;
end
addr_rd: begin
if(st_done) begin
next_state = data_rd; //发送器件地址读状态结束,进入读数据状态
end
else begin
next_state = addr_rd;
end
end
data_rd: begin //读取数据(8 bit)
if(st_done)
next_state = stop; //读完就停止
else
next_state = data_rd;
end
stop: begin
if(st_done)
next_state = idle; //结束I2C操作,进入空闲状态
else
next_state = stop ;
end
default: next_state= idle;
endcase
end
//时序电路描述状态输出
always @(posedge dri_clk or negedge rst_n) begin
if(!rst_n) begin //复位初始化
scl <= 1'b1;
sda_out <= 1'b1;
sda_dir <= 1'b1;
i2c_done <= 1'b0;
i2c_ack <= 1'b0;
cnt <= 1'b0;
st_done <= 1'b0;
data_r <= 1'b0;
i2c_data_r<= 1'b0;
wr_flag <= 1'b0;
addr_t <= 1'b0;
data_wr_t <= 1'b0;
end
else begin
st_done <= 1'b0 ;
cnt <= cnt +1'b1 ;
case(cur_state)
idle: begin //空闲状态
scl <= 1'b1;
sda_out <= 1'b1;
sda_dir <= 1'b1;
i2c_done<= 1'b0;
cnt <= 7'b0;
if(i2c_exec) begin //当IIC触发执行信号i2c_exec
wr_flag <= i2c_rh_wl ; //i2c_rh_wl读写控制信号赋值给读写标志
addr_t <= i2c_addr ; //i2c_addr器件字地址赋值临时寄存
data_wr_t <= i2c_data_w; //I2C需写的数据赋值给临时寄存
i2c_ack <= 1'b0; //I2C应答标志置为未应答
end
end
//通过计数器控制SCL的高低电平,SCL高电平时SDA保持稳定,SCL低电平时SDA允许变化
sladdr: begin //写地址(器件地址和字地址)
case(cnt)
7'd1 : sda_out <= 1'b0; //开始I2C
7'd3 : scl <= 1'b0;
7'd4 : sda_out <= SLAVE_ADDR[6]; //传送器件地址
7'd5 : scl <= 1'b1;
7'd7 : scl <= 1'b0;
7'd8 : sda_out <= SLAVE_ADDR[5];
7'd9 : scl <= 1'b1;
7'd11: scl <= 1'b0;
7'd12: sda_out <= SLAVE_ADDR[4];
7'd13: scl <= 1'b1;
7'd15: scl <= 1'b0;
7'd16: sda_out <= SLAVE_ADDR[3];
7'd17: scl <= 1'b1;
7'd19: scl <= 1'b0;
7'd20: sda_out <= SLAVE_ADDR[2];
7'd21: scl <= 1'b1;
7'd23: scl <= 1'b0;
7'd24: sda_out <= SLAVE_ADDR[1];
7'd25: scl <= 1'b1;
7'd27: scl <= 1'b0;
7'd28: sda_out <= SLAVE_ADDR[0];
7'd29: scl <= 1'b1;
7'd31: scl <= 1'b0;
7'd32: sda_out <= 1'b0; //0:写
7'd33: scl <= 1'b1;
7'd35: scl <= 1'b0;
7'd36: begin
sda_dir <= 1'b0;
sda_out <= 1'b1;
end
7'd37: scl <= 1'b1;
7'd38: begin //从机应答
st_done <= 1'b1;
if(sda_in == 1'b1) //高电平表示未应答
i2c_ack <= 1'b1; //拉高应答标志位
end
7'd39: begin
scl <= 1'b0; //SCL拉低
cnt <= 1'b0; //计数器清零
end
default : ;
endcase
end
addr16: begin
case(cnt)
7'd0 : begin
sda_dir <= 1'b1 ;
sda_out <= addr_t[15]; //传送字地址高8位
end
7'd1 : scl <= 1'b1;
7'd3 : scl <= 1'b0;
7'd4 : sda_out <= addr_t[14];
7'd5 : scl <= 1'b1;
7'd7 : scl <= 1'b0;
7'd8 : sda_out <= addr_t[13];
7'd9 : scl <= 1'b1;
7'd11: scl <= 1'b0;
7'd12: sda_out <= addr_t[12];
7'd13: scl <= 1'b1;
7'd15: scl <= 1'b0;
7'd16: sda_out <= addr_t[11];
7'd17: scl <= 1'b1;
7'd19: scl <= 1'b0;
7'd20: sda_out <= addr_t[10];
7'd21: scl <= 1'b1;
7'd23: scl <= 1'b0;
7'd24: sda_out <= addr_t[9];
7'd25: scl <= 1'b1;
7'd27: scl <= 1'b0;
7'd28: sda_out <= addr_t[8];
7'd29: scl <= 1'b1;
7'd31: scl <= 1'b0;
7'd32: begin
sda_dir <= 1'b0;
sda_out <= 1'b1;
end
7'd33: scl <= 1'b1;
7'd34: begin //从机应答
st_done <= 1'b1;
if(sda_in == 1'b1) //高电平表示未应答
i2c_ack <= 1'b1; //拉高应答标志位
end
7'd35: begin
scl <= 1'b0;
cnt <= 1'b0;
end
default : ;
endcase
end
addr8: begin
case(cnt)
7'd0: begin
sda_dir <= 1'b1 ;
sda_out <= addr_t[7]; //字地址低8位
end
7'd1 : scl <= 1'b1;
7'd3 : scl <= 1'b0;
7'd4 : sda_out <= addr_t[6];
7'd5 : scl <= 1'b1;
7'd7 : scl <= 1'b0;
7'd8 : sda_out <= addr_t[5];
7'd9 : scl <= 1'b1;
7'd11: scl <= 1'b0;
7'd12: sda_out <= addr_t[4];
7'd13: scl <= 1'b1;
7'd15: scl <= 1'b0;
7'd16: sda_out <= addr_t[3];
7'd17: scl <= 1'b1;
7'd19: scl <= 1'b0;
7'd20: sda_out <= addr_t[2];
7'd21: scl <= 1'b1;
7'd23: scl <= 1'b0;
7'd24: sda_out <= addr_t[1];
7'd25: scl <= 1'b1;
7'd27: scl <= 1'b0;
7'd28: sda_out <= addr_t[0];
7'd29: scl <= 1'b1;
7'd31: scl <= 1'b0;
7'd32: begin
sda_dir <= 1'b0;
sda_out <= 1'b1;
end
7'd33: scl <= 1'b1;
7'd34: begin //从机应答
st_done <= 1'b1;
if(sda_in == 1'b1) //高电平表示未应答
i2c_ack <= 1'b1; //拉高应答标志位
end
7'd35: begin
scl <= 1'b0;
cnt <= 1'b0;
end
default : ;
endcase
end
data_wr: begin //写数据(8 bit)
case(cnt)
7'd0: begin
sda_out <= data_wr_t[7]; //I2C写8位数据
sda_dir <= 1'b1;
end
7'd1 : scl <= 1'b1;
7'd3 : scl <= 1'b0;
7'd4 : sda_out <= data_wr_t[6];
7'd5 : scl <= 1'b1;
7'd7 : scl <= 1'b0;
7'd8 : sda_out <= data_wr_t[5];
7'd9 : scl <= 1'b1;
7'd11: scl <= 1'b0;
7'd12: sda_out <= data_wr_t[4];
7'd13: scl <= 1'b1;
7'd15: scl <= 1'b0;
7'd16: sda_out <= data_wr_t[3];
7'd17: scl <= 1'b1;
7'd19: scl <= 1'b0;
7'd20: sda_out <= data_wr_t[2];
7'd21: scl <= 1'b1;
7'd23: scl <= 1'b0;
7'd24: sda_out <= data_wr_t[1];
7'd25: scl <= 1'b1;
7'd27: scl <= 1'b0;
7'd28: sda_out <= data_wr_t[0];
7'd29: scl <= 1'b1;
7'd31: scl <= 1'b0;
7'd32: begin
sda_dir <= 1'b0;
sda_out <= 1'b1;
end
7'd33: scl <= 1'b1;
7'd34: begin //从机应答
st_done <= 1'b1;
if(sda_in == 1'b1) //高电平表示未应答
i2c_ack <= 1'b1; //拉高应答标志位
end
7'd35: begin
scl <= 1'b0;
cnt <= 1'b0;
end
default : ;
endcase
end
addr_rd: begin //写地址以进行读数据
case(cnt)
7'd0 : begin
sda_dir <= 1'b1;
sda_out <= 1'b1;
end
7'd1 : scl <= 1'b1;
7'd2 : sda_out <= 1'b0; //重新开始
7'd3 : scl <= 1'b0;
7'd4 : sda_out <= SLAVE_ADDR[6]; //传送器件地址
7'd5 : scl <= 1'b1;
7'd7 : scl <= 1'b0;
7'd8 : sda_out <= SLAVE_ADDR[5];
7'd9 : scl <= 1'b1;
7'd11: scl <= 1'b0;
7'd12: sda_out <= SLAVE_ADDR[4];
7'd13: scl <= 1'b1;
7'd15: scl <= 1'b0;
7'd16: sda_out <= SLAVE_ADDR[3];
7'd17: scl <= 1'b1;
7'd19: scl <= 1'b0;
7'd20: sda_out <= SLAVE_ADDR[2];
7'd21: scl <= 1'b1;
7'd23: scl <= 1'b0;
7'd24: sda_out <= SLAVE_ADDR[1];
7'd25: scl <= 1'b1;
7'd27: scl <= 1'b0;
7'd28: sda_out <= SLAVE_ADDR[0];
7'd29: scl <= 1'b1;
7'd31: scl <= 1'b0;
7'd32: sda_out <= 1'b1; //1:读
7'd33: scl <= 1'b1;
7'd35: scl <= 1'b0;
7'd36: begin
sda_dir <= 1'b0;
sda_out <= 1'b1;
end
7'd37: scl <= 1'b1;
7'd38: begin //从机应答
st_done <= 1'b1;
if(sda_in == 1'b1) //高电平表示未应答
i2c_ack <= 1'b1; //拉高应答标志位
end
7'd39: begin
scl <= 1'b0;
cnt <= 1'b0;
end
default : ;
endcase
end
data_rd: begin //读取数据(8 bit)
case(cnt)
7'd0: sda_dir <= 1'b0;
7'd1: begin
data_r[7] <= sda_in;
scl <= 1'b1;
end
7'd3: scl <= 1'b0;
7'd5: begin
data_r[6] <= sda_in ;
scl <= 1'b1 ;
end
7'd7: scl <= 1'b0;
7'd9: begin
data_r[5] <= sda_in;
scl <= 1'b1 ;
end
7'd11: scl <= 1'b0;
7'd13: begin
data_r[4] <= sda_in;
scl <= 1'b1 ;
end
7'd15: scl <= 1'b0;
7'd17: begin
data_r[3] <= sda_in;
scl <= 1'b1 ;
end
7'd19: scl <= 1'b0;
7'd21: begin
data_r[2] <= sda_in;
scl <= 1'b1 ;
end
7'd23: scl <= 1'b0;
7'd25: begin
data_r[1] <= sda_in;
scl <= 1'b1 ;
end
7'd27: scl <= 1'b0;
7'd29: begin
data_r[0] <= sda_in;
scl <= 1'b1 ;
end
7'd31: scl <= 1'b0;
7'd32: begin
sda_dir <= 1'b1;
sda_out <= 1'b1;
end
7'd33: scl <= 1'b1;
7'd34: st_done <= 1'b1; //非应答
7'd35: begin
scl <= 1'b0;
cnt <= 1'b0;
i2c_data_r <= data_r;
end
default : ;
endcase
end
stop: begin //结束I2C操作
case(cnt)
7'd0: begin
sda_dir <= 1'b1; //结束I2C
sda_out <= 1'b0;
end
7'd1 : scl <= 1'b1;
7'd3 : sda_out <= 1'b1;
7'd15: st_done <= 1'b1;
7'd16: begin
cnt <= 1'b0;
i2c_done <= 1'b1; //向上层模块传递I2C结束信号
end
default : ;
endcase
end
endcase
end
end
endmodule
小梅哥给出了另一种写法,注释有空再写
module i2c_control(
Clk,
Rst_n,
wrreg_req,
rdreg_req,
addr,
addr_mode,
wrdata,
rddata,
device_id,
RW_Done,
ack,
i2c_sclk,
i2c_sdat
);
input Clk;
input Rst_n;
input wrreg_req;
input rdreg_req;
input [15:0]addr;
input addr_mode;
input [7:0]wrdata;
output reg[7:0]rddata;
input [7:0]device_id;
output reg RW_Done;
output reg ack;
output i2c_sclk;
inout i2c_sdat;
reg [5:0]Cmd;
reg [7:0]Tx_DATA;
wire Trans_Done;
wire ack_o;
reg Go;
wire [15:0] reg_addr;
assign reg_addr = addr_mode?addr:{addr[7:0],addr[15:8]};
wire [7:0]Rx_DATA;
localparam
WR = 6'b000001, //写请求
STA = 6'b000010, //起始位请求
RD = 6'b000100, //读请求
STO = 6'b001000, //停止位请求
ACK = 6'b010000, //应答位请求
NACK = 6'b100000; //无应答请求
i2c_bit_shift i2c_bit_shift(
.Clk(Clk),
.Rst_n(Rst_n),
.Cmd(Cmd),
.Go(Go),
.Rx_DATA(Rx_DATA),
.Tx_DATA(Tx_DATA),
.Trans_Done(Trans_Done),
.ack_o(ack_o),
.i2c_sclk(i2c_sclk),
.i2c_sdat(i2c_sdat)
);
reg [6:0]state;
reg [7:0]cnt;
localparam
IDLE = 7'b0000001, //空闲状态
WR_REG = 7'b0000010, //写寄存器状态
WAIT_WR_DONE = 7'b0000100, //等待写寄存器完成状态
WR_REG_DONE = 7'b0001000, //写寄存器完成状态
RD_REG = 7'b0010000, //读寄存器状态
WAIT_RD_DONE = 7'b0100000, //等待读寄存器完成状态
RD_REG_DONE = 7'b1000000; //读寄存器完成状态
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)begin
Cmd <= 6'd0;
Tx_DATA <= 8'd0;
Go <= 1'b0;
rddata <= 0;
state <= IDLE;
ack <= 0;
end
else begin
case(state)
IDLE:
begin
cnt <= 0;
ack <= 0;
RW_Done <= 1'b0;
if(wrreg_req)
state <= WR_REG;
else if(rdreg_req)
state <= RD_REG;
else
state <= IDLE;
end
WR_REG:
begin
state <= WAIT_WR_DONE;
case(cnt)
0:write_byte(WR | STA, device_id);
1:write_byte(WR, reg_addr[15:8]);
2:write_byte(WR, reg_addr[7:0]);
3:write_byte(WR | STO, wrdata);
default:;
endcase
end
WAIT_WR_DONE:
begin
Go <= 1'b0;
if(Trans_Done)begin
ack <= ack | ack_o;
case(cnt)
0: begin cnt <= 1; state <= WR_REG;end
1:
begin
state <= WR_REG;
if(addr_mode)
cnt <= 2;
else
cnt <= 3;
end
2: begin
cnt <= 3;
state <= WR_REG;
end
3:state <= WR_REG_DONE;
default:state <= IDLE;
endcase
end
end
WR_REG_DONE:
begin
RW_Done <= 1'b1;
state <= IDLE;
end
RD_REG:
begin
state <= WAIT_RD_DONE;
case(cnt)
0:write_byte(WR | STA, device_id);
1:write_byte(WR, reg_addr[15:8]);
2:write_byte(WR, reg_addr[7:0]);
3:write_byte(WR | STA, device_id | 8'd1);
4:read_byte(RD | NACK | STO);
default:;
endcase
end
WAIT_RD_DONE:
begin
Go <= 1'b0;
if(Trans_Done)begin
if(cnt <= 3)
ack <= ack | ack_o;
case(cnt)
0: begin cnt <= 1; state <= RD_REG;end
1:
begin
state <= RD_REG;
if(addr_mode)
cnt <= 2;
else
cnt <= 3;
end
2: begin
cnt <= 3;
state <= RD_REG;
end
3:begin
cnt <= 4;
state <= RD_REG;
end
4:state <= RD_REG_DONE;
default:state <= IDLE;
endcase
end
end
RD_REG_DONE:
begin
RW_Done <= 1'b1;
rddata <= Rx_DATA;
state <= IDLE;
end
default:state <= IDLE;
endcase
end
task read_byte;
input [5:0]Ctrl_Cmd;
begin
Cmd <= Ctrl_Cmd;
Go <= 1'b1;
end
endtask
task write_byte;
input [5:0]Ctrl_Cmd;
input [7:0]Wr_Byte_Data;
begin
Cmd <= Ctrl_Cmd;
Tx_DATA <= Wr_Byte_Data;
Go <= 1'b1;
end
endtask
endmodule
module i2c_bit_shift(
Clk,
Rst_n,
Cmd,
Go,
Rx_DATA,
Tx_DATA,
Trans_Done,
ack_o,
i2c_sclk,
i2c_sdat
);
input Clk;
input Rst_n;
input [5:0]Cmd;
input Go;
output reg[7:0]Rx_DATA;
input [7:0]Tx_DATA;
output reg Trans_Done;
output reg ack_o;
output reg i2c_sclk;
inout i2c_sdat;
reg i2c_sdat_o;
//系统时钟采用50MHz
parameter SYS_CLOCK = 50_000_000;
//SCL总线时钟采用400kHz
parameter SCL_CLOCK = 400_000;
//产生时钟SCL计数器最大值
localparam SCL_CNT_M = SYS_CLOCK/SCL_CLOCK/4 - 1;
reg i2c_sdat_oe;
localparam
WR = 6'b000001, //写请求
STA = 6'b000010, //起始位请求
RD = 6'b000100, //读请求
STO = 6'b001000, //停止位请求
ACK = 6'b010000, //应答位请求
NACK = 6'b100000; //无应答请求
reg [19:0]div_cnt;
reg en_div_cnt;
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
div_cnt <= 20'd0;
else if(en_div_cnt)begin
if(div_cnt < SCL_CNT_M)
div_cnt <= div_cnt + 1'b1;
else
div_cnt <= 0;
end
else
div_cnt <= 0;
wire sclk_plus = div_cnt == SCL_CNT_M;
//assign i2c_sdat = i2c_sdat_oe?i2c_sdat_o:1'bz;
assign i2c_sdat = !i2c_sdat_o && i2c_sdat_oe ? 1'b0:1'bz;
reg [7:0]state;
localparam
IDLE = 8'b00000001, //空闲状态
GEN_STA = 8'b00000010, //产生起始信号
WR_DATA = 8'b00000100, //写数据状态
RD_DATA = 8'b00001000, //读数据状态
CHECK_ACK = 8'b00010000, //检测应答状态
GEN_ACK = 8'b00100000, //产生应答状态
GEN_STO = 8'b01000000; //产生停止信号
reg [4:0]cnt;
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)begin
Rx_DATA <= 0;
i2c_sdat_oe <= 1'd0;
en_div_cnt <= 1'b0;
i2c_sdat_o <= 1'd1;
Trans_Done <= 1'b0;
ack_o <= 0;
state <= IDLE;
cnt <= 0;
end
else begin
case(state)
IDLE:
begin
Trans_Done <= 1'b0;
i2c_sdat_oe <= 1'd1;
if(Go)begin
en_div_cnt <= 1'b1;
if(Cmd & STA)
state <= GEN_STA;
else if(Cmd & WR)
state <= WR_DATA;
else if(Cmd & RD)
state <= RD_DATA;
else
state <= IDLE;
end
else begin
en_div_cnt <= 1'b0;
state <= IDLE;
end
end
GEN_STA:
begin
if(sclk_plus)begin
if(cnt == 3)
cnt <= 0;
else
cnt <= cnt + 1'b1;
case(cnt)
0:begin i2c_sdat_o <= 1; i2c_sdat_oe <= 1'd1;end
1:begin i2c_sclk <= 1;end
2:begin i2c_sdat_o <= 0; i2c_sclk <= 1;end
3:begin i2c_sclk <= 0;end
default:begin i2c_sdat_o <= 1; i2c_sclk <= 1;end
endcase
if(cnt == 3)begin
if(Cmd & WR)
state <= WR_DATA;
else if(Cmd & RD)
state <= RD_DATA;
end
end
end
WR_DATA:
begin
if(sclk_plus)begin
if(cnt == 31)
cnt <= 0;
else
cnt <= cnt + 1'b1;
case(cnt)
0,4,8,12,16,20,24,28:begin i2c_sdat_o <= Tx_DATA[7-cnt[4:2]]; i2c_sdat_oe <= 1'd1;end //set data;
1,5,9,13,17,21,25,29:begin i2c_sclk <= 1;end //sclk posedge
2,6,10,14,18,22,26,30:begin i2c_sclk <= 1;end //sclk keep high
3,7,11,15,19,23,27,31:begin i2c_sclk <= 0;end //sclk negedge
/*
0 :begin i2c_sdat_o <= Tx_DATA[7];end
1 :begin i2c_sclk <= 1;end //sclk posedge
2 :begin i2c_sclk <= 1;end //sclk keep high
3 :begin i2c_sclk <= 0;end //sclk negedge
4 :begin i2c_sdat_o <= Tx_DATA[6];end
5 :begin i2c_sclk <= 1;end //sclk posedge
6 :begin i2c_sclk <= 1;end //sclk keep high
7 :begin i2c_sclk <= 0;end //sclk negedge
8 :begin i2c_sdat_o <= Tx_DATA[5];end
9 :begin i2c_sclk <= 1;end //sclk posedge
10:begin i2c_sclk <= 1;end //sclk keep high
11:begin i2c_sclk <= 0;end //sclk negedge
12:begin i2c_sdat_o <= Tx_DATA[4];end
13:begin i2c_sclk <= 1;end //sclk posedge
14:begin i2c_sclk <= 1;end //sclk keep high
15:begin i2c_sclk <= 0;end //sclk negedge
16:begin i2c_sdat_o <= Tx_DATA[3];end
17:begin i2c_sclk <= 1;end //sclk posedge
18:begin i2c_sclk <= 1;end //sclk keep high
19:begin i2c_sclk <= 0;end //sclk negedge
20:begin i2c_sdat_o <= Tx_DATA[2];end
21:begin i2c_sclk <= 1;end //sclk posedge
22:begin i2c_sclk <= 1;end //sclk keep high
23:begin i2c_sclk <= 0;end //sclk negedge
24:begin i2c_sdat_o <= Tx_DATA[1];end
25:begin i2c_sclk <= 1;end //sclk posedge
26:begin i2c_sclk <= 1;end //sclk keep high
27:begin i2c_sclk <= 0;end //sclk negedge
28:begin i2c_sdat_o <= Tx_DATA[0];end
29:begin i2c_sclk <= 1;end //sclk posedge
30:begin i2c_sclk <= 1;end //sclk keep high
31:begin i2c_sclk <= 0;end //sclk negedge
*/
default:begin i2c_sdat_o <= 1; i2c_sclk <= 1;end
endcase
if(cnt == 31)begin
state <= CHECK_ACK;
end
end
end
RD_DATA:
begin
if(sclk_plus)begin
if(cnt == 31)
cnt <= 0;
else
cnt <= cnt + 1'b1;
case(cnt)
0,4,8,12,16,20,24,28:begin i2c_sdat_oe <= 1'd0; i2c_sclk <= 0;end //set data;
1,5,9,13,17,21,25,29:begin i2c_sclk <= 1;end //sclk posedge
2,6,10,14,18,22,26,30:begin i2c_sclk <= 1; Rx_DATA <= {Rx_DATA[6:0],i2c_sdat};end //sclk keep high
3,7,11,15,19,23,27,31:begin i2c_sclk <= 0;end //sclk negedge
default:begin i2c_sdat_o <= 1; i2c_sclk <= 1;end
endcase
if(cnt == 31)begin
state <= GEN_ACK;
end
end
end
CHECK_ACK:
begin
if(sclk_plus)begin
if(cnt == 3)
cnt <= 0;
else
cnt <= cnt + 1'b1;
case(cnt)
0:begin i2c_sdat_oe <= 1'd0; i2c_sclk <= 0;end
1:begin i2c_sclk <= 1;end
2:begin ack_o <= i2c_sdat; i2c_sclk <= 1;end
3:begin i2c_sclk <= 0;end
default:begin i2c_sdat_o <= 1; i2c_sclk <= 1;end
endcase
if(cnt == 3)begin
if(Cmd & STO)
state <= GEN_STO;
else begin
state <= IDLE;
Trans_Done <= 1'b1;
end
end
end
end
GEN_ACK:
begin
if(sclk_plus)begin
if(cnt == 3)
cnt <= 0;
else
cnt <= cnt + 1'b1;
case(cnt)
0:begin
i2c_sdat_oe <= 1'd1;
i2c_sclk <= 0;
if(Cmd & ACK)
i2c_sdat_o <= 1'b0;
else if(Cmd & NACK)
i2c_sdat_o <= 1'b1;
end
1:begin i2c_sclk <= 1;end
2:begin i2c_sclk <= 1;end
3:begin i2c_sclk <= 0;end
default:begin i2c_sdat_o <= 1; i2c_sclk <= 1;end
endcase
if(cnt == 3)begin
if(Cmd & STO)
state <= GEN_STO;
else begin
state <= IDLE;
Trans_Done <= 1'b1;
end
end
end
end
GEN_STO:
begin
if(sclk_plus)begin
if(cnt == 3)
cnt <= 0;
else
cnt <= cnt + 1'b1;
case(cnt)
0:begin i2c_sdat_o <= 0; i2c_sdat_oe <= 1'd1;end
1:begin i2c_sclk <= 1;end
2:begin i2c_sdat_o <= 1; i2c_sclk <= 1;end
3:begin i2c_sclk <= 1;end
default:begin i2c_sdat_o <= 1; i2c_sclk <= 1;end
endcase
if(cnt == 3)begin
Trans_Done <= 1'b1;
state <= IDLE;
end
end
end
default:state <= IDLE;
endcase
end
endmodule