拖了三周,总算是完成NorFlash驱动了,相关用法见08-SylixOS&Driver&FS-Continue-Part1。本节主要介绍相关实现原理
参考文献:
事实上,无论是Fake
还是True Nor
,它们的原理都基本相同。下面先从共性上来讲原理,再从特性上来讲。
读部分较为简单。因为NorFlash是Ram-Like器件,所以其与用Ram模拟的Fake Nor的读部分基本是一致的,都是直接获取相应地址的内容即可,关键代码如下:
UINT8 read_byte_from_mem(UINT32 base, UINT32 offset){
volatile UINT8* p = (volatile UINT8*)(base + offset);
return *p;
}
UINT8 read_nor(UINT offset, PCHAR content, UINT size_bytes){
...
INT i;
pretty_print("[nor read statue]", INFO "nor read start", DONT_CENTRAL);
for (i = 0; i < size_bytes; i++)
{
*(content + i) = read_byte_from_mem(NOR_FLASH_BASE, offset + i);
}
pretty_print("[nor read statue]", INFO "nor read successfully", DONT_CENTRAL);
return 0;
}
这里将写与擦除结合起来写,因为擦除与写之间联系性较高,写通常要涉及到擦除操作。
这部分比较麻烦。有几个问题需要重点提出来:
-
Am29LV160DB NorFlash的写入是将1写为0,而其擦除是将0还原为1。如果Flash上原本是1,那么写入0或1都不需要进行擦除操作;但是,如果Flash上原本是0,这时候写入1就需要先擦除、再写入了,而此时写入0也不需要进行擦除操作。绘制表格如下:
Flash介质\写入 1 0 0 需要擦除 不需要擦除 1 不需要擦除 不需要擦除,直接写入 -
如果Am29LV160DB NorFlash的某个sector全为1,那么它是干净的,就不需要擦除;
-
正如在06-SylixOSPractice-Part1.md一文中提到的那样,写入要涉及到是否覆盖原始sector中的内容,因为NorFlash的擦除是以sector为单位进行的;
接下来,可以针对上述三个问题,设计写入与擦除了。
基本流程如下图所示:
其中分段部分即计算写入的内容的sector范围,按照每个sector来进行写入,关键代码如下:
UINT8 start_sector_no = GET_SECTOR_NO(offset);
UINT8 end_sector_no = GET_SECTOR_NO(offset + size_bytes - 1);
接下来计算每一次将要写入的内容偏移、大小,关键代码如下:
UINT size_bytes_to_write;
PCHAR content_to_write;
UINT offset_to_write;
if(sector_no == start_sector_no){ /* 第一个sector */
if(start_sector_no == end_sector_no){ /* 如果只写一个sector,那么就是写size_bytes */
size_bytes_to_write = size_bytes;
}
else {
size_bytes_to_write = GET_SECTOR_OFFSET(sector_no + 1) - offset;/* 否则,写offset ~ 第一个sector结束的所有位置 */
}
}
else if (sector_no == end_sector_no) /* 最后一个sector */
{
size_bytes_to_write = size_bytes - size_bytes_have_written; /* 总共需要写的字节 减去 已经写入的字节 */
}
else {
size_bytes_to_write = GET_SECTOR_SIZE(sector_no); /* 写入一个sector大小 */
}
content_to_write = content + size_bytes_have_written; /* 写入内容偏移量 */
offset_to_write = offset + size_bytes_have_written; /* 写入位置偏移量 */
接着,通过nor_check_should_erase (UINT32 base, UINT offset, PCHAR content, UINT size_bytes)检查是否应该擦除相应内容,这里涉及到一个算法。从前面的表中,我们可以看到,当且仅当Flash介质上是0,我们要写入1时才会需要擦除,根据这个特点,我们可以设计如下流程进行判断:
-
假设要写入的字节α是 :A1 A2 ... A8;
-
假设Flash上将要写入的位置的字节β是:A1' A2' ... A8';
-
那么字节t = A1 A2 ... A8 ^ A1' A2' ... A8便记录了字节α与字节β的所有不同位;
例如,00101011 ^ 00101001 = 00000010,不同位是从右往左第2位;
-
因为从表格可知,二者相同时,不需要擦除,所以我们只需考察不同位即可。此时,写入和被写入的位置一定一个1一个0,而写入为1时,我们需要擦除,因此只需判断字节t & 字节α == 0即可。如果结果为0,那么说明不必擦除,否则需要擦除。
上述流程对应的代码如下:
BOOL nor_check_should_erase(UINT32 base, UINT offset, PCHAR content, UINT size_bytes){
INT i;
for (i = 0; i < size_bytes; i++)
{
UINT8 byte_in_flash = read_byte_from_mem(base, offset + i);
UINT8 byte_to_write = (UINT8)(*(content + i));
UINT8 byte_diff = byte_in_flash ^ byte_to_write;
...
if((byte_diff & byte_to_write) != 0){
return TRUE;
}
}
return FALSE;
}
接下来的内容将变得较为简单,相信代码能说明一切:
switch (ops)
{
case WRITE_OVERWRITE:{ /* 覆盖写,如果需要擦除则直接擦除 */
UINT32 start_setcor_offset = GET_SECTOR_OFFSET(sector_no);
if(is_sector_should_erased){
erase_nor(start_setcor_offset, ERASE_SECTOR);
}
nor_write_buffer(NOR_FLASH_BASE, offset_to_write, content_to_write, size_bytes_to_write);
break;
}
case WRITE_KEEP:{
UINT32 start_setcor_offset = GET_SECTOR_OFFSET(sector_no); /* 保持写,如果需要擦除,则先保存整个sector,然后再将整个sector写入 */
UINT sector_size = GET_SECTOR_SIZE(sector_no);
if(is_sector_should_erased){
PCHAR buffer = (PCHAR)lib_malloc(sector_size); /* 读出sector的内容到buffer中 */
read_nor(start_setcor_offset, buffer, sector_size);
UINT bias_to_write = offset_to_write - start_setcor_offset; /* 计算写入偏移相对该sector的偏移量 */
lib_memcpy(buffer + bias_to_write, content_to_write, size_bytes_to_write); /* 向buffer中写入要写的内容 */
erase_nor(start_setcor_offset, ERASE_SECTOR); /* 擦除sector */
nor_write_buffer(NOR_FLASH_BASE, start_setcor_offset, buffer, sector_size); /* 将buffer写入sector */
lib_free(buffer);
}
else
{
nor_write_buffer(NOR_FLASH_BASE, offset_to_write, content_to_write, size_bytes_to_write); /* 直接写入sector */
}
break;
}
default:
break;
}
size_bytes_have_written += size_bytes_to_write;
其中,nor_write_buffer函数将针对不同的启动模式给出不同的写入操作,Fake Nor直接写入内存即可,而True Nor仅需按照Data Sheet时序写入即可,相关代码如下:
VOID nor_write_buffer(UINT32 base, UINT offset, PCHAR content, UINT size_bytes){
UINT8 sector_no = GET_SECTOR_NO(offset);
UINT32 sector_start_offset = GET_SECTOR_OFFSET(sector_no);
UINT sector_remain_size = GET_SECTOR_SIZE(sector_no) - (offset - sector_start_offset);
if(size_bytes > sector_remain_size){
pretty_print("[nor write buffer]", FAIL "size not permit", DONT_CENTRAL);
return;
}
if(IS_FAKE_MODE()){
PCHAR p = content;
if(get_sector_is_bad(sector_no)){ /* 随机修改 */
printf("| -> [sector #%d is bad, there may be some error(s), remember to check]\n", sector_no);
PCHAR pe = p + size_bytes;
for (; p < pe; p++)
{
INT possibily = rand() % 100 + 1;
INT random_change = rand() % 127; /* 0 ~ 127 ascii */
if(possibily >= 50){ /* 50%的几率写错 */
*p += random_change;
}
}
}
lib_memcpy((PVOID)(base + offset), content, size_bytes);
}
else{
UINT size_words = size_bytes / 2;
INT remain_byte = size_bytes - size_words * 2 ;
INT i;
for (i = 0; i < size_words; i++)
{
INT index = 2 * i;
UINT32 data = content[index] + (content[index + 1] << 8);
nor_command_unlock(base);
write_word_to_mem(base, 0x555, 0xA0);
write_word_to_mem(base, offset >> 1, data);
wait_ready(base, offset);
offset += 2;
}
if(remain_byte){
UINT32 data = content[size_bytes - 1];
nor_command_unlock(base);
write_word_to_mem(base, 0x555, 0xA0);
write_word_to_mem(base, offset >> 1, data);
wait_ready(base, offset);
}
}
}
擦除部分较为简单,只需判断相应sector是否需要擦除即可,流程图如下:
检查是否需要擦除,只需要检查该sector的内容是否全为1,因为Nor Flash干净状态就是全1。于是,相关判断代码如下:
static inline BOOL IS_SECTOR_DIRTY(UINT32 base, UINT8 sector_no){
INT i;
if(sector_no < 0 || sector_no >= NOR_FLASH_NSECTOR){
return TRUE;
}
UINT sector_size = GET_SECTOR_SIZE(sector_no);
UINT32 addr = GET_SECTOR_OFFSET(sector_no) + base;
volatile UINT8* p;
for (i = 0; i < sector_size; i++)
{
p = (volatile UINT8*)(addr + i);
if((*p & 0xFF) != 0xFF){
return TRUE;
}
}
return FALSE;
}
擦除部分代码如下,同样,分为Fake和True两种实现方式,其中,Fake模式下,只需设置相应sector对应的内存空间为全1即可,这里承接06-SylixOSPractice-Part1部分,还需要对该sector的擦写次数进行加一操作;True模式下,只需根据Data Sheet时序擦除即可。
if(IS_FAKE_MODE()){
UINT8 sector_no = GET_SECTOR_NO(offset);
UINT32 sector_start_offset = GET_SECTOR_OFFSET(sector_no);
UINT sector_size = GET_SECTOR_SIZE(sector_no);
lib_memset((PVOID)(base + sector_start_offset), (INT)-1, sector_size);
sector_infos[sector_no].erase_cnt++;
}
else{
nor_command_unlock(NOR_FLASH_BASE);
write_word_to_mem(NOR_FLASH_BASE,0x555, 0x80);
nor_command_unlock(NOR_FLASH_BASE);
write_word_to_mem(NOR_FLASH_BASE,offset >> 1, 0x30); /* 发出扇区地址 */
wait_ready(NOR_FLASH_BASE, offset); /* 等待擦除完成 */
}
与06-SylixOSPractice-Part1部分相同,具体可参考该部分的内容;
SylixOS不支持直接操作物理内存,需要做地址映射。关键代码如下:
NOR_FLASH_BASE = (UINT32)API_VmmIoRemap2(0, 2 * 1024 * 1024);
也就是说,与Fake Nor一样,True Nor的起始地址也并非地址0,而是从NOR_FLASH_BASE开始。
- 要从NOR_FLASH_START_OFFSET处开始擦除或者写入;
- 擦除干净块会提示错误,然后终止擦除操作;
- 擦除或写入NOR_FLASH_START_OFFSET之前的块会出现no permission错误,然后操作会被终止;
- 在True Nor中,没有坏块管理一说,可以尝试用文件系统来记录每个块的擦写次数,但是,目前看来,创建文件似乎失败了,还需要进一步的调整;
与06-SylixOSPractice-Part1部分相同,具体可参考该部分的内容;