0.序言
使用vivado实现IIC协议对EEPROM进行数据存储与读取。本文是基于正点原子的“达芬奇”开发板资料进行学习的笔记,对部分地方进行了修改,并进行了详细的讲解。
1.IIC协议简介
(1)简介
IIC(Inter-Integrated Circuit),即集成电路总线,是一种同步半双工串行总线,用于连接微控制器及外围设备,是用于数据量不大及传输距离不大的场合下的主从通信。IIC是为了与低速设备通信而发明的,所以IIC的传输速率比不上SPI。
(2)物理层接口协议
IIC一共有两根总线:一条是主设备提供给从设备的串行时钟线SCL,一条是双向传输的串行数据线SDA;
SCL:Serial clock line,是时钟线,也就是控制数据发送的时钟;
SDA:Serial data,是数据线,用于数据的传输;
若实现一主多从通信:所有从设备的数据线SDA都接到主设备提供的总线SDA上,所有从设备的时钟线SCL都接到总线SCL上,每一个从器件都有一个唯一的器件地址,主设备每次通信都会发送一个器件地址,与对应器件地址的从器件进行通信。
三态门:IIC是半双工通信,在数据线SDA上分时进行发送和接收数据,双向数据线SDA要通过三态门实现:
三态门使用:
定义数据线sda为inout类型;
由条件语句控制三态门,控制端sda_dir拉高,三态门导通,数据线sda数据为FPGA输出数据sda_out;控制端sda_dir拉低,三态门关闭,门电路为高阻态;门电路为高阻态,fpga输入数据sda_in为数据线上数据sda;
(3)协议层
IIC数据传输包含五部分,开始、发送、接收、应答、结束五个部分构成。SDA和SCL数据线默认为上拉状态(高电平)。
A开始
当SCL为高电平时,SDA由高变为低,为传输开始标志。
B发送
在发送数据前应保证三态门处于输出状态(sda_dir=1’b1)。发送数据时,需在时钟线sclk为低电平时修改数据线sda的数据。
C接收
在接收数据前应保证三态门处于输入状态(sda_dir=1’b0)。接收数据时,外设也是在时钟线sclk为低电平时修改数据线sda的数据,所以接收数据采样是在时钟sclk上升沿。
D应答
从机每接收到主机发送的8bit数据后,从机会通过数据线sda向主机反馈一位应答信号,通常为低电平,表示已经接收到数据,主机可以通过这一位应答信号判断从机状态。应答是从机输入给主机的,此时应保证三态门处于输入状态(sda_dir=1’b0)。
E结束
当SCL为高电平时,SDA由低变为高,为传输结束标志。
2.EEPROM(使用的EEPROM型号:AT24C64)
(1)EEPROM简介
EEPROM,电可擦除可编程只读存储器,断电后数据不会丢失。
(2)芯片AT24C64硬件接口
A0~A2 :器件地址,在实现通信时,主机会发送三位器件地址,当三位器件地址与A2~A0 电平对应时,主从之间实现通信。这也是主从通信时片选的实现。所以在主从通信时,不同的从机,A2~A0接的电平不一样;
WP:写保护,当写保护引脚连接至 GND 时,芯片可以正常写,当写保护引脚连接至 VCC 时,使能写保护功能,此时禁止向芯片写入数据,只能进行读操作;
SCL和SDA即IIC通信的时钟线和数据线。
(2) AT24C64读写时序
A 序言
AT24C64有8k个字节的存储空间,分成了256页,每页32个字节。且有多种读写模式,有字节写、页写、当前地址读、随机地址读、连续读模式。
B 写数据
(a)字节写
时序图:
说明:一次字节写在开始(START)和结束(STOP)标志之间,包含了一个字节的器件地址,两个字节的数据地址,和一个字节的数据,且每个字节传输完后都有一位应答信号ACK(低电平)。
器件地址:8位器件地址,高4位默认1010,后接3位从机对应的器件地址A2~A0,最后还有一位读写控制位(R/W)(此时为写数据给0,后面读数据给1);
数据地址:前面提到,AT24C64,有8k个字节数据,每个字节数据对应一个地址,共13位地址,用两个字节传输数据地址,在第一个数据地址低五位传输数据地址的高五位,在第二个数据地址字节传输数据地址低8位。(图中的*号表示无关位,因为这个图截自数据手册,手册同时包含了AT24C64和AT24C32两个芯片的说明,AT24C32存储空间只有AT24C64的一半,所以会少一位数据地址,所以+号对于AT24C64芯片来说是数据地址最高位,对于AT24C32来说也是无关位);
数据:传给EEPROM的一个字节8位数据。
(b)页写
时序图:
说明:与字节写不同的是,在给完数据地址后,不仅仅写了一个字节数据,是连续写了多个字节数据;
注意:我们只给了第一个数据的存储地址,后面字节数据的13位存储地址高8位保持不变,低5位在给的存储地址的基础上累加,因为前面提到,芯片的存储空间是按页排布(256页x32字节),相当于页选信号(高8位)不变,在当前页内存储地址(低5位)累加,当连续存入的数据超过了当前页的存储空间(32个字节),数据地址又会从当前页第一个数据重新存入。
(c )注意(注意注意注意:强调一下)
完成单次写操作,AT24C64并不是立刻将数据存入,而是需要需要花费一定时间,数据手册上说是最大是10ms,实际上达不到10ms,大家可以试一下,给的代码中是10ms一次写操作。
C 随机地址读(只介绍这一种,其他两种少见)
(a)时序图
(b)说明
随机地址读为伪写操作+读操作,伪写操作给读操作提供地址,对应字节写的开始标志+器件地址+数据地址,读操作包含一个字节器件地址和一个字节数据,最后一个停止标志;
注意:随机地址读数据后无应答信号
3.实例
(1)功能描述
使用字节写和随机地址读模式,给EEPROM的0到63地址中,存入数据0到63,并从任意地址中取出一个数据进行验证,验证正确点亮led灯。
(2)结构框图
TOP module:顶层模块,用于例化读写控制模块和IIC驱动模块;
WR control:读写控制模块,控制IIC驱动模块的读/写操作,产生读写数据有效标志/读写控制信号/读写数据地址/写入的数据,接收读出的数据/单次IIC操作完成标志,并对接收数据进行验证,以led灯进行指示;
IIC_drive:IIC驱动模块,将需要存储的数据转换为IIC数据格式并发送,将读出的IIC数据还原为数据格式;
(3)实现
(A)顶层模块(IIC_EEPROM_top)
功能描述:用于例化读写控制模块和IIC驱动模块
(B)读写控制模块(WR_control)
功能描述:控制IIC模块的读写操作,并对写数据进行验证。
首先,产生IIC驱动模块的驱动时钟:
系统时钟是50MHz,AT24C64的对时钟线上时钟的要求如下,有100kHz(1.8V,2.5V,2.7V)和400kH(5V),在正点原子达芬奇开发板中提供的电源为3.3V,250kHz也能正常工作,所以使用的250kHz。为满足开始/结束标志,和数据传输时修改数据线变量在时钟线的低电平时刻,这里的驱动时钟使用的是数据线上时钟的4倍频率,即1MHz。所以分频系数=(系统时钟频率/sclk时钟频率)/4;要实现n分频,计数器记到**(n/2)-1**,时钟状态就要翻转一次(只针对于偶数倍分频器,奇数倍分频器要使用时钟双沿,或者先倍频)。
其次,实现存入数据,读出数据,验证数据三个状态:实现每10ms写一个数据,在写数据时需要利用加法器产生数据(data_wr),地址(data_addr),读写控制位(rw_con=1’b0),使能有效标志(data_valid,为iic驱动模块提供的写开始标志),将0到63写入地址0到63的存储空间;然后随机读一个地址中的数(这里读的是十进制数10),读书据时将想验证数据的地址(read_data_add)赋给地址变量(data_addr),同时产生读写控制位(rw_con=1’b1),写使能有效标志(data_valid)。读出后进行验证,一致后点亮led灯。
(C )IIC驱动模块(iic_drive)
有限状态机:
说明,因为使用的是字节写和随机地址读,根据上图中由AT24C64数据手册字节写模式和随机地址读模式的时序图可以得出:
字节写:包含写器件地址(开始标志只有一位,将开始标志包含入内)、高位数据地址、低位数据地址、写入数据和结束状态五个状态;
随机地址读:包含写器件地址(开始标志只有一位,将开始标志包含入内)、高位数据地址、低位数据地址、读操作器件地址、读出数据和结束状态六个状态。
同时在不传输数据为空闲状态,所以整合后的状态机如上图所示,共8个状态,每个状态时序说明如下:
a 空闲状态(st_idle)
对控制信号暂存,使能(data_val)有效时对输入数据进行暂存,包括写的数据(data_wr),地址(data_addr),读写控制信号(rw_con);
b写器件地址(st_sladder_w)
c 地址高八位(st_addr_h)
d 地址低八位(st_addr_l)
e写入数据(st_data_w)
f读器件地址(st_sladder_r)
g读入数据(st_data_r)
h结束状态(st_done_byte)
结束标志,产生IIC操作完成标志信号(iic_down)。且当是读数据时,将读到的数据输出。
(4)代码
A 顶层模块
`timescale 1ns / 1ps
module IIC_EEPROM_top(
input clk ,
input rst_n ,
//output led_s ,
output sclk ,
inout sda,
output led_s
);
wire dri_clk; //驱动时钟
wire data_val; //给iic驱动模块数据有效标志
wire rw_con; //读写控制信号
wire [15:0] data_addr; //数据地址
wire [7:0] data_wr; //写入EEPROM的数据
wire [7:0] data_re; //从EEPROM读出的数据
wire iic_done; //单次读写操作完成标志
WR_control #
(
.sys_clk_fre (26'd50_000_000), //系统时钟频率
.sclk_fre (18'd250_000)
)
u_WR_control(
.clk (clk),
.rst_n (rst_n),
.dri_clk (dri_clk), //iic驱动时钟
.data_valid (data_val), //数据有效标志
.rw_con (rw_con), //读写控制位
.data_addr (data_addr), //读写对应的EEPROM中的地址
.data_wr (data_wr), //写操作时需要写入的数据
.data_re (data_re), //读操作时读出的数据
.iic_done (iic_done), //一次IIC完成的标志
.led_s (led_s)
);
iic_drive #
(
.SLAVE_ADDR (7'b1010_000)
)
u_iic_drive
(
.rst_n (rst_n),
.dri_clk (dri_clk), //iic驱动时钟
.data_val (data_val), //数据有效标志
.rw_con (rw_con), //读写控制位
.data_addr (data_addr),
.data_wr (data_wr),
.data_re (data_re), //读操作时读出的数据
.iic_done (iic_done),
.sclk (sclk),
.sda (sda)
);
// EEPROM_AT24C64 u_EEPROM_AT24C64(
// .scl (sclk),
// .sda (sda)
// );
endmodule
B 读写控制模块
//读写控制模块
module WR_control
#(
parameter sys_clk_fre = 26'd50_000_000, //系统时钟频率
parameter sclk_fre = 18'd250_000 //sclk时钟频率
)
(
input clk ,
input rst_n ,
output reg dri_clk , //iic驱动时钟
output reg data_valid , //数据有效标志
output reg rw_con , //读写控制位
output reg [15:0] data_addr , //读写对应的EEPROM中的地址
output reg [7:0] data_wr , //写操作时需要写入的数据
input [7:0] data_re , //读操作时读出的数据
input iic_done , //一次IIC完成的标志
output reg led_s
);
parameter wr_cnt_max = 10_000; //写操作时钟计数
parameter read_data_add = 8'd10; //验证读出数据的位置
parameter wr_data_num = 8'd64; //写入数据个数
//使用4状态状态机
parameter idle = 4'b0001;
parameter state_write = 4'b0010;
parameter state_read = 4'b0100;
parameter state_vf = 4'b1000;
reg [3:0] current_state;
reg [3:0] next_state = 4'b0000;
//产生驱动时钟
wire [8:0] fre_divi;
reg [9:0] divi_cnt;
assign fre_divi = (sys_clk_fre/sclk_fre)>>2'd2;
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
dri_clk <= 1'b0;
divi_cnt <= 10'd0;
end
else if(divi_cnt == (fre_divi[8:1]-1'b1))begin
dri_clk <= ~dri_clk;
divi_cnt <= 10'd0;
end
else begin
dri_clk <= dri_clk;
divi_cnt <= divi_cnt + 1'b1;
end
end
//10ms计数器
reg [13:0] wr_cnt;
always@(posedge dri_clk or negedge rst_n)begin
if(!rst_n)
wr_cnt <= 0;
else if(current_state == state_write)
if(wr_cnt == 14'd9_999)
wr_cnt <= 14'd0;
else
wr_cnt <= wr_cnt+1'b1;
else
wr_cnt <= 0;
end
//读写数据产生和验证状态机(以后的时钟都在IIC驱动时钟下完成)
//第一段:时序always过程块,将次态赋给现态
always@(posedge dri_clk or negedge rst_n)begin
if(!rst_n)
current_state <= idle;
else
current_state <= next_state;
end
//第二段:组合always过程块,状态转移条件判断
always@(*)begin
case(current_state)
idle:begin
next_state = state_write;
end
state_write:begin
if(wr_cnt == (wr_cnt_max-1))
if(data_wr == wr_data_num-1)
next_state = state_read;
else
next_state = state_write;
else
next_state = state_write;
end
state_read:begin
if(iic_done == 1'b1)
next_state = state_vf;
else
next_state = state_read;
end
default:begin
next_state = state_vf;
end
endcase
end
//第三段:状态赋值
reg send_valid;
reg [7:0] read_data;
always@(posedge dri_clk or negedge rst_n)begin
if(!rst_n)begin
data_addr <= 16'd0;
data_wr <= 8'd0;
data_valid <= 1'b0;
rw_con <= 1'b1;
send_valid <= 1'b0;
read_data <= 8'd0;
led_s <= 1'b0;
end
else begin
case(current_state)
state_write:begin
rw_con <= 1'b0;
if(wr_cnt == 14'd0)begin
data_valid <= 1'b1;
end
else if(wr_cnt == 14'd9_999)begin
data_addr <= data_addr + 1'b1;
data_wr <= data_wr + 1'b1;
end
else begin
data_valid <= 1'b0;
data_addr <= data_addr;
data_wr <= data_wr;
end
end
state_read:begin
rw_con <= 1'b1;
send_valid <= 1'b1;
if(!send_valid)begin
data_valid <= 1'b1;
end
else begin
data_valid <= 1'b0;
end
data_addr <= read_data_add;
if(iic_done)
send_valid <= 1'b0;
end
state_vf:begin
read_data <= data_re;
if(read_data == read_data_add)
led_s <= 1'b1;
end
default:;
endcase
end
end
endmodule
C IIC驱动模块
//iic驱动模块
module iic_drive
#(
parameter SLAVE_ADDR = 7'b1010_000
)
(
input rst_n ,
input dri_clk , //iic驱动时钟
input data_val , //数据有效标志
input rw_con , //读写控制位
//读写对应的EEPROM中的地址
input [15:0] data_addr ,
//写操作时需要写入的数据
input [7:0] data_wr ,
//读操作时读出的数据
output reg [7:0] data_re , //读操作时读出的数据
output reg iic_done ,
output reg sclk ,
inout sda
);
parameter WRITE = 1'b0;
parameter READ = 1'b1;
//三态门
reg sda_dir; //三态门控制信号
reg sda_out; //三态门输出数据
wire sda_in; //三态门输入数据
assign sda = sda_dir?sda_out:1'bz;
assign sda_in = sda;
reg done; //iic字节操作完成
//三段式状态机,一位独热编码
parameter st_idle = 8'b0000_0001;
parameter st_sladder_w = 8'b0000_0010;
parameter st_addr_h = 8'b0000_0100;
parameter st_addr_l = 8'b0000_1000;
parameter st_data_w = 8'b0001_0000;
parameter st_sladder_r = 8'b0010_0000;
parameter st_data_r = 8'b0100_0000;
parameter st_done_byte = 8'b1000_0000;
reg [7:0] cur_state = 8'b0000_0000;
reg [7:0] next_state = 8'b0000_0000;
//时序always过程块
always@(posedge dri_clk or negedge rst_n)begin
if(!rst_n)
cur_state <= st_idle;
else
cur_state <= next_state;
end
//组合always过程块
always@(*)begin
case(cur_state)
st_idle:begin
if(data_val == 1'b1)
next_state = st_sladder_w;
else
next_state = st_idle;
end
st_sladder_w:begin
if(done == 1'b1)
next_state = st_addr_h;
else
next_state = st_sladder_w;
end
st_addr_h:begin
if(done)
next_state = st_addr_l;
else
next_state = st_addr_h;
end
st_addr_l:begin
if(done)begin
if(rw_con == 1'b0)
next_state = st_data_w;
else
next_state = st_sladder_r;
end
else
next_state = st_addr_l;
end
st_data_w:begin
if(done)
next_state = st_done_byte;
else
next_state = st_data_w;
end
st_sladder_r:begin
if(done)
next_state = st_data_r;
else
next_state = st_sladder_r;
end
st_data_r:begin
if(done)
next_state = st_done_byte;
else
next_state = st_data_r;
end
st_done_byte:begin
if(done)
next_state = st_idle;
else
next_state = st_done_byte;
end
default:begin
next_state = st_idle;
end
endcase
end
//状态赋值
reg [5:0] cnt; //状态赋值计数器
reg rw_con_r; //读写标志位暂存
reg [7:0] data_wr_r; //写入数据暂存
reg [15:0] data_addr_r;//写入数据地址
//reg done; //字节操作完成标志位(前面已经声明)
//reg sda_dir; //三态门控制信号(前面已经声明)
//reg sclk; //时钟信号(前面已经声明)
//reg sda_out; //输出数据线(前面已经声明)
reg iic_ack; //iic应答信号
reg [7:0] data_re_r; //iic读入的数据暂存变量
//reg iic_done; //iic操作完成信号(前面已经声明)
//reg [7:0] data_re;//读入数据(前面已经声明)
always@(posedge dri_clk or negedge rst_n)begin
if(!rst_n)begin
cnt <= 1'b0;
rw_con_r <= 1'b0;
data_wr_r <= 8'd0;
data_addr_r <= 16'd0;
done <= 1'b0;
sda_dir <= 1'b1;
sclk <= 1'b1;
sda_out <= 1'b1;
iic_ack <= 1'b0;
data_re_r <= 8'd0;
iic_done <= 1'b0;
data_re <= 8'd0;
end
else begin
case(cur_state)
st_idle:begin //信号需要复位
cnt <= 6'b0;
sclk <= 1'b1;
sda_out <= 1'b1;
if(data_val == 1'b1)begin //输入数据暂存
rw_con_r <= rw_con;
data_wr_r <= data_wr;
data_addr_r <= data_addr;
end
end
st_sladder_w:begin
cnt <= cnt + 1'b1;
case(cnt)
6'd0: sda_out <= 1'b0;
6'd1: sclk <= 1'b0;
6'd2: sda_out <= SLAVE_ADDR[6];
6'd3: sclk <= 1'b1;
6'd5: sclk <= 1'b0;
6'd6: sda_out <= SLAVE_ADDR[5];
6'd7: sclk <= 1'b1;
6'd9: sclk <= 1'b0;
6'd10: sda_out <= SLAVE_ADDR[4];
6'd11: sclk <= 1'b1;
6'd13: sclk <= 1'b0;
6'd14: sda_out <= SLAVE_ADDR[3];
6'd15: sclk <= 1'b1;
6'd17: sclk <= 1'b0;
6'd18: sda_out <= SLAVE_ADDR[2];
6'd19: sclk <= 1'b1;
6'd21: sclk <= 1'b0;
6'd22: sda_out <= SLAVE_ADDR[1];
6'd23: sclk <= 1'b1;
6'd25: sclk <= 1'b0;
6'd26: sda_out <= SLAVE_ADDR[0];
6'd27: sclk <= 1'b1;
6'd29: sclk <= 1'b0;
6'd30: sda_out <= 1'b0; //写数据标志
6'd31: sclk <= 1'b1;
6'd33: sclk <= 1'b0;
6'd34: begin //三态门的控制
sda_dir <= 1'b0; //将三态门调整为输入状态
sda_out <= 1'b1; //首先将数据线拉高
end
6'd35: sclk <= 1'b1;
6'd36: begin
done <= 1'b1; //单byte操作完成标志
if(sda_in == 1'b1)
iic_ack <= 1'b1; //应答信号,拉高表示未应答
end
6'd37: begin
sclk <= 1'b0;
done <= 1'b0;
cnt <= 6'd0;
end
default: ;
endcase
end
st_addr_h:begin
cnt <= cnt + 1'b1;
case(cnt)
6'd0:begin
sda_dir <= 1'b1;
sda_out <= data_addr_r[15]; //无关位3
end
6'd1: sclk <= 1'b1;
6'd3: sclk <= 1'b0;
6'd4: sda_out <= data_addr_r[14]; //无关位2
6'd5: sclk <= 1'b1;
6'd7: sclk <= 1'b0;
6'd8: sda_out <= data_addr_r[13]; //无关位1
6'd9: sclk <= 1'b1;
6'd11: sclk <= 1'b0;
6'd12: sda_out <= data_addr_r[12]; //地址最高位
6'd13: sclk <= 1'b1;
6'd15: sclk <= 1'b0;
6'd16: sda_out <= data_addr_r[11];
6'd17: sclk <= 1'b1;
6'd19: sclk <= 1'b0;
6'd20: sda_out <= data_addr_r[10];
6'd21: sclk <= 1'b1;
6'd23: sclk <= 1'b0;
6'd24: sda_out <= data_addr_r[9];
6'd25: sclk <= 1'b1;
6'd27: sclk <= 1'b0;
6'd28: sda_out <= data_addr_r[8]; //地址高八位最后一位
6'd29: sclk <= 1'b1;
6'd31: sclk <= 1'b0;
6'd32:begin
sda_dir <= 1'b0; //将三态门调整为输入状态
sda_out <= 1'b1;
end
6'd33: sclk <= 1'b1;
6'd34:begin
done <= 1'b1;
if(sda_in == 1'b1) //单byte操作完成标志
iic_ack <= 1'b1; //应答信号,拉高表示未应答
end
6'd35:begin
sclk <= 1'b0;
cnt <= 6'd0;
done <= 1'b0;
end
default: ;
endcase
end
st_addr_l:begin
cnt <= cnt + 1'b1;
case(cnt)
6'd0:begin
sda_dir <= 1'b1;
sda_out <= data_addr_r[7]; //无关位3
end
6'd1: sclk <= 1'b1;
6'd3: sclk <= 1'b0;
6'd4: sda_out <= data_addr_r[6]; //无关位2
6'd5: sclk <= 1'b1;
6'd7: sclk <= 1'b0;
6'd8: sda_out <= data_addr_r[5]; //无关位1
6'd9: sclk <= 1'b1;
6'd11: sclk <= 1'b0;
6'd12: sda_out <= data_addr_r[4]; //地址最高位
6'd13: sclk <= 1'b1;
6'd15: sclk <= 1'b0;
6'd16: sda_out <= data_addr_r[3];
6'd17: sclk <= 1'b1;
6'd19: sclk <= 1'b0;
6'd20: sda_out <= data_addr_r[2];
6'd21: sclk <= 1'b1;
6'd23: sclk <= 1'b0;
6'd24: sda_out <= data_addr_r[1];
6'd25: sclk <= 1'b1;
6'd27: sclk <= 1'b0;
6'd28: sda_out <= data_addr_r[0]; //地址高八位最后一位
6'd29: sclk <= 1'b1;
6'd31: sclk <= 1'b0;
6'd32:begin
sda_dir <= 1'b0; //将三态门调整为输入状态
sda_out <= 1'b1;
end
6'd33: sclk <= 1'b1;
6'd34:begin
done <= 1'b1;
if(sda_in == 1'b1) //单byte操作完成标志
iic_ack <= 1'b1; //应答信号,拉高表示未应答
end
6'd35:begin
sclk <= 1'b0;
cnt <= 6'd0;
done <= 1'b0;
end
default: ;
endcase
end
st_data_w:begin
cnt <= cnt + 1'b1;
case(cnt)
6'd0:begin
sda_dir <= 1'b1;
sda_out <= data_wr_r[7]; //无关位3
end
6'd1: sclk <= 1'b1;
6'd3: sclk <= 1'b0;
6'd4: sda_out <= data_wr_r[6];
6'd5: sclk <= 1'b1;
6'd7: sclk <= 1'b0;
6'd8: sda_out <= data_wr_r[5];
6'd9: sclk <= 1'b1;
6'd11: sclk <= 1'b0;
6'd12: sda_out <= data_wr_r[4];
6'd13: sclk <= 1'b1;
6'd15: sclk <= 1'b0;
6'd16: sda_out <= data_wr_r[3];
6'd17: sclk <= 1'b1;
6'd19: sclk <= 1'b0;
6'd20: sda_out <= data_wr_r[2];
6'd21: sclk <= 1'b1;
6'd23: sclk <= 1'b0;
6'd24: sda_out <= data_wr_r[1];
6'd25: sclk <= 1'b1;
6'd27: sclk <= 1'b0;
6'd28: sda_out <= data_wr_r[0]; //地址最低位
6'd29: sclk <= 1'b1;
6'd31: sclk <= 1'b0;
6'd32:begin
sda_dir <= 1'b0; //将三态门调整为输入状态
sda_out <= 1'b1;
end
6'd33: sclk <= 1'b1;
6'd34:begin
done <= 1'b1;
if(sda_in == 1'b1) //单byte操作完成标志
iic_ack <= 1'b1; //应答信号,拉高表示未应答
end
6'd35:begin
sclk <= 1'b0;
cnt <= 6'd0;
done <= 1'b0;
end
default: ;
endcase
end
st_sladder_r:begin
cnt <= cnt + 1'b1;
case(cnt)
6'd0:begin
sda_dir <= 1'b1; //三态门调整为输入
sda_out <= 1'b1;//数据线拉高,为开始标志做准备
end
6'd1: sclk <= 1'b1;
6'd2: sda_out <= 1'b0;
6'd3: sclk <= 1'b0;
6'd4: sda_out <= SLAVE_ADDR[6];
6'd5: sclk <= 1'b1;
6'd7: sclk <= 1'b0;
6'd8: sda_out <= SLAVE_ADDR[5];
6'd9: sclk <= 1'b1;
6'd11: sclk <= 1'b0;
6'd12: sda_out <= SLAVE_ADDR[4];
6'd13: sclk <= 1'b1;
6'd15: sclk <= 1'b0;
6'd16: sda_out <= SLAVE_ADDR[3];
6'd17: sclk <= 1'b1;
6'd19: sclk <= 1'b0;
6'd20: sda_out <= SLAVE_ADDR[2];
6'd21: sclk <= 1'b1;
6'd23: sclk <= 1'b0;
6'd24: sda_out <= SLAVE_ADDR[1];
6'd25: sclk <= 1'b1;
6'd27: sclk <= 1'b0;
6'd28: sda_out <= SLAVE_ADDR[0];
6'd29: sclk <= 1'b1;
6'd31: sclk <= 1'b0;
6'd32: sda_out <= 1'b1; //读
6'd33: sclk <= 1'b1;
6'd35: sclk <= 1'b0;
6'd36:begin
sda_dir <= 1'b0; //将三态门调整为输入状态
sda_out <= 1'b1;
end
6'd37: sclk <= 1'b1;
6'd38:begin
done <= 1'b1;
if(sda_in == 1'b1) //单byte操作完成标志
iic_ack <= 1'b1; //应答信号,拉高表示未应答
end
6'd39:begin
sclk <= 1'b0;
cnt <= 6'd0;
done <= 1'b0;
end
default: ;
endcase
end
st_data_r:begin
cnt <= cnt + 1'b1;
case(cnt)
//*******
6'd0: sda_dir <= 1'b0;
6'd1: begin
sclk <= 1'b1;
data_re_r[7] <= sda_in;
end
6'd3: sclk <= 1'b0;
6'd5: begin
sclk <= 1'b1;
data_re_r[6] <= sda_in;
end
6'd7: sclk <= 1'b0;
6'd9: begin
sclk <= 1'b1;
data_re_r[5] <= sda_in;
end
6'd11: sclk <= 1'b0;
6'd13:begin
sclk <= 1'b1;
data_re_r[4] <= sda_in;
end
6'd15: sclk <= 1'b0;
6'd17: begin
sclk <= 1'b1;
data_re_r[3] <= sda_in;
end
6'd19: sclk <= 1'b0;
6'd21: begin
sclk <= 1'b1;
data_re_r[2] <= sda_in;
end
6'd23: sclk <= 1'b0;
6'd25: begin
sclk <= 1'b1;
data_re_r[1] <= sda_in;
end
6'd27: sclk <= 1'b0;
6'd29: begin
sclk <= 1'b1;
data_re_r[0] <= sda_in;
end
6'd31: sclk <= 1'b0;
//******************
// 7'd32: begin
// sda_dir <= 1'b1;
// sda_out <= 1'b1;
// end
6'd33: sclk <= 1'b1;
6'd34: done <= 1'b1;
6'd35: begin
sclk <= 1'b0;
done <= 1'b0;
cnt <= 6'd0;
end
default: ;
endcase
end
st_done_byte:begin
cnt <= cnt + 1'b1;
case(cnt)
//**************时间不够
6'd0:begin
sda_dir <= 1'b1; //将三态门置为输出状态
sda_out <= 1'b0; //将数据线拉低,为结束标志做准备
end
6'd1:sclk <= 1'b1;
6'd2:sda_out <= 1'b1;//结束标志
6'd6:begin
done <= 1'b1;
iic_done <= 1'b1;
data_re <= data_re_r;
end
6'd7:begin
done <= 1'b0;
iic_done <= 1'b0;
cnt <= 6'd0;
end
default: ;
endcase
end
default: ;
endcase
end
end
endmodule
(5)上板验证
led_s对应的led灯被点亮,验证成功。
(6)仿真
如果需要仿真,这里提供了一个AT24C64的仿真模型(用verilog仿真的AT24C64从机),如果需要仿真,将下面模型例化到顶层模块(即将顶层模块中最下面注释的模块取消注释)文章来源:https://www.toymoban.com/news/detail-759686.html
`timescale 1ns/1ns
`define timeslice 600 //在SCL下降沿延时600ns输出应答信号 400KHz
//`define timeslice 300
module EEPROM_AT24C64(
scl,
sda
);
input scl; //串行时钟线
inout sda; //串行数据线
reg out_flag; //SDA数据输出的控制信号
reg[7:0] memory[8191:0]; //数组模拟存储器
reg[12:0]address; //地址总线
reg[7:0]memory_buf; //数据输入输出寄存器
reg[7:0]sda_buf; //SDA数据输出寄存器
reg[7:0]shift; //SDA数据输入寄存器
reg[7:0]addr_byte_h; //EEPROM存储单元地址高字节寄存器
reg[7:0]addr_byte_l; //EEPROM存储单元地址低字节寄存器
reg[7:0]ctrl_byte; //控制字寄存器
reg[1:0]State; //状态寄存器
integer i;
//---------------------------
parameter
r7 = 8'b1010_1111, w7 = 8'b1010_1110, //main7
r6 = 8'b1010_1101, w6 = 8'b1010_1100, //main6
r5 = 8'b1010_1011, w5 = 8'b1010_1010, //main5
r4 = 8'b1010_1001, w4 = 8'b1010_1000, //main4
r3 = 8'b1010_0111, w3 = 8'b1010_0110, //main3
r2 = 8'b1010_0101, w2 = 8'b1010_0100, //main2
r1 = 8'b1010_0011, w1 = 8'b1010_0010, //main1
r0 = 8'b1010_0001, w0 = 8'b1010_0000; //main0
//---------------------------
assign sda = (out_flag == 1) ? sda_buf[7] : 1'bz;
//------------寄存器和存储器初始化---------------
initial begin
addr_byte_h = 0;
addr_byte_l = 0;
ctrl_byte = 0;
out_flag = 0;
sda_buf = 0;
State = 2'b00;
memory_buf = 0;
address = 0;
shift = 0;
for(i=0;i<=8191;i=i+1)
memory[i] = 0;
end
//启动信号
always@(negedge sda) begin
if(scl == 1) begin
State = State + 1;
if(State == 2'b11)
disable write_to_eeprom;
end
end
//主状态机
always@(posedge sda) begin
if(scl == 1) //停止操作
stop_W_R;
else begin
casex(State)
2'b01: begin
read_in;
if(ctrl_byte == w7 || ctrl_byte == w6
|| ctrl_byte == w5 || ctrl_byte == w4
|| ctrl_byte == w3 || ctrl_byte == w2
|| ctrl_byte == w1 || ctrl_byte == w0) begin
State = 2'b10;
write_to_eeprom; //写操作
end
else
State = 2'b00;
//State = State;
end
2'b11:
read_from_eeprom;
default:
State = 2'b00;
endcase
end
end //主状态机结束
//操作停止
task stop_W_R;
begin
State = 2'b00;
addr_byte_h = 0;
addr_byte_l = 0;
ctrl_byte = 0;
out_flag = 0;
sda_buf = 0;
end
endtask
//读进控制字和存储单元地址
task read_in;
begin
shift_in(ctrl_byte);
shift_in(addr_byte_h);
shift_in(addr_byte_l);
end
endtask
//EEPROM的写操作
task write_to_eeprom;
begin
shift_in(memory_buf);
address = {addr_byte_h[4:0], addr_byte_l};
memory[address] = memory_buf;
State = 2'b00;
end
endtask
//EEPROM的读操作
task read_from_eeprom;
begin
shift_in(ctrl_byte);
if(ctrl_byte == r7 || ctrl_byte == w6
|| ctrl_byte == r5 || ctrl_byte == r4
|| ctrl_byte == r3 || ctrl_byte == r2
|| ctrl_byte == r1 || ctrl_byte == r0) begin
address = {addr_byte_h[4:0], addr_byte_l};
sda_buf = memory[address];
shift_out;
State = 2'b00;
end
end
endtask
//SDA数据线上的数据存入寄存器,数据在SCL的高电平有效
task shift_in;
output[7:0]shift;
begin
@(posedge scl) shift[7] = sda;
@(posedge scl) shift[6] = sda;
@(posedge scl) shift[5] = sda;
@(posedge scl) shift[4] = sda;
@(posedge scl) shift[3] = sda;
@(posedge scl) shift[2] = sda;
@(posedge scl) shift[1] = sda;
@(posedge scl) shift[0] = sda;
@(negedge scl) begin
#`timeslice;
out_flag = 1; //应答信号输出
sda_buf = 0;
end
@(negedge scl) begin
#`timeslice;
out_flag = 0;
end
end
endtask
//EEPROM存储器中的数据通过SDA数据线输出,数据在SCL低电平时变化
task shift_out;
begin
out_flag = 1;
for(i=6; i>=0; i=i-1) begin
@(negedge scl);
#`timeslice;
sda_buf = sda_buf << 1;
end
@(negedge scl) #`timeslice sda_buf[7] = 1; //非应答信号输出
@(negedge scl) #`timeslice out_flag = 0;
end
endtask
endmodule
//eeprom.v文件结束
vivado联合modelsim仿真结果:
可以看出,经验证,led被点亮。
文章来源地址https://www.toymoban.com/news/detail-759686.html
到了这里,关于【接口协议】FPGA实现IIC协议对EEPROM进行数据存储与读取(AT24C64)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!