Zynq开发实践(FPGA之spi实现)

【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】

虽然串口用的地方比较多,实现起来也比较简单。但是串口本身速度比较慢,不利于高速数据通信。而且单个串口没有办法和很多芯片设备通信。所以,人们在串口的基础之上添加clk、cs信号,这样就形成了基本的spi总线协议。它也是mcu/soc和fpga通信的重要方式之一。

1、spi和串口的区别

相比较串口,spi多了cs和clk。正因为有了cs,这样fpga就可以通过它来区分不同的spi接口芯片。此时clk、rx、tx就可以完全复用。此外有了clk之后,数据的收发都可以借助于clk的边沿来进行处理,处理的效率高了很多,甚至可以达到20~50M,快一点的话100M也是可以的,这不是串口可以比拟的。

2、spi和iic的区别

现在spi和iic比较,iic的收发是同一根pin,iic也有clk信号,但是没有cs信号。这导致iic上的芯片都会接收总线上的数据,但是如果发现设备数据不是自己的,会直接扔掉。

3、spi和iic应用范围非常广

不仅spi norflash、spi nandflash,还是spi网络芯片、spi adc、spi dac,spi和iic的应用范围都是非常广的。spi一般用于中高速芯片,iic和uart则用于低速芯片。所以说,如果掌握好了spi、iic、uart之后,基本上mcu能做的事情,都可以用fpga来完成。像rgb、mcu、vga这样mcu做不来的视频接口,也可以用fpga来完成。甚至于很多sdio接口的外设,都是可以转换成spi访问,tf卡就是一个典型的案例。

spi本身只是一个总线,所以我们实际对芯片操作的时候,还需要了解操作的方式,比如怎么发命令,怎么发命令和地址,怎么读数据。就拿spi nanflash来说,要做好这个工作需要分成这三步,

复制代码
实现基本的spi协议;
实现访问spi nandflash需要的各个命令;
根据需求访问spi nandflash,做好读、写和状态的校验。

4、spi master和spi slave

一般把主动发起命令的设备称之为master,被动接收命令的设备称之为slave。当然fpga和spi nandflash通信时,此时fpga就是master,nandflash就是slave。如果此时mcu/soc和fpga通信,这种情况下mcu/soc就是master,fpga就是slave。

如果有同学不知道如何用spi写slave,那么可以找一个spi nandflash的芯片手册,把自己当成一个spi nand,去适配master就可以了。

5、spi的四种模式

根据时钟极性cpol和时钟相位cpha,就可以把spi分成四种工作模式。其中cpol,就是空闲状态的时候,spi处于高电平还是低电平。而cpha,则告诉我们采样的时候,应该是第一个边沿采样,还是第二个边沿采样。实际应用的时候,我们记住0-0就可以,即空闲状态是0,第一个边沿触发的时候采样。

6、发送和采样数据

spi的发送和采样都是基于clock进行的。如果spi时钟不是很快,那么可以通过计数分频的办法去解决。首先我们谈一下发送。假设spi的工作模式是0-0,即空闲为0,上升沿采样。这种情况下,只需要一个周期发送一个bit数据即可,每半个周期时钟反转一下,也就是上升沿的时候提示对方接收数据,因此数据肯定是提前准备好的。

采样的时候一般是反过来的。以spi nandflash为例。fpga发送完命令之后,一般就可以开始准备采样数据了。采样的时候,其实和发送也是一样的,在上升沿的时候开始采样,因为对方数据发送一般是下降沿开始发送。所以在一个周期内spi clock做两次翻转就可以了。

7、spi协议的实现

所以这里spi的实现主要集中在底层领域,涉及到spi clock、cs、mosi、miso,可以好好看一下,

复制代码
module spi_top(input clk,
	input rst,
	input rw,
	input rw_valid,
	input[7:0] w_data,
	output[7:0] r_data,
	output status,
	// about signal
	output cs,
	output reg tx,
	output reg spi_clk,
	input rx);

reg[3:0] state;
reg[3:0] next_state;
reg[15:0] counter;
reg[3:0] num;

reg rw_reg;
reg[7:0] w_data_reg;
reg[7:0] r_data_reg;

localparam TIMER_INTERVAL = 16'd20;

localparam IDLE     = 4'h0;
localparam SPI_CLK  = 4'h1;
localparam SPI_CHK  = 4'h2;
localparam LAST_CLK = 4'h3;
localparam SPI_END  = 4'h4;

