FPGA学习笔记04——SPI通信

参考内容:Verilog硬件描述语言 西安电子科技大学 蔡觉平等主讲 https://www.bilibili.com/video/BV1zb411s7bY?p=21

王建飞《你好FPGA一本可以听的书》

蔡觉平《Verilog HDL数字集成电路设计原理与应用》

正点原子《开拓者FPGA开发指南》

https://www.cnblogs.com/liujinggang/p/9609739.html

1.介绍

SPI(Serial Peripheral Interface,串行外围设备接口),是Motorola公司提出的一种同步串行接口技术,是一种高速、全双工、同步通信总线,在芯片中只占用四根管脚用来控制及数据传输,广泛用于EEPROM、Flash、RTC(实时时钟)、ADC(数模转换器)、DSP(数字信号处理器)以及数字信号解码器上。SPI通信的速度很容易达到好几兆bps,所以可以用SPI总线传输一些未压缩的音频以及压缩的视频。

四根线的作用分别如下:

    SCK(Serial Clock):SCK是串行时钟线,作用是Master向Slave传输时钟信号,控制数据交换的时机和速率;

    MOSI(Master Out Slave in):在SPI Master上也被称为Tx-channel,作用是SPI主机给SPI从机发送数据;

    CS/SS(Chip Select/Slave Select):作用是SPI Master选择与哪一个SPI Slave通信,低电平表示从机被选中(低电平有效);

    MISO(Master In Slave Out):在SPI Master上也被称为Rx-channel,作用是SPI主机接收SPI从机传输过来的数据;

特点

  1. 采用主从模式(Master-Slave)的控制方式,支持单Master多Slave。SPI规定了两个SPI设备之间通信必须由主设备Master来控制从设备Slave。也就是说,如果FPGA是主机的情况下,不管是FPGA给芯片发送数据还是从芯片中接收数据,写Verilog逻辑的时候片选信号CS与串行时钟信号SCK必须由FPGA来产生。同时一个Master可以设置多个片选(Chip Select)来控制多个Slave。SPI协议还规定Slave设备的clock由Master通过SCK管脚提供给Slave,Slave本身不能产生或控制clock,没有clock则Slave不能正常工作。
  2. SPI总线在传输数据的同时也传输了时钟信号,所以SPI协议是一种同步(Synchronous)传输协议。Master会根据将要交换的数据产生相应的时钟脉冲,组成时钟信号,时钟信号通过时钟极性(CPOL)和时钟相位(CPHA)控制两个SPI设备何时交换数据以及何时对接收数据进行采样,保证数据在两个设备之间是同步传输的。
  3. SPI总线协议是一种全双工的串行通信协议,数据传输时高位在前,低位在后。SPI协议规定一个SPI设备不能在数据通信过程中仅仅充当一个发送者(Transmitter)或者接受者(Receiver)。在片选信号CS为0的情况下,每个clock周期内,SPI设备都会发送并接收1 bit数据,相当于有1 bit数据被交换了。数据传输高位在前,低位在后(MSB first)。

  模式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时钟的下降沿切换

2.硬件

FPGA管脚配置

信号名方向管教端口说明
sys_clkinputE1

系统时钟50M

sys_rst_ninputM1系统复位,低电平有效
led0outputF3 
led1outputF5 
key0inputG5 
key1inputL7 
key2inputJ6 
spi_misooutputD5SPI串行输入
spi_sckinputC3SPI时钟
spi_csinputD6SPI片选信号
spi_mosiinputD4SPI输出

3.程序设计

  I_tx_en是主机给从机发送数据的使能信号,当I_tx_en为1时主机才能给从机发送数据;

  I_rx _en是主机从从机接收数据的使能信号,当I_rx_en为1时主机才能从从机接收数据;

  O_tx_done是主机给从机发送数据完成的标志位,发送完成后会产生一个高脉冲;

  O_rx_done是主机从从机接收数据完成的标志位,接收完成后会产生一个高脉冲;

       data_recv是把从机接收回来的串行数据并行化以后的并行数据;

       data_send是主机要发送的并行数据;

选用模式0的时序,下面是发送数据状态机。

    状态0:SCK为0,MOSI为要发送的数据的最高位,即data_send[7]

    状态1:SCK为1,MOSI保持不变

    状态2:SCK为0,MOSI为要发送的数据的次高位,即data_send[6]

    状态3:SCK为1,MOSI保持不变

    状态4:SCK为0,MOSI为要发送的数据的下一位,即data_send[5]

    状态5:SCK为1,MOSI保持不变

    状态6:SCK为0,MOSI为要发送的数据的下一位,即data_send[4]

    状态7:SCK为1,MOSI保持不变

    状态8:SCK为0,MOSI为要发送的数据的下一位,即data_send[3]

    状态9:SCK为1,MOSI保持不变

    状态10:SCK为0,MOSI为要发送的数据的下一位,即data_send[2]

    状态11:SCK为1,MOSI保持不变

    状态12:SCK为0,MOSI为要发送的数据的下一位,即data_send[1]

    状态13:SCK为1,MOSI保持不变

    状态14:SCK为0,MOSI为要发送的数据的最低位,即data_send[0]

    状态15:SCK为1,MOSI保持不变

