Recently I have written a short post about the HAL library created by STM32. The HAL is gaining popularity among hobbyists and is more and more frequently used. However, when you would like to use it for something else than just basic stuff like generating PWM on digital output you have to write it by yourself. Not so long ago I had to use SD card in one of my projects. It turned out that there is no driver for FatFS based on HAL – at least there was not. I decided to write the driver and here you can read about it…
FatFS delivers you with a template which you have to fill up so you can communicate with the SD card. In my case I needed something simple and straightforward. I decided to implement hardware layer for SPI based on HAL. There is no DMA transfer, it is just a simple implementation that works pretty well with not very frequent transfers. It is well-suited for log purposes.
You need to write definition for a few functions:
- void SELECT(void),
- void DESELECT(void),
- void xmit_spi(BYTE Data),
- BYTE rcvr_spi(void).
Below are definitions of those four functions:
static void SELECT(void){ HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_RESET); }
static void DESELECT(void){ HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_SET); }
static void xmit_spi(BYTE Data){ while (HAL_SPI_GetState(&hspi2) != HAL_SPI_STATE_READY); HAL_SPI_Transmit(&hspi2, &Data, 1, 5000); }
static BYTE rcvr_spi(void){ unsigned char Dummy, Data; Dummy = 0xFF; Data = 0; while ((HAL_SPI_GetState(&hspi2) != HAL_SPI_STATE_READY)); HAL_SPI_TransmitReceive(&hspi2, &Dummy, &Data, 1, 5000); return Data; }
As you can see I am using SPI2 but you can use whatever SPI you want. For details about the HAL functions look up the documentation.
Final remark.
If you do not use chip select pin you can simply pull-down CS input port of SD card and leave SELECT and DESELECT functions empty or create empty macros which would be a bit more efficient.
Update
Below you can find my full implementation of diskio.c based on ChaN original file with some tweaks.
Update 2
I have forgotten to mention that this is non standart implementation in terms of timing. The function disk_timerproc() is required to be called every 10 ms (100 Hz). Since this implementation was using in a project where I was using FreeRTOS with finer grain (0.1 ms) I had to modify the sdcard_systick_timerproc() function.
void inline sdcard_systick_timerproc(void) { ++sdcard_timer; if (sdcard_timer >= 100) { sdcard_timer = 0; disk_timerproc(); } }
If you want it to be standard implementation you have to modify the code above. Simply instead of 100 place 10 and call this function every 1 ms i.e. in SysTick handler. Thanks lauba for pointing this out!
Update 3
Here I present a very simple example how to write data to the SD card:
char buffer[128]; static FATFS g_sFatFs; FRESULT fresult; FIL file; int len; WORD bytes_written; //mount SD card fresult = f_mount(&g_sFatFs, "0", 1); //open file on SD card fresult = f_open(&file, "file.txt", FA_OPEN_ALWAYS | FA_WRITE); //go to the end of the file fresult = f_lseek(&file, file.fsize); //generate some string len = sprintf( buffer, "Hello World!\r\n"); //write data to the file fresult = f_write(&file, buffer, len, &bytes_written); //close file fresult = f_close (&file);
Can I have diskio file of your FATfs library ( or FATfs folder)
Yes, I will try to upload this.
The source code was uploaded. You can find it at the bottom of this blog post.
I can’t find the source code you refer to. Could you upload it again? Thanks in advance
I can’t find the source code you refer to. Maybe it is possible to upload it again? Thanks in advance.
Please try the links at the very bottom of the article.
Also you can find the files here:
http://blog.domski.pl/wp-content/uploads/2016/01/sd_stm32.c
http://blog.domski.pl/wp-content/uploads/2016/01/sd_stm32.h
Why do you increment sdcard_timer to 100?
and where do you call the function sdcard_systick_timerproc? If in systemtick interuppt than is not 10 ms but 100 ms?
Yes, you are right. This files are from project where I was using FreeRTOS and I modified the timings. So this is not standard implementation.
My SystemTick is called every 0.1 ms so the function disk_timerproc() is called every 10 ms as required.
Thank you for pointing this out! I will add this information to the article.
But what is strange is working also with 100 ms;) and next question do you think about using SD card operations no blocking? for example using DMA for this? because when we want to save a lot of data to SD card than we have to wait when one block will be ready. Next operate with the second again save and wait and in loop the same;)
Well, I was using 100 ms because of the original driver, it had 100 ms. Now, I see the newest example of FatFS is calling this every 1 ms. As said before, it can be easily alternated.
As for the DMA. The example presented here is not very optimal. Each read/write or ioctl is working in a blocking mode. DMA with some FIFO could be used for this. However, for my purposes it is more than enough and for most of other things like data logging it should be also enough as long as you do not write tons of data.
Thank you for writing this driver. I have the project compiled but I’m unable to initialize the card. Can you post a small example of initialize, create file, write file, read file?
No problem. I will add it to my to do list. However, if you are really concerned about time maybe you should have a look at FatFS website. The Author has published many examples and also some sample code for different platforms.
I appreciate your prompt response. I’ve been trying for a few weeks to store time stamps with FatFS + SPI on STM32L4 and I feel like I’m missing something basic to put it all together. Your diskio.c example helps greatly.
Do I still use the f_*commands* like f_mount and f_open?
Yes, you should use the f_*() functions. The driver is driver implementation only — the hardware layer. It is transparent for the user.
I have also added a very short example how to write some data to a file. I hope it helps.
Hey, I am trying to interface an SPI-microSD device to an STM32F446RE. I am trying to use the HAL libraries along with FatFS. I am extremely confuse as to how to proceed. Could you please guide me.
First you have to decide which SPI are you going to use and you have to configure it accordingly. When you are decided then you have to change the hardware layer of FatFS and you are good to go. By changing the hardware layer you tell FatFS which interface it should use to communicate with the SD card. Those functions were enumerated in my blog post.
The usage is very simple and straightforward. First you initialize the SPI and you can use the FatFS functions. There is an example in one of the updates to this post.
Thank you so much for responding. Forgive me, but I am a beginner. Could you tell me exactly what i would have to initialize in the main code, or could you direct me to the blogspot where this has already been explained?
How do I change this hardware layer of FatFs as well. I am checking CubeMX but I am not able to find anything of that sort.
Just to clarify one thing. If you are using my code you should not configure FatFS in CubeMX. The CubeMX version is using SDIO, the hardware layer presented here is using SPI. If that is cleared out then you let’s go to the SPI initialization. You should initialize SPI as Master with FullDuplex, 8 bits, MSB first, low clock polarity and data transfer on rising edge and that’s all. As for the baudrate you can set it for around 10Mbit/s for starters.
After above is configured then you can adjust the hardware layer for FatFS files with your specific SPI (sd_stm32.c/h files). Finally, you add your logic to the program (FatFS functions).
Hi,
Could you tell me what I should exactly do… I skip FatFs configuration in CubeMX, I attached your *.c and *.h file to my software. I add FatFs Library directly from they website but I have lot of problem with compiling of the code. At the end I need mention that I configure SPI interface according your advice. Maybe I don’t catch everything. Thank you in advance for your support.
Solving this via comments would be cumbersome. Please send me an e-mail e.g. via contact form.
Thank you so much for writing this driver. Can you publish a small example using FreeRTOS + FatFS?
If I find some time I will publish an example. Using FatFS with FreeRTOS is not different than using FatFS without it. Important thing is to prevent interrupts disrupting write on SD card.
Hi,
It seems that attached download links, not work!
Please check them.
Thanks
I have checked them. Also I have received information that there was a problem with the server and maybe it have caused the problem. Please try again, it should work just fine, now.
Thanks
They are working now!
But another problem: “../Inc/ff.h(96): error: #20: identifier “osSemaphoreId” is undefined”
Could you please help me?!!
From what I can see you are using FatFS from Cube with the CMSIS RTOS also from Cube. Giving one line of code is not enough. It says that a semaphore named osSemaphoreId is undefined. I guess that you are missing headers in ff.h header file.
The hardware layer I’ve prepared is for SPI and it works with Cube but requires some minor alterations.
Hi
Is it possible for you to upload your spi.h file which included in sd_stm32.c file?
Thanks
I do not have an example which only is using SD card. However, in one of the comments I have pointed out how the SPI should be configured. I think this is what you are after, isn’t it. Also I will add this description to the body of the post so it would be easy to find.
Hey,
which version of FatFS is working with your code?
The hardware layer works with FatFS 0.11. I haven’t checked it with newer versions but it should work.
Hi
Why do not work this code sample in stm32f401????
char buffer[128];
static FATFS g_sFatFs;
FRESULT fresult;
FIL file;
int len;
WORD bytes_written;
//mount SD card
fresult = f_mount(0, &g_sFatFs);
//open file on SD card
fresult = f_open(&file, „file.txt”, FA_OPEN_ALWAYS | FA_WRITE);
//go to the end of the file
fresult = f_lseek(&file, file.fsize);
//generate some string
len = sprintf( buffer, „Hello World!\r\n”);
//write data to the file
fresult = f_write(&file, buffer, n, &bytes_written);
//close file
fresult = f_close (&file);
Could you give some more information? It doesn’t compile or it does not write data to SD card? Have you set up the FatFS for your uC?
Hi,
I’m using the STM32f103c8t6 to save the value of an analog port into the SD. i finally succeed on doing this, but the max frequency that i could save was about 20hz. how can i make this number bigger?
i’m using the tim interrupt for saving the data, every 50ms i mount, open, write and close the file. but when i try to save faster the program just doesnt work anymore.
Probably it hangs on remounting the SD card. However, debugging it would be the best option to see what is happening.
Even though, I would not recommend mounting, opening and closing the file in a cycle. Mount the SD card once, and open the file. Write data to the file periodically. This should solve your problem. Although, there may be one issue with actually storing the data to the SD card. You should occasionally call f_sync() for syncing the cashed data to the SD card. This way if the system crashes you will not lose all data but only a part of it. You can read about it here
f_sync
Hello, Pedro. I’m using the same hardware (stm32f103c8t6) but I’m having some trouble and it doesn’t work. Could you share your code? It would be very helpfull. Thanks a lot!
If I don’t configure FatFS on CubeMX, how can I use de functions f_*()? I used the CubeMX to configure the spi2 and the GPIO_Output as CS. After this, I added your drivers sd_stm32 .c in scr and .h in inc. Now what should I do? I need diskio.h and ff.h files, right? I’m having some trouble with this. I’m using stm32f103c8t6. Thank you a lot!!!
All you need to do is to download FatFS and put it into your project. You can find FatFS at this site
Unfortunately, I do not have a stand alone example for FatFS.
Oh great, thank you a lot! One last question: if I want to write a .txt file in my uSD card, in my main function should I call f_*() functions, like f_write(), or some function from your sd_stm32 driver? I just don’t get it how to use your driver in my main function.
Congratulations about your blog by the way, it’s really amazing!
Yes, you only use FatFS specific functions, those starting with f_ prefix. The driver is invisible, it is only a hardware abstraction layer which in fact is used by the FatFS itself.
Thank you! I’m glad you like it.
What should I do when I get “Error: unknown type BYTE”. The line #define bool BYTE is there, I don’t know why this error keep showing up.
The definition is actually using BYTE type to create a new way of referencing to it, there it is bool. The error is showing up because you do not have the definition of BYTE type itself. I believe it is in FatFS header files. Give it a try and you will find it in no time.
I just had to include ff.h before diskio.h and ir solved. Now I only have one error left: “Symbol disk_read multiply defined (by diskio.o and sd_stm32.o)”. I have never faced and error related to .o files before, so I don’t know how to fix it. Any suggestions?
You should not “include” the diskio.c. Now, you have two strong definitions of the same functions, e.g. disk_read(), and this is the reason why linker is showing you this error. You should decide which implementation you would like to use. This from diskio.c or from sd_stm32.c. Basically, you should exclude one of these files from compilation or at least from linking stage.
Now the code compiled okay, thank you! Unfortunately it seems that your simple code of Update 3 isn’t working. The function f_mount() “is expected to have 3 arguments, not 2”. Could you help me with this final issue? I just need a code to write a (.txt). I’m sorry for asking you so many questions.
Thank you a lot!
Thanks! You are right. I have fixed the example. It should be
fresult = f_mount(&g_sFatFs, "0", 1);
If you want it to be force mounted (last argument to the f_mount() function).
Hi,
First thanks for sharing this code it helps me a lot to understand how sd cards are working and how to use them with the stm32f1.
I tried to use your code with a 16Gb sdhc card and found that the disk_initialize function failed. I checked with an osciloscope that I got data through spi.
Apparently my sd card is returning 1 to this command send_cmd(CMD58, 0) which caused the function to fail.
At the bottom of this page https://openlabpro.com/guide/interfacing-microcontrollers-with-sd-card/ the state diagram shows that CSS can be 1 for SD version 2 block address.
Unfortunately I didn’t find anything relevant on CSS and if it changes the way the code was written. I will continue to investigate but if you have any clues I would be glad that you can help me.
One more time thanks for sharing!
I found my problem it was due to a error in the time management. Just to debug I removed the Timer1 variable in do-while loops of the disk_read and disk_init functions and it works perfectly now.
Thank you for the information. However, removing the Timer1 variable from the code is not recommended. It ensures the proper work of timeouts. Just to clarify one thing. Do you call disk_timerproc() periodically (every 10 [ms])? This actually decrements the Timer1 and Timer2 values so the timeouts work properly. You can put a call to disk_timerproc() in SysTick callback (by default is is called every 1 [ms]) and ensure it is processed every 10 [ms].
Thank you for the information.Is there any example related to audio file player from SD card with STM32F103C8T6??
Yes, I believe there are some examples. However, I do not have one. Although, the simplest way would be to save an audio file in WAV format since it is very straightforward and play it via DAC.
Hi I am facing issue with power cycle. While it working if I do a reset afterword. Help me…..thx
Hi, I have a problem with one line of sample code:
//go to the end of the file
fresult = f_lseek(&file, file.fsize);
Builder return “‘FIL’ has no member named ‘fsize'”.
Could you help me?
Thank you in advance