目录

一、如何代码获取

二、SPI原理简述

SPI数据收发说明

SPI的四种模式

三、SPI的FPGA代码和仿真读

源代码

modelsim仿真验证


一、如何代码获取

       推荐大家直接去开源网站下载程序代码,直接搜索想要的代码,然后根据排名先后下载即可,程序一般都比较规范,标注也详细,学习起来不容易走弯路。

        以下时github上搜索到的FPGA实现SPI的例程,建议多下载几个文件,仔细的阅读一遍,对比完之后找一个最合适的。我比较推荐以下两个:nandland/spi-master/spi-slave(Verilog)和nematoli/SPI-FPGA-VHDL(本人比较喜欢VHDL语言)。

二、SPI原理简述

需要提前了解一些SPI的知识,便于理解程序,下面对SPI的原理简单描述。SPI,是一种高速的,全双工,同步的通信总线。SPI分为主、从两种模式,一个SPI通讯系统需要包含一个(且只能是一个)主设备,一个或多个从设备。提供时钟的为主设备(Master),接收时钟的设备为从设备(Slave),SPI接口的读写操作,都是由主设备发起。当存在多个从设备时,通过各自的片选信号进行管理。

  • SPI数据收发说明

1-首先拉低对应SS信号线,表示与该设备进行通信;

2-主机通过发送SCLK时钟信号,来告诉从机写数据或者读数据。这里要注意,SCLK时钟信号可能是低电平有效,也可能是高电平有效,因为SPI有四种模式,后续会介绍;

3-主机(Master)将要发送的数据写到发送数据缓存区(Menory),缓存区经过移位寄存器(0~7),串行移位寄存器通过MOSI信号线将字节一位一位的移出去传送给从机,同时MISO接口接收到的数据经过移位寄存器一位一位的移到接收缓存区。

4-从机(Slave)也将自己的串行移位寄存器(0~7)中的内容通过MISO信号线返回给主机。同时通过MOSI信号线接收主机发送的数据,这样,两个移位寄存器中的内容就被交换。

注意:SPI只有主模式和从模式之分,没有读和写的说法,外设的写操作和读操作是同步完成的。如果只进行写操作,主机只需忽略接收到的字节;反之,若主机要读取从机的一个字节,就必须发送一个空字节来引发从机的传输。也就是说,你发一个数据必然会收到一个数据;你要收一个数据必须也要先发一个数据。

  • SPI的四种模式

SPI 有四种工作模式,通过串行时钟极性(CPOL)和相位(CPHA)的搭配来得到四种工作模式:

① CPOL=0,串行时钟空闲状态为低电平。

② CPOL=1,串行时钟空闲状态为高电平,此时可以通过配置时钟相位(CPHA)来选择具体的传输协议。

③ CPHA=0,串行时钟的第一个跳变沿(上升沿或下降沿)采集数据。

④ CPHA=1,串行时钟的第二个跳变沿(上升沿或下降沿)采集数据。

通常分化成4种工作模式,注意主设备和从设备是同步采集数据,同步发送数据,记住这一点就好理解了。

  • Mode0:CPOL=0,CPHA=0:此时空闲态时,SCLK处于低电平,数据采样是在第1个边沿,也就是SCLK由低电平到高电平的跳变,所以数据采样是在上升沿,数据发送准备是在下降沿,第一个数据要在第一个上升沿之前准备好。

  • Mode1:CPOL=0,CPHA=1:此时空闲态时,SCLK处于低电平,数据发送准备是在第1个边沿,也就是SCLK由低电平到高电平的跳变,所以数据采样是在下降沿,数据发送是在上升沿。

  • Mode2:CPOL=1,CPHA=0:此时空闲态时,SCLK处于高电平,数据采集是在第1个边沿,也就是SCLK由高电平到低电平的跳变,所以数据采集是在下降沿,数据发送是在上升沿,第一个数据要在第一个下降沿之前准备好。。

  • Mode3:CPOL=1,CPHA=1:此时空闲态时,SCLK处于高电平,数据发送是在第1个边沿,也就是SCLK由高电平到低电平的跳变,所以数据采集是在上升沿,数据发送是在下降沿。

这四种工作模式如下图所示:

三、SPI的FPGA代码和仿真

        测试代码选用nematoli/SPI-FPGA-VHDL,包含了主机程序、从机程序和激励文件(原有的不完整,后续了些内容),下面位程序的源代码,同时提供了modolsim仿真用的do文件。

  • 源代码

主机程序:

LIBRARY ieee;
USE ieee.std_logic_1164.all;
USE ieee.std_logic_arith.all;
USE ieee.std_logic_unsigned.all;

ENTITY spi_master IS
  GENERIC(
    data_length : INTEGER := 16);     --data length in bits
  PORT(
    clk     : IN     STD_LOGIC;                             --system clock
    reset_n : IN     STD_LOGIC;                             --asynchronous active low reset
    enable  : IN     STD_LOGIC;                             --initiate communication
	  cpol    : IN     STD_LOGIC;  									          --clock polarity mode
    cpha    : IN     STD_LOGIC;  									          --clock phase mode
    miso    : IN     STD_LOGIC;                             --master in slave out
    sclk    : OUT    STD_LOGIC;                             --spi clock
    ss_n    : OUT    STD_LOGIC;                             --slave select
    mosi    : OUT    STD_LOGIC;                             --master out slave in
    busy    : OUT    STD_LOGIC;                             --master busy signal
    tx		: IN     STD_LOGIC_VECTOR(data_length-1 DOWNTO 0);  --data to transmit
    rx	   : OUT    STD_LOGIC_VECTOR(data_length-1 DOWNTO 0)); --data received
END spi_master;

ARCHITECTURE behavioural OF spi_master IS
  TYPE FSM IS(init, execute);                           		--state machine
  SIGNAL state       : FSM;                             
  SIGNAL receive_transmit : STD_LOGIC;                      --'1' for tx, '0' for rx 
  SIGNAL clk_toggles : INTEGER RANGE 0 TO data_length*2 + 1;    --clock toggle counter
  SIGNAL last_bit		: INTEGER RANGE 0 TO data_length*2;        --last bit indicator
  SIGNAL rxBuffer    : STD_LOGIC_VECTOR(data_length-1 DOWNTO 0) := (OTHERS => '0'); --receive data buffer
  SIGNAL txBuffer    : STD_LOGIC_VECTOR(data_length-1 DOWNTO 0) := (OTHERS => '0'); --transmit data buffer
  SIGNAL INT_ss_n    : STD_LOGIC;                            --Internal register for ss_n 
  SIGNAL INT_sclk    : STD_LOGIC;                            --Internal register for sclk 


BEGIN
	
  -- wire internal registers to outside	
  ss_n <= INT_ss_n;
  sclk <= INT_sclk;
  
  PROCESS(clk, reset_n)
  BEGIN

    IF(reset_n = '0') THEN        --reset everything
      busy <= '1';                
      INT_ss_n <= '1';            
      mosi <= 'Z';                
      rx <= (OTHERS => '0');      
      state <= init;              

    ELSIF(falling_edge(clk)) THEN
      CASE state IS               

        WHEN init =>					 -- bus is idle
          busy <= '0';             
          INT_ss_n <= '1'; 		  
          mosi <= 'Z';             
   
          IF(enable = '1') THEN       		--initiate communication
            busy <= '1';                  --busy feedback
            INT_sclk <= cpol;        		  --set spi clock polarity
            receive_transmit <= NOT cpha; --set spi clock phase
            txBuffer <= tx;    				    --put data to buffer to transmit
            clk_toggles <= 0;        		  --initiate clock toggle counter
            last_bit <= data_length*2 + conv_integer(cpha) - 1; --set last rx data bit
            state <= execute;        
          ELSE
            state <= init;          
          END IF;


        WHEN execute =>
          busy <= '1';               
          INT_ss_n <= '0';           						      --pull the slave select signal down
			    receive_transmit <= NOT receive_transmit;   --change receive transmit mode
          
          -- counter
          IF(clk_toggles = data_length*2 + 1) THEN
            clk_toggles <= 0;               				--reset counter
          ELSE
            clk_toggles <= clk_toggles + 1; 				--increment counter
          END IF;
            
          -- toggle sclk
          IF(clk_toggles <= data_length*2 AND INT_ss_n = '0') THEN 
            INT_sclk <= NOT INT_sclk; --toggle spi clock
          END IF;
            
          --receive miso bit
          IF(receive_transmit = '0' AND clk_toggles < last_bit + 1 AND INT_ss_n = '0') THEN 
            rxBuffer <= rxBuffer(data_length-2 DOWNTO 0) & miso; 
          END IF;
            
          --transmit mosi bit
          IF(receive_transmit = '1' AND clk_toggles < last_bit) THEN 
            mosi <= txBuffer(data_length-1);                    
            txBuffer <= txBuffer(data_length-2 DOWNTO 0) & '0'; 
          END IF;
            
          -- Finish/ resume the communication
          IF(clk_toggles = data_length*2 + 1) THEN   
            busy <= '0';             
            INT_ss_n <= '1';         
            mosi <= 'Z';             
            rx <= rxBuffer;    
            state <= init;          
          ELSE                       
            state <= execute;        
          END IF;
      END CASE;
    END IF;
  END PROCESS; 
