首页 > 其他分享 >【江协STM32】11-2/3 W25Q64简介、软件SPI读写W25Q64

【江协STM32】11-2/3 W25Q64简介、软件SPI读写W25Q64

时间:2025-01-14 09:29:19浏览次数:3  
标签:11 void W25Q64 MySPI STM32 SwapByte GPIO define

1. W25Q64简介

  • W25Qxx系列是一种低成本、小型化、使用简单的非易失性存储器,常应用于数据存储、字库存储、固件程序存储等场景
  • 存储介质:Nor Flash(闪存)
  • 时钟频率:80MHz / 160MHz (Dual SPI) / 320MHz (Quad SPI)
  • 存储容量(24位地址):
        W25Q40:      4Mbit / 512KByte
        W25Q80:      8Mbit / 1MByte
        W25Q16:      16Mbit / 2MByte
        W25Q32:      32Mbit / 4MByte
        W25Q64:      64Mbit / 8MByte
        W25Q128:  128Mbit / 16MByte
        W25Q256:  256Mbit / 32MByte

1.1 硬件电路

CS上面画条横线,或者左边画"/",表示低电平有效。

1.2 W25Q64框图

W25Q64的容量是8MB,如果不进行划分,只按照一整块来使用, 则容量太大,不利于管理,所以需要进行划分。常见划分方式为,一整块存储空间,先划分为若干的块Block,其中每一块再划分为若干的扇区Sector,对于每个扇区,内部又可以分成很多页Page。

1.3 Flash操作注意事项

写入操作时:

  • 写入操作前,必须先进行写使能
  • 每个数据位只能由1改写为0,不能由0改写为1
    比如,在某一个字节的存储单元内,存储了0xAA(1010 1010)这个数据,如果直接再次在这个存储单元写入新的数据0x55(0101 0101),则这个存储单元最终的数据为0x00
  • 写入数据前必须先擦除,擦除后,所有数据位变为1
    比如,擦除后所有位变成1,也就是0xFF(1111 1111),此时写入0x55(0101 0101),这样根据第二条规则,存储单元最终的数据为0x55
  • 擦除必须按最小擦除单元进行
    在W25Q64中,可以选择整个芯片擦除、按块擦除、按扇区擦除,所以最小的擦除单元就是一个扇区(4KB,就是4096个字节),所以擦除,最少就需要4096个字节一起擦
  • 连续写入多字节时,最多写入一页的数据,超过页尾位置的数据,会回到页首覆盖写入
    因为页缓存区只有256Byte
  • 写入操作结束后,芯片进入忙状态,不响应新的读写操作

读取操作时:

  • 直接调用读取时序,无需使能,无需额外操作,没有页的限制,读取操作结束后不会进入忙状态,但不能在忙状态时读取

2. 软件SPI读写W25Q64

2.1 接线图

SPI的四根通信线CS、DO、CLK和DI,因为这里使用软件模拟SPI,所以这4根线可以接到STM32的任意GPIO口。

CS(片选)接到PA4、DO(从机输出)接到PA6、CLK(时钟)接到PA5、DI(从机输入)接到PA7

2.2 代码

先建立一个MySPI模块,这个模块中主要包含通信引脚封装、初始化,以及SPI通信的3个拼图(起始、终止和交换一个字节)。

然后基于SPI层,再建立一个W25Q64的模块,在此模块中,调用底层SPI的拼图,来拼接各种指令和功能的完整时序,比如写使能、擦除、页编程、读数据等。这一层可以称为W25Q64的硬件驱动层。

最后,在主函数中调用驱动层的函数,实现想要的功能。

MySPI.c

#include "stm32f10x.h"                  // Device header

//  写SS/CS引脚
void MySPI_W_SS(uint8_t BitValue)
{
    GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)BitValue);
}

//  写SCK/CLK引脚
void MySPI_W_SCK(uint8_t BitValue)
{
    GPIO_WriteBit(GPIOA, GPIO_Pin_5, (BitAction)BitValue);
}

//  写MOSI/DI引脚
void MySPI_W_MOSI(uint8_t BitValue)
{
    GPIO_WriteBit(GPIOA, GPIO_Pin_7, (BitAction)BitValue);
}

