正点原子stm32mp1开发板(STM32开发板资料连载)

1)实验平台:alientek NANO STM32F411 V1开发板2)摘自《正点原子STM32F4 开发指南(HAL 库版》关注官方微信号公众号,获取更多资料:正点原子

正点原子stm32mp1开发板(STM32开发板资料连载)(1)

第二十四章 SPI 实验

本章我们将向大家介绍 STM32F4 的 SPI 功能。在本章中,我们将利用 STM32F4 自带的 SPI来实现对外部 FLASH(W25Q16)的读写,并将结果通过串口调试助手显示。本章分为如下几个部分:

24.1 SPI 简介

24.2 硬件设计

24.3 软件设计

24.4 下载验证

24.1 SPI 简介

SPI 是英语 Serial Peripheral interface 的缩写,顾名思义就是串行外围设备接口。是 Motorola。

首先在其 MC68HCXX 系列处理器上定义的。SPI 接口主要应用在 EEPROM,FLASH,实时时

钟,AD 转换器,还有数字信号处理器和数字信号解码器之间。SPI,是一种高速的,全双工,

同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为 PCB 的布局

上节省空间,提供方便,正是出于这种简单易用的特性,现在越来越多的芯片集成了这种通信

协议,STM32 也有 SPI 接口。下面我们看看 SPI 的内部简明图(图 24.1.1):

正点原子stm32mp1开发板(STM32开发板资料连载)(2)

图 24.1.1 SPI 内部结构简明图

SPI 接口一般使用 4 条线通信:

MISO 主设备数据输入,从设备数据输出。

MOSI 主设备数据输出,从设备数据输入。

SCLK 时钟信号,由主设备产生。

CS 从设备片选信号,由主设备控制。

从图中可以看出,主机和从机都有一个串行移位寄存器,主机通过向它的 SPI 串行寄存器

写入一个字节来发起一次传输。寄存器通过 MOSI 信号线将字节传送给从机,从机也将自己的

移位寄存器中的内容通过 MISO 信号线返回给主机。这样,两个移位寄存器中的内容就被交换。

外设的写操作和读操作是同步完成的。如果只进行写操作,主机只需忽略接收到的字节;反之,

若主机要读取从机的一个字节,就必须发送一个空字节来引发从机的传输。

SPI 主要特点有:可以同时发出和接收串行数据;可以当作主机或从机工作;提供频率可

编程时钟;发送结束中断标志;写冲突保护;总线竞争保护等。

SPI 总线四种工作方式 SPI 模块为了和外设进行数据交换,根据外设工作要求,其输出串

行同步时钟极性和相位可以进行配置,时钟极性(CPOL)对传输协议没有重大的影响。如果

CPOL=0,串行同步时钟的空闲状态为低电平;如果 CPOL=1,串行同步时钟的空闲状态为高电

平。时钟相位(CPHA)能够配置用于选择两种不同的传输协议之一进行数据传输。如果

CPHA=0,在串行同步时钟的第一个跳变沿(上升或下降)数据被采样;如果 CPHA=1,在串

行同步时钟的第二个跳变沿(上升或下降)数据被采样。SPI 主模块和与之通信的外设备时钟

相位和极性应该一致。

不同时钟相位下的总线数据传输时序如图 24.1.2 所示:

正点原子stm32mp1开发板(STM32开发板资料连载)(3)

图 24.1.2 不同时钟相位下的总线传输时序(CPHA=0/1)

STM32F4 的 SPI 功能很强大,SPI 时钟最多可以到 50Mhz,支持 DMA,可以配置为 SPI

协议或者 I2S 协议(仅大容量型号支持)。

本章,我们将利用 STM32 的 SPI 来读取外部 SPI FLASH 芯片(W25Q16),实现类似上

节的功能。这里对 SPI 我们只简单介绍一下 SPI 的使用,STM32 的 SPI 详细介绍请参考

《STM32F411xC/E 参考手册》第 558 页,20 节。然后我们再介绍下 SPI FLASH 芯片。

这节,我们使用 STM32 的 SPI2 的主模式,下面就来看看 SPI2 部分的设置步骤吧。SPI 相

关的库函数和定义分布在文件 stm32f4xx_hal_spi.c 以及头文件 stm32f4xx_hal_spi.h 中。STM32

的主模式配置步骤如下:

1)配置相关引脚的复用功能,使能 SPI2 时钟

