cancel
Showing results for 
Search instead for 
Did you mean: 

STM32L496 - SD Card SDIO write speeds (FreeRTOS, FATFS and USB), how to get "fast" (> 100kbytes/sec) throughput?

AFahr.1
Associate III

Hi all,

I need to stream data into an SD card, at about 25KBytes/sec max. Currently I'm stuck at 11kBytes/sec, but the SDIO data transfer works, kind of. I'm using an STM32L496 board, FATFS and FreeRTOS, as well as USB.

The USB part works actually really well. Every time I plug in I'm able to download at ~400kBytes/sec and copy to the SD card at say ~200Kbytes/sec. I don't need more than that.

When the USB is unplugged, I copy data from different sensors onto a StreamBuffer (FreeRTOS), and once I get 512 bytes, I copy them over to the SD card using f_write(...) through an additional buffer. I have several issues with this:

  • If I set the clock on the SD card to be less than 2, it fails very often, seconds after it starts
hsd1.Init.ClockDiv = 2;

  • If I set the bus to be 4 bits wide with the clock 2 or more, f_write returns 1 (DISK ERROR) after about 6 seconds.
  • If I set the bus to be 1 bit wide with clock 2 or more, f_write returns 1 after a few minutes, 3-5.
  • My SD write function for FatFS is below:
DRESULT SD_write(BYTE lun, const BYTE *buff, DWORD sector, UINT count)
{
	DRESULT res = RES_ERROR;
	uint32_t timeout;
	uint32_t time;
	HAL_StatusTypeDef retval;
	HAL_SD_CardStateTypedef cardState;
	bool sd_wait = true;
 
 
	timeout = count * 500; //Timeout in MS per block
	sd_wait = true;
 
	if(true == sd_card_ready)
	{
	  	retval = HAL_SD_WriteBlocks(&hsd1, (uint8_t *)buff, sector, count, timeout);
	  	cardState = HAL_SD_GetCardState(&hsd1);
 
	  	if (HAL_OK == retval)
		{
			time = HAL_GetTick();
			while(true == sd_wait)
			{
				cardState = HAL_SD_GetCardState(&hsd1);
 
				if(HAL_SD_CARD_TRANSFER == cardState)
				{
					sd_wait = false;
					res = RES_OK;
				}
 
				if((HAL_GetTick() - time) > timeout)
				{
					sd_wait = false;
					HAL_SD_Abort(&hsd1);
				}
			}
		}
	}
 
    return res;
}

The above code is the same for the USB transfer.

I'm at a loss a bit, I thought writing a block (512bytes) of data would increase the data throughput to the SD card, and I have a lot of contiguous data blocks, all the time. Data is constantly read in at about 11 KBytes/sec.

I read somewhere else that doing a pre-check on the data alignment before the "HAL_SD_WriteBlocks" call would help. I might need to try that out.

If anyone can point me in the right direction with examples, links, explanations, etc, I'd really appreciate it. I am able to buffer up to ~500ms of data if necessary... maybe more, how can I detect or recover from a slow (250ms or so) write cycle?

Cheers,

Alberto

28 REPLIES 28

The 512-byte writes to a file need to be on 512-byte boundaries within the file. Watch for any header structures you might write breaking this.

Spanning writes will cause a lot of unnecessary Read-Write churn.

Small writes below 512-byte have significant performance issues within FatFs.

Writes of 512-byte are the least efficient on the SD-Card, it is a single sector, ideally you want to write a cluster to achieve maximum throughput on the FatFs and SDIO/SDMMC level.

The L4 should be able to sustain multi MBps continuous writes to an SD Card, generally the card limits top speed. USB-HS likely limits to about 700 KBps, make sure the MSC packet size is as large as possible, ie not just 512 bytes

Is this a board of your own design?

If you can't clock the interface at 25 MHz / 4-bit there's some issue in the wiring, termination or socket. My understanding of the MicroSD card specs is that compliant cards minimally support 50 MHz, that's not to say there aren't sub-par cards out there.

The L496 supports SDMMC DMA

The SD Cards have zero tolerance of the application wandering off-task during the data transfer phase. This could be a particular issue if you yield mid-polled transfer.

Using ping-pong buffers so you can concurrently fill new buffers whilst writing the full ones.

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..
TDK
Guru

The HAL library isn't implemented for very high transfer speeds. It will have a buffer underrun or overrun and produce a hard to find error that is typically obfuscated (e.g. DISK ERROR) at the user level. Compiling in Release mode will help mitigate. Modifying HAL code to be faster will help more.

Putting a bus monitor on the line will help debug the issue, although it is typically more work than most people are willing to put forth.

If you feel a post has answered your question, please click "Accept as Solution".
AFahr.1
Associate III

@Community member​ Thanks for that.

I'm not sure if the 512 byte writes are within the boundary of the file. The data being written onto the file is from the buffer where it's collected, and usually in 512 byte chunks or multiples, except when closing the file (tail end of the stream).

