Part 9 – Accessing the SD card
A computer’s not much use without a way to load data onto it, so the latest aspect of this project has been getting the SD card slot doing something useful.
SD cards have more than one way of accessing them – they have a “native” protocol, and then an SPI mode. While the native mode can provide better performance, an SPI host is a built-in feature of many microcontrollers, and documentation is easier to come by. The Minimig project uses the SD card in SPI mode, and since I’m using that project for reference wherever I’m finding gaps in my own understanding, I’ve used SPI as well!
At one time it was nearly impossible for a hobbyist to obtain official SD card specifications – thankfully the situation is much improved now, and a simplified specification is now available for free download at https://www.sdcard.org/downloads/pls/
Another useful page for reference is this: http://elm-chan.org/docs/mmc/mmc_e.html
One thing that confused me to start with is that to invoke “CMD0” we have to send 0x40 to the SD card, while “CMD1” ends up being 0x41, and so on. This is because the SD native protocol employs a ‘0’ start bit, then a ‘1’ transmission bit – and these are retained even in SPI mode.
Another peculiarity of SPI is that communication is always bidirectional; the host provides 8 clocks, and eight bits of data are sent in each direction. This means that in order to read responses from the card, the host must write a dummy byte for each byte it wishes to receive.
For the MiniSOC project, I’ve added some extra hardware registers in the peripheral controller at base 0x810000
0x20: SD On read, returns the data received during the previous write operation. On write, causes the byte written to the low 8 bits to be clocked out, and data from the card to be clocked in. Note: this is asynchronous, so it's important to check that any previous write has finished! 0x22 SD_CS Read: bit 15 indicates whether the SPI host is busy performing a transfer Write: bit 0 sets the chip select line of the SD card. 0x24 - SD_Blocking. This is equivalent to 0x20 except that both reads and writes will incur wait states until any previous transfer has completed, eliminating the need to poll 0x22. 0x100 onwards: This area is deliberately incompletely decoded, so reads from anywhere within the region will have the same effect. Unlike both 0x20 and 0x24, reading from these registers will trigger a new transfer, sending sixteen bits of 0xffff and receiving 16 bits back from the SD card. The point of this is to allow driver code to use constructs like this: ; set up a block read command, then... lea 0x810100,a0 lea sector_buffer,a1 move.l #15,d7 move.w (a0),d0 ; pump the first 16 bits. .loop movem.l (a0),d0-6/a2 ; pump 64 bits in one command! movem.l d0-6/a2,(a1) add.l #32,a1 dbf d7,.loop This is much faster than receiving data a single byte at a time.
Full source and binaries can be found here, for anyone that might be interested.
To try this out, you’ll need (a) an Altera DE1 board, (b) HyperTerminal or (preferably) something similar but faster, and (c) an SD card containing the file “Test.img” from the Misc directory in the archive.
Use Quartus to program the DE1 with the .sof file.
Use HyperTerminal or similar at 115200 baud, 8N1, to send the file “out.srec” from the CFirmware directory in the archive. If everything goes to plan, the MiniSOC should load the image, display it on screen, then scroll up and down as before.
One of the next tasks will be to bootstrap directly from the SD card, eliminating the need for serial bootstrapping.