文章目录
一、IC协议原理
1.I2C总线
I2C总线特点:
- 连接简单,只有SDA(串行数据线)、SCL(串行时钟线)两条串行总线;
- I2C总线是一个真正的多主机总线,所有组件之间都存在简单的主/从关系,连接到总线的每个设备均可通过唯一地址进行软件寻址,但同一时刻只允许有一个主机。
- 有三种传输速率,标准模式100Kbps、快速模式400Kbps、高速模式3.4Mbps。
一次完整的数据传输包括起始位、停止位、器件地址、读写标志、存储单元地址、读写传输数据。在主机发送数据后,从机给出ACK应答位。
起始位:SCL高电平时,SDA出现下降沿。
停止位:SCL高电平时,SDA出现上升沿。
传输数据:先写高位,后写低位。
SCL高电平时,SDA数据保持稳定;SCL低电平时,SDA数据变化。因此需要设计代码时在SCL高低电平中点读取数据、变化数据。
2.单字节数据写时序
1字节地址写时序
2字节地址写时序
3.单字节数据读时序
1字节地址读时序
2字节地址读时序
4.多字节数据读时序
二、I2C最小单元读写模块设计
1.设计思路
根据上述时序图,控制字段的7位器件地址和1位读写标志可合并为8位数据,则I2C总线SDA上的数据可归为6类:起始位、读8位数据、写8位数据、检查应答位、产生应答位ACK/NOACK、停止位。设计I2C最小单元模块,实现这6类数据组合的一个字节数据的传输,后续只需要调用该模块即可实现I2C通信。
使用状态机实现上述过程,由上层控制模块调用最小单元模块,输入对应的指令即可实现I2C读写操作。
如上图所示,I2C最小单元模块将图中红框部分划为一个单元,单元①包括起始位、写8位数据(器件地址+读标志)、检查应答位;单元②包括写8位数据(存储单元地址)、检查应答位;单元③包括写8位数据、检查应答位、停止位。控制模块中只需要告诉最小单元模块需要完成哪些操作,由状态机在具体的操作之间跳转。
2.模块端口说明
端口名称 | 方向 | 说明 |
---|---|---|
Clk | input | 系统时钟 |
Rst_n | input | 复位信号 |
Cmd [5:0] | input | I2C读写控制命令 |
Go | input | I2C读写开始信号 |
RX_DATA [7:0] | output | I2C读取数据 |
TX_DATA [7:0] | input | I2C发送数据 |
Trans_Done | output | 单次读写操作完成标志 |
Ack_o | output | 应答信号ACK |
i2c_sclk | output | I2C时钟SCLK |
i2c_sdat | inout | I2C数据线SDA |
Cmd为控制模块给出的控制命令组合,包括起始位请求STA、写请求WR、读请求RD、停止位请求STO、应答位请求ACK、无应答请求NACK,如图中的单元①,控制命令组合为STA+WR,单元②控制命令组合为WR,单元③控制命令组合为WR+STO。
Go为I2C读写开始信号,只需提供一个脉冲信号即可实现1byte数据的传输。
RX_DATA为I2C读取到的8位数据,TX_DATA为I2C写入存储单元的8位数据。
Ack_o为从机应答信号,应答信号为低电平,非应答信号为高电平。
i2c_sdat是I2C数据线SDA,为了实现输入和输出,该信号为inout。
3.代码设计
(1)时钟设置
I2C有三种传输速率,标准模式100Kbps、快速模式400Kbps、高速模式3.4Mbps,在代码中定义速度参数SCLK_CLOCK,本文选择速率为400Kbps,根据50Mhz的系统时钟SYS_CLOCK计算出SCLK分频计数器计数最大值SCL_CNT_M。由于I2C在SCLK低电平中点改变数据,高电平中点读取数据,故将其分为4份,在计算时除以4。
div_cnt_en为SCLK分频计数器使能信号,当模块接收到Go I2C读写开始信号时,div_cnt_en置1,完成最小单元1byte数据读写回到IDLE状态时,div_cnt_en清0。
//系统时钟50Mhz
parameter SYS_CLOCK = 50_000_000;
//SCL时钟 标准模式100Khz 快速模式400Khz 高速模式3.4Mhz
parameter SCL_CLOCK = 400_000;//快速模式
//计算SCLK时钟计数器最大值
localparam SCL_CNT_M = SYS_CLOCK/SCL_CLOCK/4 - 1;
reg div_cnt_en;//分频计数器使能
reg [8:0] div_cnt;
//分频计数器
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
div_cnt <= 8'd0;
else if(div_cnt_en) begin
if(div_cnt == SCL_CNT_M)
div_cnt <= 8'd0;
else
div_cnt <= div_cnt + 8'd1;
end
else
div_cnt <= div_cnt;
wire sclk_plus = div_cnt == SCL_CNT_M;//SCLK 1/4脉冲信号
sclk_plus为SCLK 1/4脉冲信号,分频计数器计数到最大值时,该信号置1,表示SCLK边沿或高低电平中点,用于在状态机中实现相应操作。sclk_plus四个为一组,在代码中用计数器cnt记录sclk_plus脉冲,脉冲和SCLK对应关系如下图所示,所有状态下的SCLK信号连接起来,构成I2C完整的SCLK时钟。在状态机中,cnt为0时是SCLK低电平中点,为2时是SCLK高电平中点,1、3为SCLK边沿。
(2)I2C SDA信号三态开漏模式代码实现
I2C总线要求SDA是开漏输出,低电平时,通过给总线赋值为0实现,高电平时总线为高阻态,通过外部上拉电阻实现高电平。
为了避免在I2C读取从机数据时,总线i2c_sda受到I2C输出信号i2c_sda_od的影响,在代码中添加i2c_sda_oe使能信号控制i2c_sda_od的输出。
i2c_sda作为输出端口,i2c_sda_oe使能打开,i2c_sda_od的值传输到i2c_sda_id和i2c_sda;i2c_sda作为输入端口,i2c_sda_oe使能关闭,i2c_sda的值传输到i2c_sda_id。SDA三态开漏模式实现代码如下:
reg i2c_sda_oe,i2c_sda_od;//输出使能,输出寄存器
assign i2c_sdat = i2c_sda_oe?(i2c_sda_od?1'bz:1'b0):1'bz;
(3)状态转换
使用状态机完成I2C最小单元模块设计,包含以下7个状态:
IDLE:空闲状态,等待单次传输需要执行的内容
GEN_STA:产生起始位
WR_DATA:写8位数据
RD_DATA:读8位数据
CHECK_ACK:检查应答位
GEN_ACK:产生ACK/NOACK
GEN_STO:产生停止位
状态转换图如下:
根据前面I2C的读写时序图,无论是读数据还是写数据,都需要先写器件地址和存储单元地址,所以发送起始位后一定是写数据,数据写入从机后,一定会产生应答信号,故跳入CHECK_ACK检查应答位。检查完应答位后,可能继续读写数据,回到空闲状态,也可能是读数据完毕发送停止位。
Cmd为I2C读写操作命令的组合,在状态机中根据对应命令跳入指定状态。命令及状态机定义如下:
localparam
STA = 6'b000001, //起始位请求
WR = 6'b000010, //写请求
RD = 6'b000100, //读请求
STO = 6'b001000, //停止位请求
ACK = 6'b010000, //应答位请求
NACK = 6'b100000; //无应答请求
reg [7:0] state;
localparam
IDLE = 8'b00000001,//空闲状态,等待单次传输需要执行的内容
GEN_STA = 8'b00000010,//产生起始位
WR_DATA = 8'b00000100,//写8位数据
RD_DATA = 8'b00001000,//读8位数据
CHECK_ACK = 8'b00010000,//检查应答位
GEN_ACK = 8'b00100000,//产生ACK/NOACK
GEN_STO = 8'b01000000;//产生停止位
(4)空闲状态
当检测到Go信号为高电平时,开始I2C数据传输,未检测到Go信号则在IDLE信号等待。
Cmd和上面定义的I2C命令按位作与运算,若为1,则跳入对应状态GEN_STA、WR_DATA、RD_DATA。
IDLE:begin
Trans_Done <= 1'b0;
div_cnt_en <= 1'b0;
i2c_sda_oe <= 1'b1;
i2c_sda_od <= 1'b1;
Ack_o <= 1'b0;
if(Go)begin
div_cnt_en <= 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
state <= IDLE;
end
(5)产生起始位
前面提到sclk_plus为SCLK 1/4脉冲信号,表示SCLK边沿或高低电平中点,cnt用于计数sclk_plus,cnt为0时是SCLK低电平中点,为2时是SCLK高电平中点。I2C起始位定义为在SCL高电平时SDA出现下降沿,根据前面的cnt和SCLK的关系图,cnt为0、1、2时SCLK为高电平,cnt为3时SCLK为低电平。cnt为1时,将SDA输出使能i2c_sda_oe打开,i2c_sda_od输出高电平。cnt为2时,将i2c_sda_od拉低,输出低电平。cnt计满四个数跳入下一状态。
GEN_STA:begin
if(sclk_plus)begin
case(cnt)
0:begin i2c_sda_oe <= 1'b1;i2c_sda_od <= 1'b1; end
1:i2c_sclk <= 1'b1;
2:begin i2c_sda_od <= 1'b0;i2c_sclk <= 1'b1; end
3:begin i2c_sda_od <= 1'b1;i2c_sclk <= 1'b0; end
default:begin i2c_sda_od <= 1'b1;i2c_sclk <= 1'b1; end
endcase
if(cnt == 5'd3)begin
cnt <= 5'd0;
state <= WR_DATA;//起始位结束后一定会写7位地址位
end
else
cnt <= cnt + 1'b1;
end
end
(6)写8位数据
8位数据需要传输8个SCLK时钟,则sclk_plus脉冲计数器cnt计数32次。在SCLK低电平中点改变数据,即cnt为0、4、8、12……时将TX_DATA放入i2c_sda_od。
WR_DATA:begin
if(sclk_plus)begin
case(cnt)
0,4,8 ,12,16,20,24,28:begin i2c_sda_oe <= 1'b1;i2c_sda_od <= TX_DATA[7-cnt[4:2]];end
1,5,9 ,13,17,21,25,29:i2c_sclk <= 1'b1;
2,6,10,14,18,22,26,30:i2c_sclk <= 1'b1;
3,7,11,15,19,23,27,31:i2c_sclk <= 1'b0;
default:begin i2c_sda_od <= 1'b1;i2c_sclk <= 1'b1; end
endcase
if(cnt == 5'd31)begin
cnt <= 5'd0;
state <= CHECK_ACK;//写数据结束后检查应答位
end
else
cnt <= cnt +1'b1;
end
end
(7)读8位数据
读数据和写数据类似,sclk_plus脉冲计数器cnt计数32次。在高电平中点2、6、10、14……读取数据,采用移位的方法,每次将i2c_sdat放入RX_DATA最低位。
RD_DATA:begin
if(sclk_plus)begin
case(cnt)
0,4,8 ,12,16,20,24,28:begin i2c_sda_oe <= 1'b0; i2c_sclk <= 1'b0;end
1,5,9 ,13,17,21,25,29:i2c_sclk <= 1'b1;
2,6,10,14,18,22,26,30:begin RX_DATA <= {RX_DATA[6:0],i2c_sdat}; i2c_sclk <= 1'b1;end
3,7,11,15,19,23,27,31:i2c_sclk <= 1'b0;
default:begin i2c_sda_od <= 1'b1;i2c_sclk <= 1'b1; end
endcase
if(cnt == 5'd31)begin
cnt <= 5'd0;
state <= GEN_ACK;//读数据结束后产生应答位
end
else
cnt <= cnt +1'b1;
end
end
(8)检查应答位
写8数据后检查从机返回来的应答位。cnt为0时关闭SDA输出使能,在SCLK高电平中点cnt为2时,读取i2c_sdat数据存入Ack_o。若下一状态为发送停止位STO,则跳入下一状态;否则本次I2C最小单元全部发送完毕,跳回IDLE状态等待下一命令,并将单次读写操作完成标志Trans_Done置1。
CHECK_ACK:begin
if(sclk_plus)begin
case(cnt)
0:begin i2c_sclk <= 1'b0; i2c_sda_oe <= 1'b0;end//关闭SDA输出使能
1:i2c_sclk <= 1'b1;
2:begin Ack_o <= i2c_sdat;i2c_sclk <= 1'b1; end
3:i2c_sclk <= 1'b0;
default:begin i2c_sda_od <= 1'b1;i2c_sclk <= 1'b1; end
endcase
if(cnt == 5'd3)begin
cnt <= 5'd0;
if(Cmd & STO)
state <= GEN_STO;
else begin
state <= IDLE;
Trans_Done <= 1'b1;
end
end
else
cnt <= cnt + 1'b1;
end
end
(9)产生ACK/NOACK
在读多字节数据的时序图中,单次读完8位数据后,向从机发送ACK低电平表示继续读数据,发送NOACK高电平表示不再读数据。在cnt为0时,打开SDA输出使能,在i2c_sda_od产生应答位和非应答位。在本状态判断是否需要发送停止位,若需要发送,则跳入GEN_STO,否则返回IDLE状态,并将单次读写操作完成标志Trans_Done置1。
GEN_ACK:begin
if(sclk_plus)begin
case(cnt)
0:begin
i2c_sda_oe <= 1'b1;
if(Cmd & ACK)
i2c_sda_od <= 1'b0;//产生应答位
else if(Cmd & NACK)
i2c_sda_od <= 1'b1;//产生非应答位
else i2c_sda_od <= 1'b1;
i2c_sclk <= 1'b0;
end
1:i2c_sclk <= 1'b1;
2:i2c_sclk <= 1'b1;
3:i2c_sclk <= 1'b0;
default:begin i2c_sda_od <= 1'b1;i2c_sclk <= 1'b1; end
endcase
if(cnt == 5'd3)begin
cnt <= 5'd0;
if(Cmd & STO)
state <= GEN_STO;
else begin
state <= IDLE;
Trans_Done <= 1'b1;
end
end
else
cnt <= cnt + 1'b1;
end
end
(10)产生停止位
停止位是在SCL高电平时SDA出现上升沿。和起始位类似,cnt为0时SCLK为低电平,cnt为1、2、3时SCLK为高电平。cnt为1时,将SDA输出使能i2c_sda_oe打开,i2c_sda_od输出低电平。cnt为2时,将i2c_sda_od拉低,输出高电平。
GEN_STO:begin
if(sclk_plus)begin
case(cnt)
0:begin i2c_sda_oe <= 1'b1;i2c_sda_od <= 1'b0; end
1:i2c_sclk <= 1'b1;
2:begin i2c_sda_od <= 1'b1;i2c_sclk <= 1'b1; end
3:i2c_sclk <= 1'b1;//空闲状态时SCLK为高电平
default:begin i2c_sda_od <= 1'b1;i2c_sclk <= 1'b1; end
endcase
if(cnt == 5'd3)begin
cnt <= 5'd0;
Trans_Done <= 1'b1;//单次读写操作完成
state <= IDLE;//停止位结束后回到空闲状态
end
else
cnt <= cnt + 1'b1;
end
end
I2C最小单元发送模块源代码
module i2c_bit_shift(
input Clk, //系统时钟
input Rst_n, //复位信号
input [5:0] Cmd, //I2C读写控制命令
input Go, //I2C读写开始信号
output reg [7:0] RX_DATA, //I2C读取数据
input [7:0] TX_DATA, //I2C发送数据
output reg Trans_Done, //单次读写操作完成标志
output reg Ack_o, //应答信号ACK
output reg i2c_sclk, //I2C时钟SCLK
inout i2c_sdat //I2C数据线SDA
);
//系统时钟50Mhz
parameter SYS_CLOCK = 50_000_000;
//SCL时钟 标准模式100Khz 快速模式400Khz 高速模式3.4Mhz
parameter SCL_CLOCK = 400_000;//快速模式
//计算SCLK时钟计数器最大值
//由于在低电平中点改变数据,高电平中点读取数据,故将其分为4份
localparam SCL_CNT_M = SYS_CLOCK/SCL_CLOCK/4 - 1;
reg i2c_sda_oe,i2c_sda_od;//输出使能,输出寄存器
assign i2c_sdat = i2c_sda_oe?(i2c_sda_od?1'bz:1'b0):1'bz;
localparam
STA = 6'b000001, //起始位请求
WR = 6'b000010, //写请求
RD = 6'b000100, //读请求
STO = 6'b001000, //停止位请求
ACK = 6'b010000, //应答位请求
NACK = 6'b100000; //无应答请求
reg [7:0] state;
localparam
IDLE = 8'b00000001,//空闲状态,等待单次传输需要执行的内容
GEN_STA = 8'b00000010,//产生起始位
WR_DATA = 8'b00000100,//写8位数据
RD_DATA = 8'b00001000,//读8位数据
CHECK_ACK = 8'b00010000,//检查应答位
GEN_ACK = 8'b00100000,//产生ACK/NOACK
GEN_STO = 8'b01000000;//产生停止位
reg div_cnt_en;//分频计数器使能
reg [8:0] div_cnt;
//分频计数器
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
div_cnt <= 8'd0;
else if(div_cnt_en) begin
if(div_cnt == SCL_CNT_M)
div_cnt <= 8'd0;
else
div_cnt <= div_cnt + 8'd1;
end
else
div_cnt <= div_cnt;
wire sclk_plus = div_cnt == SCL_CNT_M;//SCLK 1/4脉冲信号
reg [4:0] cnt;// 1/4 SCLK计数器
//I2C单次传输状态机
always@(posedge Clk or negedge Rst_n)
if(!Rst_n) begin
Trans_Done <= 1'b0;
Ack_o <= 1'b0; //应答信号为低电平,非应答信号为高电平
i2c_sclk <= 1'b1; //空闲状态时SCLK为高电平
RX_DATA <= 8'd0;
div_cnt_en <= 1'b0;
i2c_sda_oe <= 1'b1;
i2c_sda_od <= 1'b1; //空闲状态时SDA为高电平
cnt <= 5'd0;
state <= IDLE;
end
else begin
case(state)
IDLE:begin
Trans_Done <= 1'b0;
div_cnt_en <= 1'b0;
i2c_sda_oe <= 1'b1;
i2c_sda_od <= 1'b1;
Ack_o <= 1'b0;
if(Go)begin
div_cnt_en <= 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
state <= IDLE;
end
GEN_STA:begin
if(sclk_plus)begin
case(cnt)
0:begin i2c_sda_oe <= 1'b1;i2c_sda_od <= 1'b1; end
1:i2c_sclk <= 1'b1;
2:begin i2c_sda_od <= 1'b0;i2c_sclk <= 1'b1; end
3:begin i2c_sda_od <= 1'b1;i2c_sclk <= 1'b0; end
default:begin i2c_sda_od <= 1'b1;i2c_sclk <= 1'b1; end
endcase
if(cnt == 5'd3)begin
cnt <= 5'd0;
state <= WR_DATA;//起始位结束后一定会写7位地址位
end
else
cnt <= cnt + 1'b1;
end
end
WR_DATA:begin
if(sclk_plus)begin
case(cnt)
0,4,8 ,12,16,20,24,28:begin i2c_sda_oe <= 1'b1;i2c_sda_od <= TX_DATA[7-cnt[4:2]];end
1,5,9 ,13,17,21,25,29:i2c_sclk <= 1'b1;
2,6,10,14,18,22,26,30:i2c_sclk <= 1'b1;
3,7,11,15,19,23,27,31:i2c_sclk <= 1'b0;
default:begin i2c_sda_od <= 1'b1;i2c_sclk <= 1'b1; end
endcase
if(cnt == 5'd31)begin
cnt <= 5'd0;
state <= CHECK_ACK;//写数据结束后检查应答位
end
else
cnt <= cnt +1'b1;
end
end
RD_DATA:begin
if(sclk_plus)begin
case(cnt)
0,4,8 ,12,16,20,24,28:begin i2c_sda_oe <= 1'b0; i2c_sclk <= 1'b0;end
1,5,9 ,13,17,21,25,29:i2c_sclk <= 1'b1;
2,6,10,14,18,22,26,30:begin RX_DATA <= {RX_DATA[6:0],i2c_sdat}; i2c_sclk <= 1'b1;end
3,7,11,15,19,23,27,31:i2c_sclk <= 1'b0;
default:begin i2c_sda_od <= 1'b1;i2c_sclk <= 1'b1; end
endcase
if(cnt == 5'd31)begin
cnt <= 5'd0;
state <= GEN_ACK;//读数据结束后产生应答位
end
else
cnt <= cnt +1'b1;
end
end
CHECK_ACK:begin
if(sclk_plus)begin
case(cnt)
0:begin i2c_sclk <= 1'b0; i2c_sda_oe <= 1'b0;end//关闭SDA输出使能
1:i2c_sclk <= 1'b1;
2:begin Ack_o <= i2c_sdat;i2c_sclk <= 1'b1; end
3:i2c_sclk <= 1'b0;
default:begin i2c_sda_od <= 1'b1;i2c_sclk <= 1'b1; end
endcase
if(cnt == 5'd3)begin
cnt <= 5'd0;
if(Cmd & STO)
state <= GEN_STO;
else begin
state <= IDLE;
Trans_Done <= 1'b1;
end
end
else
cnt <= cnt + 1'b1;
end
end
GEN_ACK:begin
if(sclk_plus)begin
case(cnt)
0:begin
i2c_sda_oe <= 1'b1;
if(Cmd & ACK)
i2c_sda_od <= 1'b0;//产生应答位
else if(Cmd & NACK)
i2c_sda_od <= 1'b1;//产生非应答位
else i2c_sda_od <= 1'b1;
i2c_sclk <= 1'b0;
end
1:i2c_sclk <= 1'b1;
2:i2c_sclk <= 1'b1;
3:i2c_sclk <= 1'b0;
default:begin i2c_sda_od <= 1'b1;i2c_sclk <= 1'b1; end
endcase
if(cnt == 5'd3)begin
cnt <= 5'd0;
if(Cmd & STO)
state <= GEN_STO;
else begin
state <= IDLE;
Trans_Done <= 1'b1;
end
end
else
cnt <= cnt + 1'b1;
end
end
GEN_STO:begin
if(sclk_plus)begin
case(cnt)
0:begin i2c_sda_oe <= 1'b1;i2c_sda_od <= 1'b0; end
1:i2c_sclk <= 1'b1;
2:begin i2c_sda_od <= 1'b1;i2c_sclk <= 1'b1; end
3:i2c_sclk <= 1'b1;//空闲状态时SCLK为高电平
default:begin i2c_sda_od <= 1'b1;i2c_sclk <= 1'b1; end
endcase
if(cnt == 5'd3)begin
cnt <= 5'd0;
Trans_Done <= 1'b1;//单次读写操作完成
state <= IDLE;//停止位结束后回到空闲状态
end
else
cnt <= cnt + 1'b1;
end
end
default:state <= IDLE;
endcase
end
endmodule
三、I2C最小单元发送模块仿真
1.将单次I2C读写代码封装
为了简化仿真文件,保持其简洁性和可读性,将单次读写操作封装成task任务I2C_WR和I2C_RD,在仿真时只需要将Cmd和待发送数据TX_DATA传入任务即可。
task I2C_WR;
input [5:0] cmd;
input [7:0] wr_DATA;
begin
Cmd = cmd;
TX_DATA = wr_DATA;
Go = 1'b1;#20;
Go = 1'b0;#20;
wait(Trans_Done);
#200;
end
endtask
task I2C_RD;
input [5:0] cmd;
begin
Cmd = cmd;
Go = 1'b1;#20;
Go = 1'b0;#20;
wait(Trans_Done);
#200;
end
endtask
2.仿真文件
采用EEPROM仿真模型M24LC04B对本模块进行仿真。将数据8’h76写入存储单元地址8’h3B中。
`timescale 1ns/1ns
module i2c_bit_shift_tb();
reg Clk,Rst_n,Go;
reg [5:0] Cmd;
reg [7:0] TX_DATA;
wire [7:0] RX_DATA;
wire Trans_Done;
wire Ack_o;
wire i2c_sclk;
wire i2c_sdat;
pullup(i2c_sdat); //模拟外部上拉电阻
localparam
STA = 6'b000001, //起始位请求
WR = 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), //I2C读写控制命令
.Go(Go), //I2C读写开始信号
.RX_DATA(RX_DATA), //I2C读取数据
.TX_DATA(TX_DATA), //I2C发送数据
.Trans_Done(Trans_Done),//单次读写操作完成标志
.Ack_o(Ack_o), //应答信号ACK
.i2c_sclk(i2c_sclk), //I2C时钟SCLK
.i2c_sdat(i2c_sdat) //I2C数据线SDA
);
M24LC04B M24LC04B(
.A0(0),
.A1(0),
.A2(0),
.WP(0),//写保护引脚,高电平不允许写,低电平允许写
.SDA(i2c_sdat),
.SCL(i2c_sclk),
.RESET(~Rst_n)
);
initial Clk = 1'b1;
always #10 Clk = ~Clk;
initial begin
Rst_n = 1'b0;
TX_DATA = 8'd0;
Go = 1'b0;
Cmd = 6'd0;
#201;
Rst_n = 1'b1;
#20;
//写数据步骤:起始位->发送器件地址->ACK->
//发送存储单元地址->ACK->写入DATA->ACK->停止位
I2C_WR(STA|WR,8'hA0&8'hfe);//写数据,发送器件地址
I2C_WR(WR,8'h3B);//发送存储单元地址
I2C_WR(WR|STO,8'h76);//发送存储数据
#20000;
I2C_WR(STA|WR,8'hA0&8'hfe);//写数据,发送器件地址
I2C_WR(WR,8'h3B);//发送存储单元地址
I2C_WR(STA|WR,8'hA0|8'd1);//读数据,发送器件地址
I2C_RD(RD|NACK|STO);//读取存储数据
#20000;
$stop;
end
task I2C_WR;
input [5:0] cmd;
input [7:0] wr_DATA;
begin
Cmd = cmd;
TX_DATA = wr_DATA;
Go = 1'b1;#20;
Go = 1'b0;#20;
wait(Trans_Done);
#200;
end
endtask
task I2C_RD;
input [5:0] cmd;
begin
Cmd = cmd;
Go = 1'b1;#20;
Go = 1'b0;#20;
wait(Trans_Done);
#200;
end
endtask
endmodule
3.仿真结果
从仿真结果可以看出,写入EEPROM存储单元8’h3B的数据,和从中读取的数据一致,均为8’h76。