END behavioural;

从机程序:

LIBRARY ieee;
USE ieee.std_logic_1164.all;
USE ieee.std_logic_arith.all;
--this is comment
ENTITY spi_slave IS
  GENERIC(
    data_length : INTEGER := 16);     --data length in bits
  PORT(
    reset_n      : IN     STD_LOGIC;  																	 --asynchronous active low reset
	  cpol    	   : IN 	  STD_LOGIC;  																	 --clock polarity mode
    cpha    	   : IN 	  STD_LOGIC;  																	 --clock phase mode
    sclk         : IN     STD_LOGIC;  																	 --spi clk
	  ss_n         : IN     STD_LOGIC;  																	 --slave select
    mosi         : IN     STD_LOGIC;  																	 --master out slave in
    miso         : OUT    STD_LOGIC;  																	 --master in slave out
	  rx_enable    : IN     STD_LOGIC;  																	 --enable signal to wire rxBuffer to outside 
    tx			     : IN     STD_LOGIC_VECTOR(data_length-1 DOWNTO 0);  						 --data to transmit
    rx		       : OUT    STD_LOGIC_VECTOR(data_length-1 DOWNTO 0) := (OTHERS => '0');  --data received
    busy         : OUT    STD_LOGIC := '0');  														 --slave busy signal	 
END spi_slave;

ARCHITECTURE behavioural OF spi_slave IS
  SIGNAL mode    : STD_LOGIC;  																	  --according to CPOL and CPHA
  SIGNAL clk     : STD_LOGIC;  
  SIGNAL bit_counter : STD_LOGIC_VECTOR(data_length DOWNTO 0); 						  --active bit indicator
  SIGNAL rxBuffer  : STD_LOGIC_VECTOR(data_length-1 DOWNTO 0) := (OTHERS => '0');  --receiver buffer
  SIGNAL txBuffer  : STD_LOGIC_VECTOR(data_length-1 DOWNTO 0) := (OTHERS => '0');  --transmit buffer
BEGIN
  busy <= NOT ss_n;  
  
  mode <= cpol XOR cpha;  

  PROCESS (mode, ss_n, sclk)
  BEGIN
  IF(ss_n = '1') then
     clk <= '0';
  ELSE
     IF (mode = '1') then
	     clk <= sclk;
	  ELSE
	     clk <= NOT sclk;
	  END IF;
  END IF;
  END PROCESS;

  --where is the active bit
  PROCESS(ss_n, clk)
  BEGIN
    IF(ss_n = '1' OR reset_n = '0') THEN                         
	   bit_counter <= (conv_integer(NOT cpha) => '1', OTHERS => '0'); --reset active bit indicator
    ELSE                                                         
      IF(rising_edge(clk)) THEN                                  
        bit_counter <= bit_counter(data_length-1 DOWNTO 0) & '0';    --left shift active bit indicator
      END IF;
    END IF;
  END PROCESS;


  PROCESS(ss_n, clk, rx_enable, reset_n)
  BEGIN      
  
	 --receive mosi bit
    IF(cpha = '0') then
		 IF(reset_n = '0') THEN			--reset the buffer
			rxBuffer <= (OTHERS => '0');
		 ELSIF(bit_counter /= "00000000000000010" and falling_edge(clk)) THEN
			rxBuffer(data_length-1 DOWNTO 0) <= rxBuffer(data_length-2 DOWNTO 0) & mosi;  --shift in the received bit
		 END IF;
	 ELSE
		 IF(reset_n = '0') THEN       --reset the buffer
			rxBuffer <= (OTHERS => '0');
		 ELSIF(bit_counter /= "00000000000000001" and falling_edge(clk)) THEN
			rxBuffer(data_length-1 DOWNTO 0) <= rxBuffer(data_length-2 DOWNTO 0) & mosi;  --shift in the received bit
		 END IF;
	 END IF;

    --if user wants the received data output it
    IF(reset_n = '0') THEN
      rx <= (OTHERS => '0');
    ELSIF(ss_n = '1' AND rx_enable = '1') THEN  
      rx <= rxBuffer;
    END IF;

    --transmit registers
    IF(reset_n = '0') THEN
      txBuffer <= (OTHERS => '0');
    ELSIF(ss_n = '1') THEN  
      txBuffer <= tx;
    ELSIF(bit_counter(data_length) = '0' AND rising_edge(clk)) THEN
      txBuffer(data_length-1 DOWNTO 0) <= txBuffer(data_length-2 DOWNTO 0) & txBuffer(data_length-1);  --shift through tx data
    END IF;

    --transmit miso bit
    IF(ss_n = '1' OR reset_n = '0') THEN           
      miso <= 'Z';
    ELSIF(rising_edge(clk)) THEN
      miso <= txBuffer(data_length-1);               
    END IF;
    
  END PROCESS;
