The time has come that I finally need to look into getting SD cards to work with the stm32. Looking online there seems to be a few really great resources for connecting to a SD card over spi from a smaller lower power however I could not find any guides for the STM32 line of chips.
I am currently working using the HAL libraries as this allows for flexibility in working with different chips (in theory!). This does come at a cost of performance, however I have found the cost to be fairly low compared to being able to have already setup functions for most things.
Most of the STM chips include a SDIO interface, which is fantastic and really fast (and comes with HAL drivers), which works really really well… If you only want to use one sd card with your system. SD cards do not support being shared on the SDIO interface, unlike mmc memories which you can give addresses to and share the bus. Online there appears to be speculation on wether you can share SD cards by switching the CMD line between multiple cards, which in theory would work. However I did not go down the route, mostly because on the IC I eventually plan to use (F4 or F7 in TQFP) the possible CMD pin can only be mapped to opposing sides of the IC, which just doesnt work well.
The connections to the SD cards are exactly the same as when working with any other microcontroller out there, as the stm is 3.3V logic no level converters are required.
Dual Breakout Board
I designed up a dual micro SD card holder in kicad, which just works as a nice breakout for two of the really common online holders for the cards. I prefer this style as they are cheap, and have easy to hand solder pins (Why reflow when you dont have to!).
The basic idea behind talking to a SD card via SPI is fairly simple, we first setup the card so that it knows it is talking in SPI and not SDIO. From there we can read/write the data in 512 byte blocks (sectors), and the card will manage all the erase operations as required. For this implimentation I am mostly looking at the ability to read the card, so I can avoid the need to support bulk read/write operations for larger blocks.
I expose a very small set of functions about the card (Read,Write getSize). This keeps the required code small and easy to understand, and makes debugging easier. The downside to this is performance, before each operation the card needs time to setup the internal state machine. Normally in SDIO mode the clock runs continiously so the card’s state machine always has clock cycles to work with for setup, however in SPI we only send the clock when we are sending data, so the card needs some to get started.
Huge Big Warning
When I initially wrote this code I kept coming across issues where the card would return invalid responses (ie the msb was set to 1) or it would ignore my commands completely, or reset to being uninitalized. If you run across this issue there are a few key things to check
- You must provide some idle clocks to the chip before the command. This is easily done by clocking 0xFF into the chip until it returns 0xFF back at you
- Before you ask the chip to come out of idle state, give it at least 80 clocks with CS high (ie deselected)
- Whenever you clock data out of the chip, make sure you send it 0xFF!!
This last one is vitally important. Seriously. It is not documented anywhere that I could find that you must send idle state (high) whenever you clock out data. It appears the card tries to interpret the incoming data even though it is currently sending data out. This makes sense as you can send a CMD12 while the unit is clocking out multiple blocks to abort the transfer. This however was not an issue i noticed until after i killed a sd card.
Yes, clocking in random data can kill a card. The card still enumerates,but crashes as soon as you try to read from its memory….
The HAL Libraries
When ST made the HAL libraries, they took a shortcut when they coded the function for recieving data from another device. In SPI when you want to clock out data while you clock in data for a transfer. When ST coded the library, instead of just clocking out 0x00 or 0xFF as is usually done, instead their code clocks out whatever was in the array your reading into before hand. So in my case I was throwing uninitalized data out the SPI port at the SD card and no wonder it would randomly lock up. To combat this I have included a custom SPI Recieve command that does not do this, and instead just passes 0xFF out the port.
The final code
The final code is really what you would expect if you looked at any other online tutorial so I will not go into heavy details, and really most of the information you would like can be found in the specifications online.
The basic concept is:
- Create the object and pass in a pointer to the spi port and the CS pins details (pin number and port)
- Call the initalize function to start the card up and get it out of idle
Then you can use the following 3 commands to interact with the card:
- getSize() -> Returns the size of the card as a multiple of 512b blocks (aka sector count)
- readBlock() -> Reads the given sector off the disk into the given array
- writeBlock() -> Writes the given data buffer into a sector on the disk
These functions link really easily with most FAT implimentations as they are almost always built around reading/writing indivdiual sectors from the disk.
In the next section I will cover setting up the USB link so that this sd card can be passed through over usb to an operating system so you can build the slowest sd card reader ever (I get around 400kb/sec).