版权声明:本版面文章皆为原创、或参考其他技术网站、博客后自己动手做实验所得,转载请注明出处。
鸣谢:感谢eric硬汉
商务合作:[email protected]
易开嵌入式工作室
基于单片机系统的nandflash文件系统,一直是比较头疼的问题。由于nandflash存在硬件坏块,所以ecc动态均衡校验就显得十分重要。目前开源的ftl算法,都或多或少存在一些问题(没问题的也没开源,反正我没找到)。如此一来,keil的rl-flashfs就显得十足珍贵。
先来看看rl-flashfs的特点:
1. RL-FlashFS本身支持擦写均衡,坏块管理,ECC和掉电保护。
2. RL-FlashFS是FAT兼容的文件系统。
3. RL-FlashFS的文件名仅支持ASCII,不支持中文,这点要特别注意。
4. 首次格式化后使用,读速度2.3MB/S左右,写速度3.2MB/S左右,配置不同的文件系统缓冲大小,速度有区别。
5. RL-FlashFS的函数是标准的C库函数,跟电脑端的文件系统使用方法一样。
6. RL-FlashFS与FatFS的区别,FatFS仅是一个FAT类的文件件系统,擦写均衡,坏块管理,ECC和掉电保护都不支持。
这些都需要用户自己去实现。
7. UFFS,YAFFS这两款文件系统是不兼容FAT的,也就是无法在Windows端模拟U盘。
但是比较讨厌的是,在MDK4.74之后,rl-flashfs与keil自家的操作系统做了比较深的耦合。虽然eric硬汉哥提供了freertos的教程,但是有没有可能把rl-flashfs与目前国内比较火的操作系统rt-thread做兼容呢?虽然rt-thread自带的uffs文件系统也能用,但是占用ram太高,且不兼容FAT。
说干就干,经过两天的移植和测试,初步完成rl-flashfs在rt-thread操作系统下的移植。
硬件平台:stm32f407zgtx
NANDFLASH:W29N02GVSIAA(FSMC)
(有需要开发板合作的可以私信邮箱)
从官网下载rl-arm的源码包,如下:
rt-thread有非常棒的scons构建工具,根据该构建语法,编写对应的配置文件Kconfig和SConscript。编写完成后,利用rt-thread提供的Env工具,输入menuconfig,按照下图进行选择。注意,由于时间关系,并未和rtt原生的虚拟文件系统做兼容适配,故选择关闭Device virtual file system。
选择完毕后,在Env工具中 输入scons --target=mdk5,可自动将我配置好的源码目录加入到MDK中(本人使用的是mdk5.23)。工程如下:
工程中可以看到三个重要文件,寄File_Config.c、FS_NAND_FlashPrg.c和FSN_CM3.lib,注意FSN_CM3.lib只能用于keil下,对于使用IAR的同学,只能说声拜拜。
File_Config配置如下:
在FS_NAND_FlashPrg.c文件中,需要根据自己的硬件环境,实现如下五个函数:
const NAND_DRV nand0_drv = {
Init,
UnInit,
PageRead,
PageWrite,
BlockErase,
};
为了实现这五个函数,首先新建drv_nand.c文件,实现W29N02GVSIAA驱动,代码如下:
static rt_uint8_t FSMC_NAND_ReadStatus(void)
{
rt_uint8_t ucData;
rt_uint8_t ucStatus = NAND_BUSY;
NAND_CMD_AREA = NAND_CMD_STATUS;
ucData = *(__IO rt_uint8_t *)(Bank_NAND_ADDR);
if((ucData & NAND_ERROR) == NAND_ERROR)
{
ucStatus = NAND_ERROR;
}
else if((ucData & NAND_READY) == NAND_READY)
{
ucStatus = NAND_READY;
}
else
{
ucStatus = NAND_BUSY;
}
return (ucStatus);
}
static rt_uint8_t FSMC_NAND_GetStatus(void)
{
rt_uint32_t ulTimeout = 0x10000;
rt_uint8_t ucStatus = NAND_READY;
ucStatus = FSMC_NAND_ReadStatus();
while ((ucStatus != NAND_READY) &&( ulTimeout != 0x00))
{
ucStatus = FSMC_NAND_ReadStatus();
if(ucStatus == NAND_ERROR)
{
return (ucStatus);
}
ulTimeout--;
}
if(ulTimeout == 0x00)
{
ucStatus = NAND_TIMEOUT_ERROR;
}
return (ucStatus);
}
//读取NAND FLASH的ID
//不同的NAND略有不同,请根据自己所使用的NAND FALSH数据手册来编写函数
//返回值:NAND FLASH的ID值
static rt_uint32_t NAND_ReadID(void)
{
NAND_IDTypeDef nand_id;
HAL_NAND_Read_ID(&NAND_Handler,&nand_id);
NAND_DEBUG("ID[%X,%X,%X,%X]\n",nand_id.Maker_Id,nand_id.Device_Id,nand_id.Third_Id,nand_id.Fourth_Id);
return 0;
}
//复位NAND
//返回值:0,成功;
// 其他,失败
static rt_uint8_t NAND_Reset(void)
{
NAND_CMD_AREA = NAND_RESET; //复位NAND
if(FSMC_NAND_GetStatus()==NAND_READY)
return 0; //复位成功
else
return 1; //复位失败
}
void rt_hw_mtd_nand_deinit(void)
{
HAL_NAND_DeInit(&NAND_Handler);
}
//初始化NAND FLASH
rt_uint8_t rt_hw_mtd_nand_init(void)
{
if(&NAND_Handler != NULL){
rt_hw_mtd_nand_deinit();
}
FMC_NAND_PCC_TimingTypeDef ComSpaceTiming,AttSpaceTiming;
NAND_Handler.Instance = FMC_NAND_DEVICE;
NAND_Handler.Init.NandBank = FSMC_NAND_BANK2; //NAND挂在BANK2上
NAND_Handler.Init.Waitfeature = FSMC_NAND_PCC_WAIT_FEATURE_DISABLE; //关闭等待特性
NAND_Handler.Init.MemoryDataWidth = FSMC_NAND_PCC_MEM_BUS_WIDTH_8; //8位数据宽度
NAND_Handler.Init.EccComputation = FSMC_NAND_ECC_DISABLE; //不使用ECC
NAND_Handler.Init.ECCPageSize = FSMC_NAND_ECC_PAGE_SIZE_2048BYTE; //ECC页大小为2k
NAND_Handler.Init.TCLRSetupTime = 1; //设置TCLR(tCLR=CLE到RE的延时)=(TCLR+TSET+2)*THCLK,THCLK=1/180M=5.5ns
NAND_Handler.Init.TARSetupTime = 1; //设置TAR(tAR=ALE到RE的延时)=(TAR+TSET+2)*THCLK,THCLK=1/180M=5.5n。
ComSpaceTiming.SetupTime = 2; //建立时间
ComSpaceTiming.WaitSetupTime = 5; //等待时间
ComSpaceTiming.HoldSetupTime = 3; //保持时间
ComSpaceTiming.HiZSetupTime = 1; //高阻态时间
AttSpaceTiming.SetupTime = 2; //建立时间
AttSpaceTiming.WaitSetupTime = 5; //等待时间
AttSpaceTiming.HoldSetupTime = 3; //保持时间
AttSpaceTiming.HiZSetupTime = 1; //高阻态时间
HAL_NAND_Init(&NAND_Handler,&ComSpaceTiming,&AttSpaceTiming);
NAND_Reset(); //复位NAND
rt_thread_mdelay(100);
return 0;
}
rt_uint8_t FSMC_NAND_ReadPage(rt_uint8_t *_pBuffer, rt_uint32_t _ulPageNo, rt_uint16_t _usAddrInPage, rt_uint16_t NumByteToRead)
{
rt_uint32_t i;
NAND_CMD_AREA = NAND_AREA_A;
//发送地址
NAND_ADDR_AREA = _usAddrInPage;
NAND_ADDR_AREA = _usAddrInPage >> 8;
NAND_ADDR_AREA = _ulPageNo;
NAND_ADDR_AREA = (_ulPageNo & 0xFF00) >> 8;
NAND_ADDR_AREA = (_ulPageNo & 0xFF0000) >> 16;
NAND_CMD_AREA = NAND_AREA_TRUE1;
/* 必须等待,否则读出数据异常, 此处应该判断超时 */
for (i = 0; i < 20; i++);
while(rt_pin_read(NAND_RB)==0);
/* 读数据到缓冲区pBuffer */
for(i = 0; i < NumByteToRead; i++)
{
_pBuffer[i] = NAND_DATA_AREA;
}
return RT_EOK;
}
rt_uint8_t FSMC_NAND_WritePage(rt_uint8_t *_pBuffer, rt_uint32_t _ulPageNo, rt_uint16_t _usAddrInPage, rt_uint16_t NumByteToRead)
{
rt_uint32_t i;
rt_uint8_t ucStatus;
NAND_CMD_AREA = NAND_WRITE0;
//发送地址
NAND_ADDR_AREA = _usAddrInPage;
NAND_ADDR_AREA = _usAddrInPage >> 8;
NAND_ADDR_AREA = _ulPageNo;
NAND_ADDR_AREA = (_ulPageNo & 0xFF00) >> 8;
NAND_ADDR_AREA = (_ulPageNo & 0xFF0000) >> 16;
for (i = 0; i < 20; i++);
for(i = 0; i < NumByteToRead; i++)
{
NAND_DATA_AREA = _pBuffer[i];
}
NAND_CMD_AREA = NAND_WRITE_TURE1;
for (i = 0; i < 20; i++);
ucStatus = FSMC_NAND_GetStatus();
if(ucStatus == NAND_READY)
{
ucStatus = RTV_NOERR;
}
else if(ucStatus == NAND_ERROR)
{
ucStatus = ERR_NAND_PROG;
}
else if(ucStatus == NAND_TIMEOUT_ERROR)
{
ucStatus = ERR_NAND_HW_TOUT;
}
return (ucStatus);
}
//擦除一个块
//BlockNum:要擦除的BLOCK编号,范围:0-(block_totalnum-1)
//返回值:0,擦除成功
// 其他,擦除失败
rt_uint8_t NAND_EraseBlock(rt_uint32_t _ulBlockNo)
{
rt_uint8_t ucStatus;
NAND_CMD_AREA = NAND_ERASE0;
_ulBlockNo <<= 6;
NAND_ADDR_AREA = _ulBlockNo;
NAND_ADDR_AREA = _ulBlockNo >> 8;
NAND_ADDR_AREA = _ulBlockNo >> 16;
NAND_CMD_AREA = NAND_ERASE1;
ucStatus = FSMC_NAND_GetStatus();
if(ucStatus == NAND_READY)
{
ucStatus = RTV_NOERR;
}
else if(ucStatus == NAND_ERROR)
{
ucStatus = ERR_NAND_PROG;
}
else if(ucStatus == NAND_TIMEOUT_ERROR)
{
ucStatus = ERR_NAND_HW_TOUT;
}
return (ucStatus);
}
//全片擦除NAND FLASH
void NAND_EraseChip(void)
{
rt_uint8_t status;
rt_uint16_t i=0;
for(i=0;i<2048;i++) //循环擦除所有的块
{
status=NAND_EraseBlock(i);
if(status)
NAND_DEBUG("Erase %d block fail!!,ERRORCODE %d\r\n",i,status);//擦除失败
}
}
实现FS_NAND_FlashPrg.c中五个函数后,还需要对rt-thread原生的stubs.c文件进行修改,根据rl-flashfs重新映射IO输入输出,代码如下:
/*
* Copyright (c) 2006-2018, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2012-11-23 Yihui The first version
* 2013-11-24 aozima fixed _sys_read()/_sys_write() issues.
* 2014-08-03 bernard If using msh, use system() implementation
* in msh.
*/
#include <string.h>
#include <rt_sys.h>
#include "rtthread.h"
#include "libc.h"
#ifdef RT_USING_DFS
#include "dfs_posix.h"
#endif
#ifdef RT_USING_RL_FLASHFS
#include <File_Config.h>
struct __FILE { int handle; /* Add whatever you need here */ };
#endif
#ifdef __CLANG_ARM
__asm(".global __use_no_semihosting\n\t");
#else
#pragma import(__use_no_semihosting_swi)
#endif
/* Standard IO device handles. */
#define STDIN 0x8001
#define STDOUT 0x8002
#define STDERR 0x8003
/* Standard IO device name defines. */
const char __stdin_name[] = "STDIN";
const char __stdout_name[] = "STDOUT";
const char __stderr_name[] = "STDERR";
/**
* required by fopen() and freopen().
*
* @param name - file name with path.
* @param openmode - a bitmap hose bits mostly correspond directly to
* the ISO mode specification.
* @return -1 if an error occurs.
*/
FILEHANDLE _sys_open(const char *name, int openmode)
{
#ifdef RT_USING_DFS
int fd;
int mode = O_RDONLY;
#endif
/* Register standard Input Output devices. */
if (strcmp(name, __stdin_name) == 0)
return (STDIN);
if (strcmp(name, __stdout_name) == 0)
return (STDOUT);
if (strcmp(name, __stderr_name) == 0)
return (STDERR);
#ifndef RT_USING_DFS
#ifdef RT_USING_RL_FLASHFS
return (__sys_open (name, openmode));
#else
return -1;
#endif
#else
/* Correct openmode from fopen to open */
if (openmode & OPEN_PLUS)
{
if (openmode & OPEN_W)
{
mode |= (O_RDWR | O_TRUNC | O_CREAT);
}
else if (openmode & OPEN_A)
{
mode |= (O_RDWR | O_APPEND | O_CREAT);
}
else
mode |= O_RDWR;
}
else
{
if (openmode & OPEN_W)
{
mode |= (O_WRONLY | O_TRUNC | O_CREAT);
}
else if (openmode & OPEN_A)
{
mode |= (O_WRONLY | O_APPEND | O_CREAT);
}
}
fd = open(name, mode, 0);
if (fd < 0)
return -1;
else
return fd;
#endif
}
int _sys_close(FILEHANDLE fh)
{
#ifndef RT_USING_DFS
#ifdef RT_USING_RL_FLASHFS
if (fh > 0x8000) {
return (0);
}
return (__sys_close (fh));
#else
return 0;
#endif
#else
if (fh <= STDERR) return 0;
return close(fh);
#endif
}
/*
* Read from a file. Can return:
* - zero if the read was completely successful
* - the number of bytes _not_ read, if the read was partially successful
* - the number of bytes not read, plus the top bit set (0x80000000), if
* the read was partially successful due to end of file
* - -1 if some error other than EOF occurred
*
* It is also legal to signal EOF by returning no data but
* signalling no error (i.e. the top-bit-set mechanism need never
* be used).
*
* So if (for example) the user is trying to read 8 bytes at a time
* from a file in which only 5 remain, this routine can do three
* equally valid things:
*
* - it can return 0x80000003 (3 bytes not read due to EOF)
* - OR it can return 3 (3 bytes not read), and then return
* 0x80000008 (8 bytes not read due to EOF) on the next attempt
* - OR it can return 3 (3 bytes not read), and then return
* 8 (8 bytes not read, meaning 0 read, meaning EOF) on the next
* attempt
*
* `mode' exists for historical reasons and must be ignored.
*/
int _sys_read(FILEHANDLE fh, unsigned char *buf, unsigned len, int mode)
{
#ifdef RT_USING_DFS
int size;
#endif
if (fh == STDIN)
{
#ifdef RT_USING_POSIX
size = libc_stdio_read(buf, len);
return len - size;
#else
/* no stdin */
return -1;
#endif
}
#ifndef RT_USING_RL_FLASHFS
if ((fh == STDOUT) || (fh == STDERR))
return -1;
#endif
#ifndef RT_USING_DFS
#ifdef RT_USING_RL_FLASHFS
if (fh > 0x8000) {
return (-1);
}
return (__sys_read (fh, buf, len));
#else
return 0;
#endif
#else
size = read(fh, buf, len);
if (size >= 0)
return len - size;
else
return -1;
#endif
}
/*
* Write to a file. Returns 0 on success, negative on error, and
* the number of characters _not_ written on partial success.
* `mode' exists for historical reasons and must be ignored.
*/
int _sys_write(FILEHANDLE fh, const unsigned char *buf, unsigned len, int mode)
{
#ifdef RT_USING_DFS
int size;
#endif
if ((fh == STDOUT) || (fh == STDERR))
{
#if !defined(RT_USING_CONSOLE) || !defined(RT_USING_DEVICE)
return 0;
#else
#ifdef RT_USING_POSIX
size = libc_stdio_write(buf, len);
return len - size;
#else
if (rt_console_get_device())
{
rt_device_write(rt_console_get_device(), -1, buf, len);
return 0;
}
return -1;
#endif
#endif
}
#ifndef RT_USING_RL_FLASHFS
if (fh == STDIN) return -1;
#endif
#ifndef RT_USING_DFS
#ifdef RT_USING_RL_FLASHFS
if (fh > 0x8000) {
return (-1);
}
return (__sys_write (fh, buf, len));
#else
return 0;
#endif
#else
size = write(fh, buf, len);
if (size >= 0)
return len - size;
else
return -1;
#endif
}
/*
* Move the file position to a given offset from the file start.
* Returns >=0 on success, <0 on failure.
*/
int _sys_seek(FILEHANDLE fh, long pos)
{
#ifndef RT_USING_RL_FLASHFS
if (fh < STDERR)
return -1;
#endif
#ifndef RT_USING_DFS
#ifdef RT_USING_RL_FLASHFS
if (fh > 0x8000) {
return (-1);
}
return (__sys_seek (fh, pos));
#else
return -1;
#endif
#else
/* position is relative to the start of file fh */
return lseek(fh, pos, 0);
#endif
}
/**
* used by tmpnam() or tmpfile()
*/
int _sys_tmpnam(char *name, int fileno, unsigned maxlength)
{
#ifdef RT_USING_RL_FLASHFS
return 1;
#else
return -1;
#endif
}
char *_sys_command_string(char *cmd, int len)
{
#ifdef RT_USING_RL_FLASHFS
return cmd;
#else
/* no support */
return RT_NULL;
#endif
}
/* This function writes a character to the console. */
void _ttywrch(int ch)
{
#ifdef RT_USING_CONSOLE
char c;
c = (char)ch;
rt_kprintf(&c);
#endif
}
RT_WEAK void _sys_exit(int return_code)
{
/* TODO: perhaps exit the thread which is invoking this function */
while (1);
}
/**
* return current length of file.
*
* @param fh - file handle
* @return file length, or -1 on failed
*/
long _sys_flen(FILEHANDLE fh)
{
struct stat stat;
#ifndef RT_USING_RL_FLASHFS
if (fh < STDERR)
return -1;
#endif
#ifndef RT_USING_DFS
#ifdef RT_USING_RL_FLASHFS
if (fh > 0x8000) {
return (0);
}
return (__sys_flen (fh));
#else
return -1;
#endif
#else
fstat(fh, &stat);
return stat.st_size;
#endif
}
int _sys_istty(FILEHANDLE fh)
{
#ifdef RT_USING_RL_FLASHFS
if (fh > 0x8000) {
return (1);
}
return (0);
#else
if((STDIN <= fh) && (fh <= STDERR))
return 1;
else
return 0;
#endif
}
int _sys_ensure (FILEHANDLE fh) {
if (fh > 0x8000) {
return (-1);
}
return (__sys_ensure (fh));
}
int remove(const char *filename)
{
#ifndef RT_USING_DFS
return -1;
#else
return unlink(filename);
#endif
}
#if defined(RT_USING_FINSH) && defined(FINSH_USING_MSH) && defined(RT_USING_MODULE) && defined(RT_USING_DFS)
/* use system(const char *string) implementation in the msh */
#else
int system(const char *string)
{
RT_ASSERT(0);
for (;;);
}
#endif
#ifdef __MICROLIB
#include <stdio.h>
int fputc(int c, FILE *f)
{
char ch[2] = {0};
ch[0] = c;
rt_kprintf(&ch[0]);
return 1;
}
int fgetc(FILE *f)
{
#ifdef RT_USING_POSIX
char ch;
if (libc_stdio_read(&ch, 1) == 1)
return ch;
#endif
return -1;
}
#endif
到这里,移植工作告一段落。
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------
rt-thread吸引我的,除了强大配置功能之外,还有类似于linux的msh功能。既然是对文件系统移植,那就自定义几个shell命令来玩玩吧。参考linux的shell脚本,稍作修改:
static void glz_nand(int argc, char **argv)
{
/* If the number of arguments less than 2 */
if (argc < 2)
{
help:
rt_kprintf("\n");
rt_kprintf("glz_nand [OPTION] [PARAM ...]\n");
rt_kprintf(" ls 显示指定工作目录下之内容\n");
rt_kprintf(" cat <filename> 显示文件内容\n");
rt_kprintf(" mkdir <docname> 创建文件夹\n");
rt_kprintf(" rm <filename> 删除文件\n");
rt_kprintf(" formatall 磁盘格式化\n");
rt_kprintf(" df 显示磁盘空间\n");
return ;
}
else if (!strcmp(argv[1], "ls"))
{
if(argv[2] != NULL){
ViewRootDir(argv[2]);
}else{
ViewRootDir(NULL);
}
}
else if (!strcmp(argv[1], "cat"))
{
if (argc < 3)
{
rt_kprintf("The input parameters are too few!\n");
goto help;
}
ReadFileData(argv[2]);
}
else if (!strcmp(argv[1], "echo"))
{
if (argc < 4)
{
rt_kprintf("The input parameters are too few!\n");
goto help;
}
if(!strcmp(argv[3], ">")){
EchotextFile(argv[2],argv[4]);
}else{
rt_kprintf("bad parameters\n");
}
}
else if (!strcmp(argv[1], "formatall"))
{
Formatflash();
}
else if (!strcmp(argv[1], "df"))
{
ViewNandCapacity();
}
else if (!strcmp(argv[1], "df"))
{
ViewNandCapacity();
}
else if (!strcmp(argv[1], "mkdir"))
{
if (argc < 2)
{
rt_kprintf("The input parameters are too few!\n");
goto help;
}
CreateNewFile(argv[2]);
}
else if (!strcmp(argv[1], "rm"))
{
if (argc < 2)
{
rt_kprintf("The input parameters are too few!\n");
goto help;
}
DeleteDirFile(argv[2]);
}
else
{
rt_kprintf("Input parameters are not supported!\n");
goto help;
}
}
MSH_CMD_EXPORT(glz_nand, GLZ nand RL-FLASHFS test function);
实现上述所有函数后,编译、下载,我们来看下效果撒。
首先,demo板上电,从串口输出log:
在msd中输入glz_nand,回车,可以看到命令提示:
先试下df,看下磁盘空间:
可以看到 磁盘空间大小为256MB。
下面分别实现其他指令操作:
好了,再也不用担心在单片机下对nandflash进行操作了。。。。。。。。结束!