5.28 NAND设备
5.28 NAND设备
📚 本节导读
学习时长: 约 40 分钟
难度级别: ⭐⭐⭐⭐⭐
前置知识: C 语言基础、NAND Flash 基础知识、5.1 设备驱动框架、5.27 MTD 设备
本节讲解 OneOS 的 NAND Flash 设备。NAND Flash 容量大、价格低,但有两大特有难题:坏块(出厂/使用中会产生坏块)和位翻转(需要 ECC 纠错)。OneOS 的 NAND 设备框架专门处理这些,并提供页/块/区(Plane)三级寻址、坏块管理、硬件 ECC、Spare 区(OOB)读写。
本节会带你:
- 理解 NAND 的 页/块/区三级寻址模型 与
nand_page2addr/nand_addr2page的位运算; - 拆解
os_nand_device_register如何根据芯片 ID 配置寻址参数并注册进 FAL; - 拆解坏块检测
os_nand_badflag_check与坏块标记os_nand_badflag_mark的完整源码(读/写两个 Spare 区固定位置); - 拆解
os_nand_read/os_nand_write的完整源码(含硬件 ECC 控制与 Spare 区拼装); - 掌握 NAND 设备的使用流程。
说明:本节所有源码均取自项目
drivers/nand/nand.c,与项目源码逐字一致(含原始英文注释)。
🎯 学习目标
- 理解 NAND Flash 的页/块/区(Plane)三级寻址模型
- 掌握
os_nand_device和os_nand_ops核心结构体 - 逐个拆解
os_nand_device_register的地址位计算 - 掌握坏块管理(检测与标记)的完整流程
- 拆解
os_nand_read/os_nand_write的硬件 ECC 与 Spare 区处理
一、概述
1.1 NAND 解决什么问题
一句话核心:NAND 设备适用于需要访问大容量 NAND Flash、并处理坏块和位翻转的场景。它让你"按页/块/区地址操作 NAND,框架自动处理坏块检测、硬件 ECC、Spare 区(OOB)读写"。
为什么需要专门框架:
- 坏块:NAND 出厂就有坏块,且使用中会新增。使用前必须检测并标记坏块,避开它们,否则数据会写丢。
- 位翻转:NAND 读取时偶尔会读错位(位翻转),需要 ECC 校验纠错。
- Spare 区(OOB):NAND 每页除了主数据区还有一段额外空间(OOB),用于存 ECC 校验码、坏块标记、文件系统元数据。这些都需要专门接口。
1.2 三级寻址模型(页 → 块 → 区)
NAND 的物理组织是分层的:
NAND 芯片
├─ 区(Plane) 如 2 个 Plane
│ ├─ 块(Block) 每个 Plane 有 N 个块,每块如 128KB
│ │ ├─ 页(Page) 每块 64 页,每页如 2048B
│ │ └─ 页...
│ └─ 块...
└─ 区...关键理解:NAND 用"线性页号"作为统一地址,但通过位段划分拆成 page/block/plane 三级。nand_page2addr / nand_addr2page 负责在"线性页号"与"三级地址"间换算。
数值演算(假设:页 2048B → page_shift=11,块 64 页 → block_shift=17,区含 1024 块 → plane_shift=27,2 个 Plane):
| 三级地址 | 位段 | 含义 |
|---|---|---|
| Plane | [27, 31] | 第几区 |
| Block | [17, 26] | 第几块(区内) |
| Page | [11, 16] | 第几页(块内) |
线性页号 =
plane<<plane_shift | block<<block_shift | page<<page_shift(nand_addr2page)。
1.3 NAND 操作函数表与设备结构
NAND 底层操作通过 os_nand_ops 函数表完成:
struct os_nand_ops
{
os_err_t (*read_page)(os_nand_device_t *nand, uint32_t page_addr, uint8_t *buff, uint32_t page_nr);
os_err_t (*write_page)(os_nand_device_t *nand, uint32_t page_addr, const uint8_t *buff, uint32_t page_nr);
os_err_t (*read_spare)(os_nand_device_t *nand, uint32_t page_addr, uint8_t *buff, uint32_t spare_nr);
os_err_t (*write_spare)(os_nand_device_t *nand,
uint32_t page_addr,
const uint8_t *buff,
uint32_t spare_nr);
os_err_t (*erase_block)(os_nand_device_t *nand, uint32_t page_addr);
os_err_t (*config_hardecc)(os_nand_device_t *nand);
os_err_t (*control_hardecc)(os_nand_device_t *nand, os_bool_t enable);
os_err_t (*get_hardecc)(os_nand_device_t *nand, uint32_t *ecc_value);
os_err_t (*get_status)(os_nand_device_t *nand);
};函数指针详解(源码无注释,下表补充说明,不属于源码):
| 函数指针 | 说明 |
|---|---|
read_page/write_page | 读/写主数据区(页号、页数) |
read_spare/write_spare | 读/写 Spare 区(OOB,页号、Spare 页数) |
erase_block | 擦除一块(page_addr 为块内首页页号) |
config_hardecc | 初始化硬件 ECC |
control_hardecc | 使能/禁用硬件 ECC |
get_hardecc | 取硬件计算出的 ECC 值 |
get_status | 取芯片状态 |
NAND 设备结构:
struct os_nand_device
{
struct os_device parent;
struct os_nand_config cfg;
const struct os_nand_ops *ops;
uint8_t hardecc_flag;
uint8_t *page_buff;
uint8_t *spare_buff;
};字段详解(源码无注释,下表补充说明,不属于源码):
| 字段 | 含义 |
|---|---|
parent | 首成员继承 os_device_t,注册为 OS_DEVICE_TYPE_MTD 设备 |
cfg | 配置(芯片信息 + 寻址位宽/掩码 + oob_len + capacity) |
ops | 底层 NAND 操作函数表 |
hardecc_flag | 是否启用硬件 ECC(os_nand_device_register 探测) |
page_buff/spare_buff | 页/Spare 中转缓冲(注册时按页大小/Spare 大小分配) |
1.4 NAND 使用场景
1.4.1 一句话核心
NAND 设备适用于使用大容量 NAND Flash 做存储、需要坏块管理和 ECC 纠错的场景。它是 MTD(5.27)的 NAND 具体实现,也自动注册到 FAL(5.17)。
1.4.2 典型应用场景
场景一:整片 NAND 作为 FAL Flash 使用
os_nand_device_register 在 OS_USING_FAL 下自动调用 fal_nand_flash_register,把 NAND 注册成 FAL Flash,上层用 FAL 分区访问:
应用(FAL 分区)
│
▼
FAL → fal_nand_read_page/write_page/erase_block
│
▼
os_nand_read_page/write_page/erase_block
│
▼
nand->ops->read_page/write_page/erase_block(底层驱动)场景二:坏块管理
写数据前:
├─ os_nand_badflag_check(nand, block_no) ← 检查块是否坏
│ ├─ 读块首、次页的 Spare 区固定位置
│ └─ 若标记值 != 0xFF → 该块是坏块
└─ 若坏 → os_nand_badflag_mark(nand, block_no) 或跳过为什么用 NAND:坏块检测/标记由框架封装,应用或文件系统只需调用即可避开坏块。
1.4.3 选型边界表(何时不该用它)
| 场景 | 该用什么 | 为什么 |
|---|---|---|
| 小容量、简单存储 | NOR Flash(SFlash 5.25)/ EEPROM(5.26) | NAND 用于大容量,小容量用更简单器件 |
| 已有 FAL 分区且不需坏块细节 | FAL(5.17) | FAL 在 NAND 之上,上层用分区即可 |
| 需要文件系统 | Block + 文件系统 | 在 NAND 之上做块设备再挂文件系统 |
二、核心数据结构
2.1 NAND 地址结构体
来源:drivers/nand/nand.h
typedef struct
{
uint32_t page; /*!< NAND memory Page address */
uint32_t plane; /*!< NAND memory Zone address */
uint32_t block; /*!< NAND memory Block address */
} os_nand_addr_t;字段详解(源码注释为英文原版,下表补充说明,不属于源码):
| 字段 | 含义 |
|---|---|
page | 页地址(块内第几页) |
plane | 区地址(第几区) |
block | 块地址(区内第几块) |
2.2 NAND 配置 os_nand_config
来源:drivers/nand/nand.h
typedef struct os_nand_config
{
struct nand_device_info info;
uint32_t plane_mask;
uint32_t plane_shift;
uint32_t block_mask;
uint32_t block_shift;
uint32_t page_mask;
uint32_t page_shift;
uint32_t oob_len; /* NAND memory number of spare free memory */
uint32_t capacity;
} os_nand_config_t;字段详解(源码注释为英文原版,下表补充说明,不属于源码):
| 字段 | 含义 |
|---|---|
info | 芯片信息(页/块/区/Spare/坏块标记/ECC 布局) |
plane_mask / plane_shift | 区位掩码 / 区位宽 |
block_mask / block_shift | 块位掩码 / 块位宽 |
page_mask / page_shift | 页位掩码 / 页位宽 |
oob_len | 可用 OOB 长度(spare_size - 坏块标记长度 - ECC 长度) |
capacity | 总容量(字节) |
关键理解:
*_shift和*_mask是nand_page2addr/nand_addr2page位运算的核心参数,由os_nand_device_register根据芯片信息算出。字段顺序与源码一致(plane/block/page从高到低)。
2.3 NAND 设备结构体
struct os_nand_device
{
struct os_device parent;
struct os_nand_config cfg;
const struct os_nand_ops *ops;
uint8_t hardecc_flag;
uint8_t *page_buff;
uint8_t *spare_buff;
};三、核心源码详解
说明:以下源码均取自
drivers/nand/nand.c,与项目源码逐字一致(含原始英文注释)。
3.1 os_nand_device_register —— 注册 NAND 设备
一句话定位:根据芯片 ID 配置寻址参数、页缓冲、OOB 布局,并把设备注册到设备框架与 FAL。
完整源码(drivers/nand/nand.c,与项目一致):
os_err_t os_nand_device_register(os_nand_device_t *nand, const char *name, uint32_t id)
{
const struct nand_device_info *info = OS_NULL;
OS_ASSERT(nand != OS_NULL);
info = get_nand_info_by_id(id);
if (info == OS_NULL)
{
LOG_E(DRV_EXT_TAG, "can not find nand with id: 0x%08x", id);
return OS_FAILURE;
}
memcpy(&nand->cfg.info, info, sizeof(struct nand_device_info));
nand->cfg.info.badflag_info.mark_data = 0x55;
nand->cfg.info.badflag_info.length = 0x01;
nand->cfg.page_shift = os_ffs(nand->cfg.info.page_size) - 1;
nand->cfg.block_shift = nand->cfg.page_shift + os_ffs(nand->cfg.info.block_size) - 1;
nand->cfg.plane_shift = nand->cfg.block_shift + os_ffs(nand->cfg.info.plane_size) - 1;
nand->cfg.page_mask = (nand->cfg.info.block_size - 1) << nand->cfg.page_shift;
nand->cfg.block_mask = (nand->cfg.info.plane_size - 1) << nand->cfg.block_shift;
nand->cfg.plane_mask = (nand->cfg.info.plane_nr - 1) << nand->cfg.plane_shift;
nand->cfg.capacity = nand->cfg.info.page_size * nand->cfg.info.block_size * nand->cfg.info.plane_size * nand->cfg.info.plane_nr;
if (nand->ops->config_hardecc && nand->ops->control_hardecc && nand->ops->get_hardecc)
{
if (nand->ops->config_hardecc(nand) == OS_SUCCESS)
{
nand->hardecc_flag = OS_TRUE;
}
else
{
nand->hardecc_flag = OS_FALSE;
}
}
else
{
nand->hardecc_flag = OS_FALSE;
}
nand->cfg.oob_len =
nand->cfg.info.spare_size - nand->cfg.info.badflag_info.length - nand->cfg.info.hardecc_info.length;
nand->page_buff = os_calloc(1, nand->cfg.info.page_size);
OS_ASSERT(nand->page_buff);
nand->spare_buff = os_calloc(1, nand->cfg.info.spare_size);
OS_ASSERT(nand->spare_buff);
nand->parent.ops = OS_NULL;
nand->parent.type = OS_DEVICE_TYPE_MTD;
os_device_register(&nand->parent, name);
#ifdef OS_USING_FAL
return fal_nand_flash_register(nand);
#else
return OS_SUCCESS;
#endif
}分步详解:
第 1 步:按 ID 查找芯片信息(条件 info == OS_NULL)
info = get_nand_info_by_id(id);
if (info == OS_NULL)
{
LOG_E(DRV_EXT_TAG, "can not find nand with id: 0x%08x", id);
return OS_FAILURE;
}详细解释:get_nand_info_by_id(id) 在内置 NAND 芯片表中按 ID 查找。找不到返回 OS_FAILURE。
第 2 步:复制芯片信息并设置坏块标记(无条件执行)
memcpy(&nand->cfg.info, info, sizeof(struct nand_device_info));
nand->cfg.info.badflag_info.mark_data = 0x55;
nand->cfg.info.badflag_info.length = 0x01;详细解释:把芯片信息拷进 cfg.info。坏块标记值设为 0x55,长度 1 字节。坏块标记约定:在块首、次页的 Spare 区固定位置写 0x55 表示坏块。
第 3 步:计算寻址位宽(无条件执行)
nand->cfg.page_shift = os_ffs(nand->cfg.info.page_size) - 1;
nand->cfg.block_shift = nand->cfg.page_shift + os_ffs(nand->cfg.info.block_size) - 1;
nand->cfg.plane_shift = nand->cfg.block_shift + os_ffs(nand->cfg.info.plane_size) - 1;详细解释:
page_shift = log2(page_size):页地址占几位。block_shift = page_shift + log2(块页数):块地址的起始位。plane_shift = block_shift + log2(区块数):区地址的起始位。
数值演算(页 2048B → page_shift=11;块 64 页 → 块页数 64;区 1024 块 → 区块数 1024):
| 计算项 | 代入计算 | 结果 |
|---|---|---|
page_shift | os_ffs(2048)-1 = 11(页大小 2048=2^11) | 11 |
block_shift | 11 + os_ffs(64)-1 = 11+6(块页数 64=2^6) | 17 |
plane_shift | 17 + os_ffs(1024)-1 = 17+10(区块数 1024=2^10) | 27 |
注意:
os_ffs(n)返回最低位为 1 的位号(1-based),os_ffs(n)-1 = log2(n)。所以块页数 64(2^6)贡献6位(块号用 6 bit 表示块内页),区块数 1024(2^10)贡献10位。block_shift=17表示块段从 bit17 开始、占 10 位(bit17~26),与 1.2 位段表一致。
第 4 步:计算地址掩码(无条件执行)
nand->cfg.page_mask = (nand->cfg.info.block_size - 1) << nand->cfg.page_shift;
nand->cfg.block_mask = (nand->cfg.info.plane_size - 1) << nand->cfg.block_shift;
nand->cfg.plane_mask = (nand->cfg.info.plane_nr - 1) << nand->cfg.plane_shift;详细解释:掩码用于提取各段地址。
page_mask = (块页数-1) << page_shift:提取页段。block_mask = (区块数-1) << block_shift:提取块段。plane_mask = (区数-1) << plane_shift:提取区段。
数值演算(块 64 页、区 1024 块、2 区,page_shift=11、block_shift=17、plane_shift=27):
| 掩码 | 代入计算 | 结果 |
|---|---|---|
page_mask | (64-1) << 11 | 0x1F800(bit11~16) |
block_mask | (1024-1) << 17 | 0x7FE0000(bit17~26) |
plane_mask | (2-1) << 27 | 0x08000000(bit27) |
验证:
0x7FE0000 = 0x3FF << 17(区块数 1024-1=0x3FF,占 10 位,左移 17 到 bit17~26);0x08000000 = 1 << 27(区数 2-1=1)。三个掩码互不重叠,正好覆盖 0~27 位(共 28 位地址,对应 256MB 容量 2^28 字节)。
第 5 步:计算总容量(无条件执行)
nand->cfg.capacity = nand->cfg.info.page_size * nand->cfg.info.block_size * nand->cfg.info.plane_size * nand->cfg.info.plane_nr;详细解释:容量 = 页大小 × 块页数 × 区块数 × 区数。
数值演算(2048 × 64 × 1024 × 2):2048 × 64 × 1024 × 2 = 268435456 字节 = 256MB。
第 6 步:配置硬件 ECC(条件 三个 ECC 函数都存在)
if (nand->ops->config_hardecc && nand->ops->control_hardecc && nand->ops->get_hardecc)
{
if (nand->ops->config_hardecc(nand) == OS_SUCCESS)
{
nand->hardecc_flag = OS_TRUE;
}
else
{
nand->hardecc_flag = OS_FALSE;
}
}
else
{
nand->hardecc_flag = OS_FALSE;
}详细解释:若底层实现了 ECC 三个函数,调用 config_hardecc 初始化硬件 ECC,成功则置 hardecc_flag = OS_TRUE(后续读/写启用 ECC);否则 OS_FALSE。
第 7 步:计算 OOB 长度并分配缓冲(无条件执行)
nand->cfg.oob_len =
nand->cfg.info.spare_size - nand->cfg.info.badflag_info.length - nand->cfg.info.hardecc_info.length;
nand->page_buff = os_calloc(1, nand->cfg.info.page_size);
OS_ASSERT(nand->page_buff);
nand->spare_buff = os_calloc(1, nand->cfg.info.spare_size);
OS_ASSERT(nand->spare_buff);详细解释:可用 OOB 长度 = Spare 区总大小 - 坏块标记长度 - ECC 长度(这两块被框架占用)。分配页缓冲和 Spare 缓冲供读写中转用。
第 8 步:注册设备并接入 FAL(无条件执行)
nand->parent.ops = OS_NULL;
nand->parent.type = OS_DEVICE_TYPE_MTD;
os_device_register(&nand->parent, name);
#ifdef OS_USING_FAL
return fal_nand_flash_register(nand);
#else
return OS_SUCCESS;
#endif详细解释:注册为标准 MTD 设备。OS_USING_FAL 下调用 fal_nand_flash_register 注册成 FAL Flash。
os_nand_device_register() 条件走向总结:
os_nand_device_register() 条件走向总结:
├── info == OS_NULL(找不到芯片)→ LOG_E + 返回 OS_FAILURE
├── 复制芯片信息、设坏块标记 0x55 长度 1
├── 计算 page/block/plane_shift 和 *_mask
├── 计算 capacity
├── 若实现 3 个 ECC 函数且 config_hardecc 成功 → hardecc_flag = OS_TRUE
│ └── 否则 → hardecc_flag = OS_FALSE
├── 计算 oob_len;分配 page_buff / spare_buff
├── 注册 MTD 设备
├── #if OS_USING_FAL → fal_nand_flash_register
└── 返回结果3.2 三级地址转换 nand_page2addr / nand_addr2page
完整源码(drivers/nand/nand.c,与项目一致):
void nand_page2addr(os_nand_device_t *nand, uint32_t page_addr, os_nand_addr_t *nand_addr)
{
uint32_t addr = page_addr << nand->cfg.page_shift;
nand_addr->plane = (addr & nand->cfg.plane_mask) >> nand->cfg.plane_shift;
nand_addr->block = (addr & nand->cfg.block_mask) >> nand->cfg.block_shift;
nand_addr->page = (addr & nand->cfg.page_mask) >> nand->cfg.page_shift;
}分步详解:
第 1 步:把页号左移得到字节地址(无条件执行)
uint32_t addr = page_addr << nand->cfg.page_shift;详细解释:线性页号 page_addr 左移 page_shift 位,变成"以字节为单位的地址"。因为每页 2^page_shift 字节,页号 × 页大小 = 字节地址。
第 2 步:用掩码提取三级地址(无条件执行)
nand_addr->plane = (addr & nand->cfg.plane_mask) >> nand->cfg.plane_shift;
nand_addr->block = (addr & nand->cfg.block_mask) >> nand->cfg.block_shift;
nand_addr->page = (addr & nand->cfg.page_mask) >> nand->cfg.page_shift;详细解释:分别用 plane_mask / block_mask / page_mask 与 addr 做位与,取出对应位段,再右移回到 0 起始的编号,得到区号/块号/页号。
数值演算(线性页号 = 64,即块 1 的第 0 页、区 0。设 page_shift=11、block_shift=17、plane_shift=27,page_mask=0x1F800、block_mask=0x7FE0000、plane_mask=0x08000000):
| 计算项 | 代入计算 | 结果 |
|---|---|---|
addr | 64 << 11 | 0x20000(bit17) |
plane | (0x20000 & 0x08000000) >> 27 | 0 |
block | (0x20000 & 0x7FE0000) >> 17 | 1(bit17 落在块段最低位) |
page | (0x20000 & 0x1F800) >> 11 | 0 |
验证:页号 64 = 块 1 的第 0 页(每块 64 页,块 1 页 0 的线性页号 =
1*64 + 0 = 64)。addr=64<<11=0x20000,其 bit17 为 1,正好落在block_mask的 bit17~26 区间,所以block=1;page/plane 位段全 0,page=0、plane=0。逆运算nand_addr2page把block<<17 = 1<<17组合回64。
nand_addr2page(逆运算):
uint32_t nand_addr2page(os_nand_device_t *nand, os_nand_addr_t *nand_addr)
{
uint32_t page_addr;
page_addr = nand_addr->plane << nand->cfg.plane_shift;
page_addr |= nand_addr->block << nand->cfg.block_shift;
page_addr |= nand_addr->page << nand->cfg.page_shift;
return page_addr;
}详细解释:逆运算:把区号左移 plane_shift、块号左移 block_shift、页号左移 page_shift,再按位或组合成线性页号。
3.3 坏块检测 os_nand_badflag_check
一句话定位:检查某个块是否坏块,通过读块首、次页的 Spare 区固定位置判断标记是否为 0xFF。
完整源码(drivers/nand/nand.c,与项目一致):
os_err_t os_nand_badflag_check(os_nand_device_t *nand, uint32_t block_no)
{
os_err_t ret;
uint8_t badflag_buf = 0;
uint32_t page_addr = block_no * nand->cfg.info.block_size + nand->cfg.info.badflag_info.first_page_offset;
ret = nand->ops->read_spare(nand, page_addr, nand->spare_buff, 1);
if (ret != OS_SUCCESS)
{
LOG_E(DRV_EXT_TAG, "nand read spare failed");
return OS_FAILURE;
}
memcpy(&badflag_buf,
nand->spare_buff + nand->cfg.info.badflag_info.inpage_offset,
nand->cfg.info.badflag_info.length);
if (badflag_buf != 0xFF)
{
LOG_E(DRV_EXT_TAG, "nand block %d bad", block_no);
return OS_FAILURE;
}
page_addr = block_no * nand->cfg.info.block_size + nand->cfg.info.badflag_info.second_page_offset;
ret = nand->ops->read_spare(nand, page_addr, nand->spare_buff, 1);
if (ret != OS_SUCCESS)
{
LOG_E(DRV_EXT_TAG, "nand read spare failed");
return OS_FAILURE;
}
memcpy(&badflag_buf,
nand->spare_buff + nand->cfg.info.badflag_info.inpage_offset,
nand->cfg.info.badflag_info.length);
if (badflag_buf != 0xFF)
{
LOG_E(DRV_EXT_TAG, "nand block %d bad", block_no);
return OS_FAILURE;
}
return OS_SUCCESS;
}分步详解:
第 1 步:定位块首页的 Spare 区并读取(条件 读失败)
uint32_t page_addr = block_no * nand->cfg.info.block_size + nand->cfg.info.badflag_info.first_page_offset;
ret = nand->ops->read_spare(nand, page_addr, nand->spare_buff, 1);
if (ret != OS_SUCCESS)
{
LOG_E(DRV_EXT_TAG, "nand read spare failed");
return OS_FAILURE;
}详细解释:block_no * block_size 是块内第一页的页号,加上 first_page_offset 得到坏块标记所在的"块首页"。读该页 Spare 区到 spare_buff。
第 2 步:读取标记字节并判断(条件 badflag_buf != 0xFF)
memcpy(&badflag_buf,
nand->spare_buff + nand->cfg.info.badflag_info.inpage_offset,
nand->cfg.info.badflag_info.length);
if (badflag_buf != 0xFF)
{
LOG_E(DRV_EXT_TAG, "nand block %d bad", block_no);
return OS_FAILURE;
}详细解释:从 spare_buff 的 inpage_offset 位置拷 1 字节标记。关键判断:若标记值不是 0xFF(如被写成了 0x55),说明是坏块,返回 OS_FAILURE。正常块 Spare 区该位置是 0xFF。
第 3 步:检查块次页(无条件执行,逻辑同首页)
page_addr = block_no * nand->cfg.info.block_size + nand->cfg.info.badflag_info.second_page_offset;
ret = nand->ops->read_spare(nand, page_addr, nand->spare_buff, 1);
if (ret != OS_SUCCESS)
{
LOG_E(DRV_EXT_TAG, "nand read spare failed");
return OS_FAILURE;
}
memcpy(&badflag_buf,
nand->spare_buff + nand->cfg.info.badflag_info.inpage_offset,
nand->cfg.info.badflag_info.length);
if (badflag_buf != 0xFF)
{
LOG_E(DRV_EXT_TAG, "nand block %d bad", block_no);
return OS_FAILURE;
}
return OS_SUCCESS;
}详细解释:为防首页标记被误擦,NAND 约定在块首、次页两个位置各写一次坏块标记。这里再检查次页(读 Spare → 取标记字节 → 判断),两处都正常(0xFF)才返回 OS_SUCCESS(好块)。
os_nand_badflag_check() 条件走向总结:
os_nand_badflag_check() 条件走向总结:
├── 读块首页 Spare → 失败则返回 OS_FAILURE
├── 取标记字节
│ ├── != 0xFF → LOG_E "block bad" + 返回 OS_FAILURE
│ └── == 0xFF → 继续
├── 读块次页 Spare → 失败则返回 OS_FAILURE
├── 取标记字节
│ ├── != 0xFF → LOG_E "block bad" + 返回 OS_FAILURE
│ └── == 0xFF → 继续
└── 返回 OS_SUCCESS(好块)3.4 坏块标记 os_nand_badflag_mark
一句话定位:把一个块标记为坏块,在块首、次页的 Spare 区固定位置写入 mark_data(0x55)。
完整源码(drivers/nand/nand.c,与项目一致):
os_err_t os_nand_badflag_mark(os_nand_device_t *nand, uint32_t block_no)
{
os_err_t ret;
uint32_t page_addr = block_no * nand->cfg.info.block_size + nand->cfg.info.badflag_info.first_page_offset;
memset(nand->spare_buff, 0xFF, nand->cfg.info.spare_size);
memcpy(nand->spare_buff + nand->cfg.info.badflag_info.inpage_offset,
&nand->cfg.info.badflag_info.mark_data,
nand->cfg.info.badflag_info.length);
ret = nand->ops->write_spare(nand, page_addr, nand->spare_buff, 1);
if (ret != OS_SUCCESS)
{
LOG_E(DRV_EXT_TAG, "nand write spare failed");
return OS_FAILURE;
}
page_addr = block_no * nand->cfg.info.block_size + nand->cfg.info.badflag_info.second_page_offset;
memset(nand->spare_buff, 0xFF, nand->cfg.info.spare_size);
memcpy(nand->spare_buff + nand->cfg.info.badflag_info.inpage_offset,
&nand->cfg.info.badflag_info.mark_data,
nand->cfg.info.badflag_info.length);
ret = nand->ops->write_spare(nand, page_addr, nand->spare_buff, 1);
if (ret != OS_SUCCESS)
{
LOG_E(DRV_EXT_TAG, "nand write spare failed");
return OS_FAILURE;
}
return ret;
}分步详解:
第 1 步:块首页 Spare 填 0xFF 并写入标记(条件 写失败)
memset(nand->spare_buff, 0xFF, nand->cfg.info.spare_size);
memcpy(nand->spare_buff + nand->cfg.info.badflag_info.inpage_offset,
&nand->cfg.info.badflag_info.mark_data,
nand->cfg.info.badflag_info.length);
ret = nand->ops->write_spare(nand, page_addr, nand->spare_buff, 1);
if (ret != OS_SUCCESS)
{
LOG_E(DRV_EXT_TAG, "nand write spare failed");
return OS_FAILURE;
}详细解释:把整页 Spare 区先填成 0xFF,再把坏块标记值 mark_data(0x55)写入 inpage_offset 位置,然后整页写回块首页 Spare。为什么先填 0xFF:NAND 只能把 1 写成 0,把其余位置先置 1,再只把标记位写成 0x55。
第 2 步:块次页写入标记(条件 写失败)
page_addr = block_no * nand->cfg.info.block_size + nand->cfg.info.badflag_info.second_page_offset;
memset(nand->spare_buff, 0xFF, nand->cfg.info.spare_size);
memcpy(nand->spare_buff + nand->cfg.info.badflag_info.inpage_offset,
&nand->cfg.info.badflag_info.mark_data,
nand->cfg.info.badflag_info.length);
ret = nand->ops->write_spare(nand, page_addr, nand->spare_buff, 1);
if (ret != OS_SUCCESS)
{
LOG_E(DRV_EXT_TAG, "nand write spare failed");
return OS_FAILURE;
}
return ret;
}详细解释:同样在块次页 Spare 区写入标记(填 0xFF → 写 mark_data → write_spare),确保坏块标记在两处都有效。写失败则打印并返回 OS_FAILURE。
os_nand_badflag_mark() 条件走向总结:
os_nand_badflag_mark() 条件走向总结:
├── 块首页:Spare 填 0xFF → 写 mark_data 到 inpage_offset → write_spare
│ ├── 写失败 → LOG_E + 返回 OS_FAILURE
├── 块次页:Spare 填 0xFF → 写 mark_data → write_spare
│ ├── 写失败 → LOG_E + 返回 OS_FAILURE
└── 返回 ret3.5 os_nand_read —— 读页数据与 OOB
一句话定位:读一页主数据(可带 ECC 校验)和 OOB 数据,跳过坏块标记区和 ECC 区。
完整源码(drivers/nand/nand.c,与项目一致):
os_err_t os_nand_read(os_nand_device_t *nand,
uint32_t page_addr,
uint8_t *buff,
uint32_t buff_len,
uint8_t *oob,
uint32_t oob_len)
{
os_err_t ret;
uint32_t hardecc_cal = 0;
uint32_t hardecc_get = 0;
if (((buff == OS_NULL) && (oob == OS_NULL)) || ((buff_len == 0) && (oob_len == 0)))
{
return OS_INVAL;
}
if ((buff_len > nand->cfg.info.page_size) || (oob_len > nand->cfg.oob_len))
{
LOG_E(DRV_EXT_TAG, "nandflash read size out of range!");
return OS_FAILURE;
}
if (buff != OS_NULL)
{
if (nand->hardecc_flag)
{
nand->ops->control_hardecc(nand, OS_TRUE);
}
if (buff_len == nand->cfg.info.page_size)
{
ret = nand->ops->read_page(nand, page_addr, buff, 1);
}
else
{
ret = nand->ops->read_page(nand, page_addr, nand->page_buff, 1);
memcpy(buff, nand->page_buff, buff_len);
}
if (nand->hardecc_flag)
{
nand->ops->get_hardecc(nand, &hardecc_cal);
nand->ops->control_hardecc(nand, OS_FALSE);
}
if (ret != OS_SUCCESS)
{
LOG_E(DRV_EXT_TAG, "nand read page failed!");
return OS_FAILURE;
}
}
ret = nand->ops->read_spare(nand, page_addr, nand->spare_buff, 1);
if (ret != OS_SUCCESS)
{
LOG_E(DRV_EXT_TAG, "nand read spare failed!");
return OS_FAILURE;
}
for (int i = 0; i < nand->cfg.info.spare_size; i++)
{
if ((i >= nand->cfg.info.badflag_info.inpage_offset) &&
(i < nand->cfg.info.badflag_info.inpage_offset + nand->cfg.info.badflag_info.length))
{
}
else if ((i >= nand->cfg.info.hardecc_info.inpage_offset) &&
(i < nand->cfg.info.hardecc_info.inpage_offset + nand->cfg.info.hardecc_info.length))
{
hardecc_get = (uint32_t) * ((uint32_t *)(nand->spare_buff + i));
i += nand->cfg.info.hardecc_info.length;
}
else
{
if (oob != OS_NULL)
{
*oob++ = *(nand->spare_buff + i);
}
}
}
if (buff && (hardecc_cal != hardecc_get) && nand->hardecc_flag)
{
LOG_E(DRV_EXT_TAG, "nand read page data hardecc err %p", page_addr);
return OS_FAILURE;
}
return ret;
}分步详解:
第 1 步:参数校验(条件 无数据可读或无 OOB)
if (((buff == OS_NULL) && (oob == OS_NULL)) || ((buff_len == 0) && (oob_len == 0)))
{
return OS_INVAL;
}
if ((buff_len > nand->cfg.info.page_size) || (oob_len > nand->cfg.oob_len))
{
LOG_E(DRV_EXT_TAG, "nandflash read size out of range!");
return OS_FAILURE;
}详细解释:buff 和 oob 都为空,或长度都为 0,无意义,返回 OS_INVAL。长度超限返回 OS_FAILURE。
第 2 步:读主数据(条件 buff != OS_NULL)
if (buff != OS_NULL)
{
if (nand->hardecc_flag)
{
nand->ops->control_hardecc(nand, OS_TRUE);
}
if (buff_len == nand->cfg.info.page_size)
{
ret = nand->ops->read_page(nand, page_addr, buff, 1);
}
else
{
ret = nand->ops->read_page(nand, page_addr, nand->page_buff, 1);
memcpy(buff, nand->page_buff, buff_len);
}
if (nand->hardecc_flag)
{
nand->ops->get_hardecc(nand, &hardecc_cal);
nand->ops->control_hardecc(nand, OS_FALSE);
}
if (ret != OS_SUCCESS)
{
LOG_E(DRV_EXT_TAG, "nand read page failed!");
return OS_FAILURE;
}
}详细解释:需要读主数据时:
- 若启用 ECC,先
control_hardecc(OS_TRUE)打开硬件 ECC。 buff_len == page_size直接读到用户缓冲;否则读整页到page_buff再拷buff_len字节。- 读完后若启用 ECC,
get_hardecc取硬件计算的 ECC 值hardecc_cal,并control_hardecc(OS_FALSE)关闭。 - 读页失败(
ret != OS_SUCCESS)则打印并返回OS_FAILURE。
第 3 步:读 Spare 区并提取 OOB(无条件执行)
ret = nand->ops->read_spare(nand, page_addr, nand->spare_buff, 1);
if (ret != OS_SUCCESS)
{
LOG_E(DRV_EXT_TAG, "nand read spare failed!");
return OS_FAILURE;
}
for (int i = 0; i < nand->cfg.info.spare_size; i++)
{
if ((i >= nand->cfg.info.badflag_info.inpage_offset) &&
(i < nand->cfg.info.badflag_info.inpage_offset + nand->cfg.info.badflag_info.length))
{
}
else if ((i >= nand->cfg.info.hardecc_info.inpage_offset) &&
(i < nand->cfg.info.hardecc_info.inpage_offset + nand->cfg.info.hardecc_info.length))
{
hardecc_get = (uint32_t) * ((uint32_t *)(nand->spare_buff + i));
i += nand->cfg.info.hardecc_info.length;
}
else
{
if (oob != OS_NULL)
{
*oob++ = *(nand->spare_buff + i);
}
}
}详细解释:读整页 Spare 到 spare_buff,遍历每个字节:
- 坏块标记区:跳过(不提供给用户,属于框架)。
- ECC 区:读出硬件存储的 ECC 值
hardecc_get,跳过。 - 其余区域:作为用户 OOB 数据拷贝给
oob。
第 4 步:ECC 校验(条件 buff 存在 且 ECC 值不一致 且启用 ECC)
if (buff && (hardecc_cal != hardecc_get) && nand->hardecc_flag)
{
LOG_E(DRV_EXT_TAG, "nand read page data hardecc err %p", page_addr);
return OS_FAILURE;
}
return ret;
}详细解释:若读主数据时硬件算出的 ECC(hardecc_cal)与 Spare 区存储的 ECC(hardecc_get)不一致,说明数据有错(位翻转),返回 OS_FAILURE 提示 ECC 校验失败。
os_nand_read() 条件走向总结:
os_nand_read() 条件走向总结:
├── buff 和 oob 都空 或 长度都为 0 → 返回 OS_INVAL
├── 长度超限 → 返回 OS_FAILURE
├── if (buff != OS_NULL):
│ ├── hardecc_flag → control_hardecc(ON)
│ ├── buff_len == page_size → 直接读整页
│ │ └── 否则 → 读 page_buff 再拷贝
│ ├── hardecc_flag → get_hardecc(cal) + control_hardecc(OFF)
│ └── 读页失败 → 返回 OS_FAILURE
├── 读 Spare → 失败则返回 OS_FAILURE
├── for 遍历 Spare:
│ ├── 坏块标记区 → 跳过
│ ├── ECC 区 → 取 hardecc_get,跳过
│ └── 其余 → 拷贝给 oob
├── buff && ECC 值不一致 && hardecc_flag → LOG_E + 返回 OS_FAILURE
└── 返回 ret3.6 os_nand_write —— 写页数据与 OOB
一句话定位:写一页主数据(可带 ECC)与 OOB,把用户 OOB 与坏块标记、ECC 值拼装进 Spare 区。
完整源码(drivers/nand/nand.c,与项目一致):
os_err_t os_nand_write(os_nand_device_t *nand,
uint32_t page_addr,
const uint8_t *buff,
uint32_t buff_len,
const uint8_t *oob,
uint32_t oob_len)
{
os_err_t ret;
uint32_t hardecc_cal = 0;
if (((buff == OS_NULL) && (oob == OS_NULL)) || ((buff_len == 0) && (oob_len == 0)))
{
return OS_INVAL;
}
if ((buff_len > nand->cfg.info.page_size) || (oob_len > nand->cfg.oob_len))
{
LOG_E(DRV_EXT_TAG, "nandflash write size out of range");
return OS_FAILURE;
}
if (buff != OS_NULL)
{
if (nand->hardecc_flag)
{
nand->ops->control_hardecc(nand, OS_TRUE);
}
if (buff_len == nand->cfg.info.page_size)
{
ret = nand->ops->write_page(nand, page_addr, buff, 1);
}
else
{
memset(nand->page_buff, 0xFF, nand->cfg.info.page_size);
memcpy(nand->page_buff, buff, buff_len);
ret = nand->ops->write_page(nand, page_addr, nand->page_buff, 1);
}
if (nand->hardecc_flag)
{
nand->ops->get_hardecc(nand, &hardecc_cal);
nand->ops->control_hardecc(nand, OS_FALSE);
}
if (ret != OS_SUCCESS)
{
LOG_E(DRV_EXT_TAG, "nand write page failed!");
return OS_FAILURE;
}
}
if (oob != OS_NULL)
{
memset(nand->spare_buff, 0xFF, nand->cfg.info.spare_size);
for (int i = 0; i < nand->cfg.info.spare_size; i++)
{
if ((i >= nand->cfg.info.badflag_info.inpage_offset) &&
(i < nand->cfg.info.badflag_info.inpage_offset + nand->cfg.info.badflag_info.length))
{
*(nand->spare_buff + i) = 0xFF;
}
else if ((i >= nand->cfg.info.hardecc_info.inpage_offset) &&
(i < nand->cfg.info.hardecc_info.inpage_offset + nand->cfg.info.hardecc_info.length))
{
*(uint32_t *)(nand->spare_buff + i) = hardecc_cal;
i += nand->cfg.info.hardecc_info.length;
}
else
{
*(nand->spare_buff + i) = *oob++;
}
}
ret = nand->ops->write_spare(nand, page_addr, nand->spare_buff, 1);
if (ret != OS_SUCCESS)
{
LOG_E(DRV_EXT_TAG, "nand write spare failed!");
return OS_FAILURE;
}
}
return ret;
}分步详解:
第 1 步:参数校验(条件 无数据或无 OOB;长度超限)
if (((buff == OS_NULL) && (oob == OS_NULL)) || ((buff_len == 0) && (oob_len == 0)))
{
return OS_INVAL;
}
if ((buff_len > nand->cfg.info.page_size) || (oob_len > nand->cfg.oob_len))
{
LOG_E(DRV_EXT_TAG, "nandflash write size out of range");
return OS_FAILURE;
}详细解释:与读相同,校验参数有效性。
第 2 步:写主数据(条件 buff != OS_NULL)
if (buff != OS_NULL)
{
if (nand->hardecc_flag)
{
nand->ops->control_hardecc(nand, OS_TRUE);
}
if (buff_len == nand->cfg.info.page_size)
{
ret = nand->ops->write_page(nand, page_addr, buff, 1);
}
else
{
memset(nand->page_buff, 0xFF, nand->cfg.info.page_size);
memcpy(nand->page_buff, buff, buff_len);
ret = nand->ops->write_page(nand, page_addr, nand->page_buff, 1);
}
if (nand->hardecc_flag)
{
nand->ops->get_hardecc(nand, &hardecc_cal);
nand->ops->control_hardecc(nand, OS_FALSE);
}
if (ret != OS_SUCCESS)
{
LOG_E(DRV_EXT_TAG, "nand write page failed!");
return OS_FAILURE;
}
}详细解释:与读对称。写整页直接写;写部分页时,把 page_buff 填 0xFF 再拷入 buff_len 字节,整页写(NAND 整页编程)。启用 ECC 时,写后 get_hardecc 取得硬件算出的 ECC 值 hardecc_cal(稍后写入 Spare)。写页失败(ret != OS_SUCCESS)则打印并返回 OS_FAILURE。
第 3 步:拼装 Spare 区并写入(条件 oob != OS_NULL)
if (oob != OS_NULL)
{
memset(nand->spare_buff, 0xFF, nand->cfg.info.spare_size);
for (int i = 0; i < nand->cfg.info.spare_size; i++)
{
if ((i >= nand->cfg.info.badflag_info.inpage_offset) &&
(i < nand->cfg.info.badflag_info.inpage_offset + nand->cfg.info.badflag_info.length))
{
*(nand->spare_buff + i) = 0xFF;
}
else if ((i >= nand->cfg.info.hardecc_info.inpage_offset) &&
(i < nand->cfg.info.hardecc_info.inpage_offset + nand->cfg.info.hardecc_info.length))
{
*(uint32_t *)(nand->spare_buff + i) = hardecc_cal;
i += nand->cfg.info.hardecc_info.length;
}
else
{
*(nand->spare_buff + i) = *oob++;
}
}
ret = nand->ops->write_spare(nand, page_addr, nand->spare_buff, 1);
if (ret != OS_SUCCESS)
{
LOG_E(DRV_EXT_TAG, "nand write spare failed!");
return OS_FAILURE;
}
}详细解释:把整页 Spare 填 0xFF,遍历:
- 坏块标记区:写
0xFF(不覆盖坏块标记)。 - ECC 区:写入
hardecc_cal(本次写入产生的 ECC 校验值)。 - 其余:写入用户 OOB 数据。
然后整页写回 Spare 区。写 Spare 失败(ret != OS_SUCCESS)则打印并返回 OS_FAILURE。
os_nand_write() 条件走向总结:
os_nand_write() 条件走向总结:
├── 无数据且无 OOB 或长度都 0 → 返回 OS_INVAL
├── 长度超限 → 返回 OS_FAILURE
├── if (buff != OS_NULL):
│ ├── hardecc_flag → control_hardecc(ON)
│ ├── 整页 → 直接写;部分页 → 填 0xFF 再拷贝整页写
│ ├── hardecc_flag → get_hardecc(cal) + control_hardecc(OFF)
│ └── 写失败 → 返回 OS_FAILURE
├── if (oob != OS_NULL):
│ ├── Spare 填 0xFF,遍历拼装(坏块区 0xFF、ECC 区写 hardecc_cal、其余写 oob)
│ ├── write_spare;失败 → 返回 OS_FAILURE
└── 返回 ret3.7 页级与擦除封装
以下 API 是 os_nand_ops 的薄封装:
os_err_t os_nand_read_page(os_nand_device_t *nand, uint32_t page_addr, uint8_t *buff, uint32_t page_nr)
{
return nand->ops->read_page(nand, page_addr, buff, page_nr);
}
os_err_t os_nand_write_page(os_nand_device_t *nand, uint32_t page_addr, const uint8_t *buff, uint32_t page_nr)
{
return nand->ops->write_page(nand, page_addr, buff, page_nr);
}
os_err_t os_nand_erase_block(os_nand_device_t *nand, uint32_t block_no)
{
return nand->ops->erase_block(nand, block_no * nand->cfg.info.block_size);
}关键理解:os_nand_erase_block 把块号 block_no 乘以每块页数 block_size 转成页号,再调底层擦除。
四、完整使用示例
示例:坏块检测 + 页读写
#include <nand.h>
#include <device.h>
#include <os_stddef.h>
void nand_demo(void)
{
os_device_t *dev;
os_nand_device_t *nand;
uint8_t buf[2048];
uint32_t good_block;
int block;
os_err_t ret;
/* 1. 打开 NAND 设备 */
dev = os_device_open("nand0");
if (dev == OS_NULL)
{
os_kprintf("nand0 open failed\r\n");
return;
}
nand = (os_nand_device_t *)dev;
/* 2. 扫描前 64 个块,找到第一个好块 */
good_block = 0;
for (block = 0; block < 64; block++)
{
ret = os_nand_badflag_check(nand, block);
if (ret == OS_SUCCESS)
{
good_block = block;
break;
}
}
os_kprintf("first good block = %d\r\n", good_block);
/* 3. 写一页数据到好块第 0 页(页号 = 块号 * 块页数) */
memset(buf, 0xAA, sizeof(buf));
ret = os_nand_write_page(nand, good_block * nand->cfg.info.block_size, buf, 1);
if (ret != OS_SUCCESS)
{
os_kprintf("write page failed\r\n");
return;
}
/* 4. 读回并校验 */
memset(buf, 0x00, sizeof(buf));
ret = os_nand_read_page(nand, good_block * nand->cfg.info.block_size, buf, 1);
if (ret != OS_SUCCESS)
{
os_kprintf("read page failed\r\n");
return;
}
for (int i = 0; i < (int)sizeof(buf); i++)
{
if (buf[i] != 0xAA)
{
os_kprintf("verify failed at %d\r\n", i);
return;
}
}
os_kprintf("nand read/write/verify ok\r\n");
}说明:示例展示 NAND 标准用法:打开设备 → 坏块检测找到好块 → 写页 → 读页校验。实际效果:
os_nand_badflag_check检查块首/次页 Spare 标记判断好坏,os_nand_write_page/read_page封装底层页操作。页号需由块号 * block_size换算。
注意:写前必须先确保目标块是好块(坏块检测),并先擦除(NAND 写前需擦除,应用或文件系统处理)。数据位翻转由 ECC 校验发现(
os_nand_read内部校验)。
五、NAND 的适配(接入新 NAND 芯片 / 新 MCU 控制器)
5.1 先认识适配层:NAND 框架怎么"长"到芯片上
drivers/nand/nand.c 是跨平台统一框架(三级寻址、坏块管理、硬件 ECC 控制、Spare 区拼装、FAL 注册),不碰具体寄存器。真正操作 NAND 控制器的是各厂商 HAL 驱动(如 ST drv_nand.c、GD drv_nand.c),它们实现 os_nand_ops 九回调。换 NAND 芯片/新 MCU,就是实现一套 os_nand_ops + 声明芯片信息。
适配全景图:
┌──────────────────────────────────────────────────────────┐
│ 应用层(os_nand_read/write/badflag_check) │
└──────────────────────────────┬───────────────────────────┘
▼
┌──────────────────────────────────────────────────────────┐
│ NAND 框架层(drivers/nand/nand.c,通用不改) │
│ 三级寻址 + 坏块管理 + 硬件 ECC + Spare 拼装 + FAL 注册 │
└───────────────┬──────────────────────────┬────────────────┘
│ ①实现 os_nand_ops │ ②声明 nand_device_info
▼ ▼
┌──────────────────────────────────────────────────────────┐
│ 厂商驱动(drv_nand.c)★需实现★ │
│ read/write_page/spare、erase_block、3 个硬件 ECC 回调 │
└───────────────┬──────────────────────────┬────────────────┘
│ HAL 库(NAND 控制器) │
▼ ▼
┌──────────────────────────────────────────────────────────┐
│ 硬件(NAND 芯片 + MCU NAND 控制器) │
└──────────────────────────────────────────────────────────┘适配的关键洞察:接入新 NAND 只需做两件事:① 实现 struct os_nand_ops 九个回调(read/write_page、read/write_spare、erase_block、config/control/get_hardecc、get_status);② 声明 nand_device_info 芯片信息表(ID/页/块/区/Spare/ECC 布局),框架按 ID 查找并自动配置。hardecc_flag 由 os_nand_device_register 探测(三个 ECC 回调都实现且 config_hardecc 成功才启用)。
5.2 接口契约:接入 NAND 需要实现什么
| 层 | 接口/回调 | 职责 | 说明 |
|---|---|---|---|
| 操作回调 | os_nand_ops.read_page/write_page | 主数据区读写 | 页号语义 |
| 操作回调 | os_nand_ops.read_spare/write_spare | Spare 区(OOB)读写 | 页号语义 |
| 操作回调 | os_nand_ops.erase_block | 擦一块 | page_addr 为块首页 |
| 操作回调 | os_nand_ops.config_hardecc | 初始化硬件 ECC | 不实现则 hardecc_flag=FALSE |
| 操作回调 | os_nand_ops.control_hardecc | 启停硬件 ECC | 同上 |
| 操作回调 | os_nand_ops.get_hardecc | 取 ECC 值 | 同上 |
| 操作回调 | os_nand_ops.get_status | 取芯片状态 | —— |
| 芯片信息 | nand_device_info 表 | ID/几何/Spare/ECC 布局 | get_nand_info_by_id 查找 |
5.3 适配新 NAND 芯片/控制器的标准流程
第 1 步:查手册确认芯片参数——ID(nand_device_info.id)、页大小、Spare 大小、块页数、区块数、区数、坏块标记位置、硬件 ECC 布局。
第 2 步:声明芯片信息表——在 nand_devices.c 用 OS_NAND_INFO(或类似声明)加入 nand_device_info,填 ID/几何/badflag_info/hardecc_info,get_nand_info_by_id 才能按 ID 找到。
第 3 步:实现 os_nand_ops 九回调——写厂商 drv_nand.c,实现页/Spare 读写、擦除、三个 ECC 回调、状态读取。
第 4 步:注册设备——os_nand_device_register(&nand, "nand0", id),框架自动算寻址参数、分配缓冲、注册 FAL。
第 5 步:测试——os_nand_badflag_check 测坏块、os_nand_write/read 测页读写回读、开 ECC 验证纠错。
5.4 适配检查清单(快速自查)
| 检查项 | 说明 |
|---|---|
芯片 id 与 nand_device_info 表一致? | 不一致则 get_nand_info_by_id 返回 OS_NULL、注册失败 |
| 硬件 ECC 三回调齐全? | 缺一个则 hardecc_flag=FALSE,读时不做 ECC 校验 |
badflag_info/hardecc_info 偏移填对? | 错则坏块检测读错位置、ECC 拼装错位 |
bus_width/几何参数填对? | 错则寻址位宽算错、容量错 |
| 页缓冲/Spare 缓冲分配? | 框架自动 os_calloc,OS_ASSERT 保护,内存不足崩溃 |
📝 本节小结
核心要点回顾
本节围绕 NAND Flash 设备展开,核心要点如下:
1. 三级寻址模型
- 线性页号通过位段划分成 page/block/plane 三级。
nand_page2addr(线性→三级)用掩码提取;nand_addr2page(三级→线性)用移位 + 位或。- 关键参数
page_shift/block_shift/plane_shift由os_nand_device_register计算。
2. 坏块管理
- 检测:读块首、次页 Spare 固定位置,标记 ≠
0xFF即坏块。 - 标记:块首、次页 Spare 写入
mark_data(0x55)。
3. 硬件 ECC
- 读:
control_hardecc(ON)→ 读页 →get_hardecc(cal)→ 比对 Spare 存储的hardecc_get,不一致报 ECC 错。 - 写:
control_hardecc(ON)→ 写页 →get_hardecc(cal)→ 把cal写入 Spare 的 ECC 区。
4. Spare 区布局
- Spare 区 = 坏块标记区 + ECC 区 + 用户 OOB。
oob_len = spare_size - badflag.length - hardecc.length。
5. 硬件适配
- 接入新 NAND = 实现
os_nand_ops九回调 + 声明nand_device_info芯片表,框架按 ID 配置寻址/坏块/ECC。 hardecc_flag由三 ECC 回调齐全且config_hardecc成功决定。
关键易错点速查
| 易错点 | 正确做法 |
|---|---|
| 写前未检测坏块 | 写前必须先 os_nand_badflag_check,坏块要跳过或标记,否则数据写丢 |
| 混淆块号与页号 | os_nand_erase_block 传入的是块号,read/write_page 传入的是页号,页号 = 块号 × 块页数 |
| 忽略写前擦除 | NAND 写前必须先擦除块,否则写入失败 |
混淆 os_nand_read 与 os_nand_read_page | os_nand_read 带 ECC + OOB + Spare 拼装;os_nand_read_page 只是底层页读封装 |
| 手动改坏块标记区 | 坏块标记区由框架管理(读时跳过、写时置 0xFF),用户 OOB 不应覆盖它 |
| 误以为 ECC 一定能纠错 | 框架检测到 ECC 不一致只返回 OS_FAILURE(提示数据错误),能否纠错取决于底层驱动 |
芯片 id 与表不一致 | get_nand_info_by_id 按 ID 查表,不一致则返回 OS_NULL、os_nand_device_register 失败 |
| ECC 回调不齐全 | 三 ECC 回调缺一 → hardecc_flag=FALSE,读时无 ECC 校验、位翻转检测不到 |
下一步
掌握了 NAND Flash 设备后,接下来学习 Block 块设备:
- 5.29 Block设备:了解 OneOS 以块为单位读写、为文件系统提供标准接口的 Block 设备。