NandFlash作为现代嵌入式系统的核心存储介质,其重要性不言而喻。我第一次接触NandFlash是在2012年调试一块工业控制板时,当时系统频繁出现数据丢失问题,追查后发现是NandFlash驱动配置不当导致的。这种非易失性存储器以其高密度、低成本的优势,在消费电子、物联网设备等领域占据主导地位,但同时也因其特殊的物理特性给开发者带来不少挑战。
NandFlash与NorFlash的最大区别在于访问方式:NorFlash支持随机访问,而NandFlash只能按页(Page)读写。这种差异直接影响了它们在系统中的应用场景——NorFlash常用于存储启动代码,而NandFlash则更适合大容量数据存储。目前主流NandFlash的页大小从4KB到16KB不等,每个块(Block)包含64-256个页,典型的块大小从256KB到4MB。
重要提示:NandFlash的"页"是读写最小单位,"块"是擦除最小单位,这个特性直接影响文件系统设计和驱动实现
NandFlash的存储单元采用浮栅MOSFET结构,通过浮栅中捕获的电子数量来表示数据状态。SLC(Single-Level Cell)每个单元存储1bit数据,MLC存储2bit,TLC存储3bit,QLC则达到4bit。随着存储密度的提高,单元的耐久性和读写速度会相应降低。以某品牌64Gb NandFlash为例,其SLC版本可承受10万次擦写,而QLC版本可能只有1000次。
存储阵列采用"串-并联"结构,多个存储单元串联形成NAND串,多个NAND串并联形成阵列。这种结构使得NandFlash的读取需要经过位线预充电、字线电压施加、感应放大器检测等多个阶段,典型的随机读取延迟在25μs左右。
NandFlash出厂时就存在坏块,且在使用过程中会不断产生新的坏块。坏块管理是驱动设计中的关键环节,通常采用以下方法:
在Linux驱动中,坏块管理通常由MTD层和NandFlash控制器共同完成。以某平台为例,其坏块表结构如下:
| 偏移量 | 长度 | 描述 |
|---|---|---|
| 0x00 | 1B | 坏块标记(0xFF表示好块) |
| 0x01 | 3B | 保留 |
| 0x04 | 28B | ECC校验码 |
Linux的MTD(Memory Technology Device)子系统为NandFlash提供了统一的抽象接口,其架构分为四层:
典型的驱动注册流程如下:
c复制static struct platform_driver my_nand_driver = {
.probe = my_nand_probe,
.remove = my_nand_remove,
.driver = {
.name = "my-nand",
.of_match_table = my_nand_of_match,
},
};
static int __init my_nand_init(void)
{
return platform_driver_register(&my_nand_driver);
}
struct nand_chip:封装NandFlash芯片的属性和操作
cmdfunc:发送命令函数指针read_byte:读取字节函数指针write_buf:写入缓冲区函数指针struct mtd_info:MTD设备的核心结构
_read:读操作函数_write:写操作函数_erase:擦除操作函数struct nand_controller:控制器抽象
ops:包含控制器操作集合lock:并发控制锁以某ARM平台为例,完整的初始化序列如下:
配置时钟和引脚复用
c复制writel(0x5, CLK_REG_BASE + 0x30); // 设置NAND控制器时钟
set_pin_mux(GPIO_BANK3, 0x3F, ALT_FUNCTION2); // 配置数据线
初始化DMA控制器(如使用)
c复制dma_cfg.src_addr = NAND_DATA_REG;
dma_cfg.dst_addr = buf_dma_addr;
dma_config_channel(DMA_CH0, &dma_cfg);
设置时序参数(以某芯片为例)
c复制timing_reg.tCLS = 3; // CLE到RE的延迟
timing_reg.tALS = 2; // ALE到RE的延迟
timing_reg.tWP = 5; // WE脉冲宽度
write_timing_reg(&timing_reg);
NandFlash操作通常需要处理以下中断:
推荐采用任务完成量的方式避免忙等待:
c复制static DECLARE_COMPLETION(cmd_done);
irqreturn_t nand_isr(int irq, void *dev_id)
{
u32 status = readl(STATUS_REG);
if (status & CMD_DONE) {
complete(&cmd_done);
}
return IRQ_HANDLED;
}
void send_nand_cmd(u8 cmd)
{
reinit_completion(&cmd_done);
writeb(cmd, CMD_REG);
if (!wait_for_completion_timeout(&cmd_done, msecs_to_jiffies(100))) {
dev_err(dev, "CMD timeout\n");
}
}
使用多平面操作:同时操作多个平面(需芯片支持)
c复制// 设置多平面命令序列
nand_cmdfunc(mtd, NAND_CMD_READ0, 0, page);
nand_cmdfunc(mtd, NAND_CMD_READSTART, 0, page+1);
启用片上缓存:利用NandFlash的缓存机制
c复制// 配置缓存模式
chip->cache_op = my_cache_operation;
chip->setup_read_retry = my_setup_read_retry;
DMA优化:减少CPU参与
c复制dma_map_single(dev, buf, len, DMA_FROM_DEVICE);
start_dma_transfer();
wait_for_dma_completion();
dma_unmap_single(dev, dma_addr, len, DMA_FROM_DEVICE);
数据校验错误:
擦除失败:
驱动加载失败:
现代NandFlash控制器通常集成硬件ECC模块,以某SoC为例,其BCH编码配置如下:
设置ECC强度(每512字节的纠错能力)
c复制ecc_cfg.strength = 8; // 可纠正8bit错误
ecc_cfg.size = 512; // 每512字节一个ECC段
set_ecc_config(&ecc_cfg);
计算ECC校验码
c复制start_ecc_calculation(data_buf);
wait_ecc_done();
ecc_code = read_ecc_result();
错误定位与纠正
c复制if (check_ecc_error()) {
locate_ecc_errors();
correct_errors();
}
对于没有硬件ECC的系统,可采用以下软件方案:
Hamming码:简单高效,适合SLC NandFlash
c复制uint8_t calculate_hamming(uint8_t *data, int len)
{
uint8_t ecc = 0;
for (int i = 0; i < len; i++) {
ecc ^= data[i];
}
return ecc;
}
BCH码:适合MLC/TLC的高强度纠错
c复制struct bch_control *bch = init_bch(13, 8, 0); // m=13,t=8
encode_bch(bch, data, len, ecc);
Reed-Solomon:用于NandFlash控制器的高级ECC
3D Nand与传统2D Nand的主要差异:
地址周期变化:增加了层选择命令
c复制// 3D Nand地址周期示例
addrs[0] = column & 0xff;
addrs[1] = (column >> 8) & 0x0f;
addrs[2] = page & 0xff;
addrs[3] = (page >> 8) & 0xff;
addrs[4] = (page >> 16) & 0x03; // 层选择
新命令集:
时序调整:
ONFI参数读取:
c复制nand_cmdfunc(mtd, NAND_CMD_READID, 0x20, -1);
onfi_params = kmalloc(sizeof(*onfi_params), GFP_KERNEL);
nand_read_data_op(chip, onfi_params, sizeof(*onfi_params), false);
时序模式切换:
c复制// 切换到ONFI 3.0模式
set_features(chip, ONFI_FEATURE_ADDR_TIMING, ONFI_TIMING_MODE_3);
DQS信号处理(Toggle模式):
c复制if (chip->parameters.onfi.version >= ONFI_VERSION_2_3) {
chip->options |= NAND_USE_DQS;
setup_toggle_mode_io();
}
在最近的一个项目中,我们遇到了NandFlash在低温下(-20℃)数据保持时间急剧缩短的问题。经过两周的排查,最终发现是ECC配置不当导致的:
另一个常见问题是NandFlash在长期使用后出现的"读干扰"现象。我们的应对方案是: