C8T6--SPI读FLASH和双通信

news2024/9/20 2:11:27

C8T6–SPI读取FLASH和双通信

本小节以一种使用 SPI 通讯的串行 FLASH 存储芯片的读写实验为大家讲解 STM32 的 SPI 使用方法。实验中 STM32 的 SPI 外设采用主模式,通过查询事件的方式来确保正常通讯

大纲

  1. SPI读取FLASH
  2. 双SPI接口进行主从相互通信

具体案例

SPI读取FLASH

硬件介绍

在这里插入图片描述
本实验板中的 FLASH 芯片 (型号:W25Q64) 是一种使用 SPI 通讯协议的 NOR FLASH 存储器,它的 CS/CLK/DIO/DO 引脚分别连接到了 STM32 对应的 SPI 引脚 NSS/SCK/MOSI/MISO 上,其中STM32 的 NSS 引脚是一个普通的 GPIO,不是 SPI 的专用 NSS 引脚,所以程序中我们要使用软件控制的方式

FLASH 芯片中还有 WP 和 HOLD 引脚。WP 引脚可控制写保护功能,当该引脚为低电平时,禁止写入数据。我们直接接电源,不使用写保护功能。HOLD 引脚可用于暂停通讯,该引脚为低电平时,通讯暂停,数据输出引脚输出高阻抗状态,时钟和数据输入引脚无效。我们直接接电源,不使用通讯暂停功能

代码

SPI_FLASH.H
#ifndef __BSP_SPI_FLASH_H
#define	__BSP_SPI_FLASH_H


#include "stm32f10x.h"


/**************************SPI参数定义********************************/
#define      FLASH_SPIx                        SPI1
#define      FLASH_SPI_APBxClock_FUN          RCC_APB2PeriphClockCmd
#define      FLASH_SPI_CLK                     RCC_APB2Periph_SPI1

//CS(NSS)引脚 片选选普通GPIO即可
#define      FLASH_SPI_CS_APBxClock_FUN       RCC_APB2PeriphClockCmd
#define      FLASH_SPI_CS_CLK                  RCC_APB2Periph_GPIOC    
#define      FLASH_SPI_CS_PORT                 GPIOA
#define      FLASH_SPI_CS_PIN                  GPIO_Pin_4

//SCK引脚
#define      FLASH_SPI_SCK_APBxClock_FUN      RCC_APB2PeriphClockCmd
#define      FLASH_SPI_SCK_CLK                 RCC_APB2Periph_GPIOA   
#define      FLASH_SPI_SCK_PORT                GPIOA   
#define      FLASH_SPI_SCK_PIN                 GPIO_Pin_5
//MISO引脚
#define      FLASH_SPI_MISO_APBxClock_FUN     RCC_APB2PeriphClockCmd
#define      FLASH_SPI_MISO_CLK                RCC_APB2Periph_GPIOA    
#define      FLASH_SPI_MISO_PORT               GPIOA 
#define      FLASH_SPI_MISO_PIN                GPIO_Pin_6
//MOSI引脚
#define      FLASH_SPI_MOSI_APBxClock_FUN     RCC_APB2PeriphClockCmd
#define      FLASH_SPI_MOSI_CLK                RCC_APB2Periph_GPIOA    
#define      FLASH_SPI_MOSI_PORT               GPIOA 
#define      FLASH_SPI_MOSI_PIN                GPIO_Pin_7

#define  		FLASH_SPI_CS_LOW()     						GPIO_ResetBits( FLASH_SPI_CS_PORT, FLASH_SPI_CS_PIN )
#define  		FLASH_SPI_CS_HIGH()    						GPIO_SetBits( FLASH_SPI_CS_PORT, FLASH_SPI_CS_PIN )

#define DUMMY 					0x00
#define READ_JEDEC_ID   0x9F
#define ERASE_SECTOR		0x20
#define READ_STATUS			0x05
#define READ_DATA				0X03
#define	WRITE_ENABLE		0x06
#define WRITE_DATA			0x02

void SPI_FLASH_Init();
uint32_t SPI_Read_ID(void);
uint8_t SPI_FLASH_Read_Byte(void);
void SPI_Erase_Sector(uint32_t addr);
void SPI_WaitForWriteEnd(void);
void SPI_Read_Data(uint32_t addr,uint8_t *readBuff,uint32_t numByteToRead);
void SPI_Write_Data(uint32_t addr,uint8_t *writeBuff,uint32_t numByteToWrite);


/*等待超时时间*/
#define SPIT_FLAG_TIMEOUT         ((uint32_t)0x1000)
#define SPIT_LONG_TIMEOUT         ((uint32_t)(10 * SPIT_FLAG_TIMEOUT))


/*信息输出*/
#define FLASH_DEBUG_ON         0

#define FLASH_INFO(fmt,arg...)           printf("<<-FLASH-INFO->> "fmt"\n",##arg)
#define FLASH_ERROR(fmt,arg...)          printf("<<-FLASH-ERROR->> "fmt"\n",##arg)
#define FLASH_DEBUG(fmt,arg...)          do{\
                                          if(FLASH_DEBUG_ON)\
                                          printf("<<-FLASH-DEBUG->> [%d]"fmt"\n",__LINE__, ##arg);\
                                          }while(0)




#endif 

这里是对我们需要的信息进行宏定义

SPI_FLASH.C
先进行SPI各个端口的初始化
static void SPI_GPIO_Config(void)
{
  GPIO_InitTypeDef  GPIO_InitStructure; 

	// 使能SPI有关时钟
	FLASH_SPI_APBxClock_FUN(FLASH_SPI_CLK,ENABLE);
	FLASH_SPI_CS_APBxClock_FUN ( FLASH_SPI_CS_CLK|FLASH_SPI_SCK_CLK|
								FLASH_SPI_MISO_PIN|FLASH_SPI_MOSI_PIN, ENABLE );
	
	// 初始化MISO,MOSI,SCK
	GPIO_InitStructure.GPIO_Pin = FLASH_SPI_SCK_PIN;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(FLASH_SPI_SCK_PORT,&GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Pin = FLASH_SPI_MOSI_PIN;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(FLASH_SPI_MOSI_PORT,&GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Pin = FLASH_SPI_MISO_PIN;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(FLASH_SPI_MISO_PORT,&GPIO_InitStructure);
	
	// 初始化CS引脚,使用软件控制,所以直接设置成推挽输出
	
	GPIO_InitStructure.GPIO_Pin = FLASH_SPI_CS_PIN;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(FLASH_SPI_CS_PORT,&GPIO_InitStructure);
	
	FLASH_SPI_CS_HIGH();
}

大概的流程是,先打开各个引脚的时钟,再配置每个引脚,最后设置初始电位
注意:我们对于CS引脚是采用软件控制的方式来进行控制的,通过GPIO的高低电位来实现SPI的打开和关闭

初始化SPI
// 初始化SPI
static void SPI_Mode_Config(void)
{
  SPI_InitTypeDef SPI_InitStructure;
	
  SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2;
	
	// 配置成模式三
	SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;
	SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;
	
	SPI_InitStructure.SPI_CRCPolynomial = 0;// 不使用CRC校验功能,数值随便写
	SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
	SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
	SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
	SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
	SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
	
	
	SPI_Init(FLASH_SPIx,&SPI_InitStructure);
	
	SPI_Cmd(FLASH_SPIx,ENABLE);// 使能SPI
}

这里主要是对SPI的结构体进行配置,如全双工,时钟极性,时钟相位,CRC,一次发送的数据位数,高低位进行发送,主从机模式的选择,软件控制CS引脚

调用的整个初始函数
void SPI_FLASH_Init()
{
	SPI_GPIO_Config();
	SPI_Mode_Config();
}

发送函数
// 发送一个字节
uint8_t SPI_FLASH_Send_Byte(uint8_t data)
{
	SPITimeout = SPIT_LONG_TIMEOUT; 
	
	// 检测发送缓冲区是否为空,不为空就等待
	while(SPI_I2S_GetFlagStatus(FLASH_SPIx,SPI_I2S_FLAG_TXE) == RESET)
	{
		if(SPITimeout-- == 0)
		{
		return SPI_TIMEOUT_UserCallback(0);
		}
	}
	
	// 到这,说明TX发送缓冲区已经为空
	// 发送数据
	SPI_I2S_SendData(FLASH_SPIx,data);
	
	SPITimeout = SPIT_LONG_TIMEOUT; // 重新置位等待时间
	
	
	/*
	为什么要通过检测接收缓存数据区的接收非空信号来判断发送是否完毕呢?
	
	因为TXE为1时,代表发送缓冲区为空,此时往里面写入数据,一但数据写入进去时,
	TXE就会立马置为0,所以不能通过TXE来判断是否发生完毕,因为SPI是同步发送的,
	而当RXNE为1时,代表接收缓冲区不为空,已经发送完毕了,具体可以看原理图
	*/
	
	// 这里是检测数据发送完毕没有,没发生完就等待
		while(SPI_I2S_GetFlagStatus(FLASH_SPIx,SPI_I2S_FLAG_RXNE) == RESET)
			{
		if(SPITimeout-- == 0)
		{
		return SPI_TIMEOUT_UserCallback(0);
		}
	}
	
	// 程序执行到此处,说明发送完毕
	
	// 此时数据已经写入DR数据区
	// 读出数据
	return SPI_I2S_ReceiveData(FLASH_SPIx);
}

这里我们主要是判断标志位,当正在发送时,即TXE为1时,我们才进行下一步的发送,否则会卡在循环内,这里使用了一个软件的计数,是为了防止标志位一直卡死,到最后程序卡死,然后判断RXNE是否为1,当RXNE为1时,代表接收区不为空,此时意味着这次的发送结束

接收函数
uint8_t SPI_FLASH_Read_Byte(void)
{
	return SPI_FLASH_Send_Byte(DUMMY);
}

这里本质上还是调用的发送函数,因为SPI是同步发送,所以如果我们要接收数据,还是需要先发送,才能使SPI开启接收

控制FLASH的指令

下面是需要与FLASH进行交互,进行交互需要FLASH控制的指令
在这里插入图片描述
该表中的第一列为指令名,第二列为指令编码,第三至第 N 列的具体内容根据指令的不同而有不同的含义。其中带括号的字节参数,方向为 FLASH 向主机传输,即命令响应,不带括号的则为主机向 FLASH 传输。表中“A0~A23”指 FLASH 芯片内部存储器组织的地址;“M00~M7”为厂商号(MANUFACTURERID);“ID0-ID15”为 FLASH 芯片的 ID;“dummy”指该处可为任意数据;“D0~D7”为 FLASH 内部存储矩阵的内容

在 FLSAH 芯片内部,存储有固定的厂商编号 (M7-M0) 和不同类型 FLASH 芯片独有的编号 (ID15-ID0)
在这里插入图片描述
通过指令表中的读 ID 指令“JEDEC ID”可以获取这两个编号,该指令编码为“9F h”,其中“9Fh”是指 16 进制数“9F”(相当于 C 语言中的 0x9F)。紧跟指令编码的三个字节分别为 FLASH 芯片输出的“(M7-M0)”、“(ID15-ID8)”及“(ID7-ID0)”

读取FLASH的ID函数

在这里插入图片描述
主机首先通过 MOSI 线向 FLASH 芯片发送第一个字节数据为“9F h”,当 FLASH 芯片收到该数据后,它会解读成主机向它发送了“JEDEC 指令”,然后它就作出该命令的响应:通过 MISO 线把它的厂商 ID(M7-M0) 及芯片类型 (ID15-0) 发送给主机,主机接收到指令响应后可进行校验。常见的应用是主机端通过读取设备 ID 来测试硬件是否连接正常,或用于识别设备

// 读取 FLASH 的 ID 号,来判断是否初始正常
uint32_t SPI_Read_ID(void)
{
	uint32_t flash_id;
	
		FLASH_SPI_CS_LOW();// 把CS引脚电平拉低,使SPI开始工作
	  SPI_FLASH_Send_Byte(READ_JEDEC_ID);// 发送读取 FLASH 的 ID 的指令
	  
		flash_id = SPI_FLASH_Send_Byte(DUMMY);
	
		flash_id <<= 8;
	/* 
	注意返回的三个ID每个都是一个字节
	而我们定义的 flash_id 是32位,即四个字节: 0x 00 00 00 00
	接收到第一个数据时,flash_id 变为 : 0x 00 00 00 ef
	此时我们向左移八位(即一个字节长度) 0x 00 00 00 ef -> 0x 00 00 ef 00
	然后用新得到的 flash_id 与 新接收的 ID 进行 | 操作,完成新的 ID 的写入
	0x 00 00 ef 00 -> 0x 00 00 ef 40
	*/
	
		flash_id |= SPI_FLASH_Send_Byte(DUMMY);
	
		flash_id <<= 8;
	
		flash_id |= SPI_FLASH_Send_Byte(DUMMY);
		
		FLASH_SPI_CS_HIGH(); // 记得拉高关闭CS引脚,来关闭SPI
		
		return flash_id;
}

先打开CS引脚,然后发送FLASH的读取ID指令(READ_JEDEC_ID),查看手册,需要发送DUMMY(空位)来进行获取返回ID。因为每次返回8位,我们要进行三次返回,我们用一个数据来接收保存读取的ID,每次返回8位,我们也需要往前移动8位,最后把CS引脚关闭

FLASH写入使能
//FLASH写入使能
void SPI_Write_Enable()
{
	FLASH_SPI_CS_LOW();
	SPI_FLASH_Send_Byte(WRITE_ENABLE);
	FLASH_SPI_CS_HIGH();
}

调用 SPI_FLASH_Send_Byte 函数发送 WRITE_ENABLE 指令来使能FLASH来完成对其的写入使能,由于 FLASH 芯片向内部存储矩阵写入数据需要消耗一定的时间,并不是在总线通讯结束的一瞬间完成的,所以在写操作后需要确认 FLASH 芯片“空闲”时才能进行再次写入。为了表示自己的工作状态,FLASH 芯片定义了一个状态寄存器
如下:
在这里插入图片描述
我们只关注这个状态寄存器的第 0 位“BUSY”,当这个位为“1”时,表明 FLASH 芯片处于忙碌状态,它可能正在对内部的存储矩阵进行“擦除”或“数据写入”的操作

利用指令表中的“Read Status Register”指令可以获取 FLASH 芯片状态寄存器的内容,其时序见下图(读取状态寄存器的时序 )
在这里插入图片描述

等待FLASH内部时序操作完成

只要向 FLASH 芯片发送了读状态寄存器的指令,FLASH 芯片就会持续向主机返回最新的状态寄存器内容,直到收到 SPI 通讯的停止信号。据此我们编写了具有等待 FLASH 芯片写入结束功能的函数,如下:

// 等待FLASH内部时序操作完成
void SPI_WaitForWriteEnd(void)
{
	uint8_t status_reg = 0;
	
	// 片选使能
	FLASH_SPI_CS_LOW();
	
	SPI_FLASH_Send_Byte(READ_STATUS);
	
	do
	{
		status_reg = SPI_FLASH_Send_Byte(DUMMY);
	}while((status_reg & 0x01) == 1);// 忙碌
	
}

这段代码发送读状态寄存器的指令编码“W25X_ReadStatusReg”后,在 while 循环里持续获取寄存器的内容并检验它的“WIP_Flag 标志”(即 BUSY 位),一直等待到该标志表示写入结束时才退出本函数,以便继续后面与 FLASH 芯片的数据通讯

其实简而言之,就是一直读取该程序执行时的状态位,进行循环判断,当不为BUSY时,代表这次操作完全完成,可以进入下一个步骤

擦去FLASH指定的扇区
// 擦除FLASH指定扇区
void SPI_Erase_Sector(uint32_t addr)
{
		SPI_Write_Enable();
   	FLASH_SPI_CS_LOW();// 把CS引脚电平拉低,使SPI开始工作
	  SPI_FLASH_Send_Byte(ERASE_SECTOR);// 发送读取 FLASH 的 ID 的指令
	  
		SPI_FLASH_Send_Byte((addr >> 16)&0xFF);
	
	
		SPI_FLASH_Send_Byte((addr >> 8)&0xFF);
	
	  SPI_FLASH_Send_Byte(addr & 0xFF);
		
		FLASH_SPI_CS_HIGH(); // 记得拉高关闭CS引脚,来关闭SPI
}

由于 FLASH 存储器的特性决定了它只能把原来为“1”的数据位改写成“0”,而原来为“0”的数据位不能直接改写为“1”。所以这里涉及到数据“擦除”的概念,在写入前,必须要对目标存储矩阵进行擦除操作,把矩阵中的数据位擦除为“1”,在数据写入的时候,如果要存储数据“1”,那就不修改存储矩阵,在要存储数据“0”时,才更改该位

通常,对存储矩阵擦除的基本操作单位都是多个字节进行,如本例子中的 FLASH 芯片支持“扇区擦除”、“块擦除”以及“整片擦除”
在这里插入图片描述
FLASH 芯片的最小擦除单位为扇区 (Sector),而一个块 (Block) 包含 16 个扇区,其内部存储矩阵分布见下图 FLASH 芯片的存储矩阵
在这里插入图片描述

虽说在一个扇区内的地址有些情况下可以代表把整个扇区清空,但是为了避免不必要的错误,我们一般都是取的首地址
在这里插入图片描述

扇区擦除指令的第一个字节为指令编码,紧接着发送的 3 个字节用于表示要擦除的存储矩阵地址。要注意的是在扇区擦除指令前,还需要先发送“写使能”指令,发送扇区擦除指令后,通过读取寄存器状态等待扇区擦除操作完毕

注意输入的地址要对齐到 4KB

读取N个字节
/ 读取N个字节
void SPI_Read_Data(uint32_t addr,uint8_t *readBuff,uint32_t numByteToRead)
{
		FLASH_SPI_CS_LOW();// 把CS引脚电平拉低,使SPI开始工作
	  SPI_FLASH_Send_Byte(READ_DATA);// 发送读取 FLASH 的 ID 的指令
	  
		SPI_FLASH_Send_Byte((addr >> 16)&0xFF);
	
	
		SPI_FLASH_Send_Byte((addr >> 8)&0xFF);
	
	  SPI_FLASH_Send_Byte(addr & 0xFF);
	
		while(numByteToRead--)
		{
			*readBuff = SPI_FLASH_Send_Byte(DUMMY);
			readBuff++;
		}
	
		SPI_WaitForWriteEnd();
		FLASH_SPI_CS_HIGH(); // 记得拉高关闭CS引脚,来关闭SPI
}

发送读取的指令后,把我们发送的地址位传入之后。使用移位符进行移位,之后发送空位来接收信息,最后关闭CS引脚

FLASH写入
// FLASH写入操作
void SPI_Write_Data(uint32_t addr,uint8_t *writeBuff,uint32_t numByteToWrite)
{
		SPI_Write_Enable();
		FLASH_SPI_CS_LOW();// 把CS引脚电平拉低,使SPI开始工作
	  SPI_FLASH_Send_Byte(WRITE_DATA);// 发送读取 FLASH 的 ID 的指令
	  
		SPI_FLASH_Send_Byte((addr >> 16)&0xFF);
	
	
		SPI_FLASH_Send_Byte((addr >> 8)&0xFF);
	
	  SPI_FLASH_Send_Byte(addr & 0xFF);
	
		while(numByteToWrite--)
		{
			SPI_FLASH_Send_Byte(*writeBuff);
			writeBuff++;
		}
		
		FLASH_SPI_CS_HIGH();
		SPI_WaitForWriteEnd();
	}

先进行写入使能,再拉低CS引脚,然后进行发送地址,最后进行写入数据,在写入数据时是一位位写入的,写入完成之后,进行关闭CS引脚,

双SPI接口进行主从相互通信

注意:要明确自己板子上的SPI接口,要实现对应的接口进行连接,这点和串口的连接方式不一样,因为我们是一个板子进行的连接,所以根据实际情况来判断是否需要使用杜姆线进行连接

BSP_SPI.H

#ifndef __BSP_SPI_H
#define __BSP_SPI_H

#include "stm32f10x.h"

void bsp_SPI1_Init(void);
void bsp_SPI2_Init(void);
uint8_t	SPI1_ReadWriteByte(uint8_t	TxData);
uint8_t	SPI2_ReadWriteByte(uint8_t	TxData);

#endif


SPI1初始化

下面是代码:

// SPI1初始化
void bsp_SPI1_Init(void)
{
		// 结构体声明
	GPIO_InitTypeDef		GPIO_InitStructure;
	SPI_InitTypeDef 		SPI_InitStructure;
	
	// 打开外设的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1,ENABLE);
	
	// 配置SPI的GPIO端口
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	// SPI的基本配置
	SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;// 设置SPI为双线双向全双工模式
	SPI_InitStructure.SPI_Mode = SPI_Mode_Master;//	设置SPI工作模式:设置为主机
	SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;//	设置SPI的数据大小,SPI发送接收8位帧结构
	SPI_InitStructure.SPI_CPOL	=	SPI_CPOL_High;// 串行同步时钟的空闲状态为高电平
	SPI_InitStructure.SPI_CPHA	=	SPI_CPHA_2Edge;//	串行同步时钟的第二个跳变(沿上升或下降)
	SPI_InitStructure.SPI_NSS	=	SPI_NSS_Soft;// 设置CS引脚为软件控制电压变化
	SPI_InitStructure.SPI_BaudRatePrescaler	=	SPI_BaudRatePrescaler_256;//	定义波特率的预分频组
	SPI_InitStructure.SPI_FirstBit	=	SPI_FirstBit_MSB;// 设置数据传输是从高位还是低位开始
	SPI_InitStructure.SPI_CRCPolynomial	=	7; // CRC值计算的多项式
	SPI_Init(SPI1,&SPI_InitStructure);	//	根据SPI_InitStruct中指定的参数初始化外设SPIx寄存器
	
	SPI_Cmd(SPI1,ENABLE);// 使能SPI外设
}

首先打开SPI1的时钟和要初始化的MISO,MOIS,CS,SCK这几个GPIO引脚的端口的时钟,然后对SPI的结构体进行配置,和上面差不多一样进行配置后,调用初始化函数进行初始化,然后进行使能
注意:这里把SPI1配置为的是主机模式

SPI1收发数据函数

//	SPI1完成发送接收数据
uint8_t	SPI1_ReadWriteByte(uint8_t	TxData)
{
	uint8_t time = 0;
	//	检查是发送缓冲区是否为空,并且在这里通过时间延迟防止程序卡死
	while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_TXE)	==	RESET)
	{
		time++;
		if(time > 200)
		{
			return 0;
		}
	}
	
	// 当发送缓冲区不为空时,这个时候调用发送函数,把数据发送
	SPI_I2S_SendData(SPI1,TxData);
	
	// 重置time
	time = 0;
	
	// 检测接收缓冲区是否已经不为空,并且在这里也通过time控制程序,防止卡死
	while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_RXNE)	==	RESET)
	{
		time++;
		if(time > 200)
		{
			return	0;
		}
	}
	
	// 当接收数据完成时,这个时候返回接收到的数据
	return	SPI_I2S_ReceiveData(SPI1);
}

