FatFS dosya sistemi, mikrodenetleyiciler alanında yaygın olarak bilinen bir dosya sistemidir. Hafif yapısı ve uyumluluğu sayesinde MCU geliştiricileri tarafından sıklıkla tercih edilmektedir. USB bellekler ve SD kartlarda dosya okuma ve yazma gibi işlemleri gerçekleştirirken, çalışmalarımızı destekleyecek bir dosya sistemine sıklıkla ihtiyaç duyarız. Özellikle bazı MCU uygulamalarında, bir dosya sisteminin eklenmesi sistem etkileşiminin kullanıcı dostu olmasını önemli ölçüde artırabilir. Bu makalede, STM32F4 üzerinde FatFS dosya sisteminin nakli ve uygulamasını tanıtıyoruz.
FatFS'nin Özelliği
- DOS/Windows ile uyumlu FAT/exFAT dosya sistemi.
- Platformdan bağımsız ve taşınması kolay.
- Program kodu ve çalışma alanı çok az yer kaplar.
- Aşağıdaki çeşitli yapılandırma seçeneklerini destekler:
- ANSI/OEM veya Unicode formatında uzun dosya adları.
- Büyük miktarda veri depolamak için exFAT dosya sistemi, 64-bit LBA ve GPT.
- RTOS'un iş parçacığı güvenliği.
- Birden fazla birim (fiziksel sürücüler ve bölümler).
- Değişken sektör boyutu.
- DBCS dahil olmak üzere birden fazla kod sayfası.
- Salt okunur, isteğe bağlı API, G/Ç tamponları vb.
MCU'da FatFS Nakli
Hazırlık
FatFS naklini başlatmadan önce bazı gerekli hazırlıkları yapmamız gerekiyor. Öncelikle, uygun donanım platformunu hazırlamanız gerekiyor. Burada STM32F407VET6 işletim platformunu kullanıyoruz. USB donanımıyla ilgili kütüphanelerin platforma uyarlama çalışmaları da tamamlandı.
İkinci olarak, FatFS'nin ilgili kaynak kodunu da hazırlamamız gerekiyor. Burada, web sitesinden indirilebilen en son R0.14b sürümünü kullanıyoruz:
http://elm-chan.org/fsw/ff/00index_e.html
İndirilen kaynak kodu açıldıktan sonra, document ve source olmak üzere iki klasör bulunur. Document klasörü, web sitesindeki içerikle aynı olan ilgili belgeleri içerir. Taşıma işlemi sırasında çalışmak için bu belgeleri görüntüleyebilirsiniz. Source klasörü, kaynak koduyla ilgili dosyaları içerir ve başlıca şunları içerir:

Yukarıdaki resimde gösterilen dosya dizisi içinde, 00readme.txt dosyası her bir dosya hakkında bir tanıtım içermektedir. Dosyanın içeriği şu şekildedir:
| File Name | Description |
|---|---|
| 00readme.txt | Readme file |
| 00history.txt | Version history file |
| ff.c | FatFs module |
| ffconf.h | Configuration file for the FatFs module |
| ff.h | Header file for the FatFs application module |
| diskio.h | Header file for the FatFs and disk I/O module |
| diskio.c | An instance that attaches disk I/O functions to FatFS |
| ffunicode.c | Unicode encoding utility functions |
| ffsystem.c | Optional operating system-related file instance |
Bu dosyalar arasında ff.c ve ff.h temel dosyalardır. ffunicode.c karakter kodlamasını içerir ve kodlama, yapılandırma dosyasındaki ayarlara göre seçilecektir. ffsystem.c dosyası ise kendi ihtiyaçlarınıza göre belirlenir. Bu nedenle, belirli uygulama platformuyla ilgili olan ve bizim tarafımızdan uygulanması gereken dosyalar, yapılandırma dosyası ffconf.h ile disk işlem dosyaları diskio.h ve diskio.c'dir. Bu dosyalar aynı zamanda nakil çalışmamızın odak noktasıdır.
Nakil İşlemi
Nakil için hazırlık çalışmalarını tamamladık ve şimdi büyük kapasiteli USB diskler için uygulama naklini gerçekleştireceğiz. Daha önce de belirttiğimiz gibi, nakil için işlenmesi gereken dosyalar yapılandırma dosyası ffconf.h ile disk işlem dosyaları diskio.h ve diskio.c’dir.
ffconf.h yapılandırma dosyası hakkında, aslında kendisinin bir örneği vardır. Yalnızca yapılandırmayı gerektiği gibi değiştirmemiz yeterlidir. Burada değiştirmemiz gereken yapılandırma parametreleri şunlardır:
Desteklenen kodlama yöntemi yapılandırma parametresi FF_CODE_PAGE, dosya kodlama sorunuyla ilgilidir. Basitleştirilmiş Çince'yi destekleyecek şekilde yapılandırıyoruz.
Mantıksal sürücü sayısı FF_VOLUMES parametresiyle yapılandırılır. FatFS aynı anda birden fazla sürücüye uygulanabilir, bu nedenle sürücü sayısını gerçek duruma göre yapılandırmamız gerekir.
Zaman damgası yapılandırma parametresi FF_FS_NORTC; çoğu zaman zaman damgalarını kaydetmemiz gerekmediğinden, burada bunu devre dışı bırakıyoruz.
Geri kalanlar, disk IO işlemlerini gerçekleştirmekle ilgili işlevlerdir. FatFS yardım belgesinde, uygulanması gereken iki tür işlev olduğu belirtilmektedir: Birincisi, disk aygıtı denetimi ile ilgili işlevlerdir; bunlar çoğunlukla aygıt durumunu alma, aygıt işlevlerini başlatma ve okuma işlevleridir. Veri işlevleri, veri yazma işlevleri ve aygıt kontrolüyle ilgili işlevler; ikinci kategori ise gerçek zamanlı saat işlevidir ve bu işlev esas olarak geçerli saati almak için kullanılır. Bu nedenle, bu 6 işlevi uygulamak, naklin ana görevidir.
Cihaz durumunu alma işlevi
Disk durumu algılama işlevi disk_status. Disk durumunu algılamak için kullanılır ve ff.c dosyasında çağrılacaktır. İşlev prototipi aşağıdaki gibidir:
ˆ DSTATUS disk_status(BYTE drV);
Prototip tanımına ve USB yığın depolama cihazımızın gereksinimlerine göre, disk durumu alma işlevini şu şekilde uygulayabiliriz:
/*Status acquisition function for USBH*/
static DSTATUS USBH_status(BYTE lun)
{
DRESULT res = RES_ERROR;
if(USBH_MSC_UnitIsReady(&hUsbHostFS, lun))
{
res = RES_OK;
}
else
{
res = RES_ERROR;
}
return res;
}
Aygıt başlatma işlevi
disk_initialize depolama ortamı başlatma işlevi. Bu işlev, disk aygıtını başlatmak için kullanılır ve ff.c dosyasında çağrılacaktır. İşlev prototipi aşağıdaki gibidir:
ˆ DSTATUS disk_initialize(BYTE drv);
Prototip tanımına ve USB yığın depolama aygıtımızın gereksinimlerine göre, disk sürücüsü başlatma işlevini uygulayabiliriz; ancak burada buna aslında ihtiyacımız yok, çünkü başlatma işlemi USB HOST kütüphanesinde tamamlanmış durumda; bu nedenle doğrudan doğru işlevi döndürmek yeterlidir.
/*Initialization function for USBH*/
static DSTATUS USBH_initialize(BYTE lun)
{
//Initialization has been completed in the USB HOST library
return RES_OK;
}
Verileri oku
disk_read sektör okuma işlevini okuyun. Bu işlev, disk verilerini okumak için kullanılır. Belirli bir disk I/O'suna göre yazılmıştır ve ff.c dosyasında çağrılacaktır. İşlev prototipi şu şekildedir:
- DRESULT disk_read(BYTE drv, BYTE*buff, DWORD sector, BYTE.count);
Prototip tanımına ve USB yığın depolama cihazımızın gereksinimlerine göre, disk veri okuma işlevini şu şekilde uygulayabiliriz:
/*Read sector function for USBH*/
static DRESULT USBH_read(BYTE lun, BYTE *buff, DWORD sector, UINT count)
{
DRESULT res = RES_ERROR;
MSC_LUNTypeDef info;
if(USBH_MSC_Read(&hUsbHostFS, lun, sector, buff, count) == USBH_OK)
{
res = RES_OK;
}
else
{
USBH_MSC_GetLUNInfo(&hUsbHostFS, lun, &info);
switch(info.sense.asc)
{
case SCSI_ASC_LOGICAL_UNIT_NOT_READY:
case SCSI_ASC_MEDIUM_NOT_PRESENT:
case SCSI_ASC_NOT_READY_TO_READY_CHANGE:
USBH_ErrLog ("USB Disk is not ready!");
res = RES_NOTRDY;
break;
default:
res = RES_ERROR;
break;
}
}
return res;
}
Verileri yaz
disk_write sektör yazma işlevini yazın. Bu işlev, diske veri yazmak için kullanılır. İşlev, ilgili disk I/O'suna göre yazılır ve ff.c dosyasında çağrılacaktır. İşlevin prototipi aşağıdaki gibidir:
- DRESULT disk_write(BYTE drv, const BYTE*buff, DWORD sector, BYTE count);
Prototip tanımına ve USB yığın depolama cihazımızın gereksinimlerine göre, disk veri yazma işlevini şu şekilde uygulayabiliriz:
/*Write sector function for USBH*/
static DRESULT USBH_write(BYTE lun, const BYTE *buff, DWORD sector, UINT count)
{
DRESULT res = RES_ERROR;
MSC_LUNTypeDef info;
if(USBH_MSC_Write(&hUsbHostFS, lun, sector, (BYTE *)buff, count) == USBH_OK)
{
res = RES_OK;
}
else
{
USBH_MSC_GetLUNInfo(&hUsbHostFS, lun, &info);
switch(info.sense.asc)
{
case SCSI_ASC_WRITE_PROTECTED:
USBH_ErrLog("USB Disk is Write protected!");
res = RES_WRPRT;
break;
case SCSI_ASC_LOGICAL_UNIT_NOT_READY:
case SCSI_ASC_MEDIUM_NOT_PRESENT:
case SCSI_ASC_NOT_READY_TO_READY_CHANGE:
USBH_ErrLog("USB Disk is not ready!");
res = RES_NOTRDY;
break;
default:
res = RES_ERROR;
break;
}
}
return res;
}
Kontrol cihazıyla ilgili işlevler
disk_ioctl depolama ortamı kontrol işlevi. Bu işlev içine, depolama ortamının boyutunu alma, depolama ortamının açık olup olmadığını algılama ve depolama ortamının sektör sayısını belirleme gibi ihtiyaç duyduğunuz işlevsel kodları yazabilirsiniz. Basit bir uygulama ise, bunu yazmanıza gerek yoktur. İşlevin prototipi şu şekildedir:
- DRESULT disk_ioctl(BYTE drv, BYTE ctrl, VoiI*buff);
Prototip tanımına ve USB yığın depolama aygıtımızın gereksinimlerine göre, disk aygıtı denetimi ile ilgili işlevleri şu şekilde uygulayabiliriz:
/*USBH IO control function */
static DRESULT USBH_ioctl(BYTE lun, BYTE cmd, void *buff)
{
DRESULT res = RES_ERROR;
MSC_LUNTypeDef info;
switch(cmd)
{
/* Make sure that no pending write process */
case CTRL_SYNC:
res = RES_OK;
break;
/* Get number of sectors on the disk (DWORD) */
case GET_SECTOR_COUNT :
if(USBH_MSC_GetLUNInfo(&hUsbHostFS, lun, &info) == USBH_OK)
{
*(DWORD*)buff = info.capacity.block_nbr;
res = RES_OK;
}
else
{
res = RES_ERROR;
}
break;
/* Get R/W sector size (WORD) */
case GET_SECTOR_SIZE :
if(USBH_MSC_GetLUNInfo(&hUsbHostFS, lun, &info) == USBH_OK)
{
*(DWORD*)buff = info.capacity.block_size;
res = RES_OK;
}
else
{
res = RES_ERROR;
}
break;
/* Get erase block size in unit of sector (DWORD) */
case GET_BLOCK_SIZE :
if(USBH_MSC_GetLUNInfo(&hUsbHostFS, lun, &info) == USBH_OK)
{
*(DWORD*)buff = info.capacity.block_size / USB_DEFAULT_BLOCK_SIZE;
res = RES_OK;
}
else
{
res = RES_ERROR;
}
break;
default:
res = RES_PARERR;
}
return res;
}
Şu anki saati öğren
get_fattime gerçek zamanlı saat işlevi. Geçerli saati almak ve 32 bitlik işaretsiz bir tamsayı döndürmek için kullanılır. Saat bilgisi bu 32 bit içinde yer alır. Zaman damgası kullanmıyorsanız, doğrudan 0 gibi bir sayı döndürebilirsiniz. İşlev prototipi şu şekildedir:
ˆ DWORD get_fattime(Void);
Prototip tanımına ve USB yığın depolama cihazımızın gereksinimlerine göre, disk durumu alma işlevini şu şekilde uygulayabiliriz:
/*Read clock function*/
DWORD get_fattime(void)
{
return 0;
}
Yukarıdaki 6 programın yazımını tamamladığınızda, taşıma işlemi temel olarak tamamlanmış olur. Uyguladığımız işlev adının prototip işlevden farklı göründüğünü fark edebilirsiniz. Bu, esas olarak aynı anda birden fazla depolama aygıtı mevcut olduğunda işlemleri kolaylaştırmak içindir. Hedef işlevde uyguladığımız işlevi doğrudan çağırabiliriz.
Uygulama Testi
FatFS'nin kurulumunu tamamladık; şimdi kurulumun doğru olup olmadığını kontrol edelim. Bunun için, bir USB bellek üzerindeki dosyaya veri yazan ve dosya verilerini okuyan bir uygulama yazalım.
/* USB HOST MSC operation function, this part of the function is set according to needs */
static void MSC_Application(void)
{
FRESULT res; /* FatFs function return value */
uint32_t byteswritten, bytesread; /* Number of files read and written */
uint8_t wtext[] = "This is STM32 working with FatFs!"; /* Write file buffer */
uint8_t wtext2[] = "This is an example of FatFs reading and writing!"; /* Write file buffer */
uint8_t wtext3[] = "This is a test of appending data to a file!"; /* Write file buffer */
uint8_t rtext[100]; /* Read file buffer */
/* Register the file system object to the FatFs module */
if(f_mount(&USBHFatFS, (TCHAR const*)USBHPath, 0) != FR_OK)
{
/* Error handling */
Error_Handler();
}
else
{
/* Open a file */
if(f_open(&USBHFile, "STM32.TXT", FA_OPEN_EXISTING | FA_WRITE) != FR_OK)
{
/* Error handling */
Error_Handler();
}
else
{
res=f_lseek(&USBHFile,f_size(&USBHFile)); //Point the pointer to the end of the file
//res=f_lseek(&USBHFile,100); //Point the pointer to the end of the file
/* Write data to file */
res = f_write(&USBHFile, wtext, sizeof(wtext), (void *)&byteswritten);
res = f_write(&USBHFile, "\r\n", sizeof("\r\n")-1, &byteswritten);
res = f_write(&USBHFile, wtext2, sizeof(wtext2), (void *)&byteswritten);
res = f_write(&USBHFile, "\r\n", sizeof("\r\n")-1, &byteswritten);
res = f_write(&USBHFile, wtext3, sizeof(wtext3), (void *)&byteswritten);
res = f_write(&USBHFile, "\r\n", sizeof("\r\n")-1, &byteswritten);
if((byteswritten == 0) || (res != FR_OK))
{
/* Error handling */
Error_Handler();
}
else
{
/* Close file */
f_close(&USBHFile);
/* Open file for reading */
if(f_open(&USBHFile, "STM32.TXT", FA_READ) != FR_OK)
{
/* Error handling */
Error_Handler();
}
else
{
/* Read data from file */
res = f_read(&USBHFile, rtext, sizeof(rtext), (void *)&bytesread);
if((bytesread == 0) || (res != FR_OK))
{
/* Error handling */
Error_Handler();
}
else
{
/* Close file */
f_close(&USBHFile);
/* Compare read and written data */
if((bytesread != byteswritten))
{
/* Error handling */
Error_Handler();
}
else
{
/* No errors */
}
}
}
}
}
}
FATFS_UnLinkDriver(USBHPath);
}
USB sürücüsünde ilk olarak oluşturduğumuz dosyanın adı "STM32.TXT"dir. Yukarıdaki kaynak kodunda, dosyayı oluşturduktan sonra dosyayı açıp kapatacak şekilde düzenliyoruz. Aşağıdaki şekilde gösterildiği gibi, oluşturulan STM32.TXT dosyasına "Bu, FatFs ile çalışan STM32'dir!" yazın:

Şimdi, mevcut bir dosyaya içerik eklemeye çalışacağız. Aşağıda gösterildiği gibi, bu dosya yine STM32.TXT dosyasıdır:

Bu aşamada, FatFS dosya sisteminin taşınması ve test edilmesi işlemlerini tamamladık. Test sonuçlarına göre, taşıma işlemi doğru bir şekilde gerçekleştirilmiş; en azından basit uygulamalarda herhangi bir sorun tespit edilmedi.
Sonuç
Bu makalede, FatFS dosya sistemini STM32F4'e uyarladık ve basit bir okuma-yazma testi gerçekleştirdik. Test sonuçlarına bakıldığında, FatFS uyarlama sürecinde herhangi bir sorun olmadığı görülüyor.
Taşıma işlemi sırasında, aynı anda birden fazla sürücü mevcut olduğunda kullanım kolaylığını göz önünde bulundurduk. Tanımladığımız disk IO işlem fonksiyonunun, gerçek donanıma göre uygulanması gerekiyor; ardından, yazdığımız disk IO fonksiyonu, sistem tarafından belirtilen geri arama fonksiyonunda çağrılıyor.