//  读MISO/DO引脚
uint8_t MySPI_R_MISO(void)
{
    return GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6);
}

void MySPI_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;//	输出引脚配置为推挽输出
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_7;//	CS/SS(PA4),CLK/SCK(PA5),DI/MOSI(PA7)
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//	输入引脚配置为上拉输入
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;//	DO/MISO(PA6)
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    MySPI_W_SS(1);//    默认高电平,不选中从机
    MySPI_W_SCK(0);//   使用SPI模式0,所以默认是低电平
}

//  时序基本单元:起始条件
void MySPI_Start(void)
{
    MySPI_W_SS(0);
}

//  时序基本单元:终止条件
void MySPI_Stop(void)
{
    MySPI_W_SS(1);
}

//  时序基本单元:交换一个字节(模式0)
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{
    uint8_t i,ByteReceive = 0x00;
    
    for(i = 0; i < 8; i++)
    {
        MySPI_W_MOSI(ByteSend & (0x80 >> i));//    发送ByteSend最高位、次高位...
        MySPI_W_SCK(1);//   产生上升沿。上升沿时,从机会自动把MOSI的数据读走
        if(MySPI_R_MISO() == 1){ByteReceive |= (0x80 >> i);}// 如果读到MISO的数据位是1,则把最高位、次高位...存到ByteReceive中
        MySPI_W_SCK(0);//   产生下降沿。
    }
    
    return ByteReceive;
}

MySPI.h

#ifndef __MYSPI_H
#define __MYSPI_H

void MySPI_Init(void);
void MySPI_Start(void);
void MySPI_Stop(void);
uint8_t MySPI_SwapByte(uint8_t ByteSend);

#endif

W25Q64.c

#include "stm32f10x.h"                  // Device header
#include "MySPI.h"
#include "W25Q64_Ins.h"

void W25Q64_Init(void)
{
    MySPI_Init();
}

//  读取ID。因为函数有2个返回值,所以使用指针实现
//  MID为厂商ID,DID为设备ID
void W25Q64_ReadID(uint8_t *MID, uint16_t *DID)
{
    MySPI_Start();//    SS引脚置低,开始传输
    MySPI_SwapByte(W25Q64_JEDEC_ID);// 发送0x9F(宏定义W25Q64_JEDEC_ID),返回值不使用。0x9F根据手册,代表读ID号指令
    *MID = MySPI_SwapByte(W25Q64_DUMMY_BYTE);// 0xFF无意义(宏定义W25Q64_DUMMY_BYTE),这句的作用是把从机有意义的数据置换过来
    *DID = MySPI_SwapByte(W25Q64_DUMMY_BYTE);// 0xFF无意义,置换出设备ID的高8位
    *DID <<= 8;//   把第一次读到的数据运到DID的高8位
    *DID |= MySPI_SwapByte(W25Q64_DUMMY_BYTE);// 0xFF无意义,置换出设备ID的低8位
    MySPI_Stop();
}

//  写使能
void W25Q64_WriteEnable(void)
{
    MySPI_Start();
    MySPI_SwapByte(W25Q64_WRITE_ENABLE);
    MySPI_Stop();
}

//  等待忙
void W25Q64_WaitBusy(void)
{
    uint32_t Timeout;
    MySPI_Start();
    MySPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1);
    Timeout = 100000;// 给定超时计数时间
    while((MySPI_SwapByte(W25Q64_DUMMY_BYTE) & 0x01) == 0x01)//    返回值是状态寄存器1,取出最低位
    {
		Timeout --;//   等待时,计数值自减
		if (Timeout == 0)// 自减到0后,等待超时
		{
			/*超时的错误处理代码,可以添加到此处*/
			break;//    跳出等待,不等了
		}
	}
    MySPI_Stop();
}

/**
  * 函    数:W25Q64页编程
  * 参    数:Address 页编程的起始地址,范围:0x000000~0x7FFFFF
  * 参    数:DataArray	用于写入数据的数组
  * 参    数:Count 要写入数据的数量,范围:0~256
  * 返 回 值:无
  * 注意事项:写入的地址范围不能跨页
  */
