【接口协议】FPGA实现IIC协议对EEPROM进行数据存储与读取(AT24C64)

这篇具有很好参考价值的文章主要介绍了【接口协议】FPGA实现IIC协议对EEPROM进行数据存储与读取(AT24C64)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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上,每一个从器件都有一个唯一的器件地址,主设备每次通信都会发送一个器件地址,与对应器件地址的从器件进行通信。
at24c64数据手册,【接口协议】FPGA,fpga开发
三态门:IIC是半双工通信,在数据线SDA上分时进行发送和接收数据,双向数据线SDA要通过三态门实现:
at24c64数据手册,【接口协议】FPGA,fpga开发
三态门使用:
定义数据线sdainout类型;
由条件语句控制三态门,控制端sda_dir拉高,三态门导通,数据线sda数据为FPGA输出数据sda_out;控制端sda_dir拉低,三态门关闭,门电路为高阻态;门电路为高阻态,fpga输入数据sda_in为数据线上数据sdaat24c64数据手册,【接口协议】FPGA,fpga开发

(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由低变为高,为传输结束标志。
at24c64数据手册,【接口协议】FPGA,fpga开发

2.EEPROM(使用的EEPROM型号:AT24C64)

(1)EEPROM简介

EEPROM,电可擦除可编程只读存储器,断电后数据不会丢失。

(2)芯片AT24C64硬件接口

at24c64数据手册,【接口协议】FPGA,fpga开发
at24c64数据手册,【接口协议】FPGA,fpga开发
A0~A2 :器件地址,在实现通信时,主机会发送三位器件地址,当三位器件地址与A2~A0 电平对应时,主从之间实现通信。这也是主从通信时片选的实现。所以在主从通信时,不同的从机,A2~A0接的电平不一样;
WP:写保护,当写保护引脚连接至 GND 时,芯片可以正常写,当写保护引脚连接至 VCC 时,使能写保护功能,此时禁止向芯片写入数据,只能进行读操作;
SCL和SDA即IIC通信的时钟线和数据线。

(2) AT24C64读写时序

A 序言
AT24C64有8k个字节的存储空间,分成了256页,每页32个字节。且有多种读写模式,有字节写、页写、当前地址读、随机地址读、连续读模式。
B 写数据

(a)字节写
时序图:
at24c64数据手册,【接口协议】FPGA,fpga开发

说明:一次字节写在开始(START)结束(STOP)标志之间,包含了一个字节的器件地址,两个字节的数据地址,和一个字节的数据,且每个字节传输完后都有一位应答信号ACK(低电平)
器件地址:8位器件地址,高4位默认1010,后接3位从机对应的器件地址A2~A0,最后还有一位读写控制位(R/W)(此时为写数据给0,后面读数据给1);
at24c64数据手册,【接口协议】FPGA,fpga开发
数据地址:前面提到,AT24C64,有8k个字节数据,每个字节数据对应一个地址,共13位地址,用两个字节传输数据地址,在第一个数据地址低五位传输数据地址的高五位,在第二个数据地址字节传输数据地址低8位。(图中的*号表示无关位,因为这个图截自数据手册,手册同时包含了AT24C64和AT24C32两个芯片的说明,AT24C32存储空间只有AT24C64的一半,所以会少一位数据地址,所以+号对于AT24C64芯片来说是数据地址最高位,对于AT24C32来说也是无关位);
数据:传给EEPROM的一个字节8位数据。

(b)页写
时序图:
at24c64数据手册,【接口协议】FPGA,fpga开发
说明:与字节写不同的是,在给完数据地址后,不仅仅写了一个字节数据,是连续写了多个字节数据;
注意:我们只给了第一个数据的存储地址,后面字节数据的13位存储地址高8位保持不变低5位在给的存储地址的基础上累加,因为前面提到,芯片的存储空间是按页排布(256页x32字节),相当于页选信号(高8位)不变,在当前页内存储地址(低5位)累加,当连续存入的数据超过了当前页的存储空间(32个字节),数据地址又会从当前页第一个数据重新存入。

(c )注意(注意注意注意:强调一下)
完成单次写操作,AT24C64并不是立刻将数据存入,而是需要需要花费一定时间,数据手册上说是最大是10ms,实际上达不到10ms,大家可以试一下,给的代码中是10ms一次写操作。
at24c64数据手册,【接口协议】FPGA,fpga开发
C 随机地址读(只介绍这一种,其他两种少见)

(a)时序图
at24c64数据手册,【接口协议】FPGA,fpga开发
(b)说明
随机地址读为伪写操作+读操作伪写操作给读操作提供地址,对应字节写的开始标志+器件地址+数据地址,读操作包含一个字节器件地址和一个字节数据,最后一个停止标志;
注意:随机地址读数据后无应答信号

3.实例

(1)功能描述

使用字节写和随机地址读模式,给EEPROM的0到63地址中,存入数据0到63,并从任意地址中取出一个数据进行验证,验证正确点亮led灯。

(2)结构框图

at24c64数据手册,【接口协议】FPGA,fpga开发
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**,时钟状态就要翻转一次(只针对于偶数倍分频器,奇数倍分频器要使用时钟双沿,或者先倍频)。

at24c64数据手册,【接口协议】FPGA,fpga开发

其次,实现存入数据读出数据验证数据三个状态:实现每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数据手册,【接口协议】FPGA,fpga开发

说明,因为使用的是字节写随机地址读,根据上图中由AT24C64数据手册字节写模式随机地址读模式的时序图可以得出:
字节写:包含写器件地址(开始标志只有一位,将开始标志包含入内)、高位数据地址低位数据地址写入数据结束状态五个状态;
随机地址读:包含写器件地址(开始标志只有一位,将开始标志包含入内)、高位数据地址低位数据地址读操作器件地址读出数据结束状态六个状态。
同时在不传输数据为空闲状态,所以整合后的状态机如上图所示,共8个状态,每个状态时序说明如下:

a 空闲状态(st_idle
对控制信号暂存,使能(data_val)有效时对输入数据进行暂存,包括写的数据(data_wr),地址(data_addr),读写控制信号(rw_con);

b写器件地址(st_sladder_w
at24c64数据手册,【接口协议】FPGA,fpga开发
c 地址高八位(st_addr_h
at24c64数据手册,【接口协议】FPGA,fpga开发
d 地址低八位(st_addr_l
at24c64数据手册,【接口协议】FPGA,fpga开发
e写入数据(st_data_w
at24c64数据手册,【接口协议】FPGA,fpga开发

f读器件地址(st_sladder_r
at24c64数据手册,【接口协议】FPGA,fpga开发
g读入数据(st_data_r
at24c64数据手册,【接口协议】FPGA,fpga开发
h结束状态(st_done_byte
结束标志,产生IIC操作完成标志信号(iic_down)。且当是读数据时,将读到的数据输出。
at24c64数据手册,【接口协议】FPGA,fpga开发

(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灯被点亮,验证成功。
at24c64数据手册,【接口协议】FPGA,fpga开发

(6)仿真

如果需要仿真,这里提供了一个AT24C64的仿真模型(用verilog仿真的AT24C64从机),如果需要仿真,将下面模型例化到顶层模块(即将顶层模块中最下面注释的模块取消注释)

`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被点亮。
at24c64数据手册,【接口协议】FPGA,fpga开发文章来源地址https://www.toymoban.com/news/detail-759686.html

到了这里,关于【接口协议】FPGA实现IIC协议对EEPROM进行数据存储与读取(AT24C64)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【接口协议】FPGA实现SPI协议基于ADC128S022进行模拟信号采集

    使用vivado联合modelsim实现SPI协议基于ADC128S022进行模拟信号连续采集。 SPI是串行外设接口,是一种同步/全双工/主从式接口。通常由四根信号线构成: CS_N :片选信号,主从式接口,可以有多个从机,用片选信号进行从机选择; SCLK :串行时钟线,由主机提供给从机; MISO :主机

    2024年02月07日
    浏览(51)
  • IIC通信协议详解 & PCF8591应用(Verilog实现FPGA)

    该文章结合PCF8591 8-bit AD/DA 模数/数模转换器来详细介绍IIC通信协议,尽量做到条理清晰,通俗易懂。该文图片均从PCF8591手册中截取,一定程度上引导读者学习阅读data sheet。 之后可能会更新 如何将IIC的Verilog实现变为一个IP核,并在pynq-Z2板子上使用 。 2.1 地址位 在I2C总线系统

    2024年02月04日
    浏览(60)
  • 【【IIC模块Verilog实现---用IIC协议从FPGA端读取E2PROM】】

    下面是 design 设计 下面是testbench 下面是注意事项 因为时钟的不同 我们先设计出本次时钟所需要的dri_clk 在配置完dri_clk 之后 我们需要做的是对整个I2C结构 进行状态机的 书写 建议 写成经典的三段状态机的形式 同步时序描述状态转移 组合逻辑判断状态转移条件 时序电路描述

    2024年02月03日
    浏览(52)
  • STM32软件模拟IIC时序实现与EEPROM的通信

                       IIC简介  IIC物理层 用软件模拟IIC时序         一、空闲状态(初始化):SCL 和SDA都保持高电平         二、开始信号 :SCL为高电平期间,SDA由高电平变为低电平。         三、停止信号:SCL为高电平期间,SDA由低电平变为高电平   

    2024年02月09日
    浏览(77)
  • FPGA:三大协议(IIC、UART、SPI)之IIC

    摘要:1、本文讲述IIC的物理层面的结构(使用iic工作的物理层面的连接);2、本文讲解协议层面的通信交流格式(IIC时序);3、提供一个主机和从机的一个verilog代码;4、本文的主从机指的是:板子一号作为主机,发送数据给作为从机的板子二号;注意:在实际应用中,一

    2024年02月06日
    浏览(45)
  • 【FLASH】STM32内部Flash模拟EEPROM磨损均衡算法--存储设备擦写均衡自带掉电保护接口-如何在同等存储空间下增加FLASH寿命呢?往下看-STM32F334实现FLASH擦写均衡

    主要思想就是将FLASH 分配一块区域给我们的管理机,然后用索引的方式累积写FLASH,中途不进行擦写,在存满整个分区时进行统一擦写,读取根据ID进行读取,并且加上了数据校验,异常回调。主要用于存储系统配置,运行记录等。支持多个存储管理机管理不同的区域。   P

    2024年02月04日
    浏览(63)
  • 通信协议详解(二):IIC总线协议(传输时序+数据格式+设计实现)

        IIC(Inter-Integrated Circuit)是一种具有两线传输的串行通信总线,使用多主从架构,由飞利浦公司在1980年为了让主板、嵌入式系统或手机连接低速周边设备而提出,适用于数据量不大且传输距离短的场合。     IIC串行总线由两根信号线组成,一根是双向的 数据线

    2024年02月04日
    浏览(54)
  • FPGA:三大协议(UART、IIC、SPI)之SPI

    摘要:1、本文介绍SPI物理层面连接(通过哪几条线通信),2、本文介绍SPI时序(通过哪种方式进行器件之间交流)。3、提供主机和从机verilog代码。4、仅供自己参考 一、SPI物理层连接 (1)有四根线连接:CS_N(片选信号--主机发出)、miso(从机发出,主机接收)、mosi(主机发

    2024年02月14日
    浏览(42)
  • 【51单片机】EEPROM-IIC实验(按键控制数码管)

    目录   🎁I2C总线 ​编辑 🎁代码 🏳️‍🌈main.c 🏳️‍🌈i2.c 🎆代码分析   I2C总线是Philips公司在八十年代初推出的一种串行、半双工的总线,主要用于近距离、低速的芯片之间的通信;I2C总线有两根双向的信号线,一根 数据线SDA 用于收发数据,一根 时钟线SCL 用于通信

    2024年02月11日
    浏览(50)
  • FPGA实现SPI协议基于ADC128S022进行模拟信号采集

    使用vivado联合modelsim实现SPI协议基于ADC128S022进行模拟信号连续采集。 SPI是串行外设接口,是一种同步/全双工/主从式接口。通常由四根信号线构成: CS_N :片选信号,主从式接口,可以有多个从机,用片选信号进行从机选择; SCLK :串行时钟线,由主机提供给从机; MISO :主机

    2024年02月14日
    浏览(44)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包