这块和上面的发送数据代码是一样的,这里就不进行过多赘述

SPI2初始化

// SPI2初始化
void bsp_SPI2_Init(void)
{
		// 结构体声明
	GPIO_InitTypeDef		GPIO_InitStructure;
	SPI_InitTypeDef 		SPI_InitStructure;
	NVIC_InitTypeDef		NVIC_InitStructure;
	
	// 打开外设的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2,ENABLE);
	
	// 配置SPI的GPIO端口
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB,&GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB,&GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB,&GPIO_InitStructure);
	
	// SPI的基本配置
	SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;// 设置SPI为双线双向全双工模式
	SPI_InitStructure.SPI_Mode = SPI_Mode_Slave;//	设置SPI工作模式:这里SPI2设置为从机
	SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;//	设置SPI的数据大小,SPI发送接收8位帧结构
	SPI_InitStructure.SPI_CPOL	=	SPI_CPOL_High;// 串行同步时钟的空闲状态为高电平
	SPI_InitStructure.SPI_CPHA	=	SPI_CPHA_2Edge;//	串行同步时钟的第二个跳变(沿上升或下降)
	SPI_InitStructure.SPI_NSS	=	SPI_NSS_Soft;// 设置CS引脚为软件控制电压变化
	SPI_InitStructure.SPI_BaudRatePrescaler	=	SPI_BaudRatePrescaler_256;//	定义波特率的预分频组
	SPI_InitStructure.SPI_FirstBit	=	SPI_FirstBit_MSB;// 设置数据传输是从高位还是低位开始
	SPI_InitStructure.SPI_CRCPolynomial	=	7; // CRC值计算的多项式
	SPI_Init(SPI2,&SPI_InitStructure);	//	根据SPI_InitStruct中指定的参数初始化外设SPIx寄存器
	
	SPI_I2S_ITConfig(SPI2,SPI_I2S_IT_RXNE,ENABLE);// 使能接收中断
	
	SPI_Cmd(SPI2,ENABLE);// 使能SPI外设
	
	
	// 完成中断的配置
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//	设置中断优先级分组
	
	NVIC_InitStructure.NVIC_IRQChannel	=	SPI2_IRQn;// 设置中断源
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority	=	1;// 设置抢占优先级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority	=	3;// 设置子优先级
	NVIC_InitStructure.NVIC_IRQChannelCmd	=	ENABLE;// 使能IRQ通道
	NVIC_Init(&NVIC_InitStructure);	// 初始化中断的配置
}