void W25Q64_PageProgram(uint32_t Address, uint8_t *DataArray, uint16_t Count)
{
	uint16_t i;
	
	W25Q64_WriteEnable();						//写使能
	
	MySPI_Start();								//SPI起始
	MySPI_SwapByte(W25Q64_PAGE_PROGRAM);		//交换发送页编程的指令
	MySPI_SwapByte(Address >> 16);				//交换发送地址23~16位。例如0x123456,这里交换0x12
	MySPI_SwapByte(Address >> 8);				//交换发送地址15~8位。右移8位为0x1234,由于交换字节函数只能接收8位数据,所以高位舍弃,实际发送0x34
	MySPI_SwapByte(Address);					//交换发送地址7~0位。由于交换字节函数只能接收8位数据,所以高位舍弃,实际发送0x56
	for (i = 0; i < Count; i ++)				//循环Count次
	{
		MySPI_SwapByte(DataArray[i]);			//依次在起始地址后写入数据
	}
	MySPI_Stop();								//SPI终止
	
	W25Q64_WaitBusy();							//等待忙
}

/**
  * 函    数:W25Q64扇区擦除(4KB)
  * 参    数:Address 指定扇区的地址,范围:0x000000~0x7FFFFF
  * 返 回 值:无
  */
void W25Q64_SectorErase(uint32_t Address)
{
	W25Q64_WriteEnable();						//写使能
	
	MySPI_Start();								//SPI起始
	MySPI_SwapByte(W25Q64_SECTOR_ERASE_4KB);	//交换发送扇区擦除的指令
	MySPI_SwapByte(Address >> 16);				//交换发送地址23~16位。例如0x123456,这里交换0x12
	MySPI_SwapByte(Address >> 8);				//交换发送地址15~8位。右移8位为0x1234,由于交换字节函数只能接收8位数据,所以高位舍弃,实际发送0x34
	MySPI_SwapByte(Address);					//交换发送地址7~0位。由于交换字节函数只能接收8位数据,所以高位舍弃,实际发送0x56
	MySPI_Stop();								//SPI终止
	
	W25Q64_WaitBusy();							//等待忙
}

/**
  * 函    数:W25Q64读取数据
  * 参    数:Address 读取数据的起始地址,范围:0x000000~0x7FFFFF
  * 参    数:DataArray 用于接收读取数据的数组,通过输出参数返回
  * 参    数:Count 要读取数据的数量,范围:0~0x800000
  * 返 回 值:无
  */
void W25Q64_ReadData(uint32_t Address, uint8_t *DataArray, uint32_t Count)
{
	uint32_t i;
	MySPI_Start();								//SPI起始
	MySPI_SwapByte(W25Q64_READ_DATA);			//交换发送读取数据的指令
	MySPI_SwapByte(Address >> 16);				//交换发送地址23~16位
	MySPI_SwapByte(Address >> 8);				//交换发送地址15~8位
	MySPI_SwapByte(Address);					//交换发送地址7~0位
	for (i = 0; i < Count; i ++)				//循环Count次
	{
		DataArray[i] = MySPI_SwapByte(W25Q64_DUMMY_BYTE);	//依次在起始地址后读取数据
	}
	MySPI_Stop();								//SPI终止
}

W25Q64.h

#ifndef __W25Q64_H
#define __W25Q64_H

void W25Q64_Init(void);
void W25Q64_ReadID(uint8_t *MID, uint16_t *DID);
void W25Q64_PageProgram(uint32_t Address, uint8_t *DataArray, uint16_t Count);
void W25Q64_SectorErase(uint32_t Address);
void W25Q64_ReadData(uint32_t Address, uint8_t *DataArray, uint32_t Count);

#endif

W25Q64_Ins.h

#ifndef __W25Q64_INS_H
#define __W25Q64_INS_H

