STM32与FPGA之间的SPI通讯

2023-11-09

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。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

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

  • ubuntu 下安装chrome浏览器

    1 将google chrome stable current amd64软件复制移动到家目录下 2 打开终端 路径在家目录下 3 依次运行下面三条命令 sudo apt get install google chrome stable s
  • Swift的几种传值方式

    传值方式 在进行页面跳转过程中无法避免需要进行值的传递 那么值的传递可以分为正向传值和反向传值 例如在SourceViewController跳转至DestinationViewController的过程中需把前者的属性值传递给后者称为正向

随机推荐

  • 【系列 1】手写vue响应式原理

    手写vue响应式原理 首先我们看看原生 vue 做了什么 可见 vm 第一层与 data 内都能获取到 data 数据 并且其数值都进行了 ge
  • 王道训练营-C语言-1

    1 字符 include
  • 【热门框架】Maven中聚合,继承指的是什么?有什么作用?

    Maven中的聚合和继承是两个重要的功能 用于管理多个项目的共同部分 1 聚合 Maven中的聚合 Aggregation 指的是将多个子项目聚合成一个父项目的过程 聚合的语法如下 xml
  • 数据库初探(1)————关于InnoDB和MyISAM两种数据库存储引擎

    1 mysql中最常见的两种数据库引擎 InnoDB存储引擎 InnoDB存储引擎是Mysql的默认事务引擎 也是最重要 使用最广泛的存储引擎 它被设计用来处理大量的短期事务 短期事务大部分情况下都是可以正常提交的 很少回滚 MyISAM存
  • 【超详解】JavaWeb三大组件讲解

    文章目录 前言 一 Servlet 二 Filter 三 Listener 总结 前言 JavaWeb三大组件指的是 Servlet Filter Listener 三者提供不同的功能 然而很多人可能只用过其中一个或者两个 Servlet
  • 创建React项目

    在开发React项目前最关键的当然是项目的创建 现在的前端工程化使得前端项目的创建也变得越来越复杂 在这里介绍三种从零开始创建React项目的方式 分别是在浏览器中直接引入 使用官方脚手架create react app 使用Webpack
  • 不会盗QQ,还当什么程序员?

    上面这个段子估计很多朋友都看过 程序员被黑过无数次 在其他人眼中 仿佛我们需要写得了木马 翻得了围墙 修得了电脑 找得到资源 但凡是跟计算机沾点边的 咱都得会才行 段子归段子 言归正传 对于咱们程序员来说 多多少少了解一些信息安全的技术知识
  • 打印HashMap的方法分享

    HashMap简介 Hash Map是哈希表基于 Map 接口的实现类 HashMap用于存储数据 允许使用null值和null键 除了非同步和允许使用 null 之外 HashMap 类与 Hashtable 大致相同 HashMap不保
  • 区块链三加一:什么是量化交易

    量化交易是指以先进的数学模型替代人为的主观判断 利用计算机技术从庞大的历史数据中海选能带来超额收益的多种 大概率 事件以制定策略 极大地减少了投资者情绪波动的影响 避免在市场极度狂热或悲观的情况下作出非理性的投资决策 量化交易 有时候也称自
  • Kali Linux Armitage生成被控端和主控端

    目录 说明 使用 Armitage生成被控端和主控端 说明 按照 Kali Linux2 网络渗透测试实践指南 第二版 第八章操作 仅供学习讨论使用 请勿进行非法操作 使用 Armitage生成被控端和主控端 选中 payload 然后选择
  • 深入解析锂电池保护电路工作原理

    1 锂离子电池介绍 锂离子电池是一种二次电池 充电电池 它主要依靠锂离子在正极和负极之间移动来工作 在充放电过程中 Li 在两个电极之间往返嵌入和脱嵌 充电时 Li 从正极脱嵌 经过电解质嵌入负极 负极处于富锂状态 放电时则相反 锂离子电池
  • 对象不支持“addEventListener”属性或方法 ie8 jquery

    解决方法 1 请查看你使用的jquery版本 2 jQuery 2 x 已经不支持IE9以下的IE浏览器 如果你想继续支持IE6 7 8 请使用jQuery 1 x版本 最新版本 jQuery 1 11 0 3 如果要兼容 IE 6 7 8
  • 假设检验/T检验/F检验/Z检验/卡方检验

    显著性水平 一个概率值 原假设为真时 拒绝原假设的概率 表示为 alpha 常用取值为0 01 0 05 0 10 什么是P值 p值是当原假设为真时样本观察结果及更极端结果出现的概率 如果P值很小 说明这种情况发生的概率很小 如果这种情况还
  • react面试题(三)

    1 setState 何时同步何时异步 1 setState 只在合成事件 react为了解决跨平台 兼容性问题 自己封装了一套事件机制 代理了原生的事件 像在jsx中常见的onClick onChange这些都是合成事件 和钩子函数 生命
  • hive遇到的错误

    1 数据库的命名不能用数字开头 0 jdbc hive2 192 168 171 151 10000 gt create database 0328 不区分大小写字母 Error Error while compiling statemen
  • 离散数学期末复习-求主范式

    文章目录 析取范式与合取范式 定义 简单析取式 析取式 简单合取式 合取式 析取范式 合取范式 范式 公式A的析取范式 公式A的合取范式 命题公式的范式 求公式A的范式的步骤 求公式的范式举例 极大项与极小项 定义 主析取范式与主合取范式
  • 银河麒麟操作系统(kylin os)学习

    poweroff 关机命令 sudo su 进入超级用户 su username 切普通用户 Ctrl Alt t 打开终端terminal窗口 Ctrl Alt d 最小化全部应用 然后显示桌面 dpkg i libqt5 deb 安装d
  • 安卓app与阿里云服务器的无线通信(非局域网)

    参考 安卓app与阿里云服务器的无线通信 非局域网 作者 图触靓 发布时间 2020 08 01 16 13 14 网址 https blog csdn net bhbhhyg article details 107732156 目录 写在
  • react学习笔记9:循环处理和数组map函数

    为什么80 的码农都做不了架构师 gt gt gt 在我们实际开发中 循环是必不可少的 尤其在表格中的数据显示 我们会把ajax的json数据显示在表格中 1 一个最简单的例子 import React from react import
  • STM32与FPGA之间的SPI通讯

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