STM32CubeMX | 30-使用硬件SPI读写FLASH(W25Q64)

发布者:kappa20最新更新时间:2021-07-26 来源: eefocus关键字:STM32CubeMX  硬件SPI  读写FLASH  W25Q64 手机看文章 扫描二维码
随时随地手机看文章

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


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

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

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

  • 向 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发送指定长度的数据

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

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

 * @retval   成功返回HAL_OK

 */

static HAL_StatusTypeDef SPI_Transmit(uint8_t* send_buf, uint16_t size)

{

    return HAL_SPI_Transmit(&hspi1, send_buf, size, 100);

}


封装接收数据的函数

/**

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

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

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

 * @retval  成功返回HAL_OK

 */

static HAL_StatusTypeDef SPI_Receive(uint8_t* recv_buf, uint16_t size)

{

   return HAL_SPI_Receive(&hspi1, recv_buf, size, 100);

}


封装发送数据同时读取数据的函数

/**

 * @brief   SPI在发送数据的同时接收指定长度的数据

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

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

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

 * @retval  成功返回HAL_OK

 */

static HAL_StatusTypeDef SPI_TransmitReceive(uint8_t* send_buf, uint8_t* recv_buf, uint16_t size)

{

   return HAL_SPI_TransmitReceive(&hspi1, send_buf, recv_buf, size, 100);

}


5. 编写W25Q64的驱动程序

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


读取Manufacture ID和Device ID

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


检测SPI Flash是否存在

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

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

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


/**

 * @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;

    uint8_t send_data[4] = {ManufactDeviceID_CMD,0x00,0x00,0x00};   //待发送数据,命令+地址

    

    /* 使能片选 */

    HAL_GPIO_WritePin(W25Q64_CHIP_SELECT_PORT, W25Q64_CHIP_SELECT_PIN, GPIO_PIN_RESET);

    

    /* 发送并读取数据 */

    if (HAL_OK == SPI_Transmit(send_data, 4)) {

        if (HAL_OK == SPI_Receive(recv_buf, 2)) {

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

        }

    }

    

    /* 取消片选 */

    HAL_GPIO_WritePin(W25Q64_CHIP_SELECT_PORT, W25Q64_CHIP_SELECT_PIN, GPIO_PIN_SET);

    

    return device_id;

}


读取状态寄存器数据并判断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    状态寄存器的值

 */

static uint8_t W25QXX_ReadSR(uint8_t reg)

{

    uint8_t result = 0; 

    uint8_t send_buf[4] = {0x00,0x00,0x00,0x00};

    switch(reg)

    {

        case 1:

            send_buf[0] = READ_STATU_REGISTER_1;

        case 2:

            send_buf[0] = READ_STATU_REGISTER_2;

        case 0:

        default:

            send_buf[0] = READ_STATU_REGISTER_1;

    }

    

     /* 使能片选 */

    HAL_GPIO_WritePin(W25Q64_CHIP_SELECT_PORT, W25Q64_CHIP_SELECT_PIN, GPIO_PIN_RESET);

    

    if (HAL_OK == SPI_Transmit(send_buf, 4)) {

        if (HAL_OK == SPI_Receive(&result, 1)) {

            HAL_GPIO_WritePin(W25Q64_CHIP_SELECT_PORT, W25Q64_CHIP_SELECT_PIN, GPIO_PIN_SET);

            

            return result;

        }

    }

    

    /* 取消片选 */

    HAL_GPIO_WritePin(W25Q64_CHIP_SELECT_PORT, W25Q64_CHIP_SELECT_PIN, GPIO_PIN_SET);


    return 0;

}


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


/**

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

 * @param   none

 * @retval  none

 */

static void W25QXX_Wait_Busy(void)

{

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

}


读取数据

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

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


/**

 * @brief   读取SPI FLASH数据

 * @param   buffer      —— 数据存储区

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

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

 * @retval  成功返回0,失败返回-1

 */

int W25QXX_Read(uint8_t* buffer, uint32_t start_addr, uint16_t nbytes)

{

    uint8_t cmd = READ_DATA_CMD;

    

    start_addr = start_addr << 8;

    

W25QXX_Wait_Busy();

    

     /* 使能片选 */

    HAL_GPIO_WritePin(W25Q64_CHIP_SELECT_PORT, W25Q64_CHIP_SELECT_PIN, GPIO_PIN_RESET);

    

    SPI_Transmit(&cmd, 1);

    

    if (HAL_OK == SPI_Transmit((uint8_t*)&start_addr, 3)) {

        if (HAL_OK == SPI_Receive(buffer, nbytes)) {

            HAL_GPIO_WritePin(W25Q64_CHIP_SELECT_PORT, W25Q64_CHIP_SELECT_PIN, GPIO_PIN_SET);

            return 0;

        }

    }

    

    HAL_GPIO_WritePin(W25Q64_CHIP_SELECT_PORT, W25Q64_CHIP_SELECT_PIN, GPIO_PIN_SET);

    return -1;

}


