STM32+CubeMX移植SPI协议驱动W25Q16FLash存储器

STM32+CubeMX移植SPI协议驱动W25Q16FLash存储器

  • SPI简介
    • 拓扑结构
    • 时钟相位(CPHA)和时钟极性( CPOL)
  • W25Q16简介
    • 什么是Flash,有什么特点?
    • W25Q16内部块、扇区、页的划分
    • 引脚定义
    • 通讯方式
    • 控制指令
    • 原理图
  • CubeMX配置
  • 驱动代码
  • 运行结果

W25Q16 是一种常见的串行闪存(Flash)存储器芯片,由 Winbond 公司生产。它是一种高性能、低功耗的闪存存储器,采用的是SPI通讯协议,想要想使用W25Q16,就必须要先会SPI协议。

SPI简介

SPI(Serial Peripheral Interface)是一种常用的串行通信协议,通常用于在数字集成电路之间进行通信,如微控制器、传感器、存储器、显示器等。SPI 协议定义了一种全双工、同步的串行数据传输方式,它通常由一个主设备(Master)和一个或多个从设备(Slave)组成。

SPI 协议的主要特点包括:

  • 全双工通信:主设备和从设备可以同时发送和接收数据,这使得 SPI 通信速度较快。

  • 同步通信:通信时钟由主设备生成,主设备控制数据传输的时序。这种同步方式可以提供较高的通信速率。

  • 多从设备支持:SPI 允许主设备与多个从设备进行通信,每个从设备都有一个片选信号(Chip Select),用于选择要通信的目标设备。

  • 单主多从结构:在标准的 SPI 总线中,通常只有一个主设备,多个从设备。但是,也有一些扩展协议支持多主设备的情况。

  • 串行传输:数据在时钟的边沿进行传输,可以是上升沿或下降沿,取决于设备的配置。

SPI 协议通常由以下几条信号线组成:

  • SCLK(Serial Clock):时钟信号,由主设备产生,用于同步数据传输。

  • MOSI(Master Out Slave In):主设备输出、从设备输入的数据线。

  • MISO(Master In Slave Out):主设备输入、从设备输出的数据线。

  • SS/CS(Slave Select/Chip Select):片选信号,由主设备控制,用于选择要通信的从设备。

由于本篇章的重点是W25Q16芯片介绍,所以这里不做过多的介绍SPI通讯协议,基础内容可以浏览:蓝桥杯单片机学习——SPI协议&DS1302实时时钟,这里介绍以下STM32上SPI的几个重要概念:

拓扑结构

在这里插入图片描述

