FatFs with FLASH Memory

STM32Cube framework provides similar interface for FatFS as USB MSC discussed before. Again the same argument applies to this case about the basic unit of read/write operation on a flash memory. In this case, the sector size and the cluster size of the FatFs should be equal to the sector size of FLASH memory, namely 4KB.

This can be set in the file ffconf.h, in which a lot of customization can be done by changing the definitions. In particular, following definitions should be set.

  204 #define _MIN_SS    4096  /* 512, 1024, 2048 or 4096 */
  205 #define _MAX_SS    4096  /* 512, 1024, 2048 or 4096 */

In the file user_diskio.c, USER_read and USER_write functions can be implemented as the same way as before. Here we assume that the count is always one for the simplicity. In fact this can be bigger than one if you call f_read() with the buffer size larger than the sector size (4096). However given the limited memory capacity of a mcu, that is not very realistic. And by similar token pdrv variable can be ignored.

    1 /**
    2   * @brief  Reads Sector(s) 
    3   * @param  pdrv: Physical drive number (0..)
    4   * @param  *buff: Data buffer to store read data
    5   * @param  sector: Sector address (LBA)
    6   * @param  count: Number of sectors to read (1..128)
    7   * @retval DRESULT: Operation result
    8   */
    9 DRESULT USER_read (
   10     BYTE pdrv,      /* Physical drive nmuber to identify the drive */
   11     BYTE *buff,     /* Data buffer to store read data */
   12     DWORD sector,   /* Sector address in LBA */
   13     UINT count      /* Number of sectors to read */
   14 )
   15 {
   16   /* USER CODE BEGIN READ */
   17     SFlash_ReadSector(sector, (uint8_t*)buff);
   18     return RES_OK;
   19   /* USER CODE END READ */
   20 }
   21 
   22 /**
   23   * @brief  Writes Sector(s)  
   24   * @param  pdrv: Physical drive number (0..)
   25   * @param  *buff: Data to be written
   26   * @param  sector: Sector address (LBA)
   27   * @param  count: Number of sectors to write (1..128)
   28   * @retval DRESULT: Operation result
   29   */
   30 #if _USE_WRITE == 1
   31 DRESULT USER_write (
   32     BYTE pdrv,          /* Physical drive nmuber to identify the drive */
   33     const BYTE *buff,   /* Data to be written */
   34     DWORD sector,       /* Sector address in LBA */
   35     UINT count          /* Number of sectors to write */
   36 )
   37 { 
   38   /* USER CODE BEGIN WRITE */
   39     SFlash_WriteSector(sector, (uint8_t*)buff);
   40     /* USER CODE HERE */
   41     return RES_OK;
   42   /* USER CODE END WRITE */
   43 }
   44 #endif /* _USE_WRITE == 1 */

If ioctl is to be used, implement corresponding parts in the USER_ioctl function.

   84 #if _USE_IOCTL == 1
   85 DRESULT USER_ioctl (
   86     BYTE pdrv,      /* Physical drive nmuber (0..) */
   87     BYTE cmd,       /* Control code */
   88     void *buff      /* Buffer to send/receive control data */
   89 )
   90 {
   91   /* USER CODE BEGIN IOCTL */
   92     DRESULT res = RES_ERROR;
   93 
   94     switch (cmd)
   95     {
   96     case CTRL_SYNC :
   97         res = RES_OK;
   98         break;
   99 
  100     case GET_SECTOR_COUNT :
  101         *(DWORD*)buff = SFLASH_NUM_SECTORS;
  102         res = RES_OK;
  103         break;
  104 
  105     case GET_SECTOR_SIZE :
  106         *(WORD*)buff = SFLASH_SECTOR_SIZE;
  107         res = RES_OK;
  108         break;
  109 
  110     case GET_BLOCK_SIZE :
  111         *(DWORD*)buff = SFLASH_SECTOR_SIZE;
  112         res = RES_OK;
  113         break;
  114 
  115     default:
  116         res = RES_PARERR;
  117         break;
  118     }
  119 
  120     return res;
  121   /* USER CODE END IOCTL */
  122 }
  123 #endif /* _USE_IOCTL == 1 */

FatFs allows users to not only read and write files but also to create a file system on a raw media. Unfortunately in this particular case, the capacity of the media is too small (1MB) for the FatFs to compute correct parameters. Thus its f_mkfs function failed. However, as shown before the file system can be created by the host system if it is connected to the host a s USB MSC device.

(source code)

USB MSC Device with FLASH Memory

USB Mass Storage Class implements bulk-only-transfer (BOT) with SCSI protocol. USB packets from the host eventually converted into SCSI transport commands by the middleware, in which data is exchanged (read / write) with the unit of logical block, typically 512 bytes.

This SCSI commands works well with SD card system where a dedicated controller does the job of managing the actual memory elements. If you want to use a FLASH chip as a memory unit instead, you need to handle read / write operation directly.

Fortunately, most of flash memory support 4KB block erase. This makes the 4096 bytes as a natural choice for the size of the logical block. In STM32Cube framework this is defined in the file usbd_storage_if.c

   94 #define STORAGE_LUN_NBR                  1
   95 #define STORAGE_BLK_NBR                  0x10000
   96 #define STORAGE_BLK_SIZ                  0x200

During initial enumeration, this information is registered to the host. The middleware maintains one logical block size of buffer and handles USB transaction whose each payload is only 64 bytes. It then calls SCSI requests to store / retrieve data to / from physical memory device in this case a FLASH memory when the buffer is filled. Thus the buffer size should be increased by the same amount in the file usbd_conf.h

  108 #define MSC_MEDIA_PACKET     4096

Now, remaing task is to simply convert SCSI block read/write commands into FLASH memory sector read/write operations.

    1 /**
    2   * @brief  .
    3   * @param  lun: .
    4   * @retval USBD_OK if all operations are OK else USBD_FAIL
    5   */
    6 int8_t STORAGE_Read_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len)
    7 {
    8   /* USER CODE BEGIN 6 */
    9     SFlash_ReadSector(blk_addr, buf);
   10     return (USBD_OK);
   11   /* USER CODE END 6 */
   12 }
   13 
   14 /**
   15   * @brief  .
   16   * @param  lun: .
   17   * @retval USBD_OK if all operations are OK else USBD_FAIL
   18   */
   19 int8_t STORAGE_Write_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len)
   20 {
   21   /* USER CODE BEGIN 7 */
   22     SFlash_WriteSector(blk_addr, buf);
   23     return (USBD_OK);
   24   /* USER CODE END 7 */
   25 }

Here the logical unit number(lun) is always zero. This is because there is only one LUN is defined. And the number of logical blocks (blk_len) is always one. This is because the internal buffer size is equal to the size of local block. See MSC_MEDIA_PACKET definition above.

When it is first plugged in, it appears as a raw disk with no FAT. So no logical drive will appear as if unformatted drive. It is necessary to create a FAT using fdisk. To minimize the overhead space, choose FAT16 <32M option in the fdisk (or cfdisk) utility.

Then format the drive

sudo mkfs.vfat -F 16 /dev/sdb1

At this point the host system may recognize the device as a formatted disk drive. Otherwise plug it out and in again. In the picture below, Nucleo32-L432KC (at the center) and Adesto AT25SF081 (bottom left) are used in this project. 

(source code)