一个字节数据发送完毕以后,产生一个发送完成标志位O_tx_done并把CS/SS信号拉高完成一次发送。

当FPGA通过SPI总线从QSPI Flash中接收一个字节(8-bit)的数据时,首先FPGA把CS/SS片选信号设置为0,表示准备开始接收数据,整个接收数据过程其实也可以分为16个状态,但是与发送过程不同的是,为了保证接收到的数据准确,必须在数据的正中间采样,也就是说模式0时序图中灰色实线的地方才是代码中锁存数据的地方,所以接收过程的每个状态执行的操作为:

 状态0:SCK为0,不锁存MISO上的数据

    状态1:SCK为1,锁存MISO上的数据,即把MISO上的数据赋值给data_recv[7]

    状态2:SCK为0,不锁存MISO上的数据

    状态3:SCK为1,锁存MISO上的数据,即把MISO上的数据赋值给data_recv[6]

    状态4:SCK为0,不锁存MISO上的数据

    状态5:SCK为1,锁存MISO上的数据,即把MISO上的数据赋值给data_recv[5]

    状态6:SCK为0,不锁存MISO上的数据

    状态7:SCK为1,锁存MISO上的数据,即把MISO上的数据赋值给data_recv[4]

    状态8:SCK为0,不锁存MISO上的数据

    状态9:SCK为1,锁存MISO上的数据,即把MISO上的数据赋值给data_recv[3]

    状态10:SCK为0,不锁存MISO上的数据

    状态11:SCK为1,锁存MISO上的数据,即把MISO上的数据赋值给data_recv[2]

    状态12:SCK为0,不锁存MISO上的数据

    状态13:SCK为1,锁存MISO上的数据,即把MISO上的数据赋值给data_recv[1]

    状态14:SCK为0,不锁存MISO上的数据

    状态15:SCK为1,锁存MISO上的数据,即把MISO上的数据赋值给data_recv[0]

一个字节数据接收完毕以后,产生一个接收完成标志位O_rx_done并把CS/SS信号拉高完成一次数据的接收。

发送程序:

    if(I_tx_en)begin
		spi_cs    <=  1'b0    ; // pull down the cs
		case(R_tx_state)
		4'd0:
			begin
				spi_mosi   <= data_send[7];
				spi_sck    <= 1'd0;
				R_tx_state <= R_tx_state + 1'd1;
			end
		4'd1:
			begin
				spi_sck    <= 1'd1;
				R_tx_state <= R_tx_state + 1'd1;			
			end
		4'd2:
			begin
				spi_mosi   <= data_send[6];
				spi_sck    <= 1'd0;
				R_tx_state <= R_tx_state + 1'd1;
			end
		4'd3:
			begin
				spi_sck    <= 1'd1;
				R_tx_state <= R_tx_state + 1'd1;
			end
		4'd4:
			begin
				spi_mosi   <= data_send[5];
				spi_sck    <= 1'd0;
				R_tx_state <= R_tx_state + 1'd1;
			end
		4'd5:
			begin
				spi_sck    <= 1'd1;
				R_tx_state <= R_tx_state + 1'd1;
			end
		4'd6:
			begin
				spi_mosi   <= data_send[4];
				spi_sck    <= 1'd0;
				R_tx_state <= R_tx_state + 1'd1;
			end
		4'd7:
			begin
				spi_sck    <= 1'd1;
				R_tx_state <= R_tx_state + 1'd1;
			end
		4'd8:
			begin
				spi_mosi   <= data_send[3];
				spi_sck    <= 1'd0;
				R_tx_state <= R_tx_state + 1'd1;
			end
		4'd9:
			begin
				spi_sck    <= 1'd1;
				R_tx_state <= R_tx_state + 1'd1;
			end
		4'd10:
			begin
				spi_mosi   <= data_send[2];
				spi_sck    <= 1'd0;
				R_tx_state <= R_tx_state + 1'd1;
			end
		4'd11:
			begin
				spi_sck    <= 1'd1;
				R_tx_state <= R_tx_state + 1'd1;
			end
		4'd12:
			begin
				spi_mosi   <= data_send[1];
				spi_sck    <= 1'd0;
				R_tx_state <= R_tx_state + 1'd1;
			end
		4'd13:
			begin
				spi_sck    <= 1'd1;
				R_tx_state <= R_tx_state + 1'd1;
			end
		4'd14:
			begin
				spi_mosi   <= data_send[0];
				spi_sck    <= 1'd0;
				R_tx_state <= R_tx_state + 1'd1;
				O_tx_done  <= 1'd1;
			end
		4'd15:
			begin
				spi_sck    <= 1'd1;
				R_tx_state <= R_tx_state + 1'd1;
				O_tx_done  <= 1'd0;
			end
		default:;
		endcase

