I2C接口简介
1.1:
I2C全称:Inter-Integrated Circuit,是一种同步、半双工的通信总线。
同步:发送接收端要严格同步,一般有同步时钟线。
半双工:I2C只有一条数据线,所以master发数据与收数据不能同时进行。
![](https://img-blog.csdnimg.cn/img_convert/5c65d5fb86a79f187b25ecbae378ffdb.jpeg)
图中是2个master+2个slave的示意,同一时刻只有一个master与一个slave通信。若想实现这个效果:
1.多个master-slave 时钟、数据线连在一起,需要实现信号的“线与”逻辑(所以SDA、SCL 被设计为漏极开路结构,外加上拉电阻实现“线与”)。
2.需要实现 “时钟同步”和“总线仲裁”,引脚在输出信号的同时还能对引脚上的电平进行检测,检测是否与刚才输出一致,为 “时钟同步”和“总线仲裁”提供硬件基础。
3.I2C在读写时需要带上设备地址,这样不使用多的信号线就可指定特定的slave(而SPI通信需要多的片选线)。
1.2:
写过程:
![](https://img-blog.csdnimg.cn/img_convert/49f53fef93074d0d1332522619d348ce.jpeg)
Master发起START
Master发送I2C addr(7bit)和W(写)操作0(1bit),等待ACK
Slave发送ACK
Master发送reg addr(8bit),等待ACK
Slave发送ACK
Master发送data(8bit),即要写入寄存器中的数据,等待ACK
Slave发送ACK
第6步和第7步可以重复多次,即顺序写多个寄存器
Master发起STOP结束传输
读过程:
![](https://img-blog.csdnimg.cn/img_convert/1c6172320394531e60ebe4001385b4b4.jpeg)
Master发起START
Master发送I2C addr(7bit)和r(读)操作1(1bit),等待ACK
Slave发送ACK
Slave发送data(8bit),即寄存器里的值
Master发送ACK
第7步和第8步可以重复多次,即顺序读多个寄存器
当master接收完想要的数据后,由Master发送NACK,告知slave停止发送数据
Master发送STOP结束传输
1.3:
1.总线空闲状态:
SDA和SCL同时处于高电平时,规定为总线的空闲状态。此时各个器件的输出级场效应管均处在截止状态,即释放总线,由两条信号线各自的上拉电阻把电平拉高。
2.总线START:
SCL为高电平时,SDA由高电平向低电平跳变,开始传送数据。
3.总线STOP:
SCL为高电平时,SDA由低电平向高电平跳变,结束传送数据。
4.总线Restart:
SCL为高电平时,SDA由高电平向低电平跳变,本质上也是START信号,用在完整I2C读过程中的读阶段,在首次发送停止信号之前,master通过发送Restart信号,可以转换与当前slave的通信模式(从写模式到读模式),或是切换到与另一个slave通信。
5.数据阶段:
在IIC总线上传送的每一位数据都有一个时钟脉冲相对应(或同步控制),即在SCL串行时钟的配合下,在SDA上逐位地串行传送每一位数据。进行数据传送时,在SCL呈现高电平期间,SDA上的电平必须保持稳定。只有在SCL为低电平期间,才允许SDA上的电平改变状态。简单的说就是,数据在SCL下降会被采样,所以SDA需要在SCL为高电平时保持稳定。
6. ACK与NACK信号:
IIC总线上的所有数据都是以8位字节传送的,发送器每发送一个字节,就在第9个时钟脉冲期间释放数据线,由接收器反馈一个应答信号。应答信号为低电平时,规定为有效应答位(ACK简称应答位),表示接收器已经成功地接收了该字节;应答信号为高电平时,规定为非应答位(NACK),一般表示接收器接收该字节没有成功。
这段话再说细一点:
在写阶段,master写了一字节数据,在第9个时钟脉冲期间释放数据线,由slave反馈应答信号,ACK(低)表示数据成功接收,NACK(高)表示该字节没有接收成功;
在读阶段,master向slave收数据,slave写了一字节数据,在第9个时钟脉冲期间释放数据线,由master反馈应答信号,ACK(低)表示数据成功接收,NACK(高)表示该字节没有接收成功。
还有一种特殊情况:当master决定不再接收数据时,应向slave发送NACK信号,高速slave不再发送。
以下情况会导致出现NACK位:
1.接收器没有发送机响应的地址,接收端没有任何ACK发送给发送器
2.由于接收器正在忙碌处理实时程序导致接无法接收或者发送
传输过程中,接收机器别不了发送机的数据或命令
3.接收器无法接收
4.master接收完成读取数据后,要发送NACK结束告知slave。
当master接收到slave的NACK信号后,可以STOP这次传输,也可以重新START。
所以:NACK并不只是表示字节没有成功接收,也可以表示master告诉slave不再需要发送数据。
I2C程序
![](https://img-blog.csdnimg.cn/img_convert/b920d98948eeaccf99ec3d25b165cc65.png)
`timescale 1ns / 1ps
//
// Company:
// Engineer:
//
// Create Date: 2023/02/23 16:16:18
// Design Name:
// Module Name: i2c_dri
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module i2c_dri
#(
parameter SLAVE_ADDR = 7'b1010000,
parameter CLK_FREQ = 26'd50_000_000,
parameter I2C_FREQ = 18'd250_000
)
(
input clk ,
input rst_n ,
input i2c_exec ,
input i2c_rh_wl ,
input bit_ctrl ,
input [15:0] i2c_addr ,
input [7:0] i2c_data_w ,
output reg [7:0] i2c_data_r ,
output reg i2c_done ,
output reg i2c_ack ,
output reg scl ,
inout sda ,
output reg dri_clk
);
localparam st_idle = 8'b0000_0001; //空闲状态
localparam st_sladdr = 8'b0000_0010; //发送器件地址
localparam st_addr16 = 8'b0000_0100; //发送16位地址
localparam st_addr8 = 8'b0000_1000; //发送8位地址
localparam st_data_wr = 8'b0001_0000; //写数据
localparam st_addr_rd = 8'b0010_0000; //读地址
localparam st_data_rd = 8'b0100_0000; //读数据
localparam st_stop = 8'b1000_0000; //结束i2c操作
reg sda_dir ;
reg sda_out ;
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 ;
reg [9:0] clk_cnt ;
wire sda_in ;
wire [8:0] clk_divide ;
assign sda = sda_dir ? sda_out : 1'bz;
assign sda_in = sda;
assign clk_divide = (CLK_FREQ/I2C_FREQ) >> 2'd2;
//I2C的SCL四倍频
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;
end
else
clk_cnt <= clk_cnt + 1'd1;
end
//状态机状态转移
always@(posedge dri_clk or negedge rst_n)begin
if(!rst_n)
cur_state <= st_idle;
else
cur_state <= next_state;
end
//状态机状态转移条件
always@(*)begin
next_state = st_idle;
case(cur_state)
st_idle: begin
if(i2c_exec == 1'b1)
next_state = st_sladdr;
else
next_state = st_idle;
end
st_sladdr:begin
if(st_done)begin
if(bit_ctrl)
next_state = st_addr16;
else
next_state = st_addr8;
end
else
next_state = st_sladdr;
end
st_addr16:begin
if(st_done)begin
next_state = st_addr8;
end
else begin
next_state = st_addr16;
end
end
st_addr8:begin
if(st_done)begin
if(wr_flag == 1'b0)
next_state = st_data_wr;
else
next_state = st_addr_rd;
end
else begin
next_state = st_addr8;
end
end
st_data_wr:begin
if(st_done)
next_state = st_stop;
else
next_state = st_data_wr;
end
st_addr_rd:begin
if(st_done)begin
next_state = st_data_rd;
end
else begin
next_state = st_addr_rd;
end
end
st_data_rd:begin
if(st_done)
next_state = st_stop;
else
next_state = st_data_rd;
end
st_stop:begin
if(st_done)
next_state = st_idle;
else
next_state = st_stop;
end
default:next_state = st_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)
st_idle:begin
scl <=1'b1;
sda_out <=1'b1;
sda_dir <=1'b1;
i2c_done<=1'b0;
cnt <=1'b0;
if(i2c_exec)begin
wr_flag <=i2c_rh_wl;
addr_t <=i2c_addr;
data_wr_t <=i2c_data_w;
i2c_ack <=1'b0;
end
end
st_sladdr:begin
case(cnt)
7'd1 : 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'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;
cnt <= 1'b0;
end
default : ;
endcase
end
st_addr16:begin
case(cnt)
7'd0:begin
sda_dir <= 1'b1;
sda_out <= addr_t[15];
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'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
st_addr8:begin
case(cnt)
7'd0:begin
sda_dir <= 1'b1;
sda_out <= addr_t[7];
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'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
st_data_wr:begin
case(cnt)
7'd0:begin
sda_out <= data_wr_t[7];
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
st_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;
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
st_data_rd:begin
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
st_stop:begin
case(cnt)
7'd0:begin
sda_dir <= 1'b1;
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;
end
default : ;
endcase
end
endcase
end
end
endmodule
![](https://img-blog.csdnimg.cn/img_convert/4220bd34f7af02819d8263cc19b80ef3.png)
![](https://img-blog.csdnimg.cn/img_convert/8329310aa6e349248fbd893584696aae.png)
分频系数就是scl一个周期有几个clk,dri_clk=4*scl,所以分频系数再除以四(等价于右移两位)。
//I2C的SCL四倍频
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;
end
else
clk_cnt <= clk_cnt + 1'd1;
end