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)

Leave a comment