我们要用 SPI2,第一步就要使能 SPI2 的时钟。其次要设置 SPI2 的相关引脚为复用输出,

这样才会连接到 SPI2 上否则这些 IO 口还是默认的状态,也就是标准输入输出口。这里我们使

用的是 PB13、14、15 这 3 个(SCK.、MISO、MOSI,CS 使用软件管理方式),所以设置这三

个为复用 IO。

使能 SPI2 时钟的方法为:

__HAL_RCC_SPI2_CLK_ENABLE(); //使能 SPI2 时钟

复用 PB13,PB14,PB15 引脚是通过 HAL_GPIO_Init 函数实现,代码如下:

GPIO_InitTypeDef GPIO_Initure;

GPIO_Initure.Pin=GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15;

GPIO_Initure.Mode=GPIO_MODE_AF_PP;

//复用推挽输出

GPIO_Initure.Pull=GPIO_PULLUP;

//上拉

GPIO_Initure.Speed=GPIO_SPEED_HIGH;

//快速

GPIO_Initure.Alternate = GPIO_AF5_SPI2;

//复用为 SPI2

HAL_GPIO_Init(GPIOB,&GPIO_Initure);

2)初始化 SPI2,设置 SPI2 工作模式

接下来我们要初始化 SPI2,设置 SPI2 为主机模式,设置数据格式为 8 位,然设置 SCK 时钟

极性及采样方式。并设置 SPI2 的时钟频率(最大 50Mhz),以及数据的格式(MSB 在前还是

LSB 在前)。在 HAL 库中初始化 SPI 的函数为:

HAL_StatusTypeDef HAL_SPI_Init(SPI_HandleTypeDef *hspi);

下面我们来看看 SPI_HandleTypeDef 的定义:

typedef struct __SPI_HandleTypeDef

{

SPI_TypeDef

*Instance; //基地址

SPI_InitTypeDef

Init; //初始化结构体

uint8_t

*pTxBuffPtr;//发送缓存

uint16_t

TxXferSize;//发送数据大小

__IO uint16_t

TxXferCount;//还剩余多少个数据要发送

uint8_t

*pRxBuffPtr;//接收缓存

uint16_t

RxXferSize;//接收数据大小

__IO uint16_t

RxXferCount; //还剩余多少个数据要接收

void

(*RxISR)(struct __SPI_HandleTypeDef * hspi);

void

(*TxISR)(struct __SPI_HandleTypeDef * hspi);

DMA_HandleTypeDef

*hdmatx;//DMA 发送句柄

DMA_HandleTypeDef

*hdmarx;//DMA 接收句柄

HAL_LockTypeDef

Lock;

__IO HAL_SPI_StateTypeDef

State;

__IO uint32_t

ErrorCode;

}SPI_HandleTypeDef;

该结构体和串口句柄结构体类似,同样有 6 个成员变量和 2 个 DMA_HandleTypeDef 指针

类型变量。这几个参数的作用这里我们就不做过多讲解,大家如果对 HAL 库串口通信理解了,

那么这些就很好理解。这里我们主要讲解第二个成员变量 Init,它是 SPI_InitTypeDef 结构体类

型,该结构体定义如下:

typedef struct

{

uint32_t Mode; //模式:主(SPI_MODE_MASTER),从(SPI_MODE_SLAVE)

uint32_t Direction; //方式: 只接收模式,单线双向通信数据模式,全双工

uint32_t DataSize; //8 位还是 16 位帧格式选择项

uint32_t CLKPolarity; //时钟极性

uint32_t CLKPhase;//时钟相位

uint32_t NSS; //SS 信号由硬件(NSS 管脚)还是软件控制

uint32_t BaudRatePrescaler;//设置 SPI 波特率预分频值

uint32_t FirstBit;//起始位是 MSB 还是 LSB

uint32_t TIMode; //帧格式 SPI motorola 模式还是 TI 模式

uint32_t CRCCalculation; //硬件 CRC 是否使能

uint32_t CRCPolynomial; //CRC 多项式

}SPI_InitTypeDef;