#define W25Q64_WRITE_ENABLE							0x06
#define W25Q64_WRITE_DISABLE						0x04
#define W25Q64_READ_STATUS_REGISTER_1				0x05
#define W25Q64_READ_STATUS_REGISTER_2				0x35
#define W25Q64_WRITE_STATUS_REGISTER				0x01
#define W25Q64_PAGE_PROGRAM							0x02
#define W25Q64_QUAD_PAGE_PROGRAM					0x32
#define W25Q64_BLOCK_ERASE_64KB						0xD8
#define W25Q64_BLOCK_ERASE_32KB						0x52
#define W25Q64_SECTOR_ERASE_4KB						0x20
#define W25Q64_CHIP_ERASE							0xC7
#define W25Q64_ERASE_SUSPEND						0x75
#define W25Q64_ERASE_RESUME							0x7A
#define W25Q64_POWER_DOWN							0xB9
#define W25Q64_HIGH_PERFORMANCE_MODE				0xA3
#define W25Q64_CONTINUOUS_READ_MODE_RESET			0xFF
#define W25Q64_RELEASE_POWER_DOWN_HPM_DEVICE_ID		0xAB
#define W25Q64_MANUFACTURER_DEVICE_ID				0x90
#define W25Q64_READ_UNIQUE_ID						0x4B
#define W25Q64_JEDEC_ID								0x9F
#define W25Q64_READ_DATA							0x03
#define W25Q64_FAST_READ							0x0B
#define W25Q64_FAST_READ_DUAL_OUTPUT				0x3B
#define W25Q64_FAST_READ_DUAL_IO					0xBB
#define W25Q64_FAST_READ_QUAD_OUTPUT				0x6B
#define W25Q64_FAST_READ_QUAD_IO					0xEB
#define W25Q64_OCTAL_WORD_READ_QUAD_IO				0xE3

#define W25Q64_DUMMY_BYTE							0xFF

#endif

main.c

#include "stm32f10x.h"                  // Device 
#include "OLED.h"
#include "W25Q64.h"

uint8_t MID;							//定义用于存放MID号的变量
uint16_t DID;							//定义用于存放DID号的变量
uint8_t ArrayWrite[] = {0xAA, 0xBB, 0xCC, 0xDD};	//定义要写入数据的测试数组
uint8_t ArrayRead[4];								//定义要读取数据的测试数组

int main(void)
{
    OLED_Init();
    W25Q64_Init();
    
    /*显示静态字符串*/
    OLED_ShowString(1, 1, "MID:   DID:");
	OLED_ShowString(2, 1, "W:");
	OLED_ShowString(3, 1, "R:");
    
    /*显示ID号*/
	W25Q64_ReadID(&MID, &DID);			//获取W25Q64的ID号
	OLED_ShowHexNum(1, 5, MID, 2);		//显示MID
	OLED_ShowHexNum(1, 12, DID, 4);		//显示DID
    
    /*W25Q64功能函数测试*/
	W25Q64_SectorErase(0x000000);					//扇区擦除
	W25Q64_PageProgram(0x000000, ArrayWrite, 4);	//将写入数据的测试数组写入到W25Q64中
	
	W25Q64_ReadData(0x000000, ArrayRead, 4);		//读取刚写入的测试数据到读取数据的测试数组中
	
	/*显示数据*/
	OLED_ShowHexNum(2, 3, ArrayWrite[0], 2);		//显示写入数据的测试数组
	OLED_ShowHexNum(2, 6, ArrayWrite[1], 2);
	OLED_ShowHexNum(2, 9, ArrayWrite[2], 2);
	OLED_ShowHexNum(2, 12, ArrayWrite[3], 2);
	
	OLED_ShowHexNum(3, 3, ArrayRead[0], 2);			//显示读取数据的测试数组
	OLED_ShowHexNum(3, 6, ArrayRead[1], 2);
	OLED_ShowHexNum(3, 9, ArrayRead[2], 2);
	OLED_ShowHexNum(3, 12, ArrayRead[3], 2);
    
    while(1)
    {
        
    }
}

