/* * 模块名称:GD5F2GQ5UE SPI NAND Flash 驱动 * 模块功能:提�?GD5F2GQ5UE SPI NAND Flash 的初始化、读、写、擦除接�? * 适用平台:STM32F407ZGT6 + SPI1 硬件 SPI * 作者:王建�? * 创建日期�?026-07-16 * 修改记录�? * 2026-07-16 王建�? 创建初始版本 * 2026-07-17 王建�? 切换硬件 SPI,修正擦除地址,参�?NuttX 驱动 */ /* 头文件包含区 */ #include #include "gd5f2gq5ue.h" /* * 调试输出配置 */ #define DBG_TAG "[NAND]" #include "dbg_log.h" /* 私有宏定义区 */ #define GD5F_SPI_TIMEOUT 100 #define GD5F_BBT_SIZE (GD5F_TOTAL_BLOCKS / 8) /* BBT 静态数组(1 bit 表示一个块,0=好块 1=坏块) */ static uint8_t s_bbt[GD5F_BBT_SIZE]; /* 外部 SPI 句柄声明 */ extern SPI_HandleTypeDef hspi1; /* DMA 完成标志(ch395f.c 中的 HAL_SPI_TxRxCpltCallback 会设置此标志) */ volatile uint8_t g_spi1_dma_done; /* DMA 阈值(字节数超过此值使用 DMA 传输) */ #define GD5F_DMA_THRESHOLD 32 /* * 函数功能:SPI1 TX DMA 完成回调(PROGRAM_LOAD 数据发送用) */ void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi) { if (hspi->Instance == SPI1) { g_spi1_dma_done = 1U; } } /* * 函数功能:SPI1 RX DMA 完成回调(READ_FROM_CACHE 数据接收用) * 函数说明:HAL_SPI_Receive_DMA() 在 2 线 master 模式下 * 内部走 HAL_SPI_TransmitReceive_DMA 但 state=BUSY_RX, * 完成回调是 HAL_SPI_RxCpltCallback 而非 TxRxCpltCallback */ void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi) { if (hspi->Instance == SPI1) { g_spi1_dma_done = 1U; } } /* ======================== 私有函数声明 ======================== */ 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); /* ======================== SPI 原语定义 ======================== */ /* * 函数功能:等待芯片操作完成(轮询 OIP 位) * 入口参数:timeout_ms - 超时时间 uint32_t > 0 * 返回值:0 - 操作完成,-2 - 超时 * 限定条件:SPI 已初始化 * 函数说明:循环读取状态寄存器直到 OIP 位清零或超时 */ 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 命令再拉�? */ 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 地址后读�?字节状态�? */ 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 清零 */ int gd5f_page_read(uint32_t page_addr) { uint8_t cmd[4] = {0}; 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 后读取数�? */ int gd5f_read_from_cache(uint16_t column, uint8_t *p_buf, size_t size) { uint8_t cmd[4] = {0}; 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); if (size > GD5F_DMA_THRESHOLD) { uint32_t tick_start = HAL_GetTick(); g_spi1_dma_done = 0U; if (HAL_SPI_Receive_DMA(&hspi1, p_buf, (uint16_t)size) != HAL_OK) { GD5F_CS_HIGH(); return GD5F_ERROR; } while (g_spi1_dma_done == 0U) { if ((HAL_GetTick() - tick_start) >= GD5F_SPI_TIMEOUT) { HAL_SPI_DMAStop(&hspi1); GD5F_CS_HIGH(); return GD5F_BUSY_TIMEOUT; } } } else { 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 - 成功,其�?- 错误�? * 限定条件:目标区域已擦除 * 函数说明�?. 写使�?- 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_write_enable(); ret = gd5f_program_load(column, p_buf, size); if (ret != GD5F_OK) return ret; return gd5f_program_exec(page_addr); } int gd5f_program_load(uint16_t column, const uint8_t *p_buf, size_t size) { uint8_t cmd[3]; cmd[0] = GD5F_CMD_PROGRAM_LOAD; cmd[1] = (uint8_t)(column >> 8); cmd[2] = (uint8_t)(column); GD5F_CS_LOW(); HAL_SPI_Transmit(&hspi1, cmd, 3, GD5F_SPI_TIMEOUT); if (size > GD5F_DMA_THRESHOLD) { uint32_t tick_start = HAL_GetTick(); g_spi1_dma_done = 0U; if (HAL_SPI_Transmit_DMA(&hspi1, (uint8_t *)p_buf, (uint16_t)size) != HAL_OK) { GD5F_CS_HIGH(); return GD5F_ERROR; } while (g_spi1_dma_done == 0U) { if ((HAL_GetTick() - tick_start) >= GD5F_SPI_TIMEOUT) { HAL_SPI_DMAStop(&hspi1); GD5F_CS_HIGH(); return GD5F_BUSY_TIMEOUT; } } } else { HAL_SPI_Transmit(&hspi1, (uint8_t *)p_buf, size, GD5F_SPI_TIMEOUT); } GD5F_CS_HIGH(); return GD5F_OK; } int gd5f_program_exec(uint32_t page_addr) { int ret; uint8_t cmd[4]; uint8_t status; 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; } int gd5f_check_ecc(void) { uint8_t status; gd5f_read_status(&status); uint8_t ecc = (status >> 4) & 0x03; if (ecc == 0x02) return GD5F_ECC_ERROR; 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 */ int gd5f_block_erase(uint32_t block_addr) { int ret = GD5F_OK; uint8_t cmd[4]; uint8_t status = 0; uint32_t page_addr = block_addr * GD5F_PAGES_PER_BLOCK; gd5f_write_enable(); cmd[0] = GD5F_CMD_BLOCK_ERASE; 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(5000); if (ret != GD5F_OK) { return ret; } gd5f_read_status(&status); if (status & GD5F_STATUS_E_FAIL) { return GD5F_ERASE_FAIL; } return GD5F_OK; } /* * 函数功能:扫描所有块构建 BBT(在 ECC 使能前调用) * 入口参数:无 * 返回值:0 - 成功 * 限定条件:SPI 已初始化,ECC 尚未使能 * 函数说明:读取每个块最后一页(page 63)的 spare byte 0, * 若不为 0xFF 则为出厂坏块 */ static int gd5f_bbt_scan(void) { uint32_t block; uint8_t spare[8]; uint32_t bad_count = 0; memset(s_bbt, 0, GD5F_BBT_SIZE); for (block = 0; block < GD5F_TOTAL_BLOCKS; block++) { uint32_t page = (block << 6) | 63; gd5f_page_read(page); gd5f_read_from_cache(GD5F_PAGE_SIZE, spare, 1); if (spare[0] != 0xFF) { s_bbt[block >> 3] |= (1 << (block & 7)); bad_count++; } } DBG_INFO("BBT scan: %lu bad blocks found", bad_count); return GD5F_OK; } void gd5f2gq5ue_bbt_rescan(void) { uint32_t blk; uint8_t spare[64]; memset(s_bbt, 0, GD5F_BBT_SIZE); for (blk = 0; blk < GD5F_TOTAL_BLOCKS; blk++) { uint32_t page = (blk << 6) | 63; gd5f_page_read(page); gd5f_read_from_cache(GD5F_PAGE_SIZE, spare, sizeof(spare)); if (spare[0] != 0xFF) { s_bbt[blk >> 3] |= (1 << (blk & 7)); } } } void gd5f2gq5ue_bbt_clear(void) { memset(s_bbt, 0, GD5F_BBT_SIZE); } void gd5f2gq5ue_print_bbt(void) { uint32_t bad_count = 0; for (uint32_t b = 0; b < GD5F_TOTAL_BLOCKS; b++) { if ((s_bbt[b >> 3] >> (b & 7)) & 1) bad_count++; } DBG_INFO("BBT: %lu/%lu blocks bad", bad_count, GD5F_TOTAL_BLOCKS); if (bad_count > 0) { uint32_t shown = 0; for (uint32_t b = 0; b < GD5F_TOTAL_BLOCKS && shown < 32; b++) { if ((s_bbt[b >> 3] >> (b & 7)) & 1) { DBG_INFO(" block %lu (offset 0x%08lX)", b, b * GD5F_BLOCK_SIZE); shown++; } } if (bad_count > 32) DBG_INFO(" ... and %lu more", bad_count - 32); } } /* ======================== 公共函数定义 ======================== */ /* * 函数功能:初始化 GD5F2GQ5UE * 入口参数:无 * 返回值:0 - 成功,其他 - 错误码 * 限定条件:SPI1 和相关 GPIO 已由 CubeMX 初始化完成 * 函数说明:1. 发送复位命令并等待完成 * 2. 读取芯片 ID 并校验 * 3. 扫描出厂坏块构建 BBT(ECC 使能前) * 4. 使能内部 ECC (B0h bit4) * 5. 解除所有块保护 (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("Scanning bad blocks..."); gd5f_bbt_scan(); 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 命令后接�?�?dummy + MID + DID */ int gd5f2gq5ue_read_id(uint8_t *p_mid, uint8_t *p_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(); *p_mid = id_buf[1]; *p_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; } /* * 函数功能:查询块是否坏块 * 入口参数:block - 块编号 uint32_t 0 ~ GD5F_TOTAL_BLOCKS-1 * 返回值:0 - 好块,1 - 坏块 * 限定条件:gd5f2gq5ue_init() 已成功调用 * 函数说明:通过 BBT 查询 */ int gd5f2gq5ue_is_block_bad(uint32_t block) { if (block >= GD5F_TOTAL_BLOCKS) { return 1; } return (s_bbt[block >> 3] >> (block & 7)) & 1; } /* * 函数功能:标记块为坏块 * 入口参数:block - 块编号 uint32_t 0 ~ GD5F_TOTAL_BLOCKS-1 * 返回值:无 * 限定条件:gd5f2gq5ue_init() 已成功调用 * 函数说明:更新 BBT */ void gd5f2gq5ue_mark_block_bad(uint32_t block) { if (block >= GD5F_TOTAL_BLOCKS) { return; } s_bbt[block >> 3] |= (1 << (block & 7)); }