2016年11月19日 星期六

作業系統之前的程式 for stm32f4discovery (18) - 在 sd card 上使用 fat 檔案系統

問渠那得清如許? 為有源頭活水來
裙子好像太短了, 離題了。這次要站在巨人的肩膀上, 使用的是這個 FatFs - Generic FAT File System Module 讀寫 fat 的 library, 這個 library 支援 fat12, fat16, fat32, 還有長檔名以及中文檔名, 也會從硬碟分割區找到第一個 fat 檔案系統, 真的好用, 站在巨人的肩膀真好。

一樣是參考 STM32 不完原手冊的範例:
相關函式庫的用法:
FatFs - Generic FAT File System Module
當然, 在可以使用這些函式之前得先把要移植的部份搞定才行。

該怎麼開始驗證呢? 先從能正常讀取 fat 的檔案開始, 依照慣例, 得想個好方法來測試, 我決定在 pc 上測試這個 library, 一開始就在 stm32f4discovery 開發板寫程式, 不好除錯, 也很容易失敗。

先建立 fat 檔案系統的 image file, 再用它來測試 fat, 慣用 linux 的人對這一定不陌生。

以下分別建立軟碟以及硬碟影像檔, 建立硬碟影像檔是為了測試能不能從硬碟分割表找到 fat 檔案系統。

建立軟碟影像檔 create floppy images
sudo mkfs.msdos -C imagefile.img 40960 # 40M, fat 12
sudo mount imagefile.img /media/1/

# format to fat32
How to format a usb drive with FAT32 file system on Linux

mkdosfs -F 32 -I /dev/sdc1

硬碟影像檔比較複雜一點, 用 boch bximage 建立硬碟影像檔。

用 boch bximage 建立硬碟影像檔 Loop-mounting partitions from a disk image
bximage # 有選單可以選, 我建立了 flat 80MB 的 c.img

再用 fdisk c.img 切出 2 個 fat 分割區
root@debian64:load_from_sd# fdisk c.img

以 sector 為單位, 列出分割區
root@debian64:load_from_sd# fdisk -lu c.img
Disk c.img: 79.8 MiB, 83607552 bytes, 163296 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0xb976a32b

Device     Boot Start    End Sectors  Size Id Type
c.img1           2048  83967   81920   40M  c W95 FAT32 (LBA)
c.img2          83968 163295   79328 38.8M  c W95 FAT32 (LBA)

mount 這個 c.img, mount 硬碟影像檔一樣比磁片複雜些, 需要上述紅色部份的資訊 (2048, 83968), 這個單位需要是 secotr, 所以才使用 -u 參數, 而且不是直接 mount c.img, 需要透過 /dev/loop device files。

把硬碟分割表的磁區 offset 傳給 losetup
  
losetup  /dev/loop0 fat_disk -o $((62*512))
root@debian64:load_from_sd# mkfs.vfat /dev/loop0 
mkfs.fat 3.0.28 (2015-05-16)
Loop device does not match a floppy size, using default hd params

root@debian64:load_from_sd# file -s /dev/loop0 
/dev/loop0: DOS/MBR boot sector, code offset 0x3c+2, OEM-ID "mkfs.fat", sectors/cluster 4, root entries 512, Media descriptor 0xf8, sectors/FAT 80, sectors/track 32, heads 64, sectors 79328 (volumes > 32 MB) , serial number 0xc8373271, unlabeled, FAT (16 bit)

建立 fat32 檔案系統
root@debian64:load_from_sd# mkdosfs -F 32 -I /dev/loop0 
mkfs.fat 3.0.28 (2015-05-16)
Loop device does not match a floppy size, using default hd params
root@debian64:load_from_sd# file -s /dev/loop0 
/dev/loop0: DOS/MBR boot sector, code offset 0x58+2, OEM-ID "mkfs.fat", Media descriptor 0xf8, sectors/track 32, heads 64, sectors 79328 (volumes > 32 MB) , FAT (32 bit), sectors/FAT 610, serial number 0xcd3c9115, unlabeled

file -s /dev/loop0 
mount /dev/loop0  /media/2/
umount /media/2
losetup  -d /dev/loop0

fig 2 是不完原手冊講解這部份的內容, 建議仔細閱讀這章節。

fig 2 STM32 不完原手冊相關章節


在 diskio.c 補上讀取 fat image 的相關程式:

FILE_IMAGE_01, FILE_IMAGE_02 就是我額外補上的, 所以

f_mount(fs[2],"2:",1))

2, 2: 就是 FILE_IMAGE_01
3, 3: 就是 FILE_IMAGE_02
0, 0: 就是執行 spi sd card 相關的程式碼

