cembeddedstm32keilusb-flash-drive

STM32L072 USB Flash Drive


I am working on an embedded C project in Keil µVision. Part of the project is to make the uC detect and operate as a USB flash drive. I am using a STM32L072 that is connected to external SPI Flash via SPI2 (P/N: SST26VF016B). I can read/write/erase/ the SST26V flash chip with no issues and I can communicate over USB with no problems. If I plug the device into a Windows based computer via USB it detects as a flash drive with the appropriate capacity (in this case 64KB[see NOTE 1 below]). Windows then will prompt me to format the drive. When I try to format the drive with the following parameters:

(Capacity: 64KB, File System: FAT (Default), Allocation unit size: 512 bytes, Quick Format: checked)

Windows will issue a bunch of reads and writes and eventually fail with an error messages that reads "Windows was unable to complete the format".

I seem to be having a problem bridging the gap between USB and SPI. I modified the usbd_storage_if.c following tutorials like this one. I only modified a few lines of code shown below:

#define STORAGE_LUN_NBR                  1  // Logical Unit Number
//#define STORAGE_BLK_NBR                  3840 // Use all memory in the SST26V
#define STORAGE_BLK_NBR                  80 // Use ONLY the first 64kB of flash for testing. 
#define STORAGE_BLK_SIZ                  0x200

#define SST26V_MEMORY_OFFSET                        0x10000 // This is the starting address of the SST26V's memory

....

/**
  * @brief  .
  * @param  lun: .
  * @retval USBD_OK if all operations are OK else USBD_FAIL
  */
int8_t STORAGE_Read_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len)
{
  /* USER CODE BEGIN 6 */
    printf("\r\nR%d:0x%08X", blk_len*STORAGE_BLK_SIZ, (blk_addr * STORAGE_BLK_SIZ + SST26V_MEMORY_OFFSET)); // print the size and address
    _SPI_FLASH_GetData((blk_addr * STORAGE_BLK_SIZ + SST26V_MEMORY_OFFSET), blk_len*STORAGE_BLK_SIZ, buf);
  return (USBD_OK);
  /* USER CODE END 6 */
}

/**
  * @brief  .
  * @param  lun: .
  * @retval USBD_OK if all operations are OK else USBD_FAIL
  */
    int8_t STORAGE_Write_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len)
    {
      /* USER CODE BEGIN 7 */
// incoming USB data packets are 512Bytes

    printf("\r\nW%d:0x%08X", blk_len*STORAGE_BLK_SIZ, (blk_addr * STORAGE_BLK_SIZ + SST26V_MEMORY_OFFSET)); // print the size and address
        _SPI_FLASH_WritePage(256, (blk_addr * STORAGE_BLK_SIZ + SST26V_MEMORY_OFFSET), buf); // As per SST26V datasheet, can only write 256B at a time
        _SPI_FLASH_WritePage(256, (blk_addr * STORAGE_BLK_SIZ + SST26V_MEMORY_OFFSET + 256), buf+256);
      return (USBD_OK);
      /* USER CODE END 7 */
    }

If I attempt to format the drive and then dump the 64KB flash contents over UART I get the following:

:00010000|2000000000000000200020000000000000000000000000000000000000000000
:00010020|0000000000002000000020000000000000002000200000000000200020002000
:00010040|0800100010001000000000001000000010000000000000000000000010000000
:00010060|0400040004000000040004000400800000000400000010000000000000001000
:00010080|0000010000000000010000008100000000000000010000000000010001000100
:000100A0|0100010001000000000001000000000000000100000010000000000000001000
:000100C0|0000020000008200000082008200000002000000020000000000000000000000
:000100E0|0200000000000200000002000000000000000200000000001000100010000000
:00010100|0000000000000000000000000000000000000000000000000000000000000000
... (all zeros in between these regions)
:00010C00|F8FFFF0000000000000000000000000000000000000000000000000000000000
... (all zeros in between these regions)
:00010E00|F8FFFF0000000000000000000000000000000000000000000000000000000000
... (all zeros in between these regions)
:00015000|FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
... (all FF's in between these regions)
:0001FE00|0000000000000000000000000000000000000000000000000000000000000000
:0001FE20|0000000000000000000000000000000000000000000000000000000000000000
:0001FE40|0000000000000000000000000000000000000000000000000000000000000000
:0001FE60|0000000000000000000000000000000000000000000000000000000000000000
:0001FE80|0000000000000000000000000000000000000000000000000000000000000000
:0001FEA0|0000000000000000000000000000000000000000000000000000000000000000
:0001FEC0|0000000000000000000000000000000000000000000000000000000000000000
:0001FEE0|0000000000000000000000000000000000000000000000000000000000000000
:0001FF00|0000000000000000000000000000000000000000000000000000000000000000
:0001FF20|0000000000000000000000000000000000000000000000000000000000000000
:0001FF40|0000000000000000000000000000000000000000000000000000000000000000
:0001FF60|0000000000000000000000000000000000000000000000000000000000000000
:0001FF80|0000000000000000000000000000000000000000000000000000000000000000
:0001FFA0|0000000000000000000000000000000000000000000000000000000000000000
:0001FFC0|0000000000000000000000000000000000000000000000000000000000000000
:0001FFE0|0000000000000000000000000000000000000000000000000000000000000000

Any ideas what I might be missing? I also find it slightly odd that the first 20,448 (0x4FE0) bytes is primarily zeros. This memory chip is NAND flash and so I would except all unused space to be FF's. Which makes me think; any space being written to must be erased before being written to (unless the space is already all FF's), but the smallest size I can erase on the SST26V is a sector (4 Kbyte). Does this mean the my Allocation unit size should be 4096 bytes, not 512 bytes?

