W25Q128笔记

说明:基于GD32F407硬件SPI通信,在江科协为W25Q64写的驱动基础上改的代码,代码仅供参考。指令码可以参考芯片手册,对每条指令都有说明。

一、芯片简介

W25Q128是一款高性能FLASH闪存芯片,主要特点包括:

存储容量:具有128 Mb(16 MB)的存储容量,适用于需要较大数据存储的应用。

接口:支持SPI(Serial Peripheral Interface)和QSPI(Quad Serial Peripheral Interface)接口,能够实现高速数据传输。

速度:在QSPI模式下,具有高达104 MHz的时钟频率,提供快速的读取和写入性能。

编程和擦除:支持页编程和块擦除功能,具有良好的写入效率。每页通常为256字节。

四字节地址模式:支持四字节地址模式,适合大容量存储需求的应用,能够处理超过16 MB的地址空间。

耐用性:具有高达100,000次的擦写周期和超过20年的数据保持能力,适合长期使用的场合。

应用领域:广泛应用于消费电子、工业控制、汽车电子、网络设备等需要非易失性存储的场合。

1. 存储空间划分

        W25Q128具有128 Mb(16 MB)的存储容量,最小存储单元为页(page),每页容量为256字节(bytes)。多页构成扇区(sector),每个扇区的存储容量为4 KB(即16页);16个扇区构成一个块(block),单块的存储空间为64 KB;整个存储空间由256个块组成,总容量为16 MB。        

2. 页编程/页擦除

        在进行页编程/页擦除时,指定的地址最低8 bits必须为0。就像下面框的地址。

3. 标准SPI通信

        该存储芯片支持SPI通信模式0(0,0)和模式3(1,1)。上图为模式0:SCLK空闲状态为低电平,在第一个时钟上升沿,将总线上的数据移入至移位寄存器(也有人称之为采样),时钟下降沿将需要发送的数据放在总线上。模式3的时序如下图:SCLK空闲状态为高电平,在第一个时钟下降沿将数据放置在总线上,时钟上升沿将总线上的数据移入移位寄存器中。

4. 写保护特性

  • 当VCC低于阈值时,设备重置。
  • 上电后禁用延时写入。
  • 写入启用/禁用指令,并在擦除或编程后自动写入禁用。
  • 使用状态寄存器的软件写保护。
  • 额外的单个块/扇区锁用于阵列保护。
  • 使用断电指令进行写保护。
  • 锁定状态寄存器的写保护,直到下一次上电。

二、参考代码

1. 指令码

        这部分单独为一个头文件,用于存放官方定义的指令码,指令码未列完整,可以根据需求自己加入或删除官方给定的指令码。

#ifndef __W25Q128_INS_H
#define __W25Q128_INS_H

#define W25Q128_WRITE_ENABLE							0x06
#define W25Q128_WRITE_DISABLE							0x04
#define W25Q128_READ_STATUS_REGISTER_1					0x05
#define W25Q128_READ_STATUS_REGISTER_2					0x35
#define W25Q128_READ_STATUS_REGISTER_3					0x15
#define W25Q128_WRITE_STATUS_REGISTER_1					0x01
#define W25Q128_WRITE_STATUS_REGISTER_2					0x31
#define W25Q128_WRITE_STATUS_REGISTER_3					0x11
#define W25Q128_PAGE_PROGRAM							0x02
#define W25Q128_QUAD_PAGE_PROGRAM						0x32
#define W25Q128_BLOCK_ERASE_64KB						0xD8
#define W25Q128_BLOCK_ERASE_32KB						0x52
#define W25Q128_SECTOR_ERASE_4KB						0x20
#define W25Q128_CHIP_ERASE								0xC7
#define W25Q128_ERASE_SUSPEND							0x75
#define W25Q128_ERASE_RESUME							0x7A
#define W25Q128_POWER_DOWN								0xB9
#define W25Q128_HIGH_PERFORMANCE_MODE					0xA3
#define W25Q128_CONTINUOUS_READ_MODE_RESET				0xFF
#define W25Q128_RELEASE_POWER_DOWN_HPM_DEVICE_ID		0xAB
#define W25Q128_MANUFACTURER_DEVICE_ID					0x90
#define W25Q128_READ_UNIQUE_ID							0x4B
#define W25Q128_JEDEC_ID								0x9F
#define W25Q128_GLOBAL_BLOCK_LUCK						0x7E
#define W25Q128_GLOBAL_BLOCK_UNLUCK						0x98
#define W25Q128_READ_DATA								0x03
#define W25Q128_FAST_READ								0x0B
#define W25Q128_FAST_READ_DUAL_OUTPUT					0x3B
#define W25Q128_FAST_READ_DUAL_IO						0xBB
#define W25Q128_FAST_READ_QUAD_OUTPUT					0x6B
#define W25Q128_FAST_READ_QUAD_IO						0xEB
#define W25Q128_OCTAL_WORD_READ_QUAD_IO					0xE3
#define W25Q128_INDIVIDUAL_BLOCK_LUCK					0x36
#define W25Q128_INDIVIDUAL_BLOCK_UNLUCK					0x39
#define W25Q128_READ_BLOCK_LUCK							0x3D
	
