C8T6--SPI读FLASH和双通信

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);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

挽天技术

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值