Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1422191
  • 博文数量: 124
  • 博客积分: 4078
  • 博客等级: 中校
  • 技术积分: 3937
  • 用 户 组: 普通用户
  • 注册时间: 2010-07-21 11:28
个人简介

新博客:http://sparkandshine.net/

文章分类

全部博文(124)

分类: 嵌入式

2012-03-29 11:54:01

摘要:

    本文讲述了Coffee文件系统移植重要一步--宏映射,分析了COFFEE_READ、COFFEE_WRITE和COFFEE_ERASE宏映射的源码,给出一个测试例子。最后给出个人对Coffee的一些看法。


PS:本文测试平台是IAR+STM32F103RBT6。


一、之前的问题

    博文《 Contiki学习笔记:Coffee文件系统移植 》只是根据分析的,并没有实际跑起来。后来几乎把源码分析了一遍,期间发现Coffee有逻辑问题。现在回想起来,觉得很幼稚。

    问题出在我将小组移植的代码当官方源码读了(他在源码并没有注释,后来才知道是他移植的),具体来说,就是将宏COFFEE_ERASE(sector)映射的函数逻辑功能理解错了,COFFEE_ERASE实际功能是将sector全部写入0,而不是擦除,这大概称得上代码可读性极差吧!(我通过研读cpu\stm32w108\cfs-coffee-arch.c源代码证实了这个问题)


二、COFFEE_READ、COFFEE_WRITE和COFFEE_ERASE宏映射

2.1 概述

    移植Coffee一个很重要的工作就是将COFFEE_READ、COFFEE_WRITE和COFFEE_ERASE宏映射到硬件相关的函数。COFFEE_READ对应于FLASH读stm32_flash_read,COFFEE_WRITE对应于文件写stm32_flash_write,COFFEE_ERASE却不是对应于FLASH擦除,为了避免搞混,这里用stm32_coffee_erase而不是stm32_flash_erase。部分源代码如下:

  1. //filename:cfs-coffee-arch.h
  2. #define COFFEE_WRITE(buf, size, offset) stm32_flash_write(COFFEE_START + offset, buf, size)
  3. #define COFFEE_READ(buf, size, offset) stm32_flash_read(COFFEE_START + offset, buf, size)
  4. #define COFFEE_ERASE(sector) stm32_coffee_erase(sector)

2.2 stm32_coffee_erase

    COFFEE_ERASE宏用于将sector的内容全部写为0,而Coffee文件系统格式化cfs_coffee_format函数就是循环调用COFFEE_ERASE将Coffee所有的sector(总共有COFFEE_SECTOR_COUNT)内容写为0,详情可参见博文《Contiki学习笔记:Coffee文件系统格式化cfs_coffee_format》。那么,stm32_coffee_erase实现的功能就是将sector写入0,源代码如下:

  1. void stm32_coffee_erase(u8_t sector)
  2. {
  3.   u32_t data = 0;
  4.   u32_t addr = COFFEE_START + (sector) *COFFEE_SECTOR_SIZE;
  5.   u32_t end = addr + COFFEE_SECTOR_SIZE;

  6.   if(!(addr >= COFFEE_START && end <= COFFEE_START + COFFEE_SIZE)) //确保地址在Coffee管理的区间
  7.   {
  8.     return ;
  9.   }

  10.   FLASH_Unlock(); /* Unlock the Flash Program Erase controller */
  11.   for(; addr < end; addr += 4)
  12.   {
  13.     if(FLASH_ProgramWord(addr, data) != FLASH_COMPLETE)
  14.     {
  15.       PRINTF("FLASH_ProgramHalfWord Error.\n");
  16.     }
  17.   }
  18.   FLASH_Lock();
  19. }

