STM32与FPGA之间的SPI通讯

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模式CPOLCPHA空闲时SCK时钟采样时刻
000低电平奇数边沿
101低电平偶数边沿
210低电平奇数边沿
311低电平偶数边沿

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-------------------------------
//SPI作为从机设备,读取主机STM32发送过来的数据,上升沿采样
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,8位数据接收完,标志位设为1
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。

  • 18
    点赞
  • 191
    收藏
    觉得还不错? 一键收藏
  • 28
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 28
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值