#define W25Q128_DUMMY_BYTE								0xFF

#endif

2. 初始化函数

        这里注意,使用硬件SPI的时候配置为模式0或者模式3。初始化过程中注意自己连接的GPIO口,开启对应的GPIO时钟和SPI时钟。

#include "gd32f4xx.h"                   // Device header
#include "FLASH_INS.h"

#define		FLASH_SPI			SPI2
#define 	FLASH_PORT			GPIOB
#define 	FLASH_NSS			GPIO_PIN_6
#define 	FLASH_SCK   		GPIO_PIN_3
#define 	FLASH_MISO			GPIO_PIN_4
#define 	FLASH_MOSI			GPIO_PIN_5

/*!
    \brief        初始化GPIO与SPI
    \param[in]    无
    \param[out]   无
    \retval       无
*/
void Flash_Init()
{
	//开启外设时钟,根据走线更改
	rcu_periph_clock_enable(RCU_GPIOB);
	rcu_periph_clock_enable(RCU_SPI2);
	
	gpio_af_set(FLASH_PORT, GPIO_AF_5 ,FLASH_SCK | FLASH_MISO | FLASH_MOSI);
	gpio_mode_set(FLASH_PORT, GPIO_MODE_AF, GPIO_PUPD_NONE,  FLASH_SCK | FLASH_MISO | FLASH_MOSI);
	gpio_mode_set(FLASH_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_PULLUP, FLASH_NSS);
	gpio_output_options_set(FLASH_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ,  FLASH_SCK | FLASH_MOSI | FLASH_NSS);
	
	//初始化SPI!!!!!!!!!!!!!!!!!!!!!!
	rcu_periph_clock_enable(RCU_SPI2);
	spi_parameter_struct spi_initstructure;
	spi_initstructure.device_mode = SPI_MASTER;
	spi_initstructure.trans_mode = SPI_TRANSMODE_FULLDUPLEX;
	spi_initstructure.frame_size = SPI_FRAMESIZE_8BIT;
	spi_initstructure.nss = SPI_NSS_SOFT;
	spi_initstructure.clock_polarity_phase = SPI_CK_PL_LOW_PH_1EDGE;		//不是很确定
	spi_initstructure.prescale = SPI_PSC_32;
	spi_initstructure.endian = SPI_ENDIAN_MSB;
	
	spi_init(FLASH_SPI, &spi_initstructure);
	
	spi_enable(FLASH_SPI);

	gpio_bit_set(FLASH_PORT, FLASH_NSS);
}

3. 起始和结束信号

/*!
    \brief        片选信号:开始
    \param[in]    无
    \param[out]   无
    \retval       无
*/
void Flash_Start(void)
{
	gpio_bit_reset(FLASH_PORT, FLASH_NSS);
}

/*!
    \brief        片选信号:结束
    \param[in]    无
    \param[out]   无
    \retval       无
*/
void Flash_Stop(void)
{
	gpio_bit_set(FLASH_PORT, FLASH_NSS);
}