时钟相位(CPHA)和时钟极性( CPOL)

  • 时钟极性( CPOL):在没有数据传输时时钟线的电平状态,0表示空闲时低电平,1表示空闲时高电平。
  • 时钟相位(CPHA:时钟线在第几个时钟边沿采集数据,0表示在第一个边沿开始采集,1表示在第二个边沿开始采集。
  • 不同的时钟相位和时钟极性,可以得到不同的SPI工作频率:
    在这里插入图片描述

W25Q16简介

W25Q16 是一种常见的串行闪存(Flash)存储器芯片,由 Winbond 公司生产。W25Q16采用的是SPI通讯协议,支持STM32的SPI数据传输时序0(CPOL = 0 ,CPHA = 0)和模式3(CPOL = 1,CPHA = 1),数据格式是长度为8位,MSB在前,LSB在后。它是一种高性能、低功耗的闪存存储器,具有以下特点:

  • 存储容量:W25Q16 芯片的存储容量为16 Megabit(Mb),相当于2 Megabyte(MB),其中1 Byte = 8 bit。

  • 接口:W25Q16 使用 SPI(Serial Peripheral Interface)串行接口进行通信,这使得它易于与各种微控制器和数字集成电路进行连接。

  • 工作电压:典型的工作电压为2.7V 至 3.6V,支持广泛的供电范围,适用于各种电源条件下的应用。

  • 工作温度:W25Q16 在工业级温度范围内(-40°C 至 +85°C)可靠工作,适用于工业和商业应用环境。

  • 封装:W25Q16 芯片通常采用表面贴装封装(Surface Mount Package),如8-pin SOIC(Small Outline Integrated Circuit)封装,方便集成到电路板上。

  • 快速擦除和编程:W25Q16 支持快速擦除和编程操作,使得数据更新和存储操作更加高效。

  • 多种保护功能:W25Q16 芯片提供了多种保护功能,如写保护功能、全片擦除保护、写使能锁定等,有助于提高数据的安全性和可靠性。

其他型号的Flas芯片,主要差距体现在存储器大小,比如W25Q32就是存储容量为32Mb = 4MB

什么是Flash,有什么特点?

  • FLASH是常用的用于储存数据的半导体器件,它具有容量大,可重复擦写、按“扇区/块”擦除、掉电后数据可继续保存的特性。
  • FLASH是有一个物理特性:只能写0,不能写1,写1靠擦除。
  • FLASH主要有NOR Flash和NAND Flash两种类型,NOR和NAND是两种数字门电路。
  • NOR Flash:基于字节读写,读取速度快,独立地址/数据线,无坏块,支持XIP,常见的应用有25QXX系列芯片和存储程序的ROM
  • NAND FLASH:基于块读写,读取速度稍慢,地址数据线共用,有坏块,不支持XIP,常见的应用有EMMC、SSD、U盘等

XIP:一般来说,处理器都是在flash上读取代码,到RAM里面执行,这样读取效率相对会低,而XIP则是支持处理器直接在FLash上读取程序并执行,可以提高运行效率。

W25Q16内部块、扇区、页的划分

在这里插入图片描述

  • W25Q16将内部的存储空间分为了32个块,每个块大小64KB
  • 每个块分为16个扇区,大小为4KB
  • 每个扇区分作16个页,每个页为256个字节,页是Flash读写操作的最小单位。
  • W25Q16共有16 * 16 * 32 = 8192个页

Flash存储器存在坏块的情况,当某一个页损坏而无法读写时,可能会导致整个块读写过程中某个数据读写异常而无法使用,且这种损坏是不可逆的,换句话来说,一个页损坏,会导致整个块无法正常读写,只能使用其他块进行读写,导致Flash容量下降。

引脚定义

在这里插入图片描述

  • /CS:片选信号,低电平有效,用作SPI通讯使用
  • DO(IO1):数据输出引脚(MISO)
  • /WP(IO2):写保护引脚,低电平有效
  • GND:接地
  • DI(IO4):数据输入引脚(MOSI)
  • CLK:SPI时钟引脚
  • /HOLD(IO3):暂停通讯引脚,高电平有效
  • VCC:电源引脚

通讯方式

控制指令

在对W25Q16进行操作之前,需要先发送指令,以控制W25Q16,以下是所有的指令集:

在这里插入图片描述
在这里插入图片描述

通常,我们对W25Q16进行读写操作,只需要以下几条指令即可完成:

在这里插入图片描述

原理图

在这里插入图片描述

CubeMX配置

  1. SPI配置:注意芯片引脚和原理图对应上。
    在这里插入图片描述

  2. GPIO配置:配置的是片选信号,使其默认高电平即可。
    在这里插入图片描述

  3. 定时器配置:用于试下微妙延时,以实现W25Q16的底层代码时序要求。
    在这里插入图片描述

  4. 其他部分的配置,自行完成,由于不涉及W25Q16的驱动,所以不做展示。

驱动代码

关于驱动代码,这里我是采用的正点原子提供的源码,然后修改以一部分,用作CubeMX配置移植使用,仅作个人学习使用!!!

  1. norflash.c
#include "norflash.h"
#include "spi.h"
#include "delay.h"
#include "usart.h"
#include "stm32f4xx_hal_gpio.h"
//
//本程序只供学习使用,未经作者许可,不得用于其它任何用途
//ALIENTEK NANO STM32F4开发板
//W25QXX驱动代码
//正点原子@ALIENTEK
//技术论坛:www.openedv.com
//创建日期:2019/4/23
//版本:V1.0
//版权所有,盗版必究。
//Copyright(C) 广州市星翼电子科技有限公司 2019-2029
//All rights reserved
//

uint16_t NORFLASH_TYPE = NM25Q16; //默认就是NM25Q16

//4Kbytes为一个Sector
//16个扇区为1个Block
//W25X16
//容量为2M字节,共有32个Block,512个Sector

//初始化SPI FLASH的IO口
void Norflash_Init(void)
{
//    GPIO_InitTypeDef GPIO_Initure;

//    __HAL_RCC_GPIOB_CLK_ENABLE();           //使能GPIOB时钟

//    GPIO_Initure.Pin = GPIO_PIN_12;         //PB12
//    GPIO_Initure.Mode = GPIO_MODE_OUTPUT_PP; //推挽输出
//    GPIO_Initure.Pull = GPIO_PULLUP;        //上拉
//    GPIO_Initure.Speed = GPIO_SPEED_HIGH;   //高速
//    HAL_GPIO_Init(GPIOB, &GPIO_Initure);    //初始化

    NORFLASH_CS = 1;			              //SPI FLASH不选中
//    SPI2_Init();		   			        //初始化SPI
//    SPI2_SetSpeed(SPI_BAUDRATEPRESCALER_4); //设置为24M时钟,高速模式
    NORFLASH_TYPE = Norflash_ReadID();	      //读取FLASH ID.
}

//读取SPI_FLASH的状态寄存器
//BIT7  6   5   4   3   2   1   0
//SPR   RV  TB BP2 BP1 BP0 WEL BUSY
//SPR:默认0,状态寄存器保护位,配合WP使用
//TB,BP2,BP1,BP0:FLASH区域写保护设置
//WEL:写使能锁定
//BUSY:忙标记位(1,忙;0,空闲)
//默认:0x00
uint8_t Norflash_ReadSR(void)
{
    uint8_t byte = 0;
    NORFLASH_CS = 0;                          //使能器件
    SPI2_ReadWriteByte(FLASH_ReadStatusReg);    //发送读取状态寄存器命令
    byte = SPI2_ReadWriteByte(0Xff);           //读取一个字节
    NORFLASH_CS = 1;                          //取消片选
    return byte;
}
//写SPI_FLASH状态寄存器
//只有SPR,TB,BP2,BP1,BP0(bit 7,5,4,3,2)可以写!!!
void Norflash_Write_SR(uint8_t sr)
{
    NORFLASH_CS = 0;                          //使能器件
    SPI2_ReadWriteByte(FLASH_WriteStatusReg);   //发送写取状态寄存器命令
    SPI2_ReadWriteByte(sr);               //写入一个字节
    NORFLASH_CS = 1;                          //取消片选
}
//SPI_FLASH写使能
//将WEL置位
void Norflash_Write_Enable(void)
{
    NORFLASH_CS = 0;                          //使能器件
    SPI2_ReadWriteByte(FLASH_WriteEnable);      //发送写使能
    NORFLASH_CS = 1;                          //取消片选
}
//SPI_FLASH写禁止
//将WEL清零
void Norflash_Write_Disable(void)
{
    NORFLASH_CS = 0;                          //使能器件
    SPI2_ReadWriteByte(FLASH_WriteDisable);     //发送写禁止指令
    NORFLASH_CS = 1;                          //取消片选
}
//读取芯片ID W25X16的ID:0XEF14
uint16_t Norflash_ReadID(void)
{
    uint16_t Temp = 0;
    NORFLASH_CS = 0;
    SPI2_ReadWriteByte(0x90);//发送读取ID命令
    SPI2_ReadWriteByte(0x00);
    SPI2_ReadWriteByte(0x00);
    SPI2_ReadWriteByte(0x00);
    Temp |= SPI2_ReadWriteByte(0xFF) << 8;
    Temp |= SPI2_ReadWriteByte(0xFF);
    NORFLASH_CS = 1;
    return Temp;
}
//读取SPI FLASH
//在指定地址开始读取指定长度的数据
//pBuffer:数据存储区
//ReadAddr:开始读取的地址(24bit)
//NumByteToRead:要读取的字节数(最大65535)
void Norflash_Read(uint8_t *pBuffer, uint32_t ReadAddr, uint16_t NumByteToRead)
{
    uint16_t i;
    NORFLASH_CS = 0;                          //使能器件
    SPI2_ReadWriteByte(FLASH_ReadData);         //发送读取命令
    SPI2_ReadWriteByte((uint8_t)((ReadAddr) >> 16)); //发送24bit地址
    SPI2_ReadWriteByte((uint8_t)((ReadAddr) >> 8));
    SPI2_ReadWriteByte((uint8_t)ReadAddr);

    for (i = 0; i < NumByteToRead; i++)
    {
        pBuffer[i] = SPI2_ReadWriteByte(0XFF); //循环读数
    }

    NORFLASH_CS = 1;                          //取消片选
}
//SPI在一页(0~65535)内写入少于256个字节的数据
//在指定地址开始写入最大256字节的数据
//pBuffer:数据存储区
//WriteAddr:开始写入的地址(24bit)
//NumByteToWrite:要写入的字节数(最大256),该数不应该超过该页的剩余字节数!!!
void Norflash_Write_Page(uint8_t *pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite)
{
    uint16_t i;
    Norflash_Write_Enable();                  //SET WEL
    NORFLASH_CS = 0;                          //使能器件
    SPI2_ReadWriteByte(FLASH_PageProgram);      //发送写页命令
    SPI2_ReadWriteByte((uint8_t)((WriteAddr) >> 16)); //发送24bit地址
    SPI2_ReadWriteByte((uint8_t)((WriteAddr) >> 8));
    SPI2_ReadWriteByte((uint8_t)WriteAddr);

    for (i = 0; i < NumByteToWrite; i++)SPI2_ReadWriteByte(pBuffer[i]); //循环写数

    NORFLASH_CS = 1;                          //取消片选
    Norflash_Wait_Busy();					   //等待写入结束
}
//无检验写SPI FLASH
//必须确保所写的地址范围内的数据全部为0XFF,否则在非0XFF处写入的数据将失败!
//具有自动换页功能
//在指定地址开始写入指定长度的数据,但是要确保地址不越界!
//pBuffer:数据存储区
//WriteAddr:开始写入的地址(24bit)
//NumByteToWrite:要写入的字节数(最大65535)
//CHECK OK
void Norflash_Write_NoCheck(uint8_t *pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite)
{
    uint16_t pageremain;
    pageremain = 256 - WriteAddr % 256; //单页剩余的字节数

    if (NumByteToWrite <= pageremain)pageremain = NumByteToWrite; //不大于256个字节

    while (1)
    {
        Norflash_Write_Page(pBuffer, WriteAddr, pageremain);

        if (NumByteToWrite == pageremain)break; //写入结束了
        else //NumByteToWrite>pageremain
        {
            pBuffer += pageremain;
            WriteAddr += pageremain;

            NumByteToWrite -= pageremain;			 //减去已经写入了的字节数

            if (NumByteToWrite > 256)pageremain = 256; //一次可以写入256个字节
            else pageremain = NumByteToWrite; 	 //不够256个字节了
        }
    };
}
//写SPI FLASH
//在指定地址开始写入指定长度的数据
//该函数带擦除操作!
//pBuffer:数据存储区
//WriteAddr:开始写入的地址(24bit)
//NumByteToWrite:要写入的字节数(最大65535)
uint8_t W25QXX_BUFFER[4096];
void Norflash_Write(uint8_t *pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite)
{
    uint32_t secpos;
    uint16_t secoff;
    uint16_t secremain;
    uint16_t i;

    secpos = WriteAddr / 4096; //扇区地址 0~511 for w25x16
    secoff = WriteAddr % 4096; //在扇区内的偏移
    secremain = 4096 - secoff; //扇区剩余空间大小

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

    while (1)
    {
        Norflash_Read(W25QXX_BUFFER, secpos * 4096, 4096); //读出整个扇区的内容

        for (i = 0; i < secremain; i++) //校验数据
        {
            if (W25QXX_BUFFER[secoff + i] != 0XFF)break; //需要擦除
        }

        if (i < secremain) //需要擦除
        {
            Norflash_Erase_Sector(secpos);//擦除这个扇区

            for (i = 0; i < secremain; i++)	 //复制
            {
                W25QXX_BUFFER[i + secoff] = pBuffer[i];
            }

            Norflash_Write_NoCheck(W25QXX_BUFFER, secpos * 4096, 4096); //写入整个扇区

        }
        else Norflash_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;			//下一个扇区可以写完了
        }
    }
}
//擦除整个芯片
//整片擦除时间:
//W25X16:25s
//W25X32:40s
//W25X64:40s
//等待时间超长...
void Norflash_Erase_Chip(void)
{
    Norflash_Write_Enable();                  //SET WEL
    Norflash_Wait_Busy();
    NORFLASH_CS = 0;                          //使能器件
    SPI2_ReadWriteByte(FLASH_ChipErase);        //发送片擦除命令
    NORFLASH_CS = 1;                          //取消片选
    Norflash_Wait_Busy();   				   //等待芯片擦除结束
}
//擦除一个扇区
//Dst_Addr:扇区地址 0~511 for w25x16
//擦除一个山区的最少时间:150ms
void Norflash_Erase_Sector(uint32_t Dst_Addr)
{
    Dst_Addr *= 4096;
    Norflash_Write_Enable();                  //SET WEL
    Norflash_Wait_Busy();
    NORFLASH_CS = 0;                          //使能器件
    SPI2_ReadWriteByte(FLASH_SectorErase);      //发送扇区擦除指令
    SPI2_ReadWriteByte((uint8_t)((Dst_Addr) >> 16)); //发送24bit地址
    SPI2_ReadWriteByte((uint8_t)((Dst_Addr) >> 8));
    SPI2_ReadWriteByte((uint8_t)Dst_Addr);
    NORFLASH_CS = 1;                          //取消片选
    Norflash_Wait_Busy();   				   //等待擦除完成
}
//等待空闲
void Norflash_Wait_Busy(void)
{
    while ((Norflash_ReadSR() & 0x01) == 0x01); // 等待BUSY位清空
}
//进入掉电模式
void Norflash_PowerDown(void)
{
    NORFLASH_CS = 0;                          //使能器件
    SPI2_ReadWriteByte(FLASH_PowerDown);        //发送掉电命令
    NORFLASH_CS = 1;                          //取消片选
    delay_us(3);                               //等待TPD
}
//唤醒
void Norflash_WAKEUP(void)
{
    NORFLASH_CS = 0;                          //使能器件
    SPI2_ReadWriteByte(FLASH_ReleasePowerDown);   //  send W25X_PowerDown command 0xAB
    NORFLASH_CS = 1;                          //取消片选
    delay_us(3);                               //等待TRES1
}



  1. norflash.h
