2024-11-06 11:05 PM - edited 2024-11-07 5:29 PM
I am working on a project to connect QSPI flash memory to a PC as a USB mass storage device (MSC) using USBX, FILEX, LVLX, and THREADX on an STM32. This setup should allow the QSPI flash to appear as a drive in Windows.
Previously, I successfully implemented a similar project using the MCU's internal RAM as the storage medium. However, after switching to QSPI flash, I encounter the error message: 'Please insert a disk into USB Drive' when connecting the device to a PC.
To debug this issue, I tested the QSPI flash read/write functionality using FILEX + LVLX + THREADX without USBX, and it worked perfectly. This suggests the problem lies in the integration with USB MSC.
I am including my code for review. Could anyone help identify any potential errors or missing steps in my implementation? Additionally, are there specific areas I should focus on to resolve this issue? Thank you.
EDIT :
lastlba ux_device_msc.c
Solved! Go to Solution.
2024-11-15 5:30 AM - edited 2024-11-15 5:31 AM
Hi @Silexman
when working with USBX Device MSC class what ever the storage media is (uSD, NOR Flash) you should not use the FileX API because the logic of the filesystem is managed by the Host, the PC for example.
so you'll need to access the NOR Flash via LEVELX API.it will manage the wearleveling and the mapping between logic sectors for the Host request and the respective physical block.
regards
Haithem.
2024-11-07 9:41 AM
Hi @Silexman
AFAIK, no direct example, you can start with the example using LevelX FileX. Then, customize usbx_device_mass_storage example using FileX After that you can simply use the example of MSC device. So, you can properly connect external flash memory to a PC as a USB mass storage device.
To give better visibility on the answered topics, please click on Accept as Solution on the reply which solved your issue or answered your question.
2024-11-07 6:00 PM
Halo, @FBL . Thank you for the response.
I followed your instructions. First, I created a project using LevelX and FileX as in the example you mentioned, formatting the nor flash to 512 bytes per sector. Then, I integrated it with USBX MSC, as per your suggestion. However, when the flash was formatted to 512 bytes per sector, a hard fault occurred upon connecting the USB to a PC. After reformatting to 2048 bytes per sector, the hard fault was resolved, but I now encounter a 'Please insert a disk into USB Drive' notification.
2024-11-07 8:47 PM - edited 2024-11-07 8:49 PM
If the SCSI Inquiry or Sense Codes indicate the media not being present.
Or it's not formatted properly. For a File System to work your block read/write methods need to WORK, and flawlessly return the data patterns written previously. Otherwise all bets are off..
I seriously double 512 to 2048 bytes sectors will work without a lot of effort.
The QSPI NOR Flash likely needs a 4K sector as this is the minimum erase block/page size, and the only things that's reasonably easy to manage.
With FATFS we'd use f_mkfs() to preformat new QSPI instances before FIRST USE
2024-11-08 12:26 AM - edited 2024-11-08 2:07 AM
Halo @Tesla DeLorean , thank you for the reply
I tried read/write operations again using FileX+LevelX+ThreadX as in this example project, formatting the media to 4096 bytes per sector, and the read results matched what was written earlier. I also checked the media using fx_media_check, and the result showed no errors on the media.
Additionally, I changed the media format to 4096 bytes per sector in the USB MSC project. I also checked the media through Disk Management in Windows with the following results. Still not working so far
2024-11-15 5:30 AM - edited 2024-11-15 5:31 AM
Hi @Silexman
when working with USBX Device MSC class what ever the storage media is (uSD, NOR Flash) you should not use the FileX API because the logic of the filesystem is managed by the Host, the PC for example.
so you'll need to access the NOR Flash via LEVELX API.it will manage the wearleveling and the mapping between logic sectors for the Host request and the respective physical block.
regards
Haithem.
2024-11-18 7:27 PM - edited 2024-11-20 10:41 PM
Halo @Haithem Rahmani Thank you for the replay.
After I accessed it using LevelX, the USB MSC feature didn’t work initially. However, after formatting it using the formatting tool in Windows, the USB MSC started working, and I could read and write files. The problem is, whenever I unplug the USB—whether the device is powered or not—the files I saved become corrupted. What steps should I take to identify and resolve this issue?
edit : after I changed the sectore size to 512 bytes, the issue is gone
2025-06-13 11:13 AM
Hi, i'm facing the same issues you were, would you mind sharing ux_device_msc.c with levelx integration? I would be interested in seeing how you set it up.
Thank you very much!
2025-06-16 6:23 PM
Halo, sorry for late response. this is my ux msc source file
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file ux_device_msc.c
* @author MCD Application Team
* @brief USBX Device applicative file
******************************************************************************
* @attention
*
* Copyright (c) 2024 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "ux_device_msc.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "app_filex.h"
#include "app_usbx_device.h"
#include "lx_api.h"
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
extern FX_MEDIA NOR_W25Q;
extern LX_NOR_FLASH W25Q_FLASH;
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
/**
* @brief USBD_STORAGE_Activate
* This function is called when insertion of a storage device.
* @PAram storage_instance: Pointer to the storage class instance.
* @retval none
*/
VOID USBD_STORAGE_Activate(VOID *storage_instance)
{
/* USER CODE BEGIN USBD_STORAGE_Activate */
UX_PARAMETER_NOT_USED(storage_instance);
/* USER CODE END USBD_STORAGE_Activate */
return;
}
/**
* @brief USBD_STORAGE_Deactivate
* This function is called when extraction of a storage device.
* @PAram storage_instance: Pointer to the storage class instance.
* @retval none
*/
VOID USBD_STORAGE_Deactivate(VOID *storage_instance)
{
/* USER CODE BEGIN USBD_STORAGE_Deactivate */
UX_PARAMETER_NOT_USED(storage_instance);
/* USER CODE END USBD_STORAGE_Deactivate */
return;
}
/**
* @brief USBD_STORAGE_Read
* This function is invoked to read from media.
* @PAram storage_instance : Pointer to the storage class instance.
* @PAram lun: Logical unit number is the command is directed to.
* @PAram data_pointer: Address of the buffer to be used for reading or writing.
* @PAram number_blocks: number of sectors to read/write.
* @PAram lba: Logical block address is the sector address to read.
* @PAram media_status: should be filled out exactly like the media status
* callback return value.
* @retval status
*/
UINT USBD_STORAGE_Read(VOID *storage_instance, ULONG lun, UCHAR *data_pointer,
ULONG number_blocks, ULONG lba, ULONG *media_status)
{
UINT status = UX_SUCCESS;
/* USER CODE BEGIN USBD_STORAGE_Read */
UX_PARAMETER_NOT_USED(storage_instance);
UX_PARAMETER_NOT_USED(media_status);
while(number_blocks--)
{
// status = fx_media_read(&NOR_W25Q,lba,data_pointer);
status = lx_nor_flash_sector_read(&W25Q_FLASH,lba,data_pointer);
data_pointer+=FX_NOR_QSPI_SECTOR_SIZE;//2048
lba++;
if (status != FX_SUCCESS) {
Error_Handler();
}
}
// for (UINT block = 0; block < number_blocks; block++)
// {
// status = fx_media_read(&NOR_W25Q, lba + block, data_pointer);
// data_pointer += NOR_W25Q.fx_media_bytes_per_sector;
// if(status != UX_SUCCESS)
// {
// break;
// }
// }
/* USER CODE END USBD_STORAGE_Read */
return status;
}
/**
* @brief USBD_STORAGE_Write
* This function is invoked to write in media.
* @PAram storage_instance : Pointer to the storage class instance.
* @PAram lun: Logical unit number is the command is directed to.
* @PAram data_pointer: Address of the buffer to be used for reading or writing.
* @PAram number_blocks: number of sectors to read/write.
* @PAram lba: Logical block address is the sector address to read.
* @PAram media_status: should be filled out exactly like the media status
* callback return value.
* @retval status
*/
UINT USBD_STORAGE_Write(VOID *storage_instance, ULONG lun, UCHAR *data_pointer,
ULONG number_blocks, ULONG lba, ULONG *media_status)
{
UINT status = UX_SUCCESS;
/* USER CODE BEGIN USBD_STORAGE_Write */
UX_PARAMETER_NOT_USED(storage_instance);
UX_PARAMETER_NOT_USED(media_status);
while(number_blocks--)
{
// status = fx_media_write(&NOR_W25Q,lba,data_pointer);
status = lx_nor_flash_sector_write(&W25Q_FLASH,lba,data_pointer);
data_pointer+=FX_NOR_QSPI_SECTOR_SIZE;//2048
lba++;
if(status != FX_SUCCESS)
{
Error_Handler();
}
}
// for (UINT block = 0; block < number_blocks; block++)
// {
//
// status = fx_media_write(&NOR_W25Q, lba + block, data_pointer);
//
// data_pointer += NOR_W25Q.fx_media_bytes_per_sector;
// if(status != UX_SUCCESS)
// {
// break;
// }
// }
/* USER CODE END USBD_STORAGE_Write */
return status;
}
/**
* @brief USBD_STORAGE_Flush
* This function is invoked to flush media.
* @PAram storage_instance : Pointer to the storage class instance.
* @PAram lun: Logical unit number is the command is directed to.
* @PAram number_blocks: number of sectors to read/write.
* @PAram lba: Logical block address is the sector address to read.
* @PAram media_status: should be filled out exactly like the media status
* callback return value.
* @retval status
*/
UINT USBD_STORAGE_Flush(VOID *storage_instance, ULONG lun, ULONG number_blocks,
ULONG lba, ULONG *media_status)
{
UINT status = UX_SUCCESS;
/* USER CODE BEGIN USBD_STORAGE_Flush */
UX_PARAMETER_NOT_USED(storage_instance);
UX_PARAMETER_NOT_USED(lun);
UX_PARAMETER_NOT_USED(number_blocks);
UX_PARAMETER_NOT_USED(lba);
UX_PARAMETER_NOT_USED(media_status);
/* USER CODE END USBD_STORAGE_Flush */
return status;
}
/**
* @brief USBD_STORAGE_Status
* This function is invoked to obtain the status of the device.
* @PAram storage_instance : Pointer to the storage class instance.
* @PAram lun: Logical unit number is the command is directed to.
* @PAram media_id: is not currently used.
* @PAram media_status: should be filled out exactly like the media status
* callback return value.
* @retval status
*/
UINT USBD_STORAGE_Status(VOID *storage_instance, ULONG lun, ULONG media_id,
ULONG *media_status)
{
UINT status = UX_SUCCESS;
/* USER CODE BEGIN USBD_STORAGE_Status */
UX_PARAMETER_NOT_USED(storage_instance);
UX_PARAMETER_NOT_USED(lun);
UX_PARAMETER_NOT_USED(media_id);
UX_PARAMETER_NOT_USED(media_status);
/* USER CODE END USBD_STORAGE_Status */
return status;
}
/**
* @brief USBD_STORAGE_Notification
* This function is invoked to obtain the notification of the device.
* @PAram storage_instance : Pointer to the storage class instance.
* @PAram lun: Logical unit number is the command is directed to.
* @PAram media_id: is not currently used.
* @PAram notification_class: specifies the class of notification.
* @PAram media_notification: response for the notification.
* @PAram media_notification_length: length of the response buffer.
* @retval status
*/
UINT USBD_STORAGE_Notification(VOID *storage_instance, ULONG lun, ULONG media_id,
ULONG notification_class, UCHAR **media_notification,
ULONG *media_notification_length)
{
UINT status = UX_SUCCESS;
/* USER CODE BEGIN USBD_STORAGE_Notification */
UX_PARAMETER_NOT_USED(storage_instance);
UX_PARAMETER_NOT_USED(lun);
UX_PARAMETER_NOT_USED(media_id);
UX_PARAMETER_NOT_USED(notification_class);
UX_PARAMETER_NOT_USED(media_notification);
UX_PARAMETER_NOT_USED(media_notification_length);
/* USER CODE END USBD_STORAGE_Notification */
return status;
}
/**
* @brief USBD_STORAGE_GetMediaLastLba
* Get Media last LBA.
* @PAram none
* @retval last lba
*/
ULONG USBD_STORAGE_GetMediaLastLba(VOID)
{
ULONG LastLba = 0U;
/* USER CODE BEGIN USBD_STORAGE_GetMediaLastLba */
// LastLba = ((0x1000000 - 0x10000)/ 2048) -1 ;
LastLba = (LX_STM32_W25Q_FLASH_SIZE / FX_NOR_QSPI_SECTOR_SIZE)-1;
/* USER CODE END USBD_STORAGE_GetMediaLastLba */
return LastLba;
}
/**
* @brief USBD_STORAGE_GetMediaBlocklength
* Get Media block length.
* @PAram none.
* @retval block length.
*/
ULONG USBD_STORAGE_GetMediaBlocklength(VOID)
{
ULONG MediaBlockLen = 0U;
/* USER CODE BEGIN USBD_STORAGE_GetMediaBlocklength */
// MediaBlockLen = 2048;
MediaBlockLen = FX_NOR_QSPI_SECTOR_SIZE;
// MediaBlockLen = 0x10000;
/* USER CODE END USBD_STORAGE_GetMediaBlocklength */
return MediaBlockLen;
}
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
first, you have to open the media with lx_media_open. In my case, I erase the chip and then format it using windows by right click on the media then format and not using the fx_media_format.
2025-06-17 5:44 AM
Thank you very much! It seems that i managed to make it work following your code.
Can you confirm that you set FX_NOR_QSPI_SECTOR_SIZE to 512?
I also had to modify:
#define LX_STM32_OSPI_SECTOR_SIZE 512
and add also LX_STM32_OSPI_BLOCK_SIZE define so that levelX knows that the underlying flash geometry has a minimum erasable block size of 4kb.
#define LX_STM32_OSPI_BLOCK_SIZE 4096
#define LX_STM32_OSPI_FLASH_SIZE W25Q16JWXHIQ_FLASH_SIZE
INT lx_stm32_ospi_get_info(UINT instance, ULONG *block_size, ULONG *total_blocks)
{
INT status = 0;
*block_size = LX_STM32_OSPI_BLOCK_SIZE;
*total_blocks = (LX_STM32_OSPI_FLASH_SIZE / LX_STM32_OSPI_BLOCK_SIZE);
return status;
}
It's quite frustrating that the concepts of "sectors" and "blocks" have different meanings through the whole levelx, usbx and stm32 nor drivers toolchain.
So basically, if i got it right, LevelX works with 512 bytes logical sectors, that are grouped in blocks. The number of sectors in one block depends on how the user configures the low level NOR driver and are set runtime by levelX when "lx_nor_flash_open" is called. In my case I used the minimum erasable unit of the flash (4kb), but also the larger 64kb block could be used.