其他引用的头文件和c代码可在此处查阅:OLED.h(【江协STM32】4 OLED调试工具

标签:11,void,W25Q64,MySPI,STM32,SwapByte,GPIO,define
From: https://blog.csdn.net/qq_19395823/article/details/145119491

相关文章

  • 基于STM32C6T6的智能小车设计:自动寻迹、避障与无线控制全解析(含有源码资料)
    一、设计要求:1.1功能要求:设计并制作一个基于STM32C6T6核心板的智能小车,具备自动寻迹、避障和无线控制功能。小车应能够沿着不规则的黑色轨迹行驶,遇到障碍物时能够自动绕行,并可通过蓝牙模块进行无线控制。自动寻迹:小车应能够沿着不规则的黑色轨迹行驶,根据五路灰度循迹模块的......
  • Win32汇编学习笔记11.游戏辅助的实现
    Win32汇编学习笔记11.游戏辅助的实现-C/C++基础-断点社区-专业的老牌游戏安全技术交流社区-BpSend.net游戏基址游戏基址的概念游戏基址是保持恒定的两部分内存地址的一部分并提供一个基准点,从这里可以计算一个字节数据的位置。基址伴随着一个加到基上的偏移值来确定信息准确......
  • 《STM32开发:深入解析 TIM2->CCR2 与 TIM2.CCR2 的区别与应用》
    前言在最初学习STM32的过程中,由于知识不进脑子,经常边学边忘,并且C语言学习的也比较浅,涉及到指针地址等方面的知识,内心就有点排斥。第一次遇到->和.这两种操作符时,我只是知道按照示例“照着用”,但并不清楚它们之间的具体区别,也没有深入理解它们的内在逻辑。这样的学习方......
  • STM32之LWIP网络通讯设计-下(十五)
    STM32F407系列文章-ETH-LWIP(十五)目录前言一、软件设计二、CubeMX实现1.配置前准备2.CubeMX配置1.ETH模块配置2.时钟模块配置3.中断模块配置4.RCC及SYS配置5.LWIP模块配置3.生成代码1.main文件2.用户层源文件3.用户层头文件4.效果演示三、移植实现总结......
  • 【事件分析】20250112-Usual 赎回机制调整事件
    背景信息https://docs.usual.money/Usual是一个聚合RWA的稳定币发行协议,经济模型中存在三种代币:USD0:Usual发行的稳定币。USD0++:USD0++是USD0的质押版本,为期4年,可获得USUAL代币奖励。USUAL:Usual协议的治理代币。事发缘由https://usual.money/blog/usual-s-next-......
  • 基于STM32F103标准库实现FFT,并实现音乐频谱绘制
    整个工程文件是在江科大的OLED显示屏OLED-V2.0版本IIC四针脚接口UTF-8的工程上编写的,在屏幕显示过程中,只用到了OLED显示屏的绘制直线和绘制像素点两个函数(注意,显示屏的绘制函数坐标可以任意指定,而不是按页写入。任一屏幕只要有上述两个函数均可使用。工程接线:STM32F103C8T6......
  • windows11电脑弹窗报错提示”找不到kprometheus.dll“文件的原因和修复方案
    在数字化办公与娱乐日益紧密融合的今天,Windows11以其强大功能和流畅体验备受用户青睐。然而,不少用户在使用过程中遭遇了棘手难题:电脑突然弹窗报错,提示“找不到kprometheus.dll”文件。这一状况不仅打乱操作节奏,还可能引发对系统稳定性的担忧,探寻原因和修复方案迫在眉睫。......
  • 我国无人机新增实名登记110.3 万架,累计完成飞行2666万小时
    据央视新闻从中国民航局了解到,2024年我国全年新增通航企业145家、通用机场26个,颁发无人驾驶航空器型号合格证6个、新增实名登记无人机110.3万架,无人机运营单位总数超过2万家,累计完成无人机飞行2666万小时,同比增长15%。民航充分利用国家空域改革成果,研究制定专项......
  • 1130: 【入门】简单a+b(字符串式子a+b)
    看到了吗,不是正常的输入a和b,然后直接相加,而是一个式子,没关系,一个字符串对于电脑而言奥秘多多,给电脑一个式子,他会反应吗?是不是不会。诶,但是让他去提取,那就是“怎么看都看不够”,嘿嘿,开个玩笑,就是提取字符串里的信息可以解决不少问题,这题就是这样。下面是代码:#include<bits/stdc......
  • 【江协STM32】11-1 SPI通信协议
    SPI(SerialPeripheralInterface)是由Motorola公司开发的一种通用数据总线四根通信线:SCK(SerialClock)、MOSI(MasterOutputSlaveInput)、MISO(MasterInputSlaveOutput)、SS(SlaveSelect)同步,全双工支持总线挂载多设备(一主多从)         1. 硬件电路所有SPI设......