原创 基于FPGA的SPI控制器

2009-6-17 15:28 3658 8 10 分类: FPGA/CPLD
基于FPGA的SPI控制器


一 SPI协议概括

SPI,是英语Serial Peripheral interface的缩写,顾名思义就是串行外围设备接口。是Motorola首先在其MC68HCXX系列处理器上定义的。SPI接口主要应用在 EEPROM,FLASH,实时时钟,AD转换器,还有数字信号处理器和数字信号解码器之间。SPI,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便,正是出于这种简单易用的特性,现在越来越多的芯片集成了这种通信协议,比如AT91RM9200.
SPI的通信原理很简单,它以主从方式工作,这种模式通常有一个主设备和一个或多个从设备,需要至少4根线,事实上3根也可以(单向传输时)。也是所有基于SPI的设备共有的,它们是SDI(数据输入),SDO(数据输出),SCK(时钟),CS(片选)。
(1)SDO     – 主设备数据输出,从设备数据输入
(2)SDI      – 主设备数据输入,从设备数据输出
(3)SCLK   – 时钟信号,由主设备产生
(4)CS        – 从设备使能信号,由主设备控制
其中CS是控制芯片是否被选中的,也就是说只有片选信号为预先规定的使能信号时(高电位或低电位),对此芯片的操作才有效。这就允许在同一总线上连接多个SPI设备成为可能。
接下来就负责通讯的3根线了。通讯是通过数据交换完成的,这里先要知道SPI是串行通讯协议,也就是说数据是一位一位的传输的。这就是SCK时钟线存在的原因,由SCK提供时钟脉冲,SDI,SDO则基于此脉冲完成数据传输。数据输出通过 SDO线,数据在时钟上升沿或下降沿时改变,在紧接着的下降沿或上升沿被读取。完成一位数据传输,输入也使用同样原理。这样,在至少8次时钟信号的改变(上沿和下沿为一次),就可以完成8位数据的传输。 
要注意的是,SCK信号线只由主设备控制,从设备不能控制信号线。同样,在一个基于SPI的设备中,至少有一个主控设备。这样传输的特点:这样的传输方式有一个优点,与普通的串行通讯不同,普通的串行通讯一次连续传送至少8位数据,而SPI允许数据一位一位的传送,甚至允许暂停,因为SCK时钟线由主控设备控制,当没有时钟跳变时,从设备不采集或传送数据。也就是说,主设备通过对SCK时钟线的控制可以完成对通讯的控制。SPI还是一个数据交换协议:因为SPI的数据输入和输出线独立,所以允许同时完成数据的输入和输出。不同的SPI设备的实现方式不尽相同,主要是数据改变和采集的时间不同,在时钟信号上沿或下沿采集有不同定义,具体请参考相关器件的文档。
在点对点的通信中,SPI接口不需要进行寻址操作,且为全双工通信,显得简单高效。在多个从设备的系统中,每个从设备需要独立的使能信号,硬件上比I2C系统要稍微复杂一些。
最后,SPI接口的一个缺点:没有指定的流控制,没有应答机制确认是否接收到数据。
AT91RM9200的SPI接口主要由4个引脚构成:SPICLK、MOSI、MISO及 /SS,其中SPICLK是整个SPI总线的公用时钟,MOSI、MISO作为主机,从机的输入输出的标志,MOSI是主机的输出,从机的输入,MISO 是主机的输入,从机的输出。/SS是从机的标志管脚,在互相通信的两个SPI总线的器件,/SS管脚的电平低的是从机,相反/SS管脚的电平高的是主机。在一个SPI通信系统中,必须有主机。SPI总线可以配置成单主单从,单主多从,互为主从。
SPI的片选可以扩充选择16个外设,这时PCS输出=NPCS,说NPCS0~3接4-16译码器,这个译码器是需要外接4-16译码器,译码器的输入为NPCS0~3,输出用于16个外设的选择。
二 SPI协议举例