接收程序:

    else if(I_rx_en)begin        
        spi_cs    <=  1'b0    ; // pull down the cs
		case(R_rx_state)
		4'd0:
			begin
				spi_sck      <= 1'd0;
				R_rx_state   <= R_rx_state + 1'd1;
				O_rx_done    <= 1'd0;
			end
		4'd1:
			begin
				spi_sck      <= 1'd1;
				R_rx_state	 <= R_rx_state + 1'd1;
				data_recv[7] <= spi_miso;
				O_rx_done    <= 1'd0;
			end
		4'd2:
			begin
				spi_sck      <= 1'd0;
				R_rx_state   <= R_rx_state + 1'd1;
				O_rx_done    <= 1'd0;
			end
		4'd3:
			begin
				spi_sck      <= 1'd1;
				R_rx_state	 <= R_rx_state + 1'd1;
				data_recv[6] <= spi_miso;
				O_rx_done    <= 1'd0;
			end
		4'd4:
			begin
				spi_sck      <= 1'd0;
				R_rx_state   <= R_rx_state + 1'd1;
				O_rx_done    <= 1'd0;
			end
		4'd5:
			begin
				spi_sck      <= 1'd1;
				R_rx_state	 <= R_rx_state + 1'd1;
				data_recv[5] <= spi_miso;
				O_rx_done    <= 1'd0;
			end
		4'd6:
			begin
				spi_sck      <= 1'd0;
				R_rx_state   <= R_rx_state + 1'd1;
			end
		4'd7:
			begin
				spi_sck      <= 1'd1;
				R_rx_state	 <= R_rx_state + 1'd1;
				data_recv[4] <= spi_miso;
			end
		4'd8:
			begin
				spi_sck      <= 1'd0;
				R_rx_state   <= R_rx_state + 1'd1;
			end
		4'd9:
			begin
				spi_sck      <= 1'd1;
				R_rx_state	 <= R_rx_state + 1'd1;
				data_recv[3] <= spi_miso;
			end
		4'd10:
			begin
				spi_sck      <= 1'd0;
				R_rx_state   <= R_rx_state + 1'd1;
			end
		4'd11:
			begin
				spi_sck      <= 1'd1;
				R_rx_state	 <= R_rx_state + 1'd1;
				data_recv[2] <= spi_miso;
			end
		4'd12:
			begin
				spi_sck      <= 1'd0;
				R_rx_state   <= R_rx_state + 1'd1;
			end
		4'd13:
			begin
				spi_sck      <= 1'd1;
				R_rx_state	 <= R_rx_state + 1'd1;
				data_recv[1] <= spi_miso;
			end
		4'd14:
			begin
				spi_sck      <= 1'd0;
				R_rx_state   <= R_rx_state + 1'd1;
			end
		4'd15:
			begin
				spi_sck      <= 1'd1;
				R_rx_state	 <= R_rx_state + 1'd1;
				data_recv[0] <= spi_miso;
				O_rx_done    <= 1'd1;
			end
		default:;
		endcase

用sigaltap观察波形

  • 4
    点赞
  • 48
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
FPGA(现场可编程门阵列)和STM32是两种不同的芯片。FPGA是一种可编程逻辑器件,它可以按照用户需求重新配置其逻辑电路,适用于复杂的数字电路设计。而STM32是一种微控制器,它包含了一个处理器核心以及丰富的外设,适用于嵌入式系统设计。 SPI(串行外设接口)是一种常用的串行通信协议,可以用于连接多个设备,实现设备之间的数据传输。FPGA和STM32都支持SPI通信,可以通过相应的硬件接口和软件配置实现。 在使用FPGA实现SPI通信时,我们可以通过编写Verilog或VHDL代码来定义FPGA中的SPI接口,配置FPGA的引脚和时钟,并实现发送和接收数据的逻辑。FPGA可以使用其可编程逻辑电路来处理SPI通信协议的各个部分,例如时序、数据格式和校验等。通过适当的配置和连接,我们可以将FPGA与其他SPI设备(如传感器、存储器或其他嵌入式设备)进行通信。 而在STM32中实现SPI通信,我们可以使用STM32的内置SPI外设来实现。首先,我们需要配置SPI外设的相关寄存器,包括时钟速率、数据格式和模式等。然后,使用STM32的GPIO外设来配置相关引脚,使其与SPI外设连接。最后,通过编写适当的软件代码,实现SPI数据的发送和接收。这些代码可以使用STM32的相关库函数或直接操作寄存器来实现。通过配置和连接,我们可以将STM32与其他SPI设备进行通信,实现数据的传输和交互。 无论是通过FPGA还是STM32实现SPI通信,我们都需要仔细了解SPI协议的要求和特性,充分利用相应的硬件资源和软件工具,以确保通信的准确性和可靠性。同时,还要根据具体的应用需求和系统设计,选择合适的芯片和外设,进行相应的配置和编程。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值