📜 ⬆️ ⬇️

FAT32 media emulation on stm32f4



Recently this task has arisen - FAT32 media emulation on stm32f4.

Its unusualness lies in the fact that among the strapping of the microcontroller there may be no drive at all.
')
In my case, the drive was, but the rules for working with it did not allow to place the file system. In TK, however, there was a requirement to organize a Mass Storage interface for accessing data.

The result of the work was the module, which I headlined "emfat", consisting of the same name .h and .c file.

The module is platform independent. In the attached example, it works on the stm32f4discovery board.

The function of the module is to give out the pieces of the file system that the usb-host will request, substituting user data if it tries to read a certain file.

Who may be useful


First of all , it is useful in any technical solution, where the device offers a Mass Storage interface in read-only mode. FAT32 emulation "on the fly" in this case will allow you to store data as you please, without the need to support FS.

Secondarily, it is useful to aesthetes. To those who do not have a physical drive, but want to see their device as a disk in the cherished "My Computer". In the root of the disk, there may be instructions, drivers, a file with a description of the device version, and so on.

In this case, it should be noted, instead of emulation of the carrier, you can give the host part of the “compiled” cast of the prepared FS. However, in this case, most likely, the memory consumption of the MC will be significantly higher, and the flexibility of the solution will be zero.

So how it works.




When a user attempts to read or write a file, the corresponding call is translated into usb requests, which are transmitted to our device. The essence of the query is simple - write or read the sector on the final media.

At the same time, it should be noted, the Windows (or another OS) behaves like a hostess in terms of organizing storage on the carrier. Only she knows what sector wants to read or write. But he wants to - and completely defragments us, arranging a chaotic "jangling" by sectors ... Thus, the function of a typical USB MSC controller is to submit a portion of 512 bytes with a shift, or count portions, without a murmur.

Now back to the emulation function.

Immediately I warn you, we do not emulate writing to the media. Our "carrier" is read only.

This is due to the increased complexity of control over the formation of the file table.

However, the emfat_write dummy function is present in the module API. Perhaps in the future a solution will be found for the correct emulation of the record.

The task of the module when requesting a read is to “give away” valid data. This is his main work. Depending on the requested sector, this data may be:

It should be noted that the acceleration of the decision "what data to give" emphasis was placed. Therefore, overhead costs have been minimized.

Due to the fact that we refused to maintain the recording on the drive, we are free to organize the storage structure, as we want:



Everything is absolutely standard, except for a few details:


Naturally, you need to understand that this structure is imaginary. In reality, it is not contained in RAM, but is formed accordingly, depending on the number of the sector read.

API module


The API is composed of only three functions:

bool emfat_init(emfat_t *emfat, const char *label, emfat_entry_t *entries); void emfat_read(emfat_t *emfat, uint8_t *data, uint32_t sector, int num_sectors); void emfat_write(emfat_t *emfat, const uint8_t *data, uint32_t sector, int num_sectors); 

Of these, the main function is emfat_init.

Its user calls it once - when we connect our usb device or at the start of the controller.
The function parameters are an instance of the file system (emfat), a label for the section (label) and a table of FS elements (entries).

A table is defined as an array of emfat_entry_t structures as follows:

 static emfat_entry_t entries[] = { // name dir lvl offset size max_size user read write { "", true, 0, 0, 0, 0, 0, NULL, NULL }, // root { "autorun.inf", false, 1, 0, AUTORUN_SIZE, AUTORUN_SIZE, 0, autorun_read_proc, NULL }, // autorun.inf { "icon.ico", false, 1, 0, ICON_SIZE, ICON_SIZE, 0, icon_read_proc, NULL }, // icon.ico { "drivers", true, 1, 0, 0, 0, 0, NULL, NULL }, // drivers/ { "readme.txt", false, 2, 0, README_SIZE, README_SIZE, 0, readme_read_proc, NULL }, // drivers/readme.txt { NULL } }; 

The following fields are in the table:

name: the display name of the element;
dir: whether the item is a directory (otherwise, a file);
lvl: nesting level of the element (you need emfat_init functions to understand if the element is assigned to the current directory, or to the directories above);
offset: incremental offset when calling a custom callback function to read the file;
size: file size;
user: this value is transmitted “as is”, the user callback function of reading the file;
read: a pointer to a custom callback function to read the file.

The callback function has the following prototype:

 void readcb(uint8_t *dest, int size, uint32_t offset, size_t userdata); 

It sends the address “where” to read the file (the parameter dest), the size of the data read (size), the offset (offset) and userdata.
Also in the table there is a field max_size and write. The max_size value should always be equal to the size value, and the write value should be NULL.

The remaining two functions are emfat_write and emfat_read.

The first, as mentioned earlier, is a dummy, which, however, we call if the OS receives a request to write the sector.
The second is the function we need to call when reading a sector. It fills in the data transmitted to it to the address (data) depending on the requested sector (sector).

When reading a data sector pertaining to a file, the emfat module translates the sector number into the index of the file being read and the offset, and then calls the user’s callback read function. The user, accordingly, gives a “piece” of a specific file. Where it comes from the library is not interesting. For example, in the project of the customer, I gave the configuration files from internal flash memory, other files from RAM and spi-flash.

Sample code


 #include "usbd_msc_core.h" #include "usbd_usr.h" #include "usbd_desc.h" #include "usb_conf.h" #include "emfat.h" #define AUTORUN_SIZE 50 #define README_SIZE 21 #define ICON_SIZE 1758 const char *autorun_file = "[autorun]\r\n" "label=emfat test drive\r\n" "ICON=icon.ico\r\n"; const char *readme_file = "This is readme file\r\n"; const char icon_file[ICON_SIZE] = { 0x00,0x00,0x01,0x00,0x01,0x00,0x18, ... }; USB_OTG_CORE_HANDLE USB_OTG_dev; //    emfat_t emfat; // callback    void autorun_read_proc(uint8_t *dest, int size, uint32_t offset, size_t userdata); void icon_read_proc(uint8_t *dest, int size, uint32_t offset, size_t userdata); void readme_read_proc(uint8_t *dest, int size, uint32_t offset, size_t userdata); //   static emfat_entry_t entries[] = { // name dir lvl offset size max_size user read write { "", true, 0, 0, 0, 0, 0, NULL, NULL }, // root { "autorun.inf", false, 1, 0, AUTORUN_SIZE, AUTORUN_SIZE, 0, autorun_read_proc, NULL }, // autorun.inf { "icon.ico", false, 1, 0, ICON_SIZE, ICON_SIZE, 0, icon_read_proc, NULL }, // icon.ico { "drivers", true, 1, 0, 0, 0, 0, NULL, NULL }, // drivers/ { "readme.txt", false, 2, 0, README_SIZE, README_SIZE, 0, readme_read_proc, NULL }, // drivers/readme.txt { NULL } }; // callback    "autorun.inf" void autorun_read_proc(uint8_t *dest, int size, uint32_t offset, size_t userdata) { int len = 0; if (offset > AUTORUN_SIZE) return; if (offset + size > AUTORUN_SIZE) len = AUTORUN_SIZE - offset; else len = size; memcpy(dest, &autorun_file[offset], len); } // callback    "icon.ico" void icon_read_proc(uint8_t *dest, int size, uint32_t offset, size_t userdata) { int len = 0; if (offset > ICON_SIZE) return; if (offset + size > ICON_SIZE) len = ICON_SIZE - offset; else len = size; memcpy(dest, &icon_file[offset], len); } // callback    "readme.txt" void readme_read_proc(uint8_t *dest, int size, uint32_t offset, size_t userdata) { int len = 0; if (offset > README_SIZE) return; if (offset + size > README_SIZE) len = README_SIZE - offset; else len = size; memcpy(dest, &readme_file[offset], len); } //       ,     -   //   int main(void) { emfat_init(&emfat, "emfat", entries); #ifdef USE_USB_OTG_HS USBD_Init(&USB_OTG_dev, USB_OTG_HS_CORE_ID, &USR_desc, &USBD_MSC_cb, &USR_cb); #else USBD_Init(&USB_OTG_dev, USB_OTG_FS_CORE_ID, &USR_desc, &USBD_MSC_cb, &USR_cb); #endif while (true) { } } 

Also the key part of the StorageMode.c module (USB MSC event handling):

 int8_t STORAGE_Read( uint8_t lun, // logical unit number uint8_t *buf, // Pointer to the buffer to save data uint32_t blk_addr, // address of 1st block to be read uint16_t blk_len) // nmber of blocks to be read { emfat_read(&emfat, buf, blk_addr, blk_len); return 0; } int8_t STORAGE_Write(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len) { emfat_write(&emfat, buf, blk_addr, blk_len); return 0; } 


findings


To use Mass Storage in your project, it is not necessary to have a drive with a file system organized on it. You can use the FS emulator.

The library implements only basic functions and has several limitations:

Despite the limitations, I personally have enough functionality in the projects, but, depending on the demand, I allow the release of an updated version in the future.

Project repository
Link to the project archive

Source: https://habr.com/ru/post/247673/


All Articles