SPI协议(一):读SPI_Flash(M25P16)设备ID

一 SPI通信协议

        串行外围设备接口(Serial Peripheral Interface,SPI)是一种高速、全双工、同步通讯协议,广泛应用于EEPROM、Flash、ADC、DSP等器件控制和数据传输。SPI协议主要包含CS_N、SCK、MOSI、MISO信号线,它们的作用如下:

       (1) 片选信号线 (Chip Select,CS_N):SPI协议中没有设备地址,通过将从设比的CS_N设置为低电平来选择从设备,整个SPI通讯以CS_N信号线为低电平开始,以CS_N线为高电平结束。

       (2) 时钟信号线 (Serial Clock,SCK):用于同步通讯数据。

       (3) 主设备输入/从设备输出信号线(Master Output Slave Input,MOSI):主机的数据从这条信号线输出,从机由这条信号线读入主机发送的数据,数据方向由主机到从机。

       (4)主设备输出/从设备输入信号线(Master Intput Slave Output,MISO):主机从这条信号线读入数据,从机的数据通过这条线输出到主句,数据方向为从机到主机。

1.1CPOL/CPHA及通讯模式

      SPI通讯协议一共有四种通讯模式,即模式0、模式1、模式2、模式3,四种模式由时钟极性(Clock Porilarity,CPOL)和时钟相位(Clock Phase,CPHA)来决定。CPOL规定了空闲状态时SCK时钟信号的电平状态,CPHA规定了数据采样在SCK时钟的奇数边沿还是偶数边沿。四种模式如下表所示

工作模式

CPOL、CPHA

模式0

CPOL=0

CPHA=0

空闲状态时SCK为低电平,数据采样在SCK奇数沿,数据更新在SCK偶数沿,奇数边沿为上升沿,偶数边沿为下降沿。

模式1

CPOL=0

CPHA=1

空闲状态时SCK为低电平,数据采样在SCK偶数沿,数据更新在SCK奇数沿,奇数边沿为上升沿,偶数边沿为下降沿。

模式2

CPOL=1

CPHA=0

空闲状态时SCK为高电平,数据采样在SCK奇数沿,数据更新在SCK偶数沿,奇数边沿为下降沿,偶数边沿为上升沿。

模式3

CPOL=1

CPHA=1

空闲状态时SCK为高电平,数据采样在SCK偶数沿,数据更新在SCK奇数沿,奇数边沿为下降沿,偶数边沿为上升沿。

1.2通讯过程

       通讯过程以模式0为例如下图所示。CS_N、SCK、MOSI信号均有主机控制产生,MISO信号由从机产生。CS_N用于选择从机设备,信号由高变低为起始信号,由低变高为停止信号,表示本次通讯结束;SCK时时钟信号,用于同步数据;MOSI和MISO数据线在SCK的每个时钟周期传输一位数据,且数据输入和输出时同时进行的。

       观察图中的②③④⑤标号处,MOSI 及 MISO 的数据在 SCK 的下降沿期间变化输出, 在 SCK 的上升沿时被采样。即在 SCK 的上升沿时刻,MOSI 及 MISO 的数据有效,高电平时表示数据“1”,为低电平时表示数据“0”。在其它时刻,数据无效,MOSI 及 MISO 为下一次表示数据做准备。

二 SPI_Flash(M25P16)读ID 实验

2.1实验内容

       通过控制按键读取M25P16芯片ID标识,当检测到按键按下时,通过SPI协议读取Flash芯片的制造厂商标识20h、设备标识(内存类型20h、内存容量15h),读写时钟速率为12.5MHz。读ID指令RDID为8’h9F,读出数据字为1~3字节, RDID时序如下图所示。

2.2 读ID工程代码  

    读ID工程主要包括顶层例化spi_rdid_top、读ID模块spi_rdid、按键消抖模块key_filter三个部分。spi_rd_id模块如下:

`timescale 1ns/1ps
module spi_rdid(
	input			clk,
	input			rst_n,
	input			key_in,
	input			miso,
	
	output	reg	    cs_n,
	output	reg	    sck,
	output	reg	    mosi
);

parameter	DEVICE_ID	=	8'h9f;	//读ID指令
parameter	IDLE		=	3'b001;	//空闲状态
parameter	WR_INST		=	3'b010;	//写入ID指令状态
parameter	RD_ID		=	3'b100;	//读出flash数据状态

reg	[2:0] curr_state;
reg	[2:0]	next_state;
reg	[4:0]	clk_cnt;			//系统时钟计数
reg	[1:0]	sck_cnt;			//spi时钟计数器
reg	[2:0]	bit_cnt;			//数据位计数器
reg	[1:0]	byte_cnt;		//字节计数器
reg	[7:0]	miso_r;			//对miso数据存储的移位寄存器
reg	[7:0]	recv_data;		//读出设备ID数据
reg			recv_flag;		//读出一个字节数据有效信号
reg			miso_flag;		//对miso数据进行移位存储的标志信号

//同步时序描述状态转移
always @(posedge clk or negedge rst_n)
begin
	if(!rst_n)
		curr_state	<=	IDLE;
	else
		curr_state	<=	next_state;
end

//组合逻辑描述状态转移规律
always @(*)
begin
	next_state	<=	IDLE;
	case(curr_state)
	
		IDLE:begin
			if(key_in)					 //当检测到按键按下
				next_state	<=	WR_INST;
			else
				next_state	<=	IDLE;
		end
		
		WR_INST:begin
			if(byte_cnt == 2'd0 && clk_cnt == 5'd31)	//当RD_ID指令写入完成
				next_state	<=	RD_ID;
			else
				next_state	<=	WR_INST;	
		end
		
		RD_ID:begin
			if(byte_cnt == 2'd3 && clk_cnt == 5'd31)
				next_state	<=	IDLE;
			else
				next_state	<=	RD_ID;	
		end
		
		default: next_state	<=	IDLE;
	endcase
end

//clk_cnt:系统时钟计数器
always @(posedge clk or negedge rst_n)
begin
	if(!rst_n)
		clk_cnt	<=	5'd0;
	else if(curr_state != IDLE)
		clk_cnt	<=	clk_cnt + 1'b1;
	else
		clk_cnt	<=	5'd0;
end


//sck_cnt:sck时钟计数器,对clk进行4分频
always @(posedge clk  or negedge rst_n)
begin
	if(!rst_n)
		sck_cnt	<=	2'd0;
	else if(cs_n == 1'b0)
		sck_cnt	<=	sck_cnt + 1'b1;	
	else
		sck_cnt	<=	2'd0;
end

//bit_cnt:数据位计数器
always @(posedge clk or negedge rst_n)
begin
	if(!rst_n)
		bit_cnt <=	3'd0;
	else if(sck_cnt == 2'd1)
		bit_cnt	<=	bit_cnt + 1'b1;
	else
		bit_cnt	<=	bit_cnt;
end

//byte_cnt:字节计数器
always @(posedge clk or negedge rst_n)
begin
	if(!rst_n)
		byte_cnt	<=	2'd0;
	else if(byte_cnt == 2'd3 && clk_cnt == 5'd31)
		byte_cnt	<=	2'd0;
	else if(clk_cnt == 5'd31)
		byte_cnt	<=	byte_cnt + 1'b1;
	else
		byte_cnt    <=	byte_cnt;
end

//cs_n:片选信号
always @(posedge clk or negedge rst_n)
begin
	if(!rst_n)
		cs_n	<=	1'b1;
	else if(key_in)
		cs_n	<=	1'b0;
	else if(byte_cnt == 2'd3 && clk_cnt ==  5'd31)
		cs_n	<=	1'b1;
	else
		cs_n	<=	cs_n;
end

//sck:spi工作时钟
always @(posedge clk or negedge rst_n)
begin
	if(!rst_n)
		sck	<=	1'b0;
	else if(sck_cnt == 2'd1)
		sck	<=	1'b1;
	else if(sck_cnt == 2'd3)
		sck	<=	1'd0;
	else
		sck	<=	sck;
end

//mosi:主机输出RD_ID指令到从机
always @(posedge clk or negedge rst_n)
begin
	if(!rst_n)
		mosi	<=	1'b0;
	else if(byte_cnt == 2'd0 && clk_cnt == 5'd31)
		mosi	<=	1'b0;
	else if(curr_state == WR_INST)
		mosi	<=	DEVICE_ID[7 - bit_cnt];
	else 
		mosi	<=	mosi;
end

//miso_r:对miso数据进行移位存储
always @(posedge clk or negedge rst_n)
begin
	if(!rst_n)
		miso_r	<=	8'd0;
	else if(miso_flag == 1'b1)
		miso_r	<=	{miso_r[6:0],miso};
	else
		miso_r	<=	miso_r;
end

//miso_flag:移位标志信号产生
always @(posedge clk or negedge rst_n)
begin
	if(!rst_n)
		miso_flag	<=	1'b0;
	else if(curr_state == RD_ID && sck_cnt == 2'd1)
		miso_flag	<=	1'b1;
	else
		miso_flag	<=	1'b0;
end
//recv_flag:读出设备ID有效信号
always @(posedge clk or negedge  rst_n)
begin
	if(!rst_n)
		recv_flag	<=	1'b0;
	else if(curr_state == RD_ID && clk_cnt == 5'd30)
		recv_flag	<=	1'b1;
	else 
		recv_flag	<=	1'b0;
end

//recv_data:读出的设备ID数据
always @(posedge clk or negedge rst_n)
begin
	if(!rst_n)
		recv_data	<=	8'd0;
	else if(recv_flag == 1'b1)
		recv_data	<=	miso_r;
	else
		recv_data	<=	recv_data;
end

endmodule 

       按键消抖模块key_filter如下:

`timescale 1ns/1ps 
module key_filter(
	input		clk,
	input		rst_n,
	input		key_in,		    //按键输入
	
	output	reg	key_flag		//检测到按键按下标志信号
);

localparam	CNT_MAX = 999_999;  //计数最大值时,时间为20ms

reg [19:0]	clk_cnt;	        //	系统时钟计数器

//clk_cnt:系统时钟计数器
always @(posedge clk or negedge rst_n)
begin
	if(!rst_n)
		clk_cnt <=	20'd0;
	else if(key_in == 1'b1)
		clk_cnt	<=	20'd0;
	else if(key_in == 1'b0 && clk_cnt < CNT_MAX)
		clk_cnt	<=	clk_cnt + 1'b1;
	else
		clk_cnt	<=	clk_cnt;
end

//key_flag:检测到按键按下标志信号
always @(posedge clk or negedge rst_n)
begin
	if(!rst_n)
		key_flag	<=	1'b0;
	else if(clk_cnt == CNT_MAX - 1'b1)
		key_flag	<=	1'b1;
	else
		key_flag	<=	1'b0;
end

endmodule

顶层完成模块例化spi_rdid_top:

`timescale 1ns/1ps
module spi_rdid_top(
	input			sys_clk,		//系统时钟输入50MHz
	input			rst_n,		//系统复位
	input			key_in,		//按键输入
	input			miso,			//spi主入从出信号
	
	output			cs_n,			//spi片选信号
	output			sck,			//spi读写时钟信号
	output			mosi			//spi主出从入信号
);	

wire		key_flag;				//按键按下表示信号

//例化spi_rdid模块
spi_rdid u_spi_rdid(
	.clk(sys_clk),
	.rst_n(rst_n),
	.key_in(key_flag),
	.miso(miso),
	
	.cs_n(cs_n),
	.sck(sck),
	.mosi(mosi)
);

//例化key_filter模块
key_filter u_key_filter(
	.clk(sys_clk),
	.rst_n(rst_n),
	.key_in(key_in),
	
	.key_flag(key_flag)
);

endmodule

仿真代码如下:

`timescale 1ns/1ps
module spi_rdid_tb();

reg		sys_clk;
reg		rst_n;
reg		key_in;

wire	miso;
wire	cs_n;
wire	sck;
wire	mosi;

initial
begin
	sys_clk	=	1'b1;
	rst_n	=	1'b0;
	key_in	=	1'b1;
	#200
	rst_n	=	1'b1;
	#100
	key_in	=	1'b0;
	#21_000_000;
	key_in	=	1'b1;
end

always #10	sys_clk	<=	~sys_clk;

m25p16  memory (
    .c          (sck    ), 
    .data_in    (mosi   ), 
    .s          (cs_n   ), 
    .w          (1'b1   ), 
    .hold       (1'b1   ), 
    .data_out   (miso   )
); 

spi_rdid_top u_spi_rdid_top(
	.sys_clk(sys_clk),
	.rst_n(rst_n),
	.key_in(key_in),
	.miso(miso),
	
	.cs_n(cs_n),
	.sck(sck),
	.mosi(mosi)
);

endmodule 

仿真结果如下:

        仿真结果如上图所示,在curr_state为WR_ISNT(010)时,通过mosi向spi_flash发送读ID指令8’b1001_1111,然后curr_state跳转到RD_ID(100)状态,此时FPGA通过miso接收flash传入的设备ID标识数据,通过recv_data将接收的8位串行数据寄存,接收的3个字节数据依次为8’h20、8’h20、8’h15,与手册上数据一致。

2.3 板级验证

       将代码下载到cyclone IV开发板中,通过singal tap抓取fpga接收到的数据,抓取cs_n下降沿,可以看到当miso将读指令8’b1001_1111写入spi_flash后,flash的设备ID标识数据通过miso传输到FPGA,FPGA接收到的miso数据依次为8’h20、8’h20、8’h15。

 

 

 

  • 7
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值