#ifndef __NORFLASH_H
#define __NORFLASH_H  
#include "main.h"
//	 
//本程序只供学习使用,未经作者许可,不得用于其它任何用途
//ALIENTEK NANO STM32F4开发板
//W25QXX驱动代码	   
//正点原子@ALIENTEK
//技术论坛:www.openedv.com
//创建日期:2019/4/23
//版本:V1.0
//版权所有,盗版必究。
//Copyright(C) 广州市星翼电子科技有限公司 2019-2029
//All rights reserved									  
//


//W25X系列/Q系列芯片列表
#define W25Q80 	    0XEF13          /* W25Q80   芯片ID */
#define W25Q16 	    0XEF14          /* W25Q16   芯片ID */
#define W25Q32 	    0XEF15          /* W25Q32   芯片ID */
#define W25Q64 	    0XEF16          /* W25Q64   芯片ID */
#define W25Q128	    0XEF17          /* W25Q128  芯片ID */
#define NM25Q16     0X6814          /* NM25Q16  芯片ID */
#define NM25Q64     0X5216          /* NM25Q64  芯片ID */
#define NM25Q128    0X5217          /* NM25Q128 芯片ID */
#define BY25Q64     0X6816          /* BY25Q64  芯片ID */
#define BY25Q128    0X6817          /* BY25Q128 芯片ID */

