FPGA实现的SPI协议(二)----基于SPI接口的FLASH芯片M25P16的使用

article/2025/8/23 7:13:47

写在前面

        SPI协议系列文章:

                FPGA实现的SPI协议(一)----SPI驱动

                FPGA实现的SPI协议(二)----基于SPI接口的FLASH芯片M25P16的使用

        在上篇文章,简要介绍了SPI协议,编写了SPI协议的FPGA驱动,但是在验证环节,仅仅验证了发送时序,而没有与从机进行通信验证,未免测试不够周全。本文通过对FLASH芯片M25P16的仿真模型进行一系列测试,从而验证SPI驱动的代码的正确性,同时对M25P16进行一个了解。


1、M25P16芯片

1.1、概述

        M25P16是一款带有先进写保护机制和高速SPI总线访问的2MB串行Flash存储器,该存储器主要特点:

  • 2M字节的存储空间,分32个扇区,每个扇区256页,每页256字节;
  • 写入1页数据所需时间为0.64ms(典型值);能单块擦除和整块擦除:
  • 2.7~3.6 V单电源供电电压;
  • SPI总线和50 MHz数据传输时钟频率;
  • 每扇区擦写次数保证10万次、数据保存期限至少20年。

        该款器件特别适用于一体化打印机、PC主板、机顶盒、CD唱机和DVD视盘机、数字电视、数码相机、图形卡和平面显示器等各种应用的代码和数据存储需求。

1.2、引脚

        其引脚描述如下:

  • DQ1:数据输出,相当于SPI总线的主机输入、从机输出MISO
  • DQ0:数据输入,相当于SPI总线的主机输出、从机输入MOSI
  • C:时钟信号,相当于SPI总线的SCLK
  • S#:片选信号,相当于SPI总线的片选信号CS
  • HOLD:在选中期间期间输出高阻态,实际上比较像SDRAM的“掩码”
  • W#:写保护,低电平有效,在写保护有效时无法写入数据
  • VCC:电源信号
  • VSS :电源地

1.3、SPI模式

        M25P16根据SPI时钟信号的高低电平自适应的支持SPI通讯模式的模式0和模式3:

1.4、存储架构

        M25P16一共2MB字节的存储空间,分32个扇区(SECTOR),每个扇区256页(PAGE),每页256字节(BYTE)。每个字节的的存储地址由扇区地址(8bit)+页地址(8bit)+字节地址(8bit)构成,地址表如下:

1.5、指令表

        M25P16支持页写入,全擦除,扇区擦除,读取数据等一系列指令,具体指令表如下:

1.6、其他

        需要注意的是M25P16支持的频率如下,所以在我们的仿真实验中选择12.5M这个频率。

        此外,页写入, 全擦除,扇区擦除等指令在发出后,仍需要一定的时间才能真正执行完,各个指令所需的时间如下:

        

        最后需要注意的一点是,在两个指令之间需要间隔一定的时间(比如发送全擦除指令前需要发送写使能指令,在这两个指令之间就需要间隔一定的时间),具体时间如下:

2、指令测试

        在这一章针对集中常用的指令进行代码编写及仿真测试。

2.1、页写(PAGE PROGRAM)

2.1.1、时序

        页写(Page Program)操作,简称 PP,操作指令为 8’b0000_0010(02h)。页写指令是根据写入数据将存储单元中的“1” 置为“0”,实现数据的写入。在写入页写指令之前,需要先写入写使能(WREN)指令,将芯片设置为写使能锁存(WEL)状态;随后要拉低片选信号,写入页写指令、扇区地址、页 地址、字节地址,紧跟地址写入要存储在 Flash 的字节数据,在指令、地址以及数据写入过程中,片选信号始终保持低电平,待指令、地址、数据被芯片锁存后,将片选信号 高;片选信号拉高后,等待一个完整的页写周期(tPP),才能完成 Flash 芯片的页写操作。

        Flash 芯片中一页最多可以存储 256 字节数据,这也表示页写操作一次最多向 Flash 芯片写入 256 字节数据。如字节首地址为 8’0000_1111,字节首地址地址到末地址之间的存储单元个数为 241 个,即本页最多可写入 241 字节数据,若写入数据为 200 个字节,数据可以被正确写入; 若写入数据为 256 个字节,前 241 个字节的数据可以正确写入 Flash 芯片,而超出的 15 个字节就以本页的首地址 8’b0000_0000 为数据写入首地址顺序写入,覆盖本页原有的前 15 个字节的数据。

        页写时序如下:

2.1.2、Verilog代码

        Verilog代码分为3个模块:SPI驱动模块spi_drive、SPI页写控制模块spi_page_program_ctrl和页写顶层模块spi_page_program。

  • SPI驱动模块spi_drive:提供SPI模式0的读写驱动,具体可参见:  FPGA实现的SPI协议(一)----SPI驱动
  • SPI页写控制模块spi_page_program_ctrl:该模块使用一段式状态机编写,功能就是调用SPI驱动模块,发送页写指令,然后发送扇区地址+页地址+字节地址,接着给SPI总线上发送一定量的数据(可设置)。
  • 页写顶层模块spi_page_program:例化前面两个子模块。

        SPI页写控制模块spi_page_program_ctrl代码如下:

//SPI页写控制模块
`timescale 1ns/1ns		//时间单位/精度
module spi_page_program_ctrl
#(parameter 	SECTOR_ADDR = 8'b0000_0000, 		//扇区地址parameter	PAGE_ADDR   = 8'b0000_0000,			//页地址parameter	BYTE_ADDR   = 8'b0000_0000			//字节地址
)
(input               sys_clk		, 				// 全局时钟50MHzinput               sys_rst_n	, 				// 复位信号,低电平有效input  		        send_done	, 				// 主机发送一个字节完毕标志位output  reg         spi_start	,				// 发送传输开始信号,一个高电平output  reg        	spi_end		,				// 发送传输结束信号,一个高电平output  reg  [7:0]  data_send    				// 要发送的数据         
);//指令定义
localparam 	WR_EN 		 = 8'b0000_0110, 			//写使能指令	PAGE_PROGRAM = 8'b0000_0010;			//页写指令
localparam	DATA_MAX 	 = 8'd10;					//最大数据写入个数//reg define		
reg	[7:0]	flow_cnt;								//状态跳转计数器
reg	[7:0]	cnt_wait;								//上电等待计数器
reg	[7:0]	data_cnt;								//数据写入个数计数器always@(posedge sys_clk or negedge sys_rst_n)beginif(!sys_rst_n)begindata_send <= 8'd0;spi_start <= 1'b0;	spi_end <= 1'b0;	flow_cnt <= 1'd0;cnt_wait <= 8'd0;data_cnt <= 8'd0;endelse beginspi_start <= 1'b0;							//便于生成脉冲信号spi_end <= 1'b0;                            //便于生成脉冲信号case(flow_cnt)'d0:beginif(cnt_wait == 100)begin			//上电后等待稳定cnt_wait <= 8'd0;flow_cnt <= flow_cnt + 1'd1;endelse begincnt_wait <= cnt_wait + 1'd1;flow_cnt <= flow_cnt;								endend'd1:begin									data_send <= WR_EN;					//写使能指令spi_start <= 1'b1;					//拉高spi开始通讯信号flow_cnt <= flow_cnt + 1'd1;end'd2:beginif(send_done)begin					//主机一个字节数据被发送完成flow_cnt <= flow_cnt + 1'd1;spi_end <= 1'b1;				//结束第1次SPI通信endelseflow_cnt <= flow_cnt;end	'd3:beginif(cnt_wait == 10)begin				//等待200ns,两次命令的间隔时间cnt_wait <= 8'd0;				//等待计数器清零flow_cnt <= flow_cnt + 1'd1;endelse begincnt_wait <= cnt_wait + 1'd1;flow_cnt <= flow_cnt;								endend'd4:begin									data_send <= PAGE_PROGRAM;          //页写指令spi_start <= 1'b1;					//拉高spi开始通讯信号flow_cnt <= flow_cnt + 1'd1;end				'd5:begin								//发送扇区地址if(send_done)begin					//指令被发送完成flow_cnt <= flow_cnt + 1'd1;data_send <= SECTOR_ADDR;		//数据为扇区地址endelse beginflow_cnt <= flow_cnt;data_send <= data_send;							endend	'd6:begin								//发送页地址if(send_done)begin					//发送完成flow_cnt <= flow_cnt + 1'd1;data_send <= PAGE_ADDR;			//数据为页地址地址endelse beginflow_cnt <= flow_cnt;data_send <= data_send;							endend	'd7:begin								//发送字节地址if(send_done)begin					//指令被发送完成flow_cnt <= flow_cnt + 1'd1;data_send <= BYTE_ADDR;			//数据为字节地址endelse beginflow_cnt <= flow_cnt;data_send <= data_send;							endend	'd8:begin								//停留在这个状态if(send_done)begin					//指令被发送完成flow_cnt <= flow_cnt + 1'd1;data_send <= 8'd0;				//发送数据从0开始end	else flow_cnt <= flow_cnt;										end				'd9:begin										//写入数据if(send_done)begin							//主机一个字节数据被发送完成if(data_cnt == DATA_MAX - 1'b1)begin	//数据全部写入flow_cnt <= flow_cnt + 1'd1;spi_end <= 1'b1;					//结束第1次SPI通信data_cnt <= 8'd0;data_send <= 8'd0;endelse beginflow_cnt <= flow_cnt;data_cnt <= data_cnt + 8'd1;		//计数器累加1	// data_send <= data_send + 8'd2;	//数据累加2	data_send <= data_send + 8'd4;		//数据累加4	endendelse beginflow_cnt <= flow_cnt;data_send <= data_send;data_cnt <= data_cnt;endend					'd10:begin										//停留在这个状态flow_cnt <= flow_cnt;end				default:;endcaseend
endendmodule

        页写顶层模块spi_page_program代码如下:

`timescale 1ns/1ns		//时间单位/精度
//页写
module spi_page_program(
// 系统接口input	sys_clk		,		//全局时钟50MHzinput	sys_rst_n	,   	//复位信号,低电平有效
// SPI物理接口							input	spi_miso	,   	//SPI串行输入,用来接收从机的数据output	spi_sclk	,   	//SPI时钟output	spi_cs    	,   	//SPI片选信号,低电平有效output	spi_mosi	    	//SPI输出,用来给从机发送数据   
);
parameter 	SECTOR_ADDR = 8'b0000_0000; 		//扇区地址
parameter	PAGE_ADDR   = 8'b0000_0000;			//页地址
parameter	BYTE_ADDR   = 8'b0000_0000;			//字节地址wire			spi_start	;	//发送传输开始信号,一个高电平
wire			spi_end		;   //发送传输结束信号,一个高电平
wire	[7:0]  	data_send   ;   //要发送的数据
wire	[7:0]  	data_rec   	;   //接收到的数据
wire         	send_done	;   //主机发送一个字节完毕标志
wire         	rec_done	;   //主机接收一个字节完毕标志//------------<例化模块>----------------------------------------------------------------
//页写模块
spi_page_program_ctrl
#(.SECTOR_ADDR 	(SECTOR_ADDR),.PAGE_ADDR   	(PAGE_ADDR  ),.BYTE_ADDR   	(BYTE_ADDR  )
)	
spi_sector_erase_ctrl_inst
(.sys_clk		(sys_clk	), 			.sys_rst_n		(sys_rst_n	), 			.send_done		(send_done	), 			.spi_start		(spi_start	),			.spi_end		(spi_end	),			.data_send    	(data_send	)						
);
//SPI驱动
spi_drive	spi_drive_inst(.sys_clk		(sys_clk	), 			.sys_rst_n		(sys_rst_n	), 			.spi_start		(spi_start	), 			.spi_end		(spi_end	), 			.data_send		(data_send	), 			.data_rec  		(data_rec	), 			.send_done		(send_done	), 			.rec_done		(rec_done	), 			.spi_miso		(spi_miso	), 			.spi_sclk		(spi_sclk	), 			.spi_cs    		(spi_cs		), 			.spi_mosi		(spi_mosi	)			
);endmodule

2.1.3、Testbench及仿真结果

        Testbench比较简单直接例化SPI页写模块和仿真模型m25p16即可,需要注意的是SPI的的页写操作需要一定的时间(前面已经提到过--5ms)。

        仿真结果如下:

  

        从地址24'h0开始,一次写入数据0x00,0x02,0x04···0x12一共10个数据,可以看到在MOSI上,依次出现了上述10个数据,说明符合SPI协议规范。

        命令窗口打印内容如下(单位:ps):

        在约12us处开始进行页写操作,5ms后页写操作完成,同样符合芯片参数。

2.1.4、上板验证

        同读数据操作一同验证,详见2.2.4章节。 

2.2、读数据(READ DATA BYTES)

