学习笔记之FPGA的SPI通讯

这篇具有很好参考价值的文章主要介绍了学习笔记之FPGA的SPI通讯。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

      FPGA由于 FPGA 是基于 SRAM 结构的,程序掉电后会丢失,所以需要一个外置 Flash 保存程序, FPGA 每次上电后去读取 Flash 中的配置程序,在 ALINX 开发板中,很多使用的是 SPI 接口的 nor flash,这种 flash 只需要 4 根 IO。FPGA 的配置 flash 是特殊的 IO,上电时工作,FPGA 要使用这些 IO 来读取 Flash,读取完成后释放这些 IO 交给用户使用。

1. SPI的基本知识

        SPI(Serial Peripheral Interface)是一种同步串行通信协议,通常用于在嵌入式系统中连接微控制器和外部设备。它允许微控制器与多个外设同时进行全双工通信,实现高速数据传输。SPI协议通常用于连接存储器芯片、传感器、显示屏、通信模块和其他外设。 

         SPI协议的基本工作原理如下:微控制器通过主设备选择(SS)引脚选择要与之通信的外设,并通过时钟信号(SCLK)同步数据传输。它还使用主输出主输入(MOSI)和主输入主输出(MISO)引脚进行双向数据传输。在传输过程中,主设备将数据位传输到外设的输入引脚,同时从外设的输出引脚读取数据位。

        SPI协议具有以下特点:

  • 简单灵活:SPI协议使用少量的引脚和简单的硬件电路,易于实现和调试。

  • 高速通信:由于采用同步传输方式,SPI协议可以实现高速数据传输,适用于实时应用。

  • 可靠性好:SPI协议具有错误检测和纠正机制,可以提供可靠的数据传输。

  • 多设备支持:SPI协议使用主从结构,一个主设备可以连接多个从设备,使得系统具备良好的扩展性。

        SPI协议的配置通常由以下参数确定:

  • 时钟极性(CPOL):确定时钟信号的空闲状态和活动状态。

  • 时钟相位(CPHA):确定数据采样的时机。

  • 数据位顺序:确定数据位的传输顺序,可以是最高位先传输(MSB-first)或最低位先传输(LSB-first)。

        在实际应用中,SPI协议根据不同的硬件平台和设备需求进行配置。通过正确配置参数,微控制器可以与外设进行可靠的通信和数据交换。

2. ADC读写实验

2.1 ADC128S022产品信息

        ADC128S022器件是一种低功耗、8通道CMOS 12位模数转换器,其转换吞吐量为50 ksps到200 ksps。该转换器是基于一个逐次逼近寄存器结构与一个内部跟踪和保持电路。它可以配置为在输入IN0到IN7接受多达8个输入信号。串行数据的输出是直接二进制,并兼容多种标准,如SPI, QSPI, MICROWIRE和许多常见的DSP串行接口。 ADC128S022可使用独立的模拟和数字电源。模拟电源(VA)的电压范围是2.7 V到5.25 V,数字电源(VD)的电压范围是2.7 V到VA。使用3v或5v电源的正常功耗分别为1.2 mW和7.5 mW。当使用3v电源时,功耗为0.06µW;当使用5v电源时,功耗为0.25µW确保在-40°C至+105°C的扩展工业温度范围内运行。

fpga之间spi,fpga开发,学习,笔记

 2.2 ADC128S022的时序图

        CS:片选信号,低电平表示选中。

        SCLK:输入时钟。

        DIN:串行输入,SCLK的上升沿进行采集。

        DOUT:转换输出,SCLK的下降沿进行数据输出。

2.3 实验代码

module SPI(
	input clk,
	input rst_n,
	input start,
	input [2:0]channel,
	
	//========ADC128S022===========//
	output reg SCLK,
	output reg DIN,
	output reg CS_N,
	input DOUT,
	
	output reg done,
	output reg [11:0]data
);

	reg en;
	reg [2:0]r_channel;
	reg [4:0]cnt;
	reg cnt_flag;
	reg [5:0]SCLK_CNT;
	reg [11:0]r_data;
	
//==============写入通道===================//  
	always@(posedge clk or negedge rst_n)begin
		if(!rst_n)
			r_channel <= 'd0;
		else if(start)
			r_channel <= channel;
		else
			r_channel <= r_channel;
	end
	
