版权声明:本版面文章皆为原创、或参考其他技术网站、博客后自己动手做实验所得,转载请注明出处。
鸣谢:感谢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文件中,需要根据自己的硬件环境,实现如下五个函数:
1 2 3 4 5 6 7 | const NAND_DRV nand0_drv = { Init, UnInit, PageRead, PageWrite, BlockErase, }; |
为了实现这五个函数,首先新建drv_nand.c文件,实现W29N02GVSIAA驱动,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 | 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输入输出,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 | /* * 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脚本,稍作修改:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 | 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进行操作了。。。。。。。。结束!