It is a board of our own design, and yes, there might be some wiring issues, although, as mentioned before, when the USB is plugged, we get roughly 700KBps read speeds and no transfer errors.

The card should be ok, it's one of the better specced SanDisks.

I will have a look at DMA transfers, that might be what's breaking the transfer, the wandering off phase.

Cheers,

Alberto

AFahr.1
Associate III

Thanks a lot for all your comments so far.

I realised one mistake I made on the setup of the SD card.

  hsd1.Instance = SDMMC1;
  hsd1.Init.ClockEdge = SDMMC_CLOCK_EDGE_RISING;
  hsd1.Init.ClockBypass = SDMMC_CLOCK_BYPASS_DISABLE;
  hsd1.Init.ClockPowerSave = SDMMC_CLOCK_POWER_SAVE_DISABLE;
  hsd1.Init.BusWide = SDMMC_BUS_WIDE_1B;
  hsd1.Init.HardwareFlowControl = SDMMC_HARDWARE_FLOW_CONTROL_ENABLE; ///< It was set to DISABLE!
  hsd1.Init.ClockDiv = 0;

I changed line 6 to ENABLE and all started working beautifully. I recently ran a test overnight, and there were no transfer errors over 640 MBytes. I'll increase the speed, but it's looking good at the moment.

Cheers,

Alberto

Jtron.11
Senior

Hi Alberto,

May I ask in this project, when you are done writing t to SD card using FatFS, are you be able to retrieve the data from PC using USB to read data from your SD card? May I ask if you are using USB MSC or USB CDC implementation?

Isn't a USB MTP implementation a better route?

Having two devices share the media is fraught with issues, caching on the PC side (Windows/Linux) needs to be managed. One device should own the media, the other needs to deal with a NOT READY issue, and retry (FatFs and the user application side needs to be a lot smarter), and also be able to handle a MEDIA CHANGED event, where either device need to reassess the current content of the media/file system.

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..

Hi Jtron,

We're using the USB MSC implementation, combined with CDC.

  • CDC is used for sending commands to the device
  • MSC is used for data transfer. We get a stable throughput of about 500 - 700 KBytes per second

The way the system works, when the USB device is plugged in, all files are closed in the application, i.e., no access to files. Only the Windows file system can access the files.

When the USB is unplugged, the application can start writing to files again.

static void usb_monitor_task(void* pvParameters)
{
	while(1)
	{
		vTaskDelay(50);
 
		if(USB_START == dg_usb_state)
		{
			if(true == HAL_GPIO_ReadPin(USB_Det_GPIO_Port, USB_Det_Pin))
			{
				/* USB cable plugged in */
				dg_usb_state = USB_CONNECTED;
				MX_USB_DEVICE_Init();
				log_msg(LOG_INFO, (const char*)"USB Connected!");
				rmi_init();
			}
			else
			{
				/* USB cable unplugged */
				dg_usb_state = USB_DISCONNECTED;
				log_msg(LOG_INFO, (const char*)"USB Disconnected!");
				rmi_deinit();
			}
		}
		else if(USB_CONNECTED == dg_usb_state)
		{
			if(false == HAL_GPIO_ReadPin(USB_Det_GPIO_Port, USB_Det_Pin))
			{
				/* USB cable unplugged */
				dg_usb_state = USB_DISCONNECTED;
				log_msg(LOG_INFO, (const char*)"USB Disconnected!");
				MX_USB_DEVICE_Deinit();
				rmi_deinit();
			}
		}
		else if(USB_DISCONNECTED == dg_usb_state)
		{
			if(true == HAL_GPIO_ReadPin(USB_Det_GPIO_Port, USB_Det_Pin))
			{
				/* USB cable plugged in */
				dg_usb_state = USB_CONNECTED;
				MX_USB_DEVICE_Init();
				log_msg(LOG_INFO, (const char*)"USB Connected!");
				rmi_init();
			}
		}
	}
}

I've tried this, and it seems to work well, I have not had issues with USB so far. The only thing, you MUST wait until Windows recognises the SD drive before you attempt any data transfer using the CDC interface.

The combined implementation is a bit of a pain to be honest, but once it works it's really helpful.

Cheers,

Alberto

the flexibility of storage mode extends not only to what you can do, but how you can do it; it's accessible to any software that can access a filesystem (your favorite file browser, rsync, etc). MTP can only be accessed by software that speaks MTP (although there are ways to partially work around this).

Hi Alberto,

Thank you for the quick reply. Can you please elaborate little more about the architecture?

As I understand, CDC will expose to PC as VCP, and I understood using VCP from PC you can send your command to STM32.

Now during the USB doesn't connect connect from STM32 to PC, you said "using MSC" to store the data to SD card. Didn't you mean using SDIO/SDMMC as the frame work to store the data in uSD? Then once we send the command from PC thru VCP-USB to STM32, STM32 will use SDIO/SDMM method to read back the file in uSD and stream it out to PC using CDC/VCP method?