SDNAND 作为一种采用 SD 接口的 NAND 闪存,其读 / 写 / 擦除操作与标准 SD 卡有相似之处,但也有其独特特性。下面详细介绍这些操作的函数实现:
SDNAND 的读取操作通常支持按页读取和按块读取两种方式:
#include "sd_nand.h"
/* 读取单个页(通常为2KB) */
HAL_StatusTypeDef SDNAND_ReadPage(uint8_t *pData, uint32_t PageAddr, uint32_t Timeout)
{
HAL_StatusTypeDef status;
// 发送CMD17: 读取单个块命令
status = HAL_SD_SendCmd(&hsd1, SD_CMD_READ_SINGLE_BLOCK, PageAddr, 0, Timeout);
if(status != HAL_OK)
{
return status;
}
// 接收数据
status = HAL_SD_ReadBlocks(&hsd1, pData, PageAddr, 1, Timeout);
return status;
}
/* 读取多个页 */
HAL_StatusTypeDef SDNAND_ReadPages(uint8_t *pData, uint32_t PageAddr, uint32_t NumPages, uint32_t Timeout)
{
HAL_StatusTypeDef status;
// 发送CMD18: 读取多个块命令
status = HAL_SD_SendCmd(&hsd1, SD_CMD_READ_MULT_BLOCK, PageAddr, 0, Timeout);
if(status != HAL_OK)
{
return status;
}
// 接收数据
status = HAL_SD_ReadBlocks(&hsd1, pData, PageAddr, NumPages, Timeout);
return status;
}
/* 读取页的保留区域(用于存储ECC等信息) */
HAL_StatusTypeDef SDNAND_ReadSpareArea(uint8_t *pData, uint32_t PageAddr, uint32_t Timeout)
{
HAL_StatusTypeDef status;
uint8_t buffer[512]; // 假设使用512字节块大小
// 首先读取整个页
status = SDNAND_ReadPage(buffer, PageAddr, Timeout);
if(status != HAL_OK)
{
return status;
}
// 从页数据中提取保留区域数据
// 保留区域位置和大小取决于具体的SDNAND型号
// 这里假设保留区域在页的最后部分
memcpy(pData, &buffer[496], 16); // 假设保留区域为16字节
return HAL_OK;
}
SDNAND 的写入操作需要注意以下几点:
#include "sd_nand.h"
/* 写入单个页 */
HAL_StatusTypeDef SDNAND_WritePage(uint8_t *pData, uint32_t PageAddr, uint32_t Timeout)
{
HAL_StatusTypeDef status;
// 检查页是否已擦除
if(!SDNAND_IsPageErased(PageAddr))
{
// 擦除页
status = SDNAND_EraseBlock(PageAddr / PAGES_PER_BLOCK, Timeout);
if(status != HAL_OK)
{
return status;
}
}
// 发送CMD24: 写入单个块命令
status = HAL_SD_SendCmd(&hsd1, SD_CMD_WRITE_BLOCK, PageAddr, 0, Timeout);
if(status != HAL_OK)
{
return status;
}
// 发送数据
status = HAL_SD_WriteBlocks(&hsd1, pData, PageAddr, 1, Timeout);
return status;
}
/* 写入多个页 */
HAL_StatusTypeDef SDNAND_WritePages(uint8_t *pData, uint32_t PageAddr, uint32_t NumPages, uint32_t Timeout)
{
HAL_StatusTypeDef status;
uint32_t i;
// 检查并擦除需要的块
for(i = 0; i < NumPages; i++)
{
if(!SDNAND_IsPageErased(PageAddr + i))
{
status = SDNAND_EraseBlock((PageAddr + i) / PAGES_PER_BLOCK, Timeout);
if(status != HAL_OK)
{
return status;
}
}
}
// 发送CMD25: 写入多个块命令
status = HAL_SD_SendCmd(&hsd1, SD_CMD_WRITE_MULT_BLOCK, PageAddr, 0, Timeout);
if(status != HAL_OK)
{
return status;
}
// 发送数据
status = HAL_SD_WriteBlocks(&hsd1, pData, PageAddr, NumPages, Timeout);
return status;
}
/* 写入页的保留区域 */
HAL_StatusTypeDef SDNAND_WriteSpareArea(uint8_t *pData, uint32_t PageAddr, uint32_t Timeout)
{
HAL_StatusTypeDef status;
uint8_t buffer[512]; // 假设使用512字节块大小
// 首先读取整个页
status = SDNAND_ReadPage(buffer, PageAddr, Timeout);
if(status != HAL_OK)
{
return status;
}
// 更新保留区域数据
memcpy(&buffer[496], pData, 16); // 假设保留区域为16字节
// 写回整个页
return SDNAND_WritePage(buffer, PageAddr, Timeout);
}
NAND 闪存的一个重要特性是必须先擦除才能写入,且擦除操作是以块为单位进行的:
#include "sd_nand.h"
/* 擦除单个块 */
HAL_StatusTypeDef SDNAND_EraseBlock(uint32_t BlockAddr, uint32_t Timeout)
{
HAL_StatusTypeDef status;
uint32_t StartAddr, EndAddr;
// 计算块的起始和结束地址
StartAddr = BlockAddr * BLOCK_SIZE;
EndAddr = StartAddr + BLOCK_SIZE - 1;
// 发送CMD32: 设置擦除块起始地址
status = HAL_SD_SendCmd(&hsd1, SD_CMD_ERASE_WR_BLK_START_ADDR, StartAddr, 0, Timeout);
if(status != HAL_OK)
{
return status;
}
// 发送CMD33: 设置擦除块结束地址
status = HAL_SD_SendCmd(&hsd1, SD_CMD_ERASE_WR_BLK_END_ADDR, EndAddr, 0, Timeout);
if(status != HAL_OK)
{
return status;
}
// 发送CMD38: 执行擦除
status = HAL_SD_SendCmd(&hsd1, SD_CMD_ERASE, 0, 0, Timeout);
return status;
}
/* 擦除多个块 */
HAL_StatusTypeDef SDNAND_EraseBlocks(uint32_t StartBlock, uint32_t NumBlocks, uint32_t Timeout)
{
HAL_StatusTypeDef status;
uint32_t i;
for(i = 0; i < NumBlocks; i++)
{
status = SDNAND_EraseBlock(StartBlock + i, Timeout);
if(status != HAL_OK)
{
return status;
}
}
return HAL_OK;
}
/* 检查页是否已擦除 */
uint8_t SDNAND_IsPageErased(uint32_t PageAddr)
{
uint8_t buffer[512]; // 假设使用512字节块大小
uint32_t i;
// 读取页数据
if(HAL_SD_ReadBlocks(&hsd1, buffer, PageAddr, 1, SD_MAX_TIMEOUT) != HAL_OK)
{
return 0; // 读取失败,假设未擦除
}
// 检查是否所有位都是1(擦除状态)
for(i = 0; i < 512; i++)
{
if(buffer[i] != 0xFF)
{
return 0; // 存在非擦除位
}
}
return 1; // 页已擦除
}
SDNAND 通常还提供一些特殊操作函数,用于管理闪存特性:
#include "sd_nand.h"
/* 擦除单个块 */
HAL_StatusTypeDef SDNAND_EraseBlock(uint32_t BlockAddr, uint32_t Timeout)
{
HAL_StatusTypeDef status;
uint32_t StartAddr, EndAddr;
// 计算块的起始和结束地址
StartAddr = BlockAddr * BLOCK_SIZE;
EndAddr = StartAddr + BLOCK_SIZE - 1;
// 发送CMD32: 设置擦除块起始地址
status = HAL_SD_SendCmd(&hsd1, SD_CMD_ERASE_WR_BLK_START_ADDR, StartAddr, 0, Timeout);
if(status != HAL_OK)
{
return status;
}
// 发送CMD33: 设置擦除块结束地址
status = HAL_SD_SendCmd(&hsd1, SD_CMD_ERASE_WR_BLK_END_ADDR, EndAddr, 0, Timeout);
if(status != HAL_OK)
{
return status;
}
// 发送CMD38: 执行擦除
status = HAL_SD_SendCmd(&hsd1, SD_CMD_ERASE, 0, 0, Timeout);
return status;
}
/* 擦除多个块 */
HAL_StatusTypeDef SDNAND_EraseBlocks(uint32_t StartBlock, uint32_t NumBlocks, uint32_t Timeout)
{
HAL_StatusTypeDef status;
uint32_t i;
for(i = 0; i < NumBlocks; i++)
{
status = SDNAND_EraseBlock(StartBlock + i, Timeout);
if(status != HAL_OK)
{
return status;
}
}
return HAL_OK;
}
/* 检查页是否已擦除 */
uint8_t SDNAND_IsPageErased(uint32_t PageAddr)
{
uint8_t buffer[512]; // 假设使用512字节块大小
uint32_t i;
// 读取页数据
if(HAL_SD_ReadBlocks(&hsd1, buffer, PageAddr, 1, SD_MAX_TIMEOUT) != HAL_OK)
{
return 0; // 读取失败,假设未擦除
}
// 检查是否所有位都是1(擦除状态)
for(i = 0; i < 512; i++)
{
if(buffer[i] != 0xFF)
{
return 0; // 存在非擦除位
}
}
return 1; // 页已擦除
}
通过以上函数,你可以实现 SDNAND 的基本操作。在实际应用中,还应根据具体的 SDNAND 型号和应用需求进行适当调整。