SPI是一个环形总线结构,由ss(cs)、sck、sdi、sdo构成,其时序其实很简单,主要是在sck的控制下,两个双向移位寄存器进行数据交换。
       假设下面的8位寄存器装的是待发送的数据10101010,上升沿发送、下降沿接收、高位先发送。
       那么第一个上升沿来的时候 数据将会是sdo=1;寄存器=0101010x。下降沿到来的时候,sdi上的电平将所存到寄存器中去,那么这时寄存器=0101010sdi,这样在 8个时钟脉冲以后,两个寄存器的内容互相交换一次。这样就完成里一个spi时序。
举例:
      假设主机和从机初始化就绪:并且主机的sbuff=0xaa,从机的sbuff=0x55,下面将分步对spi的8个时钟周期的数据情况演示一遍:假设上升沿发送数据 

这样就完成了两个寄存器8位的交换,上面的上表示上升沿、下表示下降沿,sdi、sdo相对于主机而言的。其中ss引脚作为主机的时候,从机可以把它拉底被动选为从机,作为从机的是时候,可以作为片选脚用。根据以上分析,一个完整的传送周期是16位,即两个字节,因为,首先主机要发送命令过去,然后从机根据主机的命令准备数据,主机在下一个8位时钟周期才把数据读回来。
      SPI 总线是Motorola公司推出的三线同步接口,同步串行3线方式进行通信:一条时钟线SCK,一条数据输入线MOSI,一条数据输出线MISO;用于CPU与各种外围器件进行全双工、同步串行通讯。SPI主要特点有:可以同时发出和接收串行数据;可以当作主机或从机工作;提供频率可编程时钟;发送结束 中断标志;写冲突保护;总线竞争保护等。下图示出SPI总线工作的四种方式,其中使用的最为广泛的是SPI0和SPI3方式 (实线表示):
                    
                            
                                                    

                                                                                               SPI总线四种工作方式 
SPI 模块为了和外设进行数据交换,根据外设工作要求,其输出串行同步时钟极性和相位可以进行配置,时钟极性(CPOL)对传输协议没有重大的影响。如果 CPOL=0,串行同步时钟的空闲状态为低电平;如果CPOL=1,串行同步时钟的空闲状态为高电平。时钟相位(CPHA)能够配置用于选择两种不同的传输协议之一进行数据传输。如果CPHA=0,在串行同步时钟的第一个跳变沿(上升或下降)数据被采样;如果CPHA=1,在串行同步时钟的第二个跳变沿(上升或下降)数据被采样。SPI主模块和与之通信的外设备时钟相位和极性应该一致。
SPI总线包括1根串行同步时钟信号线以及2根数据线。
       SPI模块为了和外设进行数据交换,根据外设工作要求,其输出串行同步时钟极性和相位可以进行配置,时钟极性(CPOL)对传输协议没有重大的影响。如果CPOL=0,串行同步时钟的空闲状态为低电平;如果CPOL=1,串行同步时钟的空闲状态为高电平。时钟相位(CPHA)能够配置用于选择两种不同的传输协议之一进行数据传输。如果CPHA=0,在串行同步时钟的第一个跳变沿(上升或下降)数据被采样;如果CPHA=1,在串行同步时钟的第二个跳变沿(上升或下降)数据被采样。SPI主模块和与之通信的外设音时钟相位和极性应该一致。SPI接口时序如图3、图4所示。

补充: 
上文中最后一句话:SPI主模块和与之通信的外设备时钟相位和极性应该一致。个人理解这句话有2层意思:其一,主设备SPI时钟和极性的配置应该由外设来决定;其二,二者的配置应该保持一致,即主设备的SDO同从设备的SDO配置一致,主设备的SDI同从设备的SDI配置一致。因为主从设备是在SCLK的控制下,同时发送和接收数据,并通过2个双向移位寄存器来交换数据。工作原理演示如下图:

上升沿主机SDO发送数据1,同时从设备SDO发送数据0;紧接着在SCLK的下降沿的时候从设备的SDI接收到了主机发送过来的数据1,同时主机也接收到了从设备发送过来的数据0.

