Search code examples
stm32sd-cardspifatfs

FATFS R0.11 slow read speeds on STM32F103 - SPI


I am working with STM32F103C8 and I got FATFS R0.11 working fine on it (it’s the latest version that CUBEMX uses on STM32F103C8Tx by default), but the read speed is quite low, in the neighbourhood of 100 kB/s.

The SPI clock is set to 18 MHz (changed from 9 MHz, but that made no difference, contrarious to 4.5 MHz, whose effect on read speed was noticeable - read time got increased by 30%) and I have turned off the majority of additional options to conserve RAM. With two files open, I have approximately 10 kB (from 20 kB) of RAM left (while compiling in KEIL, STM32CUBEIDE used much more RAM), which should be enough for the rest of my project.

The file is read into a buffer of 4096 bytes, 2048 bytes at a time (it will be a wav file, that is going to be sent to a I2C DAC at a sampling rate equal to 22050 Hz, the size of said buffer, depending on the RAM usage of the rest of important parts of my project, may be finally increased, but that is an unknown at this time).

Single, 2048 byte read operation takes approximately 20 ms. I was using a class 4 as well as class 10 SD card, but the results were the same. The cluster size of said SD card, when decreased to 512 bytes, allowed me to achieve the mentioned 20-21 ms per read instead of 25 ms at different format settings. Since I am also planning to display images, any lower read speed would be greatly appreciated. Time was measured with the help of HAL_GetTick() function.

I tried reading 4096 bytes at a time, even 8192 bytes, but the time taken to do so scaled appropriately, so no benefit got recorded during those tests. I even tried doing a full format instead of the default quick option, but nothing changed.

Do You think, that there is anything I can do in order to further decrease the read time or is this in some way, shape or form limited with my settings or am I doing something wrong? Below, I linked the archive with my project, which You can compile and test it for yourself (I compiled it under Keil uVision5, 0 errors as well as 0 warnings)

From clean curiosity, I also probed the SPI interface during reading of the file (that is the only thing that I am doing, currently no writing to the SD card).

Link to .rar archive

From top to bottom: SCK-yellow, MISO-magenta, MOSI-cyan, CS-blue. I added additional delays to show the different parts of transmission: the first block is responsible for mounting and opening the first file, after the 4 ms delay I open the second file (delaying opening of the first file didn't cause any delay), then after 2 ms I perform the first read and after additional 2 ms delay, I read the next 2048 bytes. Entirety of communication, added delays

Zoomed fragment

My SD card adapter and connections: standard micro SD to SD adapter with soldered goldpins and wires connecting everything on a solderless breadboard with ST-LINK V2.1 "borrowed" from STM32F429ZI-DISC1. PA4-CS, PA5-SCK, PA6-MISO, PA7-MOSI. Thanks for reading :)


Solution

  • OK, I managed to figure something out, You can clearly see on the above attached pictures, that the delay between consecutive actions on SPI bus is quite long, that seems to be the cause with HAL doing a lot of overhead work, which slows things down. I replaced the default function to transmit and receive data with a custom one, that does everything on registers, and the read time decreased by four times, that is now I can read 2 kB of data in 5-6 ms, that is equal to approximately 400 kB/s. It is still quite low, so I will wait for some time, to see if someone has a better idea on how to improve things further. I also upgraded FATFS to version R0.12c, but that didn't change anything. It seems to me, that using DMA can speed up things even more. The function in question is shown below:

    ######### from file fatfs_sd.c #########
    uint8_t SPI1_Transfer(uint8_t data){
        SPI1->CR1 |= SPI_CR1_SPE;               //enable SPI1
        SPI1->DR = data;                        // Write data to be transmitted to the SPI data register
        while (!(SPI1->SR & (SPI_SR_TXE)));     // Wait until transmit complete
        while (!(SPI1->SR & (SPI_SR_RXNE)));    // Wait until receive complete
        while (SPI1->SR & (SPI_SR_BSY));        // Wait until SPI is not busy anymore
        return SPI1->DR;                        // Return received data from SPI data register
    }
    
    static void SPI_TxByte(BYTE data){
        SPI1_Transfer(data);
    }
    static uint8_t SPI_RxByte(void){
        return SPI1_Transfer(0xff);
    }
    

    Link to updated .rar archive

    Entirety of comm

    Zoomed part

    This question also helped me: How to decrease SPI overhead time for STM32L4 HAL library