extern uint16_t NORFLASH_TYPE;//定义我们使用的flash芯片型号

#define	NORFLASH_CS PBout(12)  //W25QXX的片选信号 

extern uint8_t W25QXX_BUFFER[4096];

//指令表
#define FLASH_WriteEnable       0x06
#define FLASH_WriteDisable      0x04
#define FLASH_ReadStatusReg     0x05
#define FLASH_WriteStatusReg    0x01
#define FLASH_ReadData          0x03
#define FLASH_FastReadData      0x0B
#define FLASH_FastReadDual      0x3B
#define FLASH_PageProgram       0x02
#define FLASH_BlockErase        0xD8
#define FLASH_SectorErase       0x20
#define FLASH_ChipErase         0xC7
#define FLASH_PowerDown         0xB9
#define FLASH_ReleasePowerDown  0xAB
#define FLASH_DeviceID          0xAB
#define FLASH_ManufactDeviceID  0x90
#define FLASH_JedecDeviceID     0x9F

void Norflash_Init(void);
uint16_t  Norflash_ReadID(void);  	      //读取FLASH ID
uint8_t	 Norflash_ReadSR(void);         //读取状态寄存器
void Norflash_Write_SR(uint8_t sr);  	  //写状态寄存器
void Norflash_Write_Enable(void);   //写使能
void Norflash_Write_Disable(void);  //写保护
void Norflash_Read(uint8_t *pBuffer, uint32_t ReadAddr, uint16_t NumByteToRead); //读取flash
void Norflash_Write(uint8_t *pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite); //写入flash
void Norflash_Erase_Chip(void);    	   //整片擦除
void Norflash_Erase_Sector(uint32_t Dst_Addr);//扇区擦除
void Norflash_Wait_Busy(void);           //等待空闲
void Norflash_PowerDown(void);           //进入掉电模式
void Norflash_WAKEUP(void);			   //唤醒

