NOR Flash 驱动

更新时间:
2024-01-15
下载文档

NOR Flash 驱动

本章将介绍 MS-RTOS NOR Flash 驱动开发及测试。

NOR Flash 根据数据传输的位数可以分为并行(Parallel)NOR Flash 和串行(SPI)NOR Flash(即 SPI Flash)。SPI NOR Flash 每次传输一个 bit 位的数据,parallel NOR Flash 每次传输多个 bit 位的数据(有 x8 和 x16 bit 两种);SPI Flash 便宜,接口简单点,但速度慢。

NAND Flash 具有较快的抹写时间,而且每个存储单元的面积也较小,这让NAND Flash 相较于 NOR Flash 具有较高的存储密度与较低的每比特成本。同时它的可抹除次数也高出 NOR Flash 十倍。然而 NAND Flash 的 I/O 接口并没有随机存取外部地址总线,它必须以区块性的方式进行读取,NAND Flash 典型的区块大小是数百至数千比特。NAND Flash 非常适合用于储存卡之类的大量存储设备。

eMMC(Embedded Multi Media Card)为 MMC 协会所订立的,eMMC 相当于 NandFlash+ 主控 IC ,对外的接口协议与 SD、TF 卡一样,主要是针对手机或平板电脑等产品的内嵌式存储器标准规格。eMMC 的一个明显优势是在封装中集成了一个控制器,它提供标准接口并管理闪存,使得手机厂商就能专注于产品开发的其它部分,并缩短向市场推出产品的时间。

1. NOR Flash 基础知识

1.1 NOR Flash 接口

NOR Flash 根据数据传输的位数可以分为并行(Parallel)NOR Flash 和串行(SPI)NOR Flash(即 SPI Flash),接下来我们主要讲解 SPI Flash 的相关内容。

NOR Flash_interface

FLASH 芯片(型号:W25Q128)是一种使用 SPI 通讯协议的 NOR FLASH 存储器,它的 CS/CLK/DIO/DO 引脚分别连接到了 STM32 对应的 SDI 引脚 NSS/SCK/MOSI/MISO 上,其中 STM32 的 NSS 引脚是一个普通的 GPIO,不是 SPI 的专用 NSS 引脚,所以程序中我们要使用软件控制的方式。

FLASH 芯片中还有 WP 和 HOLD 引脚。WP 引脚可控制写保护功能,当该引脚为低电平时,禁止写入数据。我们直接接电源,不使用写保护功能。HOLD 引脚可用于暂停通讯,该引脚为低电平时,通讯暂停,数据输出引脚输出高阻抗状态,时钟和数据输入引脚无效。

1.2 NOR Flash 读写

主机首先通过 MOSI 线向 FLASH 芯片发送第一个字节数据为 9F h,当 FLASH 芯片收到该数据后,它会解读成主机向它发送了 JEDEC 指令,然后它就作出该命令的响应:通过 MISO 线把它的厂商 ID(M7-M0)及芯片类型(ID15-0)发送给主机,主机接收到指令响应后可进行校验。常见的应用是主机端通过读取设备 ID 来测试硬件是否连接正常,或用于识别设备。

NOR Flash_read_id

在向 FLASH 芯片存储矩阵写入数据前,首先要使能写操作,通过 Write Enable 命令即可写使能。我们只关注这个状态寄存器的第 0 位 BUSY,当这个位为 1 时,表明 FLASH 芯片处于忙碌状态,它可能正在对内部的存储矩阵进行擦除或数据写入的操作。利用指令表中的 Read Status Register 指令可以获取 FLASH 芯片状态寄存器的内容。

NOR Flash_status

通常,对存储矩阵擦除的基本操作单位都是多个字节进行,如本例子中的 FLASH 芯片支持扇区擦除、块擦除以及整片擦除。扇区擦除指令的第一个字节为指令编码,紧接着发送的 3 个字节用于表示要擦除的存储矩阵地址。要注意的是在扇区擦除指令前,还需要先发送写使能指令,发送扇区擦除指令后,通过读取寄存器状态等待扇区擦除操作完毕。

NOR Flash_erase

目标扇区被擦除完毕后,就可以向它写入数据了。与 EEPROM 类似,FLASH 芯片也有页写入命令,使用页写入命令最多可以一次向 FLASH 传输 256 个字节的数据,我们把这个单位称为页大小。