该结构体每个成员变量的含义我们已经在变量后面注释了,请大家参考学习。SPI 初始化

实例代码如下:

SPI2_Handler.Instance=SPI2;

//SPI2

SPI2_Handler.Init.Mode=SPI_MODE_MASTER; //设置为主模式

SPI2_Handler.Init.Direction=SPI_DIRECTION_2LINES; //SPI 设置为双线模式

SPI2_Handler.Init.DataSize=SPI_DATASIZE_8BIT; //SPI 发送接收 8 位帧结构

SPI2_Handler.Init.CLKPolarity=SPI_POLARITY_HIGH; //串行同步时钟的空闲状态为高电平

SPI2_Handler.Init.CLKPhase=SPI_PHASE_2EDGE; //第二个跳变沿数据被采样

SPI2_Handler.Init.NSS=SPI_NSS_SOFT;//NSS 信号由硬件管理

SPI2_Handler.Init.BaudRatePrescaler=SPI_BAUDRATEPRESCALER_256

;//定义波特率预分频的值:波特率预分频值为 256

SPI2_Handler.Init.FirstBit=SPI_FIRSTBIT_MSB; //数据传输从 MSB 位开始

SPI2_Handler.Init.TIMode=SPI_TIMODE_DISABLE; //关闭 TI 模式

SPI2_Handler.Init.CRCCalculation=SPI_CRCCALCULATION_DISABLE;//关闭硬件 CRC

SPI2_Handler.Init.CRCPolynomial=7;

//CRC 值计算的多项式

HAL_SPI_Init(&SPI2_Handler);//初始化

同样,HAL 库也提供了 SPI 初始 MSP 回调函数 HAL_SPI_MspInit,定义如下:

void HAL_SPI_MspInit(SPI_HandleTypeDef *hspi);

关于回调函数使用,这里我们就不做过多讲解。

3)使能 SPI2

初始化完成之后接下来是要使能 SPI2 通信了,在使能 SPI2 之后,我们就可以开始 SPI 通

讯了。使能 SPI2 的方法是:

__HAL_SPI_ENABLE(&SPI2_Handler);

//使能 SPI2

4)SPI 传输数据

通信接口当然需要有发送数据和接受数据的函数,HAL 库提供的发送数据函数原型为:

HAL_StatusTypeDef HAL_SPI_Transmit(SPI_HandleTypeDef *hspi, uint8_t *pData,

uint16_t Size, uint32_t Timeout);

这个函数很好理解,往 SPIx 数据寄存器写入数据 Data,从而实现发送。

HAL 库提供的接受数据函数原型为:

HAL_StatusTypeDef HAL_SPI_Receive(SPI_HandleTypeDef *hspi, uint8_t *pData,

uint16_t Size, uint32_t Timeout);

这个函数也不难理解,从 SPIx 数据寄存器读出接受到的数据。

前面我们讲解了 SPI 通信的原理,因为 SPI 是全双工,发送一个字节的同事接受一个字节,

发送和接收同时完成,所以 HAL 也提供了一个发送接收同一函数:

HAL_StatusTypeDef HAL_SPI_TransmitReceive(SPI_HandleTypeDef *hspi, uint8_t *pTxData,

uint8_t *pRxData, uint16_t Size, uint32_t Timeout);

该函数发送一个字节的同时负责接收一个字节。

5)设置 SPI 传输速度

SPI 初始化结构体 SPI_InitTypeDef 有一个成员变量是 BaudRatePrescaler,该成员变量用来

设置 SPI 的预分频系数,从而决定了 SPI 的传输速度。但是 HAL 库并没有提供单独的 SPI 分频

系数修改函数,如果我们需要在程序中不时的修改速度,那么我们就要通过设置 SPI 的 CR1 寄

存器来修改,具体实现方法请参考后面软件设计小节相关函数。