2.2.1、时序

        读数据操作,操作指令为 8’b0000_0011(03h),要执行数据读指令,首先拉低片选信号选中 Flash 芯片,随后写入数据读(READ)指 令,紧跟指令写入 3 字节的数据读取首地址,指令和地址会在串行时钟上升沿被芯片锁存。随后存储地址对应存储单元中的数据在串行时钟下降沿通过串行数据总线输出。 数据读取首地址可以为芯片中的任何一个有效地址,使用数据读(READ)指令可以对芯 片内数据连续读取,当首地址数据读取完成,会自动对首地址的下一个地址进行数据读取。若最高位地址内数据读取完成,会自动跳转到芯片首地址继续进行数据读取,只有再次拉高片选信号,才能停止数据读操作,否者会对芯片执行无线循环读操作。具体时序如下:

2.2.2、Verilog代码

        Verilog代码分为3个模块:SPI驱动模块spi_drive、SPI读数据控制模块spi_read_ctrl和例化前面两个子模块的读数据顶层模块spi_read。

  • SPI驱动模块spi_drive:提供SPI模式0的读写驱动,具体可参见:  FPGA实现的SPI协议(一)----SPI驱动
  • SPI读数据控制模块spi_read_ctrl:该模块使用一段式状态机编写,功能就是调用SPI驱动模块,发送读数据指令,然后发送扇区地址+页地址+字节地址,接着从SPI总线上接收一定量的数据(可设置)。
  • 读数据顶层模块spi_read:例化前面两个子模块。

        SPI读数据控制模块spi_read_ctrl代码如下:

//FLASH读数据控制模块:合适的调用SPI驱动模块
module spi_read_ctrl
#(parameter	BYTE_MAX 	= 8'd10			,		//一共读取多少个BYTE的数据SECTOR_ADDR = 8'b0000_0000	,		//扇区地址PAGE_ADDR   = 8'b0000_0000	,		//页地址BYTE_ADDR   = 8'b0000_0000			//字节地址
)
(input               sys_clk		, 				// 全局时钟50MHzinput               sys_rst_n	, 				// 复位信号,低电平有效input		[7:0]	data_rec  	, 				// 接收到的数据input				rec_done	, 				// 主机接收一个字节完毕标志位	input  		        send_done	, 				// 主机发送一个字节完毕标志位output  reg         spi_start	,				// 发送传输开始信号,一个高电平output  reg        	spi_end		,				// 发送传输结束信号,一个高电平output  reg  [7:0]  data_send    				// 要发送的数据         
);	//指令定义	
localparam	READ	 	= 8'h03; 					//读数据指令//reg define		
reg	[7:0]	flow_cnt;								//状态跳转计数器
reg	[7:0]	data_cnt;								//数据接收计数器
reg	[7:0]	cnt_wait;								//上电等待计数器always@(posedge sys_clk or negedge sys_rst_n)beginif(!sys_rst_n)begin	                            //复位状态data_send <= 8'd0;spi_start <= 1'b0;	spi_end <= 1'b0;	flow_cnt <= 1'd0;cnt_wait <= 8'd0;data_cnt <= 8'd0;endelse beginspi_start <= 1'b0;							//便于生成脉冲信号spi_end <= 1'b0;                            //便于生成脉冲信号case(flow_cnt)'d0:beginif(cnt_wait == 100)begin			//上电后等待稳定cnt_wait <= 8'd0;flow_cnt <= flow_cnt + 1'd1;endelse begincnt_wait <= cnt_wait + 1'd1;flow_cnt <= flow_cnt;								endend'd1:begin								//发送读数据指令	data_send <= READ;					//读数据指令spi_start <= 1'b1;					//拉高spi开始通讯信号flow_cnt <= flow_cnt + 1'd1;end	'd2:begin								//发送扇区地址if(send_done)begin					//指令被发送完成flow_cnt <= flow_cnt + 1'd1;data_send <= SECTOR_ADDR;		//数据为扇区地址endelse beginflow_cnt <= flow_cnt;data_send <= data_send;							endend	'd3:begin								//发送页地址if(send_done)begin					//发送完成flow_cnt <= flow_cnt + 1'd1;data_send <= PAGE_ADDR;			//数据为页地址endelse beginflow_cnt <= flow_cnt;data_send <= data_send;							endend	'd4:begin								//发送字节地址if(send_done)begin					//指令被发送完成flow_cnt <= flow_cnt + 1'd1;data_send <= BYTE_ADDR;			//数据为字节地址endelse beginflow_cnt <= flow_cnt;data_send <= data_send;							endend				'd5:beginif(send_done)begin					//字节地址被发送完成flow_cnt <= flow_cnt + 1'd1;data_send <= 8'd0;				//清空发送数据endelseflow_cnt <= flow_cnt;end	'd6:beginif(rec_done)						//这个发送最后一个字节的接收完成标志flow_cnt <= flow_cnt + 1'd1;				elseflow_cnt <= flow_cnt;end				'd7:begin								//读取数据阶段if(rec_done)begin					//接收到了一个BYTE数据if(data_cnt == BYTE_MAX - 1'd1)begin	//接收到了指定长度个数据data_cnt <= 8'd0;			//计数器清零spi_end <= 1'b1;			//结束SPI传输flow_cnt <= flow_cnt + 1'd1;endelse begin						//没有接收到指定长度的数据则继续接收data_cnt <= data_cnt + 1'd1;flow_cnt <= flow_cnt;								end				endelse begin							//一个BYTE数据接收未完成data_cnt <= data_cnt;flow_cnt <= flow_cnt;								end				end'd8:begin								//停留在这个状态flow_cnt <= flow_cnt;end				default:;endcaseend
endendmodule

        读数据顶层模块spi_read代码如下:

//FLASH读取数据顶层模块
module spi_read
#(parameter	BYTE_MAX 	= 8'd10			,		//一共读取多少个BYTE的数据SECTOR_ADDR = 8'b0000_0000	,		//扇区地址PAGE_ADDR   = 8'b0000_0000	,		//页地址BYTE_ADDR   = 8'b0000_0000			//字节地址
)
(
// 系统接口input	sys_clk		,			//全局时钟50MHzinput	sys_rst_n	,   		//复位信号,低电平有效
// SPI物理接口								input	spi_miso	,   		//SPI串行输入,用来接收从机的数据output	spi_sclk	,   		//SPI时钟output	spi_cs    	,   		//SPI片选信号,低电平有效output	spi_mosi	    		//SPI输出,用来给从机发送数据   
);	wire			spi_start	;		//发送传输开始信号,一个高电平
wire			spi_end		;   	//发送传输结束信号,一个高电平
wire	[7:0]  	data_send   ;   	//要发送的数据
wire	[7:0]  	data_rec   	;   	//接收到的数据
wire         	send_done	;   	//主机发送一个字节完毕标志
wire         	rec_done	;   	//主机接收一个字节完毕标志//------------<例化模块>----------------------------------------------------------------
//读数据控制模块
spi_read_ctrl
#(.BYTE_MAX		(BYTE_MAX		),.SECTOR_ADDR	(SECTOR_ADDR	),.PAGE_ADDR		(PAGE_ADDR		),.BYTE_ADDR		(BYTE_ADDR		)
)	
spi_read_ctrl_inst(.sys_clk		(sys_clk	), 			.sys_rst_n		(sys_rst_n	), 			.send_done		(send_done	), 			.spi_start		(spi_start	),			.spi_end		(spi_end	),			.data_send    	(data_send	),			.data_rec    	(data_rec	),			.rec_done    	(rec_done	)			
);
//SPI驱动
spi_drive	spi_drive_inst(.sys_clk		(sys_clk	), 			.sys_rst_n		(sys_rst_n	), 			.spi_start		(spi_start	), 			.spi_end		(spi_end	), 			.data_send		(data_send	), 			.data_rec  		(data_rec	), 			.send_done		(send_done	), 			.rec_done		(rec_done	), 			.spi_miso		(spi_miso	), 			.spi_sclk		(spi_sclk	), 			.spi_cs    		(spi_cs		), 			.spi_mosi		(spi_mosi	)			
);endmodule

2.2.3、Testbench及仿真结果

        Testbench比较简单直接例化读数据模块和仿真模型m25p16即可,同时让命令窗口打印读取到的数据。

//------------------------------------------------
//--SPI驱动仿真--读数据仿真
//------------------------------------------------
`timescale 1ns/1ns		//时间单位/精度//------------<模块及端口声明>----------------------------------------
module tb_spi_read();reg		sys_clk		;
reg		sys_rst_n	;wire	spi_miso	;
wire	spi_sclk	;
wire	spi_cs    	;
wire	spi_mosi	;parameter	BYTE_MAX 	= 8'd10			,		//一共读取多少个BYTE的数据SECTOR_ADDR = 8'b0000_0000	,		//扇区地址PAGE_ADDR   = 8'b0000_0000	,		//页地址BYTE_ADDR   = 8'b0000_0000	;		//字节地址
//------------<例化被测试模块>----------------------------------------
//读数据模块
spi_read	
#(.BYTE_MAX		(BYTE_MAX		),.SECTOR_ADDR	(SECTOR_ADDR	),.PAGE_ADDR		(PAGE_ADDR		),.BYTE_ADDR		(BYTE_ADDR		)
)
spi_read_inst(.sys_clk	(sys_clk	),.sys_rst_n	(sys_rst_n	),.spi_miso	(spi_miso	),.spi_sclk	(spi_sclk	),.spi_cs    	(spi_cs		),.spi_mosi	(spi_mosi	)
);
//m25p16仿真模型
m25p16  memory (.c          (spi_sclk	), .data_in    (spi_mosi   ), .s          (spi_cs   	), .w          (1'b1		), .hold       (1'b1   	), .data_out   (spi_miso   )
);	//------------<设置初始测试条件>----------------------------------------
initial beginsys_clk = 1'b0;					//初始时钟为0sys_rst_n <= 1'b0;				//初始复位#20								//20个时钟周期后sys_rst_n <= 1'b1;				//拉高复位,系统进入工作状态	
end//打印数据
always@(*)beginif(spi_read_inst.rec_done && spi_read_inst.spi_read_ctrl_inst.flow_cnt == 'd7)$display("READ	:%h",spi_read_inst.data_rec);		//打印读取的数据
end//重定义初始化数值
defparam memory.mem_access.initfile = "initM25P16_test.txt";	//其中的每页数据是从00累加到FF	//------------<设置时钟>----------------------------------------------
always #10 sys_clk = ~sys_clk;		//系统时钟周期20nsendmodule

        需要注意的是:

  • 地址设置为24‘b0,这样比较好操作一点
  • 读取数据长度设定为10
  • 仿真模型在仿真进行前会载入文件initM25P16_test中的数据作为初值,而该文件将m25p16的每页的256个字节的数据从00累加到FF,也就是说扇区00的页00的字节00的数据为00,扇区00的页00的字节33的数据为33

        所以我们仿真的预期结果应该是读取的数据结果为00~09(共10个数据),仿真结果如下:

        命令窗口打印如下:

 

可以看到读取的数据分别为0x00~0x09,与初始化的数据一致。

2.2.4、上板验证

        使用使用一块Cyclone IV E的开发板上板验证,该开发板板载了一个M25P16芯片作为上电后读取程序的FLASH。需要注意的是,该FLASH的管脚需要从专用下载管脚,配置成普通的IO管脚,如下:

        首先验证写模块:从地址24’d0开始分别写入10个数据:0x00~0x12。使用Signal Tap II抓取的波形如下:

  

        可以看到抓取的波形与仿真波形一致。

        接下来使用读数据模块从地址 24’d0开始分别读取数据11次,比较读取的数据与写入的数据是否一致(第十一次用来对比)。使用Signal Tap II抓取的波形如下:

         可以前10个读取的数据分别为0x00~0x12,第11个数据因为前面没有写,所以是默认的0xFF,符合预期结果。

2.3、扇区擦除(Sector Erase)

2.3.1、时序

        扇区擦除(Sector Erase)操作,简称 SE,操作指令为 8’b1101_0000(D8h),扇区擦除指令是将 Flash 芯片中的被选中扇区的所有存储单元设置为全 1,在 Flash 芯片写入扇区擦出指令之前,需要先写入写使能 (WREN)指令;随后要拉低片选信号,写入扇区擦除指令、扇区地址、页地址和字节地址,在指令、地址写入过程中,片选信号始终保持低电平,待指令、地址被芯片锁存后,将片选信号拉高;扇区擦除指令、地址被锁存并执行后,需要等待一个完整的扇区擦除周期(tSE),才能完成 Flash 芯片的扇区擦除操作。时序图如下:

2.3.2、Verilog代码

        Verilog代码分为3个模块:SPI驱动模块spi_drive、SPI扇区擦除控制模块spi_sector_erase_ctrl和扇区擦除顶层模块spi_sector_erase。

  • SPI驱动模块spi_drive:提供SPI模式0的读写驱动,具体可参见:  FPGA实现的SPI协议(一)----SPI驱动
  • SPI扇区擦除控制模块spi_sector_erase_ctrl:该模块使用一段式状态机编写,功能就是调用SPI驱动模块,发送扇区擦除指令,然后发送扇区地址+页地址+字节地址
  • SPI扇区擦除顶层模块spi_sector_erase:例化前面两个子模块

        SPI扇区擦除控制模块spi_sector_erase_ctrl代码如下:

//SPI扇区擦除控制模块
`timescale 1ns/1ns		//时间单位/精度
module spi_sector_erase_ctrl
#(parameter 	SECTOR_ADDR = 8'b0000_0000, 		//扇区地址parameter	PAGE_ADDR   = 8'b0000_0000,			//页地址parameter	BYTE_ADDR   = 8'b0000_0000			//字节地址
)
(input               sys_clk		, 				// 全局时钟50MHzinput               sys_rst_n	, 				// 复位信号,低电平有效input  		        send_done	, 				// 主机发送一个字节完毕标志位output  reg         spi_start	,				// 发送传输开始信号,一个高电平output  reg        	spi_end		,				// 发送传输结束信号,一个高电平output  reg  [7:0]  data_send    				// 要发送的数据         
);//指令定义
localparam 	WR_EN 		 = 8'b0000_0110, 			//写使能指令	SECTOR_ERASE = 8'b1101_1000;			//扇区擦除指令//reg define		
reg	[7:0]	flow_cnt;								//状态跳转计数器
reg	[7:0]	cnt_wait;								//上电等待计数器always@(posedge sys_clk or negedge sys_rst_n)beginif(!sys_rst_n)begindata_send <= 8'd0;spi_start <= 1'b0;	spi_end <= 1'b0;	flow_cnt <= 1'd0;cnt_wait <= 8'd0;endelse beginspi_start <= 1'b0;							//便于生成脉冲信号spi_end <= 1'b0;                            //便于生成脉冲信号case(flow_cnt)'d0:beginif(cnt_wait == 100)begin			//上电后等待稳定cnt_wait <= 8'd0;flow_cnt <= flow_cnt + 1'd1;endelse begincnt_wait <= cnt_wait + 1'd1;flow_cnt <= flow_cnt;								endend'd1:begin									data_send <= WR_EN;					//数据为写使能指令spi_start <= 1'b1;					//拉高spi开始通讯信号flow_cnt <= flow_cnt + 1'd1;end'd2:beginif(send_done)begin					//主机一个字节数据被发送完成flow_cnt <= flow_cnt + 1'd1;spi_end <= 1'b1;				//结束第1次SPI通信endelseflow_cnt <= flow_cnt;end	'd3:beginif(cnt_wait == 10)begin				//等待200ns,两次命令的间隔时间cnt_wait <= 8'd0;				//等待计数器清零flow_cnt <= flow_cnt + 1'd1;endelse begincnt_wait <= cnt_wait + 1'd1;flow_cnt <= flow_cnt;								endend'd4:begin									data_send <= SECTOR_ERASE;          //扇区擦除指令spi_start <= 1'b1;					//拉高spi开始通讯信号flow_cnt <= flow_cnt + 1'd1;end				'd5:begin								//发送扇区地址if(send_done)begin					//指令被发送完成flow_cnt <= flow_cnt + 1'd1;data_send <= SECTOR_ADDR;		//数据为扇区地址endelse beginflow_cnt <= flow_cnt;data_send <= data_send;							endend	'd6:begin								//发送页地址if(send_done)begin					//发送完成flow_cnt <= flow_cnt + 1'd1;data_send <= PAGE_ADDR;			//数据为页地址地址endelse beginflow_cnt <= flow_cnt;data_send <= data_send;							endend	'd7:begin								//发送字节地址if(send_done)begin					//指令被发送完成flow_cnt <= flow_cnt + 1'd1;data_send <= BYTE_ADDR;			//数据为字节地址endelse beginflow_cnt <= flow_cnt;data_send <= data_send;							endend				'd8:beginif(send_done)begin					//主机一个字节数据被发送完成flow_cnt <= flow_cnt + 1'd1;spi_end <= 1'b1;				//结束第1次SPI通信endelseflow_cnt <= flow_cnt;end					'd9:begin								//停留在这个状态flow_cnt <= flow_cnt;end				default:;endcaseend
endendmodule

        SPI扇区擦除顶层模块spi_sector_erase代码如下:

`timescale 1ns/1ns		//时间单位/精度
//扇区擦除
module spi_sector_erase(
// 系统接口input	sys_clk		,						//全局时钟50MHzinput	sys_rst_n	,   					//复位信号,低电平有效
// SPI物理接口											input	spi_miso	,   					//SPI串行输入,用来接收从机的数据output	spi_sclk	,   					//SPI时钟output	spi_cs    	,   					//SPI片选信号,低电平有效output	spi_mosi	    					//SPI输出,用来给从机发送数据   
);
parameter 	SECTOR_ADDR = 8'b0000_0000; 		//扇区地址
parameter	PAGE_ADDR   = 8'b0000_0000;			//页地址
parameter	BYTE_ADDR   = 8'b0000_1000;			//字节地址wire			spi_start	;					//发送传输开始信号,一个高电平
wire			spi_end		;   				//发送传输结束信号,一个高电平
wire	[7:0]  	data_send   ;   				//要发送的数据
wire	[7:0]  	data_rec   	;   				//接收到的数据
wire         	send_done	;   				//主机发送一个字节完毕标志
wire         	rec_done	;   				//主机接收一个字节完毕标志//------------<例化模块>----------------------------------------------------------------
//扇区擦除模块
spi_sector_erase_ctrl
#(.SECTOR_ADDR 	(SECTOR_ADDR),.PAGE_ADDR   	(PAGE_ADDR  ),.BYTE_ADDR   	(BYTE_ADDR  )
)	
spi_sector_erase_ctrl_inst
(.sys_clk		(sys_clk	), 			.sys_rst_n		(sys_rst_n	), 			.send_done		(send_done	), 			.spi_start		(spi_start	),			.spi_end		(spi_end	),			.data_send    	(data_send	)						
);
//SPI驱动
spi_drive	spi_drive_inst(.sys_clk		(sys_clk	), 			.sys_rst_n		(sys_rst_n	), 			.spi_start		(spi_start	), 			.spi_end		(spi_end	), 			.data_send		(data_send	), 			.data_rec  		(data_rec	), 			.send_done		(send_done	), 			.rec_done		(rec_done	), 			.spi_miso		(spi_miso	), 			.spi_sclk		(spi_sclk	), 			.spi_cs    		(spi_cs		), 			.spi_mosi		(spi_mosi	)			
);endmodule

2.3.3、Testbench及仿真结果

        Testbench比较简单直接例化扇区擦除模块和仿真模型m25p16即可。需要注意的是m25p16的扇区擦除需要等待的时间较长(3s),为了尽快完成仿真,我把这个等待参数改成了1s。

//------------------------------------------------
//--SPI驱动仿真--扇区擦除仿真
//------------------------------------------------
`timescale 1ns/1ns		//时间单位/精度//------------<模块及端口声明>----------------------------------------
module tb_spi_sector_erase();reg		sys_clk		;
reg		sys_rst_n	;wire	spi_miso	;
wire	spi_sclk	;
wire	spi_cs    	;
wire	spi_mosi	;//------------<例化被测试模块>----------------------------------------
//扇区擦除模块
spi_sector_erase	spi_sector_erase_inst(.sys_clk	(sys_clk	),.sys_rst_n	(sys_rst_n	),.spi_miso	(spi_miso	),.spi_sclk	(spi_sclk	),.spi_cs    	(spi_cs		),.spi_mosi	(spi_mosi	)
);
//m25p16仿真模型
m25p16  memory (.c          (spi_sclk	), .data_in    (spi_mosi   ), .s          (spi_cs   	), .w          (1'b1		), .hold       (1'b1   	), .data_out   (spi_miso   )
);	//------------<设置初始测试条件>----------------------------------------
initial beginsys_clk = 1'b0;					//初始时钟为0sys_rst_n <= 1'b0;				//初始复位#20								//20个时钟周期后sys_rst_n <= 1'b1;				//拉高复位,系统进入工作状态	
end//重定义初始化数值
defparam memory.mem_access.initfile = "initM25P16_test.txt";	//其中的数据是从0累加	
//------------<设置时钟>----------------------------------------------
always #10 sys_clk = ~sys_clk;		//系统时钟周期20nsendmodule

        仿真结果如下:

        命令窗口打印内容如下(单位:ps):约5us处开始进行扇区擦除操作,1s后扇区擦除操作完成。与预期结果一致。