2.3 stm32_flash_read

    读取Flash的内容比较简单,因为是片上FLASH,与内存统一编址,读取FLASH可以像读取普通内存一样,这里调用IAR库函数memcpy实现,源代码如下:

  1. void stm32_flash_read(u32_t address, void *data, u32_t length)
  2. {
  3.   u8_t *pdata = (u8_t*)address;
  4.   ENERGEST_ON(ENERGEST_TYPE_FLASH_READ);
  5.   memcpy(data, pdata, length);
  6.   ENERGEST_OFF(ENERGEST_TYPE_FLASH_READ);
  7. }

    其中,宏ENERGEST_ON和ENERGEST_OFF是Contiki系统函数(在core/sys/energest.h定义),用于统计能量消耗,这里暂且忽略之。

2.4 stm32_flash_write

    往FLASH写入内容就比较复杂了,首先得确保存储单元在写入前是0xFFFF(因为FLASH写入只能从1变成0的物理特性),否则会报PGERR错误(即Programming error,由硬件自动设置)。具体写入步骤是这样的:读取内容-->修改内容-->擦除FLASH-->写入内容。源代码如下:

  1. // Allocates a buffer of FLASH_PAGE_SIZE bytes statically (rather than on the stack).
  2. #ifndef STATIC_FLASH_BUFFER
  3.   #define STATIC_FLASH_BUFFER 1
  4. #endif


  5. void stm32_flash_write(u32_t address, const void *data, u32_t length)
  6. {
  7.   const u32_t end = address + length;
  8.   u32_t i;
  9.   u32_t next_page, curr_page;
  10.   u16_t offset;

  11.   #if STATIC_FLASH_BUFFER
  12.     static u8_t buf[FLASH_PAGE_SIZE];
  13.   #else
  14.     u8_t buf[FLASH_PAGE_SIZE];
  15.   #endif

  16.   for(i = address; i < end;)
  17.   {
  18.     next_page = (i | (FLASH_PAGE_SIZE - 1)) + 1;
  19.     curr_page = i &~(FLASH_PAGE_SIZE - 1);
  20.     offset = i - curr_page;
  21.     if(next_page > end)
  22.     {
  23.       next_page = end;
  24.     }
  25.     //1.Read a page from flash and put it into a mirror buffer.
  26.     stm32_flash_read(curr_page, buf, FLASH_PAGE_SIZE);
  27.     //2.Update flash mirror data with new data.
  28.     memcpy(buf + offset, data, next_page - i);
  29.     /**********3. Erase flash page. ***************/
  30.     ENERGEST_ON(ENERGEST_TYPE_FLASH_WRITE);
  31.     FLASH_Unlock();
  32.     FLASH_ErasePage(i);

  33.     /*****4. Write modified data form mirror buffer into the flash.********/
  34.     unsigned int j = 0;
  35.     while(j < FLASH_PAGE_SIZE)
  36.     {
  37.       FLASH_ProgramWord(curr_page + j, *(u32_t*) &buf[j]);
  38.       j += 4;
  39.     }
  40.     FLASH_Lock();
  41.     ENERGEST_OFF(ENERGEST_TYPE_FLASH_WRITE);

  42.     data = (u8_t*)data + next_page - i;
  43.     i = next_page;
  44.   }
  45. }

2.5 stm32_flash_erase

    Coffee进行读写前,需先进行文件系统格式化,而格式化主要是将Coffee管辖的FLASH区域全部写入0,而FLASH写入的前提是编程单元是0xFFFF,所以还需要一个函数将Coffee管辖的FLASH区域全部擦除,即由0变1。源代码如下:

  1. //erase flash between COFFEE_START to COFFEE_START+COFFEE_SIZE
  2. void stm32_flash_erase(void)
  3. {
  4.   u32_t curr_page_addr = COFFEE_START &~(FLASH_PAGE_SIZE - 1);
  5.   u32_t last_page_addr = (COFFEE_START + COFFEE_SIZE - FLASH_PAGE_SIZE) &~(FLASH_PAGE_SIZE - 1); //用于Coffee最后一页的起始地址
  6.   u32_t i;

  7.   FLASH_Unlock(); /* Unlock the Flash Program Erase controller */
  8.   for(i = curr_page_addr; i <= last_page_addr;)
  9.   {
  10.     FLASH_ErasePage(i); //FLASH_Status FLASH_ErasePage(uint32_t Page_Address)
  11.     i += FLASH_PAGE_SIZE;
  12.   }
  13.   FLASH_Lock();
  14. }


