说明:基于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();
}