目录
一、如何代码获取
推荐大家直接去开源网站下载程序代码,直接搜索想要的代码,然后根据排名先后下载即可,程序一般都比较规范,标注也详细,学习起来不容易走弯路。
以下时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拉低之后,下降沿时准备发送数据,等到上升沿时,采集接收数据。