三、测试

3.1 测试用例

    文件Contiki\cpu\stm32w108\cfs-coffee-arch.c有个cfs_coffee_test函数,对Coffee进行多方面测试。我也是拿这个来测试Coffee,这里只举简单的例子,将字符串写入,再读出,通过串口打印观其是否相同。测试源码如下:

  1. #include "contiki.h"
  2. #include "debug-uart.h"
  3. #include "cfs/cfs.h"
  4. #include "cfs-coffee-arch.h"

  5. PROCESS(cfs_test_process, "cfs test");
  6. AUTOSTART_PROCESSES(&cfs_test_process);

  7. PROCESS_THREAD(cfs_test_process, ev, data)
  8. {
  9.   PROCESS_BEGIN();
  10.   usart_puts("***cfs test process start***\n");

  11.   if(cfs_coffee_format() == - 1)
  12.   {
  13.     usart_puts("coffee format error.");
  14.     return - 1;
  15.   }

  16.   int fd = cfs_open("CoffeeTest", CFS_WRITE | CFS_READ);
  17.   if(fd == - 1)
  18.   {
  19.     usart_puts("First time open error.");
  20.     return - 1;
  21.   }

  22.   char buf1[] = "Hello, World!";
  23.   char buf2[32] = "Orignal!";

  24.   usart_puts("The orignal buf1 and buf2 is : ");
  25.   usart_puts(buf1);
  26.   usart_puts(buf2);

  27.   int size_write = cfs_write(fd, buf1, sizeof(buf1));
  28.   cfs_seek(fd, 0, CFS_SEEK_SET); //NOTE
  29.   int size_read = cfs_read(fd, buf2, sizeof(buf1));

  30.   usart_puts("The update buf1 and buf2 is : ");
  31.   usart_puts(buf1);
  32.   usart_puts(buf2);

  33.   cfs_close(fd);

  34.   PROCESS_END();
  35. }

值得注意的是,需要在main函数加入如下代码:

  1. stm32_flash_erase();//erase flash between COFFEE_START to COFFEE_START+COFFEE_SIZE
  2. FLASH_ClearFlag(FLASH_FLAG_BSY | FLASH_FLAG_EOP | FLASH_FLAG_PGERR | FLASH_FLAG_WRPRTERR); /* Clear All pending flags */

3.2 运行结果

图1 Coffee测试运行结果

四、一个新的BUG

    细心的你会发现,这其中隐含着一个很大的BUG。试想这样情况,当空间不足调用垃圾回收时,垃圾回收调用COFFEE_ERASE(sector),而COFFEE_ERASE映射到stm32_coffee_erase,而stm32_coffee_erase往sector区全部写入0,问题就出在这里,此时sector区存储单元未必是0xFFFF(因为sector已经被用过,只是标识无效,没擦除过),在写入的时候就会产生PGERR。

    需要重新设计stm32_coffee_erase函数,在写入之前先擦除再写入0(类似于stm32_flash_read,但没那么繁琐,无须读出、修改)。如此,系统启动也就无须调用stm32_flash_erase函数,这个操作现在已经包含在stm32_coffee_erase函数。需要考虑COFFEE_SECTOR_SIZE与FLASH_PAGE_SIZE关系,这里简化处理,假设两者相等。重新设计的stm32_coffee_erase函数源码如下:

  1. void stm32_coffee_erase(u8_t sector)
  2. {
  3.   u32_t data = 0;
  4.   u32_t addr = COFFEE_START + (sector) *COFFEE_SECTOR_SIZE;
  5.   u32_t end = addr + COFFEE_SECTOR_SIZE;

  6.   if(!(addr >= COFFEE_START && end <= COFFEE_START + COFFEE_SIZE)) //确保地址在Coffee管理的区间
  7.   {
  8.     return ;
  9.   }

  10.   FLASH_Unlock();
  11.   FLASH_ErasePage(addr); //先擦除 NOTE:assume COFFEE_SECTOR_SIZE=FLASH_PAGE_SIZE
  12.   for(; addr < end; addr += 4)
  13.   {
  14.     if(FLASH_ProgramWord(addr, data) != FLASH_COMPLETE)
  15.     {
  16.       PRINTF("FLASH_ProgramHalfWord Error.\n");
  17.     }
  18.   }
  19.   FLASH_Lock();
  20. }

    通过这样改进,还可以带来额外的好处,启动系统无须先全部擦除FLASH,如果也没有进行文件系统格式化,那么,之前创建的文件还是存在的。