SPI2 的使用就介绍到这里,接下来介绍一下 W25Q16。W25Q16 是华邦公司推出的大容量

SPI FLASH 产品,W25Q16 的容量为 16Mb,该系列还有 W25Q80/32/64 等。ALIENTEK 所选

择的 W25Q16 容量为 16Mb,也就是 2M 字节。

W25Q16 将 2M 的容量分为 32 个块(Block),每个块大小为 64K 字节,每个块又分为 16

个扇区(Sector),每个扇区 4K 个字节。W25Q16 的最小擦除单位为一个扇区,也就是每次必

须擦除 4K 个字节。这样我们需要给 W25Q16 开辟一个至少 4K 的缓存区,这样对 SRAM 要求

比较高,要求芯片必须有 4K 以上 SRAM 才能很好的操作。

W25Q16 的擦写周期多达 10W 次,具有 20 年的数据保存期限,支持电压为 2.7~3.6V,

W25Q16 支持标准的 SPI,还支持双输出/四输出的 SPI,最大 SPI 时钟可以到 133Mhz(双输出

时相当于 266Mhz,四输出时相当于 532M),更多的 W25Q16 的介绍,请参考 W25Q16 的

DATASHEET。

24.2 硬件设计

本章实验功能简介:开机的时候先检测 W25Q16 是否存在,然后在主循环里面检测两个按

键,其中 1 个按键(KEY_UP)用来执行写入 W25Q16 的操作,另外一个按键(KEY1)用来

执行读出操作,DS2 提示读写状态。在串口调试助手打印显示相关信息。同时用 DS0 提示程序

正在运行。所要用到的硬件资源如下:

1) 指示灯 DS0、DS2

2) KEY_UP 和 KEY1 按键

3) 串口(打印信息和 USMART 调试)

4) SPI2

5) W25Q16

这里只介绍 W25Q16 与 STM32 的连接,板上的 W25Q16 是直接连在 STM32 的 SPI2 上的,

F_CS 是连接在 PB12 上面,连接关系如图 29.2.1 所示:

正点原子stm32mp1开发板(STM32开发板资料连载)(4)

图 24.2.1 STM32 与 W25Q128 连接电路图

24.3 软件设计

打开我们光盘的 SPI 实验工程,可以看到我们加入了 spi.c,w25qxx.c 文件以及头文件 spi.h

和 w25qxx.h,同时引入了库函数文件 stm32f1xx_hal_spi.c 文件以及头文件 stm32f1xx_hal_spi.h。

打开 spi.c 文件,看到如下代码:

SPI_HandleTypeDef SPI2_Handler; //SPI2 句柄

//以下是 SPI 模块的初始化代码,配置成主机模式

//SPI 口初始化

//这里针是对 SPI2 的初始化

void SPI2_Init(void)

{

SPI2_Handler.Instance=SPI2; //SPI2

SPI2_Handler.Init.Mode=SPI_MODE_MASTER; //设置为主模式

SPI2_Handler.Init.Direction=SPI_DIRECTION_2LINES; //SPI 设置为双线模式

SPI2_Handler.Init.DataSize=SPI_DATASIZE_8BIT; //SPI 发送接收 8 位帧结构

SPI2_Handler.Init.CLKPolarity=SPI_POLARITY_HIGH;

//串行同步时钟的空闲状态为高电平

SPI2_Handler.Init.CLKPhase=SPI_PHASE_2EDGE; //第二个跳变沿数据被采样

SPI2_Handler.Init.NSS=SPI_NSS_SOFT;//NSS 信号由硬件管理

SPI2_Handler.Init.BaudRatePrescaler=SPI_BAUDRATEPRESCALER_256;

//定义波特率预分频的值:波特率预分频值为 256

SPI2_Handler.Init.FirstBit=SPI_FIRSTBIT_MSB; //数据传输从 MSB 位开始

SPI2_Handler.Init.TIMode=SPI_TIMODE_DISABLE; //关闭 TI 模式

SPI2_Handler.Init.CRCCalculation=SPI_CRCCALCULATION_DISABLE;

//关闭硬件 CRC

SPI2_Handler.Init.CRCPolynomial=7;

//CRC 值计算的多项式

HAL_SPI_Init(&SPI2_Handler);//初始化

__HAL_SPI_ENABLE(&SPI2_Handler); //使能 SPI2

SPI2_ReadWriteByte(0Xff); //启动传输

}

