2025-10-13 7:53 AM
Hello,
I am trying to set up an external flash memory (MX25V1635F) on a Nucleo-U575ZI-Q using SPI.
- I managed to simulate a disk using RAM memory, Windows formats it perfectly, which leads me to believe that my USBX configuration is correct.
- I also tested my LevelX functions for reading and writing to my NOR flash, and they seem to be working correctly, which leads me to believe that the LevelX configuration is correct.
- I disabled all my FileX functions (format, open, read, and write) because it seems that Windows (USB Host) takes over the formatting. (Btw, if FileX can't be used, how can I write a formatted file to my flash memory?).
My problem: when I leave the USBD_STORAGE_Write and USBD_STORAGE_Read functions empty, Windows can see the size of my disk and offers to format it (without success):
And when I implement these two functions (based on the only example I found online using the Levelx functions lx_nor_flash_sector_write and lx_nor_flash_sector_read: LINK), Windows can no longer read my disk volume or offer to format it. I think my functions are not defined correctly :
/**
* @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_flash_disk,lba,data_pointer);
status = lx_nor_flash_sector_read(&nor_flash,lba,data_pointer);
data_pointer+=SECTOR_SIZE_LOGICAL;//512
lba++;
if (status != FX_SUCCESS) {
Error_Handler();
}
}
//
// for (UINT block = 0; block < number_blocks; block++)
// {
// //status = fx_media_read(&nor_flash_disk,lba,data_pointer);
// status = lx_nor_flash_sector_read(&nor_flash, lba + block, data_pointer);
// data_pointer += SECTOR_SIZE_LOGICAL;//512
// 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_flash_disk,lba,data_pointer);
status = lx_nor_flash_sector_write(&nor_flash,lba,data_pointer);
data_pointer+=SECTOR_SIZE_LOGICAL;//512
lba++;
if(status != FX_SUCCESS)
{
Error_Handler();
}
}
// for (UINT block = 0; block < number_blocks; block++)
// {
// //status = fx_media_write(&nor_flash_disk, lba + block, data_pointer);
// status = lx_nor_flash_sector_write(&nor_flash, lba + block, data_pointer);
// data_pointer += SECTOR_SIZE_LOGICAL;//512
// if(status != UX_SUCCESS)
// {
// break;
// }
// }
/* USER CODE END USBD_STORAGE_Write */
return status;
}
Also, should I use lx_nor_flash_open, and if so, where should I call it?
lx_nor_flash_open(&nor_flash, "NOR_FLASH_DISK", lx_stm32_nor_custom_driver_initialize);
Thank you for your help,
2025-10-15 8:06 AM
I still haven't managed to resolve this issue.
For your information, here is the information from my storage instance when I do:
lx_nor_flash_open(&nor_flash, "NOR_FLASH_DISK", lx_stm32_nor_custom_driver_initialize);
Does LX_NOR_SECTOR_SIZE correspond to the size of a physical sector?
Mine are 4096 bytes (or 1024 words). And will this value define the size of the logical sectors?
I have the impression that the logical sectors need to be 512 bytes.
Can you help me?
thanks
2025-10-15 11:59 AM
Hello RemiNao,
block size does not need to be 512 but you should return a corresponding number in USBD_STORAGE_GetMediaBlocklength(). But it makes just little sense to use large block size for NOR so I would consider setting it to 512. Also, consider returning UX_STATE_NEXT instead of UX_SUCCESS in these two functions. You may call lx_nor_flash_open() in USBD_STORAGE_Activate().
Regards,
Dmitry
2025-10-16 12:40 AM
Hello @DmitryR
Thank you for your help.
I forgot to mention that I’m already returning UX_STATE_NEXT and calling lx_nor_flash_open() inside USBD_STORAGE_Activate().
If you notice anything else, please let me know.
2025-10-16 1:49 AM
Hello RemiNao,
I have checked that your flash has 4K sector so you should leave this too. The one more thing I may suppose is that you use LevelX functions but you check for FileX return codes (FX_SUCCESS). That may lead to a situation when your code always ends up in the error handler.
With best regards,
Dmitry
2025-10-16 3:35 AM
Thank you,
I changed FX_SUCCESS by LX_SUCCESS, but it was not my problem.
After debbug, my program go in hardfault handler after trying de read (USBD_STORAGE_Read) only when number_blocks are superior to 1.
Here is my updated code :
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file ux_device_msc.c
* @author MCD Application Team
* @brief USBX Device applicative file
******************************************************************************
* @attention
*
* Copyright (c) 2025 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 */
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
#define STORAGE_LUN_NBR 1
#define STORAGE_BLK_NBR 500
#define STORAGE_BLK_SIZ 0x200
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
extern FX_MEDIA nor_flash_disk;
extern LX_NOR_FLASH nor_flash;
__attribute__ ((section (".usb_data"))) volatile uint8_t etx_buffer[STORAGE_BLK_NBR*STORAGE_BLK_SIZ];
/* 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);
UINT status;
status = _lx_nor_flash_open(&nor_flash, "NOR_FLASH_DISK", lx_stm32_nor_custom_driver_initialize);
/* 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>0)
{
number_blocks--;
status = lx_nor_flash_sector_read(&nor_flash,lba,data_pointer);
data_pointer+=512;//512, 128
lba++;
if (status != LX_SUCCESS) {
Error_Handler();
}
}
status = UX_STATE_NEXT;
//status = UX_SUCCESS;
/* 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>0)
{
number_blocks--;
status = lx_nor_flash_sector_write(&nor_flash,lba,data_pointer);
data_pointer+=512;//512, 128
lba++;
if(status != LX_SUCCESS)
{
Error_Handler();
}
}
status = UX_STATE_NEXT;
//status = UX_SUCCESS;
/* 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);
//fx_media_flush(&nor_flash_disk);
/* 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);
//*media_status = UX_SUCCESS;
/* 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 = 4096 - 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 = 512;//LX_NOR_SECTOR_SIZE; //SECTOR_SIZE_LOGICAL;
/* USER CODE END USBD_STORAGE_GetMediaBlocklength */
return MediaBlockLen;
}
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
And here are my physical read/write functions, lx_nor_driver_read and lx_nor_driver_write.
These are called by the sector read/write functions :
static UINT lx_nor_driver_read(ULONG *flash_address, ULONG *destination, ULONG words)
{
UINT ret = LX_SUCCESS;
/* USER CODE BEGIN NOR_READ */
uint32 byte_addr = (uint32)(flash_address); // + offset si besoin
uint32 byte_len = words * sizeof(ULONG);
if (CMD_READ(byte_addr, (uint8*)destination, byte_len) != FlashOperationSuccess){
ret = LX_ERROR;
}
/* USER CODE END NOR_READ */
return ret;
}
static UINT lx_nor_driver_write(ULONG *flash_address, ULONG *source, ULONG words)
{
UINT ret = LX_SUCCESS;
/* USER CODE BEGIN NOR_DRIVER_WRITE */
uint32 byte_addr = (uint32)(flash_address);
uint32 byte_len = words * sizeof(ULONG);
// Programmer page par page (Max 256 bytes)
uint32 offset = 0;
uint32 chunk;
while (offset < byte_len)
{
if ((byte_len - offset) > NOR_PAGE_SIZE)
{
chunk = NOR_PAGE_SIZE;
}
else
{
chunk = byte_len - offset;
}
if (CMD_PP(byte_addr + offset, ((uint8*)source) + offset, chunk) != FlashOperationSuccess)
{
ret = LX_ERROR;
break;
}
offset += chunk;
}
/* USER CODE END NOR_DRIVER_WRITE */
return ret;
}
2025-10-16 5:08 AM
Hello RemiNao,
first of all I would want to point that one never should use hardcoded values for parameters as it is a low style of programming that leads to many problems. In this case sector size, total number of blocks and so on should be used as defines, preferably that CubeMX has done for you. This way your code will be configurable and portable. Do not tell yourself that "it is for debug only" and then you will correct all this back again. No, low programming practices do not help to debug.
Then, as I have already told your flash has physical sector size of 4K bytes so it is the absolute minimum that you should use for logical sector size. Otherwise LevelX will try to erase on 512 byte blocks which for your flash is simply impossible.
At last, the hard fault means that the caller has reserved less memory than you use. In this particular case I think that the caller (USBX) assumes your sector size less than LevelX assumes. In this case you could surely become a hard fault even with one block, but it is a question of probability.
And one more advice. Switch on stack overflow check while debugging. USBX sometimes needs surprisingly huge stack.
With best regards,
Dmitry
2025-10-19 4:07 AM
Hi @RemiNao
I've shared a draft application under this thread.
It combines USBX Device MSC class and NOR Flash memory.
Have a look at it.
regards
Haithem.