STM32与FPGA实现SPI通讯的研究

STM32与FPGA之间的SPI通讯

  • SPI通讯协议
  • SPI协议物理层
  • 协议层
  • STM32的SPI特性及架构
  • STM32的SPI架构
  • SPI初始化结构体(STM32标准库)
  • STM32实验代码
  • FPGA从机代码编写
  • 实验结果
  • SPI通讯协议

    SPI协议物理层

    SPI协议是一种高速全双工的通信总线。SPI设备之间的连接方式如图所示:
    SPI通讯使用3条总线及一个片选线

    SPI通讯使用3条总线及一个片选线,SCK为时钟信号线,MISO为主设备输入/从设备输出,MOSI为主设备输出/从设备输入。

    协议层

    下图就是SPI通讯的通讯时序:
    1)采样时刻,MISO与MOSI的数据才有效,高电平表示为“1”,低电平表示为“0”。
    2)通讯的起始信号:片选信号由高变低;SPI的停止信号:片选信号由低变高。
    SPI的基本通讯时序

    SPI共有4种通讯模式,由CPOL和CPHA决定:

    1. 时钟极性CPOL ,表示SPI通讯设备处于空闲状态时,SCK的电平信号;CPOL为0时,即指通讯开始前SCK为低电平。
    2. 时钟相位CPHA ,指数据的采样时刻,CPHA = 0,数据线在SCK时钟线的“奇数边沿”采样;CPHA = 1,数据线在SCK时钟线的“偶数边沿”采样。
    SPI模式 CPOL CPHA 空闲时SCK时钟 采样时刻
    0 0 0 低电平 奇数边沿
    1 0 1 低电平 偶数边沿
    2 1 0 低电平 奇数边沿
    3 1 1 低电平 偶数边沿

    STM32的SPI特性及架构

    STM32的SPI外设支持最高的时钟频率为fpclk/2(STM32F103 型号的芯片默认 f pclk1 为 72MHz,f pclk2 为 36MHz)。本实验采用双线全双工模式。

    STM32的SPI架构

    1. 通讯引脚 ,STM32有多个SPI外设,使用对应SPI外设引脚,其中片选引脚一般采用普通IO口,使用软件控制片选段。
    2. 时钟控制逻辑 ,SCK 线的时钟信号,由波特率发生器根据“控制寄存器 CR1”中的 BR[0:2]位控制。通过配置“控制寄存器 CR”的“CPOL 位”及“CPHA”位可以把 SPI 设置成前面分析的 4 种 SPI模式。
    3. 数据控制逻辑通过写 SPI的“数据寄存器 DR”把数据填充到发送 F 缓冲区中,通讯读“数据寄存器 DR”,可以获得接收缓冲区内容。
    4. 整体控制逻辑整体控制逻辑负责协调整个 SPI 外设,控制逻辑的工作模式根据我们配置的**“控制寄存器(CR1/CR2)”的参数而改变,基本的控制参数包括前面提到的 SPI 模式、波特率、LSB先行、主从模式、单双向模式等等。在外设工作时,控制逻辑会根据外设的工作状态修改“状态寄存器(SR)”**,我们只要读取状态寄存器相关的寄存器位,就可以了解 SPI 的工作状态了。
      主模式通讯流程

    SPI初始化结构体(STM32标准库)


    配置完这些结构体成员后,我们要调用 SPI_Init 函数把这些参数写入到寄存器中,实现 SPI的初始化,然后调用 SPI_Cmd 来使能 SPI外设。

    STM32实验代码

    本实验采用SPI模式3进行主模式代码编写,编程要点如下:

    1. 初始化通讯使用的目标引脚及端口时钟;
    2. 使能SPI外设时钟
    3. 配置 SPI外设的模式、地址、速率等参数并使能 SPI外设
    4. 编写SPI按照字节收发的函数
      新建一个c文件,用于存放SPI初始化及读写数据相关函数。
    /**
      * @brief  SPI_FPGA初始化
      * @param  无
      * @retval 无
      */
        #include "./fpga/bsp_spi_fpga.h"
    void SPI_FPGA_Init(void)
    {
      SPI_InitTypeDef  SPI_InitStructure;
      GPIO_InitTypeDef GPIO_InitStructure;
    	
    	/* 使能SPI时钟 */
    	FPGA_SPI_APBxClock_FUN ( FPGA_SPI_CLK, ENABLE );
    	
    	/* 使能SPI引脚相关的时钟 */
     	FPGA_SPI_CS_APBxClock_FUN ( FPGA_SPI_CS_CLK|FPGA_SPI_SCK_CLK|
    																	FPGA_SPI_MISO_PIN|FPGA_SPI_MOSI_PIN, ENABLE );
    	
      /* 配置SPI的 CS引脚,普通IO即可 */
      GPIO_InitStructure.GPIO_Pin = FPGA_SPI_CS_PIN;
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
      GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
      GPIO_Init(FPGA_SPI_CS_PORT, &GPIO_InitStructure);
    	
      /* 配置SPI的 SCK引脚*/
      GPIO_InitStructure.GPIO_Pin = FPGA_SPI_SCK_PIN;
      GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
      GPIO_Init(FPGA_SPI_SCK_PORT, &GPIO_InitStructure);
    
      /* 配置SPI的 MISO引脚*/
      GPIO_InitStructure.GPIO_Pin = FPGA_SPI_MISO_PIN;
      GPIO_Init(FPGA_SPI_MISO_PORT, &GPIO_InitStructure);
    
      /* 配置SPI的 MOSI引脚*/
      GPIO_InitStructure.GPIO_Pin = FPGA_SPI_MOSI_PIN;
      GPIO_Init(FPGA_SPI_MOSI_PORT, &GPIO_InitStructure);
    
      /* 停止信号 FPGA: CS引脚高电平*/
      SPI_FPGA_CS_HIGH();
    
      /* SPI 模式配置 */
      // FPGA芯片 支持SPI模式0及模式3,据此设置CPOL CPHA
      SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
      SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
      SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
      SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;
      SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;
      SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
      SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4;
      SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
      SPI_InitStructure.SPI_CRCPolynomial = 7;
      SPI_Init(FPGA_SPIx , &SPI_InitStructure);
    
      /* 使能 SPI  */
      SPI_Cmd(FPGA_SPIx , ENABLE);
    	
    }
    
    /**
      * @brief  使用SPI发送一个字节的数据
      * @param  byte:要发送的数据
      * @retval 返回接收到的数据
      */
    u8 SPI_FPGA_SendByte(u8 byte)
    {
    	 SPITimeout = SPIT_FLAG_TIMEOUT;
      /* 等待发送缓冲区为空,TXE事件 */
      while (SPI_I2S_GetFlagStatus(FPGA_SPIx , SPI_I2S_FLAG_TXE) == RESET)
    	{
        if((SPITimeout--) == 0) return SPI_TIMEOUT_UserCallback(0);
       }
    
      /* 写入数据寄存器,把要写入的数据写入发送缓冲区 */
      SPI_I2S_SendData(FPGA_SPIx , byte);
    
    	SPITimeout = SPIT_FLAG_TIMEOUT;
      /* 等待接收缓冲区非空,RXNE事件 */
      while (SPI_I2S_GetFlagStatus(FPGA_SPIx , SPI_I2S_FLAG_RXNE) == RESET)
      {
        if((SPITimeout--) == 0) return SPI_TIMEOUT_UserCallback(1);
       }
    
      /* 读取数据寄存器,获取接收缓冲区数据 */
      return SPI_I2S_ReceiveData(FPGA_SPIx );
    }
     /**
      * @brief  使用SPI读取一个字节的数据
      * @param  无
      * @retval 返回接收到的数据
      */
    u8 SPI_FPGA_ReadByte(void)
    {
      return (SPI_FPGA_SendByte(Dummy_Byte));
    }
    /**
      * @brief  等待超时回调函数
      * @param  None.
      * @retval None.
      */
    static  uint16_t SPI_TIMEOUT_UserCallback(uint8_t errorCode)
    {
      /* 等待超时后的处理,输出错误信息 */
      FPGA_ERROR("SPI 等待超时!errorCode = %d",errorCode);
      return 0;
    }
    void Delay(__IO uint32_t nCount)
    {
      for(; nCount != 0; nCount--);
    }
    

    bsp_spi_fpga.h内容如下:

    /*命令定义-结尾*******************************/
    /*SPI接口定义-开头****************************/
    #ifndef __SPI_FPGA_H
    #define __SPI_FPGA_H
    
    #include "stm32f10x.h"
    #include <stdio.h>
    #define      FPGA_SPIx                        SPI2
    #define      FPGA_SPI_APBxClock_FUN          RCC_APB1PeriphClockCmd
    #define      FPGA_SPI_CLK                     RCC_APB1Periph_SPI2
    
    //CS(NSS)引脚 片选选普通GPIO即可
    #define      FPGA_SPI_CS_APBxClock_FUN       RCC_APB2PeriphClockCmd
    #define      FPGA_SPI_CS_CLK                  RCC_APB2Periph_GPIOC    
    #define      FPGA_SPI_CS_PORT                 GPIOC
    #define      FPGA_SPI_CS_PIN                  GPIO_Pin_3
    
    //SCK引脚
    #define      FPGA_SPI_SCK_APBxClock_FUN      RCC_APB2PeriphClockCmd
    #define      FPGA_SPI_SCK_CLK                 RCC_APB2Periph_GPIOB   
    #define      FPGA_SPI_SCK_PORT                GPIOB   
    #define      FPGA_SPI_SCK_PIN                 GPIO_Pin_13
    //MISO引脚
    #define      FPGA_SPI_MISO_APBxClock_FUN     RCC_APB2PeriphClockCmd
    #define      FPGA_SPI_MISO_CLK                RCC_APB2Periph_GPIOB    
    #define      FPGA_SPI_MISO_PORT               GPIOB 
    #define      FPGA_SPI_MISO_PIN                GPIO_Pin_14
    //MOSI引脚
    #define      FPGA_SPI_MOSI_APBxClock_FUN     RCC_APB2PeriphClockCmd
    #define      FPGA_SPI_MOSI_CLK                RCC_APB2Periph_GPIOB    
    #define      FPGA_SPI_MOSI_PORT               GPIOB 
    #define      FPGA_SPI_MOSI_PIN                GPIO_Pin_15
    
    #define  		SPI_FPGA_CS_LOW()     						GPIO_ResetBits( FPGA_SPI_CS_PORT, FPGA_SPI_CS_PIN )
    #define  		SPI_FPGA_CS_HIGH()    						GPIO_SetBits( FPGA_SPI_CS_PORT, FPGA_SPI_CS_PIN )
    /*SPI接口定义-结尾****************************/
    
    /*等待超时时间*/
    #define SPIT_FLAG_TIMEOUT         ((uint32_t)0x1000)
    #define SPIT_LONG_TIMEOUT         ((uint32_t)(10 * SPIT_FLAG_TIMEOUT))
    
    /*信息输出*/
    #define FPGA_DEBUG_ON         1
    
    #define FPGA_INFO(fmt,arg...)           printf("<<-FPGA-INFO->> "fmt"\n",##arg)
    #define FPGA_ERROR(fmt,arg...)          printf("<<-FPGA-ERROR->> "fmt"\n",##arg)
    #define FPGA_DEBUG(fmt,arg...)          do{\                                        if(FPGA_DEBUG_ON)\                                         printf("<<-FPGA-DEBUG->> [%d]"fmt"\n",__LINE__, ##arg);\                                     }while(0)
    
    void SPI_FPGA_Init(void);
    u8 SPI_FPGA_ReadByte(void);
    u8 SPI_FPGA_SendByte(u8 byte);
    void Delay(__IO uint32_t nCount);
    static  uint16_t SPI_TIMEOUT_UserCallback(uint8_t errorCode);																				
    #endif /* __SPI_FPGA_H */
    

    工程主函数为:

    /*
     * 函数名:main
     * 描述  :主函数
     * 输入  :无
     * 输出  :无
     */
    int main(void)
    { 	
    	LED_GPIO_Config();
    	LED_BLUE;	
    	/* 配置串口为:115200 8-N-1 */
    	USART_Config();
    	printf("\r\n 这是一个STM32与FPGA的通讯实验!\r\n");
    	
    	/* 8M串行FPGA初始化 */
    	SPI_FPGA_Init();
    		Temp = 123;
    				SPI_FPGA_CS_LOW();
    				SPI_FPGA_SendByte(Temp);
    				SPI_FPGA_CS_HIGH();
    				Delay(10000);
    			printf("\r\n 写入的数据为:%d \r\t", Temp);	
    				
    				SPI_FPGA_CS_LOW();
    				SPI_FPGA_SendByte(245);
    				SPI_FPGA_CS_HIGH();
    				Delay(10000);
    				SPI_FPGA_CS_LOW();
    				
    				Temp1 = SPI_FPGA_SendByte(Dummy_Byte);
    				SPI_FPGA_CS_HIGH();
    				printf("\r\n 读出的数据为:%d \r\n", Temp1);
    }
    

    FPGA从机代码编写

    //use SPI 3 mode,CHOL = 1,CHAL = 1
    module spi
    (	
    	input 				clk			,
    	input 				rst_n		,
    	input 				CS_N		,
    	input 				SCK			,
    	input 				MOSI		,
    	
    	output reg 			MISO		,
    	output				led			,
    	output				led1		
    	);	
    wire [7:0]	txd_data	;
    assign txd_data = 8'b001_1000;
    reg [7:0] 	rxd_data;
    wire		rxd_flag;
    
    reg    [7:0]	spi_cnt;
    
    //-------------------------capture the sck-----------------------------
    reg sck_r0,sck_r1;
    wire sck_n,sck_p;
    always@(posedge clk or negedge rst_n)
    if(!rst_n)
    begin
    sck_r0 <= 1'b0; //sck of the idle state is high
    sck_r1 <= 1'b0;
    end
    else
    begin
    sck_r0 <= SCK;
    sck_r1 <= sck_r0;
    end
    assign sck_n = (~sck_r0 & sck_r1)? 1'b1:1'b0; //capture the sck negedge
    assign sck_p = (~sck_r1 & sck_r0)? 1'b1:1'b0; //capture the sck posedge
    //-----------------------spi_slaver read data-------------------------------
    reg rxd_flag_r;
    reg [2:0] rxd_state;
    always@(posedge clk or negedge rst_n)
    begin
    if(!rst_n)
    begin
    rxd_data <= 1'b0;
    rxd_flag_r <= 1'b0;
    rxd_state <= 1'b0;
    end
    else if(sck_p && !CS_N)
    begin
    case(rxd_state)
    3'd0:begin
    rxd_data[7] <= MOSI;
    rxd_flag_r <= 1'b0; //reset rxd_flag
    rxd_state <= 3'd1;
    end
    3'd1:begin
    rxd_data[6] <= MOSI;
    rxd_state <= 3'd2;
    end
    3'd2:begin
    rxd_data[5] <= MOSI;
    rxd_state <= 3'd3;
    end
    3'd3:begin
    rxd_data[4] <= MOSI;
    rxd_state <= 3'd4;
    end
    3'd4:begin
    rxd_data[3] <= MOSI;
    rxd_state <= 3'd5;
    end
    3'd5:begin
    rxd_data[2] <= MOSI;
    rxd_state <= 3'd6;
    end
    3'd6:begin
    rxd_data[1] <= MOSI;
    rxd_state <= 3'd7;
    end
    3'd7:begin
    rxd_data[0] <= MOSI;
    rxd_flag_r <= 1'b1; //set rxd_flag
    rxd_state <= 3'd0;
    end
    default: ;
    endcase
    end
    end
    //--------------------capture spi_flag posedge--------------------------------
    reg rxd_flag_r0,rxd_flag_r1;
    always@(posedge clk or negedge rst_n)
    begin
    if(!rst_n)
    begin
    rxd_flag_r0 <= 1'b0;
    rxd_flag_r1 <= 1'b0;
    end
    else
    begin
    rxd_flag_r0 <= rxd_flag_r;
    rxd_flag_r1 <= rxd_flag_r0;
    end
    end
    assign rxd_flag = (~rxd_flag_r1 & rxd_flag_r0)? 1'b1:1'b0;
    //---------------------spi_slaver send data---------------------------
    reg [2:0] txd_state;
    always@(posedge clk or negedge rst_n)
    begin
    if(!rst_n)
    begin
    txd_state <= 1'b0;
    end
    else if(sck_n && !CS_N)
    begin
    case(txd_state)
    3'd0:begin
    MISO <= txd_data[7];
    txd_state <= 3'd1;
    end
    3'd1:begin
    MISO <= txd_data[6];
    txd_state <= 3'd2;
    end
    3'd2:begin
    MISO <= txd_data[5];
    txd_state <= 3'd3;
    end
    3'd3:begin
    MISO <= txd_data[4];
    txd_state <= 3'd4;
    end
    3'd4:begin
    MISO <= txd_data[3];
    txd_state <= 3'd5;
    end
    3'd5:begin
    MISO <= txd_data[2];
    txd_state <= 3'd6;
    end
    3'd6:begin
    MISO <= txd_data[1];
    txd_state <= 3'd7;
    end
    3'd7:begin
    MISO <= txd_data[0];
    txd_state <= 3'd0;
    end
    default: ;
    endcase
    end
    end
    always@(posedge clk or negedge rst_n)
    if(rst_n == 1'b0)
    	spi_cnt <= 8'd0;
    else if(rxd_flag == 1'b1)
    	spi_cnt <= spi_cnt + 1'b1;
    always@(posedge clk or negedge rst_n)
    if(rst_n == 1'b0)
    	led <= 1'b0;
    else if(rxd_data == 8'd123)
    	led <= 1'b1;
    always@(posedge clk or negedge rst_n)
    if(rst_n == 1'b0)
    	led1 <= 1'b0;
    else if(rxd_data == 8'd245 && spi_cnt == 8'd2)
    	led1 <= 1'b1;
    endmodule
    

    实验结果

    STM32依次给FPGA发送数据123、245并接收FPGA发送过来的数据,通过串口打印出;FPGA给STM32发送数据8’b0001_1000。
    FPGA接收的数据为123,则点亮led0灯;如果FPGA第二次接收到的数据为245,则点亮led1灯。
    实验结果如图所示:
    STM32的串口打印信息:

    本实验使用的FPGA开发板是基于 Xilinx 公司的 Spartan6 系列 FPGA,型号为 XC6SLX9。

    物联沃分享整理
    物联沃-IOTWORD物联网 » STM32与FPGA实现SPI通讯的研究

    发表评论