当前位置: 首页 > news >正文

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			0x02void 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,SCKGPIO_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引脚,来关闭SPIreturn 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);// 重置timetime = 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);// 重置timetime = 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.mrgr.cn/news/30550.html

相关文章:

  • C++20 概念与约束(1)—— SFINAE
  • 数字IC实践项目(10)—基于System Verilog的DDR4 Model/Tb 及基础Verification IP的设计与验证(付费项目)
  • Scaling Laws终结,量化无用,AI大佬都在审视这篇论文
  • 万字长文解读深度学习——卷积神经网络CNN
  • XXL JOB DockerCompose部署
  • WebGIS四大地图框架:Leaflet、OpenLayers、Mapbox、Cesium
  • 目前人工智能时代,程序员如何保持核心竞争力?
  • 项目小总结
  • Web开发:ABP框架2——入门级别的增删改查Demo
  • C++ Primer Plus(速记版)-类和数据抽象
  • LeetCode 876
  • Mysql中DML和DQL
  • 代码随想录八股训练营第四十天| C++
  • Activiti7《第二式:破剑式》——工作流中的以柔克刚
  • 注册商标为什么要一定找代理机构?
  • 【C++ Primer Plus习题】16.9
  • C++日期类,详细!!!
  • HTML基础和常用标签
  • pg入门2—pg中的database和schema有什么区别
  • 【资料分析】刷题日记2
  • 图书管理系统(面向对象的编程练习)
  • 【STL】priority_queue 基础,应用与操作
  • VirtualBox增加磁盘并给docker用
  • shell常用命令
  • Qwen 2.5:阿里巴巴集团的新一代大型语言模型
  • 二进制补码及与原码的互相转换方法-成都仪器定制