492 lines
13 KiB
C
492 lines
13 KiB
C
/*
|
||
* 模块名称:GD5F2GQ5UE SPI NAND Flash 驱动
|
||
* 模块功能:提供 GD5F2GQ5UE SPI NAND Flash 的初始化、读、写、擦除接口
|
||
* 适用平台:STM32F407ZGT6 + SPI1 硬件 SPI
|
||
* 作者:王建锋
|
||
* 创建日期:2026-07-16
|
||
* 修改记录:
|
||
* 2026-07-16 王建锋 创建初始版本
|
||
* 2026-07-17 王建锋 切换硬件 SPI,修正擦除地址,参考 NuttX 驱动
|
||
*/
|
||
|
||
/* 头文件包含区 */
|
||
#include "gd5f2gq5ue.h"
|
||
#include <string.h>
|
||
|
||
/*
|
||
* 调试输出配置
|
||
*/
|
||
#define DBG_TAG "[NAND]"
|
||
#include "dbg_log.h"
|
||
|
||
/* 私有宏定义区 */
|
||
#define GD5F_SPI_TIMEOUT 100
|
||
|
||
/* 外部 SPI 句柄声明 */
|
||
extern SPI_HandleTypeDef hspi1;
|
||
|
||
/* ======================== 私有函数声明 ======================== */
|
||
|
||
static int gd5f_wait_busy(uint32_t timeout_ms);
|
||
static int gd5f_write_enable(void);
|
||
static int gd5f_read_status(uint8_t *p_status);
|
||
static int gd5f_page_read(uint32_t page_addr);
|
||
static int gd5f_read_from_cache(uint16_t column, uint8_t *p_buf,
|
||
size_t size);
|
||
static int gd5f_page_program(uint32_t page_addr, uint16_t column,
|
||
const uint8_t *p_buf, size_t size);
|
||
static int gd5f_set_feature(uint8_t addr, uint8_t data);
|
||
static int gd5f_block_erase(uint32_t block_addr);
|
||
|
||
/* ======================== 私有函数定义 ======================== */
|
||
|
||
/*
|
||
* 函数功能:等待芯片操作完成(轮询 OIP 位)
|
||
* 入口参数:timeout_ms - 超时时间 uint32_t > 0
|
||
* 返回值:0 - 操作完成,-2 - 超时
|
||
* 限定条件:SPI 已初始化
|
||
* 函数说明:循环读取状态寄存器直到 OIP 位清零或超时
|
||
*/
|
||
static int gd5f_wait_busy(uint32_t timeout_ms)
|
||
{
|
||
uint8_t cmd = GD5F_CMD_GET_FEATURE;
|
||
uint8_t addr = GD5F_REG_STATUS;
|
||
uint8_t status = 0;
|
||
uint32_t tick_start = HAL_GetTick();
|
||
|
||
while (1) {
|
||
GD5F_CS_LOW();
|
||
HAL_SPI_Transmit(&hspi1, &cmd, 1, GD5F_SPI_TIMEOUT);
|
||
HAL_SPI_Transmit(&hspi1, &addr, 1, GD5F_SPI_TIMEOUT);
|
||
HAL_SPI_Receive(&hspi1, &status, 1, GD5F_SPI_TIMEOUT);
|
||
GD5F_CS_HIGH();
|
||
|
||
if ((status & GD5F_STATUS_OIP) == 0) {
|
||
return GD5F_OK;
|
||
}
|
||
if ((HAL_GetTick() - tick_start) >= timeout_ms) {
|
||
return GD5F_BUSY_TIMEOUT;
|
||
}
|
||
}
|
||
}
|
||
|
||
/*
|
||
* 函数功能:发送写使能命令
|
||
* 入口参数:无
|
||
* 返回值:0 - 成功
|
||
* 限定条件:SPI 已初始化
|
||
* 函数说明:CS# 拉低后发送 06h 命令再拉高
|
||
*/
|
||
static int gd5f_write_enable(void)
|
||
{
|
||
uint8_t cmd = GD5F_CMD_WRITE_ENABLE;
|
||
|
||
GD5F_CS_LOW();
|
||
HAL_SPI_Transmit(&hspi1, &cmd, 1, GD5F_SPI_TIMEOUT);
|
||
GD5F_CS_HIGH();
|
||
|
||
return GD5F_OK;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:读取状态寄存器
|
||
* 入口参数:p_status - 状态值输出指针 uint8_t* 不为 NULL
|
||
* 返回值:0 - 成功
|
||
* 限定条件:SPI 已初始化
|
||
* 函数说明:发送 0Fh + C0h 地址后读取1字节状态值
|
||
*/
|
||
static int gd5f_read_status(uint8_t *p_status)
|
||
{
|
||
uint8_t cmd = GD5F_CMD_GET_FEATURE;
|
||
uint8_t addr = GD5F_REG_STATUS;
|
||
|
||
GD5F_CS_LOW();
|
||
HAL_SPI_Transmit(&hspi1, &cmd, 1, GD5F_SPI_TIMEOUT);
|
||
HAL_SPI_Transmit(&hspi1, &addr, 1, GD5F_SPI_TIMEOUT);
|
||
HAL_SPI_Receive(&hspi1, p_status, 1, GD5F_SPI_TIMEOUT);
|
||
GD5F_CS_HIGH();
|
||
|
||
return GD5F_OK;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:页读取(将数据从存储阵列加载到内部缓存)
|
||
* 入口参数:page_addr - 页地址 uint32_t
|
||
* 返回值:0 - 成功,其他 - 错误码
|
||
* 限定条件:SPI 已初始化
|
||
* 函数说明:发送 13h + 3字节行地址,等待 OIP 清零
|
||
*/
|
||
static int gd5f_page_read(uint32_t page_addr)
|
||
{
|
||
uint8_t cmd[4];
|
||
|
||
cmd[0] = GD5F_CMD_PAGE_READ;
|
||
cmd[1] = (page_addr >> 16) & 0xFF;
|
||
cmd[2] = (page_addr >> 8) & 0xFF;
|
||
cmd[3] = page_addr & 0xFF;
|
||
|
||
GD5F_CS_LOW();
|
||
HAL_SPI_Transmit(&hspi1, cmd, 4, GD5F_SPI_TIMEOUT);
|
||
GD5F_CS_HIGH();
|
||
|
||
return gd5f_wait_busy(100);
|
||
}
|
||
|
||
/*
|
||
* 函数功能:从内部缓存读取数据
|
||
* 入口参数:column - 列地址(页内偏移) uint16_t 0 - 2047
|
||
* p_buf - 数据输出缓冲区 uint8_t* 不为 NULL
|
||
* size - 读取字节数 size_t > 0
|
||
* 返回值:0 - 成功
|
||
* 限定条件:必须先调用 gd5f_page_read 完成数据加载
|
||
* 函数说明:发送 0Bh + 2字节列地址 +1字节 dummy 后读取数据
|
||
*/
|
||
static int gd5f_read_from_cache(uint16_t column, uint8_t *p_buf,
|
||
size_t size)
|
||
{
|
||
uint8_t cmd[4];
|
||
|
||
cmd[0] = GD5F_CMD_READ_FROM_CACHE;
|
||
cmd[1] = (column >> 8) & 0xFF;
|
||
cmd[2] = column & 0xFF;
|
||
cmd[3] = 0x00;
|
||
|
||
GD5F_CS_LOW();
|
||
HAL_SPI_Transmit(&hspi1, cmd, 4, GD5F_SPI_TIMEOUT);
|
||
HAL_SPI_Receive(&hspi1, p_buf, size, GD5F_SPI_TIMEOUT);
|
||
GD5F_CS_HIGH();
|
||
|
||
return GD5F_OK;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:页编程(将数据写入指定页)
|
||
* 入口参数:page_addr - 页地址 uint32_t
|
||
* column - 列地址 uint16_t 0 - 2047
|
||
* p_buf - 数据缓冲区 const uint8_t*
|
||
* size - 写入字节数 size_t > 0
|
||
* 返回值:0 - 成功,其他 - 错误码
|
||
* 限定条件:目标区域已擦除
|
||
* 函数说明:1. 写使能 - 02h 加载数据 - 10h 执行编程 - 等待完成
|
||
* 2. 编程完成后检查 P_FAIL 位
|
||
*/
|
||
static int gd5f_page_program(uint32_t page_addr, uint16_t column,
|
||
const uint8_t *p_buf, size_t size)
|
||
{
|
||
int ret = GD5F_OK;
|
||
uint8_t cmd[4];
|
||
uint8_t status = 0;
|
||
|
||
gd5f_write_enable();
|
||
|
||
cmd[0] = GD5F_CMD_PROGRAM_LOAD;
|
||
cmd[1] = (column >> 8) & 0xFF;
|
||
cmd[2] = column & 0xFF;
|
||
|
||
GD5F_CS_LOW();
|
||
HAL_SPI_Transmit(&hspi1, cmd, 3, GD5F_SPI_TIMEOUT);
|
||
HAL_SPI_Transmit(&hspi1, (uint8_t *)p_buf, size,
|
||
GD5F_SPI_TIMEOUT);
|
||
GD5F_CS_HIGH();
|
||
|
||
cmd[0] = GD5F_CMD_PROGRAM_EXEC;
|
||
cmd[1] = (page_addr >> 16) & 0xFF;
|
||
cmd[2] = (page_addr >> 8) & 0xFF;
|
||
cmd[3] = page_addr & 0xFF;
|
||
|
||
GD5F_CS_LOW();
|
||
HAL_SPI_Transmit(&hspi1, cmd, 4, GD5F_SPI_TIMEOUT);
|
||
GD5F_CS_HIGH();
|
||
|
||
ret = gd5f_wait_busy(1000);
|
||
if (ret != GD5F_OK) {
|
||
return ret;
|
||
}
|
||
|
||
gd5f_read_status(&status);
|
||
if (status & GD5F_STATUS_P_FAIL) {
|
||
return GD5F_PROGRAM_FAIL;
|
||
}
|
||
|
||
return GD5F_OK;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:设置 Feature 寄存器
|
||
* 入口参数:addr - 寄存器地址 uint8_t
|
||
* data - 写入数据 uint8_t
|
||
* 返回值:0 - 成功
|
||
* 限定条件:SPI 已初始化
|
||
* 函数说明:先写使能,发送 1Fh + 地址 + 数据,等待操作完成
|
||
*/
|
||
static int gd5f_set_feature(uint8_t addr, uint8_t data)
|
||
{
|
||
uint8_t cmd[3];
|
||
|
||
gd5f_write_enable();
|
||
|
||
cmd[0] = GD5F_CMD_SET_FEATURE;
|
||
cmd[1] = addr;
|
||
cmd[2] = data;
|
||
|
||
GD5F_CS_LOW();
|
||
HAL_SPI_Transmit(&hspi1, cmd, 3, GD5F_SPI_TIMEOUT);
|
||
GD5F_CS_HIGH();
|
||
|
||
gd5f_wait_busy(100);
|
||
|
||
return GD5F_OK;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:块擦除(擦除128KB块)
|
||
* 入口参数:block_addr - 块编号 uint32_t 0 - 2047
|
||
* 返回值:0 - 成功,其他 - 错误码
|
||
* 限定条件:gd5f2gq5ue_init() 已调用
|
||
* 函数说明:1. 写使能 - D8h + 3字节字节地址 - 等待完成
|
||
* 2. 擦除完成后检查 E_FAIL 位
|
||
*/
|
||
static int gd5f_block_erase(uint32_t block_addr)
|
||
{
|
||
int ret = GD5F_OK;
|
||
uint8_t cmd[4];
|
||
uint8_t status = 0;
|
||
uint32_t byte_addr = block_addr * GD5F_BLOCK_SIZE;
|
||
|
||
gd5f_write_enable();
|
||
|
||
cmd[0] = GD5F_CMD_BLOCK_ERASE;
|
||
cmd[1] = (byte_addr >> 16) & 0xFF;
|
||
cmd[2] = (byte_addr >> 8) & 0xFF;
|
||
cmd[3] = byte_addr & 0xFF;
|
||
|
||
GD5F_CS_LOW();
|
||
HAL_SPI_Transmit(&hspi1, cmd, 4, GD5F_SPI_TIMEOUT);
|
||
GD5F_CS_HIGH();
|
||
|
||
ret = gd5f_wait_busy(5000);
|
||
if (ret != GD5F_OK) {
|
||
return ret;
|
||
}
|
||
|
||
gd5f_read_status(&status);
|
||
if (status & GD5F_STATUS_E_FAIL) {
|
||
return GD5F_ERASE_FAIL;
|
||
}
|
||
|
||
return GD5F_OK;
|
||
}
|
||
|
||
/* ======================== 公共函数定义 ======================== */
|
||
|
||
/*
|
||
* 函数功能:初始化 GD5F2GQ5UE
|
||
* 入口参数:无
|
||
* 返回值:0 - 成功,其他 - 错误码
|
||
* 限定条件:SPI1 和相关 GPIO 已由 CubeMX 初始化完成
|
||
* 函数说明:1. 发送复位命令并等待完成
|
||
* 2. 读取芯片 ID 并校验
|
||
* 3. 使能内部 ECC (B0h bit4)
|
||
* 4. 解除所有块保护 (A0h = 0x00)
|
||
*/
|
||
int gd5f2gq5ue_init(void)
|
||
{
|
||
int ret = GD5F_OK;
|
||
uint8_t mid = 0;
|
||
uint8_t did = 0;
|
||
|
||
GD5F_CS_HIGH();
|
||
GD5F_WP_HIGH();
|
||
GD5F_HOLD_HIGH();
|
||
|
||
HAL_Delay(10);
|
||
|
||
DBG_INFO("Resetting NAND...");
|
||
ret = gd5f2gq5ue_reset();
|
||
if (ret != GD5F_OK) {
|
||
DBG_ERROR("Reset failed: %d", ret);
|
||
return ret;
|
||
}
|
||
|
||
HAL_Delay(5);
|
||
|
||
DBG_INFO("Reading NAND ID...");
|
||
ret = gd5f2gq5ue_read_id(&mid, &did);
|
||
if (ret != GD5F_OK) {
|
||
DBG_ERROR("Read ID failed: %d", ret);
|
||
return ret;
|
||
}
|
||
|
||
DBG_INFO("NAND ID: MID=0x%02X, DID=0x%02X", mid, did);
|
||
|
||
if (mid != GD5F_MANUFACTURER_ID || did != GD5F_DEVICE_ID) {
|
||
DBG_ERROR("ID mismatch: expected MID=0x%02X DID=0x%02X, got MID=0x%02X DID=0x%02X",
|
||
GD5F_MANUFACTURER_ID, GD5F_DEVICE_ID, mid, did);
|
||
return GD5F_ID_MISMATCH;
|
||
}
|
||
|
||
DBG_INFO("Enabling ECC...");
|
||
gd5f_set_feature(0xB0, 0x10);
|
||
gd5f_wait_busy(100);
|
||
|
||
DBG_INFO("Unlocking block protection...");
|
||
gd5f_set_feature(GD5F_REG_PROTECT, 0x00);
|
||
gd5f_wait_busy(100);
|
||
|
||
DBG_INFO("NAND init OK");
|
||
return GD5F_OK;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:读取芯片 ID(MID + DID)
|
||
* 入口参数:mid - 制造商 ID 输出指针 uint8_t* 不为 NULL
|
||
* did - 设备 ID 输出指针 uint8_t* 不为 NULL
|
||
* 返回值:0 - 成功
|
||
* 限定条件:SPI 已初始化
|
||
* 函数说明:发送 9Fh 命令后接收1个 dummy + MID + DID
|
||
*/
|
||
int gd5f2gq5ue_read_id(uint8_t *mid, uint8_t *did)
|
||
{
|
||
uint8_t cmd = GD5F_CMD_READ_ID;
|
||
uint8_t id_buf[3] = {0};
|
||
|
||
GD5F_CS_LOW();
|
||
HAL_SPI_Transmit(&hspi1, &cmd, 1, GD5F_SPI_TIMEOUT);
|
||
HAL_SPI_Receive(&hspi1, id_buf, 3, GD5F_SPI_TIMEOUT);
|
||
GD5F_CS_HIGH();
|
||
|
||
*mid = id_buf[1];
|
||
*did = id_buf[2];
|
||
|
||
return GD5F_OK;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:从 NAND 读取数据(支持跨页)
|
||
* 入口参数:offset - 起始字节偏移 long 0 ~ 总容量-1
|
||
* p_buf - 数据缓冲区 uint8_t* 不为 NULL
|
||
* size - 读取字节数 size_t > 0
|
||
* 返回值:0 - 成功,其他 - 错误码
|
||
* 限定条件:gd5f2gq5ue_init() 已成功调用
|
||
* 函数说明:自动处理跨页读取
|
||
*/
|
||
int gd5f2gq5ue_read(long offset, uint8_t *p_buf, size_t size)
|
||
{
|
||
int ret = GD5F_OK;
|
||
|
||
while (size > 0) {
|
||
uint32_t page_addr = offset / GD5F_PAGE_SIZE;
|
||
uint16_t column = offset % GD5F_PAGE_SIZE;
|
||
size_t bytes = GD5F_PAGE_SIZE - column;
|
||
|
||
if (bytes > size) {
|
||
bytes = size;
|
||
}
|
||
|
||
ret = gd5f_page_read(page_addr);
|
||
if (ret != GD5F_OK) {
|
||
return ret;
|
||
}
|
||
|
||
ret = gd5f_read_from_cache(column, p_buf, bytes);
|
||
if (ret != GD5F_OK) {
|
||
return ret;
|
||
}
|
||
|
||
offset += bytes;
|
||
p_buf += bytes;
|
||
size -= bytes;
|
||
}
|
||
|
||
return GD5F_OK;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:向 NAND 写入数据(支持跨页)
|
||
* 入口参数:offset - 起始字节偏移 long 0 ~ 总容量-1
|
||
* p_buf - 数据缓冲区 const uint8_t* 不为 NULL
|
||
* size - 写入字节数 size_t > 0
|
||
* 返回值:0 - 成功,其他 - 错误码
|
||
* 限定条件:gd5f2gq5ue_init() 已成功调用,目标区域已擦除
|
||
* 函数说明:自动处理跨页写入
|
||
*/
|
||
int gd5f2gq5ue_write(long offset, const uint8_t *p_buf, size_t size)
|
||
{
|
||
int ret = GD5F_OK;
|
||
|
||
while (size > 0) {
|
||
uint32_t page_addr = offset / GD5F_PAGE_SIZE;
|
||
uint16_t column = offset % GD5F_PAGE_SIZE;
|
||
size_t bytes = GD5F_PAGE_SIZE - column;
|
||
|
||
if (bytes > size) {
|
||
bytes = size;
|
||
}
|
||
|
||
ret = gd5f_page_program(page_addr, column, p_buf, bytes);
|
||
if (ret != GD5F_OK) {
|
||
return ret;
|
||
}
|
||
|
||
offset += bytes;
|
||
p_buf += bytes;
|
||
size -= bytes;
|
||
}
|
||
|
||
return GD5F_OK;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:擦除块(按块擦除,最小单位 128KB)
|
||
* 入口参数:offset - 起始字节偏移 long 必须块对齐
|
||
* size - 擦除字节数 size_t 必须块大小整数倍
|
||
* 返回值:0 - 成功,其他 - 错误码
|
||
* 限定条件:gd5f2gq5ue_init() 已成功调用
|
||
* 函数说明:擦除操作以块为单位
|
||
*/
|
||
int gd5f2gq5ue_erase(long offset, size_t size)
|
||
{
|
||
int ret = GD5F_OK;
|
||
|
||
if (offset % GD5F_BLOCK_SIZE != 0) {
|
||
return GD5F_ERROR;
|
||
}
|
||
if (size % GD5F_BLOCK_SIZE != 0) {
|
||
return GD5F_ERROR;
|
||
}
|
||
|
||
while (size > 0) {
|
||
uint32_t block_addr = offset / GD5F_BLOCK_SIZE;
|
||
|
||
ret = gd5f_block_erase(block_addr);
|
||
if (ret != GD5F_OK) {
|
||
return ret;
|
||
}
|
||
|
||
offset += GD5F_BLOCK_SIZE;
|
||
size -= GD5F_BLOCK_SIZE;
|
||
}
|
||
|
||
return GD5F_OK;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:复位芯片
|
||
* 入口参数:无
|
||
* 返回值:0 - 成功
|
||
* 限定条件:SPI 已初始化
|
||
* 函数说明:发送 FFh 复位命令后等待 5ms
|
||
*/
|
||
int gd5f2gq5ue_reset(void)
|
||
{
|
||
uint8_t cmd = GD5F_CMD_RESET;
|
||
|
||
GD5F_CS_LOW();
|
||
HAL_SPI_Transmit(&hspi1, &cmd, 1, GD5F_SPI_TIMEOUT);
|
||
GD5F_CS_HIGH();
|
||
|
||
HAL_Delay(5);
|
||
|
||
return GD5F_OK;
|
||
}
|