#endif


  1. spi.c:在spi.c中添加以下内容。同时注意在spi.h中声明该函数
/SPI2 读写一个字节
//TxData:要写入的字节
//返回值:读取到的字节
u8 SPI2_ReadWriteByte(u8 TxData)
{
    u8 Rxdata;
    HAL_SPI_TransmitReceive(&hspi2,&TxData,&Rxdata,1, 1000);       
 	return Rxdata;          		    //返回收到的数据		
}
  1. delay.c
// Core\Src\delay.c
#include "tim.h"
#include "delay.h"
/**
 * @brief    微秒延时
 * @param    Delay_us  —— 指定延迟时间长度,单位为微秒。
 * @retval   None
 */
void delay_us(uint32_t Delay_us) 
{
		__HAL_TIM_SetCounter(&htim11, 0);
		__HAL_TIM_ENABLE(&htim11);
		while(__HAL_TIM_GetCounter(&htim11) < Delay_us);
		/* Disable the Peripheral */
		__HAL_TIM_DISABLE(&htim11);
}


  1. delay.h
// Core\Inc\delay.h
#ifndef __DELAY_H__
#define __DELAY_H__


#include "main.h"
#include "tim.h"

void delay_us(uint32_t Delay_us);

#endif /* __DELAY_H__ */


  1. main.h:添加以下代码