END behavioural;

激励文件:

LIBRARY ieee;
USE ieee.STD_LOGIC_1164.ALL; 
USE ieee.numeric_std.ALL;
 
ENTITY SPI_TB IS
END SPI_TB;
 
ARCHITECTURE behavior OF SPI_TB IS 
 
    -- Component Declaration for the Unit Under Test (UUT)
 
    COMPONENT spi_master
    PORT(
         clk 		: IN  STD_LOGIC;
         reset_n 	: IN  STD_LOGIC;
         enable 	: IN  STD_LOGIC;
		 cpol    	: IN  STD_LOGIC;
		 cpha    	: IN  STD_LOGIC;
		 miso		: IN  STD_LOGIC;
         sclk 		: OUT STD_LOGIC;
         ss_n 		: OUT STD_LOGIC;
         mosi 		: OUT STD_LOGIC;
         busy 		: OUT STD_LOGIC;
         tx 		: IN  STD_LOGIC_VECTOR(15 downto 0);
         rx 		: OUT STD_LOGIC_VECTOR(15 downto 0)
        );
    END COMPONENT;
	 
	COMPONENT spi_slave
	  PORT(
		 reset_n      : IN     STD_LOGIC; 
    	 cpol    	  : IN 	  STD_LOGIC;  																	 
       	 cpha    	  : IN 	  STD_LOGIC;  																	 
		 sclk         : IN     STD_LOGIC; 
		 ss_n         : IN     STD_LOGIC; 
		 mosi         : IN     STD_LOGIC; 
		 miso         : OUT    STD_LOGIC;
		 rx_enable    : IN     STD_LOGIC; 
		 tx 		  : IN     STD_LOGIC_VECTOR(15 DOWNTO 0);  
		 rx           : OUT    STD_LOGIC_VECTOR(15 DOWNTO 0);  
		 busy         : OUT    STD_LOGIC  
		 ); 
	END COMPONENT;		


	SIGNAL clk, miso, mosi, sclk, ss_n, busy_slave, busy_master, reset_n :STD_LOGIC;
	SIGNAL start :STD_LOGIC := '0';
	SIGNAL enable, rx_enable:STD_LOGIC := '1';
	SIGNAL rx_master, rx_slave: STD_LOGIC_VECTOR(15 downto 0);

	-- Data to be transfered by master and by slave:
	SIGNAL tx_master: STD_LOGIC_VECTOR(15 downto 0) := "1100010001110011";
	SIGNAL tx_slave:  STD_LOGIC_VECTOR(15 downto 0) := "1100101010010001";
	
	-- Configure CPHA and CPOL for master and slave
	SIGNAL cpha :STD_LOGIC := '0';
	SIGNAL cpol :STD_LOGIC := '0';

	-- clk generate
	constant SYSCLK_PERIOD : time := 10 ns;   --100MHz
	constant SYSCLK_PERIOD_40M : time := 25 ns;   --40MHz
    --clk1
    signal SYSCLK :           std_logic := '0';
    signal NSYSRESET :        std_logic := '0';
	signal SYSCLK_100M :      std_logic := '0';
    --clk2
    signal ComClk1M :         std_logic := '0';  --1Mhz  
	signal ComClk20M :        std_logic := '0';  --20Mhz 

	--time stamp
	constant 	detect_delay_500u:                           integer := 499;               --500us
	constant 	detect_delay_1m:                           	 integer := 999;               --1ms
	constant 	detect_delay_2m:                           	 integer := 1999;              --2ms
	constant 	detect_delay_3m:                             integer := 2999;              --3ms
	constant 	detect_delay_4m:                             integer := 3999;              --4ms
	constant 	detect_delay_5m:                             integer := 4999;              --5ms
	constant 	detect_delay_6m:                           	 integer := 5999;              --6ms
	constant 	detect_delay_7m:                             integer := 6999;              --7ms
	constant 	detect_delay_8m:                             integer := 7999;              --8ms
	constant 	detect_delay_9m:                             integer := 8999;              --9ms
	constant 	detect_delay_10m:                            integer := 9999;              --10ms
	constant 	detect_delay_100m:                           integer := 99999;             --100ms
		
	signal 		com_cnt:   		                  		   	 integer range 0 to detect_delay_100m;  --max delay
	signal 		test_en:        							 std_logic := '0';			
	
