【Zephyr开发实践系列】06_存储块设备驱动开发(Nand Flash)

文章目录

  • 前言
  • 一、Flash驱动模型介绍
    • 1.1 核心基础应用API(必须)
    • 1.2 高级功能应用API(可选)
    • 1.3 设置数据结构
    • 1.4 硬件初始化
    • 1.5 设备实例化
  • 二、数据结构定义
    • 2.1 获取Flash块与页大小
  • 三、核心API函数实现
    • 3.1 擦除函数
    • 3.2 读取函数
    • 3.3 写入函数
    • 4.4 layout函数
    • 4.5 坏区检测函数
  • 总结


前言

在嵌入式系统中,常见的 Flash 存储模块根据接口类型和用途可分为NOR、NAND、EMMC、SD卡等。本文基于NAND-Flash介绍Zephyr下的驱动模型,SPI与HAL层寄存器操作暂不做解析,仅基于API实现最基本接口。


一、Flash驱动模型介绍

最基本的Flash模块驱动模型包括以下几个要点:

1.1 核心基础应用API(必须)

  • read:从 Flash 读取数据
  • write:写入数据到 Flash
  • erase:擦除指定区域(扇区/块)
  • get_parameters:获取 Flash 物理参数(页大小、块大小、总容量等)

1.2 高级功能应用API(可选)

  • page_layout:提供 Flash 物理布局(如分页/分块信息)
  • sfdp_read:通过 SFDP自动识别 Flash 参数
  • read_jedec_id:读取JEDEC ID
  • ex_op:扩展操作(如 QSPI 四线模式使能、DTR 模式配置等)

1.3 设置数据结构

  • 配置结构: 只保留时钟频率/设备、块大小,页大小

1.4 硬件初始化

  • 时钟使能: 确保flash模块时钟开启与在要求频率范围
  • 引脚配置:若无pin-ctrl系统,需要直接配置引脚
  • 取消保护操作: 取消Flash模块块保护
  • 根据需要配置模式(四线,两线)

1.5 设备实例化

  • 配置结构初始化: 从设备树提取基本配置
  • 设备注册宏: 使用DEVICE_DT_INST_DEFINE注册设备
  • 初始化优先级: 设置为POST_KERNEL
  • 根据需求注册电源管理PM

二、数据结构定义

2.1 获取Flash块与页大小

static const struct flash_parameters shenju_flash_parameters = {
#if DT_NODE_HAS_PROP(SOC_FLASH_NODE, write_block_size)
	.write_block_size = DT_PROP(SOC_FLASH_NODE, write_block_size),
#else
	.write_block_size = DEFAULT_WRITE_BLOCK_SIZE,
#endif
	.erase_value = 0xff,
};

说明:

  • DT_PROP根据设备树获取信息
  • 参数write_block_size:128*1024Byte
  • 擦除后值为0xFF

三、核心API函数实现

3.1 擦除函数

static int flash_erase(const struct device *dev, off_t offset, size_t len)
{
  
if (FLASH_TYPE == NAND)
    int block_size = spinand_get_block_size();
    int block_number = offset / block_size;
    int block_count = len / block_size;
    if(len % block_size) block_count++;
    spinand_erase_length(block_number, block_count, 1);
    return 0;
}

说明:

  • 一块block大小为128k
  • 根据地址判断待擦除的block号
  • 根据擦除的数据长度,若存在少量剩余,则按一块计算

3.2 读取函数

static int flash_read(const struct device *dev, off_t offset, void *data, size_t len)
{
    sys_cache_data_flush_and_invd_range(data, len);
	if (FLASH_TYPE == NAND)
    // spinand_read((uint32_t)data, offset, len);
    spinand_read_quad((uint32_t)data, offset, len);
    return 0;
}

说明:

  • 读取数据之前需要确保缓冲区数据与物理内存一致(关键)
  • 使用标准SPI或四线Quad SPI进行接收数据
  • 标准SPI可不使用cache,Quad SPI必须使用cache方式操作

3.3 写入函数

static int flash_write(const struct device *dev, off_t offset, const void *data, size_t len)
{

    uint32_t write_flash_addr = offset;
    uint16_t write_len = len;
    uint8_t *write_data = (uint8_t *)data;
    
	if ((ret = spinand_write_enable()) != 0) {
    	return ret;
	}
    sys_cache_data_flush_range(write_data, write_len);


	if (FLASH_TYPE == NAND)
    // spinand_write((uint32_t)write_data, write_flash_addr, write_len);
    spinand_write_quad((uint32_t)write_data, write_flash_addr, write_len);
    return 0;

}

说明:

  • 发送Write Enable
  • 数据维护
  • Quad模式页或普通模式写入

4.4 layout函数

void flash_page_layout(const struct device *dev,
			     const struct flash_pages_layout **layout,
			     size_t *layout_size)
{
	static struct flash_pages_layout flash_layout = {
		.pages_count = 0,
		.pages_size = 0,
	};

	ARG_UNUSED(dev);

	if (flash_layout.pages_count == 0) {
        flash_layout.pages_size = DT_PROP(DT_CHOSEN(zephyr_flash), block_size);
		flash_layout.pages_count = DT_REG_SIZE(DT_CHOSEN(zephyr_flash))/flash_layout.pages_size;
	}

	*layout = &flash_layout;
	*layout_size = 1;
}

说明:

  • 作用于文件分区系统
  • 可用于数据更新与板卡升级

4.5 坏区检测函数

void spinand_detect_bad_block_4kpage(void)
{
    uint8_t buf[4];
    for (uint32_t i = 0; i < 64; i++) {
        spinand_read_to_cache(64 * i, 1);
        spinand_read_from_cache((uint32_t)buf, 1024 * 2, 4);
        if (buf[0] != 0xff) {
            LOG_WRN("block:%u bad, value:%x\n", i, buf[0]);
        }
    }
}

说明:

  • 遍历所有块(假设共64个块)

  • 读取每个块的第一页 的OOB(Out-of-Band)区域前4字节

  • 检查OOB数据:若首字节非 0xFF,则标记为坏块

总结

这段代码展示了SPI NAND Flash(如W25N04KV)驱动的核心逻辑:通过坏块检测和Quad SPI加速实现高效存储管理。包括:揪出故障区块、四线Quad SPI拉满传输速度、sys_cache系列操作确保CPU与闪存数据一致性

你可能感兴趣的:(Zephyr实践开发,驱动开发,单片机,嵌入式硬件,linux,iot,mcu,物联网)