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 的相关内容。
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 来测试硬件是否连接正常,或用于识别设备。
在向 FLASH 芯片存储矩阵写入数据前,首先要使能写操作,通过 Write Enable
命令即可写使能。我们只关注这个状态寄存器的第 0 位 BUSY,当这个位为 1 时,表明 FLASH 芯片处于忙碌状态,它可能正在对内部的存储矩阵进行擦除或数据写入的操作。利用指令表中的 Read Status Register
指令可以获取 FLASH 芯片状态寄存器的内容。
通常,对存储矩阵擦除的基本操作单位都是多个字节进行,如本例子中的 FLASH 芯片支持扇区擦除、块擦除以及整片擦除。扇区擦除指令的第一个字节为指令编码,紧接着发送的 3 个字节用于表示要擦除的存储矩阵地址。要注意的是在扇区擦除指令前,还需要先发送写使能指令,发送扇区擦除指令后,通过读取寄存器状态等待扇区擦除操作完毕。
目标扇区被擦除完毕后,就可以向它写入数据了。与 EEPROM 类似,FLASH 芯片也有页写入命令,使用页写入命令最多可以一次向 FLASH 传输 256 个字节的数据,我们把这个单位称为页大小。
相对于写入,FLASH 芯片的数据读取要简单得多,使用读取指令 Read Data
即可。发送了指令编码及要读的起始地址后,FLASH 芯片就会按地址递增的方式返回存储矩阵的内容,读取的数据量没有限制,只要没有停止通讯,FLASH 芯片就会一直返回数据。
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-FLASHFS | MCU 内部 FLASH | 掉电安全,用于存放 APP 镜像和启动参数文件,支持 APP XIP |
fatfs | SD 卡、U 盘 | FAT 文件系统,PC 交换数据便利,开源免费 |
littlefs | NOR FLASH | 掉电安全、磨损平衡,开源免费 |
yaffs | NAND FLASH | 掉电安全、磨损平衡、坏块管理,十分成熟,商用收费 |
uffs | NAND FLASH | 掉电安全、磨损平衡、坏块管理,内存占用较 yaffs 少,开源免费 |
edgefs | SD 卡、U 盘 | 掉电安全,商用收费 |
附录(Appendix)
1. Reference
2. FAQ
(1)简述 FLASH 存储器与 EEPROM 存储器的区别?
首先从 IO 引脚占用方面比较,EEPROM 只需占用两个 IO 引脚,时钟(clk)和数据(data)引脚,外加电源三个引脚即可,符合 I2C 通讯协议。而 FLASH 需要占用更多 IO 引脚,有并行和串行的,串行的需要一个片选(cs)引脚(可用作节电功耗控制),一个时钟(clk)引脚,FLASH 读出和写入引脚各一个,也就是四个。并行的需要八个数据引脚,当然比串行的读写速度要快。
从功能方面比较,EEPROM 可以单字节读写,FLASH 部分芯片只能以块方式擦除(整片擦除),部分芯片可以单字节写入(编程),一般需要采用块写入方式;FLASH 比 EEPROM 读写速度更快,可靠性更高。但比单片机片内 RAM 的读写还要慢。
价格方面比较,FLASH 应该要比 EEPROM 贵。