三 FPGA实现源码

LIBRARY ieee;
USE ieee.std_logic_1164.ALL;
USE ieee.std_logic_arith.ALL;
USE ieee.std_logic_unsigned.ALL;

ENTITY spi IS
 PORT
 (
  --全局信号
  NReset   : IN  STD_LOGIC;        --全局复位信号
  Clk    : IN  STD_LOGIC;        --全局时钟
--  ResetWdi  : BUFFER STD_LOGIC;        --看门狗的喂狗信号
  --SPI通讯端口
  SPINcs   : INOUT  STD_LOGIC;        --SPI片选(低有效)
  SPIClk   : INOUT  STD_LOGIC;        --SPI时钟
  SPIMOSI   : INOUT  STD_LOGIC;        --SPI的主出从入
  SPIMISO   : INOUT  STD_LOGIC;        --SPI的主入从出
  --SPI数据寄存器
  SPITxdata  : IN  STD_LOGIC_VECTOR(15 DOWNTO 0);   --待发送数据
  SPIRxdata  : BUFFER STD_LOGIC_VECTOR(15 DOWNTO 0);   --接收数据
  --SPI状态寄存器
  SPIStatus  : BUFFER STD_LOGIC_VECTOR(7 DOWNTO 0);   --第5位为trdy(发送器准备好),第6位rrdy(接收器准备好)
  --SPI控制寄存器
  SPIControl  : IN  STD_LOGIC_VECTOR(7 DOWNTO 0);   --第0位为mode(模式设置,0为主模式,1为从模式),
                   --第1位为clkpolarity(时钟极性,0时为空闲是低,1为空闲是高),
                   --第2位为clkphasic(时钟相位,0时为前沿检测,1为后沿检测),
                   --第3位为datapriority(数据先后,0时为MSB在前,1为LSB在前),
                   --第4位为start(上升沿开始发送数据),
                   --第7位sso(1时始终置cs为1(无效))
  SPILength  : IN  STD_LOGIC_VECTOR(7 DOWNTO 0);   --数据位数寄存器
  --SPI频率设置寄存器
  SPISetClk  : IN  STD_LOGIC_VECTOR(31 DOWNTO 0)   --SPIClk为Clk的SPISetClk分频
 );
END spi;

ARCHITECTURE rtl OF spi IS
 TYPE StateType IS (S0_Wait,S1_Data,S2_Stop);       --状态机定义
 SIGNAL StateIndex  : StateType;         --状态机
 SIGNAL CntCycle  : integer RANGE 0 TO 16383;     --位周期计数器
 SIGNAL CntBit   : integer RANGE 0 TO 15;      --位数计数器
 SIGNAL SPITxdataTmp : STD_LOGIC_VECTOR(15 DOWNTO 0);    --数据锁存
 SIGNAL CycleBit  : integer RANGE 0 TO 16383;     --位周期
 
 SIGNAL SPIClkCut  : STD_LOGIC_VECTOR(31 DOWNTO 0);   --生成SPIClk计数器
 SIGNAL SPIBitCut  : integer RANGE 0 TO 15;     --SPI的数据位数计数
 --看门狗计数
-- SIGNAL CntWdi   : STD_LOGIC_VECTOR(9 DOWNTO 0);
BEGIN

