【STM32Cube-18】使用硬件QSPI读写SPI Flash(W25Q64)

发布者:Shuxiang最新更新时间:2021-07-22 来源: eefocus关键字:读写SPI  Flash  W25Q64 手机看文章 扫描二维码
随时随地手机看文章

本篇详细的记录了如何使用STM32CubeMX配置STM32L431RCT6的硬件QSPI外设与 SPI Flash 通信(W25Q64)。


1. 准备工作

硬件准备

  • 开发板
    首先需要准备一个开发板,这里我准备的是STM32L4的开发板(BearPi):

mark

  • SPI Flash
    小熊派开发板板载一片SPI Flash,型号为 W25Q64,大小为 8 MB,最大支持 80 Mhz的操作频率。

软件准备

  • 需要安装好Keil - MDK及芯片对应的包,以便编译和下载生成的代码;

  • 准备一个串口调试助手,这里我使用的是Serial Port Utility;


2.生成MDK工程

选择芯片型号

打开STM32CubeMX,打开MCU选择器:
mark

搜索并选中芯片STM32L431RCT6:
mark

配置时钟源

  • 如果选择使用外部高速时钟(HSE),则需要在System Core中配置RCC;

  • 如果使用默认内部时钟(HSI),这一步可以略过;

这里我都使用外部时钟:

mark

配置串口

小熊派开发板板载ST-Link并且虚拟了一个串口,原理图如下:

mark

这里我将开关拨到AT-MCU模式,使PC的串口与USART1之间连接。

接下来开始配置USART1:

mark

配置QSPI接口

首先查看小熊派开发板上 SPI Flash 的原理图:

mark

其引脚连接情况如下:

SPI Flash连接引脚对应引脚
QUADSPI_BK1_NCSPB11
QUADSPI_BK1_CLKPB10
QUADSPI_BK1_IO0PB1
QUADSPI_BK1_IO1PB0

接下来配置 QSPI 接口:

mark

配置时钟树

STM32L4的最高主频到80M,所以配置PLL,最后使HCLK = 80Mhz即可:
mark

生成工程设置

mark

代码生成设置

最后设置生成独立的初始化文件:

mark

生成代码

点击GENERATE CODE即可生成MDK-V5工程:

mark

3. 在MDK中编写、编译、下载用户代码

重定向printf( )函数

参考: 【STM32Cube_09】重定向printf函数到串口输出的多种方法。

4. 封装 SPI Flash(W25Q64)的命令和底层函数

MCU 通过向 SPI Flash 发送各种命令 来读写 SPI Flash内部的寄存器,所以这种裸机驱动,首先要先宏定义出需要使用的命令,然后利用 HAL 库提供的库函数,封装出三个底层函数,便于移植:

  • 向 SPI Flash 发送命令的函数

  • 向 SPI Flash 发送数据的函数

  • 从 SPI Flash 接收数据的函数

接下来开始编写代码~

宏定义操作命令

#define ManufactDeviceID_CMD 0x90

#define READ_STATU_REGISTER_1 0x05

#define READ_STATU_REGISTER_2 0x35

#define READ_DATA_CMD 0x03

#define WRITE_ENABLE_CMD 0x06

#define WRITE_DISABLE_CMD 0x04

#define SECTOR_ERASE_CMD 0x20

#define CHIP_ERASE_CMD 0xc7

#define PAGE_PROGRAM_CMD 0x02

封装发送命令的函数(重点)

/**

* @brief 向SPI Flash发送指令

* @param instruction —— 要发送的指令

* @param address —— 要发送的地址

* @param dummyCycles —— 空指令周期

* @param instructionMode —— 指令发送模式

* @param addressMode —— 地址发送模式

* @param addressSize —— 地址大小

* @param dataMode —— 数据发送模式

* @retval 成功返回HAL_OK

*/

HAL_StatusTypeDef QSPI_Send_Command(uint32_t instruction,

uint32_t address,

uint32_t dummyCycles,

uint32_t instructionMode,

uint32_t addressMode,

uint32_t addressSize,

uint32_t dataMode)