写使能/禁止

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

mark

mark

编写函数如下:


/**

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

 * @param    none

 * @retval

 */

void W25QXX_Write_Enable(void)

{

    uint8_t cmd= WRITE_ENABLE_CMD;

    

    HAL_GPIO_WritePin(W25Q64_CHIP_SELECT_PORT, W25Q64_CHIP_SELECT_PIN, GPIO_PIN_RESET);

    

    SPI_Transmit(&cmd, 1);

    

    HAL_GPIO_WritePin(W25Q64_CHIP_SELECT_PORT, W25Q64_CHIP_SELECT_PIN, GPIO_PIN_SET);

    

    W25QXX_Wait_Busy();


}


/**

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

 * @param    none

 * @retval    none

 */

void W25QXX_Write_Disable(void)

{

    uint8_t cmd = WRITE_DISABLE_CMD;


    HAL_GPIO_WritePin(W25Q64_CHIP_SELECT_PORT, W25Q64_CHIP_SELECT_PIN, GPIO_PIN_RESET);

    

    SPI_Transmit(&cmd, 1);

    

    HAL_GPIO_WritePin(W25Q64_CHIP_SELECT_PORT, W25Q64_CHIP_SELECT_PIN, GPIO_PIN_SET);

    

    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)

{

    uint8_t cmd = SECTOR_ERASE_CMD;

    

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

    sector_addr <<= 8;

    

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

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

   

     /* 使能片选 */

    HAL_GPIO_WritePin(W25Q64_CHIP_SELECT_PORT, W25Q64_CHIP_SELECT_PIN, GPIO_PIN_RESET);

    

    SPI_Transmit(&cmd, 1);

    

    SPI_Transmit((uint8_t*)§or_addr, 3);

    

    HAL_GPIO_WritePin(W25Q64_CHIP_SELECT_PORT, W25Q64_CHIP_SELECT_PIN, GPIO_PIN_SET);

    

    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 nbytes)