--看门狗的喂狗信号的产生
--CntWdi <= CntWdi + 1 WHEN (Clk'EVENT AND Clk = '1');
--ResetWdi <= CntWdi(9);


--设置SPI的主从模式,若为主模式则设置SPINcs和SPIClk
PROCESS (NReset, Clk)
BEGIN
 IF(NReset = '0') THEN     --复位状态
  SPINcs <= 'Z';
  SPIClk <= 'Z';
  SPIMOSI <= 'Z';
  SPIMISO <= 'Z';
  SPIStatus <= "01100000";
  SPIRxdata <= X"0000";
  StateIndex <= S0_Wait;
  SPIClkCut <= X"00000000";
  SPIBitCut <= 0;
 ELSIF(Clk'EVENT AND Clk = '1') THEN
  IF(SPIControl(0) = '0') THEN   --主模式状态
   SPIMISO <= 'Z';
   CASE StateIndex IS
    WHEN S0_Wait =>
     SPIClk <= SPIControl(1);
     IF(SPIControl(4) = '1') THEN --判断起始标志
      SPINcs <= SPIControl(7);
      IF(SPIControl(3) = '0') THEN  --判断数据MSB还是LSB在前
       SPIMOSI <= SPITxdata(CONV_INTEGER(SPILength) - 1);
      ELSE
       SPIMOSI <= SPITxdata(0);
      END IF;
      SPIRxdata <= X"0000";
      StateIndex <= S1_Data;
      SPIStatus <= "00000000";
     ELSE
      SPINcs <= '1';
      SPIMOSI <= '0';
      StateIndex <= StateIndex;
      SPIStatus <= "01100000";
     END IF;
     SPIBitCut <= 0;
     SPIClkCut <= X"00000000";     
    WHEN S1_Data =>
     SPINcs <= SPIControl(7);
     IF(SPIClkCut  = ('0' & SPISetClk(31 DOWNTO 1)-'1')) THEN --SPIClk前沿
      SPIClk <= NOT SPIClk;
      SPIClkCut <= SPIClkCut + 1;
      IF(SPIControl(2) = '0') THEN   --前沿检测
       SPIMOSI <= SPIMOSI;
       IF(SPIControl(3) = '0') THEN  --判断数据MSB还是LSB在前
        SPIRxdata <= SPIRxdata;
        SPIRxdata(CONV_INTEGER(SPILength) - SPIBitCut - 1) <= SPIMISO;
       ELSE
        SPIRxdata <= SPIRxdata;
        SPIRxdata(SPIBitCut) <= SPIMISO;
       END IF;
      ELSE         --后沿检测
       IF(SPIControl(3) = '0') THEN  --判断数据MSB还是LSB在前
        SPIMOSI <= SPITxdata(CONV_INTEGER(SPILength) - SPIBitCut - 1);
       ELSE
        SPIMOSI <= SPITxdata(SPIBitCut);
       END IF;
       SPIRxdata <= SPIRxdata;
      END IF;
      StateIndex <= StateIndex;
      SPIBitCut <= SPIBitCut;
     ELSIF(SPIClkCut >= (SPISetClk(31 DOWNTO 0) -'1')) THEN  --SPIClk后沿
      SPIClk <= NOT SPIClk;
      SPIClkCut <= X"00000000";
      IF(SPIControl(2) = '0') THEN   --前沿检测
       IF(SPIControl(3) = '0') THEN  --判断数据MSB还是LSB在前
        SPIMOSI <= SPITxdata(CONV_INTEGER(SPILength) - SPIBitCut - 2);
       ELSE
        SPIMOSI <= SPITxdata(SPIBitCut);
       END IF;
       SPIRxdata <= SPIRxdata;       
      ELSE         --后沿检测
       SPIMOSI <= SPIMOSI;
       IF(SPIControl(3) = '0') THEN  --判断数据MSB还是LSB在前
        SPIRxdata <= SPIRxdata;
        SPIRxdata(CONV_INTEGER(SPILength) - SPIBitCut - 1) <= SPIMISO;
       ELSE
        SPIRxdata <= SPIRxdata;
        SPIRxdata(SPIBitCut) <= SPIMISO;
       END IF;
      END IF;
      IF(SPIBitCut >= CONV_INTEGER(SPILength) - 1) THEN
       SPIBitCut <= 0;
       SPIMOSI <= SPIMOSI;
       StateIndex <= S2_Stop;
      ELSE
       SPIBitCut <= SPIBitCut + 1;
       StateIndex <= StateIndex;
      END IF;
     ELSE
      SPIClk <= SPIClk;
      SPIMOSI <= SPIMOSI;
      StateIndex <= StateIndex;
      SPIClkCut <= SPIClkCut + 1;
      SPIBitCut <= SPIBitCut;
      SPIRxdata <= SPIRxdata;
     END IF;
     SPIStatus <= "00000000";
    WHEN S2_Stop =>
     IF(SPIClkCut  = ('0' & SPISetClk(31 DOWNTO 1)-'1')) THEN
      StateIndex <= S0_Wait;
      SPIClkCut <= X"00000000";
     ELSE
      StateIndex <= StateIndex;
      SPIClkCut <= SPIClkCut + 1;
     END IF;
     SPINcs <= SPINcs;
     SPIClk <= SPIClk;
     SPIMOSI <= SPIMOSI;
     SPIStatus <= SPIStatus;
     SPIRxdata <= SPIRxdata;
     SPIBitCut <= 0;
    WHEN OTHERS =>
     SPINcs <= 'Z';
     SPIClk <= 'Z';
     SPIMOSI <= 'Z';
     SPIStatus <= "01100000";
     SPIRxdata <= X"0000";
     StateIndex <= S0_Wait;
     SPIClkCut <= X"00000000";
     SPIBitCut <= 0;
   END CASE;
  ELSE         --从模式状态
   SPINcs <= 'Z';
   SPIClk <= 'Z';
   SPIMOSI <= 'Z';
  END IF; 
 END IF;
END PROCESS;

END rtl;

PARTNER CONTENT

文章评论2条评论)

登录后参与讨论

用户239416 2010-1-7 15:24

正需要啊,太感谢了!

tengjingshu_112148725 2009-6-18 09:27

谢谢了
相关推荐阅读
bitao1983_395643617 2016-01-26 16:32
华为硬件开发是怎么做的,有什么不一样?(感同身受)
朱晓明 最近很多朋友咨询的一些硬件问题,发现朋友们没有仔细的看datasheet,也没有好好的做电路分析。我讲一讲华为是怎么做硬件开发的,给正在做硬件开发的朋友一些启示。说的不对的地方,大家批...
bitao1983_395643617 2015-12-08 11:24
(多图)宽带数字下变频器的FPGA实现
随着软件无线电理论的日趋成熟,软件无线电技术越来越多地应用到军用或民用通信系统中。其中,数字下变频技术(DDC)是软件无线电中的核心技术之一。数字下变频工作在模拟前端输入模拟信号经模数转换之后,而在终...
bitao1983_395643617 2015-10-15 09:41
扒开看移动5G原型机,聊聊其硬件设计的构想?
虽然5G标准尚未成型,但距离大家公认的商用化时间已不足5年(业界公认到2020年会实现5G的大规模部署)。为了抢占先机,参与标准制定,许多公司目前已提出了就5G的新兴算法和应用进行原型设计的需求,全球...
bitao1983_395643617 2015-10-15 09:39
(多图)时钟抖动和相噪及其测量方法
抖动测量一直被称为示波器测试测量的最高境界。传统最直观的抖动测量方法是利用余辉来查看波形的变化。后来演变为高等数学概率统计上的艰深问题,抖动测量结果准还是不准的问题就于是变得更加复杂。 时钟的特性可...
bitao1983_395643617 2015-06-27 22:49
你设计的PCB EMI达标了吗?
电子设备的电子信号和处理器的频率不断提升,电子系统已是一个包含多种元器件和许多分系统的复杂设备。高密和高速会令系统的辐射加重,而低压和高灵敏度 会使系统的抗扰度降低。因此,电磁干扰(EMI)实在是威胁...
bitao1983_395643617 2015-04-10 14:28
(多图) FPGA与ADC数字数据输出的接口及LVDS应用诀窍
现场可编程门阵列(FPGA)与模数转换器(ADC)输出的接口是一项常见的工程设计挑战。本文简要介绍各种接口协议和标准,并提供有关在高速数据转换器实现方案中使用LVDS的应用诀窍和技巧。 接口方式和标...
EE直播间
更多
我要评论
2
8
关闭 站长推荐上一条 /3 下一条