C8T6--SPI读FLASH和双通信
C8T6–SPI读取FLASH和双通信
本小节以一种使用 SPI 通讯的串行 FLASH 存储芯片的读写实验为大家讲解 STM32 的 SPI 使用方法。实验中 STM32 的 SPI 外设采用主模式,通过查询事件的方式来确保正常通讯
大纲
- SPI读取FLASH
- 双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_SECTOR0x20
#define READ_STATUS0x05
#define READ_DATA0X03
#defineWRITE_ENABLE0x06
#define WRITE_DATA0x02
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_tSPI1_ReadWriteByte(uint8_tTxData);
uint8_tSPI2_ReadWriteByte(uint8_tTxData);
#endif
SPI1初始化
下面是代码:
// SPI1初始化
void bsp_SPI1_Init(void)
{
// 结构体声明
GPIO_InitTypeDefGPIO_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_tSPI1_ReadWriteByte(uint8_tTxData)
{
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)
{
return0;
}
}
// 当接收数据完成时,这个时候返回接收到的数据
returnSPI_I2S_ReceiveData(SPI1);
}
这块和上面的发送数据代码是一样的,这里就不进行过多赘述
SPI2初始化
// SPI2初始化
void bsp_SPI2_Init(void)
{
// 结构体声明
GPIO_InitTypeDefGPIO_InitStructure;
SPI_InitTypeDef SPI_InitStructure;
NVIC_InitTypeDefNVIC_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_tSPI2_ReadWriteByte(uint8_tTxData)
{
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)
{
return0;
}
}
// 当接收数据完成时,这个时候返回接收到的数据
returnSPI_I2S_ReceiveData(SPI2);
}
原文地址:https://blog.csdn.net/2403_82385265/article/details/142314079
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!