分享下之前移植ThreadX+FileX+LevelX移植到stm32f407+nandflash的经验 板子的话,用的是我之前自己布板焊接的。 我这里使用的是标准库移植的。 先放上ThreadX官方的文档介绍,都是中文的很好懂。 链接: Azure RTOS ThreadX 文档. 通过上面那个链接可以进入到ThreadX全家桶的所有文档里
移植前的准备工作
首先要先移植ThreadX,移植完后,移植levelX,通过LevelX对接到FileX里。
ThreadX移植
这里移植ThreadX参考硬汉哥的移植流程,下面放个链接 链接: ThreadX移植教程.
LevelX移植
LevelX实际上是一个nftl层,是我们的存储介质到文件系统中的中间层,用来实现nandflash的磨损均衡,掉电保护,坏块管理的功能。
源码下载
首先,我们要到这个链接下: azure-rtos/levelx.下载到LevelX的源码。解压到我们要移植的工程里面。 由于分享的是以前的移植过程,使用的是以前的版本,实际上官网上已经更新到了6.18。 进入到filex的文件目录,比较重要的是common和samples两个文件夹,cmomon里有src和inc两个文件夹,其中src里是.c文件,inc里是.h文件。 samples是微软提供的一些例程文件。
源码添加到工程
这里由于我们使用的nandflash,所以只需要添加nandflash的API到我们的工程里。如下图所示
补充接口文件
参考LevelX官方说明文档,得知我们需要补充结构体LX_NAND_FLASH的以下几个函数和参数 我们可以新建一个文件,在这个文件里实现以下部分的功能 其中 这三个是参数 lx_nand_flash_total_blocks是nandflash的总块数 lx_nand_flash_pages_per_block是每块包含的页数 lx_nand_flash_bytes_per_page是每页有多少字节 以下10个是函数指针, lx_nand_flash_driver_read是扇区读函数 lx_nand_flash_driver_write是扇区写函数 lx_nand_flash_driver_block_erase是块擦除函数 lx_nand_flash_driver_block_erased_verify是块擦除验证函数 lx_nand_flash_driver_page_erased_verify是页擦除验证函数 lx_nand_flash_driver_block_status_get块状态获取函数 lx_nand_flash_driver_block_status_set块状态设置函数 lx_nand_flash_driver_extra_bytes_get额外字节获取函数 lx_nand_flash_driver_extra_bytes_set额外字节设置函数 lx_nand_flash_driver_system_error出错时会调用的函数,相当于错误通知 下面这个是存放LevelX扇区缓冲区的指针 lx_nand_flash_page_buffer 首先,根据我用的Nandflash来补充以下参数
#define NAND_PAGE_SIZE ((uint16_t)0x0800)
#define TOTAL_BLOCKS 4096
#define PHYSICAL_PAGES_PER_BLOCK 64
#define BYTES_PER_PHYSICAL_PAGE 2048
#define WORDS_PER_PHYSICAL_PAGE 2048/4
#define SPARE_BYTES_PER_PAGE 64
#define BAD_BLOCK_POSITION 0
#define EXTRA_BYTE_POSITION 2
#define ECC_BYTE_POSITION 40
补充扇区读函数
它的函数原型是:
UINT (*lx_nand_flash_driver_read)(ULONG block, ULONG page, ULONG *destination, ULONG words);
这个函数负责读取nandflash中的特定界面,里面要实现ECC错误检查和错误校正。其中输入参数 block:要读第几块 page:要读第几页 destination:读出的数据的存放地址 words:读的个数(这里是以word为单位的,也就是读words*4个字节) 下面放一下我的具体实现
UINT nand_driver_read_page(ULONG block, ULONG page, ULONG *destination, ULONG words)
{
u32 PageNum=0,i=0,n=0;
u8 nand_err=0;
u8 ecc_num=0;
UINT lx_err=0;
PageNum=(block<<6)|(page);
for(i=0;i<TIME_OUT;i++)
{
nand_err=NAND_ReadPage(PageNum,0,(u8*)destination,words*4);
if(nand_err==0)
{
break;
}
printf("read page error,%d\r\n",nand_err);
tx_thread_sleep(50);
}
if(nand_err!=0)
{
return (LX_ERROR);
}
if(page==0)
{
for(i=0;i<words;i++)
{
if(destination[i]==0xFFFFFFFF)
{
n++;
}else
{
break;
}
}
}
if(n==words||page==0)
{
return LX_SUCCESS;
}
ecc_num=(words*4)/256;
memset(&nand_dev.ecc_buf[0],0xFF,3*ecc_num);
nand_err=NAND_ReadSpare(PageNum,ECC_BYTE_POSITION,&nand_dev.ecc_buf[0],3*ecc_num);
if(nand_err!=0)
{
printf("read ecc error,%d\r\n",nand_err);
return (LX_ERROR);
}
for(i=0;i<ecc_num;i++)
{
lx_err=_lx_nand_flash_256byte_ecc_check((u8*)destination,&nand_dev.ecc_buf[i*3]);
if(lx_err!=0&&lx_err!=6)
{
printf("ECC验证出错,%d\r\n",lx_err);
return (LX_ERROR);
}
}
return LX_SUCCESS;
}
上边TIME_OUT是我自己定义的一个超时设定,主要是为了防止读数据时偶发性的超时,做一个二次读确认。 说下ECC的具体实现思路,这里直接调用了LevelX自带的ECC校验函数,以256字节为单位,每256字节生成3个ECC校验值。每次写数据时,计算写入数据的ECC值,并将他存储到每页中spare的ECC_BYTE_POSITION的偏移地址处,2048字节的话就是要存储24字节的ECC值。这里可以看出LevelX预留的是刚好够的64-40刚好等于24字节。
而读数据时,先将数据读出来,再将我们先前存入的ECC值读出来,对读出的数据进行ECC校验,并将结果与读出的ECC值做对比,如果不一致的话,尝试修复一下数据,LevelX提供的ECC函数每256字节是可以修正1b的错误的。 以上就是ECC的大体实现思路。 再说下,移植过程中发现的小问题,算是LevelX的一个小Bug吧,影响不是很大。在LevelX的API函数lx_nand_flash_open.c的286和296行会对nandflash每个块的第0页在不擦除的情况下进行两次读写。由于每次写入数据均大于256字节,所以要对这两次都进行ECC校验,但是Nandflash的特性是每次写入之前都要先擦除,也就是Nandflash的写入操作只能从1->0。这样就导致我们第二次校验的ECC值大概率是错误的。我当时为了这个问题临时是使用了把第0页的ECC校验屏蔽了。 这个问题之前移植时已经向官方反馈了,前几天他们工作人员发邮件给我,说最近会解决这个问题。期待一下。
补充扇区写函数
函数原型:
UINT (*lx_nand_flash_driver_write)(ULONG block, ULONG page, ULONG *source, ULONG words);
参数与读函数一致。这个函数负责将数据写入Nandflash里。同样也需要实现ECC的相关功能 直接放具体的实现把:
UINT nand_driver_write_page(ULONG block, ULONG page, ULONG *source, ULONG words)
{
u32 PageNum=0;
u8 nand_err=0;
u8 ecc_num=0,i=0;
PageNum=(block<<6)|(page);
for(i=0;i<TIME_OUT;i++)
{
nand_err=NAND_WritePage(PageNum,0,(u8*)source,words*4);
if(nand_err==0)
{
break;
}
printf("wirte page error,%d\r\n",nand_err);
tx_thread_sleep(50);
}
if(nand_err!=0)
{
return (LX_ERROR);
}
ecc_num=(words*4)/256;
memset(&nand_dev.ecc_buf[0],0xFF,3*ecc_num);
for(i=0;i<ecc_num;i++)
{
_lx_nand_flash_256byte_ecc_compute((u8*)source,&nand_dev.ecc_buf[i*3]);
}
nand_err=NAND_WriteSpare(PageNum,ECC_BYTE_POSITION,&nand_dev.ecc_buf[0],3*ecc_num);
if(nand_err!=0)
{
printf("wirte ecc error,%d\r\n",nand_err);
return (LX_ERROR);
}
return LX_SUCCESS;
}
补充块擦除函数
函数原型:
UINT (*lx_nand_flash_driver_block_erase)(ULONG block, ULONG erase_count);
erase_count是擦除的数量,这里我直接调用了之前已经整理好的块擦除函数。
UINT nand_driver_block_erase(ULONG block, ULONG erase_count)
{
u8 nand_err=0;
LX_PARAMETER_NOT_USED(erase_count);
printf("erase block:%d,num=%d\r\n",block,erase_count);
nand_err=NAND_EraseBlock(block);
if(nand_err!=0)
{
printf("block_erase error,%d\r\n",nand_err);
return (LX_ERROR);
}
return(LX_SUCCESS);
}
补充块擦除验证函数
函数原型:
UINT (*lx_nand_flash_driver_block_erased_verify)(ULONG block);
这个函数是每次擦除块之后调用的,因为Nandflash擦除是0->1,理论上一个块擦除后数据应该全是0xFF,但是Nandflash使用的过程中可能会出现坏块,就导致擦除完之后不是0xFF,LevelX通过这个函数来确定坏块的产生。这个函数要对比块里的数据是不是全为0xFF 包括Page的数据区和备用区。 具体实现:
UINT nand_driver_block_erased_verify(ULONG block)
{
u16 i=0,j=0;
u32 PageNum=0;
for(i=0;i<PHYSICAL_PAGES_PER_BLOCK;i++)
{
PageNum=0;
PageNum=(block<<6)|(i);
memset(verifybuf,0,sizeof(verifybuf));
NAND_ReadPage(PageNum,0,(u8*)verifybuf,NAND_PAGE_SIZE);
for(j=0;j<NAND_PAGE_SIZE/4;j++)
{
if(verifybuf[j]!=0xFFFFFFFF)
{
printf("block[%d]_erased_verify error\r\n",block);
return(LX_ERROR);
}
}
NAND_ReadSpare(PageNum,0,SpareArea,64);
for(j=0;j<64;j++)
{
if(SpareArea[j]!=0xFF)
{
printf("block[%d]_erased_verify error\r\n",block);
return(LX_ERROR);
}
}
}
return(LX_SUCCESS);
}
补充页擦除验证函数
函数原型:
UINT (*lx_nand_flash_driver_page_erased_verify)(ULONG block, ULONG page);
与上个函数类似,这个函数是对页进行校验。 具体实现:
UINT nand_driver_page_erased_verify(ULONG block, ULONG page)
{
u16 i=0;
u32 PageNum=0;
PageNum=(block<<6)|(page);
memset(verifybuf,0,sizeof(verifybuf));
NAND_ReadPage(PageNum,0,(u8*)verifybuf,NAND_PAGE_SIZE);
for(i=0;i<NAND_PAGE_SIZE/4;i++)
{
if(verifybuf[i]!=0xFFFFFFFF)
{
printf("page_erased_verify error\r\n");
return(LX_ERROR);
}
}
NAND_ReadSpare(PageNum,0,SpareArea,64);
for(i=0;i<64;i++)
{
if(SpareArea[i]!=0xFF)
{
printf("page_erased_verify error\r\n");
return(LX_ERROR);
}
}
return(LX_SUCCESS);
}
补充块状态获取函数
函数原型:
UINT (*lx_nand_flash_driver_block_status_get)(ULONG block, UCHAR *bad_block_flag);
LevelX每个块的状态被放在第0page的spare区中,位置,LevelX已经给我们设置好了,是在BAD_BLOCK_POSITION处。我们要实现的就是从第Block的0page的Spare的BAD_BLOCK_POSITION处读出1字节的数据到bad_block_flag处就行了。 具体实现:
UINT nand_driver_block_status_get(ULONG block, UCHAR *bad_block_byte)
{
u32 PageNum=0,i=0;
u8 nand_err=0;
PageNum=(block<<6);
for(i=0;i<TIME_OUT;i++)
{
nand_err=NAND_ReadSpare(PageNum,BAD_BLOCK_POSITION,bad_block_byte,1);
if(nand_err==0)
{
break;
}
printf("status_get error!,%d\r\n",nand_err);
tx_thread_sleep(50);
}
if(nand_err!=0)
{
return(LX_ERROR);
}
return(LX_SUCCESS);
}
补充块状态设置函数
函数原型:
UINT (*lx_nand_flash_driver_block_status_set)(ULONG block, UCHAR bad_block_flag);
与上函数类似,这个函数用来标记nandflash的块状态。 具体实现:
UINT nand_driver_block_status_set(ULONG block, UCHAR bad_block_byte)
{
u32 PageNum=0,i=0;
u8 nand_err=0;
PageNum=(block<<6);
printf("block:%d,status_set:%d\r\n",block,bad_block_byte);
for(i=0;i<TIME_OUT;i++)
{
nand_err=NAND_WriteSpare(PageNum,BAD_BLOCK_POSITION,&bad_block_byte,1);
if(nand_err==0)
{
break;
}
printf("status_set write error!,%d\r\n",nand_err);
tx_thread_sleep(50);
}
if(nand_err!=0)
{
return(LX_ERROR);
}
return(LX_SUCCESS);
}
补充块额外字节获取与设置函数
函数原型:
UINT (*lx_nand_flash_driver_extra_bytes_get)(ULONG block, ULONG page, UCHAR *destination, UINT size);
根据LevelX的官方文档解释,可知,这个额外字节对于64字节的spare,是有4字节大小的。这4个字节是LevelX定义的一个扇区映射存放地点,下面是这个32位字大小每一位的含义 具体代码实现:
UINT nand_driver_block_extra_bytes_get(ULONG block, ULONG page, UCHAR *destination, UINT size)
{
u32 PageNum=0,i=0;
u8 nand_err=0;
PageNum=(block<<6)|page;
for(i=0;i<TIME_OUT;i++)
{
nand_err=NAND_ReadSpare(PageNum,EXTRA_BYTE_POSITION,destination,size);
if(nand_err==0)
{
break;
}
printf("extra_get error!,%d,%d\r\n",nand_err,size);
tx_thread_sleep(50);
}
if(nand_err!=0)
{
return(LX_ERROR);
}
return(LX_SUCCESS);
}
UINT nand_driver_block_extra_bytes_set(ULONG block, ULONG page, UCHAR *source, UINT size)
{
u32 PageNum=0,i=0;
u8 nand_err=0;
PageNum=(block<<6)|page;
for(i=0;i<TIME_OUT;i++)
{
nand_err=NAND_WriteSpare(PageNum,EXTRA_BYTE_POSITION,source,size);
if(nand_err==0)
{
break;
}
printf("extra_set write error!,%d\r\n",nand_err);
tx_thread_sleep(50);
}
if(nand_err!=0)
{
return(LX_ERROR);
}
return(LX_SUCCESS);
}
补充系统错误处理函数
原型:
UINT (*lx_nand_flash_driver_system_error)(UINT error_code, ULONG block, ULONG page);
这个函数在LevelX遇到错误时自动调用,相当于一个错误通知函数,函数参数error_code是错误代码,另外两个分别是出错的地方所在的块和页。 我这里写的就比较简单啦,就是单纯的打印一下在哪出的错,以及错误代码。
UINT nand_driver_system_error(UINT error_code, ULONG block, ULONG page)
{
LX_PARAMETER_NOT_USED(error_code);
LX_PARAMETER_NOT_USED(block);
LX_PARAMETER_NOT_USED(page);
printf("err:%d,block:%d,page:%d\r\n",error_code,block,page);
return(LX_ERROR);
}
在LevelX中操作函数
以上已经补充完了LevelX需要的所有底层API函数,接下来我们需要将这些函数注册到LevelX里面,以便LevelX能够调用到我们写的函数。相当于一个初始化函数,要在LevelX运行之前执行。
UINT nand_flash_initialize(LX_NAND_FLASH *nand_flash)
{
memset((char*)nand_flash_simulator_buffer,0xFF,BYTES_PER_PHYSICAL_PAGE);
memset((char*)verifybuf,0xFF,BYTES_PER_PHYSICAL_PAGE);
nand_flash -> lx_nand_flash_total_blocks = TOTAL_BLOCKS;
nand_flash -> lx_nand_flash_pages_per_block = PHYSICAL_PAGES_PER_BLOCK;
nand_flash -> lx_nand_flash_bytes_per_page = BYTES_PER_PHYSICAL_PAGE;
nand_flash -> lx_nand_flash_driver_read = nand_driver_read_page;
nand_flash -> lx_nand_flash_driver_write = nand_driver_write_page;
nand_flash -> lx_nand_flash_driver_block_erase = nand_driver_block_erase;
nand_flash -> lx_nand_flash_driver_block_erased_verify = nand_driver_block_erased_verify;
nand_flash -> lx_nand_flash_driver_page_erased_verify = nand_driver_page_erased_verify;
nand_flash -> lx_nand_flash_driver_block_status_get = nand_driver_block_status_get;
nand_flash -> lx_nand_flash_driver_block_status_set = nand_driver_block_status_set;
nand_flash -> lx_nand_flash_driver_extra_bytes_get = nand_driver_block_extra_bytes_get;
nand_flash -> lx_nand_flash_driver_extra_bytes_set = nand_driver_block_extra_bytes_set;
nand_flash -> lx_nand_flash_driver_system_error = nand_driver_system_error;
nand_flash -> lx_nand_flash_page_buffer = &nand_flash_simulator_buffer[0];
return(LX_SUCCESS);
}
上述函数的参数的nand_flash可以看作一个对象(其实看他们开源的代码,其实很多C源码都是以面向对象的思想写的,参考这种思想,后来也对我自己的项目以面向对象的思想进行了重构,多读读源码还是挺有意义的),其中有各种参数和方法可以调用。我们就是对这个对象中的某些方法进行补充。最后调用这个对象来实现我们需要的操作。
先写这麽多吧,后面FileX和LevelX的对接,等以后有时间了写了再写吧。
|