BEGIN


	-- generate reset signal and clocl
	process
	variable vhdl_initial : BOOLEAN := TRUE;
	begin
		if ( vhdl_initial ) then
			-- Assert Reset
			NSYSRESET <= '0';
			wait for ( SYSCLK_PERIOD * 100 );    --wait for 1us
			NSYSRESET <= '1';
			wait;
		end if;
	end process;

	ComClk1M  <= not ComClk1M  after (SYSCLK_PERIOD * 50.0 );   --1MHz
	ComClk20M <= not ComClk20M after (SYSCLK_PERIOD_40M);  		--20MHz

	reset_n <= NSYSRESET;
	clk <= ComClk1M;

	-- spi parameters configration
	cpol <= '1';
	cpha <= '1';

	-- state simulation
	test_en <= '1';
	process (reset_n,ComClk1M)
    begin
        if reset_n = '0' then
            com_cnt <= 0;
        elsif rising_edge(ComClk1M) then
            if test_en = '1' then
				if	com_cnt = detect_delay_500u then
					enable <= '1';
				end if;
			else
				com_cnt <= 0;
            end if;
        end if;	
    end process;

	-- Instantiate the Unit Under Test (UUT)
   uut1: spi_master PORT MAP (
          clk => clk,
          reset_n => reset_n,
          enable => enable,
		  cpol => cpol,
		  cpha => cpha,
          miso => miso,
          sclk => sclk,
          ss_n => ss_n,
          mosi => mosi,
          busy => busy_master,
          tx => tx_master,
          rx => rx_master
        );
		  
	uut2: spi_slave PORT MAP (
			 reset_n => reset_n,       
       	 	 cpol => cpol,  	    																	 
          	 cpha => cpha,  	    																	 
			 sclk => sclk, 
			 ss_n => ss_n, 
			 mosi => mosi, 
			 miso => miso,
			 rx_enable => rx_enable, 
			 tx => tx_slave,   
			 rx	=> rx_slave, 
			 busy	=> busy_slave  			  
	);	  

END;

modelsim仿真do文件:

http://gofile.me/5uzg9/1RmiWnWw9

  • modelsim仿真验证

主要对4种工作模式进行仿真,其他的仿真大家可以参照do文件,查看想要的变量。

1- Mode0:CPOL=0,CPHA=0的仿真结果:

ss_n信号:从机的片选使能信号;

sclk信号:主机输出的时钟信号,频率位clk频率的1/2;

uut1/txBuffer和uut1/rxBuffer信号:分别位主机的发送数据和接收数据;

uut2/txBuffer和uut2/rxBuffer信号:分别位从机的发送数据和接收数据;

测试结果:使能信号ss_n拉低之后,第一次提前准备发送数据,等到上升沿时,采集接收数据。后续下降沿时准备发送数据,上升延时采集数据。

2- Mode1:CPOL=0,CPHA=1的仿真结果:

测试结果:使能信号ss_n拉低之后,上升延时准备发送数据,等到下降沿时,采集接收数据。

3- Mode3:CPOL=1,CPHA=0的仿真结果:

测试结果:使能信号ss_n拉低之后,第一次是下降沿到来前准备发送数据,等到下降沿时,采集接收数据。后续上升延时,装备发送数据,下降沿采集数据。

4- Mode4:CPOL=1,CPHA=1的仿真结果:

测试结果:使能信号ss_n拉低之后,下降沿时准备发送数据,等到上升沿时,采集接收数据。

Logo

瓜分20万奖金 获得内推名额 丰厚实物奖励 易参与易上手

更多推荐