摘要:
本文讲述了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。部分源代码如下:
- //filename:cfs-coffee-arch.h
- #define COFFEE_WRITE(buf, size, offset) stm32_flash_write(COFFEE_START + offset, buf, size)
- #define COFFEE_READ(buf, size, offset) stm32_flash_read(COFFEE_START + offset, buf, size)
- #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,源代码如下:
- void stm32_coffee_erase(u8_t sector)
- {
- u32_t data = 0;
- u32_t addr = COFFEE_START + (sector) *COFFEE_SECTOR_SIZE;
- u32_t end = addr + COFFEE_SECTOR_SIZE;
- if(!(addr >= COFFEE_START && end <= COFFEE_START + COFFEE_SIZE)) //确保地址在Coffee管理的区间
- {
- return ;
- }
- FLASH_Unlock(); /* Unlock the Flash Program Erase controller */
- for(; addr < end; addr += 4)
- {
- if(FLASH_ProgramWord(addr, data) != FLASH_COMPLETE)
- {
- PRINTF("FLASH_ProgramHalfWord Error.\n");
- }
- }
- FLASH_Lock();
- }
2.3 stm32_flash_read
读取Flash的内容比较简单,因为是片上FLASH,与内存统一编址,读取FLASH可以像读取普通内存一样,这里调用IAR库函数memcpy实现,源代码如下:
- void stm32_flash_read(u32_t address, void *data, u32_t length)
- {
- u8_t *pdata = (u8_t*)address;
- ENERGEST_ON(ENERGEST_TYPE_FLASH_READ);
- memcpy(data, pdata, length);
- ENERGEST_OFF(ENERGEST_TYPE_FLASH_READ);
- }
其中,宏ENERGEST_ON和ENERGEST_OFF是Contiki系统函数(在core/sys/energest.h定义),用于统计能量消耗,这里暂且忽略之。
2.4 stm32_flash_write
往FLASH写入内容就比较复杂了,首先得确保存储单元在写入前是0xFFFF(因为FLASH写入只能从1变成0的物理特性),否则会报PGERR错误(即Programming error,由硬件自动设置)。具体写入步骤是这样的:读取内容-->修改内容-->擦除FLASH-->写入内容。源代码如下:
- // Allocates a buffer of FLASH_PAGE_SIZE bytes statically (rather than on the stack).
- #ifndef STATIC_FLASH_BUFFER
- #define STATIC_FLASH_BUFFER 1
- #endif
- void stm32_flash_write(u32_t address, const void *data, u32_t length)
- {
- const u32_t end = address + length;
- u32_t i;
- u32_t next_page, curr_page;
- u16_t offset;
- #if STATIC_FLASH_BUFFER
- static u8_t buf[FLASH_PAGE_SIZE];
- #else
- u8_t buf[FLASH_PAGE_SIZE];
- #endif
- for(i = address; i < end;)
- {
- next_page = (i | (FLASH_PAGE_SIZE - 1)) + 1;
- curr_page = i &~(FLASH_PAGE_SIZE - 1);
- offset = i - curr_page;
- if(next_page > end)
- {
- next_page = end;
- }
- //1.Read a page from flash and put it into a mirror buffer.
- stm32_flash_read(curr_page, buf, FLASH_PAGE_SIZE);
- //2.Update flash mirror data with new data.
- memcpy(buf + offset, data, next_page - i);
- /**********3. Erase flash page. ***************/
- ENERGEST_ON(ENERGEST_TYPE_FLASH_WRITE);
- FLASH_Unlock();
- FLASH_ErasePage(i);
- /*****4. Write modified data form mirror buffer into the flash.********/
- unsigned int j = 0;
- while(j < FLASH_PAGE_SIZE)
- {
- FLASH_ProgramWord(curr_page + j, *(u32_t*) &buf[j]);
- j += 4;
- }
- FLASH_Lock();
- ENERGEST_OFF(ENERGEST_TYPE_FLASH_WRITE);
- data = (u8_t*)data + next_page - i;
- i = next_page;
- }
- }
2.5 stm32_flash_erase
Coffee进行读写前,需先进行文件系统格式化,而格式化主要是将Coffee管辖的FLASH区域全部写入0,而FLASH写入的前提是编程单元是0xFFFF,所以还需要一个函数将Coffee管辖的FLASH区域全部擦除,即由0变1。源代码如下:
- //erase flash between COFFEE_START to COFFEE_START+COFFEE_SIZE
- void stm32_flash_erase(void)
- {
- u32_t curr_page_addr = COFFEE_START &~(FLASH_PAGE_SIZE - 1);
- u32_t last_page_addr = (COFFEE_START + COFFEE_SIZE - FLASH_PAGE_SIZE) &~(FLASH_PAGE_SIZE - 1); //用于Coffee最后一页的起始地址
- u32_t i;
- FLASH_Unlock(); /* Unlock the Flash Program Erase controller */
- for(i = curr_page_addr; i <= last_page_addr;)
- {
- FLASH_ErasePage(i); //FLASH_Status FLASH_ErasePage(uint32_t Page_Address)
- i += FLASH_PAGE_SIZE;
- }
- FLASH_Lock();
- }
三、测试
3.1 测试用例
文件Contiki\cpu\stm32w108\cfs-coffee-arch.c有个cfs_coffee_test函数,对Coffee进行多方面测试。我也是拿这个来测试Coffee,这里只举简单的例子,将字符串写入,再读出,通过串口打印观其是否相同。测试源码如下:
- #include "contiki.h"
- #include "debug-uart.h"
- #include "cfs/cfs.h"
- #include "cfs-coffee-arch.h"
- PROCESS(cfs_test_process, "cfs test");
- AUTOSTART_PROCESSES(&cfs_test_process);
- PROCESS_THREAD(cfs_test_process, ev, data)
- {
- PROCESS_BEGIN();
- usart_puts("***cfs test process start***\n");
- if(cfs_coffee_format() == - 1)
- {
- usart_puts("coffee format error.");
- return - 1;
- }
- int fd = cfs_open("CoffeeTest", CFS_WRITE | CFS_READ);
- if(fd == - 1)
- {
- usart_puts("First time open error.");
- return - 1;
- }
- char buf1[] = "Hello, World!";
- char buf2[32] = "Orignal!";
- usart_puts("The orignal buf1 and buf2 is : ");
- usart_puts(buf1);
- usart_puts(buf2);
- int size_write = cfs_write(fd, buf1, sizeof(buf1));
- cfs_seek(fd, 0, CFS_SEEK_SET); //NOTE
- int size_read = cfs_read(fd, buf2, sizeof(buf1));
- usart_puts("The update buf1 and buf2 is : ");
- usart_puts(buf1);
- usart_puts(buf2);
- cfs_close(fd);
- PROCESS_END();
- }
值得注意的是,需要在main函数加入如下代码:
- stm32_flash_erase();//erase flash between COFFEE_START to COFFEE_START+COFFEE_SIZE
- 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函数源码如下:
- void stm32_coffee_erase(u8_t sector)
- {
- u32_t data = 0;
- u32_t addr = COFFEE_START + (sector) *COFFEE_SECTOR_SIZE;
- u32_t end = addr + COFFEE_SECTOR_SIZE;
- if(!(addr >= COFFEE_START && end <= COFFEE_START + COFFEE_SIZE)) //确保地址在Coffee管理的区间
- {
- return ;
- }
- FLASH_Unlock();
- FLASH_ErasePage(addr); //先擦除 NOTE:assume COFFEE_SECTOR_SIZE=FLASH_PAGE_SIZE
- for(; addr < end; addr += 4)
- {
- if(FLASH_ProgramWord(addr, data) != FLASH_COMPLETE)
- {
- PRINTF("FLASH_ProgramHalfWord Error.\n");
- }
- }
- FLASH_Lock();
- }
通过这样改进,还可以带来额外的好处,启动系统无须先全部擦除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各位宏定义、宏判断等等。
阅读(7974) | 评论(8) | 转发(4) |