4. SPI字节交换函数

        SPI是全双工通信协议,但是在使用的时候,基本都当半双工使用。传输的时候,需要先发送字节或16 bits,再接收字节或者16 bits,借用江科协的比喻就是“抛砖引玉”。

/*!
    \brief        SPI交换字节时序
    \param[in]    待写入数据
    \param[out]   交换得到的数据
    \retval       无
*/
uint8_t MySPI_SwapByte(uint8_t SendByte)
{
	while(spi_i2s_flag_get(FLASH_SPI, SPI_FLAG_TBE) != SET);
	spi_i2s_data_transmit(FLASH_SPI, SendByte);
	while(spi_i2s_flag_get(FLASH_SPI, SPI_FLAG_RBNE) != SET);
	return spi_i2s_data_receive(FLASH_SPI);
}

5. 写使能

/*!
    \brief        写使能
    \param[in]    无
    \param[out]   无
    \retval       
*/
void Flash_WriteEnable(void)
{
	Flash_Start();
	MySPI_SwapByte(W25Q128_WRITE_ENABLE);
	Flash_Stop();
}

6. 等待函数

/*!
    \brief        等待忙
    \param[in]    无
    \param[out]   无
    \retval       
*/
void Flash_WhitBusy(void)
{
	Flash_Start();
	uint32_t timeout;
	timeout = 100000;
	while((MySPI_SwapByte(W25Q128_READ_STATUS_REGISTER_1) & 0x01) == 0x01)
	{
		timeout--;
		if(timeout == 0)
		{
			break;
		}
	}
	Flash_Stop();
}

7. 页编程函数

        注意写入的页起始地址,数据长度。

/*!
    \brief        	W25Q128页编程
    \param[in]    	Address 页编程的起始地址,范围:0x000000~0xFFFF00
					DataArray	用于写入数据的数组
					Count 要写入数据的数量,范围:0~256
    \param[out]     无
	\retval         写入的地址范围不能跨页;地址最低字节必须为0
*/
void Flash_PageProgram(uint32_t Address, uint8_t *DataArray, uint16_t Count)
{
	uint16_t i;
	Flash_WriteEnable();
	Flash_Start();
	MySPI_SwapByte(W25Q128_PAGE_PROGRAM);
	MySPI_SwapByte(Address >> 16);
	MySPI_SwapByte(Address >> 8);
	MySPI_SwapByte(Address);
	for(i = 0; i < Count; i++)
	{
		MySPI_SwapByte(DataArray[i]);
	}
	Flash_Stop();
	
	Flash_WhitBusy();
}

8. 四种擦除方式

/*!
	\brief        	W25Q128扇区擦除
    \param[in]    	Address 页编程的起始地址,范围:0x000000~0xFFFF00				
    \param[out]     无
    \retval         写入的地址范围不能跨页,地址最低字节必须为0
*/
void Flash_SecortErase(uint32_t Address)
{
	Flash_WriteEnable();
	Flash_Start();
	MySPI_SwapByte(W25Q128_SECTOR_ERASE_4KB);
	MySPI_SwapByte(Address);
	Flash_Stop();
	
	Flash_WhitBusy();
}

/*!
	\brief        	W25Q128 8个扇区擦除
    \param[in]    	Address 页编程的起始地址,范围:0x000000~0xFFFF00				
    \param[out]     无
    \retval         写入的地址范围不能跨页,地址最低字节必须为0
*/
void Flash_BlockErase32KB(uint32_t Address)
{
	Flash_WriteEnable();
	Flash_Start();
	MySPI_SwapByte(W25Q128_BLOCK_ERASE_32KB);
	MySPI_SwapByte(Address);
	Flash_Stop();
	
	Flash_WhitBusy();
}

/*!
	\brief        	W25Q128块擦除
    \param[in]    	Address 页编程的起始地址,范围:0x000000~0xFFFF00				
    \param[out]     无
    \retval         写入的地址范围不能跨页,地址最低字节必须为0
*/
void Flash_BlockErase64KB(uint32_t Address)
{
	Flash_WriteEnable();
	Flash_Start();
	MySPI_SwapByte(W25Q128_BLOCK_ERASE_64KB);
	MySPI_SwapByte(Address);
	Flash_Stop();
	
	Flash_WhitBusy();
}

