SPI 简介
SPI全称为Seriel Peripheral Interface (串行外设接口),是 MCU 中常用的外设接口。SPI 通信原理很简单,它是以主从方式进行工作,通常有一个主设备和一个或多个从设备,至少需要4根线(支持全双工)工作,分别为 MISO(主入从出),MOSI(主出从入),SCLK(时钟),SS(片选)。
Standard-SPI
基本的 SPI 协议也被称为 Standard-SPI,Standard-SPI 是串行通信协议,数据是逐位进行传输,在 SCLK 的边沿进行 MOSI 和 MISO 的传输。数据输出通过 MOSI 线进行传输,数据在时钟上升沿或者下降沿改变,在紧接着的下降沿或者上升沿被采样完成一位数据传输,输入同理。
Dual-SPI
由于在实际应用中较少使用全双工模式,因此为了能够充分利用数据线,引入了 Dual-SPI 和 Quad-SPI ,在 Dual-SPI 协议中,MOSI、MISO 数据线被重命名为 SD0、SD1 ,变为既可以输入也可以输出的 inout 信号。
Dual-SPI 由于同时使用两根数据线进行传输,一个时钟周期可以传输2 bit数据,因此可以将 Standard-SPI的吞吐率提高一倍。
Quad-SPI
Quad-SPI 是在 Dual-SPI 的基础上再添加了两根数据线,所以数据线变为了SD0、SD1、SD2、SD3。
Quad-SPI 由于同时使用四根数据线进行传输,一个时钟周期可以传输4 bit数据,因此可以在 Dual-SPI 基础上将吞吐率提高一倍。
SPI 总线四种工作方式
SPI 在数据传输的时候,需要确定两件事情
- 数据是在时钟的上升沿采集还是下降沿采集
- 时钟的初始(空闲)状态是为高电平还是低电平
CPOL:时钟极性, 表示 SPI 在空闲时, 时钟信号是高电平还是低电平。
CPHA:时钟相位, 表示 SPI 设备是在 SCK 管脚上的时钟信号变为上升沿时触发数据采样, 还是在时钟信号变为下降沿时触发数据采样。
那么CPOL 有两种可能,CPHA 有两种可能,则 SPI 传输就有四种模式。
传输方式 | 描述 |
---|---|
方式1 | CPOL= 0,CPHA=0。SCK串行时钟线空闲是为低电平,数据在SCK时钟的上升沿被采样,数据在SCK时钟的下降沿切换 |
方式2 | CPOL= 0,CPHA=1。SCK串行时钟线空闲是为低电平,数据在SCK时钟的下降沿被采样,数据在SCK时钟的上升沿切换 |
方式3 | CPOL= 1,CPHA=0。SCK串行时钟线空闲是为高电平,数据在SCK时钟的下降沿被采样,数据在SCK时钟的上升沿切换 |
方式4 | CPOL= 1,CPHA=1。SCK串行时钟线空闲是为高电平,数据在SCK时钟的上升沿被采样,数据在SCK时钟的下降沿切换 |
SPI通信协议
通讯的起始信号:SS 信号线由高变低,是 SPI 通讯的起始信号。SS 是每个从机各自独占的信号线,当从机在自己的 SS 线检测到起始信号后,就知道自己被主机选中了,开始准备与主机通讯。
通讯的停止信号:SS 信号由低变高,是 SPI 通讯的停止信号,表示本次通讯结束,从机的选中状态被取消。
数据有效性:SPI 使用 MOSI 及 MISO 信号线来传输数据,使用 SCLK 信号线进行数据同步。MOSI 及 MISO 数据线在 SCLK 的每个时钟周期传输一位数据,且数据输入输出是同时进行的。数据传输时,MSB 先行或 LSB 先行并没有作硬性规定,但要保证两个 SPI 通讯设备之间使用同样的协定,一般采用 MSB 先行的方式。
代码语言:javascript复制 _ _ _ _ _ _ _ _ _
inClk _/ _/ _/ _/ _/ _/ ....._/ _/ _/ _
___ _____
inReset_EnableB _________________________________/
_____ ___
outSpiCsB _________________________________/
_______ ___
inData8Send[7:0] _______XD8SXXX...
___ ___ ___ ___ _________
inSpiMosi XD7_XD6_XD5_X.....XD0_X_________
___________ _ _ _ _ ___________
outSpiClk _/ _/ _/ ....._/
___________
outData8Receive[7:0] XD8SX XXXD8R*D8SX___
image
FPGA 程序实现
一个用了好多年的 SPI 程序,很健壮,小编抛出来。
代码语言:javascript复制library ieee;
--Library UNISIM;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;
use ieee.numeric_std.all;
--use UNISIM.vcomponents.all;
entity SpiSerDes is
port
(
-- SerDes clock and control signals
inClk : in std_logic; -- System clock. Fmax <= SPI peripheral Fmax.
inReset_EnableB : in std_logic; -- Active-high, synchronous reset.
inStartTransfer : in std_logic; -- Active-high, initiate transfer of data
outTransferDone : out std_logic; -- DONE='1' when transfer is done
-- Parallel data ports
inData8Send : in std_logic_vector(7 downto 0); -- Sent to SPI device
outData8Receive : out std_logic_vector(7 downto 0); -- Received from SPI device
-- SPI ports and tristate control - Connect these to the SPI bus
outSpiCsB : out std_logic; -- SPI chip-select to SPI device
-- or all SPI outputs control enable
outSpiClk : out std_logic; -- SPI clock to SPI device
outSpiMosi : out std_logic; -- SPI master-out, slave-in to SPI device
inSpiMiso : in std_logic -- SPI master-in, slave-out from SPI device
);
end SpiSerDes;
architecture behavioral of SpiSerDes is
-- Constants
constant cShiftCountInit : std_logic_vector(8 downto 0) := B"000000001";
-- Registers
signal regShiftCount : std_logic_vector(8 downto 0) := cShiftCountInit;
signal regShiftData : std_logic_vector(7 downto 0) := B"00000000";
signal regSpiCsB : std_logic := '1';
signal regSpiMosi : std_logic := '1';
signal regTransferDoneDelayed : std_logic := '1';
-- Signals
signal intTransferDone : std_logic;
-- Attributes
attribute clock_signal : string;
attribute clock_signal of inClk : signal is "yes";
begin
-- Internal signals
intTransferDone <= regShiftCount(0);
-- TransferDone delayed by half clock cycle
processTransferDone : process (inClk)
begin
if (falling_edge(inClk)) then
regTransferDoneDelayed <= intTransferDone;
end if;
end process processTransferDone;
-- SPI chip-select (active-Low) is always inverse of inReset_EnableB.
processSpiCsB : process (inClk)
begin
if (rising_edge(inClk)) then
regSpiCsB <= inReset_EnableB;
end if;
end process processSpiCsB;
-- Track transfer of serial data with barrel shifter.
processShiftCount : process (inClk)
begin
if (rising_edge(inClk)) then
if (inReset_EnableB='1') then
regShiftCount <= cShiftCountInit;
elsif ((intTransferDone='0') or (inStartTransfer='1')) then
-- Barrel shift (rotate right)
regShiftCount <= regShiftCount(0) & regShiftCount(8 downto 1);
end if;
end if;
end process processShiftCount;
-- Simultaneous serialize outgoing data & deserialize incoming data. MSB first
processShiftData : process (inClk)
begin
if (rising_edge(inClk)) then
if (intTransferDone='0') then
-- SHIFT-left while not outTransferDone
regShiftData <= regShiftData(6 downto 0) & inSpiMiso;
elsif (inStartTransfer='1') then
-- Load data to start a new transfer sequence from a done state
regShiftData <= inData8Send;
end if;
end if;
end process processShiftData;
-- SPI MOSI register outputs on falling edge of inClk. MSB first.
processSpiMosi : process (inClk)
begin
if (falling_edge(inClk)) then
if (inReset_EnableB='1') then
regSpiMosi <= '1';
elsif (intTransferDone='0') then
regSpiMosi <= regShiftData(7);
end if;
end if;
end process processSpiMosi;
-- Assign outputs
outSpiClk <= (inClk or intTransferDone or regTransferDoneDelayed);
outSpiCsB <= regSpiCsB;
outSpiMosi <= regSpiMosi;
outTransferDone <= intTransferDone;
outData8Receive <= regShiftData;
end behavioral;
假设上升沿发送、下降沿接收、高位先发送。
假设主机8位寄存器装的是待发送的数据10101010
参考链接
https://blog.csdn.net/weiqifa0/article/details/82765892
https://mp.weixin.qq.com/s/h1xco58oRDbIq8z3zaP_pA