{

    uint8_t cmd = PAGE_PROGRAM_CMD;

    

    WriteAddr <<= 8;

    

    W25QXX_Write_Enable();

    

[1] [2]
关键字:STM32CubeMX  硬件SPI  读写FLASH  W25Q64 引用地址:STM32CubeMX | 30-使用硬件SPI读写FLASH(W25Q64)

上一篇:基于STM32芯片创建HelloWorld工程
下一篇:STM32CubeMX | 32-使用硬件FMC驱动TFT-LCD屏幕(MCU屏)

推荐阅读最新更新时间:2024-11-13 21:30

STM32之SPI读写FLASH(W25Q64)
/* 名称:STM32之SPI读写FLASH(W25Q64) 说明: 1.对于SPI读写FLASH和I2C读写EEPROM很相似,都是通过一定的通信协议来操纵外部存储设备。我们需要按照对应的通信协议发送存储设备所支持的指令(如读指令、写指令等),然后等待存储设备根据主机所接收到的指令进行相应的动作。 2.再来说说不同点吧:对于通信协议来说,I2C相对来说要简单些,通信速度也稍微较慢些。而SPI串行通信协议则要相对复杂的多,当然其通信速度也要高不少。对于存储设备来说,EEPROM属于小容量的存储设备,支持字节擦除、页写入,现在一般用于存储小容量的数据;而FLASH属于大容量的存储设备,不支持字节擦除,只支持扇区擦除、块擦除
[单片机]
基于stm32cubemx对fatfs文件系统进行移植
今天要给大家分享的是使用FatFs这个库来读写SD卡上面的文件。工程的初始化函数和FatFs都是通过STM32CubeMX配置生成的,不需要我们手动添加库。 今天分享的内容和我的上一篇帖子 SDIO读取SD卡的扇区 有关,最好掌握了SDIO读取SD卡扇区的基础之后再来看这一篇帖子。 写在前面的话 上一次发表了关于SDIO读取SD卡的一篇开发分享的帖子,今天呢就更进一步使用了FatFs文件系统。同样我们完全使用ST公司提供的STM32CubeMX软件和HAL库来进行开发。如果喜欢我的帖子请多多回复我会努力更新的。 我使用的工具 开发平台:正点原子探索者STM32F407开发板 硬件:使用了NUCLEO-F446RE开发板的ST-Li
[单片机]
如何使STM32CubeMX新建工程
运行STM32CubeMX工具。 单击新建项目或菜单-》文件-》新建项目。 从Board Selector部分,过滤以选择和使用 NUCLEO-L476RG 板: 检查Nucleo64类型。 检查MCU 系列上的STM32L4。 使用板选择器选择NUCLEO-L476RG板: 单击启动项目继续。 AnswerYes使用默认模式初始化所有外围设备?弹出窗口。 2引出线配置↑ 在SYS peripheral下的Pinout选项卡中验证SerialWire是否被选为 Debug 接口: 相应的引脚 PA13 和 PA14 已自动分配和配置。 选择板后,STM32CubeMX 允许自动为板设置引脚分配,包括通信接口、LED 和其他功能
[单片机]
如何使<font color='red'>STM32CubeMX</font>新建工程
STM32CubeMX学习教程之二:GPIO输入之外部中断
本篇主要讲述GPIO外部中断输入检测,实际物理输入方式是按键,但是实际上更适合外部设备信号的输入检测,物理按键输入因为有抖动,还需要硬件或者软件去抖才比较可靠。这个例子不考虑防抖的情况。 软件版本: STM32CubeMX V4.25.0 System Workbench V2.4 硬件:OneNet 麒麟座V2.3 在STM32CubeMX中新建项目,选择正确的MCU型号 设置RCC和SYS,然后根据板子实际情况设置时钟(麒麟座外部晶振是12M,STM32F103x的最高主频是72M) 根据板子的具体连接设置4个GPIO_OUTPUT (连接到LED)和4个GPIO_EXIT*(外部中断模式,
[单片机]
<font color='red'>STM32CubeMX</font>学习教程之二:GPIO输入之外部中断
STM32CubeMX系列 | 跑马灯
跑马灯 1. GPIO口简介 每个GPIO端口有两个32位配置寄存器(GPIOx_CRL和GPIOx_CRH),两个32位数据寄存器(GPIOx_IDR和GPIOx_ODR),一个32位置位/复位寄存器(GPIOx_BSRR),一个16位复位寄存器(GPIOx_BRR)和一个32位锁定寄存器(GPIOx_LCKR) GPIO端口的每个位可以由软件分别配置成多种模式:输入浮空、输入上拉、输入下拉、模拟输入、开漏输出、推挽式输出、推挽式复用功能以及开漏复用功能 I/O端口位的基本结构如下图示 2. 硬件设计 LED(D0~D7)一端接上拉3.3V,另一端依次接到STM32F103的PC0-PC7引脚,本实验只使用D1和D2,连接原
[单片机]
STM32 对内部FLASH读写接口函数
因为要用内部FLASH代替外部EEPROM,把参数放在STM32的0x08000000+320K处,其中20K是bootloader,300K是应用程序。 原理:先要把整页FLASH的内容搬到RAM中,然后在RAM中改动,然后擦除整页FLASH,再把改动后的内容写入原Flash页。下面程序调试通过。 /******************************************************************************* * Function Name : I2C_EE_BufferRead * Description : Reads a block of data from the
[单片机]
STM32CUBEMX(9)--ADC通过轮询方式读取,USART打印
概述 本章通过使用模数转换器(ADC),通过轮询方式采集多个ADC通道电压。 之前已经做过DMA方式采样,详情请查看: https://blog.csdn.net/qq_24312945/article/details/106557538 硬件准备 首先需要准备一个开发板,这里我准备的是NUCLEO-F030R8的开发板: 选择芯片型号 使用STM32CUBEMX选择芯片stm32f030r8,如下所示: 配置时钟源 HSE与LSE分别为外部高速时钟和低速时钟,在本文中使用内置的时钟源,故都选择Disable选项,如下所示: 配置时钟树 STM32F0的最高主频到48M,所以配置48即可: 串口配置 本次实验使
[单片机]
<font color='red'>STM32CUBEMX</font>(9)--ADC通过轮询方式读取,USART打印
HAL库 STM32CubeMX教程五----看门狗(独立看门狗,窗口看门狗)
前言: 今天我们来学习看门狗的配置与函数,看门狗可以有效解决程序的跑飞,在使用过程中比较常见,是防止芯片故障的有效外设,我们一起来学习下HAL库 STM32CubeMX的独立看门狗,窗口看门狗的使用。本系列教程将HAL库与STM32CubeMX结合在一起讲解,使您可以更快速的学会各个模块的使用 所用工具: 1、芯片: STM32F407ZET6 2、STM32CubeMx软件 3、IDE: MDK-Keil软件 4、STM32F1xx/STM32F4xxHAL库 知识概括: 通过本篇博客您将学到: STM32CubeMX创建看门狗例程 独立看门狗,靠窗看门狗 工作原理 看门狗 在由单片机构成的微型计算机系统中单片
[单片机]
HAL库 <font color='red'>STM32CubeMX</font>教程五----看门狗(独立看门狗,窗口看门狗)
小广播
设计资源 培训 开发板 精华推荐

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

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

换一换 更多 相关热搜器件
随便看看

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

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