NOR Flash_write

相对于写入,FLASH 芯片的数据读取要简单得多,使用读取指令 Read Data 即可。发送了指令编码及要读的起始地址后,FLASH 芯片就会按地址递增的方式返回存储矩阵的内容,读取的数据量没有限制,只要没有停止通讯,FLASH 芯片就会一直返回数据。

NOR Flash_read

2. NOR Flash 驱动框架

2.1 驱动相关数据结构

(1)ms_io_device_t

在 MS-RTOS 中,在注册块设备驱动时,我们只需要注册一个驱动为 “null” 的设备,然后实现一个文件系统底层读写操作函数集的结构体(由具体使用的文件系统决定),并在注册设备时将该结构体作为设备的私有数据传入。

/*
 * Private information of chip
 */
typedef struct {
    char              *chip_name;
    ms_uint32_t        id;
    ms_uint8_t         en_4byte_addr;

    ms_size_t          size;
    ms_size_t          sector_size;
    ms_size_t          page_size;

    const ms_chip_cmd *cmds;
} ms_chip_info;

/*
 * Provide to litterfs for data transmission
 */
typedef struct {
    const ms_chip_info *cur_chip_info;
    ms_spi_device_t     spi_dev;
} privinfo_t;

/*
 * nor flash infomation
 */
typedef struct {
    ms_io_device_t     dev;
    privinfo_t         priv;
} ms_nor_dev_t;

以 SPI Flash 为例,我们将 SPI Flash 挂到 LittleFS ,则在注册 Flash 设备时,使用如下代码:

ms_io_device_register(&dev->dev, NOR Flash_port->dev_path, "ms_null", lfs_cfg);

(2)struct lfs_config

在 MS-RTOS 中,将一个块设备以指定的文件系统挂载到指定挂载点时,调用 ms_io_mount 来进行挂载操作。以 SPI Flash 为例,我们将 SPI Flash 挂到 LittleFS ,则使用如下代码:

struct lfs_config {
    // Read a region in a block. Negative error codes are propogated
    // to the user.
    int (*read)(const struct lfs_config *c, lfs_block_t block,
            lfs_off_t off, void *buffer, lfs_size_t size);

    // Program a region in a block. The block must have previously
    // been erased. Negative error codes are propogated to the user.
    // May return LFS_ERR_CORRUPT if the block should be considered bad.
    int (*prog)(const struct lfs_config *c, lfs_block_t block,
            lfs_off_t off, const void *buffer, lfs_size_t size);

    // Erase a block. A block must be erased before being programmed.
    // The state of an erased block is undefined. Negative error codes
    // are propogated to the user.
    // May return LFS_ERR_CORRUPT if the block should be considered bad.
    int (*erase)(const struct lfs_config *c, lfs_block_t block);

    // Sync the state of the underlying block device. Negative error codes
    // are propogated to the user.
    int (*sync)(const struct lfs_config *c);
    
    .......
};

lfs_cfg->context        = &dev->priv;
lfs_cfg->read_size      =  1U;
lfs_cfg->prog_size      =  priv->cur_chip_info->page_size;
lfs_cfg->block_size     =  priv->cur_chip_info->sector_size;
lfs_cfg->block_count    =  priv->cur_chip_info->size / priv->cur_chip_info->sector_size;
lfs_cfg->cache_size     =  priv->cur_chip_info->page_size;
lfs_cfg->block_cycles   =  500U;
lfs_cfg->lookahead_size =  8U * ((priv->cur_chip_info->size 
                                  / priv->cur_chip_info->sector_size + 63U) / 64U);
lfs_cfg->read           =  __NOR Flash_block_read;
lfs_cfg->prog           =  __NOR Flash_block_prog;
lfs_cfg->erase          =  __NOR Flash_block_erase;
lfs_cfg->sync           =  __NOR Flash_block_sync;

ms_io_mount(NOR Flash_port->mount_path,  NOR Flash_port->dev_path, 
            MS_LITTLEFS_NAME, MS_NULL);

2.2 驱动的注册和卸载

(1)Flash 驱动开发流程:

搞定 SPI 的基本收发单元后,还需要了解如何对 Flash 芯片进行读写。Flash 芯片自定义了很多指令,我们通过控制 SPI 总线向 Flash 芯片发送指令,Flash 芯片收到后就会执行相应的操作。

  • 获取必要的软硬件开发资源,了解设备的基本特性;
  • 确定 Flash 的型号,存储结构体和容量等信息;
  • 熟悉 Flash 芯片指令表,找到主要的读写指令并封装成函数接口;
  • 读取 Flash 芯片的 ID,确定 SPI 总线和 Flash 能正常通信;
  • 实现文件系统需要的底层操作接口;
  • 编写测试程序,对读写数据进行校验;

(2)驱动的注册和卸载接口:

ms_err_t ms_io_driver_register(ms_io_driver_t *drv);
ms_err_t ms_io_driver_unregister(ms_io_driver_t *drv); //此接口暂不开放

(3)设备节点的注册和卸载接口:

ms_err_t ms_io_device_register(ms_io_device_t *dev, const char *dev_path, 
                               const char *drv_name, ms_ptr_t ctx);
ms_err_t ms_io_device_unregister(ms_io_device_t *dev);

2.3 NOR Flash 驱动示例

NOR Flash 驱动示例,仅作为参考。

#define __MS_IO
#include "ms_config.h"
#include "ms_rtos.h"
#include "ms_io_core.h"
#include "includes.h"
#include "ms_littlefs.h"

/*
 * Read a region in a block. Negative error codes are propogated to the user.
 */
static int __spi_nor_block_read(const struct lfs_config *c, lfs_block_t block,
                                   lfs_off_t off, void *buffer, lfs_size_t size)
{
    int ret;

    if (BSP_QSPI_Read((uint8_t *)buffer, 
                      (block * c->block_size + off), size) == QSPI_OK) {
        ret = LFS_ERR_OK;
    } else {
        ret = LFS_ERR_CORRUPT;
    }

    return ret;
}

/*
 * Program a region in a block. The block must have previously
 * been erased. Negative error codes are propogated to the user.
 * May return LFS_ERR_CORRUPT if the block should be considered bad.
 */
static int __spi_nor_block_prog(const struct lfs_config *c, lfs_block_t block,
                                   lfs_off_t off, const void *buffer, lfs_size_t size)
{
    int ret;

    if (BSP_QSPI_Write((uint8_t *)buffer, 
                       (block * c->block_size + off), size) == QSPI_OK) {
        ret = LFS_ERR_OK;
    } else {
        ret = LFS_ERR_CORRUPT;
    }

    return ret;
}

/*
 * Erase a block. A block must be erased before being programmed.
 * The state of an erased block is undefined. Negative error codes
 * are propogated to the user.
 * May return LFS_ERR_CORRUPT if the block should be considered bad.
 *
 */
static int __spi_nor_block_erase(const struct lfs_config *c, lfs_block_t block)
{
    int ret;

    if (BSP_QSPI_Erase_Block(block * c->block_size) == QSPI_OK) {
        ret = LFS_ERR_OK;
    } else {
        ret = LFS_ERR_CORRUPT;
    }

    return ret;
}

/*
 * Sync the state of the underlying block device. Negative error codes
 * are propogated to the user.
 */
static int __spi_nor_block_sync(const struct lfs_config *c)
{
    return 0;
}

/*
 * configuration of the filesystem is provided by this struct
 */
static struct lfs_config spi_nor_cfg = {
    /*
     * block device operations
     */
    .read  = __spi_nor_block_read,
    .prog  = __spi_nor_block_prog,
    .erase = __spi_nor_block_erase,
    .sync  = __spi_nor_block_sync,
};

/*
 * Create spi nor flash device file and mount
 */
ms_err_t spi_nor_dev_init(const char *path, const char *mnt_path)
{
    static ms_io_device_t spi_nor_dev;
    QSPI_Info info;
    ms_err_t err;

    if (BSP_QSPI_Init() == QSPI_OK) {
        if (BSP_QSPI_GetInfo(&info) == QSPI_OK) {
            spi_nor_cfg.read_size      = 1U;
            spi_nor_cfg.prog_size      = info.ProgPageSize;
            spi_nor_cfg.block_size     = info.EraseSectorSize;
            spi_nor_cfg.block_count    = info.EraseSectorsNumber;
            spi_nor_cfg.cache_size     = info.ProgPageSize;
            spi_nor_cfg.block_cycles   = 500U;
            spi_nor_cfg.lookahead_size = 8U * ((spi_nor_cfg.block_count + 63U) / 64U);

            err = ms_io_device_register(&spi_nor_dev, path, "ms_null", &spi_nor_cfg);
            if (err == MS_ERR_NONE) {
                err = ms_io_mount(mnt_path, path, MS_LITTLEFS_NAME, MS_NULL);
            }
            
        } else {
            err = MS_ERR;
        }
    } else {
        err = MS_ERR;
    }

    return err;
}

