本帖最后由 lz_kwok 于 2020-4-10 10:44 编辑
版权声明:本版面文章皆为原创、或参考其他技术网站、博客后自己动手做实验所得,转载请注明出处。 鸣谢:感谢eric硬汉哥 商务合作:lz_kwok@163.com 基于单片机系统的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 = 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; } 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进行操作了。。。。。。。。结束!
|