//SPI2 底层驱动,时钟使能,引脚配置

//此函数会被 HAL_SPI_Init()调用

//hspi:SPI 句柄

void HAL_SPI_MspInit(SPI_HandleTypeDef *hspi)

{

GPIO_InitTypeDef GPIO_Initure;

__HAL_RCC_GPIOB_CLK_ENABLE();

//使能 GPIOB 时钟

__HAL_RCC_SPI2_CLK_ENABLE();

//使能 SPI2 时钟

//PB13,14,15

GPIO_Initure.Pin=GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15;

GPIO_Initure.Mode=GPIO_MODE_AF_PP; //复用推挽输出

GPIO_Initure.Pull=GPIO_PULLUP;

//上拉

GPIO_Initure.Speed=GPIO_SPEED_HIGH; //快速

GPIO_Initure.Alternate = GPIO_AF5_SPI2; //复用为 SPI2

HAL_GPIO_Init(GPIOB,&GPIO_Initure);

}

//SPI 速度设置函数

//SPI 速度=fAPB1/分频系数

//@ref SPI_BaudRate_Prescaler:SPI_BAUDRATEPRESCALER_2~

//

SPI_BAUDRATEPRESCALER_2 256

//fAPB1 时钟一般为 36Mhz:

void SPI2_SetSpeed(u8 SPI_BaudRatePrescaler)

{

assert_param(IS_SPI_BAUDRATE_PRESCALER(SPI_BaudRatePrescaler));//判断有效性

__HAL_SPI_DISABLE(&SPI2_Handler);

//关闭 SPI

SPI2_Handler.Instance->CR1&=0XFFC7;

//位 3-5 清零,用来设置波特率

SPI2_Handler.Instance->CR1|=SPI_BaudRatePrescaler;//设置 SPI 速度

__HAL_SPI_ENABLE(&SPI2_Handler);

//使能 SPI

}

//SPI2 读写一个字节

//TxData:要写入的字节

//返回值:读取到的字节

u8 SPI2_ReadWriteByte(u8 TxData)

{

u8 Rxdata;

HAL_SPI_TransmitReceive(&SPI2_Handler,&TxData,&Rxdata,1, 1000);

return Rxdata; //返回收到的数据

}

此部分代码主要初始化 SPI,这里我们选择的是 SPI2,所以在 SPI2_Init 函数里面,其相关

的操作都是针对 SPI2 的,其初始化主要是通过 HAL_SPI_Init 来实现的,初始化之后同时开启

SPI2。在初始化之后,我们就可以使用 SPI2 了,这里特别注意,SPI 初始化函数的最后一个启

动传输,这句话最大的作用就是维持 MOSI 为高电平,而且这句话也不是必须的,可以去掉。

在 SPI2_Init 函数里面,我们把 SPI2 的频率设置成了最低(90Mhz,256 分频),而在外部

我们可以随时通过函数 SPI2_SetSpeed 来设置 SPI2 的速度。函数 SPI2_ReadWriteByte 则主要通

过调用 HAL 库函数 HAL_SPI_TransmitReceive 来实现数据的发送和接收。

接下来我们来看看 w25qxx.c 文件内容。由于篇幅所限,详细代码,这里就不贴除了。我们

仅介绍几个重要的函数,首先是 W25QXX_Read 函数,该函数用于从 W25Q16 的指定地址读出

指定长度的数据。期代码如下:

//读取 SPI FLASH

//在指定地址开始读取指定长度的数据

//pBuffer:数据存储区

//ReadAddr:开始读取的地址(24bit)

//NumByteToRead:要读取的字节数(最大 65535)

void W25QXX_Read (u8* pBuffer,u32 ReadAddr,u16 NumByteToRead)

