STM32 and USB. Mass Storage + SD Card.

Within series of articles devoted to STM32Cube we start discussing different USB modes. And today we’ll realize USB Mass Storage Device class with SD-Card connected to the MCU. Thus, microcontroller STM32F10x acting as a cardreader will be the result of this post 🙂

STM32 Mass storage device

As I’ve mentioned at the beggining of the post, I’ll use STM32F10x microcontroller. SD-card will be connected via SDIO interface. So, let’s run STM32Cube and begin the configuring of MCU units!

First of all, we should enable high-speed external oscillator, USB and SDIO units. Moreover, the proper USB mode should be selected. In this case it is Mass Storage Class mode:

Mass Storage Class

Please note that when we enable any microcontroller unit, STM32CubeMx automatically marks all involved pins.

In addition, I’ll use PA10 as USB_DISCONNECT_PIN – it is responsible for software connection and disconnection of USB. So, I’ll configure this GPIO as “GPIO_Output”.

General configuration is quite clear, and now we proceed to the “Clock configuration” tab. All the frequencies can be set there. While working with USB, the most important point of clock configuration is 48 MHz at USB clock:

STM32Cube clock settings

After that we can do some extra configuration. In order to do this let’s open the “Configuration” tab and click on SDIO unit. As a result a new window will appear, where we would be able to set SDIO clock prescaler:

STM32Cube SDIO settings

In principle, after that all units are configured properly, so we can start the project generationg process!

When the generation is finished, the project is ready for programming the MCU, but we have to make some modifications in the source files. Firstly, I’ll reset the output state of USB_DISCONNECT_PIN:

/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_SDIO_SD_Init();
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_10, GPIO_PIN_RESET);
MX_USB_DEVICE_Init();

As you see, Cube has already initialised SDIO and USB, but it doesn’t take care about relations between these units. Thus, we should edit usbd_storage_if.c file, where all the functions needed for USB interface are located:

STORAGE_Init_FS,
STORAGE_GetCapacity_FS,
STORAGE_IsReady_FS,
STORAGE_IsWriteProtected_FS,
STORAGE_Read_FS,
STORAGE_Write_FS,
STORAGE_GetMaxLun_FS,
(int8_t *)STORAGE_Inquirydata_FS

STMCube doesnt’t fill this functions, so they are empty. Let’s edit some of them in order to make our device working! Firstly, we should correct the block size:

#define BLOCK_SIZE                       512

Secondly, STORAGE_GetCapacity_FS() function has to be modified. To get SD-card full size we can use HAL_SD_Get_CardInfo(). This function takes two arguments which are defined in the main.c file. So, they should be defined in usbd_storage_if.c file too:

extern SD_HandleTypeDef hsd;
extern HAL_SD_CardInfoTypedef SDCardInfo;

STORAGE_GetCapacity_FS() should return the block size and the number of blocks, and now we can calculate it. We should just divide the total size by the size of one block:

int8_t STORAGE_GetCapacity_FS (uint8_t lun, uint32_t *block_num, uint16_t *block_size)
{
  /* USER CODE BEGIN 3 */   
  HAL_SD_Get_CardInfo(&hsd, &SDCardInfo);
  *block_num  = SDCardInfo.CardCapacity / BLOCK_SIZE;
  *block_size = BLOCK_SIZE;
  return (USBD_OK);
  /* USER CODE END 3 */ 
}

Thirdly, the read and write functions should be implemented:

/*******************************************************************************
* Function Name  : STORAGE_Read_FS
* Description    : 
* Input          : None.
* Output         : None.
* Return         : None.
*******************************************************************************/
int8_t STORAGE_Read_FS (uint8_t lun, 
                        uint8_t *buf, 
                        uint32_t blk_addr,                       
                        uint16_t blk_len)
{
  /* USER CODE BEGIN 6 */ 
  HAL_SD_ReadBlocks(&hsd, (uint32_t*)buf, (uint64_t)(blk_addr * BLOCK_SIZE), BLOCK_SIZE, blk_len);
 
  return (USBD_OK);
  /* USER CODE END 6 */ 
}
 