2.3.4、上板验证

        首先使用扇区擦除模块24‘b0000_0000_0000_0000_0000_1000,实际上就是擦除扇区0,和后面的页地址和字节地址没有关系。在2.2节做页写操作的验证时,我们给地址扇区0的页0的地址0x00~0x0a分别写入了数据0x00、0x02、···、0x12,我们只要再使用读数据模块对这10个地址读取一遍,根据读出的内容就可以判断扇区擦除操作是否成功。

        使用signal tap对读数据操作抓取的波形如下:可以看到连续读取的数据均为0XFF,说明扇区擦除操作成功。

2.4、全擦除(Bulk Erase)

2.4.1、时序

        全擦除(Bulk Erase)操作,简称 BE,操作指令为 8’b1100_0111(C7h),全擦除指令是将 Flash 芯片中的所有存储单元设 置为全 1,在 Flash 芯片写入全擦出指令之前,需要先写入写使能(WREN)指令;随后要拉低片选信号,写入全擦除指令,在指令写入过程中,片选信号始终保持低电平,待指令被芯片锁存后,将片选信号拉高;全擦除指令被锁存并执行后,需要等待一个完整的全擦除周期(tBE),才能完成 Flash 芯片的全擦除操作。时序图如下:

2.4.2、Verilog代码

        Verilog代码分为3个模块:SPI驱动模块spi_drive、SPI全擦除控制模块spi_bulk_erase_ctrl和全擦除顶层模块spi_bulk_erase。

  • SPI驱动模块spi_drive:提供SPI模式0的读写驱动,具体可参见:  FPGA实现的SPI协议(一)----SPI驱动
  • SPI全擦除控制模块spi_bulk_erase_ctrl:该模块使用一段式状态机编写,功能就是调用SPI驱动模块,发送全擦除指令
  • SPI全擦除顶层模块spi_bulk_erase:例化前面两个子模块

        SPI全擦除控制模块spi_bulk_erase_ctrl代码如下:

//全擦除指令控制模块
module spi_bulk_erase_ctrl
(input               sys_clk		, 			// 全局时钟50MHzinput               sys_rst_n	, 			// 复位信号,低电平有效input         		send_done	, 			// 主机发送一个字节完毕标志位      output  reg         spi_start	,			// 发送传输开始信号,一个高电平output  reg        	spi_end		,			// 发送传输结束信号,一个高电平output  reg  [7:0]  data_send    			// 要发送的数据         
);//指令定义
parameter 	WR_EN 		= 8'b0000_0110, 			//写使能指令BULK_ERASE 	= 8'b1100_0111, 			//全擦除指令READ 		= 8'h0000_0011;				//读数据指令//reg define	
reg	[7:0]	flow_cnt;							//状态跳转计数器
reg	[31:0]	cnt_wait;							//等待计数器always@(posedge sys_clk or negedge sys_rst_n)beginif(!sys_rst_n)begindata_send <= 8'd0;spi_start <= 1'b0;	flow_cnt <= 1'd0;cnt_wait <= 'd0;endelse beginspi_start <= 1'b0;							//便于生成脉冲信号spi_end <= 1'b0;                            //便于生成脉冲信号case(flow_cnt)'d0:beginif(cnt_wait == 100)begin			//上电后等待稳定cnt_wait <= 8'd0;flow_cnt <= flow_cnt + 1'd1;endelse begincnt_wait <= cnt_wait + 1'd1;flow_cnt <= flow_cnt;								endend'd1:begin									data_send <= WR_EN;					//数据为写使能指令spi_start <= 1'b1;					//拉高spi开始通讯信号flow_cnt <= flow_cnt + 1'd1;end	'd2:beginif(send_done)begin					//主机一个字节数据被发送完成flow_cnt <= flow_cnt + 1'd1;spi_end <= 1'b1;				//结束第1次SPI通信endelseflow_cnt <= flow_cnt;end	'd3:beginif(cnt_wait == 10)begin				//等待200ns,两次命令的间隔时间cnt_wait <= 8'd0;				//等待计数器清零flow_cnt <= flow_cnt + 1'd1;endelse begincnt_wait <= cnt_wait + 1'd1;flow_cnt <= flow_cnt;								endend'd4:begin									data_send <= BULK_ERASE;            //全擦除指令spi_start <= 1'b1;					//拉高spi开始通讯信号flow_cnt <= flow_cnt + 1'd1;end	'd5:beginif(send_done)begin					//主机一个字节数据被发送完成flow_cnt <= flow_cnt + 1'd1;spi_end <= 1'b1;				//结束第2次SPI通信endelseflow_cnt <= flow_cnt;end					'd6:begin								//停留在这个状态flow_cnt <= flow_cnt;end				default:;endcaseend
endendmodule

         SPI全擦除顶层模块spi_bulk_erase代码如下:

//全擦除指令模块
module spi_bulk_erase(
// 系统接口input	sys_clk		,		//全局时钟50MHzinput	sys_rst_n	,   	//复位信号,低电平有效
// SPI物理接口							input	spi_miso	,   	//SPI串行输入,用来接收从机的数据output	spi_sclk	,   	//SPI时钟output	spi_cs    	,   	//SPI片选信号,低电平有效output	spi_mosi	    	//SPI输出,用来给从机发送数据   
);wire			spi_start	;	//发送传输开始信号,一个高电平
wire			spi_end		;   //发送传输结束信号,一个高电平
wire	[7:0]  	data_send   ;   //要发送的数据
wire         	send_done	;   //主机发送一个字节完毕标志//------------<例化模块>----------------------------------------------------------------
//全擦除控制模块
spi_bulk_erase_ctrl	spi_bulk_erase_ctrl_inst
(.sys_clk		(sys_clk	), 			.sys_rst_n		(sys_rst_n	), 			.send_done		(send_done	), 			.spi_start		(spi_start	),			.spi_end		(spi_end	),			.data_send    	(data_send	)			
);
//SPI驱动
spi_drive	spi_drive_inst(.sys_clk		(sys_clk	), 			.sys_rst_n		(sys_rst_n	), 			.spi_start		(spi_start	), 			.spi_end		(spi_end	), 			.data_send		(data_send	), 			.data_rec  		(			), 			.send_done		(send_done	), 			.rec_done		(			), 			.spi_miso		(spi_miso	), 			.spi_sclk		(spi_sclk	), 			.spi_cs    		(spi_cs		), 			.spi_mosi		(spi_mosi	)			
);endmodule

2.4.3、Testbench及仿真结果

        Testbench比较简单直接例化全擦除模块和仿真模型m25p16即可。需要注意的是m25p16的全擦除需要等待的时间较长(40s),为了尽快完成仿真,我把这个等待参数改成了1s。