{

QSPI_CommandTypeDef cmd;


cmd.Instruction = instruction; //指令

cmd.Address = address; //地址

cmd.DummyCycles = dummyCycles; //设置空指令周期数

cmd.InstructionMode = instructionMode; //指令模式

cmd.AddressMode = addressMode; //地址模式

cmd.AddressSize = addressSize; //地址长度

cmd.DataMode = dataMode; //数据模式

cmd.SIOOMode = QSPI_SIOO_INST_EVERY_CMD; //每次都发送指令

cmd.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE; //无交替字节

cmd.DdrMode = QSPI_DDR_MODE_DISABLE; //关闭DDR模式

cmd.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;


return HAL_QSPI_Command(&hqspi, &cmd, 5000);

}


封装发送数据的函数

/**

* @brief QSPI发送指定长度的数据

* @param buf —— 发送数据缓冲区首地址

* @param size —— 要发送数据的字节数

* @retval 成功返回HAL_OK

*/

HAL_StatusTypeDef QSPI_Transmit(uint8_t* send_buf, uint32_t size)

{

hqspi.Instance->DLR = size - 1; //配置数据长度

return HAL_QSPI_Transmit(&hqspi, send_buf, 5000); //接收数据

}

封装接收数据的函数

/**

* @brief QSPI接收指定长度的数据

* @param buf —— 接收数据缓冲区首地址

* @param size —— 要接收数据的字节数

* @retval 成功返回HAL_OK

*/

HAL_StatusTypeDef QSPI_Receive(uint8_t* recv_buf, uint32_t size)

{

hqspi.Instance->DLR = size - 1; //配置数据长度

return HAL_QSPI_Receive(&hqspi, recv_buf, 5000); //接收数据

}


5. 编写W25Q64的驱动程序

接下来开始利用上一节封装的宏定义和底层函数,编写W25Q64的驱动程序:


读取Manufacture ID和Device ID

读取 Flash 内部这两个ID有两个作用:


检测SPI Flash是否存在

可以根据ID判断Flash具体型号

数据手册上给出的操作时序如图:

mark

根据该时序,编写代码如下:


/**

* @brief 读取Flash内部的ID

* @param none

* @retval 成功返回device_id

*/

uint16_t W25QXX_ReadID(void)

{

uint8_t recv_buf[2] = {0}; //recv_buf[0]存放Manufacture ID, recv_buf[1]存放Device ID

uint16_t device_id = 0;

if(HAL_OK == QSPI_Send_Command(ManufactDeviceID_CMD, 0, 0, QSPI_INSTRUCTION_1_LINE, QSPI_ADDRESS_1_LINE, QSPI_ADDRESS_24_BITS, QSPI_DATA_1_LINE))

{

//读取ID

if(HAL_OK == QSPI_Receive(recv_buf, 2))

{

device_id = (recv_buf[0] << 8) | recv_buf[1];

return device_id;

}

else

{

return 0;

}

}

else

{

return 0;

}

}


读取数据

SPI Flash读取数据可以任意地址(地址长度32bit)读任意长度数据(最大 65535 Byte),没有任何限制,数据手册给出的时序如下:

mark

根据该时序图编写代码如下:


/**

* @brief 读取SPI FLASH数据

* @param dat_buffer —— 数据存储区

* @param start_read_addr —— 开始读取的地址(最大32bit)

* @param byte_to_read —— 要读取的字节数(最大65535)

* @retval none

*/

void W25QXX_Read(uint8_t* dat_buffer, uint32_t start_read_addr, uint16_t byte_to_read)

{

QSPI_Send_Command(READ_DATA_CMD, start_read_addr, 0, QSPI_INSTRUCTION_1_LINE, QSPI_ADDRESS_1_LINE, QSPI_ADDRESS_24_BITS, QSPI_DATA_1_LINE);

QSPI_Receive(dat_buffer, byte_to_read);

}


读取状态寄存器数据并判断Flash是否忙碌

上文中提到,SPI Flash的所有操作都是靠发送命令完成的,但是 Flash 接收到命令后,需要一段时间去执行该操作,这段时间内 Flash 处于“忙”状态,MCU 发送的命令无效,不能执行,在 Flash 内部有2-3个状态寄存器,指示出 Flash 当前的状态,有趣的一点是:

当 Flash 内部在执行命令时,不能再执行 MCU 发来的命令,但是 MCU 可以一直读取状态寄存器,这下就很好办了,MCU可以一直读取,然后判断Flash是否忙完:

mark

首先读取状态寄存器的代码如下:


/**

* @brief 读取W25QXX的状态寄存器,W25Q64一共有2个状态寄存器

* @param reg —— 状态寄存器编号(1~2)

* @retval 状态寄存器的值

*/

uint8_t W25QXX_ReadSR(uint8_t reg)

{

uint8_t cmd = 0, result = 0;

switch(reg)

{

case 1:

/* 读取状态寄存器1的值 */

cmd = READ_STATU_REGISTER_1;

case 2:

cmd = READ_STATU_REGISTER_2;

case 0:

default:

cmd = READ_STATU_REGISTER_1;

}

QSPI_Send_Command(cmd, 0, 0, QSPI_INSTRUCTION_1_LINE, QSPI_ADDRESS_NONE, QSPI_ADDRESS_24_BITS, QSPI_DATA_1_LINE);

QSPI_Receive(&result, 1);


return result;

}


然后编写阻塞判断Flash是否忙碌的函数:


/**

* @brief 阻塞等待Flash处于空闲状态

* @param none

* @retval none

*/

void W25QXX_Wait_Busy(void)

{

while((W25QXX_ReadSR(1) & 0x01) == 0x01); // 等待BUSY位清空

}


写使能/禁止

Flash 芯片默认禁止写数据,所以在向 Flash 写数据之前,必须发送命令开启写使能,数据手册中给出的时序如下:

mark

mark

编写函数如下:


/**

* @brief W25QXX写使能,将S1寄存器的WEL置位

* @param none

* @retval

*/

void W25QXX_Write_Enable(void)

{

QSPI_Send_Command(WRITE_ENABLE_CMD, 0, 0, QSPI_INSTRUCTION_1_LINE, QSPI_ADDRESS_NONE, QSPI_ADDRESS_8_BITS, QSPI_DATA_NONE);

W25QXX_Wait_Busy();

}


/**

* @brief W25QXX写禁止,将WEL清零

* @param none

* @retval none

*/

void W25QXX_Write_Disable(void)

{

QSPI_Send_Command(WRITE_DISABLE_CMD, 0, 0, QSPI_INSTRUCTION_1_LINE, QSPI_ADDRESS_NONE, QSPI_ADDRESS_8_BITS, QSPI_DATA_NONE);

W25QXX_Wait_Busy();

}


擦除扇区

SPI Flash有个特性:


数据位可以由1变为0,但是不能由0变为1。


所以在向 Flash 写数据之前,必须要先进行擦除操作,并且 Flash 最小只能擦除一个扇区,擦除之后该扇区所有的数据变为 0xFF(即全为1),数据手册中给出的时序如下:

mark

根据此时序编写函数如下:


/**

* @brief W25QXX擦除一个扇区

* @param sector_addr —— 扇区地址 根据实际容量设置

* @retval none

* @note 阻塞操作

*/

void W25QXX_Erase_Sector(uint32_t sector_addr)

{

sector_addr *= 4096; //每个块有16个扇区,每个扇区的大小是4KB,需要换算为实际地址

W25QXX_Write_Enable(); //擦除操作即写入0xFF,需要开启写使能

W25QXX_Wait_Busy(); //等待写使能完成

QSPI_Send_Command(SECTOR_ERASE_CMD, sector_addr, 0, QSPI_INSTRUCTION_1_LINE, QSPI_ADDRESS_1_LINE, QSPI_ADDRESS_24_BITS, QSPI_DATA_NONE);

W25QXX_Wait_Busy(); //等待扇区擦除完成

}


页写入操作

向 Flash 芯片写数据的时候,因为 Flash 内部的构造,可以按页写入:

mark

页写入的时序如图:

mark

编写代码如下:


/**

* @brief 页写入操作

* @param dat —— 要写入的数据缓冲区首地址

* @param WriteAddr —— 要写入的地址

* @param byte_to_write —— 要写入的字节数(0-256)

* @retval none

*/

void W25QXX_Page_Program(uint8_t* dat, uint32_t WriteAddr, uint16_t byte_to_write)

{

W25QXX_Write_Enable();

QSPI_Send_Command(PAGE_PROGRAM_CMD, WriteAddr, 0, QSPI_INSTRUCTION_1_LINE, QSPI_ADDRESS_1_LINE, QSPI_ADDRESS_24_BITS, QSPI_DATA_1_LINE);

QSPI_Transmit(dat, byte_to_write);

W25QXX_Wait_Busy();

}


6. 测试驱动

在 main.c 函数中编写代码,测试驱动:


首先定义两个缓存:


/* Private user code ---------------------------------------------------------*/

/* USER CODE BEGIN 0 */

uint8_t dat[11] = "mculover666";

uint8_t read_buf[11] = {0};

/* USER CODE END 0 */

然后在 main 函数中编写代码:


/* USER CODE BEGIN 2 */

printf("Test W25QXX...rn");

device_id = W25QXX_ReadID();

printf("device_id = 0x%04Xrnrn", device_id);


/* 为了验证,首先读取要写入地址处的数据 */

printf("-------- read data before write -----------rn");

W25QXX_Read(read_buf, 5, 11);

printf("read date is %srn", (char*)read_buf);


/* 擦除该扇区 */

printf("-------- erase sector 0 -----------rn");

W25QXX_Erase_Sector(0);


/* 写数据 */

printf("-------- write data -----------rn");

W25QXX_Page_Program(dat, 5, 11);


/* 再次读数据 */

printf("-------- read data after write -----------rn");

W25QXX_Read(read_buf, 5, 11);

printf("read date is %srn", (char*)read_buf);

/* USER CODE END 2 */


测试结果如下:

mark

至此,我们已经学会如何使用硬件QSPI接口读写SPI Flash的数据,下一节将讲述如何使用硬件SDMMC接口读取SD卡数据。

关键字:读写SPI  Flash  W25Q64 引用地址:【STM32Cube-18】使用硬件QSPI读写SPI Flash(W25Q64)

上一篇:【STM32Cube_17】使用硬件SPI驱动TFT-LCD(ST7789)
下一篇:【STM32Cube-19】使用SDMMC接口读写SD卡数据

推荐阅读最新更新时间:2024-11-07 10:25

基于eCos操作系统的FLASH驱动程序分析与移植
0 引 言     嵌入式系统需要支持的外部设备种类繁多,如何构造运行良好的嵌入式设备的驱动程序,对嵌入式操作系统的实际应用有重要意义。eCos是一种源代码公开的实时嵌人式操作系统,对嵌入式应用具有良好的支持,内核专门设计了便于设备驱动管理和开发的I/O包和DEV包,开发人员可以方便地将自己开发的驱动程序加入其中,与别的系统组件一起进行配置。 1 系统目标板简介     硬件目标板是为无人机系统设计的系统开发板,它是从Arca系统测试板经过功能简化设计而来的满足无人机系统要求的目标板。硬件目标板如图1所示。     GT2000支持SRAM,FLASH,ROM,VLIO(静态段支持可变等待时间I/O设备)和S
[嵌入式]
TMS320C6712的外部内存自引导功能的实现
TMS320C6000系列与TMS320C54系列的引导方式有很大差别。在开发应用TMS320C6000系列DSP时,许多开发者,尤其是初涉及者对DSP ROM引导的实现有些困难,花费许多时间和精力摸索。笔者结合开发实例,介绍了实现外部存储器引导的具体方法。 DSP的引导过程 DSP系统的引导(BOOT)是指系统加电或复位时,DSP将一段存储在外部的非易失性存储器的程序代码通过DMA方式拷贝到内部的高速内存中运行。这样既能扩展DSP有限的存储空间,又能充分发挥DSP内部资源的效能。用户的代码也可以通过掩膜方式写入到DSP内部ROM中,但这样受容量和价格的限制,且不便于扩展和升级。 DSP的引导过程如下: 1) D
[嵌入式]
博雅科技研发,上海华力流片,中国首颗50nm 256M NOR Flash量产
据华力微电子今日宣布,珠海博雅科技有限公司研发的50nm 256M ETOX NOR Flash于2020年初在上海华力领先流片成功,现已成功量产。 据透露,该产品是国内首颗50nm 256M NOR Flash,产品性能参数指标和国际一线品牌同类产品相比,达到同一水平甚至更高,其成功量产标志着国内NOR Flash高容量产品正式迈入50nm时代。 该产品采用目前业界领先的50nm ETOX NOR Flash工艺技术,结合博雅科技创新的设计研发,技术优势明显,具有集成度更高、尺寸更小、能耗更低等特点。 博雅科技是由具有北大背景的海归团队创办的专注于存储器研发设计的高科技企业,是全球唯一具有ETOX工艺和SONOS工艺的NOR
[手机便携]
STM32的Flash读写保护,SWD引脚锁的各种解决办法汇总
问题现象: MDK,J-Flash,IAR等无法连接芯片,有时候链接上了,但是无法下载。 注意:如果下载器线的接触不良,也会有这个问题。 问题描述: 1、Flash读写保护 根本原因是工程设计有问题,特别是实际晶振大小与程序中HSE_VALUE大小不一致,很容易导致Flash读保护。 而读保护的本质是芯片的选项字节被设置了,因为读保护就在选项字节里面,详情看此贴:链接 2、SWD锁 SWD接口锁住就是SWD引脚被锁住了,根本原因是用户使用这个引脚做其它功能了。 解决办法: 1、SWD引脚锁解决办法: (1)、SWD引脚被锁住的解决办法比较简单,只要下载器带了复位引脚,并且板子的SWD接口也留出了RST复位引脚,就可以
[单片机]
STM32的<font color='red'>Flash</font><font color='red'>读写</font>保护,SWD引脚锁的各种解决办法汇总
Flash技术进入机顶盒产品预计年内上市
     目前为止,AdobeFlash播放器主要还是用于电脑,使在浏览器中的动画或者像Youtube这样的视频站点成为可能,但Adobe宣布其最新版本的Flash多媒体平台将Flash技术带入到连接Internet的电视机、机顶盒、蓝光播放器以及其他数字家庭设备。将Flash引入这些设备的主要目的,是使得用户可以在他们的电视机上观看高清视频、运行互动程序以及获得新的用户界面。       Adobe同时也将Flash技术移植到了智能手机平台上,移动版本的Flash使得用户可以观看采用Flash技术的视频。而现在Adobe则将注意力转移到了起居室中高清电视的大屏幕上面,这意味着人们可以在他们的电视上直接获取内容丰富的Youtu
