Задача
Обменяться байтом с ведомым устройством по шине SPI.
Тип схемы: Конечный автомат с делителем тактовой частоты и сдвиговыми регистрами.
Модуль: spi_master. Среда: Quartus, Vivado, ISE или свободный GHDL для моделирования.
Теорию по языку смотрите в статье VHDL — язык описания аппаратуры,
по самим микросхемам — на странице ПЛИС.
Алгоритм
- Опустить сигнал выбора ведомого и начать генерацию тактов.
- По одному фронту принимать бит с линии MISO.
- По противоположному фронту выдвигать очередной бит на MOSI.
- После восьми битов поднять выбор и выдать признак завершения.
Код на VHDL
Код проверен компилятором GHDL: проходит анализ и элаборацию без ошибок и предупреждений. Скачать: v18.vhd
--=====================================================
-- Проект 18. Ведущий SPI
-- Выдаёт тактовый сигнал, сдвигает байт наружу
-- и одновременно принимает байт от ведомого.
--=====================================================
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity spi_master is
generic (
CLK_DIV : integer := 4 -- делитель тактовой частоты
);
port (
clk : in std_logic;
rst : in std_logic;
start : in std_logic;
data_in : in std_logic_vector(7 downto 0); -- байт на передачу
data_out : out std_logic_vector(7 downto 0); -- принятый байт
done : out std_logic;
sclk : out std_logic;
mosi : out std_logic;
miso : in std_logic;
cs_n : out std_logic -- выбор ведомого
);
end entity spi_master;
architecture rtl of spi_master is
type state_t is (IDLE, TRANSFER);
signal state : state_t := IDLE;
signal divider : integer range 0 to CLK_DIV := 0;
signal bit_cnt : integer range 0 to 8 := 0;
signal sclk_reg : std_logic := '0';
signal tx_reg : std_logic_vector(7 downto 0) := (others => '0');
signal rx_reg : std_logic_vector(7 downto 0) := (others => '0');
begin
process (clk, rst)
begin
if rst = '1' then
state <= IDLE;
sclk_reg <= '0';
done <= '0';
elsif rising_edge(clk) then
done <= '0';
case state is
when IDLE =>
sclk_reg <= '0';
divider <= 0;
bit_cnt <= 0;
if start = '1' then
tx_reg <= data_in;
state <= TRANSFER;
end if;
when TRANSFER =>
if divider = CLK_DIV - 1 then
divider <= 0;
sclk_reg <= not sclk_reg;
if sclk_reg = '0' then
-- нарастающий фронт: принимаем бит
rx_reg <= rx_reg(6 downto 0) & miso;
else
-- спад: выдвигаем следующий бит
tx_reg <= tx_reg(6 downto 0) & '0';
if bit_cnt = 7 then
state <= IDLE;
done <= '1';
else
bit_cnt <= bit_cnt + 1;
end if;
end if;
else
divider <= divider + 1;
end if;
end case;
end if;
end process;
sclk <= sclk_reg;
mosi <= tx_reg(7); -- старшим битом вперёд
cs_n <= '1' when state = IDLE else '0';
data_out <= rx_reg;
end architecture rtl;
Пояснения
- SPI полнодуплексный: за те же восемь тактов байт уходит и байт приходит — приёмный и передающий регистры работают одновременно.
- Приём и передача разнесены по разным фронтам такта: так данные успевают устояться до момента защёлкивания.
- Сочетание полярности и фазы такта образует четыре режима SPI; здесь реализован самый распространённый.
Что дальше
Следующие проекты — в разделе проекты на VHDL. Сравнить подход с языком Verilog можно в статье Verilog, а разобраться в устройстве самих кристаллов — в статье ПЛИС: FPGA и CPLD.