//------------------------------------------------
//--SPI驱动仿真--全擦除仿真
//------------------------------------------------
`timescale 1ns/1ns		//时间单位/精度//------------<模块及端口声明>----------------------------------------
module tb_spi_bulk_erase();reg		sys_clk		;
reg		sys_rst_n	;wire	spi_miso	;
wire	spi_sclk	;
wire	spi_cs    	;
wire	spi_mosi	;//------------<例化被测试模块>----------------------------------------
//全擦除模块
spi_bulk_erase	spi_bulk_erase_inst(.sys_clk	(sys_clk	),.sys_rst_n	(sys_rst_n	),.spi_miso	(spi_miso	),.spi_sclk	(spi_sclk	),.spi_cs    	(spi_cs		),.spi_mosi	(spi_mosi	)
);
//m25p16仿真模型
m25p16  memory (.c          (spi_sclk	), .data_in    (spi_mosi   ), .s          (spi_cs   	), .w          (1'b1		), .hold       (1'b1   	), .data_out   (spi_miso   )
);	//------------<设置初始测试条件>----------------------------------------
initial beginsys_clk = 1'b0;					//初始时钟为0sys_rst_n <= 1'b0;				//初始复位#20								//20个时钟周期后sys_rst_n <= 1'b1;				//拉高复位,系统进入工作状态	
end//------------<设置时钟>----------------------------------------------
always #10 sys_clk = ~sys_clk;		//系统时钟周期20nsendmodule

        仿真结果如下:

        命令窗口打印内容如下(单位:ps):约3us处开始进行扇区擦除操作,1s后扇区擦除操作完成。与预期结果一致。 

2.4.4、上板验证

        我们首先使用写模块往区域1(扇区0x00页0x00地址0x00~0x09)写入10个数据(从0x00开始累加2),然后往区域2(扇区0x10页0x10地址0x10~0x19)写入10个数据(从0x00开始累加4)。

        接着调用全擦除模块,接着再使用读数据模块读取这两个区域的数据,根据读出的数据来验证数据是否被全部擦除了。

        区域1写数据波形图(依次写入0x00、0x02````0x12): 

        区域2写数据波形图(依次写入0x00、0x04````0x24): 

         全擦除模块波形图:与仿真波形图一致。 

        接着调用读数据模块读取区域1的数据:读取的数据全部为0xFF,说明数据被擦除了。

        接着调用读数据模块读取区域2的数据:读取的数据全部为0xFF,说明数据被擦除了。

        以上就证明我们的全擦除模块是成功擦除了所有扇区。

3、其他

  • 还有一些其他指令,如读ID,读写状态寄存器就不列出来了,参考上述模块应该很容易就改出来了
  • 如果需要完整的工程文件请点这里:工程文件下载

  • 📣博客主页:wuzhikai.blog.csdn.net
  • 📣本文由 孤独的单刀 原创,首发于CSDN平台🐵
  • 📣您有任何问题,都可以在评论区和我交流📞!
  • 📣创作不易,您的支持是我持续更新的最大动力!如果本文对您有帮助,还请多多点赞👍、评论💬和收藏⭐!


http://chatgpt.dhexx.cn/article/sLiJ9hKC.shtml

相关文章

SPI接口总结

一、SPI协议【SerialPeripheral Interface】 串行外围设备接口&#xff0c;是一种高速全双工的通信总线。在ADC/LCD等与MCU间通信。 1、SPI信号线 SPI 包含 4 条总线&#xff0c;SPI 总线包含 4 条总线&#xff0c;分别为SS 、SCK、MOSI、MISO。 &#xff08;1&#xff09;SS…

SPI接口扫盲 SPI定义/SPI时序(CPHA CPOL)

SPI接口扫盲 douqinglgmail.com 为何要写这篇文档&#xff1f; 百度上找出来的SPI接口中文描述都说的太过简略&#xff0c;没有一篇文档能够详尽的将SPI介绍清楚的。wikipedia英文版[注释1]中&#xff0c;SPI接口介绍的很好&#xff0c;但是毕竟是英文版&#xff0c;读起来终究…

SPI接口通信原理

SPI接口通信 一、基础概念 SPI接口是Motorola 首先提出的全双工三线同步串行外围接口&#xff0c;采用主从模式&#xff08;Master Slave&#xff09;架构&#xff1b;支持多slave模式应用&#xff0c;一般仅支持单Master。时钟由主设备控制&#xff0c;在时钟移位脉冲下&…

SPI接口屏幕

在小分辨率(不高于qvga(320*240))的设备中,使用的是spi接口的屏幕&#xff0c;比如gc9306和st7789等。有两种类型的屏幕: 3线屏(3线1data或3线2data) 3线9bit I型 SCL/CSX/SDA 3…

STM32-SPI接口

一、SPI协议【SerialPeripheral Interface】 串行外围设备接口&#xff0c;是一种高速全双工的通信总线。在ADC/LCD等与MCU间通信。 1、SPI信号线 SPI 包含 4 条总线&#xff0c;SPI 总线包含 4 条总线&#xff0c;分别为SS 、SCK、MOSI、MISO。 &#xff08;1&#xff09;SS…

SPI接口原理与配置

SPI 是英语Serial Peripheral interface的缩写&#xff0c;顾名思义就是串行外围设备接口。是Motorola首先在其MC68HCXX系列处理器上定义的。 SPI&#xff0c;是一种高速的&#xff0c;全双工&#xff0c;同步的通信总线&#xff0c;并且在芯片的管脚上只占用四根线&#xff0…

SPI 接口配置

SPI&#xff08;Serial Peripheral Interface&#xff0c;串行外设接口&#xff09;是Motorola公司提出的一种同步串行数据传输标准&#xff0c;是一种高速的&#xff0c;全双工&#xff0c;同步的通信总线&#xff0c;在很多器件中被广泛应用。 SPI相关缩写 SS: Slave Select&…

D2--FPGA SPI接口通信2022-08-03

1.SPI简介 SPI是串行外设接口&#xff08;Serial Peripheral Interface&#xff09;的缩写&#xff0c;通常说SPI接口或SPI协议都是指SPI这一种串行外设接口规范。相对于串口&#xff0c;SPI是一种高速的&#xff08;可达10Mb\s以上&#xff09;&#xff0c;全双工&#xff0c…

[SPI]SPI接口简介

SPI接口简介 前言&#xff1a;串行外设接口(SPI)是微控制器和外围IC&#xff08;如传感器、ADC、DAC、移位寄存器、SRAM等&#xff09;之间使用较广泛的接口之一。本文先简要说明SPI接口&#xff0c;然后介绍ADI公司支持SPI的模拟开关与多路转换器&#xff0c;以及它们如何帮助…

计算机串口接spi,SPI串口模块-SPI接口详细介绍

SPI串口模块-SPI接口详细介绍 1. SPI串口模块-概述 SPI = Serial Peripheral Interface,是串行外围设备接口,是一种高速,全双工,同步的通信总线。常规只占用四根线,节约了芯片管脚,PCB的布局省空间。现在越来越多的芯片集成了这种通信协议,常见的有EEPROM、FLASH、AD转换…

SPI接口介绍

SPI接口的全称是”Serial Peripheral Interface”&#xff0c;即串行外围接口。SPI接口主要应用在EEPROM、FLASH、实时时钟、AD转换器&#xff0c;还有数字信号处理器和数字信号解码器之间。SPI接口是在CPU和外围低速器件之间进行同步串行数据传输&#xff0c;在主器件的移位脉…

SPI接口协议的学习1

SPI接口是一种同步串行总线&#xff08;Serial Peripheral Interface&#xff09;。 四线SPI接口连线图&#xff1a; CS为片选脚&#xff0c;用于选中从机。 SCLK为时钟脚&#xff0c;用于数据传输时提供时钟信号。 MOSI为主output&#xff0c;从input&#xff0c;即主机发送…

SPI接口详细介绍

概述 SPI Serial Peripheral Interface&#xff0c;是串行外围设备接口&#xff0c;是一种高速&#xff0c;全双工&#xff0c;同步的通信总线。常规只占用四根线&#xff0c;节约了芯片管脚&#xff0c;PCB的布局省空间。现在越来越多的芯片集成了这种通信协议&#xff0c;常…

软件模拟SPI接口程序代码

目录 SPI协议简介 SPI接口介绍 SPI接口连接图 SPI数据传输方向 SPI传输模式 模拟SPI程序 SPI协议简介 SPI的通信原理很简单&#xff0c;一般主从方式工作&#xff0c;这种模式通常有一个主设备和一个或者多个从设备&#xff0c;通常采用的是4根线&#xff0c;它们是MISO&…

SPI接口及驱动

1. 简介 SPI接口是Motorola 首先提出的全双工三线同步串行外围接口&#xff0c;采用主从模式&#xff08;Master Slave&#xff09;架构。支持多slave模式应用&#xff0c;一般仅支持单Master。时钟由Master控制&#xff0c;在时钟移位脉冲下&#xff0c;数据按位传输&#xf…

SPI 接口

SPI 接口的全称是“Serial Peripheral Interface”意为串行外围接口&#xff0c;是 Motorola 首先在其 MC68HCxx 系列处理器上定义的。SPI 接口主要应用于 EEPROM、FLASH、实时时钟、AD转换器&#xff0c;还有数字信号处理器和数字信号解码器之间。 SPI 接口是在 CPU 和外围低…

ESP32 SPI 接口的应用

总体介绍 1. ESP32 共有 4 个 SPI 控制器 SPI0、SPI1、SPI2、SPI3&#xff0c;用于连接支持 SPI 协议的设备。 SPI0 控制器作为 cache 访问外部存储单元接口使用;SPI1 作为主机使用;SPI2 和 SPI3 控制器既可作为主机使用又可作为从机使用。作主机使用时&#xff0c;每个 SPI 控…

SPI接口简介-Piyu Dhaker

SPI接口简介 作者&#xff1a; Piyu Dhaker 串行外设接口(SPI)是微控制器和外围IC&#xff08;如传感器、ADC、DAC、移位寄存器、SRAM等&#xff09;之间使用最广泛的接口之一。本文先简要说明SPI接口&#xff0c;然后介绍ADI公司支持SPI的模拟开关与多路转换器&#xff0c;以…

弄懂SPI接口

SPI&#xff08;Serial Peripheral Interface&#xff0c;串行外设接口&#xff09;是Motorola公司提出的一种同步串行数据传输标准&#xff0c;是一种高速的&#xff0c;全双工&#xff0c;同步的通信总线&#xff0c;在很多器件中被广泛应用。 SPI相关缩写 SS: Slave Select&…

SPI接口

SPI&#xff08;Serial Peripheral Interface&#xff0c;串行外设接口&#xff09;是Motorola公司提出的一种同步串行数据传输标准&#xff0c;在很多器件中被广泛应用。 1. 接口 SPI接口经常被称为4线串行总线&#xff0c;SPI协议是主从模式&#xff1a;从机不主动发起访问&…