//定义一些常用的数据类型短关键字 
typedef int32_t  s32;
typedef int16_t s16;
typedef int8_t  s8;

typedef const int32_t sc32;  
typedef const int16_t sc16;  
typedef const int8_t sc8;  

typedef __IO int32_t  vs32;
typedef __IO int16_t  vs16;
typedef __IO int8_t   vs8;

typedef __I int32_t vsc32;  
typedef __I int16_t vsc16; 
typedef __I int8_t vsc8;   

typedef uint32_t  u32;
typedef uint16_t u16;
typedef uint8_t  u8;

typedef const uint32_t uc32;  
typedef const uint16_t uc16;  
typedef const uint8_t uc8; 

typedef __IO uint32_t  vu32;
typedef __IO uint16_t vu16;
typedef __IO uint8_t  vu8;

typedef __I uint32_t vuc32;  
typedef __I uint16_t vuc16; 
typedef __I uint8_t vuc8;  

//位带操作,实现51类似的GPIO控制功能
//具体实现思想,参考<<CM3权威指南>>第五章(87页~92页).
//IO口操作宏定义
#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2)) 
#define MEM_ADDR(addr)  *((volatile unsigned long  *)(addr)) 
#define BIT_ADDR(addr, bitnum)   MEM_ADDR(BITBAND(addr, bitnum)) 
//IO口地址映射
#define GPIOA_ODR_Addr    (GPIOA_BASE+20) //0x40020014
#define GPIOB_ODR_Addr    (GPIOB_BASE+20) //0x40020414 
#define GPIOC_ODR_Addr    (GPIOC_BASE+20) //0x40020814 
#define GPIOD_ODR_Addr    (GPIOD_BASE+20) //0x40020C14     

#define GPIOA_IDR_Addr    (GPIOA_BASE+16) //0x40020010 
#define GPIOB_IDR_Addr    (GPIOB_BASE+16) //0x40020410 
#define GPIOC_IDR_Addr    (GPIOC_BASE+16) //0x40020810 
#define GPIOD_IDR_Addr    (GPIOD_BASE+16) //0x40020C10 

//IO口操作,只对单一的IO口!
//确保n的值小于16!
#define PAout(n)   BIT_ADDR(GPIOA_ODR_Addr,n)  //输出 
#define PAin(n)    BIT_ADDR(GPIOA_IDR_Addr,n)  //输入 

#define PBout(n)   BIT_ADDR(GPIOB_ODR_Addr,n)  //输出 
#define PBin(n)    BIT_ADDR(GPIOB_IDR_Addr,n)  //输入 

#define PCout(n)   BIT_ADDR(GPIOC_ODR_Addr,n)  //输出 
#define PCin(n)    BIT_ADDR(GPIOC_IDR_Addr,n)  //输入 

#define PDout(n)   BIT_ADDR(GPIOD_ODR_Addr,n)  //输出 
#define PDin(n)    BIT_ADDR(GPIOD_IDR_Addr,n)  //输入 
  1. main.c
//要写入到W25Q16的字符串数组
const uint8_t TEXT_Buffer[] = {"Hello,The man who don't write code!"};
#define SIZE sizeof(TEXT_Buffer)
#define  FLASH_SIZE  2 * 1024 * 1024	//FLASH 大小为2M字节;