/*******************************************************************************
* Function Name  : STORAGE_Write_FS
* Description    :
* Input          : None.
* Output         : None.
* Return         : None.
*******************************************************************************/
int8_t STORAGE_Write_FS (uint8_t lun, 
                         uint8_t *buf, 
                         uint32_t blk_addr,
                         uint16_t blk_len)
{
  /* USER CODE BEGIN 7 */ 
  HAL_SD_WriteBlocks(&hsd, (uint32_t*)buf, (uint64_t)(blk_addr * BLOCK_SIZE), BLOCK_SIZE, blk_len);
 
  return (USBD_OK);
  /* USER CODE END 7 */ 
}

We’ve just added the code which will read/write data from/to SD-card connected to SDIO interface.

Now the project is ready for compiling and programming the MCU! If we connect STM32 to the PC, we’ll see a new device in our system. Thus, we can create, edit and delete files on SD-card.

Well, I’ll stop here, if you have any questions, you can add a comment on this post 🙂

Like this post? Suggest to friends!

29 thoughts on “STM32 and USB. Mass Storage + SD Card.
  1. Hi Aveal,
    I try to do the MSC-FATfs connection on a custom board but fail at GetCapacity with getting zeros back. How does the underlying function know which SD card I wan to use? Is is correct to pass an empty/undefined hsd?

    Thanks, Kaf

    • Hi!

      Cube should fill the hsd structure with proper values during the initialization process. We just correct the BLOCK_SIZE.

      • Any idea where it does that? MX_FATFS_Init() links the driver to the SDcard but doesn’t seem to initialize anything. MX_SDIO_SD_Init() seems to initialize the low-lever stuff only.

        What version of CubeMX are you actualy using? My CubeMX-generated usbd_storage_if.c looks a bit different anyway, block number and sizes are pre-defined, probably to have a default. Here’s an excerpt:
        […]
        #define STORAGE_LUN_NBR 1
        #define STORAGE_BLK_NBR 0x10000
        #define STORAGE_BLK_SIZ 0x200
        […]
        int8_t STORAGE_GetCapacity_FS (uint8_t lun, uint32_t *block_num, uint16_t *block_size)
        {
        /* USER CODE BEGIN 3 */
        *block_size = STORAGE_BLK_SIZ;
        *block_num = STORAGE_BLK_NBR;
        return (USBD_OK);
        /* USER CODE END 3 */
        }

        • For this project I’ve used the old version of STM32Cube. When I worked with NAND memory and USB MSD I passed all parameters directly into the function STORAGE_GetCapacity_FS():

          *block_size = mySize;
          *block_num = myCount;

  2. Hi, Thank you very much for take the time to make this tutorial. I am working in a project in which i have to access to an eeprom instead of an sd. Could you bring me some help with how i have to configure the libraries?

    Thank you.

    • Hello!

      You need just to change read/write functions. For example:
      int8_t STORAGE_Read_FS (uint8_t lun,
      uint8_t *buf,
      uint32_t blk_addr,
      uint16_t blk_len)
      {
      EEPROM_Write(…..);

      return (USBD_OK);
      }

      You should also define functions to access to EEPROM (these functions are usually based in I2C bus driver).

      • Hi Aveal,

        Thanks for your response. Did you get something like this working? I am also in trouble with the inicialization of the i2c with thehal libraries. I can not understand at all how i have to modify the function:

        __weak void HAL_I2C_MspInit(I2C_HandleTypeDef *hi2c)
        {
        /* Prevent unused argument(s) compilation warning */
        UNUSED(hi2c);
        /* NOTE : This function should not be modified, when the callback is needed,
        the HAL_I2C_MspInit could be implemented in the user file
        */
        }

        Thanks again,

        • You have no need to modify it. After configuring I2C peripheral with STM32Cube you can call functions:
          HAL_I2C_Master_Transmit();
          HAL_I2C_Master_Receive();
          for read/write operations. All the initialization is done by the STM32Cube utility automatically.

          • Are you sure? I guess that the cube utility inicialization rutines dont configure the i2c pins as open drain.

  3. Hi, i could build the read/write eeprom rutines. But i dont know what parameters of:

    int8_t STORAGE_Write_FS (uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len)

    i have to pass to the read/write eeprom rutines. I understand that de minimal block size is 512 bytes but my eeprom buffer is 64 bytes, so that means that i have to do something in the middle. Could you please help with this?

    • You should divide your memory into 512 bytes blocks. Each of them should have its own number. So in write function you have to write this blocks with data passed through *buf pointer.

      • ok, so it means that Buf can point between 1 and 512 bytes. Thats write?

        How is organize the blk address? If i had 16 blocks of 512 bytes, the blk addresses that i receive from the function parameters would be 0,1,2,3,…,14,15 ?

        Thanks for your time

        • buf is always a pointer to an amount of data, which size is determined in USB settings. Commonly it’s 512 bytes. blk_len – is a number of 512 bytes data frames. So if you get blk_len = 2 in write() function you should write 1024 bytes.

          The block numeration is correct – from 0 to 15.

          If you’d like to organize a filesystem you should check if you have enough memory for it. Maybe 8 KBytes is too small…

          • I have an eeprom 24lc256. It has 32KBytes. There are 64 blocks of 512 bytes. That would be enough?

            So, if in write funcion parameters i have: blk_add = 5 and blk_len = 3, it means that i have to write 1536 bytes ( 512 in block 5 , 512 in block 6 and 512 in block 7). It’s that right?

            Thanks for your time

          • thank you very much for your help!! i will be continue working with this.

  4. I have build this rutine:

    int8_t STORAGE_Read_FS (uint8_t lun,
    uint8_t *buf,
    uint32_t blk_addr,
    uint16_t blk_len)
    {
    /* USER CODE BEGIN 6 */

    char y[100];

    sprintf(&y[0],”%d”, blk_addr);
    LCD_Puts(0,0,&y[0]);

    int i,j,k=0;
    uint16_t a;
    uint8_t aux;
    a = blk_addr*512;

    for(i=0;i<blk_len;i++)
    {
    for(j=0;j<512;j++)
    {
    eeRead(a,&aux,1);
    buf[k]=aux;
    a++;
    k++;
    }
    }

    return (USBD_OK);
    /* USER CODE END 6 */
    }

    and other similar to write, the problem is that now, windows not recognize my device.

      • Hi, I have a problem with connecting device to computer. There is an info in device manager (on win10) that “request for the usb device descriptor failed”. I’m using SDIO 1bit mode (which works fine, because I can read/write files using FatFs). Could this 1bit mode be a reason why USB doesn’t work?
        I’ve done rest of things like above.

  5. Hi!

    I’m new to STM32 and STM32cube and I’m using a STM32F103C MCU without the SDIO peripheral. The generated code doesn’t contain diskio.c, only user_diskio.c with empty functions. I’ve searched for a SPI diskio drive and can only find ones for the STM32F4, which is not compatible with this smaller chip.

    Any ideas how to do this with SPI?

    • Hi!

      If there is no example with this driver for STM32F103, you should implement it yourself… You can take STM32F4 functions and rewrite them to work with STM32F103.

  6. hi Aveal
    tanx for your tutorial…
    i do just like above in my program with stm32f407vgt6 .
    but mass storage not recognized completely…
    my cube version(newer) differ with your version and in usbd_storage_if.c BLOCK_SIZE not define…
    i don’t know what’s the problem…Can you tell me?

Leave a Reply

Your email address will not be published. Required fields are marked *