Hier ist eine einfache SPI-Master-Implementierung in VHDL z.B. für FPGAs. Die Übertragung findet im Mode 0 statt (CPOL=0, CPHA=0). Die Protokolllänge und die Baudrate sind generisch einstellbar. An den Port TX_Data werden die 8 zu übertragenden Bits angelegt. Danach wird mit TX_Start die Übertragung gestartet. Erst wird SS aktiviert und dann die 8 Datenbits über das Schieberegister tx_reg an MOSI ausgegeben und gleichzietig der Datenstrom von MISO in das Schieberegister rx_reg eingelesen.
Nach der Übertragung wird SS deaktivert, und danach TX_Done solange aktiviert, bis TX_Start inaktiv wird. Die empfangenen Daten sind jetzt am Port RX_Data abholbereit.
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;
entity SPI_Master is -- SPI-Modus 0: CPOL=0, CPHA=0
Generic ( Quarz_Taktfrequenz : integer := 50000000; -- Hertz
SPI_Taktfrequenz : integer := 1000000; -- Hertz / zur Berechnung des Reload-Werts für Taktteiler
Laenge : integer := 8 -- Anzahl der zu übertragenden Bits
);
Port ( TX_Data : in STD_LOGIC_VECTOR (Laenge-1 downto 0); -- Sendedaten
RX_Data : out STD_LOGIC_VECTOR (Laenge-1 downto 0); -- Empfangsdaten
MOSI : out STD_LOGIC;
MISO : in STD_LOGIC;
SCLK : out STD_LOGIC;
SS : out STD_LOGIC;
TX_Start : in STD_LOGIC;
TX_Done : out STD_LOGIC;
clk : in STD_LOGIC
);
end SPI_Master;
architecture Behavioral of SPI_Master is
signal delay : integer range 0 to (Quarz_Taktfrequenz/(2*SPI_Taktfrequenz));
constant clock_delay : integer := (Quarz_Taktfrequenz/(2*SPI_Taktfrequenz))-1;
type spitx_states is (spi_stx,spi_txactive,spi_etx);
signal spitxstate : spitx_states := spi_stx;
signal spiclk : std_logic;
signal spiclklast: std_logic;
signal bitcounter : integer range 0 to Laenge; -- wenn bitcounter = Laenge --> alle Bits uebertragen
signal tx_reg : std_logic_vector(Laenge-1 downto 0) := (others=>'0');
signal rx_reg : std_logic_vector(Laenge-1 downto 0) := (others=>'0');
begin
------ Verwaltung --------
process begin
wait until rising_edge(CLK);
if(delay>0) then delay <= delay-1;
else delay <= clock_delay;
end if;
spiclklast <= spiclk;
case spitxstate is
when spi_stx =>
SS <= '1'; -- slave select disabled
TX_Done <= '0';
bitcounter <= Laenge;
spiclk <= '0'; -- SPI-Modus 0
if(TX_Start = '1') then
spitxstate <= spi_txactive;
SS <= '0';
delay <= clock_delay;
end if;
when spi_txactive => -- Daten aus tx_reg uebertragen
if (delay=0) then -- shift
spiclk <= not spiclk;
if (bitcounter=0) then -- alle Bits uebertragen -> deselektieren
spiclk <= '0'; -- SPI-Modus 0
spitxstate <= spi_etx;
end if;
if(spiclk='1') then -- SPI-Modus 0
bitcounter <= bitcounter-1;
end if;
end if;
when spi_etx =>
SS <= '1'; -- disable Slave Select
TX_Done <= '1';
if(TX_Start = '0') then -- Handshake: warten, bis Start-Flag geloescht
spitxstate <= spi_stx;
end if;
end case;
end process;
---- Empfangsschieberegister -----
process begin
wait until rising_edge(CLK);
if (spiclk='1' and spiclklast='0') then -- SPI-Modus 0
rx_reg <= rx_reg(rx_reg'left-1 downto 0) & MISO;
end if;
end process;
---- Sendeschieberegister -------
process begin
wait until rising_edge(CLK);
if (spitxstate=spi_stx) then -- Zurücksetzen, wenn SS inaktiv
tx_reg <= TX_Data;
end if;
if (spiclk='0' and spiclklast='1') then -- SPI-Modus 0
tx_reg <= tx_reg(tx_reg'left-1 downto 0) & tx_reg(0);
end if;
end process;
SCLK <= spiclk;
MOSI <= tx_reg(tx_reg'left);
RX_Data <= rx_reg;
end Behavioral;
Die Testbench simuliert einen SPI-Slave. Sie empfängt die Daten im Schieberegister rxsrslave,
und gibt sie bei inaktiven SS an das txsrslave.
Von dort werden die Daten bei der nächsten Übertragung wieder an den Master zurückgeschickt.
LIBRARY ieee;
USE ieee.std_logic_1164.ALL;
USE ieee.numeric_std.ALL;
ENTITY tb_SPI_Master_vhd IS
END tb_SPI_Master_vhd;
ARCHITECTURE behavior OF tb_SPI_Master_vhd IS
-- Component Declaration for the Unit Under Test (UUT)
COMPONENT SPI_Master
Port ( TX_Data : in STD_LOGIC_VECTOR (7 downto 0); -- Sendedaten
RX_Data : out STD_LOGIC_VECTOR (7 downto 0); -- Empfangsdaten
MOSI : out STD_LOGIC;
MISO : in STD_LOGIC;
SCLK : out STD_LOGIC;
SS : out STD_LOGIC;
TX_Start : in STD_LOGIC;
TX_Done : out STD_LOGIC;
clk : in STD_LOGIC
);
END COMPONENT;
--Inputs
SIGNAL MISO : std_logic := '0';
SIGNAL TX_Start : std_logic := '0';
SIGNAL clk : std_logic := '0';
SIGNAL TX_Data : std_logic_vector(7 downto 0) := (others=>'0');
--Outputs
SIGNAL RX_Data : std_logic_vector(7 downto 0);
SIGNAL MOSI : std_logic;
SIGNAL SCLK : std_logic;
SIGNAL SS : std_logic;
SIGNAL TX_Done : std_logic;
SIGNAL rxsrslave : std_logic_vector(7 downto 0) := x"00";
SIGNAL txsrslave : std_logic_vector(7 downto 0) := x"00";
BEGIN
-- Instantiate the Unit Under Test (UUT)
uut: SPI_Master PORT MAP(
TX_Data => TX_Data,
RX_Data => RX_Data,
MOSI => MOSI,
MISO => MISO,
SCLK => SCLK,
SS => SS,
TX_Start => TX_Start,
TX_Done => TX_Done,
clk => clk
);
clk <= not clk after 10 ns; -- 50 MHz
-- Senden
process (sclk, ss) begin
if (falling_edge(SCLK)) then -- SPI Mode 0
txsrslave <= txsrslave(6 downto 0) & '0';
end if;
if (ss='1') then
txsrslave <= rxsrslave; -- x"80000001" after 3 ns;
end if;
end process;
MISO <= txsrslave(7) after 3 ns;
-- Empfangen
process (sclk, mosi) begin
if (rising_edge(SCLK)) then -- SPI Mode 0
rxsrslave <= rxsrslave(6 downto 0) & MOSI;
end if;
end process;
tb : PROCESS BEGIN
TX_Data <= x"7E";
wait for 3 us;
TX_Start <= '1';
wait until TX_Done='1';
TX_Start <= '0';
wait for 100 ns;
TX_Data <= x"5a";
wait for 3 us;
TX_Start <= '1';
wait until TX_Done='1';
TX_Start <= '0';
wait for 100 ns;
TX_Data <= x"C3";
wait for 3 us;
TX_Start <= '1';
wait until TX_Done='1';
TX_Start <= '0';
wait;
END PROCESS;
END;
Daraus resultiert diese Waveform
Wer eine wesentlich umfangreichere universelle SPI-Master-Implementierung sucht, findet hier eine, die weitestgehend parametrierbar ist.
Und auf der Seite von Michael Fischer ist eine Adaptierung für ein Altera DE1 Board mit NIOS zu finden: www.emb4fun.de
In Michaels Version ist auch die Paketlänge und die Datenrate per Register einstellbar.