一、IIC协议
1、初识IIC
- IIC即(Inter-Integrated Circuit)【集成电路总线】,是一种多向控制总线,由飞利浦【PHILIPS】半导体公司在八十年代设计,主要用于连接整体电路【ICS】
- 在IIC中,多个芯片可以连接到同一总线结构下,同时每个芯片都可以作为实施数据传输的控制源,简化了信号传输总线;
- 是一种简单、双向、二线制、同步串行总线;
- 一个主机可以同时挂载多个从机;
- 连接在总线上的设备都是通过唯一的地址和其他器件通信,主机和从机角色可以互换;
- 适用于近距离低速芯片间通信。
2、协议详解
1、IIC概述
IIC:两线式串行总线,它是由数据线【SDA】和时钟线【SCL】构成的串行总线,可发送和接受数据
在CPU与被控IC之间,IC与IC之间进行双向传送,高速IIC总线一般可达【400kbs】以上。
- 串行数据时钟【SCL】:在通信过程中起到控制作用;
- 串行数据线【SDA】:用来一位一位的传送数据;
2、IIC分类
硬件IIC:一块硬件电路,硬件I2C对应芯片上的I2C外设,有相应I2C驱动电路,其所使用的I2C管脚也是专用的,硬件(固件)I2C是直接调用内部寄存器进行配置。
软件IIC:软件IIC:软件IIC通信指的是用单片机的两个I/O端口模拟出来的IIC,用软件控制管脚状态以模拟I2C通信波形,软件模拟寄存器的工作方式。
1.硬件I2C的效率要远高于软件的,而软件I2C由于不受管脚限制,接口比较灵活。
2.IIC是半双工通信方式
3、外设挂载
挂载数量:IIC地址决定,8位地址,减去1位广播地址,是7位地址,2^7=128,但是地址0x00不用,那就是127个地址, 所以理论上可以挂127个从器件
IIC总线是一种【多主机总线】,连接在IIC总线上的器件分为主机和从机,主机有权发起和结束依次通信,而从机只能被主机呼叫;当总线上有多个主机同时启用总线时,IIC也具备冲突检测和仲裁的功能来防止错误发生;每个连接到IIC总线的器件都有一个【唯一的地址(7bit)】,且每个器件都可以作为从机【同一时刻只能有一个主机】,总线上的器件可以删除和增加不影响其他器件正常工作;IIC总线在通信时总线上发送数据的器件叫做发送器,接受数据的器件叫接收器
4、时序详解
IIC通信过程由开始、结束、发送、响应、接受五个部分构成;
- 【在发送、接受数据时】当SCL为高电平时,SDA线不允许变化;当SCL线为低电平时,SDA可以任意0,1变化;
- 【在任意时候】只有当SCL为高电平时,IIC电路才对SDA线上的电平(0或者1)进行记录;当SCL线为低电平时,无论SDA是高或者低,IIC都不对SDA进行采样。
- SDA和SCL同时处于高电平时,规定为总线的空闲状态。此时各个器件的输出场级效管均处于截止状态,即释放总线,由上拉电阻拉高电平。
数据有效传输在SCL信号的高电平期间,SDA数据线保持稳定;在SCL为低电平期间,允许SDA数据线变化。
开始信号
起始信号:当SCL为高期间,SDA由高到低的跳变;【SDA出现下降沿】属于电平跳变时序信号,而不是电平
结束信号
结束信号:当SCL为高电平期间,SDA由低到高的跳变,【SDA出现上升沿】属于电平跳变时序信号,而不是电平信号
应答信号
发送器每发送一个字节,就在时钟脉冲9期间释放数据线,由接收器反馈一个应答信号。【应答信号为低电平时,规定为有效应答位(ACK简称应答位)】应答信号为高电平时,规定为非应答位【NACK】一般表示接收器接受该字节没有成功。
对于反馈有效应答ACK的要求是,接收器在第九个时钟脉冲之前的低电平期间将SDA线拉低,并且确保在该时钟的高电平期间稳定的低电平。如果接收器是主控器,则在它接受到最后一个字节后,发送一个NACK信号,以通知被控发送器结束数据发送,并释放SDA线,以便主控接收器发送一个停止信号。【低电平0表示应答,1表示非应答】
IIC传输时时从MSB开始传输到LSB结束。MSB是Most Significant Bit的缩写,最高有效位。在二进制数中,MSB是最高加权位。与十进制数字中最左边的一位类似。通常,MSB位于二进制数的最左侧,LSB位于二进制数的最右侧。LSB,英文 least significant bit,中文义最低有效位。
IIC写时序
时序图:
发送器件地址:主机给地址总线上发送数据,如果地址匹配,从机给出应答,建立通讯。IIC协议一次只能和一个设备/器件进行通讯。
①产生start位;
②传送器件地址,器件地址的最后一位数据为数据的传输方向位【R/W】,低电平0表示主机往从机写数据【W】,1表示主机从从机读数据【R】。ACK应答,应答是从机发送给主机的应答(写时序不管)
③传送写入器件寄存地址,即数据要写入的位置;
④传送要写入的数据
⑤产生stop信号
IIC读时序
时序图:
①产生start信号;
②传送器件地址,ACK
③传送字地址,ACK
④再次产生start信号;
⑤再次传送器件地址,ACK
⑥读取一个字节数据,读数据最后结束前无应答信号;
⑦产生stop信号
dummy write;指在读数据的时候,先写入写控制字与字节地址,这一段表示向设备写入要读的数据的地址,而不是真的向地址写数据,这个地址会被锁存起来,而不会触发写操作。
IIC读写时序补充
双字节地址写时序:写入器件地址,写入寄存器地址高位、低位,然后写数据
多数据写时序:写入器件地址,写入器件地址,连续写入时序
双字节地址多数据写时序:写入器件地址,写入器件地址高位、低位,然后连续写入数据
双字节地址读时序:写入器件地址,写入寄存器地址高位、低位,然后再次写入器件地址,写入数据。读数据最后结束前无应答ACK信号
多数据读时序:写入器件地址,写入寄存器地址,然后再次写入器件地址,连续写入数据,读数据最后结束前无应答ACK信号
双字节地址多数据读时序:写入器件地址,写入寄存器地址,然后再次写入器件地址,连续写入数据。读数据最后结束前无应答ACK信号。
5、数据收发
- 在I2C总线上传送的每位数据都有一个时钟脉冲相对应(或同步控制),即在SCL串行时钟的配合下,SDA逐位地串行传送每一位数据。数据位的传输是边沿触发。
- 发送数据是一位一位发送,接收数据也是一位一位接收进来,最后返回应答信号.
- IIC 传输时,按照从高到低的位序进行传输。控制字节的最低位为读写控制位,当该位为 0 时表示主机对从机进行写操作,当该位为 1 时表示主机对从机进行读操作。
二、EEPROM
1、EEPROM概述
- EEPROM
(Electrically Erasable Programmable read only memory)
是指带电可擦可编程只读存储器。是一种掉电后数据不丢失的存储芯片。 EEPROM 可以在电脑上或专用设备上擦除已有信息,重新编程。一般用在即插即用。 - EEPROM是一种特殊形式的闪存,其应用通常是个人电脑中的电压来擦写和重编程。
2、特点
- EEPROM,一般用于即插即用【Plug&Play】;
- 常用在接口卡中,用来存放硬件设置数据。
- 也常用在防止软件非法拷贝的“硬件锁”上面。
3、基本原理
- 由于EPROM操作的不便,后来出的主板上BIOS ROM芯片大部分都采用EEPROM(Electrically Erasable Programmable ROM,电可擦除可编程ROM)。
- EEPROM的擦除不需要借助于其它设备,它是以电子信号来修改其内容的,而且是以Byte为最小修改单位,不必将资料全部洗掉才能写入,彻底摆脱了EPROM Eraser和编程器的束缚。
- EEPROM在写入数据时,仍要利用一定的编程电压,此时,只需用厂商提供的专用刷新程序就可以轻而易举地改写内容,所以,它属于双电压芯片。
- 借助于EEPROM芯片的双电压特性,可以使BIOS具有良好的防毒功能,在升级时,把跳线开关打至“on”的位置,即给芯片加上相应的编程电压,就可以方便地升级;平时使用时,则把跳线开关打至“off”的位置,防止CIH类的病毒对BIOS芯片的非法修改。
- 所以,仍有不少主板采用EEPROM作为BIOS芯片并作为自己主板的一大特色。
4、读写时序
总线时序数据
总线计时开始/停止
串行总线上的数据传输序列
控制字节分配
Operation | Control Code | Block Select | R/W_N |
---|---|---|---|
Read | 1010 | Block Address | 1 |
Write | 1010 | Block Address | 0 |
字节写
页写
当前地址读
随机读
顺序读
三、项目概述
1、功能简述
本次实验是基于I2C协议的EEPROM的读写实验。
本实验实现了上位机通过串口发送命令帧和数据帧给FPGA,FPGA对命令和数据帧解析之后,对EEPROM进行读操作和写操作,并将从EEPROM读取数据通过串口返回给上位机。
各个模块实现功能如下:
- 串口发送模块【uart_tx】:将从EEPROM读取的数据通过串口返回给上位机;
- 串口接收模块【uart_rx】:接收上位机发送的字节,给到命令解析模块;
- 命令解析模块【cmd_analy】:对串口接收到的字节进行解析,提取出数据和读写请求;将数据利用FIFO【wr_fifo】进行缓存;或者将读写请求给到EEPROM控制模块;
- I2C接口模块【i2c_interface】:根据接收到的读写请求、命令码,在FPGA和EEPROM之间进行通信【传输数据】;
- EEPROM控制模块【eeprom_control】:根据接收到的读写请求,向i2c接口发起读写传输请求、命令码和数据。
2、系统框图
3、状态机设计
I2C接口模块状态转移图
状态参数说明:
- IDLE:初始状态
- START:起始信号
- READ:读数据
- WRITE:写数据
- SEND_ACK:发送应答
- RECV_ACK:接收应答
- STOP:停止信号
4、源码
1、顶层模块(i2c_eeprom.v)
//顶层模块
module i2c_eeprom (
input clk ,
input rst_n ,
input uart_rxd,
output uart_txd,
output i2c_scl ,
inout i2c_sda
);
//信号定义
wire [7:0] rx_data ;
wire rx_data_vld ;
wire [7:0] wr_data ;
wire wr_data_vld ;
wire wr_en ;
wire rd_en ;
wire [8:0] rw_addr ;
wire req ;
wire [3:0] cmd ;
wire [7:0] data ;
wire wr_fail ;
wire [7:0] rd_dout ;
wire rw_done ;
wire sda_in ;
wire sda_out ;
wire sda_out_en ;
wire rdy ;
wire [7:0] tx_data ;
wire tx_data_vld ;
assign i2c_sda = sda_out_en?sda_out:1'bz;
assign sda_in = i2c_sda;
//模块例化
uart_rx u_uart_rx(
.clk (clk ),
.rst_n (rst_n ),
.din (uart_rxd ),
.dout (rx_data ),
.dout_vld (rx_data_vld )
);
cmd_analy u_cmd_analy( //命令帧解析模块
.clk (clk ),
.rst_n (rst_n ),
.din (rx_data ),
.din_vld (rx_data_vld ),
.dout (wr_data ),//写入FIFO的数据
.dout_vld (wr_data_vld ),
.wr_en (wr_en ),//写请求
.rd_en (rd_en ),//读请求
.addr (rw_addr ) //读写地址
);
eeprom_control u_eeprom_control(
.clk (clk ),
.rst_n (rst_n ),
//user interface
.wr_req (wr_en ),
.rd_req (rd_en ),
.wr_din (wr_data ),
.wr_din_vld (wr_data_vld ),
.rw_addr (rw_addr ),
.rdy (rdy ),
.tx_data (tx_data ),
.tx_data_vld(tx_data_vld ),
//i2c interface
.req (req ),
.cmd (cmd ),
.data (data ),
.wr_fail (wr_fail ),
.rd_din (rd_dout ),
.rw_done (rw_done )
);
i2c_interface u_i2c_interface( //仅仅实现I2C协议时序
.clk (clk ),//50MHz
.rst_n (rst_n ),
.req (req ),//传输使能
.cmd (cmd ),//传输命令码
.wr_din (data ),//需要传输的数据
.sda_in (sda_in ),
.sda_out (sda_out ),
.sda_out_en (sda_out_en ),
.scl (i2c_scl ),
.wr_fail (wr_fail ),//从机未应答
.rd_dout (rd_dout ),//从slave读取的1字节数据
.rw_done (rw_done ) //1个字节读写完成
);
uart_tx u_uart_tx(
.clk (clk ),
.rst_n (rst_n ),
.din (tx_data ),
.din_vld (tx_data_vld ),
.rdy (rdy ),//ready 表示串口发送模块可以接收数据并发送
.dout (uart_txd )
);
endmodule
2、串口接收模块(uart_rx.v)
//串口接收模块
`include "parm.v"
module uart_rx(
input clk ,
input rst_n ,
//串口
input din ,
output reg[7:0] dout ,
output reg dout_vld
);
//信号定义
reg [12:0] cnt_bps ;//波特率计数器
wire add_cnt_bps ;
wire end_cnt_bps ;
reg [3:0] cnt_bit ;//数据传输bit计数器
wire add_cnt_bit ;
wire end_cnt_bit ;
reg din_r0 ;//同步串口输入
reg din_r1 ;//打拍
wire nedge ;//下降沿
reg rx_flag ;//接收数据标志信号
reg [9:0] rx_data ;//接收数据寄存
//计数器设计 cnt_bps cnt_bit
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
cnt_bps <= 0;
end
else if(add_cnt_bps)begin
if(end_cnt_bps)begin
cnt_bps <= 0;
end
else begin
cnt_bps <= cnt_bps + 1;
end
end
end
assign add_cnt_bps = rx_flag;
assign end_cnt_bps = add_cnt_bps && cnt_bps == `BAUD-1;
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
cnt_bit <= 0;
end
else if(add_cnt_bit)begin
if(end_cnt_bit)begin
cnt_bit <= 0;
end
else begin
cnt_bit <= cnt_bit + 1;
end
end
end
assign add_cnt_bit = end_cnt_bps;
assign end_cnt_bit = add_cnt_bit && cnt_bit == 10-1;
//串口接收数据标志 rx_flag
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
rx_flag <= 1'b0;
end
else if(nedge)begin
rx_flag <= 1'b1;//出现下降沿 开始接收数据
end
else if(end_cnt_bit)begin
rx_flag <= 1'b0;//bit计数结束 结束数据接收
end
end
//同步打拍 检查下降沿
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
din_r0 <= 1'b1;
din_r1 <= 1'b1;
end
else begin
din_r0 <= din;
din_r1 <= din_r0;
end
end
assign nedge = ~din_r0 & din_r1;
//熟记接收 rx_data
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
rx_data <= 0;
end
else if(add_cnt_bps && cnt_bps == (`BAUD>>1))begin
rx_data[cnt_bit] <= din;
/*
rx_data <= {din,rx_data[10:1]};
*/
end
end
//dout
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
dout <= 0;
end
else begin
dout <= rx_data[8:1];
end
end
//dout_vld
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
dout_vld <= 0;
end
else begin
dout_vld <= end_cnt_bit;
end
end
endmodule
3、命令解析模块(cmd_analy.v)
//命令解析模块
module cmd_analy(
input clk ,
input rst_n ,
input [7:0] din ,//串口接收模块接口
input din_vld ,
output reg[7:0] dout ,
output reg dout_vld ,
output reg wr_en ,//写请求
output reg rd_en ,//读请求
output reg[8:0] addr
);
//信号定义
localparam IDLE = 9'b0_0000_0001,
FRAME_HEAD = 9'b0_0000_0010,//接收起始符
WRDATA_INST = 9'b0_0000_0100,//接收写指示
WR_DATA = 9'b0_0000_1000,//接收写数据
FRAME_END = 9'b0_0001_0000,//接收帧结束符
WR_REQ = 9'b0_0010_0000,//接收写请求
WR_ADDR = 9'b0_0100_0000,//接收写地址
RD_REQ = 9'b0_1000_0000,//接收读请求
RD_ADDR = 9'b1_0000_0000;//接收读地址
localparam SOF = 4'b0001,//start of frame
WR_INST = 4'b0010,//写指示
EOF0 = 4'b0100,//end of frame
EOF1 = 4'b1000;//end of frame
//信号定义
reg [8:0] state_c ;
reg [8:0] state_n ;
reg [7:0] din_r0 ;//同步
reg [7:0] din_r1 ;//打拍
wire idle2frame_head ;
wire frame_head2wrdata_inst ;
wire frame_head2wr_req ;
wire frame_head2rd_req ;
wire wrdara_inst2wr_data ;
wire wr_data2frame_end ;
wire frame_end2idle ;
wire wr_req2wr_addr ;
wire wr_addr2frame_end ;
wire rd_req2rd_addr ;
wire rd_addr2frame_end ;
//状态机 序列检测
//第一段
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
state_c <= IDLE;
end
else begin
state_c <= state_n;
end
end
//第二段
always @(*)begin
case (state_c)
IDLE :begin
if(idle2frame_head)
state_n = FRAME_HEAD;
else
state_n = state_c;
end
FRAME_HEAD :begin
if(frame_head2wrdata_inst)
state_n = WRDATA_INST;
else if(frame_head2wr_req)
state_n = WR_REQ;
else if(frame_head2rd_req)
state_n = RD_REQ;
else
state_n = state_c;
end
WRDATA_INST:begin
if(wrdara_inst2wr_data)
state_n = WR_DATA;
else
state_n = state_c;
end
WR_DATA :begin
if(wr_data2frame_end)
state_n = FRAME_END;
else
state_n = state_c;
end
FRAME_END :begin
if(frame_end2idle)
state_n = IDLE;
else
state_n = state_c;
end
WR_REQ :begin
if(wr_req2wr_addr)
state_n = WR_ADDR;
else
state_n = state_c;
end
WR_ADDR :begin
if(wr_addr2frame_end)
state_n = FRAME_END;
else
state_n = state_c;
end
RD_REQ :begin
if(rd_req2rd_addr)
state_n = RD_ADDR;
else
state_n = state_c;
end
RD_ADDR :begin
if(rd_addr2frame_end)
state_n = FRAME_END;
else
state_n = state_c;
end
default:state_n = IDLE;
endcase
end
assign idle2frame_head = state_c == IDLE && (din_vld && din == SOF);
assign frame_head2wrdata_inst = state_c == FRAME_HEAD && (din_vld && din == WR_INST);
assign frame_head2wr_req = state_c == FRAME_HEAD && (din_vld && din[4] == 1'b0);
assign frame_head2rd_req = state_c == FRAME_HEAD && (din_vld && din[4] == 1'b1);
assign wrdara_inst2wr_data = state_c == WRDATA_INST && (din_vld);
assign wr_data2frame_end = state_c == WR_DATA && (din == EOF1 && din_r0 == EOF0);
assign frame_end2idle = state_c == FRAME_END && (din_r0 == EOF1 && din_r1 == EOF0);
assign wr_req2wr_addr = state_c == WR_REQ && (1'b1);
assign wr_addr2frame_end = state_c == WR_ADDR && (din_vld);
assign rd_req2rd_addr = state_c == RD_REQ && (1'b1);
assign rd_addr2frame_end = state_c == RD_ADDR && (din_vld);
//同步打拍
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
din_r0 <= 0;
din_r1 <= 0;
end
else begin
din_r0 <= din;
din_r1 <= din_r0;
end
end
//dout
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
dout <= 0;
end
else if(state_c == WR_DATA && din_vld && din != EOF1 && din_r0 != EOF0)begin
dout <= din_r0;
end
end
//dout_vld
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
dout_vld <= 0;
end
else begin
dout_vld <= state_c == WR_DATA && din_vld && din != EOF1 && din_r0 != EOF0;
end
end
//wr_en 写请求
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
wr_en <= 1'b0;
end
else if(wr_addr2frame_end)begin
wr_en <= 1'b1;//写地址到一帧数据结束
end
else begin
wr_en <= 1'b0;
end
end
//rd_en 读请求
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
rd_en <= 1'b0;
end
else if(rd_addr2frame_end)begin
rd_en <= 1'b1;//读地址到一帧数据结束
end
else begin
rd_en <= 1'b0;
end
end
//读写地址 addr
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
addr <= 0;
end
else if(wr_addr2frame_end | rd_addr2frame_end)begin
addr <= {
din_r0[0],din};
end
end
endmodule
4、EEPROM控制模块(eeprom_control.v)
//EEPROM控制模块
`include "parm.v"
module eeprom_control(
input clk ,
input rst_n ,
//用户接口
input wr_req ,
input rd_req ,
input [7:0] wr_din ,
input wr_din_vld ,
input [8:0] rw_addr ,
input rdy ,
output reg[7:0] tx_data ,
output reg tx_data_vld,
//I2C接口模块
output reg req ,
output reg[3:0] cmd ,
output reg[7:0] data ,
input wr_fail ,
input [7:0] rd_din ,
input rw_done
);
//状态机参数定义
localparam IDLE = 6'b00_0001,
WR_REQ = 6'b00_0010,
WAIT_WDONE = 6'b00_0100,
RD_REQ = 6'b00_1000,
WAIT_RDONE = 6'b01_0000,
DONE = 6'b10_0000;
//信号定义
reg [5:0] state_c ;
reg [5:0] state_n ;
reg [7:0] cnt_byte ;
wire add_cnt_byte ;
wire end_cnt_byte ;
reg [7:0] X ;
wire idle2wr_req ;
wire idle2rd_req ;
wire wait_wdone2wr_req ;
wire wait_wdone2done ;
wire wait_rdone2rd_req ;
wire wait_rdone2done ;
reg wfifo_rdreq ;
wire wfifo_wrreq ;
wire wfifo_empty ;
wire wfifo_full ;
wire [7:0] wfifo_qout ;
wire [5:0] wfifo_usedw ;
reg rfifo_rdreq ;
reg rfifo_wrreq ;
wire rfifo_empty ;
wire rfifo_full ;
wire [7:0] rfifo_qout ;
wire [5:0] rfifo_usedw ;
//状态机设计
//第一段
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
state_c <= IDLE;
end
else begin
state_c <= state_n;
end
end
//第二段
always @(*)begin
case (state_c)
IDLE :begin
if(idle2wr_req)
state_n = WR_REQ;
else if(idle2rd_req)
state_n = RD_REQ;
else
state_n = state_c;
end
WR_REQ :begin
state_n = WAIT_WDONE;
end
WAIT_WDONE:begin
if(wait_wdone2wr_req)
state_n = WR_REQ;
else if(wait_wdone2done)
state_n = DONE;
else
state_n = state_c;
end
RD_REQ :begin
state_n = WAIT_RDONE;
end
WAIT_RDONE:begin
if(wait_rdone2rd_req)
state_n = RD_REQ;
else if(wait_rdone2done)
state_n = DONE;
else
state_n = state_c;
end
DONE :begin
state_n = IDLE;
end
default:state_n = IDLE;
endcase
end
assign idle2wr_req = state_c == IDLE && (wr_req && wfifo_usedw >= `WR_BYTE-2);
assign idle2rd_req = state_c == IDLE && (rd_req);
assign wait_wdone2wr_req = state_c == WAIT_WDONE && (rw_done && end_cnt_byte == 1'b0);
assign wait_wdone2done = state_c == WAIT_WDONE && (rw_done && end_cnt_byte == 1'b1);
assign wait_rdone2rd_req = state_c == WAIT_RDONE && (rw_done && end_cnt_byte == 1'b0);
assign wait_rdone2done = state_c == WAIT_RDONE && (rw_done && end_cnt_byte == 1'b1);
//第三段
//计数器设计 cnt_byte
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
cnt_byte <= 0;
end
else if(add_cnt_byte)begin
if(end_cnt_byte)begin
cnt_byte <= 0;
end
else begin
cnt_byte <= cnt_byte + 1;
end
end
end
assign add_cnt_byte = (state_c == WAIT_WDONE | state_c == WAIT_RDONE) && rw_done;
assign end_cnt_byte = add_cnt_byte && cnt_byte == X-1;
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
X <= 0;
end
else if(idle2wr_req)begin
X <= `WR_BYTE;
end
else if(idle2rd_req)begin
X <= `RD_BYTE;
end
end
//data cmd req
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
data <= 0;
req <= 0;
cmd <= 0;
end
else if(state_c == WR_REQ)begin
case(cnt_byte)
0:begin
data <= {
`SLAVE_CODE,2'b00,rw_addr[8],`WR_BIT};
cmd <= {
`CMD_START | `CMD_WRITE};
req <= 1;
end
1:begin
data <= rw_addr[7:0];
cmd <= `CMD_WRITE;
req <= 1;
end
`WR_BYTE-1:begin
data <= wfifo_qout;
cmd <= {
`CMD_WRITE | `CMD_STOP};
req <= 1;
end
default:begin
data <= wfifo_qout;
cmd <= `CMD_WRITE;
req <= 1;
end
endcase
end
else if(state_c == RD_REQ)begin
case(cnt_byte)
0:begin
req <= 1;
cmd <= {
`CMD_START | `CMD_WRITE};
data <= {
`SLAVE_CODE,2'b00,rw_addr[8],`WR_BIT};
end
1:begin
req <= 1;
cmd <= `CMD_WRITE;
data <= rw_addr[7:0];
end
2:begin
req <= 1;
cmd <= {
`CMD_START | `CMD_WRITE};
data <= {
`SLAVE_CODE,2'b00,rw_addr[8],`RD_BIT};
end
`RD_BYTE-1:begin
req <= 1;
cmd <= {
`CMD_READ | `CMD_STOP};
data <= 0;
end
default:begin
req <= 1;
cmd <= `CMD_READ;
data <= 0;
end
endcase
end
else begin
req <= 0;
end
end
//tx_data tx_data_vld
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
tx_data <= 0;
end
else begin
tx_data <= rfifo_qout;
end
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
tx_data_vld <= 0;
end
else begin
tx_data_vld <= rfifo_rdreq;
end
end
//rfifo_rdreq
always @(*) begin
if(rfifo_empty == 1'b0 && rdy)begin
rfifo_rdreq = 1'b1;
end
else begin
rfifo_rdreq = 1'b0;
end
end
//wfifo_rdreq
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
wfifo_rdreq <= 0;
end
else if(wfifo_empty == 1'b0 && state_c == WAIT_WDONE && req && cnt_byte > 1)begin
wfifo_rdreq <= 1'b1;
end
else begin
wfifo_rdreq <= 1'b0;
end
end
//rfifo_wrreq
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
rfifo_wrreq <= 0;
end
else if(rfifo_full == 1'b0 && rw_done && state_c == WAIT_RDONE && cnt_byte > 2)begin
rfifo_wrreq <= 1'b1;
end
else begin
rfifo_wrreq <= 1'b0;
end
end
//fifo例化
wrfifo wrfifo_inst
(
.aclr ( ~rst_n ),
.clock ( clk ),
.data ( wr_din ),
.rdreq ( wfifo_rdreq ),
.wrreq ( wfifo_wrreq ),
.empty ( wfifo_empty ),
.full ( wfifo_full ),
.q ( wfifo_qout ),
.usedw ( wfifo_usedw )
);
assign wfifo_wrreq = wr_din_vld && wfifo_full == 1'b0;
rdfifo rdfifo_inst
(
.aclr ( ~rst_n ),
.clock ( clk ),
.data ( rd_din ),
.rdreq ( rfifo_rdreq ),
.wrreq ( rfifo_wrreq ),
.empty ( rfifo_empty ),
.full ( rfifo_full ),
.q ( rfifo_qout ),
.usedw ( rfifo_usedw )
);
endmodule
5、i2c接口模块(i2c_interface.v)
//I2C接口模块 实现I2C协议时序
`include "parm.v"
module i2c_interface(
input clk ,//系统时钟
input rst_n ,
input req ,//传输使能
input [3:0] cmd ,//传输命令
input [7:0] wr_din ,//数据传输
input sda_in ,//数据输入
output reg sda_out ,//数据输出
output reg sda_out_en ,//数据输出使能
output reg scl ,
output reg wr_fail ,//从机未应答
output reg[7:0] rd_dout ,//从机读取1字节
output reg rw_done //字节读写完成
);
//状态机参数定义
localparam IDLE = 7'b000_0001,
START = 7'b000_0010,
WRITE = 7'b000_0100,
READ = 7'b000_1000,
RECV_ACK = 7'b001_0000,
SEND_ACK = 7'b010_0000,
STOP = 7'b100_0000;
//信号定义
reg [6:0] state_c ;//现态
reg [6:0] state_n ;//次态
reg [7:0] cnt_scl ;//产生i2c串行时钟
wire add_cnt_scl ;
wire end_cnt_scl ;
reg [3:0] cnt_bit ;//传输数据bit计数器
wire add_cnt_bit ;
wire end_cnt_bit ;
reg [3:0] bit_num ;
reg slave_ack ;//从机应答
wire idle2start ;
wire idle2write ;
wire idle2read ;
wire start2write ;
wire start2read ;
wire write2recv_ack ;
wire read2send_ack ;
wire recv_ack2idle ;
wire recv_ack2stop ;
wire send_ack2idle ;
wire send_ack2stop ;
wire stop2idle ;
//状态机
//第一段
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
state_c <= IDLE;
end
else begin
state_c <= state_n;
end
end
//第二段
always @(*) begin
case (state_c)
IDLE :begin
if(idle2start)
state_n = START;
else if(idle2write)
state_n = WRITE;
else if(idle2read)
state_n = READ;
else
state_n = state_c;
end
START :begin
if(start2write)
state_n = WRITE;
else if(start2read)
state_n = READ;
else
state_n = state_c;
end
WRITE :begin
if(write2recv_ack)
state_n = RECV_ACK;
else
state_n = state_c;
end
READ :begin
if(read2send_ack)
state_n = SEND_ACK;
else
state_n = state_c;
end
RECV_ACK:begin
if(recv_ack2stop)
state_n = STOP;
else if(recv_ack2idle)
state_n = IDLE;
else
state_n = state_c;
end
SEND_ACK:begin
if(send_ack2stop)
state_n = STOP;
else if(send_ack2idle)
state_n = IDLE;
else
state_n = state_c;
end
STOP :begin
if(stop2idle)
state_n = IDLE;
else
state_n = state_c;
end
default:state_n = IDLE;
endcase
end
assign idle2start = state_c == IDLE && (req && (cmd & `CMD_START));
assign idle2write = state_c == IDLE && (req && (cmd & `CMD_WRITE));
assign idle2read = state_c == IDLE && (req && (cmd & `CMD_READ));
assign start2write = state_c == START && (end_cnt_bit && (cmd & `CMD_WRITE));
assign start2read = state_c == START && (end_cnt_bit && (cmd & `CMD_READ));
assign write2recv_ack = state_c == WRITE && (end_cnt_bit);
assign read2send_ack = state_c == READ && (end_cnt_bit);
assign recv_ack2idle = state_c == RECV_ACK && (end_cnt_bit && ((cmd & `CMD_STOP) == 'd0));
assign recv_ack2stop = state_c == RECV_ACK && (end_cnt_bit && ((cmd & `CMD_STOP) || slave_ack));
assign send_ack2idle = state_c == SEND_ACK && (end_cnt_bit && ((cmd & `CMD_STOP) == 'd0));
assign send_ack2stop = state_c == SEND_ACK && (end_cnt_bit && (cmd & `CMD_STOP));
assign stop2idle = state_c == STOP && (end_cnt_bit);
//第三段
//计数器 cnt_scl
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
cnt_scl <= 0;
end
else if(add_cnt_scl)begin
if(end_cnt_scl)begin
cnt_scl <= 0;
end
else begin
cnt_scl <= cnt_scl + 1;
end
end
end
assign add_cnt_scl = (state_c != IDLE);
assign end_cnt_scl = add_cnt_scl && cnt_scl == `SCL_MAX-1;
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
cnt_bit <= 0;
end
else if(add_cnt_bit)begin
if(end_cnt_bit)begin
cnt_bit <= 0;
end
else begin
cnt_bit <= cnt_bit + 1;
end
end
end
assign add_cnt_bit = end_cnt_scl;
assign end_cnt_bit = add_cnt_bit && cnt_bit == bit_num - 1;
//bit_num
always @(*) begin
if(state_c == WRITE || state_c == READ)
bit_num = 8;
else
bit_num = 1;
end
//slave_ack 采样从机应答
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
slave_ack <= 1'b1;
end
else if(state_c == RECV_ACK && cnt_scl == `SAMPLE)begin
slave_ack <= sda_in;
end
end
//串行时钟scl
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
scl <= 1'b1;
end
//从空闲状态到读或写状态,拉低scl
else if(idle2start | idle2write | idle2read)begin
scl <= 1'b0;
end
//计数器计数结束 且非停止状态 拉低scl
else if(end_cnt_scl && stop2idle == 1'b0)begin
scl <= 1'b0;
end
//产生串行时钟 计数器计数到一半 scl拉高
else if(add_cnt_scl && cnt_scl == `SCL_HALF)begin
scl <= 1'b1;
end
end
//串行数据线 数据输出 sda_out
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
sda_out <= 1'b1;//初始状态 scl&sda处于高电平截止状态
end
//发送起始信号 scl为高电平时 拉低sda scl为低电平时 拉高sda
else if(state_c == START)begin
if(end_cnt_scl)
sda_out <= 1'b1;
else if(cnt_scl == `SAMPLE)
sda_out <= 1'b0;
end
else if(state_c == WRITE && cnt_scl == `SEND)begin
sda_out <= wr_din[7-cnt_bit];
end
else if(state_c == SEND_ACK && cnt_scl == `SEND)begin
sda_out <= (cmd & `CMD_STOP)?1'b1:1'b0;
end
else if(state_c == STOP)begin
if(cnt_scl == `SEND)
sda_out <= 1'b0;
else if(cnt_scl == `SAMPLE)
sda_out <= 1'b1;
end
end
//数据输出使能 sda_out_en
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
sda_out_en <= 1'b0;
end
else if(state_c == START | state_c == WRITE | state_c == SEND_ACK | state_c == STOP)begin
sda_out_en <= 1'b1;
end
else begin
sda_out_en <= 1'b0;
end
end
//wr_fail 从机不应答
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
wr_fail <= 1'b0;
end
else if(slave_ack && stop2idle)begin
wr_fail <= 1'b1;
end
end
//rd_dout 读输出
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
rd_dout <= 0;
end
else if(state_c == READ && cnt_scl == `SAMPLE)begin
rd_dout[7-cnt_bit] <= sda_in;
end
end
//读写完成 rw_done
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
rw_done <= 0;
end
else begin
rw_done <= recv_ack2idle | send_ack2idle | stop2idle;
end
end
endmodule
6、串口发送模块(uart_tx.v)
//串口发送模块
`include "parm.v"
module uart_tx(
input clk ,
input rst_n ,
input [7:0] din ,
input din_vld ,
output reg rdy ,//already 可以接收数据并发送
output reg dout
);
//信号定义
reg [12:0] cnt_bps ;//波特率计数器
wire add_cnt_bps ;
wire end_cnt_bps ;
reg [3:0] cnt_bit ;//数据传输bit计数器
wire add_cnt_bit ;
wire end_cnt_bit ;
reg tx_flag ;//发送数据标志信号
reg [9:0] tx_data ;//发送数据寄存 起始位+数据位+停止位
//计数器设计 cnt_bps cnt_bit
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
cnt_bps <= 0;
end
else if(add_cnt_bps)begin
if(end_cnt_bps)begin
cnt_bps <= 0;
end
else begin
cnt_bps <= cnt_bps + 1;
end
end
end
assign add_cnt_bps = tx_flag;
assign end_cnt_bps = add_cnt_bps && cnt_bps == `BAUD-1;
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
cnt_bit <= 0;
end
else if(add_cnt_bit)begin
if(end_cnt_bit)begin
cnt_bit <= 0;
end
else begin
cnt_bit <= cnt_bit + 1;
end
end
end
assign add_cnt_bit = end_cnt_bps;
assign end_cnt_bit = add_cnt_bit && cnt_bit == 10-1;
//发送数据标志信号
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
tx_flag <= 1'b0;
end
else if(din_vld)begin
tx_flag <= 1'b1;//当输入信号有效时,拉低传输数据标志 传输数据开始
end
else if(end_cnt_bit)begin
tx_flag <= 1'b0;//bit计数结束 拉低传输数据标志信号 结束数据传输
end
end
//传输数据 tx_data
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
tx_data <= 0;
end
else if(din_vld)begin
tx_data <= {
1'b1,din,1'b0};//停止位+数据位+起始位
end
end
//dout
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
dout <= 1'b1;
end
else if(add_cnt_bps && cnt_bps == 1)begin
dout <= tx_data[cnt_bit];
end
end
//rdy
always @(*) begin
if(din_vld | tx_flag)begin
rdy = 0;
end
else begin
rdy = 1;
end
end
endmodule
7、参数文件
//串口参数定义
//波特率
`define BAUD_115200
//`define BAUD_57600
//`define BAUD_38400
//`define BAUD_19200
//`define BAUD_9600
//不同波特率对应计数周期
/*50_000_000 / BAUD_x*/
`ifdef BAUD_115200
`define BAUD 434
`elsif BAUD_57600
`define BAUD 868
`elsif BAUD_38400
`define BAUD 1302
`elsif BAUD_19200
`define BAUD 2604
`elsif BAUD_9600
`define BAUD 5208
`endif
//I2C 时钟参数
`define SCL_MAX 150 //I2C时钟周期
`define SCL_HALF 75 //I2C时钟翻转时间
`define SEND 25 //数据发送时间
`define SAMPLE 115 //采样数据时间
//命令码参数定义
`define CMD_START 4'b0001 //发送起始命令
`define CMD_WRITE 4'b0010 //写1字节数据
`define CMD_READ 4'b0100 //读1字节数据
`define CMD_STOP 4'b1000 //发送停止命令
//EEPROM 读写模式定义
// `define ENABLE_BYTE_WRITE //使能字节写
`define ENABLE_PAGE_WRITE //使能页写
`define ENABLE_RANDOM_READ //使能随机地址读
`define ENABLE_SEQUEN_READ //使能连续地址读
//条件编译
`ifdef ENABLE_BYTE_WRITE
`define WR_BYTE 3
`elsif ENABLE_PAGE_WRITE
`define WR_BYTE 18
`endif
`ifdef ENABLE_RANDOM_READ
`define RD_BYTE 4
`elsif ENABLE_SEQUEN_READ
`define RD_BYTE 19
`endif
//从机器件地址 读写字节
`define SLAVE_CODE 4'b1010
`define WR_BIT 1'b0
`define RD_BIT 1'b1
四、功能验证
1、波形仿真
testbench
`timescale 1ns/1ns
module i2c_eeprom_tb();
//激励信号定义
reg clk ;
reg rst_n ;
reg [7:0] uart_din ;
reg uart_din_vld;
//输出信号定义
wire uart_txd ;
wire i2c_scl ;
wire i2c_sda ;
wire tx_rdy ;
wire uart_tx_dout;
//时钟周期参数定义
parameter CLOCK_CYCLE = 20;
uart_tx u_uart_tx(
.clk (clk ),
.rst_n (rst_n ),
.din (uart_din ),
.din_vld (uart_din_vld ),
.rdy (tx_rdy ),//ready 表示串口发送模块可以接收数据并发送
.dout (uart_tx_dout )
);
i2c_eeprom u_i2c_eeprom(
.clk (clk ),
.rst_n (rst_n ),
.uart_rxd (uart_tx_dout ),
.uart_txd (uart_txd ),
.i2c_scl (i2c_scl ),
.i2c_sda (i2c_sda )
);
i2c_slave_model i2c_slave(
.scl (i2c_scl ),
.sda (i2c_sda )
);
//产生时钟
initial clk = 1'b0;
always #(CLOCK_CYCLE/2) clk = ~clk;
task SEND;
input [7:0] wr_data ;
begin
# 1;
uart_din = wr_data;
uart_din_vld = 1'b1;
#(CLOCK_CYCLE);
uart_din_vld = 1'b0;
@(posedge tx_rdy);
#(CLOCK_CYCLE*5);
end
endtask
integer i = 0;
//产生激励
initial begin
rst_n = 1'b0;
uart_din = 0;
uart_din_vld = 0;
#(CLOCK_CYCLE*20);
rst_n = 1'b1;
#(CLOCK_CYCLE*20);
for(i=0;i<10;i=i+1)begin
//发数据
SEND(8'h55); //起始符
SEND(8'haa); //写数据指示信号
SEND(8'h55); //数据
SEND(8'h35); //数据
SEND(8'h55); //数据
SEND(8'h67); //数据
SEND(8'hfe); //数据
SEND(8'h8c); //数据
SEND(8'ha2); //数据
SEND(8'hb7); //数据
SEND(8'ha9); //数据
SEND(8'hd0); //数据
SEND(8'h53); //数据
SEND(8'h04); //结束符
SEND(8'h0d); //结束符
#(CLOCK_CYCLE*200);
//发写请求
SEND(8'h55); //起始符
SEND(8'h00); //写请求 block0
SEND(8'ha3); //写地址
SEND(8'h04); //结束符
SEND(8'h0d); //结束符
//@(posedge u_i2c_eeprom.u_eeprom_control.wait_wdone2done);
#(CLOCK_CYCLE*200);
//发读请求
SEND(8'h55); //起始符
SEND(8'h10); //写请求 block0
SEND(8'ha3); //读地址
SEND(8'h04); //结束符
SEND(8'h0d); //结束符
#(CLOCK_CYCLE*200);
end
#(CLOCK_CYCLE*200);
$stop;
end
endmodule
2、上板验证
发送数据
发送读写请求