diskio.c
 34 #define SD_CARD  0  //SD卡,卷標為0
 35 #define EX_FLASH 1 //外部flash,卷標為1
 36 #define FILE_IMAGE_01 2
 37 #define FILE_IMAGE_02 3

 50 //初始化磁碟
 51 DSTATUS disk_initialize (
 52  BYTE pdrv    /* Physical drive nmuber (0..) */
 53 )
 77                 case FILE_IMAGE_01:
 79                 {
 80 #ifndef STM32F407
 81   printf("FILE_IMAGE_01\n");
 82   //const char *fn = "fat32.img";
 83   const char *fn = "c.img";
 84 
 85   if (fs_01 == 0 )
 86   {
 87     fs_01 = fopen(fn, "r");
 88     if (fs_01 == NULL)
 89     {
 90       perror("open imagefile.img error\n");
 91       exit(1);
 92     }
 93     printf("the 1st open %s ok\n", fn);
 94   }
 95   else
 96   {
 97     printf("already open %s ok\n", fn);
 98   }
 99 
100   return 0;
101 #endif
102                   break;
103                 }
104                 case FILE_IMAGE_02:
105                 {
106 #ifndef STM32F407
107   printf("FILE_IMAGE_02\n");
108   const char *fn = "fat12.img";
109 
110   if (fs2 == 0 )
111   {
112   fs2 = fopen(fn, "r");
113   if (fs2 == NULL)
114   {
115     perror("open fat12.img error\n");
116     exit(1);
117   }
118   printf("open %s ok\n", fn);
119   }
120   else
121   {
122     printf("%s already open\n", fn);
123   }


148 DRESULT disk_read (
149  BYTE pdrv,  /* Physical drive nmuber (0..) */
150  BYTE *buff,  /* Data buffer to store read data */
151  DWORD sector, /* Sector address (LBA) */
152  UINT count  /* Number of sectors to read (1..128) */
153 )
154 {
155   u8 res=0; 
156   if (!count)return RES_PARERR;//count不能等於0,否則返回參數錯誤     
157   switch(pdrv)
158   {
159  case SD_CARD://SD卡
161    res=SD_ReadDisk(buff,sector,count);  
162    if(res)//STM32 SPI的bug,在sd卡操作失敗的時候如果不執行下面的語句,可能導致SPI讀寫異常
163    {
164      SD_SPI_SpeedLow();
165      SD_SPI_ReadWriteByte(0xff);//提供額外的8個時鐘
166      SD_SPI_SpeedHigh();
167    }
172    break;
173    #if 0
174   case EX_FLASH://外部flash
175  for(;count>0;count--)
176  {
177    SPI_Flash_Read(buff,sector*FLASH_SECTOR_SIZE,FLASH_SECTOR_SIZE);
178    sector++;
179    buff+=FLASH_SECTOR_SIZE;
180  }
181    res=0;
182    break;
183                 #endif
184                 case FILE_IMAGE_01:
185                 {
186 #ifndef STM32F407
187                         printf("call disk_image_read()\n");
188                         disk_image_read(buff, sector, count, fs_01);
189                         printf("call disk_image_read() ok\n");
190 #endif
191                         return RES_OK;  
192                   break;
193                 }
194                 case FILE_IMAGE_02:
195                 {
196 #ifndef STM32F407
197                         printf("call disk_image_read()\n");
198                         disk_image_read(buff, sector, count, fs2);
199                         printf("call disk_image_read() ok\n");
200 #endif
201                         return RES_OK;  
202                   break;
203                 }
204    default:
205      res=1; 
206  }

diskio.c L188 disk_image_read() 就是很單純的呼叫 fread 而已。

大概描述一下 fatfs 運作方式:

f_mount() call find_volume()

一開始會從 sector 0 開始讀起, 看看是不是 fat, 如果不是, 就當作 partition 來看待, 找出第一個 partition (L2248), 在 L2303 會確認出是那一個 fat 類型, fat12, fat16 或是 fat32。

ff.c
2244  /* Find an FAT partition on the drive. Supports only generic partitioning, FDISK and SFD. */
2245  bsect = 0;
2246  fmt = check_fs(fs, bsect);  /* Load sector 0 and check if it is an FAT boot sector as SFD */
2247         printf("fmt: %d\n", fmt);
2248  if (fmt == 1 || (!fmt && (LD2PT(vol)))) { /* Not an FAT boot sector or forced partition number */
2249   UINT i;
2250   DWORD br[4];
2251                 printf("xxx\n");
2252 
2253   for (i = 0; i < 4; i++) { /* Get partition offset */
2254    BYTE *pt = fs->win+MBR_Table + i * SZ_PTE;
2255    br[i] = pt[4] ? LD_DWORD(&pt[8]) : 0;
2256   }
2257   i = LD2PT(vol);  /* Partition number: 0:auto, 1-4:forced */
2258   if (i) i--;
2259   do {  /* Find an FAT volume */
2260    bsect = br[i];
2261                         printf("bsect: %d\n", bsect);
2262    fmt = bsect ? check_fs(fs, bsect) : 2; /* Check the partition */
2263   } while (!LD2PT(vol) && fmt && ++i < 4);
2264  }
...
2300  if (!nclst) return FR_NO_FILESYSTEM; /* (Invalid volume size) */
2301  fmt = FS_FAT12;
2302  if (nclst >= MIN_FAT16) fmt = FS_FAT16;
2303  if (nclst >= MIN_FAT32) fmt = FS_FAT32;
2304 
2305         static char *fat_str[] = {"", "fat12", "fat16", "fat32"};
2306 #ifndef STM32F407
2307         printf("fat type: %s\n", fat_str[fmt]);
2308 #endif

這就是其支援硬碟分割表的程式碼。

再來我把 malloc/free 的函式改成使用 array pool 去要記憶體, 雖然我已經實作了 c++ 標準程式庫 (有 new/delete), 但連我自己也不敢用讓這個實驗簡單點是我的原則, 也讓程式很容易就可以移植到任何系統上。不過 bss 又暴了, 我得縮小這些 array pool。

ff.c 就是用這樣的手法改動 malloc/free。ff.c L538 的 512 太大了, bss 暴了, 改成 8 就可以了, 這是在 128k ram 上寫程式的挑戰, 我曾經認為 128k 的記憶體實在太小, 不過我現在很享受這樣的挑戰。

所需要的改動不只這樣, 只要 malloc/free 的部份, 全都被我改成類似的作法。

ff.c
 538 // #define BUF_POOL_SIZE 512
 539 #define BUF_POOL_SIZE 8
 540 u32 buf_pool_index = 0;
 541 #define INIT_BUF(dobj) {  \
 542                          static u8 buf_pool[(_MAX_LFN + 1) * 2][BUF_POOL_SIZE]; \
 543                                                                                 \
 544                          if (buf_pool_index >= BUF_POOL_SIZE)                \
 545                                   {  \
 546                                     printf("cannot alloc memory\n");  \
 547         LEAVE_FF((dobj).fs, FR_NOT_ENOUGH_CORE);  \
 548                                   } \
 549                                     \
 550                                   lfn = &buf_pool[buf_pool_index]; \
 551                                   ++buf_pool_index; \
 552                                   /* printf("alloc buf_pool_index: %d\n", buf_pool_index); */ \
 553                                   (dobj).lfn = lfn; \
 554                                   (dobj).fn = sfn;  \
 555                                 }
 556 #define FREE_BUF()         { \
 557                                   if (buf_pool_index > 0 ) \
 558                                     --buf_pool_index; \
 559                                     /* printf("free buf_pool_index: %d\n", buf_pool_index); */ \

最後測試了 f_open, f_opendir 這些函式, load_elf.c L178 FILINFO f_info 一定要宣告成 static, 要不然會有 segment fault 的錯誤, 我搞不懂為什麼會這樣? 文件範例也是宣告成 static。

load_elf.c
 94   exfuns_init(); // 配置 fatfs 相關變數所使用的記憶體
 95   if (FR_INVALID_DRIVE == f_mount(fs[2],"2:",1)) 
 96   {
 97     printf("f_mount fail\n");
 98     return -1;
 99   }
100 
101   while(exf_getfree("2:",&total,&free))    //得到SD卡的總容量和剩餘容量
102   {
103   }
104 
105   printf("total: %d, free: %d\n", total, free);
106 #if 1
107 
108 
109   FIL fil;       /* File object */
110   char buf[BUF_SIZE]; /* Line buffer */
111   FRESULT fr;    /* FatFs return code */
112   //fr = f_open(&fil, "1.txt", FA_READ);
113   //const char *fn = "2:/MYUR_1~1.ELF";
114   const char *fn = "2:/myur_168M.elf";
115   fr = f_open(&fil, fn, FA_READ);
116   if (fr) 
117   {
118     printf("open %s fail\n", fn);
119     return (int)fr;
120   }
121 
122   TCHAR* pos=0;
123   DWORD fsize = f_size (&fil);
124   printf("fsize: %d\n", fsize);
125   int r_len = 0;
126   while (1)
127   {
128     f_read(&fil, buf, BUF_SIZE, &r_len);
129     printf("r_len: %d\n", r_len);
130     fsize -= BUF_SIZE;
131     if (fsize < BUF_SIZE)
132       print_packet(buf, fsize);
133     else
134       print_packet(buf, BUF_SIZE);
135 
136     Elf32Ehdr elf_header = *((Elf32Ehdr*)buf);
137     printf("sizeof Elf32Ehdr: %d\n", sizeof(Elf32Ehdr));
138     printf("sizeof Elf32Phdr: %d\n", sizeof(Elf32Phdr));
139     printf("elf_header.e_phoff: %d\n",  elf_header.e_phoff);
140     if (elf_header.e_phoff > BUF_SIZE)
141     {
142       printf("error elf_header.e_phoff > BUF_SIZE!!\n");
143       break;
144     }
145     Elf32Phdr elf_pheader = *((Elf32Phdr*)((u8 *)buf + elf_header.e_phoff)); // program header
146     printf("elf_header.e_phnum: %d\n", elf_header.e_phnum);
147     for (int i=0 ; i < elf_header.e_phnum; ++i)
148     {
149       int ret;
150       printf("p_vaddr: %#x offset: %#x size: %d\n", elf_pheader.p_vaddr, elf_pheader.p_offset, elf_pheader.p_filesz);
151       ret = f_lseek(&fil, elf_pheader.p_offset);
152       if (elf_pheader.p_filesz > BUF_SIZE)
153       {
154         printf("can not read: elf_pheader.p_filesz > BUF_SIZE\n");
155       }
156       else
157       {
158         f_read(&fil, buf, elf_pheader.p_filesz, &r_len);
159         printf("yy r_len: %d\n", r_len);
160         print_packet(buf, r_len);
161         #if 0
162         s32_memcpy(elf_pheader.p_vaddr, buf, r_len);
163         (*(void(*)())elf_code)();
164         #endif
165       }
166     }
167     break;
168 
169     // printf(line);
170   }
171 
172   /* Close the file */
173   f_close(&fil);
174 #endif
175 
176 #ifdef TEST_DIR
177   DIR dir;
178   static FILINFO f_info; // why need static, if no static, f_readdir will get segment fault
179   FRESULT ret;
180   TCHAR long_name_pool[_MAX_LFN * 2 + 1];
181 
182   f_info.lfsize = _MAX_LFN * 2 + 1;
183   f_info.lfname = long_name_pool;
184 
185   char path[255] = "2:\\";
186   ret = f_opendir(&dir, path);
187   if (ret == FR_OK)
188   {
189     #if 1
190     while(1)
191     {
192       ret = f_readdir (&dir, &f_info);
193       if (ret != FR_OK || f_info.fname[0] == 0) 
194         break;  /* Break on error or end of dir */
195       if (f_info.fattrib & AM_DIR)                     /* It is a directory */
196       {
197         //int i = strlen(path);
198         //sprintf(&path[i], "/%s", f_info.fname);
199         #if 0
200         ret = scan_files(path);                    /* Enter the directory */
201         if (res != FR_OK) break;
202                 path[i] = 0;
203         #endif
204       } 
205       else 
206       {                                       /* It is a file. */
207         printf("%s/%s\n", path, f_info.fname);
208         printf("%s/%s\n", path, f_info.lfname);
209       }
210     }
211     #endif
212     f_closedir(&dir);
213   }
214 #endif

FATFS文件系统的中文长文件名配置的几个注意事项

《STM32 不完原手冊範例》配置的設定就可以支援中文以及長檔名, 繁體中文要用 cp950, 雖然是 unicode, 不過讀出來的卻是 big5 編碼, 真是奇怪, 我得把終端機設定為 big5 編碼, 才能正確讀到中文檔名。

ffconf.h diff
+++ b/load_from_sd/fatfs/src/ffconf.h
@@ -57,7 +57,7 @@
-#define _CODE_PAGE     936             //採用中文 GBK 編碼
+#define _CODE_PAGE     950             //採用中文 big5 編碼

我的重點並不是把 fat 檔案列出來而已, 我要讀取 elf, 並載入其 program body, 所以我加入了這些測試。

在 elf 的測試方面, 已經可以正確讀到 elf program header 以及其中的 machine code, 再來就只剩下載入以及執行它就好了, 這部份就不太好在 linux 環境中測試了。

這個 library 似乎只會用 512 byte 讀取一個 sector, 算是記憶體用量很小的程式, 這樣的程式可不好寫, 蠻厲害的。

這篇夠折磨人了, 到這裡就好了, 下一篇再來談談怎麼載入這個 elf 和執行它。

ref:
stm32 fat 檔案系統文件

1 則留言:

  1. 「雖然我已經實作了 c++ 標準程式庫 (有 new/delete), (但連我自己也不敢用)(刪除線)」
    也太誠實了!!

    回覆刪除

使用 google 的 reCAPTCHA 驗證碼, 總算可以輕鬆留言了。

我實在受不了 spam 了, 又不想讓大家的眼睛花掉, 只好放棄匿名留言。這是沒辦法中的辦法了。留言的朋友需要有 google 帳號。