五、我的一些看法

    随着分析的深入,我总觉得Contiki文件系统Coffee并没有想像的那么好。就拿这个例子来说吧,系统首次运行需要将FLASH擦除(擦除一次),再进行文件系统格式化(实质是FLASH写入,事实上也包括擦除,也许这一点可以通过设计算法有效避免),此时的FLASH全部为0了(除了文件头file_header),下一次写入,肯定是要擦除的。也就是说,Coffee假定FLASH的初值为0,而FLASH物理特性只能从1变0,所以每次写入都得先擦除后写入。试想,Coffee假定FLASH的初值为1,后续写入就可直接写入了,而无须先擦后写,大大减少擦除次数。但此时的文件头file_header都为1,需要大量修改整个Coffee,包括file_header的flags各位宏定义、宏判断等等。

阅读(7005) | 评论(8) | 转发(4) |
给主人留下些什么吧!~~

Jelline2012-07-11 11:03:13

a_jige: 这里有一部分
contiki-2.x/platform/sky/cfs-coffee-arch.h
具体的函数在
contiki-2.x/platform/sky/dev/xmem.c.....
不好意思,现在才回复你。
你是指xmem.c的xmem_pread函数如下代码段中语句*p = ~u吗?
SPI_FLUSH();
for(; p < end; p++)
{
  unsigned char u;
  SPI_READ(u);
  *p = ~u;
}
这个我没接触过,不清楚,建议你继续追溯下去,阅读SPI_READ源码。

a_jige2012-07-09 21:04:10

这里有一部分
contiki-2.x/platform/sky/cfs-coffee-arch.h
具体的函数在
contiki-2.x/platform/sky/dev/xmem.c

Jelline2012-07-09 10:30:20

a_jige: 好吧。。。
很多platform的代码里面 读写 flash时候,都有一个 取反的操作,你可以看一下。.....
你可以给我一个文件路径吗,我去读下源码

a_jige2012-07-09 10:12:52

Jelline: 我个人认为,你理解复杂化了。
正如你知道的那样,由于FLASH物理特性,FLASH写入只能由1变0。
那么写入数据之前,得确保FLASH是1,这个过程就是咱们平常说的擦除.....
好吧。。。
很多platform的代码里面 读写 flash时候,都有一个 取反的操作,你可以看一下。

Jelline2012-07-08 17:36:36

a_jige: 这段时间都在看这个文件系统。觉得挺复杂的。 看懂一小部分。
我觉得是这样的:flash写入的时候,写入的是 原来的数 取反之后的数。也即是说,0 写入 就是 FF,.....
我个人认为,你理解复杂化了。
正如你知道的那样,由于FLASH物理特性,FLASH写入只能由1变0。
那么写入数据之前,得确保FLASH是1,这个过程就是咱们平常说的擦除,即由0变1。
至于Coffee写入,你阅读源码会发现,事实上,先擦除后写入,而这个擦除与COFFEE_ERASE含义明显不同。
我也很费解,为何如此设计,估计是因为通用性的考虑(有点说不通)。