/*!
	\brief        	W25Q128 全部擦除
    \param[in]    	无				
    \param[out]     无
    \retval         
*/
void Flash_ChipErase(void)
{
	Flash_WriteEnable();
	Flash_Start();
	MySPI_SwapByte(W25Q128_CHIP_ERASE);
	Flash_Stop();
	
	Flash_WhitBusy();
}

9. 指定页读取数据

/**
  \brief     		W25Q64读取数据
  \param[in] 		Address 读取数据的起始地址,范围:0x000000~0xFFFF00
					DataArray 用于接收读取数据的数组,通过输出参数返回
					Count 要读取数据的数量,范围:0~0x1000000
  \param[out]		无
  \retval    
*/
void Flash_ReadData(uint32_t Address, uint8_t *DataArray, uint32_t Count)
{
	uint32_t i;
	Flash_Start();
	MySPI_SwapByte(W25Q128_READ_DATA);
	MySPI_SwapByte(Address >> 16);
	MySPI_SwapByte(Address >> 8);
	MySPI_SwapByte(Address);
	for(i = 0; i < Count; i++)
	{
		DataArray[i] = MySPI_SwapByte(W25Q128_DUMMY_BYTE);
	}
	Flash_Stop();
}

10. 读取ID(用于测试)

/*!
    \brief        读取芯片ID
	\param[in]    MID: 工厂ID地址
				  DID: 设备ID地址
    \param[out]   无
    \retval       供测试使用
*/
void Flash_ReadID(uint8_t *MID, uint16_t *DID)
{
	Flash_Start();
	MySPI_SwapByte(W25Q128_JEDEC_ID);
	*MID = MySPI_SwapByte(W25Q128_DUMMY_BYTE);
	*DID = MySPI_SwapByte(W25Q128_DUMMY_BYTE);
	*DID <<= 8;
	*DID |= MySPI_SwapByte(W25Q128_DUMMY_BYTE);
	Flash_Stop();
}

### 解析 Import Error 的常见原因 当遇到 `ImportError: cannot import name 'Generic'` 错误时,通常意味着尝试从模块中导入的对象不存在或无法访问。此问题可能由多种因素引起: - 版本不兼容:不同库之间的版本冲突可能导致此类错误。 - 安装缺失:目标库未正确安装或路径配置有误。 - 导入语句不当:可能存在循环依赖或其他语法层面的问题。 ### 针对 Generic 类型的具体解决方案 对于特定于 `Generic` 的情况,考虑到 Python 中 `Generic` 是 typing 模块的一部分,在处理该类别的 ImportError 时可采取如下措施[^1]: #### 方法一:确认typing模块可用性 确保环境中已安装标准库中的 typing 模块,并且其版本支持所使用的特性。可以通过以下命令验证: ```bash python -c "from typing import Generic; print(Generic)" ``` 如果上述命令执行失败,则可能是由于 Python 或者相关扩展包的版本过低造成的。此时应考虑升级至更高版本的解释器以及对应的开发工具链。 #### 方法二:调整导入方式 有时直接通过顶层命名空间来获取所需组件会更稳定可靠。修改代码以采用这种做法可能会解决问题: ```python from collections.abc import Iterable # 如果是迭代器相关接口 from typing import TypeVar, Protocol # 对于协议和泛型定义 T = TypeVar('T') class MyContainer(Protocol[T]): ... ``` 注意这里并没有显式提到 `Generic` ,而是利用了更为基础的数据结构抽象基类或是其他替代方案实现相同功能[^2]。 #### 方法三:排查环境变量设置 检查系统的 PYTHONPATH 和虚拟环境配置是否正常工作。任何异常都可能导致某些第三方软件包找不到必要的资源文件而引发类似的错误提示。建议清理并重建项目专属的工作区以便排除干扰项的影响。 #### 示例修正后的代码片段 假设原始代码试图这样引入 `Generic` : ```python from some_module import Generic # 可能导致 ImportError ``` 改为遵循官方文档推荐的方式后变为: ```python from typing import Generic # 正确的做法 ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值