草庐IT

STM32与FPGA之间的SPI通讯

涛涛呐~ 2023-06-08 原文

STM32与FPGA之间的SPI通讯

SPI通讯协议

SPI协议物理层

SPI协议是一种高速全双工的通信总线。SPI设备之间的连接方式如图所示:

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

协议层

下图就是SPI通讯的通讯时序:
1)采样时刻,MISO与MOSI的数据才有效,高电平表示为“1”,低电平表示为“0”。
2)通讯的起始信号:片选信号由高变低;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-------------------------------
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。

有关STM32与FPGA之间的SPI通讯的更多相关文章

  1. ruby-on-rails - Rails 应用程序之间的通信 - 2

    我构建了两个需要相互通信和发送文件的Rails应用程序。例如,一个Rails应用程序会发送请求以查看其他应用程序数据库中的表。然后另一个应用程序将呈现该表的json并将其发回。我还希望一个应用程序将存储在其公共(public)目录中的文本文件发送到另一个应用程序的公共(public)目录。我从来没有做过这样的事情,所以我什至不知道从哪里开始。任何帮助,将不胜感激。谢谢! 最佳答案 无论Rails是什么,几乎所有Web应用程序都有您的要求,大多数现代Web应用程序都需要相互通信。但是有一个小小的理解需要你坚持下去,网站不应直接访问彼此

  2. ruby - #之间? Cooper 的 *Beginning Ruby* 中的错误或异常 - 2

    在Cooper的书BeginningRuby中,第166页有一个我无法重现的示例。classSongincludeComparableattr_accessor:lengthdef(other)@lengthother.lengthenddefinitialize(song_name,length)@song_name=song_name@length=lengthendenda=Song.new('Rockaroundtheclock',143)b=Song.new('BohemianRhapsody',544)c=Song.new('MinuteWaltz',60)a.betwee

  3. ruby-on-rails - `a ||= b` 和 `a = b if a.nil 之间的区别? - 2

    我正在检查一个Rails项目。在ERubyHTML模板页面上,我看到了这样几行:我不明白为什么不这样写:在这种情况下,||=和ifnil?有什么区别? 最佳答案 在这种特殊情况下没有区别,但可能是出于习惯。每当我看到nil?被使用时,它几乎总是使用不当。在Ruby中,很少有东西在逻辑上是假的,只有文字false和nil是。这意味着像if(!x.nil?)这样的代码几乎总是更好地表示为if(x)除非期望x可能是文字false。我会将其切换为||=false,因为它具有相同的结果,但这在很大程度上取决于偏好。唯一的缺点是赋值会在每次运行

  4. STM32读取串口传感器数据(颗粒物传感器,主动上传) - 2

    文章目录1.开发板选择*用到的资源2.串口通信(个人理解)3.代码分析(注释比较详细)1.主函数2.串口1配置3.串口2配置以及中断函数4.注意问题5.源码链接1.开发板选择我用的是STM32F103RCT6的板子,不过代码大概在F103系列的板子上都可以运行,我试过在野火103的霸道板上也可以,主要看一下串口对应的引脚一不一样就行了,不一样的就更改一下。*用到的资源keil5软件这里用到了两个串口资源,采集数据一个,串口通信一个,板子对应引脚如下:串口1,TX:PA9,RX:PA10串口2,TX:PA2,RX:PA32.串口通信(个人理解)我就从串口采集传感器数据这个过程说一下我自己的理解,

  5. SPI接收数据异常问题总结 - 2

    SPI接收数据左移一位问题目录SPI接收数据左移一位问题一、问题描述二、问题分析三、探究原理四、经验总结最近在工作在学习调试SPI的过程中遇到一个问题——接收数据整体向左移了一位(1bit)。SPI数据收发是数据交换,因此接收数据时从第二个字节开始才是有效数据,也就是数据整体向右移一个字节(1byte)。请教前辈之后也没有得到解决,通过在网上查阅前人经验终于解决问题,所以写一个避坑经验总结。实际背景:MCU与一款芯片使用spi通信,MCU作为主机,芯片作为从机。这款芯片采用的是它规定的六线SPI,多了两根线:RDY和INT,这样从机就可以主动请求主机给主机发送数据了。一、问题描述根据从机芯片手

  6. [工业相机] 分辨率、精度和公差之间的关系 - 2

    📢博客主页:https://blog.csdn.net/weixin_43197380📢欢迎点赞👍收藏⭐留言📝如有错误敬请指正!📢本文由Loewen丶原创,首发于CSDN,转载注明出处🙉📢现在的付出,都会是一种沉淀,只为让你成为更好的人✨文章预览:一.分辨率(Resolution)1、工业相机的分辨率是如何定义的?2、工业相机的分辨率是如何选择的?二.精度(Accuracy)1、像素精度(PixelAccuracy)2、定位精度和重复定位精度(RepeatPrecision)三.公差(Tolerance)四.课后作业(Post-ClassExercises)视觉行业的初学者,甚至是做了1~2年

  7. ruby - 无法理解 `puts{}.class` 和 `puts({}.class)` 之间的区别 - 2

    由于匿名block和散列block看起来大致相同。我正在玩它。我做了一些严肃的观察,如下所示:{}.class#=>Hash好的,这很酷。空block被视为Hash。print{}.class#=>NilClassputs{}.class#=>NilClass为什么上面的代码和NilClass一样,下面的代码又显示了Hash?puts({}.class)#Hash#=>nilprint({}.class)#Hash=>nil谁能帮我理解上面发生了什么?我完全不同意@Lindydancer的观点你如何解释下面几行:print{}.class#NilClassprint[].class#A

  8. ruby - 在模块/类之间共享全局记录器 - 2

    在许多ruby​​类之间共享记录器实例的最佳(正确)方法是什么?现在我只是将记录器创建为全局$logger=Logger.new变量,但我觉得有更好的方法可以在不使用全局变量的情况下执行此操作。如果我有以下内容:moduleFooclassAclassBclassC...classZend在所有类之间共享记录器实例的最佳方式是什么?我是以某种方式在Foo模块中声明/创建记录器还是只是使用全局$logger没问题? 最佳答案 在模块中添加常量:moduleFooLogger=Logger.newclassAclassBclassC..

  9. ruby - 在两个 ActiveRecord 类之间合并/复制属性的好方法? - 2

    之前有人问过这个问题,我发现了以下clip关于如何一次设置一个类对象的所有属性,但由于批量分配保护,这在Rails中是不可能的。(例如,您不能Object.attributes={})有没有一种很好的方法可以将一个类的属性合并到另一个类中?object1.attributes=object2.attributes.inject({}){|h,(k,v)|h[k]=vifObjectModel.column_names.include?(k);h}谢谢。 最佳答案 利用assign_attributes使用:without_prote

  10. ruby-on-rails - 2个用户之间的产品订单 - 2

    我有三个模型:User、Product、Offer以及这些模型之间的关系问题。场景:用户1发布了一个产品用户2可以向用户1发送报价,例如10美元用户1可以接受或拒绝提议我现在的问题是:用户、产品和报价之间的正确关系是什么?我如何处理那些“接受或拒绝”操作?是否有更好的解决方案?用户模型:classUser:productsend产品型号:classProduct:usersend提供模型:classOffer提前致谢:)编辑:我正在使用Rails3.2.8 最佳答案 警告:小小说来了第1部分:设置关联我建议阅读Railsguideo

随机推荐