The other night I was able to get my STM32F4DISCOVERY board to read and write files on an SD card formatted with the FAT filesystem. While I haven't done extensive testing of my code, I'm comfortable enough to share what I have with the world. Writing to a filesystem is obviously a convenient feature in many projects. Here's how you can do it with an STM32F4 processor. What follows is the long story; you can find my code at the end of the post.
I started with FatFS, which is an open-source FAT filesystem library that's been on the web for quite a while now. FatFS is written in ANSI C and is a portable library. Through preprocessor #define statements, you can choose at compile time whether to include the ability to allow filesystem writes (as opposed to mounting a read-only filesystem), allow formatting of a disk, allow the use of long filenames, and allow re-entrant code. (Each of these options increases the code size, of course.) The code size on a Thumb-2 instruction set Cortex M3 processor varies from 2,295 to 6,617 bytes, according to the author.
FatFS handles the logical filesystem layer. The implementation must provide a physical layer to interface with the media. When using FatFS, the user must provide only six functions:
- disk_initialize to initialize the hardware
- disk_status to get disk status
- disk_read to read sectors from the media
- disk_write to write sectors to the media
- get_fattime to get a current timestamp when creating a file
Clearly, the workhorse functions are the disk_read, disk_write, and disk_initialize functions. disk_initialize has to prepare the hardware and media for access. disk_status only has to determine if media is present, if it's write protected, and if it's been initialized. The get_fattime function can return a constant if you'd like. If you're not using functions to format or partition a disk, the only thing that disk_ioctl has to do is ensure write buffers are flushed before returning.
FatFs will allow the implementation of a FAT filesystem on any physical media that you can coerce into that six-function interface.
SD memory cards are an obvious media choice here. (The other one that comes to mind is a USB thumb drive. A large EEPROM chip might also work but an implementation might have some challenges with the erase block size.) SD cards support two interfaces: the native SD interface and a one-bit SPI interface. Most of the hobbyist/hacker applications you will see on the web use the SD card in SPI mode because the SPI interface is pretty universal and was the only interface that was publicly documented by the SD Association for a while. The downside is that the SPI interface is significantly slower than the native SD interface. There seems to be no guarantee on how fast an SD card can be clocked in SPI mode, whereas the card's rated speed is guaranteed in SD mode. Also, in SD mode, data gets clocked in/out 4 bits at a time instead of 1 bit at a time in SPI mode. So even assuming that SPI mode will work at the SD card's rated frequency, it will still be at most one-fourth as fast. Since the STM32F4 parts have built-in SDIO controllers, I elected to use that peripheral. (STM32F0 and STM32F3 parts do not have the SDIO controller from what I can determine. The STM32F2 parts do, but I don't have any F2 eval boards to test with.)
The challenge, then, is to write a physical interface layer to the SDIO peripheral that exposes the six functions listed above. Thankfully, ST has provided sample code in the Standard Peripheral Library download. The STM32F4xx_StdPeriph_Examples folder contains an SDIO folder with a sample project complete with a readme.txt. You'll find that the bulk of the useful code is in stm324xg_eval_sdio.c. The first three pages of this file is a comment block explaining how to use the sample code. (I highly recommend reading these comments.) The sample code exposes the following functions (among others):
It sounds like marrying these two libraries should be a piece of cake, doesn't it?
The reality actually isn't too far from the promise. I did have a number of intermittent issues trying to get things working, but a large part of that is just because I don't know what the hell I'm doing. (Remember kids, just because I have a blog doesn't make me an expert!) There is some setup required. The library uses the DMA controller to get data into and out of the SDIO controller, and interrupt and DMA completion routines have to be connected. Slowing down the SD clock speed by changing the divisor in stm324xg_eval.h is necessary when using long connecting wires (the constant to change is SDIO_TRANSFER_CLK_DIV).
Also, after calling SD_ReadMultiBlock() or SD_WriteMultiBlock, you have to wait for the DMA transfer to complete as mentioned in that long comment block. (This seems silly to me as it defeats the entire purpose of DMA. I suspect this can be optimized by waiting on the previous DMA to finish before the next read or write, but I haven't tested this yet.)
I spent several days playing with this code and having intermittent and frustrating results. I did some online searching and found this blog post by Lukasz Iwaszkiewicz. I downloaded the author's code and was delighted that with his code I could successfully read and write sectors on the SD card "out of the box," and that I could verify that by taking the card to my Linux laptop and doing a hexdump of the first few sectors.
I have no idea what differences, if any, there are between Lukasz' code and the sample code from ST. I got to a point where I was frustrated and Lukasz' code worked for me, so I used it. (I am reposting his code here with his permission.)
Emboldened, I grafted this code to FatFs and was able to read and write files. Given the amount of time I spent figuring this out, and the usefulness of this functionality, I thought I'd share.
So, let's build this project together! First, the hardware. In addition to the STM32F4DISCOVERY board, you're going to need an SD Card breakout board such as this one. If you get a breakout board somewhere else, be sure that it breaks out the D0-D3 pins needed by the SD interface and that it doesn't just break out the pins needed by the SPI interface. Connect it to the STM32F4DISCOVERY like so:
SD Card STM32F4
D2 (N/C for SPI) PC10
D3 (or CS#) PC11
CMD (or MOSI) PD2
CD Card detect signal; not used in this demo.
CLK (or SCK) PC12
D0 (or MISO) PC8
D1 (or IRQ) PC9
WP Write protect signal; not used in this demo.
Next, set up your dev environment for the STM32F4DISCOVERY board. Be sure to change the HSE_VALUE to 8MHz in system_stm32f4xx.c as described in my previous post. Once that's done, copy your template project to a new project named SDIO_Test.
Let's first get the low-level interface to the SDIO card working. In the /src folder, create a subfolder called SDIO. Download Lukasz' code from his Subversion repository on Google Code. From his repository, copy the following files into your /src/SDIO folder:
Back in Eclipse, refresh the contents of this folder in the Project Explorer.
Delete your main.c from the root of your /src folder and put Lukasz' version in its place. Refresh the folder in Project Explorer.
In your project's properties, add an Include path to /src/SDIO. In main.c, remove the #include to "simplesdio.h" (the referenced file is nothing but comments).
The SDIO sample code requires that an interrupt handler and a DMA completion routine be present in the project. Open stm32f4xx_it.c and add the following two functions:
Lastly, you'll want to verify that the system_stm32f4xx.c is built to your preferences. This file configures all of the clocks on the chip. (See my previous post on this topic.)
Find an SD card on which you don't mind destroying data. Pop it into your desktop (or laptop) computer and dump out the first sector of the card. On Linux, this can be done by typing:
hexdump -n 512 /dev/sdc
(Of course, substitute the correct path to your SD card for /dev/sdc.) On Windows, you'll need to download a disk sector editor. I used dskprobe.exe back in the day when I was working for MSFT, but I don't even know if that tool is still available for download. Winhex claims the ability to edit disk sectors but I have not tested this tool.
In any case, read the first sector from the SD card and save it off so you'll have a baseline against which to compare the same card after we've written to it.
Put the SD card into your SD card slot, build your project, upload it to the STM32F4DISCOVERY board, and run it. Step through the code in main() and watch return codes from functions and the contents of read and write buffers.
The only change I made to Lukasz' code was that I changed the value of SDIO_TRANSVER_CLOCK_DIV in sdio_low_level.h from 0x0 to 0x76. The clock divisor sets the clock rate for the card. The numerator is fixed at 48MHz, so a denominator of 0x76 yields a clock speed of 48MHz/(118 + 2) or 400kHz, which is the fastest allowable speed for initializing the SD card. Once the card is initialized the clock can go faster; see the comments in sdio_high_level.c for more information. Because I have my SD card attached to my dev board by long jumper wires, I embraced the safety of the lower clock speed for my testing.
In my next post we'll layer the FAT filesystem driver on top of the SDIO physical layer, and read and write actual files.
Code for this project is available on my SVN repository on Google Code at http://code.google.com/p/the-hacker-workshop/.