一、SPI通讯协议:
先聊点别人说过的,老生常谈的内容,(关键点和代码再后面):
1. 主从架构与信号线定义
SPI(Serial Peripheral Interface)是一种全双工同步串行通信协议,采用主从模式,包含以下四根信号线
:
- SCLK(Serial Clock):主设备生成的时钟信号,控制数据传输速率和时序。
- MOSI(Master Out Slave In):主设备输出数据线,从设备输入。
- MISO(Master In Slave Out):主设备输入数据线,从设备输出。
- CS/SS(Chip Select/Slave Select):片选信号,主设备通过拉低该信号选中特定从设备。
2. 同步通信机制
- 时钟同步:所有数据传输由SCLK驱动,主设备通过时钟边沿(上升沿或下降沿)控制数据的采样与发送时机。
- 全双工特性:主从设备可同时通过MOSI和MISO线发送和接收数据,每个时钟周期完成1位数据的双向传输。
3. 四种工作模式
SPI通过**时钟极性(CPOL)和时钟相位(CPHA)**的组合定义四种工作模式
:
- 模式0(CPOL=0, CPHA=0):空闲时SCLK为低电平,数据在上升沿采样,下降沿发送。
- 模式1(CPOL=0, CPHA=1):空闲时SCLK为低电平,数据在下降沿采样,上升沿发送。
- 模式2(CPOL=1, CPHA=0):空闲时SCLK为高电平,数据在下降沿采样,上升沿发送。
- 模式3(CPOL=1, CPHA=1):空闲时SCLK为高电平,数据在上升沿采样,下降沿发送。
二、SPI通信过程详解
1. 通信流程步骤
- 片选激活:主设备拉低目标从设备的CS信号线,启动通信。
- 时钟生成:主设备产生SCLK信号,根据CPOL和CPHA设定时钟边沿行为。
- 数据传输:
- 主→从:主设备通过MOSI线逐位发送数据,从设备在指定时钟边沿采样。
- 从→主:从设备通过MISO线返回数据,主设备同步接收。
- 移位寄存器机制:数据通过主从设备的移位寄存器实现同步交换。
- 片选释放:传输完成后,主设备拉高CS信号,结束本次通信。
- 硬件设计:每个从设备需独立的CS信号线,主设备通过译码器(如4-16译码器)扩展片选能力。
- 时序冲突避免:同一时间仅一个从设备被选中,防止数据线冲突。 三、注意事项
- 1.最重要的是第一个CLK,时钟信号的产生一定是慢于数据信号的,但是不能太慢,保证第一个变化沿的时候能正常采数。
- 2.如果涉及到与外设通讯,或者MCU通讯功能建议在FPGA端使用异步时钟,保证时常。
四、程序代码
LIBRARY IEEE;
USE IEEE.STD_LOGIC_1164.ALL;
USE IEEE.STD_LOGIC_ARITH.ALL;
USE IEEE.STD_LOGIC_UNSIGNED.ALL;
--USE IEEE.STD_LOGIC_signed.ALL;
use WORK.ChangeWorldCtrolBoard_PACKAGE.ALL;
-------------------------------------------------------------------------------------------
ENTITY SPI1 IS
GENERIC(CPHA: INTEGER := 1;CPOL: INTEGER := 1);--模式3空闲高电平,前沿输出后沿采样
PORT(
--系统时钟与复位
sysclk : IN STD_LOGIC;--50MHZ
sysreset : IN STD_LOGIC;
--SPI物理接口
CS1 : OUT STD_LOGIC;--片选信号
sclk : OUT STD_LOGIC;--串行时钟
miso : IN STD_LOGIC;--主机输入从机输出
mosi : OUT STD_LOGIC;--主机输出从机输入
--SPI其他接口
SenddataTrig : IN STD_LOGIC;
Senddata : IN std_logic_vector(7 downto 0);
Send_clear : IN STD_LOGIC;
CS : IN STD_LOGIC;
Getdata : OUT std_logic_vector(7 downto 0);
GetdataTrig : OUT STD_LOGIC
);
END ENTITY SPI1;
ARCHITECTURE SPI1 OF SPI1 IS
CONSTANT SCLK_NUM: INTEGER := 12;--对系统时钟进行16分频变为2500KHZ(40M)
CONSTANT SCLK_BEFORE: INTEGER := 2;
CONSTANT SCLK_AFTER: INTEGER := 6;
TYPE state IS(IDLE,WAIT_L,DATA,DONE);
SIGNAL state_c,state_n: state;
SIGNAL bit_cnt,get_cnt: INTEGER RANGE 0 TO 7;--bit计数器
SIGNAL sclk_cnt: INTEGER RANGE 0 TO 15;--时钟计数器
SIGNAL tx_data: STD_LOGIC_VECTOR(7 DOWNTO 0);
SIGNAL rx_data: STD_LOGIC_VECTOR(7 DOWNTO 0);
signal t,t_l,tmp,cs_io:STD_LOGIC;
--与总线的交互接口
signal din : STD_LOGIC_VECTOR(7 DOWNTO 0);
signal req : STD_LOGIC;
signal dout : STD_LOGIC_VECTOR(7 DOWNTO 0);
signal dout_vld : STD_LOGIC;
--fifo信号
signal FIFO_Data_send,FIFO_Data_get: std_logic_vector(7 downto 0);
signal FIFO_wrreq_send,FIFO_wrreq_get,rise_FIFO_wrreq_send: STD_LOGIC;
signal LLD_FIFO_rdreq_send,LLD_FIFO_rdreq_get: STD_LOGIC;
signal clear_send,clear_get,tx_flag,sclk_flag,SenddataTrig_flag : STD_LOGIC;
signal numfifo_send,numfifo_get : std_logic_vector(12 downto 0);
signal LLD_DATA_FIFORD_send,LLD_DATA_FIFORD_get: std_logic_vector(7 downto 0);
signal LLD_FIFO_empty_send,LLD_FIFO_empty_get,miso_delay,trig_flag : STD_LOGIC;
signal LLD_FIFO_full_send,LLD_FIFO_full_get,sclk_getflag,sclk_get: STD_LOGIC;
signal data_length_send :std_logic_vector(7 downto 0);
signal WorkMode_send,WorkMode_get :std_logic_vector(7 downto 0);
signal byteDataReceivedOK,CRC_check,send_data_req,LLD_FIFO_rden_get,LLD_FIFO_rden_send,rise_FIFO_wrreq_get :STD_LOGIC;--接收fifo使能信号
signal LLD_accept : STD_LOGIC_VECTOR(7 DOWNTO 0);
BEGIN
U1: LLD_UART_FIFO port map (Data=>FIFO_Data_send,Clk=>sysclk,WrEn=>rise_FIFO_wrreq_send,RdEn=>LLD_FIFO_rdreq_send,Reset=>clear_send
,Wnum=>numfifo_send,Q=>LLD_DATA_FIFORD_send,Empty=>LLD_FIFO_empty_send,Full=>LLD_FIFO_full_send);
--U2: LLD_UART_FIFO2 port map (Data=>FIFO_Data_get,Clk=>sysclk,WrEn=>rise_FIFO_wrreq_get,RdEn=>LLD_FIFO_rdreq_get,Reset=>clear_get,Wnum=>numfifo_get,Q=>LLD_DATA_FIFORD_get,Empty=>LLD_FIFO_empty_get,Full=>LLD_FIFO_full_get);
U3: rising_edge_detect port map(nreset=>sysreset,clk=>sysclk,input=>LLD_FIFO_rdreq_get,output=>LLD_FIFO_rden_get);
U4: rising_edge_detect port map(nreset=>sysreset,clk=>sysclk,input=>FIFO_wrreq_send,output=>rise_FIFO_wrreq_send);
U5: rising_edge_detect port map(nreset=>sysreset,clk=>sysclk,input=>SenddataTrig,output=>SenddataTrig_flag);
--U5: rising_edge_detect port map(nreset=>sysreset,clk=>sysclk,input=>FIFO_wrreq_get,output=>rise_FIFO_wrreq_get);
U6: rising_edge_detect port map(nreset=>sysreset,clk=>sysclk,input=>sclk_get,output=>sclk_getflag);
--参数设置
process(sysclk,sysreset)
begin
if sysreset ='0' then
FIFO_Data_send <= x"00";
FIFO_wrreq_send <= '0';
clear_send <= '0';
FIFO_Data_get <= x"00";
FIFO_wrreq_get <= '0';
elsif rising_edge(sysclk) then
FIFO_Data_send <= Senddata;
FIFO_wrreq_send <= SenddataTrig_flag;
clear_send <= Send_clear;
FIFO_Data_get <= dout;
FIFO_wrreq_get <= trig_flag ;
GetdataTrig <= trig_flag ;
Getdata <= dout;
req <= CS;
end if;
end process;
--spi通讯协议
PROCESS(sysclk,sysreset)
BEGIN
IF(sysreset = '0') THEN
state_c <= IDLE;
ELSIF(sysclk'EVENT AND sysclk = '1') THEN
state_c <= state_n;
END IF;
END PROCESS;
-------------------------------------------------------------------------------------------
PROCESS(state_c,req,bit_cnt)
BEGIN
IF(sysreset = '0') THEN
state_n <= IDLE;
else
if req = '0' then
state_n <= IDLE;
else
CASE state_c IS
WHEN IDLE =>
IF(req = '1')THEN
state_n <= WAIT_L;
END IF;
WHEN WAIT_L =>
state_n <= DATA;
WHEN DATA =>
IF(tmp = '1')THEN--() AND
state_n <= DONE;
END IF;
WHEN DONE =>
state_n <= IDLE;
WHEN OTHERS =>
state_n <= IDLE;
END CASE;
end if;
end if;
END PROCESS;
-----------------------------------bit_cnt ---------------------------------------------------------
PROCESS(sysclk,sysreset)
BEGIN
IF(sysreset = '0') THEN
bit_cnt <= 0;
byteDataReceivedOK <= '0';
ELSIF(sysclk'EVENT AND sysclk = '1')THEN
IF req = '0' then
bit_cnt <= 0;
ELSIF sclk_cnt = SCLK_NUM -1 THEN
IF(bit_cnt = 7)THEN
bit_cnt <= 0;
byteDataReceivedOK <='1';
ELSE
bit_cnt <= bit_cnt + 1;
byteDataReceivedOK <='0';
END IF;
ELSE
bit_cnt <= bit_cnt;
END IF;
--if LLD_FIFO_empty_send = '1' and tmp = '1' then
--bit_cnt <= 0;
--byteDataReceivedOK <= '0';
--end if;
END IF;
END PROCESS;
----------------------------------sclk计数器---------------------------------------------------------
PROCESS(sysclk,sysreset)
BEGIN
IF(sysreset = '0') THEN
sclk_cnt <= 0;
ELSIF(sysclk'EVENT AND sysclk = '1')THEN
if req = '0' then
sclk_cnt <= 0;
else
IF(sclk_cnt = SCLK_NUM-1)THEN
sclk_cnt <= 0;
ELSE
sclk_cnt <= sclk_cnt + 1;
END IF;
end if;
--if LLD_FIFO_empty_send = '1' and tmp = '1' then
-- sclk_cnt <= 0;
--end if;
END IF;
END PROCESS;
----------------------------------16分频串行时钟 CPHA=1 CPOL=1-----------------------------------------------------------
PROCESS(sysclk,sysreset)
BEGIN
IF(sysreset = '0') THEN
IF(CPHA = 0)THEN
sclk <= '0';--空闲低电平
ELSIF(CPHA = 1)THEN
sclk <= '1';
sclk_get <= '1';
END IF;
sclk_flag <= '0';
ELSIF(sysclk'EVENT AND sysclk = '1')THEN
sclk_get <= sclk;
if req = '1' then
if bit_cnt = 0 and sclk_cnt = 5 then
sclk_flag <= '1';
else
sclk_flag <= sclk_flag;
end if;
else
sclk_flag <= '0';
end if;
if sclk_flag = '1' then
IF sclk_cnt = 3 THEN
sclk <= NOT sclk;
ELSIF sclk_cnt = 3 + SCLK_NUM/2 then
sclk <= NOT sclk;
END IF;
else
sclk <= '1';
end if;
if req = '0' then
sclk <= '1';
end if;
--if LLD_FIFO_empty_send = '1' and tmp = '1' then
-- sclk <= '1';
--end if;
END IF;
END PROCESS;
---------------------------------发送的数据mosi,高位先行------------------------------------------------------------------
PROCESS(sysclk,sysreset)
BEGIN
IF(sysreset = '0')THEN
tx_data <= "00000000";
LLD_FIFO_rdreq_send <='0';
tx_flag <='0';
ELSIF(sysclk'EVENT AND sysclk = '1')THEN
IF(CPOL = 0)THEN
IF(sclk_cnt = SCLK_AFTER-2) AND (bit_cnt = 1) then
LLD_FIFO_rdreq_send <= '1';
else
LLD_FIFO_rdreq_send <= '0';
end if;
IF((sclk_cnt = SCLK_AFTER-1)) and tx_flag='0' THEN --后沿输出
tx_data <= LLD_DATA_FIFORD_send;
elsif LLD_FIFO_empty_send = '1' and bit_cnt = 7 and sclk_cnt = SCLK_NUM-1 then
tx_data <= "00000000";
tx_flag <='1';
else
tx_data <=tx_data;
END IF;
ELSIF(CPOL = 1)THEN
IF(sclk_cnt = 1) AND (bit_cnt = 0) then
LLD_FIFO_rdreq_send <= '1';
else
LLD_FIFO_rdreq_send <= '0';
end if;
IF sclk_cnt = 4 and tx_flag = '0' THEN
tx_data <= LLD_DATA_FIFORD_send;
elsif LLD_FIFO_empty_send = '1' and bit_cnt = 7 and sclk_cnt = SCLK_NUM-1 then
tx_data <= "00000000";
tx_flag <='1';
else
tx_data <=tx_data;
END IF;
END IF;
if LLD_FIFO_empty_send = '1' then
LLD_FIFO_rdreq_send <='0';
elsif LLD_FIFO_empty_send = '0' and LLD_FIFO_rdreq_send = '1' then
tx_flag <='0';
else
tx_flag <=tx_flag;
end if;
END IF;
END PROCESS;
----------------------------------接收的数据miso-----------------------------------------------------------------------------
PROCESS(sysclk,sysreset)
BEGIN
IF(sysreset = '0')THEN
rx_data <= "00000000";
get_cnt <= 0;
ELSIF(sysclk'EVENT AND sysclk = '1')THEN
miso_delay <= miso;
IF(CPOL = 0)THEN
IF((sclk_cnt = SCLK_BEFORE-1))THEN --前沿采样
rx_data(7-bit_cnt) <= miso_delay;
END IF;
ELSIF(CPOL = 1)THEN
if sclk_getflag = '1' and req = '1' then
get_cnt <= get_cnt + 1;
rx_data(7-get_cnt) <= miso_delay;
if get_cnt = 7 then
get_cnt <= 0;
end if;
END IF;
END IF;
END IF;
END PROCESS;
----------------------------------------出bug后加的--(sclk_cnt=15)AND(bit_cnt = 7)出现状态机不跳转---------------------------------
PROCESS(sclk_cnt)BEGIN
IF((sclk_cnt=SCLK_NUM-1))THEN
t<='1';
ELSE
t <= '0';
END IF;
END PROCESS;
PROCESS(bit_cnt)BEGIN
IF(bit_cnt = 7)THEN
t_l<='1';
ELSE
t_l <= '0';
END IF;
END PROCESS;
tmp <= t and t_l;
------------------------------------------------------------------------------------------------------------------------------------
PROCESS(sysclk,sysreset)
BEGIN
IF(sysreset = '0')THEN
ELSIF(sysclk'EVENT AND sysclk = '1')THEN
if sclk_cnt = 5 then
mosi <= tx_data(7-bit_cnt);
else
mosi <= mosi;
end if;
end if;
END PROCESS;
CS1 <= not CS;
PROCESS(sysclk,sysreset)
BEGIN
IF(sysreset = '0')THEN
dout <= x"00";
trig_flag <= '0';
ELSIF(sysclk'EVENT AND sysclk = '1')THEN
if get_cnt = 0 then
dout <= rx_data;
trig_flag <= '1';
else
dout <= dout;
trig_flag <= '0';
end if;
end if;
END PROCESS;
END SPI1;