{

u16 i;

SPI_FLASH_CS=0;

//使能器件

SPI2_ReadWriteByte(W25X_ReadData);

//发送读取命令

SPI2_ReadWriteByte((u8)((ReadAddr)>>16)); //发送 24bit 地址

SPI2_ReadWriteByte((u8)((ReadAddr)>>8));

SPI2_ReadWriteByte((u8)ReadAddr);

for(i=0;i<NumByteToRead;i )

{

pBuffer[i]=SPI2_ReadWriteByte(0XFF);

//循环读数

}

SPI_FLASH_CS=1;

}

由于 W25Q16 支持以任意地址(但是不能超过 W25Q16 的地址范围)开始读取数据,所以,

这个代码相对来说就比较简单了,在发送 24 位地址之后,程序就可以开始循环读数据了,其地

址会自动增加的,不过要注意,不能读的数据超过了 W25Q16 的地址范围哦!否则读出来的数

据,就不是你想要的数据了。

有读的函数,当然就有写的函数了,接下来,我们介绍 W25QXX_Write 这个函数,该函数

的作用与 W25QXX_Flash_Read 的作用类似,不过是用来写数据到 W25Q128 里面的,其代码如

下:

u8 W25QXX_BUFFER[4096];

void W25QXX_Write(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite)

{

u32 secpos;

u16 secoff;

u16 secremain;

u16 i;

u8 * W25QXX_BUF;

W25QXX_BUF=W25QXX_BUFFER;

secpos=WriteAddr/4096;//扇区地址

secoff=WriteAddr@96;//在扇区内的偏移

secremain=4096-secoff;//扇区剩余空间大小

//printf("ad:%X,nb:%X ",WriteAddr,NumByteToWrite);//测试用

if(NumByteToWrite<=secremain)secremain=NumByteToWrite;//不大于 4096 个字节

while(1)

{

W25QXX_Read(W25QXX_BUF,secpos*4096,4096);//读出整个扇区的内容

for(i=0;i<secremain;i )//校验数据

{

if(W25QXX_BUF[secoff i]!=0XFF)break;//需要擦除

}

if(i<secremain)//需要擦除

{

W25QXX_Erase_Sector(secpos);

//擦除这个扇区

for(i=0;i<secremain;i )

//复制

{

W25QXX_BUF[i secoff]=pBuffer[i];

}

W25QXX_Write_NoCheck(W25QXX_BUF,secpos*4096,4096);//写入整个扇区

}else W25QXX_Write_NoCheck(pBuffer,WriteAddr,secremain);

//写已经擦除了的,直接写入扇区剩余区间.

if(NumByteToWrite==secremain)break;//写入结束了

else//写入未结束

{

secpos ;//扇区地址增 1

secoff=0;//偏移位置为 0

pBuffer =secremain;

//指针偏移

WriteAddr =secremain;

//写地址偏移

NumByteToWrite-=secremain;

//字节数递减

if(NumByteToWrite>4096)secremain=4096;//下一个扇区还是写不完

else secremain=NumByteToWrite;

//下一个扇区可以写完了

}

};

}

该函数可以在 W25Q16 的任意地址开始写入任意长度(必须不超过 W25Q16 的容量)的数

据。我们这里简单介绍一下思路:先获得首地址(WriteAddr)所在的扇区,并计算在扇区内的

偏移,然后判断要写入的数据长度是否超过本扇区所剩下的长度,如果不超过,再先看看是否

要擦除,如果不要,则直接写入数据即可,如果要则读出整个扇区,在偏移处开始写入指定长

度的数据,然后擦除这个扇区,再一次性写入。当所需要写入的数据长度超过一个扇区的长度

的时候,我们先按照前面的步骤把扇区剩余部分写完,再在新扇区内执行同样的操作,如此循

环,直到写入结束。

其他的代码就比较简单了,我们这里不介绍了。接着打开 w25qxx.h 文件可以看到,这里面

就定义了一些与 W25Q16 操作相关的命令(部分省略了),这些命令在 W25Q16 的数据手册上

都有详细的介绍,感兴趣的读者可以参考该数据手册,其他的就没啥好说的了。。最后,我们

看看 main.c 里面代码如下:

//要写入到 W25Q16 的字符串数组