int main(void)
{
  /* USER CODE BEGIN 1 */
//      uint8_t len;
	uint8_t datatemp[100]; //flash读取到的内容
  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */
 
  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART1_UART_Init();
  MX_SPI2_Init();
  MX_TIM11_Init();
  /* USER CODE BEGIN 2 */
    HAL_UART_Receive_IT(&huart1, (unsigned char* )aRxBuffer, 1); //串口接收中断,用作调试
   Norflash_Init();				    //W25QXX初始化
    printf("SPI TEST\r\n");
	 NORFLASH_TYPE = Norflash_ReadID();//读取FLASH ID
	 printf("id:%#x\r\n",NORFLASH_TYPE); 
	 
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
//       if(USART_RX_STA&0x8000)
//		{					   
//			len=USART_RX_STA&0x3fff;//得到此次接收到的数据长度
//		printf("\r\n您发送的消息为:\r\n");
//			HAL_UART_Transmit(&huart1,(uint8_t*)USART_RX_BUF,len,1000);	//发送接收到的数据
//			while(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_TC)!=SET);		//等待发送结束
//		printf("\r\n\r\n");//插入换行
//			USART_RX_STA=0;
//		}
		
		 Norflash_Write((uint8_t *)TEXT_Buffer, FLASH_SIZE - 100, sizeof(TEXT_Buffer));	//从倒数第100个地址处开始,写入SIZE长度的数据
		HAL_Delay(1000);
		printf("Write:%s\r\n", TEXT_Buffer); //显示读到的字符串
		
		Norflash_Read(datatemp, FLASH_SIZE - 100, sizeof(TEXT_Buffer));	//从倒数第100个地址处开始,读出SIZE个字节
		HAL_Delay(1000);
		printf("Read:%s\r\n", datatemp); //显示读到的字符串
		
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

以上就是所有W25Q16的驱动代码,仅供个人学习哈,如果有哪里有误,还请斧正。

运行结果

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/641237.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Linux】Linux下centos更换国内yum源

&#x1f331;博客主页&#xff1a;青竹雾色间 &#x1f331;系列专栏&#xff1a;Linux &#x1f618;博客制作不易欢迎各位&#x1f44d;点赞⭐收藏➕关注 目录 1. 备份旧的 YUM 源文件2. 下载国内的 YUM 源文件阿里云&#xff1a;网易&#xff1a; 3. 清理 YUM 缓存4. 更新…

【C语言】走进指针世界(下卷)

前言 在“走进指针世界&#xff08;上卷&#xff09;”中&#xff0c;我们已经说过&#xff1a;什么是指针、内存和地址&#xff0c;指针的使用、声明、初始化&#xff0c;取地址运算符、解引用运算符以及这两者关系&#xff0c;还有指针赋值。 在正式使用指针进行各种代码的…

海外动态IP代理如何提高效率?

动态住宅IP代理之所以能够有效提升数据爬取的效率和准确性&#xff0c;主要归功于其提供的IP地址具有高度的匿名性和真实性。这些IP地址来自于真实的用户网络&#xff0c;因此相比于数据中心IP&#xff0c;它们更不容易被网站的安全系统标识为爬虫。此外&#xff0c;由于IP地址…

C:通过fwrite和fread读写数据结构

1.头文件 #include <stdio.h> 2.fopen()函数 调用fopen()函数可以打开或创建一个文件。 FILE *fopen(const char *path, const char *mode); path &#xff1a; 参数 path 指向文件路径&#xff0c;可以是绝对路径、也可以是相对路径。 mode &#xff1a; 参数 mode …

React useState数组新增和删除项

在React中&#xff0c;我们可以使用useState钩子来管理组件的状态&#xff0c;其中包括数组。如何在React函数组件中对数组进行增加和删除项的操作&#xff1f; 新增项时&#xff1a;我们可以对原数组进行解构&#xff0c;并把新增项一起加入到新数组&#xff1b; 删除项时&…

JDBC(Java DataBase Connectivity)Java数据库连接

JDBC(Java DataBase Connectivity) Java 语言连接数据库 再本模块中,java提供里一组用于连接数据库的类和接口Java 语言开发者,本身没有提供如何具体连接数据库的功能只是定义了一组java程序连接数据库的访问接口 连接到数据库向数据库发送增,修改,删除这一类的sql发送查询sq…

CATIA入门操作——为什么大佬的工具栏是水平的?如何把工具栏变水平?

目录 引出工具栏怎么变成水平&#xff1f;总结发生肾么事了&#xff1f;&#xff1f;鼠标中键旋转不了解决&#xff1a;特征树不显示参数关系 我的窗口去哪了&#xff1f;插曲&#xff1a;草图工具的调出插曲&#xff1a;颜色工具栏显示 弹窗警告警告&#xff1a;创建约束是临时…

linux系统介绍

Linux是一种免费使用和自由传播的类Unix操作系统&#xff0c;它是一个基于POSIX和Unix的多用户、多任务、支持多线程和多CPU的操作系统。Linux起源于1991年&#xff0c;由芬兰程序员林纳斯托瓦兹&#xff08;Linus Torvalds&#xff09;创建&#xff0c;并迅速获得了全球开发者…

香橙派华为昇腾CANN架构编译opencv4.9

香橙派华为升腾AI盒子 为啥要编译opencv4.9.0&#xff0c; 因为在4.9.0 中增加了华为昇腾CANN的外接开发库&#xff0c;下图为盒子外观&#xff0c;此次一接到这个盒子&#xff0c;立刻开始开箱操作&#xff0c;首先就是要编译opencv4.9&#xff0c;以前在香橙派3588 的盒子中…

抖音极速版:抖音轻量精简版本,新人享大福利

和快手一样&#xff0c;抖音也有自己的极速版&#xff0c;可视作抖音的轻量精简版&#xff0c;更专注于刷视频看广告赚钱&#xff0c;收益比抖音要高&#xff0c;可玩性更佳。 抖音极速版简介 抖音极速版是一个提供短视频创业和收益任务的平台&#xff0c;用户可以通过观看广…

当前时机是否适合进入 AIGC 行业:行业发展与市场需求分析

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

下一代Docker会让部署更丝滑吗

下一代Docker会让部署更丝滑吗 如何通俗易懂的理解DockerDocker有什么缺点Docker与AI结合&#xff0c;会让部署更加丝滑吗 随着互联网技术的不断发展&#xff0c;单机系统已经无法满足日益正常的用户量以及正常处理用户请求&#xff0c;这个时候就需要进行多机部署&#xff0c;…

贝锐向日葵打造农机设备远程运维支持方案

当物联网“万物互联”的概念向第一产业赋能&#xff0c;农机设备的智能化程度也越来越高。 所谓农业物联网&#xff0c;即在应用层将大量的传感器节点构成监控网络&#xff0c;通过各种传感器采集信息&#xff0c;以帮助农民及时发现问题&#xff0c;并准确地判定发生问题的位…

1099: 希尔排序算法实现

解法&#xff1a; 希尔增量选定n/2&#xff0c; #include<iostream> #include<vector> using namespace std; int main() {int n;cin >> n;vector<int> vec(n);for (int i 0; i < n; i) cin >> vec[i];int d n / 2;for (int i 0; i <…

【C语言】指针运算

前言 前面在“走进指针世界”中我已经讲解过指针相关的很多前置知识&#xff0c;其实还有一个很重要的部分就是指针的运算。这篇博客&#xff0c;就让我们一起了解一下指针的运算吧&#xff01; 指针作为变量&#xff0c;是可以进行算术运算的&#xff0c;只不过情况会和整型…

上门服务系统开发|东邻到家系统|上门服务系统开发流程

上门服务小程序的开发流程是一个复杂且精细的过程&#xff0c;涉及到需求分析、设计规划、开发实施、测试验收以及上线运营等多个环节。下面将详细介绍上门服务小程序的开发流程&#xff0c;帮助读者全面了解并掌握其中的关键步骤。 一、需求分析 在开发上门服务小程序之前&am…

RedisTemplateAPI:String

文章目录 ⛄1 String 介绍⛄2 命令⛄3 对应 RedisTemplate API❄️❄️ 3.1 添加缓存❄️❄️ 3.2 设置过期时间(单独设置)❄️❄️ 3.3 获取缓存值❄️❄️ 3.4 删除key❄️❄️ 3.5 顺序递增❄️❄️ 3.6 顺序递减 ⛄4 以下是一些常用的API⛄5 应用场景 ⛄1 String 介绍 Str…

hive3从入门到精通(一)

Hive3入门至精通(基础、部署、理论、SQL、函数、运算以及性能优化)1-14章 第1章:数据仓库基础理论 1-1.数据仓库概念 数据仓库&#xff08;英语&#xff1a;Data Warehouse&#xff0c;简称数仓、DW&#xff09;,是一个用于存储、分析、报告的数据系统。 数据仓库的目的是构…

世界上首位AI程序员诞生,AI将成为人类的对手吗?

3月13日&#xff0c;世界上第一位AI程序员Devin诞生&#xff0c;不仅能自主学习新技术&#xff0c;自己改Bug&#xff0c;甚至还能训练和微调自己的AI模型&#xff0c;表现已然远超GPT-4等“顶流选手”。 AI的学习速度如此之快&#xff0c;人类的教育能否跟上“机器学习”的速…

3100点失守,小丑竟是我自己

敏感时刻&#xff0c;亮剑的我们股市买单&#xff0c;海的那边反倒还涨了&#xff0c;好生气啊&#xff01;就连我们胡主编昨晚都气得睡不着&#xff0c;一点多了还在那抒发情感&#xff0c; 确实&#xff0c;有句话说到心窝里了&#xff1a;小散们是拿真金白银撑场子的&#x…