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.

Connections

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!).

PCB Pre Assembly

Courtesy of OSH Park, naturally

Assembled Breakout

Assembled

Logic

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
  • Celebrate

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.

/*
 * DualSD.cpp
 *
 *  Created on: 1 Nov 2016
 *      Author: ralim
 */

#include <SDCard.hpp>

SDCard::SDCard(SPI_HandleTypeDef* hspi, uint16_t cs1Pin,
		GPIO_TypeDef* cs1Port) {
	_spi = hspi;
	_cs1Pin = cs1Pin;
	_cs1Port = cs1Port;
	_sdSize = 0;
}

uint32_t SDCard::getSize() {
	if (_sdSize == 0) {
		//We have not read the disk size just yet :O
		csd_t csd;
		if (!readCSD(&csd))
			return 0;
		if (csd.v1.csd_ver == 0) {
			uint8_t read_bl_len = csd.v1.read_bl_len;
			uint16_t c_size = (csd.v1.c_size_high << 10)
					| (csd.v1.c_size_mid << 2) | csd.v1.c_size_low;
			uint8_t c_size_mult = (csd.v1.c_size_mult_high << 1)
					| csd.v1.c_size_mult_low;
			c_size = (uint32_t) (c_size + 1) << (c_size_mult + read_bl_len - 7);
			c_size /= 1024;
		} else if (csd.v2.csd_ver == 1) {
			uint32_t c_size = ((uint32_t) csd.v2.c_size_high << 16)
					| (csd.v2.c_size_mid << 8) | csd.v2.c_size_low;
			c_size *= 1024;
			_sdSize = c_size;

		} else {
		}
	}
	return _sdSize;
}
/** read CID or CSR register */
uint8_t SDCard::readRegister(uint8_t cmd, void* buf) {
	uint8_t* dst = reinterpret_cast<uint8_t*>(buf);
	if (cardCommand(cmd, 0)) {
		deselectCard();
		return false;
	}
	uint8_t temp = 0xFF;
	while (temp == 0xFF) {
		SPI_Recieve(&temp, 1);
	}
	// transfer data
	SPI_Recieve(dst, 16);
	SPI_Recieve(&temp, 1); //CRC1
	SPI_Recieve(&temp, 1); //CRC2
	deselectCard();
	return true;
}
bool SDCard::readBlock(uint32_t blockaddr, uint8_t* buffer) {
	if (cardCommand(CMD17, blockaddr)) {
		/*
		 * Error
		 */
		deselectCard();
		return false;
	}
	uint8_t temp = 0xFF;
	while (temp == 0xFF) {
		HAL_SPI_Receive(_spi, &temp, 1, 100);
	}

	SPI_Recieve(buffer, 512);
	//eat the CRC
	temp = 0xFF;
	SPI_Recieve(&temp, 1);
	temp = 0xFF;
	SPI_Recieve(&temp, 1);
	deselectCard();
	return true;
}

bool SDCard::writeBlock(uint32_t blockaddr, uint8_t* buffer) {
	//The cardCommand will select the card so we have to make sure we clean up
	if (cardCommand(CMD24, blockaddr)) {
		/*
		 * Error
		 */
		deselectCard();
		return false;
	}
	/*
	 * Write the data
	 */
	uint8_t temp = DATA_START_BLOCK;
	HAL_SPI_Transmit(_spi, &temp, 1, 100);
	HAL_SPI_Transmit(_spi, buffer, 512, 100);
	temp = 0xFF;
	HAL_SPI_Transmit(_spi, &temp, 1, 100);
	HAL_SPI_Transmit(_spi, &temp, 1, 100);
	//read response
	SPI_Recieve(&temp, 1);
	if ((temp & DATA_RES_MASK) != DATA_RES_ACCEPTED) {
		/*
		 * Error
		 */
		deselectCard();
		return false;
	}
	// wait for flash programming to complete
	waitUntilReady();

	// response is r2 so get and check two bytes for nonzero
	if (cardCommand(CMD13, 0)) {
		/*
		 * Error
		 */
		deselectCard();
		return false;
	}
	SPI_Recieve(&temp, 1);
	if (temp) {
		/*
		 * Error
		 */
		deselectCard();
		return false;
	}
	deselectCard();
	return true;
}

void SDCard::waitUntilReady() {
	uint8_t ans[1] = { 0 };
	while (ans[0] != 0xFF) {
		SPI_Recieve(ans, 1);
	}
}

bool SDCard::initalize() {
	_spi->Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_256; //slow down at first
	HAL_SPI_Init(_spi); //apply the speed change
	deselectCard();
//We must supply at least 74 clocks with CS high
	uint8_t buffer[4] = { 0xFF, 0xFF, 0xFF, 0xFF };
	HAL_SPI_Transmit(_spi, buffer, 4, 100);
	HAL_SPI_Transmit(_spi, buffer, 4, 100);
	HAL_SPI_Transmit(_spi, buffer, 4, 100);
	HAL_Delay(5);
	selectCard();
	uint8_t status;
	// command to go idle in SPI mode
	while ((status = cardCommand(CMD0, 0)) != R1_IDLE_STATE) {

	}
	// check SD version
	if ((cardCommand(CMD8, 0x1AA) & R1_ILLEGAL_COMMAND)) {
		deselectCard();
		return false; //Unsupported
	} else {
		// only need last byte of r7 response
		HAL_SPI_Receive(_spi, buffer, 4, 100);
		if (buffer[3] != 0XAA) {
			return false; //failed check
		}

	}
	// initialize card and send host supports SDHC
	while ((status = cardAcmd(ACMD41, 0X40000000)) != R1_READY_STATE) {

	}
	// if SD2 read OCR register to check for SDHC card
	if (cardCommand(CMD58, 0)) {
		deselectCard();
		return false;
	}
	//discard OCR reg

	SPI_Recieve(buffer, 4);
	deselectCard();
	_spi->Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4; //speed back up
	HAL_SPI_Init(_spi); //apply the speed change
	return true;
}

uint8_t SDCard::cardCommand(uint8_t command, uint32_t arg) {
	uint8_t res = 0xFF;
	/*HAL_SPI_Transmit(_spi, &res, 1, 100);
	 HAL_SPI_Transmit(_spi, &res, 1, 100);
	 HAL_SPI_Transmit(_spi, &res, 1, 100);
	 HAL_SPI_Transmit(_spi, &res, 1, 100);
	 HAL_SPI_Transmit(_spi, &res, 1, 100);
	 HAL_SPI_Transmit(_spi, &res, 1, 100);
	 HAL_SPI_Transmit(_spi, &res, 1, 100);*/

	selectCard();
	waitUntilReady(); //wait for card to no longer be busy
	uint8_t commandSequence[] = { (uint8_t) (command | 0x40), (uint8_t) (arg
			>> 24), (uint8_t) (arg >> 16), (uint8_t) (arg >> 8), (uint8_t) (arg
			& 0xFF), 0xFF };
	if (command == CMD0)
		commandSequence[5] = 0x95;
	else if (command == CMD8)
		commandSequence[5] = 0x87;
	HAL_SPI_Transmit(_spi, commandSequence, 6, 100);
	//Data sent, now await Response
	uint8_t count = 20;
	while ((res & 0x80) && count) {
		SPI_Recieve(&res, 1);
		count--;
	}
	return res;
}

void SDCard::selectCard() {
	HAL_GPIO_WritePin(_cs1Port, _cs1Pin, GPIO_PIN_RESET);
}

HAL_StatusTypeDef SDCard::SPI_Recieve(uint8_t* pData, uint16_t Size) {

	HAL_StatusTypeDef errorcode = HAL_OK;

	/* Process Locked */
	__HAL_LOCK(_spi);

	/* Don't overwrite in case of HAL_SPI_STATE_BUSY_RX */
	if (_spi->State == HAL_SPI_STATE_READY) {
		_spi->State = HAL_SPI_STATE_BUSY_TX_RX;
	}

	/* Set the transaction information */
	_spi->ErrorCode = HAL_SPI_ERROR_NONE;
	_spi->pRxBuffPtr = (uint8_t *) pData;
	_spi->RxXferCount = Size;
	_spi->RxXferSize = Size;
	_spi->pTxBuffPtr = (uint8_t *) pData;
	_spi->TxXferCount = Size;
	_spi->TxXferSize = Size;

	/*Init field not used in handle to zero */
	_spi->RxISR = NULL;
	_spi->TxISR = NULL;
	/* Check if the SPI is already enabled */
	if ((_spi->Instance->CR1 & SPI_CR1_SPE) != SPI_CR1_SPE) {
		/* Enable SPI peripheral */
		__HAL_SPI_ENABLE(_spi);
	}
	/* Transmit and Receive data in 8 Bit mode */
	while ((_spi->RxXferCount > 0U)) {
		*(__IO uint8_t *) &_spi->Instance->DR = 0xFF; //send data
		while (!(__HAL_SPI_GET_FLAG(_spi, SPI_FLAG_TXE)))
			;
		while (!(__HAL_SPI_GET_FLAG(_spi, SPI_FLAG_RXNE)))
			;
		(*(uint8_t *) pData++) = _spi->Instance->DR;
		_spi->RxXferCount--;
	}

	if (lSPI_WaitFlagStateUntilTimeout(_spi, SPI_FLAG_BSY, RESET, 100,
			HAL_GetTick()) != HAL_OK) {
		_spi->ErrorCode |= HAL_SPI_ERROR_FLAG;

		errorcode = HAL_TIMEOUT;
	}

	_spi->State = HAL_SPI_STATE_READY;
	__HAL_UNLOCK(_spi);
	return errorcode;
}

void SDCard::deselectCard() {
	HAL_GPIO_WritePin(_cs1Port, _cs1Pin, GPIO_PIN_SET);
}

/**
 * @brief Handle SPI Communication Timeout.
 * @param hspi: pointer to a SPI_HandleTypeDef structure that contains
 *              the configuration information for SPI module.
 * @param Flag: SPI flag to check
 * @param State: flag state to check
 * @param Timeout: Timeout duration
 * @param Tickstart: tick start value
 * @retval HAL status
 */
HAL_StatusTypeDef SDCard::lSPI_WaitFlagStateUntilTimeout(
		SPI_HandleTypeDef *hspi, uint32_t Flag, uint32_t State,
		uint32_t Timeout, uint32_t Tickstart) {
	while ((hspi->Instance->SR & Flag) != State) {
		if (Timeout != HAL_MAX_DELAY) {
			if ((Timeout == 0U) || ((HAL_GetTick() - Tickstart) >= Timeout)) {
				/* Disable the SPI and reset the CRC: the CRC value should be cleared
				 on both master and slave sides in order to resynchronize the master
				 and slave for their respective CRC calculation */

				/* Disable TXE, RXNE and ERR interrupts for the interrupt process */
				__HAL_SPI_DISABLE_IT(hspi,
						(SPI_IT_TXE | SPI_IT_RXNE | SPI_IT_ERR));

				if ((hspi->Init.Mode == SPI_MODE_MASTER)
						&& ((hspi->Init.Direction == SPI_DIRECTION_1LINE)
								|| (hspi->Init.Direction
										== SPI_DIRECTION_2LINES_RXONLY))) {
					/* Disable SPI peripheral */
					__HAL_SPI_DISABLE(hspi);
				}

				/* Reset CRC Calculation */
				if (hspi->Init.CRCCalculation == SPI_CRCCALCULATION_ENABLE) {
					SPI_RESET_CRC(hspi);
				}

				hspi->State = HAL_SPI_STATE_READY;

				/* Process Unlocked */
				__HAL_UNLOCK(hspi);

				return HAL_TIMEOUT;
			}
		}
	}

	return HAL_OK;
}