和上面SPI1的初始化是一样的流程,只不过注意这里配置的是从机,因为要实现主从机通信,其次,这里进行了中断的配置

SPI2收发函数

//	SPI2完成发送接收数据
uint8_t	SPI2_ReadWriteByte(uint8_t	TxData)
{
	uint8_t time = 0;
	//	检查是发送缓冲区是否为空,并且在这里通过时间延迟防止程序卡死
	while(SPI_I2S_GetFlagStatus(SPI2,SPI_I2S_FLAG_TXE)	==	RESET)
	{
		time++;
		if(time > 200)
		{
			return 0;
		}
	}
	
	// 当发送缓冲区不为空时,这个时候调用发送函数,把数据发送
	SPI_I2S_SendData(SPI2,TxData);
	
	// 重置time
	time = 0;
	
	// 检测接收缓冲区是否已经不为空,并且在这里也通过time控制程序,防止卡死
	while(SPI_I2S_GetFlagStatus(SPI2,SPI_I2S_FLAG_RXNE)	==	RESET)
	{
		time++;
		if(time > 200)
		{
			return	0;
		}
	}
	
	// 当接收数据完成时,这个时候返回接收到的数据
	return	SPI_I2S_ReceiveData(SPI2);
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2147601.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SSC377/D, 5M30 64/128MB, 1Tops1. 支持双摄,甚至三摄;2. 夜视全彩;3. 省内存、省带宽;4. 算力较大,适合新的算法模型;

 High Performance Processor Core  ARM Cortex-A35  Clock rate up to 1.0 GHz  Neon and FPU  Memory Management Unit for Linux support  DMA Engine  Image/Video Processor  Supports 8/10/12-bit parallel interface for raw data inpu…

【算法基础实验】图论-BellmanFord最短路径

理论知识 Bellman-Ford 和 Dijkstra 是两种用于计算加权图中最短路径的算法&#xff0c;它们在多个方面存在不同之处。下面是它们之间的主要区别&#xff1a; 1. 边权重的处理 Bellman-Ford&#xff1a; 能够处理带有负权重边的图&#xff0c;且可以检测负权重环&#xff08…

chapter16-坦克大战【1】——(自定义泛型)——day21

目录 569-坦克大战介绍 570-JAVA坐标体系 571-绘图入门和机制 572-绘图方法 573-绘制坦克游戏区域 574-绘制坦克 575-小球移动案例 576-事件处理机制 569-坦克大战介绍 570-JAVA坐标体系 571-绘图入门和机制 572-绘图方法 573-绘制坦克游戏区域 574-绘制坦克 575-小球移…

硬件工程师笔试面试——保险丝

目录 10、保险丝 10.1 基础 保险丝原理图 保险丝实物图 10.1.1 概念 10.1.2 保险丝的工作原理 10.1.3 保险丝的主要类型 10.1.4 保险丝的选择和使用注意事项 10.2 相关问题 10.2.1 保险丝的额定电流和额定电压是如何确定的? 10.2.2 保险丝的熔断速度对电路保护有何…

二进制补码及与原码的互相转换方法-成都仪器定制

大沙把一些基础的知识说清楚&#xff0c;本文介绍二进制补码及与原码的转换方法。 先说原码&#xff0c;原码‌是一种计算机中对数字的二进制定点表示方法。在原码表示法中&#xff0c;数值前面增加了一位符号位&#xff0c;最高位为符号位&#xff0c;0表示正数&#xff0c;1表…

keil调试变量值被篡改问题

今天遇到一个代码中变量值被篡改的问题&#xff0c;某个数组的第一个值运行一段时间之后变成了0&#xff0c;如图&#xff1a; 看现象基本可以断定是内存越界导致的&#xff0c;但是要如果定位是哪里内存越界呢? keil提供了两个工具 1、set access breakpoint at(设置访问断点…

项目小总结

这段时间主要把大概的开发流程了解完毕 修改了&#xff0c;并画了几个界面 一.界面 修改为 博客主页 个人中心 二.前后端分离开发 写前端时 就可以假设拿到这些数据了 const blogData2 {blog:{id:1,title: "如何编程飞人",author_id: 1,content: "这是一篇…

数据结构之二叉树遍历

二叉树的遍历 先序遍历 先输入父节点&#xff0c;再遍历左子树和右子树&#xff1a;A、B、D、E、C、F、G 中序遍历 先遍历左子树&#xff0c;再输出父节点&#xff0c;再遍历右子树&#xff1a;D、B、E、A、F、C、G 后序遍历 先遍历左子树&#xff0c;再遍历右子树&#xff0c;…

爬虫框架之Scrapy介绍——高效方便

# 近年来大数据分析、数据可视化和python等课程逐渐在大学各个学科中铺展开来&#xff0c;这样一来爬虫在平时小作业和期中、期末报告中出现的频率也逐渐变高。那么单一的使用requests库&#xff0c;自己从头到尾的的设计&#xff0c;考虑数据提取、线程管理和数据存储等方方面…

微服务架构详解

微服务与SOA概述 SOA历史 SOA示例 微服务历史 SOA 被抛弃了么? 微服务与 SOA 剖析 SOA 架构剖析 ESB就是一个一个微服务的功能 ESB 功能举例 对象转换还有逻辑转换 很多东西都要在ESB里面处理 微服务剖析 把一个单体结构拆分多个小服务。为了让小服务之间通信方便&#x…

用AI的智慧,传递感恩之心——GPT-4o助力教师节祝福

随着科技的飞速发展&#xff0c;人工智能在我们生活中的应用日益广泛。在这个教师节&#xff0c;不仅可以用传统的方式表达对老师的感恩之情&#xff0c;还可以借助OpenAI最新推出的GPT-4o模型&#xff0c;生成独特而温暖的祝福语和精美海报&#xff0c;让我们的感恩显得更加与…

Renesas R7FA8D1BH (Cortex®-M85)的UART使用介绍

目录 概述 1 软硬件 1.1 软硬件环境信息 1.2 开发板信息 1.3 调试器信息 2 FSP配置UART 2.1 配置参数 2.2 UART模块介绍 3 接口函数介绍 3.1 R_SCI_B_UART_Open() 3.2 R_SCI_B_UART_Close() 3.3 R_SCI_B_UART_Read() 3.4 R_SCI_B_UART_Write() 3.5 R_SCI_B_UAR…

【iOS】——JSONModel源码

JSONModel用法 基本用法 将传入的字典转换成模型&#xff1a; 首先定义模型类&#xff1a; interface Person : JSONModel property (nonatomic, copy) NSString *name; property (nonatomic, copy) NSString *sex; property (nonatomic, assign) NSInteger age; end接…

Java 23 的12 个新特性!!

Java 23 来啦&#xff01;和 Java 22 一样&#xff0c;这也是一个非 LTS&#xff08;长期支持&#xff09;版本&#xff0c;Oracle 仅提供六个月的支持。下一个长期支持版是 Java 25&#xff0c;预计明年 9 月份发布。 Java 23 一共有 12 个新特性&#xff01; 有同学表示&…

Qwen 2.5:阿里巴巴集团的新一代大型语言模型

Qwen 2.5&#xff1a;阿里巴巴集团的新一代大型语言模型 摘要&#xff1a; 在人工智能领域&#xff0c;大型语言模型&#xff08;LLMs&#xff09;的发展日新月异&#xff0c;它们在自然语言处理&#xff08;NLP&#xff09;和多模态任务中扮演着越来越重要的角色。阿里巴巴集…

「数据科学」清洗数据,使用Python语言处理数据集中的重复值

数据集中的重复值&#xff0c;产生的原因有很多&#xff0c;如果不进行处理的话&#xff0c;会对我们的后续分析过程&#xff0c;产生很大的影响。比如说&#xff0c;在统计汇总数据的时候&#xff0c;重复数据就会导致数据总数增多。要是重复数据多的话&#xff0c;会影响我们…

2024 go-zero社交项目实战

背景 一位商业大亨&#xff0c;他非常看好国内的社交产品赛道&#xff0c;想要造一款属于的社交产品&#xff0c;于是他找到了负责软件研发的小明。 小明跟张三一拍即合&#xff0c;小明决定跟张三大干一番。 社交产品MVP版本需求 MVP指&#xff1a;Minimum Viable Product&…

Java自定义集合-基于文件的泛型列表 LocalFileArrayList

Java实现基于文件的泛型列表 LocalFileArrayList 简介核心概念泛型文件操作实现细节构造函数读取和写入文件类型转换List 接口方法实现总结调用示例完整代码简介 LocalFileArrayList我自己随便起的,没怎么思考,不一定是最适合的名字。搞这东西主要是有些需求用到的数据量太大…

95分App引领年轻人省钱赚钱新风尚,闲置也能变宝藏

随着时代的发展&#xff0c;年轻一代的消费观念正经历着深刻的变革。他们不再盲目追求新品、奢侈品&#xff0c;而是喜欢上购买闲置物品来满足日常所需。在消费的同时&#xff0c;加入了卖家的行列。对自己拥有的闲置物品开启“断舍离”&#xff0c;纷纷在闲置平台进行售卖。这…

鸿蒙媒体开发系列05——音频并发播放管理与音量管理

如果你也对鸿蒙开发感兴趣&#xff0c;加入“Harmony自习室”吧&#xff01;扫描下方名片&#xff0c;关注公众号&#xff0c;公众号更新更快&#xff0c;同时也有更多学习资料和技术讨论群。 1、多音频播放的并发管理 多音频并发&#xff0c;即多个音频流同时播放。此场景下&…