Files
STM32F4-Base/Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.c
2026-07-19 01:18:33 +08:00

492 lines
13 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 模块名称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;
}
/*
* 函数功能:读取芯片 IDMID + 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;
}