// about state machine

always@(posedge clk or negedge rst)
	if(!rst)
		state <= IDLE;
	else 
		state <= next_state;

always@(*)
	case(state)
		IDLE: begin
			if(rw_valid)
				next_state = SPI_CLK;
			else
				next_state = IDLE;
		end

		SPI_CLK: begin
			if(counter == TIMER_INTERVAL)
				next_state = SPI_CHK;
			else
				next_state = SPI_CLK;
		end
			
		SPI_CHK: begin
			if(num == 4'hf)
				next_state = LAST_CLK;
			else
				next_state = SPI_CLK;
		end
			
		LAST_CLK: begin
			if(counter == TIMER_INTERVAL)
				next_state = SPI_END;
			else
				next_state = LAST_CLK;
		end
			
		SPI_END:
			next_state = IDLE;
			
		default:
			next_state = IDLE;
	endcase
	
// about cs, sometime can be used for multiple devices

assign cs = (state != IDLE) ? 0 : 1;

// save rw data

always@(posedge clk or negedge rst)
	if(!rst) begin
		rw_reg <= 0;
		w_data_reg <= 8'h0;
	end
	else if(state == IDLE && rw_valid) begin
		rw_reg <= rw;
		w_data_reg <= w_data;
	end

// about frequency divison
	
always@(posedge clk or negedge rst)
	if(!rst)
		counter <= 16'h0;
	else if(state == SPI_CLK || state == LAST_CLK)
		counter <= counter + 1;
	else
		counter <= 16'h0;

// about clk number

always@(posedge clk or negedge rst)
	if(!rst)
		num <= 4'h0;
	else if(state == SPI_CHK)
		num <= num + 1;
	else if(state == IDLE)
		num <= 4'h0;

//
// about spi clk
// this is very import, useful for low speed communication
//

always@(posedge clk or negedge rst)
	if(!rst)
		spi_clk <= 0;
	else if(state == SPI_CLK && counter == TIMER_INTERVAL)
		spi_clk <= ~spi_clk;
	else if(state == IDLE)
		spi_clk <= 0;

// about tx signal

always@(posedge clk or negedge rst)
	if(!rst)
		tx <= 0;
	else if(state == SPI_CLK && rw_reg)
		tx <= w_data_reg[0];
	else if(state == SPI_CHK && num <= 13 && rw_reg) // fix bug about num 2025.9.9
		tx <= w_data_reg[(num >> 1) +1];
	else if(state == IDLE)
		tx <= 0;

// about rx signal

always@(posedge clk or negedge rst)
	if(!rst)
		r_data_reg <= 8'h0;
	else if(state == SPI_CHK && !num[0] && !rw_reg)
		r_data_reg[num >> 1] <= rx;
	else if(state == IDLE)
		r_data_reg <= 8'h0;
		
assign r_data = r_data_reg;
assign status = (state == SPI_END);
	
endmodule

实际跑起来,添加上testbench之后,仿真截图是这样的,

相关推荐
太爱学习了8 小时前
FPGA雷达信号处理之:自适应门限阈值
fpga开发·信号处理
国科安芯14 小时前
前沿探索:RISC-V 架构 MCU 在航天级辐射环境下的可靠性测试
网络·单片机·嵌入式硬件·fpga开发·硬件架构·risc-v
范纹杉想快点毕业14 小时前
请创建一个视觉精美、交互流畅的进阶版贪吃蛇游戏
数据库·嵌入式硬件·算法·mongodb·游戏·fpga开发·交互
第二层皮-合肥1 天前
FPGA硬件设计-基础流程
fpga开发
第二层皮-合肥1 天前
FPGA硬件开发-Xilinx产品介绍
fpga开发
XINVRY-FPGA1 天前
XCVP1902-2MSEVSVA6865 AMD 赛灵思 XilinxVersal Premium FPGA
人工智能·嵌入式硬件·神经网络·fpga开发·云计算·腾讯云·fpga
热爱学习地派大星1 天前
FPGA实现CRC校验
fpga开发
芒果树技术1 天前
MT-PXle RIO模块【高性能FPGA+ LVDS】采用FPGA实现高效LVDS通讯
fpga开发·模块测试·fpga
明月清了个风1 天前
STM32初始化串口重定向后printf调试信息不输出的问题
stm32·单片机·fpga开发·嵌入式软件