3. 文件读写应用

3.1 文件系统读写

#include <ms_rtos.h>
#include <string.h>
#include "test/include/greatest.h"

/*
 * Marco definition area
 */
#define TEST_FILE_PATH1         "/nor/1.txt"
#define TEST_FILE_PATH2         "/nor/2.txt"
#define TEST_FILE_PATH3         "/nor/3.txt"
#define TEST_DIR_PATH           "/nor/workspace"
#define TEST_LINK_FILE          "/nor/link_file"
#define TEST_BUFFER_SIZE        (64)
#define TEST_FILE_SIZE          (4*1024)

/*
 * test_file_read
 */
int main(int argc, char *argv[])
{
    int         fd;
    ms_ssize_t  ret;
    ms_uint8_t  read_buf[10];
    ms_uint8_t  write_buf[10] = {11, 12, 13, 14, 15, 16, 17, 18, 19, 20};

    fd = ms_io_open(TEST_FILE_PATH2, O_CREAT | O_RDWR, 0666);
    if (fd < 0) {
        ms_printf("[error]: open file %s failed!\n", TEST_FILE_PATH2);
        return  (-1);
    }

    ret = ms_io_write(fd, write_buf, sizeof(write_buf));
    if (ret != 10) {
        ms_printf("[error]: write failed! errno = %d\n", errno);
        ms_io_close(fd);
        return  (-1);
    }

    ret = ms_io_lseek(fd, 0, SEEK_SET);
    if (ret != 0) {
        ms_printf("[error]: lseek failed! errno = %d\n", errno);
        ms_io_close(fd);
        return  (-1);
    }

    ret = ms_io_read(fd, read_buf, sizeof(read_buf));
    if (ret != 10) {
        ms_printf("[error]: read failed! errno = %d\n", errno);
        ms_io_close(fd);
        return  (-1);
    }

    if (memcmp(write_buf, read_buf, sizeof(read_buf)) != 0) {
        ms_io_close(fd);
        return  (-1);
    }

    ms_io_close(fd);

    return  (0);
}

3.2 已支持的文件系统

MS-RTOS 针对各种储存介质和需求提供了丰富的文件系统支持:

文件系统面向的储存介质特性
devfs设备文件系统
MS-FLASHFSMCU 内部 FLASH掉电安全,用于存放 APP 镜像和启动参数文件,支持 APP XIP
fatfsSD 卡、U 盘FAT 文件系统,PC 交换数据便利,开源免费
littlefsNOR FLASH掉电安全、磨损平衡,开源免费
yaffsNAND FLASH掉电安全、磨损平衡、坏块管理,十分成熟,商用收费
uffsNAND FLASH掉电安全、磨损平衡、坏块管理,内存占用较 yaffs 少,开源免费
edgefsSD 卡、U 盘掉电安全,商用收费

附录(Appendix)

1. Reference

SPI 读写 Flashopen in new window

2. FAQ

(1)简述 FLASH 存储器与 EEPROM 存储器的区别?

  • 首先从 IO 引脚占用方面比较,EEPROM 只需占用两个 IO 引脚,时钟(clk)和数据(data)引脚,外加电源三个引脚即可,符合 I2C 通讯协议。而 FLASH 需要占用更多 IO 引脚,有并行和串行的,串行的需要一个片选(cs)引脚(可用作节电功耗控制),一个时钟(clk)引脚,FLASH 读出和写入引脚各一个,也就是四个。并行的需要八个数据引脚,当然比串行的读写速度要快。

  • 从功能方面比较,EEPROM 可以单字节读写,FLASH 部分芯片只能以块方式擦除(整片擦除),部分芯片可以单字节写入(编程),一般需要采用块写入方式;FLASH 比 EEPROM 读写速度更快,可靠性更高。但比单片机片内 RAM 的读写还要慢。

  • 价格方面比较,FLASH 应该要比 EEPROM 贵。

文档内容是否对您有所帮助?
有帮助
没帮助