//============转换使能信号==================//
	always@(posedge clk or negedge rst_n)begin
		if(!rst_n)
			en <= 1'b0;
		else if(start)
			en <= 1'b1;
		else if(done)
			en <= 1'b0;
		else
			en <= en;
	end
	
//==================cnt=====================//
	always@(posedge clk or negedge rst_n)begin
		if(!rst_n)
			cnt <= 'd0;
		else if(en)begin
			if(cnt == 'd10)
				cnt <= 'd0;
			else
				cnt <= cnt + 1'b1;
		end
	else
		cnt <= 'd0;
	end
	
//===================cnt_flag===============//
	always@(posedge clk or negedge rst_n)begin
		if(!rst_n)
			cnt_flag <= 1'b0;
		else if(cnt == 'd10)
			cnt_flag <= 1'b1;
		else
			cnt_flag <= 1'b0;
	end

//===============SCLK_CNT===================//
	always@(posedge clk or negedge rst_n)begin
		if(!rst_n)
			SCLK_CNT <= 'd0;
		else if(en)begin
			if(cnt_flag && (SCLK_CNT == 'd33))
				SCLK_CNT <= 'd0;
			else if(cnt_flag)
				SCLK_CNT <= SCLK_CNT + 1'b1;
			else
				SCLK_CNT <= SCLK_CNT;
		end
		else
			SCLK_CNT <= 'd0;
	end

//===========================================//
	always@(posedge clk or negedge rst_n)begin
		if(!rst_n)begin
			SCLK <= 1'b1;
			CS_N <= 1'b1;
			DIN <= 1'b1;
		end
		else if(en)begin
			if(cnt_flag)begin
				case(SCLK_CNT)
					6'd0:begin CS_N <= 1'b0;end
					6'd1:begin SCLK <= 1'b0;DIN <= 1'b0;end
					6'd2:begin SCLK <= 1'b1;end
					6'd3:begin SCLK <= 1'b0;end
					6'd4:begin SCLK <= 1'b1;end
//写入通道
					6'd5:begin SCLK <= 1'b0;DIN <= r_channel[2];end
					6'd6:begin SCLK <= 1'b1;end
					6'd7:begin SCLK <= 1'b0;DIN <= r_channel[1];end
					6'd8:begin SCLK <= 1'b1;end
					6'd9:begin SCLK <= 1'b0;DIN <= r_channel[0];end        
//写入数据,在上升沿使数据逐次左移					6'd10,6'd12,6'd14,6'd16,6'd18,6'd20,6'd22,6'd24,6'd26,6'd28,6'd30,6'd32:
						begin SCLK <= 1'b1;r_data <= {r_data[10:0],DOUT};end
					6'd11,6'd13,6'd15,6'd17,6'd19,6'd21,6'd23,6'd25,6'd27,6'd29,6'd31:
						begin SCLK <= 1'b0;end
//传输结束,结束选中
					6'd33:begin CS_N <= 1'b1;end
					default:begin CS_N <= 1'b1;end
				endcase
			end
			else ;
		end
		else begin
			SCLK <= 1'b1;
			CS_N <= 1'b1;
			DIN <= 1'b1;
		end
	end
	
//================done=================//
	always@(posedge clk or negedge rst_n)begin
		if(!rst_n)
			done <= 1'b0;
		else if(cnt_flag && (SCLK_CNT == 'd33))
			done <= 1'b1;
		else
			done <= 1'b0;
	end
	
//================data=================//
	always@(posedge clk or negedge rst_n)begin
		if(!rst_n)
			data <= 'd0;
		else if(cnt_flag && (SCLK_CNT == 'd33))
			data <= r_data;
		else
			data <= data;
	end

endmodule

        注:这部分代码参考自b站FPGA小学生。 

2.4 测试文件

`timescale 1ns/1ns

module SPI_tb;

	reg clk;
	reg rst_n;
	reg start;
	reg [2:0]channel;
	
	wire SCLK;
	wire DIN;
	wire CS_N;
	reg DOUT;
	
	wire done;
	wire [11:0]data;

	reg i = 0;
	
	initial clk = 1'b1;
	always#10 clk = ~clk;
	
	SPI SPI_inst(
		.clk(clk),
		.rst_n(rst_n),
		.start(start),
		.channel(channel),
	
	//========ADC128S022===========//
		.SCLK(SCLK),
		.DIN(DIN),
		.CS_N(CS_N),
		.DOUT(DOUT),
	
		.done(done),
		.data(data)
);
	
	initial begin
		rst_n = 1'b0;
		channel = 'd0;
		start = 1'b0;
		DOUT = 1'b0;
		#100;
		rst_n = 1'b1;
		#100;
		channel = 3;
		for(i=0;i<3;i=i+1)begin
				start = 1;
				#20;
				start = 0;
				gene_DOUT(12'b0101_1100_1010);	//依次将存储器中存储的波形读出,按照ADC的转换结果输出方式送到DOUT信号线上
				@(posedge done);	//等待转换完成信号
				#200;
		end
		#20000;
		$stop;
	end	
				
	
	
	//将并行数据按照ADC的数据输出格式,送到DOUT信号线上,供控制模块采集读取
	task gene_DOUT;
		input [15:0]vdata;
		reg [4:0]cnt;
		begin
			cnt = 0;
			wait(!CS_N);
			while(cnt<16)begin
				@(negedge SCLK) DOUT = vdata[15-cnt];
				cnt = cnt + 1'b1;
			end
		end
	endtask

endmodule 

fpga之间spi,fpga开发,学习,笔记 

3. FLASH读写实验

3.1 FLASH的基本介绍

        FLASH闪存是一种非易失性存储器,常用于存储数据并替代传统的磁盘驱动器。它具有许多优点,例如速度快、耐用性高和体积小。其具有内部时钟,可确保数据在正确的时间段写入和读取,同时提供更高的数据传输速度和准确性,还有助于协调闪存与其他设备之间的数据传输。对其主要操作包括擦除、写入和读取。因为闪存内部的存储单元被分为多个块,每个块都可以独立操作,使得闪存在处理大量数据时表现出色。

fpga之间spi,fpga开发,学习,笔记

图 FLASH的连接方式 

3.2 FLASH的基本指令

 

注:其他有关SPI和FLASH的基础内容请移步至前章的STM32通信总结。 

3.3 实验代码

3.3.1 SPI驱动代码

        值得注意的是这里需要区分delay_cnt、bit_cnt和clk_cnt,具体如下图所示。

fpga之间spi,fpga开发,学习,笔记

         delay_cnt和clk_cnt均为1位,delay_cnt更多是作为延时开始的标志,而bit_clk会在clk_cnt高电平时加一,进而切换输出的位号。根据FLASH定义的时序,上电后会需要一段时间的延时等待,接着是8位的命令、24位的地址以及后续的数据,这也说明了在32位后才真正开始进行数据的传输。

module spi_drive(

	input             clk_100m      ,
	input             sys_rst_n     ,
	
	//user interface
	input             spi_start     ,//spi开启使能。
	input [7:0 ]      spi_cmd       ,//FLAH操作指令
	input [23:0]      spi_addr      ,//FLASH地址
	input [7:0 ]      spi_data      ,//FLASH写入的数据
	input [3:0 ]      cmd_cnt       ,
	
	output            idel_flag_r   ,//空闲状态标志的上升沿 
	output reg        w_data_req    ,//FLASH写数据请求 
	output reg [7:0]  r_data        ,//FLASH读出的数据
	output reg        erro_flag     ,//读出的数据错误标志
	
	//spi interface
	output reg        spi_cs        ,//SPI从机的片选信号,低电平有效。
	output reg        spi_clk       ,//主从机之间的数据同步时钟。
	output reg        spi_mosi      ,//数据引脚,主机输出,从机输入。
	input             spi_miso       //数据引脚,主机输入,从机输出。

);

//状态机
parameter IDLE         =4'd0;//空闲状态
parameter WEL          =4'd1;//写使能状态
parameter S_ERA        =4'd2;//扇区擦除状态
parameter C_ERA        =4'd3;//全局擦除
parameter READ         =4'd4;//读状态
parameter WRITE        =4'd5;//写状态
parameter R_STA_REG    =4'd6;

//指令集
parameter WEL_CMD      =8'h06;
parameter S_ERA_CMD    =8'h20;
parameter C_ERA_CMD    =8'hc7;
parameter READ_CMD     =8'h03;
parameter WRITE_CMD    =8'h02;
parameter R_STA_REG_CMD=8'h05;

//wire define
wire      idel_flag;

//reg define
reg[3:0]  current_state  ;
reg[3:0]  next_state     ;
reg[7:0 ] data_buffer    ;
reg[7:0 ] cmd_buffer     ;
reg[7:0 ] sta_reg        ;
reg[23:0] addr_buffer    ;
reg[31:0] bit_cnt        ;
reg       clk_cnt        ;
reg       dely_cnt       ;
reg[31:0] dely_state_cnt ;
reg[7:0 ] rd_data_buffer ;
reg       spi_clk0       ;
reg       stdone         ;
reg[7:0 ] data_check     ;
reg       idel_flag0     ;
reg       idel_flag1     ;

//*****************************************************
//**                    main code
//*****************************************************

assign idel_flag=(current_state==IDLE)?1:0;//空闲状态标志
assign idel_flag_r=idel_flag0&&(~idel_flag1);//空闲状态标志的上升沿

always @(posedge clk_100m or negedge sys_rst_n )begin
	if(!sys_rst_n)begin
		idel_flag0<=1'b1;
		idel_flag1<=1'b1;
	end
	else begin
		idel_flag0<=idel_flag;
		idel_flag1<=idel_flag0;
	end
end

always @(posedge clk_100m or negedge sys_rst_n )begin
	if(!sys_rst_n)
		w_data_req<=1'b0;
	else if((bit_cnt+2)%8==0&&bit_cnt>=30&&clk_cnt==0&&current_state==WRITE)
		w_data_req<=1'b1;
	else
		w_data_req<=1'b0;
end

always @(posedge clk_100m or negedge sys_rst_n )begin//读出的数据移位寄存
	if(!sys_rst_n)
		rd_data_buffer<=8'd0;
	else if(bit_cnt>=32&&bit_cnt<=2080&&clk_cnt==0&&current_state==READ)									
		rd_data_buffer<={rd_data_buffer[6:0],spi_miso};
	else
		rd_data_buffer<=rd_data_buffer;
end

always @(posedge clk_100m or negedge sys_rst_n )begin//检查读出的数据是否正确
	if(!sys_rst_n)
		data_check<=8'd0;
	else if(bit_cnt%8==0&&bit_cnt>=40&&clk_cnt==1&&current_state==READ)
		data_check<=data_check+1'd1;
	else
		data_check<=data_check;
end

always @(posedge clk_100m or negedge sys_rst_n )begin//读出的数据
	if(!sys_rst_n)
		r_data<=8'd0;
	else if(bit_cnt%8==0&&bit_cnt>38&&clk_cnt==1&&current_state==READ)
		r_data<=rd_data_buffer;
	else
		r_data<=r_data;
end

always @(posedge clk_100m or negedge sys_rst_n )begin//读出的数据错误标志
	if(!sys_rst_n)
		erro_flag<=1'd0;
	else if(bit_cnt>32&&bit_cnt<=2080&&current_state==READ&&cmd_cnt==6)begin
		if(data_check!=r_data)
			erro_flag<=1'd1;
		else
			erro_flag<=erro_flag;
		end
	else
		erro_flag<=erro_flag;
end
	
always @(posedge clk_100m or negedge sys_rst_n )begin
	if(!sys_rst_n)
		data_buffer<=8'd0;
	else if((bit_cnt+1)%8==0&&bit_cnt>30&&clk_cnt==1)
		data_buffer<=spi_data;
	else if(clk_cnt==1&&current_state==WRITE&&bit_cnt>=32)
		data_buffer<={data_buffer[6:0],data_buffer[7]};
	else
		data_buffer<=data_buffer;
end

always @(posedge clk_100m or negedge sys_rst_n )begin
	if(!sys_rst_n)
		cmd_buffer<=8'd0;
	else if(spi_cs==0&&dely_cnt==0)
		cmd_buffer<=spi_cmd;
	else if(clk_cnt==1&&(current_state==WEL||current_state==S_ERA||current_state==C_ERA
	       ||current_state==READ||current_state==WRITE||current_state==R_STA_REG)&&bit_cnt<8)
		cmd_buffer<={cmd_buffer[6:0],1'b1};
	else
		cmd_buffer<=cmd_buffer;
end

always @(posedge clk_100m or negedge sys_rst_n )begin
	if(!sys_rst_n)
		addr_buffer<=8'd0;
	else if(spi_cs==0&&dely_cnt==0)
		addr_buffer<=spi_addr;
	else if(clk_cnt==1&&(current_state==READ||current_state==WRITE)&&bit_cnt>=8&&bit_cnt<32)
		addr_buffer<={addr_buffer[22:0],addr_buffer[23]};
	else
		addr_buffer<=addr_buffer;
end

always @(posedge clk_100m or negedge sys_rst_n )begin
	if(!sys_rst_n)
		clk_cnt<=1'd0;
	else if(dely_cnt==1)
		clk_cnt<=clk_cnt+1'd1;
	else 
		clk_cnt<=1'd0;
end

always @(posedge clk_100m or negedge sys_rst_n )begin
	if(!sys_rst_n)
		dely_cnt<=1'd0;
	else if(spi_cs==0)begin
	    if(dely_cnt<1)
			dely_cnt<=dely_cnt+1'd1;
		else
			dely_cnt<=dely_cnt;
	end
	else
		dely_cnt<=1'd0;
end

always @(posedge clk_100m or negedge sys_rst_n )begin
	if(!sys_rst_n)
		dely_state_cnt<=1'd0;
	else if(spi_cs)begin
	    if(dely_state_cnt<400000000)
			dely_state_cnt<=dely_state_cnt+1'd1;
		else
			dely_state_cnt<=dely_state_cnt;
	end
	else
		dely_state_cnt<=1'd0;
end

always @(posedge clk_100m or negedge sys_rst_n )begin
	if(!sys_rst_n)
		bit_cnt<=11'd0;
	else if(dely_cnt==1)begin
			if(clk_cnt==1'b1)
				bit_cnt<=bit_cnt+1'd1;
			else
				bit_cnt<=bit_cnt;
	end
	else
		bit_cnt<=11'd0;
end
		
//三段式状态机
always @(posedge clk_100m or negedge sys_rst_n )begin
	if(!sys_rst_n)
		current_state<=IDLE;
	else
		current_state<=next_state;
end

always @(*)begin

	case(current_state)
	
	   IDLE: begin
	          if(spi_start&&spi_cmd==WEL_CMD)
				next_state=WEL;
			  else if(spi_start&&spi_cmd==C_ERA_CMD)
				next_state=C_ERA;
			  else if(spi_start&&spi_cmd==S_ERA_CMD)
				next_state=S_ERA;
			  else if(spi_start&&spi_cmd==READ_CMD)
				next_state=READ;
			  else if(spi_start&&spi_cmd==WRITE_CMD)
				next_state=WRITE;
			  else if(spi_start&&spi_cmd==R_STA_REG_CMD)
				next_state=R_STA_REG;
			  else
	            next_state=IDLE;
			end
	
		WEL: begin
			  if(stdone&&bit_cnt>=8)
				   next_state=IDLE;
			  else
		           next_state=WEL;
			  end
			 
		S_ERA: begin
				if(stdone)
					next_state=IDLE;
				else
					next_state=S_ERA;
				end
		C_ERA: begin		
				if(stdone)
					next_state=IDLE;
				else
					next_state=C_ERA;
				end
		READ: begin 		
				if(stdone&&bit_cnt>=8)
					next_state=IDLE;
				else
					next_state=READ;
				end
		WRITE: begin		
				 if(stdone&&bit_cnt>=8)
					next_state=IDLE;
				else
					next_state=WRITE;
				end
		R_STA_REG: begin		
				 if(stdone)
					next_state=IDLE;
				else
					next_state=R_STA_REG;
				end
		
	default: next_state=IDLE;			
	endcase				
end
									
always @(posedge clk_100m or negedge sys_rst_n )begin
	if(!sys_rst_n) begin
		spi_cs<=1'b1;
		spi_clk<=1'b0;
		spi_clk0<=1'b0;
		spi_mosi<=1'b0;	
		stdone<=1'b0;		
	end
	else begin
		case(current_state)
			IDLE: begin
				spi_cs<=1'b1;
				spi_clk<=1'b0;
				spi_mosi<=1'b0;				
			end
			
			WEL: begin
			     stdone<=1'b0;
				 spi_cs<=1'b0;
					 if(dely_cnt==1&&bit_cnt<8) begin						
						spi_clk0<=~spi_clk0;
						spi_clk<=spi_clk0;
						spi_mosi<=cmd_buffer[7];
						end
					 else if(bit_cnt==8&&clk_cnt==0)begin
					    stdone<=1'b1;
						spi_clk<=1'b0;						
						spi_mosi<=1'b0;						
					 end
					 else if(bit_cnt==8&&clk_cnt==1)begin
						spi_cs<=1'b1;						
				 end
				 end
			C_ERA: begin
					stdone<=1'b0;
			         if(dely_state_cnt==10)                
						spi_cs<=1'b0;
					 else if(dely_cnt==1&&bit_cnt<8) begin						
						spi_clk0<=~spi_clk0;
						spi_clk<=spi_clk0;
						spi_mosi<=cmd_buffer[7];
						end
					 else if(bit_cnt==8&&clk_cnt==0)begin
					    stdone<=1'b1;				    
						spi_clk<=1'b0;
						spi_mosi<=1'b0;	
					 end
					  else if(bit_cnt==8&&clk_cnt==1)begin
						spi_cs<=1'b1;						
				 end
				 end
			S_ERA: begin
			       stdone<=1'b0;				 
					if(dely_state_cnt==10)                
						spi_cs<=1'b0;
					 else if(dely_cnt==1&&bit_cnt<8) begin						
						spi_clk0<=~spi_clk0;
						spi_clk<=spi_clk0;
						spi_mosi<=cmd_buffer[7];
						end
					 else if(bit_cnt>=8&&bit_cnt<32&&spi_cs==0)begin
					    spi_cs<=1'b0;
						spi_clk0<=~spi_clk0;
						spi_clk<=spi_clk0;
						spi_mosi<=addr_buffer[23];
					 end
					 else if(bit_cnt==32&&clk_cnt==0) begin
						spi_cs<=1'b1;
						spi_clk<=1'b0;
						spi_mosi<=1'b0;
						stdone<=1'b1;
					 end
				 end
            READ: begin
			      stdone<=1'b0;
				  if(dely_state_cnt==10)                
						spi_cs<=1'b0;
					else if(dely_cnt==1&&bit_cnt<8) begin						
						spi_clk0<=~spi_clk0;
						spi_clk<=spi_clk0;
						spi_mosi<=cmd_buffer[7];
						end
					 else if(bit_cnt>=8&&bit_cnt<32&&spi_cs==0)begin					    
						spi_clk0<=~spi_clk0;
						spi_clk<=spi_clk0;
						spi_mosi<=addr_buffer[23];
					 end
					 else if(bit_cnt>=32&&bit_cnt<2080)begin						
						spi_clk0<=~spi_clk0;
						spi_clk<=spi_clk0;
						spi_mosi<=1'b0;						
					 end
					 else if(bit_cnt==2080&&clk_cnt==0) begin						
						spi_clk<=1'b0;
						spi_mosi<=1'b0;
						stdone<=1'b1;						
					 end
					  else if(bit_cnt==2080&&clk_cnt==1) begin
						spi_cs<=1'b1;
					 end
				 end
            WRITE: begin
			     stdone<=1'b0;
				  if(dely_state_cnt==10)                
						spi_cs<=1'b0;
					 else if(dely_cnt==1&&bit_cnt<8) begin						
						spi_clk0<=~spi_clk0;
						spi_clk<=spi_clk0;
						spi_mosi<=cmd_buffer[7];
						end
					 else if(bit_cnt>=8&&bit_cnt<32&&spi_cs==0)begin					   
						spi_clk0<=~spi_clk0;
						spi_clk<=spi_clk0;
						spi_mosi<=addr_buffer[23];
					 end
					 else if(bit_cnt>=32&&bit_cnt<2080)begin						
						spi_clk0<=~spi_clk0;
						spi_clk<=spi_clk0;
						spi_mosi<=data_buffer[7];
					 end
					 else if(bit_cnt==2080&&clk_cnt==0) begin
						
						spi_clk<=1'b0;
						spi_mosi<=1'b0;
						stdone<=1'b1;
					 end
					  else if(bit_cnt==2080&&clk_cnt==1) begin
						spi_cs<=1'b1;
					 end
                  end
			R_STA_REG:begin				              
						stdone<=1'b0;
				     if(dely_state_cnt==10)                
						spi_cs<=1'b0;
					else if(dely_cnt==1&&bit_cnt<8)begin						
						spi_clk0<=~spi_clk0;
						spi_clk<=spi_clk0;
						spi_mosi<=cmd_buffer[7];
						end
					 else if(bit_cnt==8)begin					   				    
						spi_clk0<=~spi_clk0;
						spi_clk<=spi_clk0;
						spi_mosi<=1'b0;						
					 end                      				 
					  else if(~spi_miso&&bit_cnt%8==0)begin
					    spi_clk<=1'b0;
						spi_cs<=1'b1;
						stdone<=1'b1;
				      end
					 else if(~spi_cs&&dely_cnt==1)begin
						spi_clk0<=~spi_clk0;
						spi_clk<=spi_clk0;
				 end	   			         	 
			  end 
             default: begin
			            stdone<=1'b0;
                        spi_cs<=1'b1;
				        spi_clk<=1'b0;
						spi_clk0<=1'b0;
				        spi_mosi<=1'b0;				        
			end
         endcase
	end
end

endmodule

3.3.2 FLASH读写代码

module flash_rw(

	input            sys_clk      ,
	input            sys_rst_n    ,
	
	input            idel_flag_r  ,//抓取空闲状态上升沿
	input            w_data_req   ,//写请求
	output reg[3:0 ] cmd_cnt      ,//计数器
	output reg       spi_start    ,//spi开启使能
	output reg[7:0 ] spi_cmd      ,//spi命令号
	output reg[7:0 ] spi_data      
   
);

//指令集
parameter WEL_CMD      =16'h06;//允许写
parameter S_ERA_CMD    =16'h20;//扇擦除
parameter C_ERA_CMD    =16'hc7;//块擦除
parameter READ_CMD     =16'h03;//读数据
parameter WRITE_CMD    =16'h02;//写数据
parameter R_STA_REG_CMD=8'h05 ;//读状态寄存器

//reg define
reg[3:0] flash_start;

//*****************************************************
//**                    main code
//*****************************************************

always @(posedge sys_clk or negedge sys_rst_n )begin //延时
	if(!sys_rst_n)
		flash_start<=0;
	else if(flash_start<=10)
	    flash_start<=flash_start+1;
	else
		flash_start<=flash_start;
end

always @(posedge sys_clk or negedge sys_rst_n )begin   
	if(!sys_rst_n)
		cmd_cnt<=0;
	else if(flash_start==9)     //生成第一次flash_start
	    spi_start<=1'b1;
	else if(idel_flag_r&&cmd_cnt<10)begin    //状态抓取idel_flag_r状态
	    cmd_cnt<=cmd_cnt+1;                  //切换下一次指令
		spi_start<=1'b1;
	end
	else begin
		cmd_cnt<=cmd_cnt;
		spi_start<=1'b0;
	end
end

always @(posedge sys_clk or negedge sys_rst_n )begin    //数据逐次加一
	if(!sys_rst_n)
		spi_data<=8'd0;
	else if(w_data_req)
		spi_data<=spi_data+1'b1;
	else
		spi_data<=spi_data;
end

always @(*)begin                                        //赋值命令
	case(cmd_cnt)
		0:spi_cmd=WEL_CMD;
		1:spi_cmd=C_ERA_CMD;
		2:spi_cmd=R_STA_REG_CMD;
		3:spi_cmd=WEL_CMD;
		4:spi_cmd=WRITE_CMD;
		5:spi_cmd=R_STA_REG_CMD;
		6:spi_cmd=READ_CMD;
		7:spi_cmd=WEL_CMD;
		8:spi_cmd=S_ERA_CMD;
		9:spi_cmd=R_STA_REG_CMD;
		10:spi_cmd=READ_CMD;
		
	default:;
	endcase
end

endmodule

免责声明:本文所引用的各种资料均用于自己学习使用,这里感谢黑金和正点原子官方的资料以及各位优秀的创作者。文章来源地址https://www.toymoban.com/news/detail-854034.html

到了这里,关于学习笔记之FPGA的SPI通讯的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

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

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

相关文章

  • 【FPGA】SPI读写flash

    SPI是同步全双工通信,通信原理以主从方式工作,通常有一个主设备和一个或多个从设备,需要4根线连接:MISO(主设备数据输入)、MOSI(主设备输出)、SCLK(时钟)、CS(片选)。通常拉低对应从机的片选来收发数据。 MISO:主设备输入,从设备输出 MOSI:主设备输出,从设备输入 SCLK:时

    2024年02月05日
    浏览(42)
  • FPGA SPI 驱动程序

    此驱动程序已经完成很久了,花了2个星期的时间,主要是提升程序运行的效率。最近整理文件的时候又看到了,记录一下。 (1)状态控制程序 其主要是以空状态,写状态,读状态三个状态顺序执行的。 (2)写命令程序部分 (3)读数据程序部分

    2024年02月10日
    浏览(44)
  • 【LabVIEW FPGA入门】LabVIEW FPGA 实现SPI通信协议

            该实现由两个组件组成:在 LabVIEW FPGA 中实现的 SPI 协议以及用于从主机 PC 或实时控制器与 FPGA 进行通信的 LabVIEW 主机接口。该架构允许从单个主机程序控制多个 SPI 端口,同时仍然允许定制 FPGA VI 以进行其他数据采集和处理。该实现不使用任何DMA(直接内存访问

    2024年01月17日
    浏览(53)
  • 【FPGA】SPI读写FLASH闪存

    通信原理 SPI也是以主从方式工作,通常需要四根线来完成数据的传输,分别是MISO MOSI CS SCLK。以下是这四根线代表的含义: MISO:主设备输入,从设备输出 MOSI:主设备输出,从设备输入 CS :片选信号,选择进行通信的从设备 SCLK:时钟线,由主设备产生给到从设备 SPI通信的

    2024年02月16日
    浏览(38)
  • FPGA使用SPI控制FLASH

    通过控制FLASH芯片进一步熟悉SPI协议 Flash 存储器 : Flash 存储器是一种非易失性存储器,它具有 RAM 和 ROM 的一些特点。与 ROM 类似,Flash 存储器的内容在断电时不会丢失,但与 RAM 类似,它可以通过编程来修改存储的内容。Flash 存储器通常用于嵌入式系统中存储程序代码、配置

    2024年03月19日
    浏览(52)
  • FPGA DAC模块 SPI通信

    1.串行外设接口(SPI)是微控制器和外围IC(如传感器、ADC、DAC、移位寄存器、SRAM等)之间使用最广泛的接口之一。 2.SPI是一种同步、全双工、主从式接口。来自主机或从机的数据在时钟上升沿或下降沿同步。主机和从机可以同时传输数据。SPI接口可以是3线式或4线式。    SPI总

    2024年02月11日
    浏览(41)
  • FPGA驱动SPI屏幕(附完整工程)

    相信大家都玩过屏幕,在FPGA上使用最多的就是VGA/HDMI接口的显示器了,这两种显示器的优点就不用说了,缺点就是体积比较大,而且价格比较贵,对于追求便携/价格低的我来说,SPI接口的屏幕才是我的首要选择,而且一般是可以带触摸的哦。 后面图像处理相关的代码都会基于

    2024年02月16日
    浏览(48)
  • FPGA模块——SPI协议(读写FLASH)

    芯片引脚图: 内部结构图: 存储区域总共分成了32块,每块64KB。每块又分成了16个部分,每个部分4KB。方便进行读取和局部操作。 电路设计 SPI的四种模式 这里使用这个模式: 主机和从机在时钟上升沿放入要输出的数据,在时钟下降沿读取要输入的数据。 8个时钟后交换一个

    2024年02月05日
    浏览(45)
  • 【最通用版FPGA 实现 SPI 驱动】

    最近研究了一下SPI协议的FPGA实现,发现网上很多大佬分享的方法都是针对某一特定的flash芯片或者某一传感器芯片来设计电路结构的。所以想根据SPI(Serial Peripheral Interface)的基本通讯协议实现一个通用版的SPI Master驱动。SPI在嵌入式领域是一个很成熟且应用非常广泛的通信协

    2024年02月05日
    浏览(45)
  • FPGA——实现三线SPI和UART

    目录 逻辑框图(原理图) 端口约束和ILA  ILA waveform  实测波形 串口调试工具 源代码 顶层  FPGA和芯片之间通过三线SPI接口通信(DATA复用一个IO端口),FPGA和PC之间通过UART串口通信。 原理图包含4个模块,分别为:内建模块IBUFDS(用于将外部差分时钟转换为内部单端时钟)、

    2024年02月09日
    浏览(34)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包