[家用电子]
利用Flash实现DSP对多个程序有选择的加载
摘要:主要介绍一种利用Flash存储器实现双DSP系统对多份用户代码有选择的上电加载的方法。其中,重点介绍M29W800AB Flash的使用和编程方法,TMS320VC54X DSP的上电自动引导过程,以及HPI模式和并行模式加载用户代码的方法。 关键词:Flash存储器 DSP 主机接口 Bootloader 引导表 引 言   在TMS320C54X系列DSP系统的开发中,由于DSP片内只有ROM和RAM存储器,如要将用户代码写入ROM中,必须要由DSP芯片厂家来完成;但这样做用户就不能再更改代码,很不实用。由于RAM在DSP掉电后不能再保存数据,因此,常常利用EPROM、 Flash等一些外部存储器来存放用户代码
[缓冲存储]
嵌入式系统flash接口电路的实现
  0引言   我们在进行嵌入式系统设计的过程中,根据需求,要设计出特定的嵌入式应用系统,而嵌入式应用系统的设计包含硬件系统设计和软件系统设计两个部分,并且这两部分设计是互相关联、密不可分的,嵌入式应用系统的设计经常需要在硬件和软件设计之间进行权衡与折中。因此,这就要求嵌入式系统设计者具有较深厚的硬件和软件基础,并具有熟练应用的能力。在整个设计过程中,硬件设计是系统设计的基础和核心,而各功能部件在整个设计中的调试又是该环节的重点和难点。本文详细介绍嵌入式系统Flash存储器的接口电路的调试。   1 Flash存储器接口电路的引脚信号及各项特性   1.1 Flash存储器接口电路的特点   Flash存储器是一种可在系统
[单片机]
嵌入式系统<font color='red'>flash</font>接口电路的实现
ARM笔记: NAND Flash程序
.text .global _start _start: ldr sp,=4096 bl disable_watch_dog bl memsetup bl nand_init ldr r0,=0x30000000 mov r1,#4096 mov r2,#2048 bl nand_read ldr sp,=0x34000000 ldr lr,=halt_loop ldr pc,=main halt_loop: b
[单片机]
小广播
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved