USB MSD Device with SD Card

Build a low level driver for SD card, then the glue logic for FatFs and USB MSD is pretty much the same as Flash memory case posed before. In case of SD card, sector size is 512 in most of the cases. Thus the memory requirement is much relaxed. You can even allocate a file buffer bigger than the sector size.

FatFs site has a dedicated page for MMC/SDC, on which you can find fairly detailed explanation about how to interface MMC/SDC via SPI bus. Implementation should be straightforward until you encounter with cheap SD cards that do not behave very well.

In such cases, you either have to protect your code with redundancy or just stick with quality devices. If you choose SanDisk or Kingston brand, you will be safe. ADATA on the other hand, frequently generates timeout error at first try.

Most of the SD card sockets have a pin to detect the presence of the card. This pin is usually connected to GND pin or some other pin. You can use this to generate interrupt whenever a card is inserted or removed.

When inserted, run FATFS_LinkDriver() and call f_mount. When removed, you need to call FATFS_UnLinkDriver() to clear the disk state.

  596         // card is removed
  597         if(HAL_GPIO_ReadPin(SDC_EX_GPIO_Port, SDC_EX_Pin))
  598         {
  599             // reset SDCard state
  600             SDCard_SetStatus(SDCARD_NODISK);
  601             // decomission fatfs
  602             FATFS_UnLinkDriver(USERPath);
  603             DbgPrintf("\r\nSD card removed");
  604         }
  605         // card is inserted
  606         else
  607         {
  608             DbgPrintf("\r\nSD card inserted");
  609             // initialize fatfs
  610             retUSER = FATFS_LinkDriver(&USER_Driver, USERPath);
  611             // mount volume
  612             if((fr = f_mount(&USERFatFS, "/", 1)) == FR_OK)
  613             {
  614                 DbgPrintf("\r\nVolume mounted");
  615             }
  616             else
  617             {
  618                 DbgPrintf("\r\nFailed to mount volume:%d", fr);
  619             }
  620         }

This hot-plugging does not work however for USB MSD interface. You have to insert the card before you plug the device to the USB port of the host system. And do not remove the card during USB operation.

(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)