I2C介绍及verilog实现(主机/从机可综合)
目录
1.简介
2.基本特征
3.物理连接
4.数据格式
4.1快速模式和低速模式(F/S)写
4.2快速模式和低速模式(F/S)读
4.3高速模式(Hs)读/写
4.4连续多次读/写
5.时序
5.1开始位和停止位
5.2字节传输时序
5.3字节内传输顺序
6.功能描述及模块分析
7.具体设计
7.1主机模块
7.1.1设计思路
7.1.2master状态机
7.2.从机模块
8.I2C顶层接inout口处理
9.代码及仿真
9.1测试模型结构
9.2测试结果
I2C是一种只有2条线的串行通信协议。可用于IC内部通信,也可以用于IC间的通信,广泛用于开关电源、触控芯片、简单的显示芯片等。
(1)2条通信线,SDA数据线,SCL时钟线。
(2)串行的8-bit双向数据传输,速率分为:
(3)在超快速模式下(UFm),最快可达5Mbit/s(单向传输)。
(4)可支持多mater,多slave。
在bus上可以接入多个主机、多个从机,需要接入上拉电阻,在无操作时,线上电压为高电平。
(本文介绍的所有内容仅牵涉单主机,多主机内容和参考附件1)
在F/S模式和Hs模式下的数据格式不同,其中又分读数据格式、写数据格式。
数据传输从开始位开始,接着传输一个7位地址(slave address,这里不介绍10bits地址)和1bit读写位(0:写,1:读)。这里进行写操作,因此读写位为0。接着主机会接收来至从机的1bit响应位(1:未响应,0:响应ok)。若此时从机响应,则主机接着传输需要写入的数据(8 bits),未响应则进入停止位。每一字节传输后都需要接收一次响应,若从机响应再继续传输,若从机未响应,则主机直接进入停止位。最后,当数据传输完成后,主机进入停止位,完成传输。
slave address用于寻找对应的芯片,例如,一个电路板上有多个开关电源,但我们只想用一个I2C主机控制,则可以将所有的SDA和SCL分别接到一起,并给不同的开关电源分配不同的地址。在主机发出开始位和地址位时,所有的开关电源(从机)都会接收到该信号,并将接收的地址同设置好自身的地址对比,若相同,则在响应位拉低SDA,响应主机,否则保持高电平。
Hs模式和F/S模式的数据格式基本相同,只是在开始位后会先发送master code。发送完master code后不需要响应,接着进入Hs模式。传输完成后,可发送停止位或者发送再开始位(Sr)进入下一次传输。
master code为可理解成Hs的标志,传输完master code后就可认为主机进入了Hs模式,若从机支持Hs模式,也应该进入Hs模式。master code是一个8位数据(0000_1xxx),应该注意0000_1000是测试用的保留字段,不能作为master code。同时也应该注意,slave address需避开master code的特征(参考文献1)。
从图中可看出,开始位需要满足的条件:SCL高电平时,SDA由高变低;停止位需要满足的条件:SCL为高电平时,SDA由低变高。(Sr和S的时序一致)
结合5.1可看出,SDA电平变化不能在SCL为高电平时进行,否则可能会被误认为成开始位或者停止位。因此SDA只能在SCL低电平时改变。
在字节内,先传输MSB。
关于I2C介绍网上资料很多,这里介绍了一些重点知识点,朋友可自行网上学习。下面开始说明verilog的具体实现。
(1)实现将fifo传过来的8bits数据转换成I2C串行数据输出的主机和接收数据从机。
(2) 这里用到的fifo由vivado ip直接产生。主机使用一个时钟wclk,一个异步复位(不采用I2C协议的软复位)。wclk分频模块分频后用于产生SCL,分频系数可配。
(3)在设计从机时,可以使用SCL和SDA设计时序电路实现数据接收,但这种设计并不灵活,很难添加其它功能,因此在此设计仍采用单独时钟用于接收数据。
(4)通常slave会先将SDA和SCL先滤波,这里不设计(后面单独讲一下如何滤波)。
(5)计划分4个模块,一个fifo发送模块,一个fifo接收模块,一个I2C主机模块,一个I2C从机模块。
特征:
从数据格式可看出,一次完整的I2C传输至少包含起始位、停止位、响应位、数据传输位,Hs模式时还需要传输master code位,因此I2C的状态也至少包含以上这些状态。另外,由于时序特征,SDA只能在SCL低电平时改变,因此在设计时,可以再细分三个状态将1bit数据分三段传输,开始位和停止位可分为2段。该设计则是用一个计数器的方式控制SDA、SCL的电平,并为再细分。
关于状态机的具体设计,可参考如下图,该状态机基本可满足现阶段常用的I2C从机。这里不做具体分析,建议配合代码理解。
这里同样给出从机的状态机
如代码所示,在设计时,sda、scl分为输入和输出4条线,但芯片外部却只有2条线。这需要如何实现呢?这就牵涉inout类型端口处理。
我们看下面这张图,out信号可以通过en0使能信号控制输出。对应到i2c,总线是有上拉电阻的,所以不操作总线时,总线就是高电平,那么我们只需要在out输出低电平时打开使能即可,即en0 = ~out。对于输入,设想当out为0时,en1也打开,那就没办法接收slave传回的真正信号了,因为不管slave输出什么,此时in肯定时0。那么en1需要在out为高时打开,及en1 = out。这就是为什么有人说只要master和slave一个为低,总线也为低。其实协议的响应位也是考虑了这个因素,理解了这层意思,才能更好理解i2c协议,在设计时,也能考虑清楚各个状态机下的电平赋值。
在fpga设计中代码如下:
assign sda = (sda_out)? 1'bz:sda_out;
assign sda_in = (sda_out)?sda:1'b1;
在ic设计中,pad有专门的模块,对设计者来说只需要生成对应的en信号即可。具体实现方式和上图类似,只是不需要设计人员写代码,而是用专门的cell模块处理。
详细代码较长,放在下载区:
I2C主机及从机Verilog代码实现.zip-硬件开发文档类资源-CSDN下载
通过vivado搭建仿真平台,生成fifo ip,代码结构如图(仿真时未使用master的rx):
前8位为master code,然后进入hs模式,master传输6个数据,接着master读6个数据。
测试代码如下:
`timescale 1ns/1ps
module i2c_test();
//assign sda = (msda_out)? 1'bz:msda_out;
//assign msda_in = (msda_out)?sda:1'b1;
reg wclk ;
reg rst_wclk_n ;
reg i2c_en ;
wire txfifo_empty ;
wire [7:0]tx_data ;
wire sda_out ;
wire scl_out ;
wire txfifo_rd_en ;
wire [7:0]rx_data ;
reg wr_en ;
reg [7:0] din;
reg swr_en;
reg [7:0] sdin;
wire [7:0] stx_fifo_dat;
wire [7:0] s_rx_dat;
//clock
initial
begin
wclk = 1'b0;
forever #50 wclk = ~wclk;
end
//reset
initial
begin
rst_wclk_n = 1'b0;
#5000;
rst_wclk_n = 1'b1;
end
//master control
initial
begin
i2c_en = 1'b0 ;
#6000;
repeat(6)
begin
@(posedge wclk)begin
din <= $random%255;
wr_en <= 1'b1;
end
end
repeat(6)
begin
@(posedge wclk)begin
sdin <= $random%255;
swr_en <= 1'b1;
end
end
@(posedge wclk)
begin
i2c_en <= 1'b1;
wr_en <= 1'b0;
end
#20000;
force i2c_master.scl_in = 1'b0;
#500;
release i2c_master.scl_in;
end
i2c_master i2c_master(
.wclk (wclk ),
.rst_wclk_n (rst_wclk_n ),
.i2c_en (i2c_en ),
.txfifo_empty(txfifo_empty ),
.hs_mode (1'b1 ),
.data_wr_rd (1'b1 ),
.master_code (8'b1001_0001 ),
.slave_addr (8'b1010_1010 ),
.tx_data (tx_data ),
.i2c_comb_wr (1'b1 ),
.rx_byte_num (8'd4 ),
.div_num (8'd100 ),
.hs_div (8'd10 ),
.sda_in (ssda_out ),
.scl_in (sscl_out ),
.sda_out (sda_out ),
.scl_out (scl_out ),
.txfifo_rd_en(txfifo_rd_en ),
.rx_data (rx_data ),
.rx_wr_en_out(rx_wr_en_out )
);
i2c_slave i2c_slave(
.sscl_in (scl_out ),
.ssda_in (sda_out ),
.wclk (wclk ),
.rst_wclk_n (rst_wclk_n ),
.slave_en (1'b1 ),
.stxfifo_empty (stxfifo_empty ),
// .srxfifo_full (srxfifo_full ),
.stx_fifo_dat (stx_fifo_dat ),
.rxfifo_almost_full(rxfifo_almost_full),
.master_code (8'b1001_0001 ),
.slave_addr (8'b1010_1011 ),
.sscl_out (sscl_out ),
.ssda_out (ssda_out ),
.rx_sdat (s_rx_dat ),
.srxfifo_en (srxfifo_en ),
.stx_fifo_rd_en (stx_fifo_rd_en )
);
i2c_txfifo master_txfifo (
.clk(wclk), // input wire clk
.srst(~rst_wclk_n), // input wire srst
.din(din), // input wire [7 : 0] din
.wr_en(wr_en), // input wire wr_en
.rd_en(txfifo_rd_en), // input wire rd_en
.dout(tx_data), // output wire [7 : 0] dout
.full(), // output wire full
.empty(txfifo_empty) // output wire empty
);
slave_rxfifo slave_rxfifo(
.clk(wclk), // input wire clk
.srst(~rst_wclk_n), // input wire srst
.din(s_rx_dat ), // input wire [7 : 0] din
.wr_en(srxfifo_en), // input wire wr_en
.rd_en(rd_en), // input wire rd_en
.dout(dout), // output wire [7 : 0] dout
.full(srxfifo_full), // output wire full
.almost_full(rxfifo_almost_full), // output wire almost_full
.empty(empty) // output wire empty
);
slave_txfifo slave_txfifo (
.clk(wclk), // input wire clk
.srst(~rst_wclk_n), // input wire srst
.din(sdin), // input wire [7 : 0] din
.wr_en(swr_en), // input wire wr_en
.rd_en(stx_fifo_rd_en), // input wire rd_en
.dout(stx_fifo_dat), // output wire [7 : 0] dout
.full(full), // output wire full
.empty(stxfifo_empty) // output wire empty
);
endmodule
附件1:I2C-bus specification and user manual(忘了免费下载地址了……)