NOTES

1. I'm using the first 64KB of flash to make debugging easier. Takes too long to print out entire 2MB (8Mbit) of flash over UART.

UPDATE # 1

I think I may have found the problem, but have yet to solve it. When I review all of the read write addresses that are printed out over UART I find multiple instances of Windows writing 512 bytes to address 0x00010000. Seeing as this is NAND flash it must be erased before it can be written to again, but because the smallest amount of memory that can be erased is 4kB I suppose this means that I will have to copy the 4kB flash sector to the STM32's RAM, modify the first 512 bytes, erase the 4kB flash sector, then write what I have in RAM back to the 4kB flash sector. Does that sound correct? Seems inefficient.

UPDATE # 2

I confirmed that my suspicions in UPDATE 1 were correct. I tried using only the first 512 bytes of each sector and it works, but as a result only has 1/8 of the capacity. More updates to follow.


Solution

  • I was able to solve my problem. I was on the correct path in UPDATES 1 & 2.

    I was able to use the entire 2MB region of the external flash by creating some new functions that deal with flash memory from a sector perspective and a buffer in RAM that is the size of a sector (4KB). See code below:

    /**
      * @brief  .
      * @param  lun: .
      * @retval USBD_OK if all operations are OK else USBD_FAIL
      */
    int8_t STORAGE_Read_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len)
    {
      /* USER CODE BEGIN 6 */
        if((blk_addr * STORAGE_BLK_SIZ + SST26V_MEMORY_OFFSET) == 0x00010000)
        printf("\r\nR %d:0x%08X", blk_len*STORAGE_BLK_SIZ, (blk_addr * STORAGE_BLK_SIZ + SST26V_MEMORY_OFFSET)); // print the size and address
        _SPI_FLASH_GetData((blk_addr * STORAGE_BLK_SIZ + SST26V_MEMORY_OFFSET), blk_len*STORAGE_BLK_SIZ, buf);
    
    
        
        
      return (USBD_OK);
      /* USER CODE END 6 */
    }
    
    /**
      * @brief  .
      * @param  lun: .
      * @retval USBD_OK if all operations are OK else USBD_FAIL
      */
    int8_t STORAGE_Write_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len)
    {
        uint8_t IsBlockEmptyFlag = 0; 
        uint8_t temp = 0xFF; 
        uint16_t sector_number = 0; 
        uint8_t block_number = 0; 
      /* USER CODE BEGIN 7 */
    
        printf("\r\nW%d:0x%08X", blk_len*STORAGE_BLK_SIZ, (blk_addr * STORAGE_BLK_SIZ +  bytes
        // First, check if block that we want to write to is empty (All bytes are 0xFF)
        _SPI_FLASH_GetData((blk_addr * STORAGE_BLK_SIZ + SST26V_MEMORY_OFFSET), blk_len*STORAGE_BLK_SIZ, RAMbuffer);
        for(uint16_t i = 0; i<512; i++)
            temp &= RAMbuffer[i]; 
        if(temp == 0xFF) // then all bytes must be 0xFF and the block is ready to be writen to. 
            IsBlockEmptyFlag = 1; 
        
        if(IsBlockEmptyFlag) // if the block is empty already, have at it, write away
        {
            _SPI_FLASH_WritePage(blk_len*STORAGE_BLK_SIZ, (blk_addr * STORAGE_BLK_SIZ + SST26V_MEMORY_OFFSET), buf); 
        }
        else // if it's not empty
        {
            printf("\r\nt=%02X", temp); 
            // Find the sector number that this block is in
            sector_number = blk_addr/8; 
            
            // Save the entire flash sector to RAM
            _SPI_FLASH_ReadSector(sector_number, RAMbuffer); 
            
            // Erase the sector flash sector
            SST26V_FLASH_EraseSectorN(sector_number); 
            
            // compute the block number of the corresponding sector 
            block_number = blk_addr % 8;
            
            // create a pointer to the RAM buffer
            uint8_t *p_RAM_buffer;
            p_RAM_buffer = RAMbuffer; 
            
            // Modify the RAM buffer's appropriate block
            memcpy(p_RAM_buffer + (512*block_number), buf, blk_len*STORAGE_BLK_SIZ); 
            
            // Write the buffer back into external flash
            _SPI_FLASH_WriteSector(sector_number, RAMbuffer); 
                    
        }
            
      return (USBD_OK);
      /* USER CODE END 7 */
    }