const u8 TEXT_Buffer[]={"NANOSTM32 SPI TEST"};

#define SIZE sizeof(TEXT_Buffer)

int main(void)

{

u8 key;

u16 i=0;

u8 datatemp[SIZE];

u32 FLASH_SIZE;

HAL_Init();

//初始化 HAL 库

Stm32_Clock_Init(96,4,2,4);

//设置时钟,96Mhz

delay_init(96);

//初始化延时函数

uart_init(115200);

//初始化串口 115200

LED_Init();

//初始化 LED

KEY_Init();

//按键初始化

W25QXX_Init();

//W25QXX 初始化

usmart_dev.init(96);

//初始化 USMARTprintf("NANO STM32 ");

printf("SPI TEST ");

while(W25QXX_ReadID()!=W25Q16)//检测 W25Q16

{

printf("W25Q16 Check Failed! ");

delay_ms(500);

printf("Please Check! ");

delay_ms(500);

LED0=!LED0;//DS0 闪烁

}

printf("W25Q16 Ready! ");

printf("KEY_UP:Write KEY1:Read ");

FLASH_SIZE=2*1024*1024;

//FLASH 大小为 2M 字节

while(1)

{

key=KEY_Scan(0);

if(key==WKUP_PRES) //WK_UP 按下,写入 W25Q16

{

LED2=0;

printf(" Start Write W25Q16.... ");

W25QXX_Write((u8*)TEXT_Buffer,FLASH_SIZE-100,SIZE);

//从倒数第 100 个地址处开始,写入 SIZE 长度的数据

printf("W25Q16 Write Finished! ");//提示传送完成

LED2=1;

}

if(key==KEY1_PRES) //KEY1 按下,读取字符串并显示

{

LED2=0;

printf(" Start Read W25Q16.... ");

W25QXX_Read(datatemp,FLASH_SIZE-100,SIZE);

//从倒数第 100 个地址处开始,读出 SIZE 个字节

printf("The Data Readed Is: ");//提示传送完成

printf("%s ",datatemp);//显示读到的字符串

LED2=1;

}

i ;

delay_ms(10);

if(i==20)

{

LED0=!LED0;//提示系统正在运行

i=0;

}

}

}

这部分代码和 IIC 实验那部分代码大同小异,我们就不多说了,实现的功能就和 IIC 差不

多,不过此次写入和读出的是 SPI FLASH,而不是 EEPROM。

最后,我们将 W25QXX_ReadID 和 W25QXX_Erase_Chip 两个函数加入 usmart 控制,这样

我们便可以通过 usmart 调用 W25QXX_ReadID 函数,来读取 SPI FLASH 的 ID,也可以调用

W25QXX_Erase_Chip 函数,实现对整个 SPI FLASH 的擦除。

24.4 下载验证

在代码编译成功之后,我们打开串口调试助手,下载代码到 ALIENTEK NANO STM32F4

上,通过先按 KEY_UP 按键写入数据,然后按 KEY1 读取数据,得到如图 24.4.1 所示:

正点原子stm32mp1开发板(STM32开发板资料连载)(5)

图 24.4.1 SPI 实验程序运行效果图

在 SPI 读和写过程中 DS2 会闪烁,同时 DS0 的不停闪烁,提示程序在运行。程序在开机的

时候会检测 W25Q16 是否存在,如果不存在则会在串口调试助手上显示错误信息,同时 DS0

慢闪。

在 USMART 测试中,我们先读取 W25Q16 的 FLASH ID 号,然后再测试 W25Q16 整片擦

除,在擦除前先开启 USMART 的函数执行时间统计功能,主要是为了查看擦除整片的时间,

擦除的时间会根据 W25Q16 内的数据量的大小而不同,这个需要注意。W25Q16 读写效果如图

24.4.2 所示:

正点原子stm32mp1开发板(STM32开发板资料连载)(6)

图 24.4.2 USMART 调试 W25Q16

,

免责声明:本文仅代表文章作者的个人观点,与本站无关。其原创性、真实性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容文字的真实性、完整性和原创性本站不作任何保证或承诺,请读者仅作参考,并自行核实相关内容。