一、iic协议介绍以及eeprom
1.1iic协议
IIC协议是传输中常见的协议,其包括了SCL 和SDA 两个引脚,它是一种半双工协议即可以收可以发但是不能同时进行。传输速度不高但较为简单。
如上图,SCL与SDA 都连接着上拉电阻,因此当SCL、SDA空闲时候被上拉为高电平1,因此我们通常用0来表示有效。在SDA线上挂着许多元器件,这些元器件有自己的器件地址,因此主机可以通过器件地址找到设备。
IIC协议的状态:
1.空闲,SCL与SDA被上拉为高电平
2.开始,当SCL为高电平时候,SDA 出现第一个下降沿表示起始信号
3.数据发送,SCL为高电平的时候SDA 保持数据不变,SCL为低电平的时候SDA 改变到需要发送的数据
4.停止,当SCL为高电平的时候SDA 出现上升沿表示停止信号,即进入空闲状态
且SDA的前八bit是SDA 对从机进(主机)行输出,第9bit是从机(主机)发出的应答信号,应答信号为0表示有效。因此SDA 是应该既需要输入也需要输出的信号。
IIC协议写模式分为以下几步:
1.写入器件地址(操作哪个器件)
2.写入寄存器地址(操作该器件的哪个寄存器)
3.写入数据
下面分别介绍三步骤的过程
1.1.1写入器件地址
首先IIC器件地址在一块板子上是固定的,是在设计板子的时候确定的,无法人为改变。对于EEPROM来说,我的板子(正点原子领航者和黑金ax7020)是1010000七位地址,其中前面四位是固定的,后面三位是通过eeprom在板子上的三个引脚决定的,其中eeprom的这三个引脚接的是GND因此为1010000,其中写入器件地址的第八位数据是读写控制信号,表示选择读模式还是写模式,其中0表示写 1表示读。因此整个数据如下:
1.1.2写入存储器地址
该部分是对存储器地址写入,存储地址根据存储数量选择例如某些存储单元为2Kbit(256BYTE,一个BYTE是横着的8bit)选择八位即可2^8=256,某些存储是64kbit(8KBYTE)需要选择13位(2^13=8192=8kBYTE)。此次板子中选用16位(有效位是低13位)的存储器地址。
在选择时候可以定义一个信号,输入1即为16位输入0即为8位。
1.1.3写入数据
写入数据分为单bit写与连续写,单bit写时候在应答结束后给出停止位,整个过程停止,下一个bit的数据需要从写入器件地址重新开始;连续写在写完第一bit数据响应后继续给出下一个数据而非停止位即可连续写。如下图:
单次写
连续写
(写命令当写完一页的时候再写会覆盖该页)
1.1.4读模式
读模式分为当前地址读(上一操作地址+1)、随机地址读(指定)
当前地址读较为简单,即再第一次发送器件地址的时候W/R给1即可开始再下一地址读
随即地址读较为复杂,其含义是从指定的一个地址开始读数据其流程是:
1.发送器件地址,且选择写命令(这一步被称为虚写)
2.发送虚写地址
3.再次发送器件地址,此时选择读
4.读出数据
(我个人认为进行虚写的目的是为了设置指定的读地址,如果不进行虚写操作将会直接进行当前地址读操作)
读也可以进行连续读模式,即最后主机回应0即可继续读下一bit,回应1 即是单bit读。
1.2 EEPROM
本开发板(正点原子领航者7020)上的eeprom是at24c64型号
这是他的原理图,eeprom是一个iic协议通信的存储空间,可以写入数据64kb
二、框架设计
本次实验目标是将0-256写入eeprom对应的地址的0-256中,且加入验证模块,若写入正确即led常亮,若写入错误则led闪烁。
对于本实验进行分析可得需要一下几个模块:eeprom驱动(编写iic协议对eeprom进行驱动),写入和读出数据封装模块(负责控制写什么数据以及解析读出的数据),led控制模块
上图是总体的实验框图,类似于hdmi显示,i2c_dri模块是负责和eeprom模块完成驱动交互的,而e2prom_rw才是确定该系统功能的。
IIC驱动模块较为负杂,采用三段式状态机。
第一段状态机负责时序更新(时序)
第二段状态机负责状态逻辑(组合逻辑)
第三段状态机负责定义每个状态内做什么(时序)
状态机的流程图如下:
三、程序设计
3.1 iic_dri
将IIC的驱动时钟设置在100KHZ-400KHZ即可,本次实验设计为250KHZ,这个时钟是用于该模块以及eeprom_rw模块的驱动时钟而非SCL,SCL应该是该时钟的四分频,具体解释如下:
由于sda是既要输入也要输出的(例如主机写数据时候从机要应答),因此sda在定义的时候应该是inout类型,即既要输出也要输入模式,其具体使用如下
assign sda = sda_dir ? sda_out : 1'bz ; //SDA数据输出或高阻
assign sda_in = sda ; //SDA数据输入
通过dir来控制是输入还是输出,如果dir是1即输出状态,sda的值由sda_out控制,实际中操作sda_out即可,如果dir是0则输入模式,这时候sda是高阻态,然后将sda输入的值给sda_in。即在下面使用过程中就不再直接使用sda,在输入时候使用sda_in,输出时候使用sda_out。
再下面就是有一个分频操作(在没有使用PLL锁相环的时候人为进行分频)以及三段式状态机。
module iic_dri(
input clk ,
input rst ,
input i2c_begin ,
input addr_bit ,//16or8
input i2c_rw ,//0w 1r
input [15:0] i2c_addr ,
input [7:0 ] i2c_wdata ,
output reg[7:0 ] i2c_rdata ,
output reg dri_clk , //dri_clk是给其他模块的时钟,scl是给eeprom实际是时钟
output reg i2c_done ,
output reg i2c_ack ,
output reg scl ,
inout sda
);
parameter st_idle = 8'b0000_0001 ;
parameter st_sladdr = 8'b0000_0010 ;
parameter st_addrh = 8'b0000_0100 ;
parameter st_addrl = 8'b0000_1000 ;
parameter st_wdata = 8'b0001_0000 ;
parameter st_addr_r = 8'b0010_0000 ;
parameter st_rdata = 8'b0100_0000 ;
parameter st_stop = 8'b1000_0000 ;
parameter FPGA_FREQ = 50000000 ;
parameter IIC_FREQ = 250000 ;
parameter EEPROM_ADDR = 7'b1010000 ;
reg sda_dir ;//三态门sda,sda_dir 1输出0输入
reg sda_out ;//dir1时候sda赋值为sdaout
reg st_done ;//st_done是每个环节完成标志位,i2cdone是整体完成
reg wr_rd ; //锁存
reg [6:0] cnt ;
reg [7:0] cur_state ;
reg [7:0] nex_state ;
reg [15:0] addr_t ;//锁存
reg [7:0] data_rd_t ; //读取的数据 锁存
reg [7:0] data_wr_t ; //I2C需写的数据的临时寄存
reg [9:0] clk_cnt ; //分频时钟计数
wire sda_in ;
wire [8:0] clk_divide;
wire [8:0] clk_change;
assign sda = sda_dir ? sda_out:1'bz;//输出状态就是out否则就是高阻态
assign sda_in = sda ;//高组态时候输入给in,输出不用in就行
assign clk_divide = (FPGA_FREQ/IIC_FREQ) >> 2'd2;//分频系数 右移两位其实就是÷2÷2
assign clk_change = clk_divide[8:1] ;//周期是2的话1s就得跳变一次
//dri采用clk的四倍频时钟。因为iic只有scl低电平的时候sda可以改变
//dri是clk四倍频,那clk的t就是dri的四倍,所以dri的上升沿可以检测到clk低电平中间的点
//这里dri_clk就是四倍频的驱动时钟
always @(posedge clk or negedge rst ) begin
if (rst == 1'b0) begin
dri_clk <= 1'b0 ;
clk_cnt <= 10'b0;
end
else if (clk_cnt == clk_change - 1'b1) begin
clk_cnt <= 10'b0;
dri_clk <= ~dri_clk;
end
else
clk_cnt <= clk_cnt + 1'b1;
end
//三段式状态机 第一段时序逻辑 状态转移 (状态机以及其他模块使用dri时钟)
//状态转移,复位进入等待,否则进入下一周期
always @(posedge dri_clk or negedge rst) begin
if (rst == 1'b0)
cur_state <= st_idle;
else
cur_state <= nex_state ;
end
//第二段,组合逻辑判断使用阻塞赋值(电平触发) 描述这几个状态之间的转移过程
always @(*) begin //*代表用到的所有电平
nex_state = st_idle;//取消锁存(可以删除)
case(cur_state)
st_idle : begin
if (i2c_begin == 1'b1)
nex_state = st_sladdr;
else
nex_state = st_idle;
end
st_sladdr : begin
if(st_done == 1'b1) begin//环节完成拉高
if (addr_bit == 1'b1)
nex_state = st_addrh;
else
nex_state = st_addrl;
end
else
nex_state = st_sladdr;
end
st_addrh : begin
if(st_done == 1'b1)
nex_state = st_addrl;
else
nex_state = st_addrh;
end
st_addrl : begin
if (st_done == 1'b1) begin
if (wr_rd == 1'b1)
nex_state = st_addr_r ;
else
nex_state = st_wdata;
end
else
nex_state = st_addrl;
end
st_wdata : begin
if (st_done == 1'b1)
nex_state = st_stop ;
else
nex_state = st_wdata;
end
st_addr_r : begin
if (st_done == 1'b1)
nex_state = st_rdata ;
else
nex_state = st_addr_r;
end
st_rdata : begin
if (st_done == 1'b1 )
nex_state = st_stop;
else
nex_state = st_rdata;
end
st_stop : begin
if (st_done == 1'b1)
nex_state = st_idle;
else
nex_state = st_stop;
end
default nex_state = st_idle;
endcase
end
//第三段****描述状态作用 时序逻辑电路 ****
always @( posedge dri_clk or negedge rst ) begin
if (rst == 1'b0 ) begin
scl <= 1'b1;//由于有上拉电阻,无电平的时候被上拉为1
sda_out <= 1'b1;//由于有上拉电阻,无电平的时候被上拉为1
sda_dir <= 1'b1;
i2c_done <= 1'b0;//整体结束
i2c_ack <= 1'b0;
cnt <= 7'b0;//利用该计数器完成数据sda以及clk时钟
st_done <= 1'b0;
addr_t <= 16'b0;
data_rd_t <= 8'b0;
data_wr_t <= 8'b0;
wr_rd <= 1'b0;
i2c_rdata <= 8'b0;
end
else begin
st_done <= 1'b0;
cnt <= cnt +1'b1; //利用该计数器完成数据sda以及clk时钟
case (cur_state )
st_idle : begin
scl <= 1'b1; //初始化
sda_out <= 1'b1; //初始化
sda_dir <= 1'b1; //初始化
i2c_done <= 1'b0; //初始化
cnt <= 7'b0; //初始化
if (i2c_begin == 1'b1) begin
wr_rd <= i2c_rw ; //锁存响应
addr_t <= i2c_addr ; //锁存响应
data_wr_t <= i2c_wdata; //锁存响应
i2c_ack <= 1'b0 ; //锁存响应
end
end
st_sladdr : begin
case ( cnt )
7'd1 : sda_out <= 1'b0 ;//下降沿开始触发iic
7'd3 : scl <= 1'b0 ;
7'd4 : sda_out <= EEPROM_ADDR[6];
7'd5 : scl <= 1'b1 ;
7'd7 : scl <= 1'b0 ;
7'd8 : sda_out <= EEPROM_ADDR[5];
7'd9 : scl <= 1'b1 ;
7'd11: scl <= 1'b0 ;
7'd12: sda_out <= EEPROM_ADDR[4];
7'd13: scl <= 1'b1 ;
7'd15: scl <= 1'b0 ;
7'd16: sda_out <= EEPROM_ADDR[3];
7'd17: scl <= 1'b1 ;
7'd19: scl <= 1'b0 ;
7'd20: sda_out <= EEPROM_ADDR[2];
7'd21: scl <= 1'b1 ;
7'd23: scl <= 1'b0 ;
7'd24: sda_out <= EEPROM_ADDR[1];
7'd25: scl <= 1'b1 ;
7'd27: scl <= 1'b0 ;
7'd28: sda_out <= EEPROM_ADDR[0];
7'd29: scl <= 1'b1 ;
7'd31: scl <= 1'b0 ;
7'd32: sda_out <= 1'b0 ; //无论是读还是写模块第一次都是输入写
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 //由于sda每次站的时间大概为4个计数单位因此在sda输入四个计数内都可以检测到应答信号
st_done <= 1'b1 ;
if (sda_in == 1'b1 ) //1是没有应答
i2c_ack <= 1'b1 ; //原本是0如果没有回应则拉高到1
end
7'd39: begin
scl <= 1'b0;
cnt <= 7'b0;
end
default : ;
endcase
end
st_addrh : begin
case (cnt)
7'd0 : begin //如果操作正确,这里应该是继续了上一步的scl=0
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'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 ; //35凑不凑都行,因为如果凑了的话刚好满足sda在下一周期第一个就跳变。
cnt <= 7'b0 ; //如果这里没有凑35 那么下面一步0就是变沿,1才是数据改变
end
default : ;
endcase
end
st_addrl : begin
case (cnt)
7'd0 : begin
sda_dir <= 1'd1 ;
sda_out <= addr_t[7] ;
end
7'd1 : scl <= 1'd1;
7'd3 : scl <= 1'd0;
7'd4 : sda_out <= addr_t[6 ];
7'd5 : scl <= 1'd1;
7'd7 : scl <= 1'd0;
7'd8 : sda_out <= addr_t[5 ];
7'd9 : scl <= 1'd1;
7'd11: scl <= 1'd0;
7'd12: sda_out <= addr_t[4 ];
7'd13: scl <= 1'd1;
7'd15: scl <= 1'd0;
7'd16: sda_out <= addr_t[3 ];
7'd17: scl <= 1'd1;
7'd19: scl <= 1'd0;
7'd20: sda_out <= addr_t[2 ];
7'd21: scl <= 1'd1;
7'd23: scl <= 1'd0;
7'd24: sda_out <= addr_t[1 ];
7'd25: scl <= 1'd1;
7'd27: scl <= 1'd0;
7'd28: sda_out <= addr_t[0 ];
7'd29: scl <= 1'd1;
7'd31: scl <= 1'd0;
7'd32: begin
sda_dir <= 1'd0 ;
sda_out <= 1'd1 ;
end
7'd33:scl <= 1'd1;
7'd34: begin
st_done <= 1'd1;
if (sda_in == 1'd1 )
i2c_ack <= 1'd1 ;
end
7'd35: begin
scl <= 1'd0 ;
cnt <= 7'd0 ; //35凑不凑都行,因为如果凑了的话刚好满足sda在下一周期第一个就跳变。
end //如果这里没有凑35 那么下面一步0就是变沿,1才是数据改变
default : ;
endcase
end
st_wdata :begin
case (cnt)
7'd0 : begin
sda_dir <= 1'd1 ;
sda_out <= data_wr_t[7] ;//所存的输入数据
end
7'd1 : scl <= 1'd1;
7'd3 : scl <= 1'd0;
7'd4 : sda_out <= data_wr_t[6 ];
7'd5 : scl <= 1'd1;
7'd7 : scl <= 1'd0;
7'd8 : sda_out <= data_wr_t[5 ];
7'd9 : scl <= 1'd1;
7'd11: scl <= 1'd0;
7'd12: sda_out <= data_wr_t[4 ];
7'd13: scl <= 1'd1;
7'd15: scl <= 1'd0;
7'd16: sda_out <= data_wr_t[3 ];
7'd17: scl <= 1'd1;
7'd19: scl <= 1'd0;
7'd20: sda_out <= data_wr_t[2 ];
7'd21: scl <= 1'd1;
7'd23: scl <= 1'd0;
7'd24: sda_out <= data_wr_t[1 ];
7'd25: scl <= 1'd1;
7'd27: scl <= 1'd0;
7'd28: sda_out <= data_wr_t[0 ];
7'd29: scl <= 1'd1;
7'd31: scl <= 1'd0;
7'd32:begin
sda_dir <= 1'd0 ;
sda_out <= 1'd1 ;
end
7'd33: scl <= 1'd1;
7'd34: begin
st_done <= 1'd1;
if (sda_in == 1'd1 )
i2c_ack <= 1'd1;
end
7'd35: begin
scl <= 1'd0;
cnt <= 7'd0;
end
default: ;
endcase
end
st_addr_r : begin
case (cnt)
7'd0 : begin
sda_dir <= 1'd1 ;
sda_out <= 1'd1 ;
end
7'd1 : scl <= 1'd1;
7'd2 : sda_out <= 1'd0; //下降沿触发信号
7'd3 : scl <= 1'd0;
7'd4 : sda_out <= EEPROM_ADDR[6 ];
7'd5 : scl <= 1'd1;
7'd7 : scl <= 1'd0;
7'd8 : sda_out <= EEPROM_ADDR[5 ];
7'd9 : scl <= 1'd1;
7'd11: scl <= 1'd0;
7'd12: sda_out <= EEPROM_ADDR[4 ];
7'd13: scl <= 1'd1;
7'd15: scl <= 1'd0;
7'd16: sda_out <= EEPROM_ADDR[3 ];
7'd17: scl <= 1'd1;
7'd19: scl <= 1'd0;
7'd20: sda_out <= EEPROM_ADDR[2 ];
7'd21: scl <= 1'd1;
7'd23: scl <= 1'd0;
7'd24: sda_out <= EEPROM_ADDR[1 ];
7'd25: scl <= 1'd1;
7'd27: scl <= 1'd0;
7'd28: sda_out <= EEPROM_ADDR[0 ];
7'd29: scl <= 1'd1;
7'd31: scl <= 1'd0;
7'd32: sda_out <= 1'd1; //读信号
7'd33: scl <= 1'd1;
7'd35: scl <= 1'd0;
7'd36: begin
sda_dir <= 1'd0;
sda_out <= 1'd1;
end
7'd37: scl <= 1'd1;
7'd38: begin
st_done <= 1'd1 ;
if (sda_in == 1'd1)
i2c_ack <= 1'd1 ;
end
7'd39: begin
cnt <= 7'd0 ;
scl <= 1'd0 ;
end
default : ;
endcase
end
st_rdata : begin
case (cnt )
7'd0 : sda_dir <= 1'd0 ;
7'd1 : begin
data_rd_t[7] <= sda_in ;
scl <= 1'd1 ;
end
7'd3 : scl <= 1'd0 ;
7'd5 :begin
data_rd_t[6] <= sda_in ;
scl <= 1'd1 ;
end
7'd7 : scl <= 1'd0 ;
7'd9 :begin
data_rd_t[5] <= sda_in ;
scl <= 1'd1 ;
end
7'd11: scl <= 1'd0 ;
7'd13:begin
data_rd_t[4] <= sda_in ;
scl <= 1'd1 ;
end
7'd15: scl <= 1'd0 ;
7'd17:begin
data_rd_t[3] <= sda_in ;
scl <= 1'd1 ;
end
7'd19: scl <= 1'd0 ;
7'd21:begin
data_rd_t[2] <= sda_in ;
scl <= 1'd1 ;
end
7'd23: scl <= 1'd0 ;
7'd25:begin
data_rd_t[1] <= sda_in ;
scl <= 1'd1 ;
end
7'd27: scl <= 1'd0 ;
7'd29:begin
data_rd_t[0] <= sda_in ;
scl <= 1'd1 ;
end
7'd31: scl <= 1'd0 ;
7'd32: begin
sda_dir <= 1'd1 ;
sda_out <= 1'd1 ;
end
7'd33: scl <= 1'd1 ;
7'd34: st_done <= 1'd1 ; //未应答
7'd35:begin
scl <= 1'd0 ;
cnt <= 7'd0 ;
i2c_rdata <= data_rd_t ;
end
default : ;
endcase
end
st_stop : begin
case (cnt )
7'd0: begin
sda_dir <= 1'd1 ;
sda_out <= 1'd0 ;
end
7'd1 : scl <= 1'b1;
7'd3 : sda_out <= 1'b1;
7'd15: st_done <= 1'b1;
7'd16: begin
cnt <= 7'b0;
i2c_done <= 1'b1;
end
default : ;
endcase
end
endcase
end
end
endmodule
3.2 eeprom_rw
该模块可以认为是iic_dir的上层模块,因为iic_dri是完成iic协议,而iic协议真正怎么使用是看该模块。
module e2prom_rw(
input dri_clk ,
input [7:0] i2c_data_r ,
input i2c_done ,
input i2c_ack ,
input rst ,
output reg i2c_begin ,
output addr_bit ,
output reg i2c_rw ,
output reg [15:0] i2c_addr ,
output reg [7:0 ] i2c_data_w ,
output reg rw_done , //done 和结果输出给灯
output reg rw_result //用在后面灯环节
);
reg [1:0] flow_cnt ; //环节计数
reg [13:0] wait_cnt ;
assign addr_bit = 1'b1 ;
parameter DATA_MAX = 256 ; //写入数据0-256一共257个数
parameter WAIT_MAX = 1250 ; //dri 频率是100_000则5ms是500个计数单位
always @(posedge dri_clk or negedge rst) begin
if (rst == 1'b0 ) begin
flow_cnt <= 2'b00 ;
i2c_rw <= 1'b0 ;
i2c_begin <= 1'b0 ;
i2c_addr <= 16'b0 ;
i2c_data_w <= 8'b0 ;
wait_cnt <= 14'b0 ;
rw_done <= 1'b0 ;
rw_result <= 1'b0 ;
end
else begin
i2c_begin <= 1'b0 ; //拉低00时候的开始信号
rw_done <= 1'b0 ;
case (flow_cnt)
2'b00 : begin //写1模块 (用于判断是否写完257次)
wait_cnt <= wait_cnt + 1'b1 ;
if (wait_cnt == WAIT_MAX - 1'b1) begin //每次写中间需要5ms间隔
wait_cnt <= 14'b0 ;
if (i2c_addr == DATA_MAX) begin //写完257次
flow_cnt <= 2'b10;
i2c_rw <= 1'b1;
i2c_addr <= 16'b0; //写完去读,没写完去写(读前清零)
end
else begin
i2c_begin<= 1'b1 ;
flow_cnt <= 2'd01 ;
end
end
end
2'b01 :begin
if (i2c_done == 1'b1 ) begin
flow_cnt <= 2'b00 ;
i2c_addr <= i2c_addr + 1'b1 ;
i2c_data_w <=i2c_data_w +1'b1 ;
end
end
2'b10 : begin
flow_cnt <= flow_cnt + 1'b1 ;
i2c_begin<= 1'b1 ;
end
2'b11: begin
if(i2c_done) begin
if ((i2c_addr[7:0] != i2c_data_r)||(i2c_ack == 1'b1)) begin
rw_done <= 1'b1 ;
rw_result <= 1'b0 ;//读的不对
end
else if (i2c_addr == (DATA_MAX - 16'b1)) begin
rw_done <= 1'b1 ;
rw_result <= 1'b1 ;
end
else begin
flow_cnt <= 2'b10 ;
i2c_addr <= i2c_addr + 16'b1;
end
end
end
default : ;
endcase
end
end
endmodule
该模块执行的内容是:
1.判断是否写完256次?如果写完了进行读操作3,反之继续进行写操作且准备再次开始一次iic2。
2.判断这次写iic是否完成,完成的话地址和数据都加一
3.写操作开始一次iic传输,判断是否传输正确或得到应答信号,如果没有结束传输,结果为错误;如果正确则判断是否督导最大地址,如果是则结束,结果是正确,返回读下一位
3.3 rw_result_led
首先定义了一个done_flag锁存eeprom_rw模块输出的判断结果。再实现想要的结果。
module rw_result_led(
input dri_clk,
input rst ,
input rw_done ,
input rw_result,
output reg led
);
parameter LED_CNT = 100000 ;
//灯反转计数器
reg [16:0] led_cnt ;
//寄存done信号
reg done_flag ;
always @(posedge dri_clk or negedge rst ) begin
if (!rst)
done_flag <= 1'b0 ;
else if (rw_done)
done_flag <= 1'b1 ;
else
done_flag <= done_flag ;
end
always @(posedge dri_clk or negedge rst ) begin
if (!rst) begin
led <= 1'b1 ;
led_cnt <= 17'b0 ;
end
else if (done_flag) begin
if (!rw_result) begin
led_cnt <= led_cnt + 17'b1 ;
if (led_cnt == LED_CNT - 17'b1) begin
led_cnt <= 17'b0 ;
led <= ~led ;
end
end
else
led <= 1'b0 ;
end
end
endmodule
3.4 顶层模块
调用四个模块即可
module e2prom_top(
input sys_clk,
input sys_rst,
output led ,
output scl ,
inout sda
);
wire i2c_begin ;
wire addr_bit ;
wire i2c_rw ;
wire [15:0] i2c_addr ;
wire [7 : 0] i2c_data_w ;
wire [7 :0] i2c_rdata ;
wire [7 : 0] i2c_data_r ;
wire dri_clk ;
wire i2c_done ;
wire i2c_ack ;
wire rw_done ;
wire rw_result ;
iic_dri u_iic_dri (
.clk ( sys_clk ),
.rst ( sys_rst ),
.i2c_begin ( i2c_begin ),
.addr_bit ( addr_bit ),
.i2c_rw ( i2c_rw ),
.i2c_addr ( i2c_addr ),
.i2c_wdata ( i2c_data_w ),
.i2c_rdata ( i2c_rdata ),
.dri_clk ( dri_clk ),
.i2c_done ( i2c_done ),
.i2c_ack ( i2c_ack ),
.scl ( scl ),
.sda ( sda )
);
e2prom_rw u_e2prom_rw(
.dri_clk ( dri_clk ),
.i2c_data_r ( i2c_rdata ),
.i2c_done ( i2c_done ),
.i2c_ack ( i2c_ack ),
.rst ( sys_rst ),
.i2c_begin ( i2c_begin ),
.addr_bit ( addr_bit ),
.i2c_rw ( i2c_rw ),
.i2c_addr ( i2c_addr ),
.i2c_data_w ( i2c_data_w ),
.rw_done ( rw_done ),
.rw_result ( rw_result )
);
rw_result_led u_rw_result_led(
.dri_clk ( dri_clk ),
.rst ( sys_rst ),
.rw_done ( rw_done ),
.rw_result ( rw_result ),
.led ( led )
);
ila_0 u_ila_0 (
.clk(sys_clk), // input wire clk
.probe0(led), // input wire [0:0] probe0
.probe1(rw_result), // input wire [255:0] probe1
.probe2(done_flag), // input wire [255:0] probe2
.probe3(i2c_addr), // input wire [255:0] probe3
.probe4(i2c_data_r) // input wire [255:0] probe4
);
endmodule
ila是我调用的探针