Работа с SD картой памяти по SDIO STM32 F4 Discovery | Blablacode

3 Стирание информации

Карты форм-фактора Micro SD поддерживают команды стирания информации. После команды стирания информации, значение указанных адресов для стирания будет заполнено значением 0xFF или 0x00, в зависимости от карты.

Алгоритм стирания информации

  1. Отправка команды CMD32. Команда устанавливает адрес первого блока для стирания информации.
  2. Ожидание ответа на команду CMD32. Ответом является ответ типа R1. В случае если ответ не равен 0x00 (произошла какая-либо ошибка при выполнении команды), переход к шагу 3, иначе к шагу 4.
  3. Состояние ошибки. Стирание завершилось неудачно, выход из алгоритма стирания информации.
  4. Отправка команды CMD33. Команда устанавливает адрес последнего блока для стирания информации. В случае если стереть нужно только один блок, адрес должен быть равен адресу из команды CMD32.
  5. Ожидание ответа на команду CMD33. Ответом является ответ типа R1. В случае если ответ не равен 0x00 (произошла какая-либо ошибка при выполнении команды), переход к шагу 3, иначе к шагу 6
  6. Отправка команды CMD38. Команда стереть информацию с выделенных блоков. В качестве аргумента должны быть отправлены любые 4 байта, кроме значений 0x00000001, 0x00000002.
  7. Ожидание ответа на команду CMD38. Ответом является ответ типа R1b. Это расширенная версия ответа, когда карта формирует ответ R1, а потом притягивает линию MISO к нулю, индицируя занятость микросхемы. Необходимо дождаться появления на линии MISO значения единицы. В случае если ответ не равен 0x00 (произошла какая-либо ошибка при выполнении команды), переход к шагу 3, иначе к шагу 8
  8. Завершение алгоритма стирания.

4 Чтение информации

Чтение информации с SD-карты по интерфейсу SPI возможно в двух вариантах.

4.1 Чтение одного блока данных

При чтении одного блока данных, Master устройство формирует для SD-карты команду на чтение одного блока данных, ожидает ответа, что команда обработана и ожидает пакета с данными от карты. После получения пакета с данными от карты, транзакция чтения заканчивается.

Работа с SD картой памяти по SDIO STM32 F4 Discovery | Blablacode
Общий вид транзакции чтения одного блока данных.

Первоначально был реализован именно такой вариант, но полученная скорость очень расстроила (сравнения скоростей будут ниже).

4.2 Чтение множества блоков данных

При чтении множества блоков данных, Master устройство формирует для SD-карты команду на чтение множества блоков данных, ожидает ответа, что команда обработана и ожидает пакета данными от карты. После отправления пакета с данными, карта отправляет следующий пакет с данными. Так будет продолжаться до тех пор, пока от Master устройства не поступит команда о завершении чтения.

Работа с SD картой памяти по SDIO STM32 F4 Discovery | Blablacode
Общий вид транзакции чтения множества блоков данных.Работа с SD картой памяти по SDIO STM32 F4 Discovery | Blablacode
Структура Data Packet

Где:


В случае, если при чтении произошла ошибка, карта вместо Data Token возвращает Error Token. Размер Error Token – 1 байт. Биты 7..5 содержат значение нуля, биты 4..0 кодируют тип ошибки.

Алгоритм чтения множества блоков данных:

  1. Отправка команды CMD18. Команда сообщает карте, что будет выполняться чтение множества блоков.
  2. Ожидание ответа на команду CMD18. Ответом является ответ типа R1. В случае если ответ не равен 0x00 (произошла какая-либо ошибка при выполнении команды), переход к шагу 3, иначе к шагу 4.
  3. Состояние ошибки. Чтение завершилось неудачно, выход из алгоритма чтения.
  4. Ожидание токена от карты. В случае если от карты получен Error Token, переход к шагу 3, иначе к шагу 5.
  5. Прием от карты блока данных размером 512 байт.
  6. Прием от карты поля CRC размером 2 байта.
  7. Проверка количества принятых блоков данных. Если принято необходимое количество блоков, переход к шагу 8, иначе к шагу 4.
  8. Отправка команды CMD12. Команда сообщает карте о необходимости остановки чтения. В случае, если в этот момент выполняется передача Data Packet, она будет прервана.
  9. Ожидание ответа на команду CMD12. Ответом является ответ типа R1b. Это расширенная версия ответа R1, в котором карта после формирования ответа притягивает линию MISO к нулю, индицируя занятость карты. Необходимо дождаться появления на линии MISO значения единицы, что будет свидетельствовать об окончании команды. В случае если ответ не равен 0x00 (произошла какая-либо ошибка при выполнении команды), переход к шагу 3, иначе к шагу 10.
  10. Завершение алгоритма чтения.

В алгоритме чтения есть небольшой нюанс. Сигнал выбора микросхемы (CS) должен быть установлен в состояние логического нуля перед формированием команды CMD18 и установлен в состояние логической единицы после получения ответа на команду CMD12.

5 Запись информации


Запись информации на SD-карту по интерфейсу SPI возможна в двух вариантах.

5.1 Запись одного блока данных

При записи одного блока данных, Master устройство формирует для SD-карты команду на запись одного блока данных, ожидает ответ от карты, что команда обработана, передает в карту пакет с данными для записи, ожидает от карты ответа, что данные записаны, завершает транзакцию.

Работа с SD картой памяти по SDIO STM32 F4 Discovery | Blablacode
Общий вид транзакции записи одного блока данных.

Как и с чтением, изначально была реализована запись одного блока данных. Результаты скорости получились неудовлетворительные.

Реализация алгоритма в аппаратуре

В результате прочтения спецификации, получаем некоторые характеристики алгоритма, которые необходимо реализовать в аппаратуре.

Возможные режимы работы:

  • Начальное формирование частоты. Линии CS и MOSI должны находиться в состоянии логической единицы.
  • Передача данных в SD-карту.
  • Прием данных от SD-карты.
  • Ожидание завершения команды. Используется, когда после формирования ответа SD-карта притягивает линию MISO к нулю, что бы дождаться появления единицы.
  • Чтение ответа при записи данных.
  • Ожидание токена при чтении данных.

Возможные режимы тактового сигнала:

  • Тактовый сигнал для инициализации.
  • Тактовый сигнал для работы.

С моей точки зрения, алгоритм оптимально реализовать с помощью трех компонентов:

  • Компонент физического уровня, который подключен непосредственно к SD-карте, формирует сигналы SCLK, CS, DI, читает с DO.
  • Компонент командного уровня, который осуществляет подготовку всех данных для компонента физического уровня.
  • Компонент общения с внешним миром, который скрывает всё внутренне устройство и предоставляет интерфейс для команд (чтение, запись, стирание) и данных.

1 Компонент физического уровня

entity SDPhy is
	generic	(	gTCQ		: time := 2 ns );
	port	(	-- Control bus
			iPhyTxData	: in	std_logic_vector( 9 downto 0);
			iPhyMode	: in	std_logic_vector( 4 downto 0);
			iPhyTxWrite	: in	std_logic;
			oPhyTxReady	: out	std_logic; 
			-- Out Data
			oPhyRxData	: out	std_logic_vector( 7 downto 0); 
			oPhyRxWrite	: out	std_logic;
			oPhyCmdEnd	: out	std_logic;
			-- Spi
			oSdCS		: out	std_logic;
			oSdClk		: out	std_logic;
			oSdMosi		: out	std_logic;
			oSdMosiT	: out	std_logic;
			iSdMiso		: in	std_logic;
			-- system
			sclk		: in	std_logic;
			pclk		: in	std_logic;
			rst		: in	std_logic ); 
end SDPhy;


Где:

  • iPhyTxData содержит данные для уровня, а iPhyMode содержит режим, как эти данные обрабатывать.
  • iPhyTxWrite показывает, в какой момент iPhyTxData и iPhyMode корректны.
  • oPhyTxReady показывает, когда компонент готов к приему данных. Фактически представляет собой выход FULL FIFO, используемого для синхронизации компонентов.
  • oPhyRxData данные и статус, прочитанные с SD-карты.
  • oPhyRxWrite показывает, в какой момент значение oPhyRxData корректно.
  • oPhyCmdEnd признак, что компонент завершил обработку команды.
  • oSdCS сигнал выбора микросхемы (CS) для SD-карты.
  • oSdClk тактовый сигнал для SD-карты.
  • oSdMosi линия передачи данных в SD-карту.
  • oSdMosiT линия управления буфером линии передачи данных в SD-карту.
  • iSdMiso линия приема данных от SD-карты.
  • sclk тактовый сигнал для работы с SD-картой (50 МГц).
  • pclk тактовый сигнал, на котором работает командный уровень.
  • rst сигнал сброса, активный уровень единица.

В ПЛИС существуют специальные блоки для работы с тактовым сигналом (PLL, MMCM), однако, получить с их выхода тактовый сигнал меньше 5 МГц проблематично. В результате физический уровень работает на частоте 50 МГц. Вместе с каждыми данными в сигнале iPhyMode поступает бит, который указывает, на какой частоте эти данные должны быть переданы в SD-карту (или приняты от нее). В зависимости от бита скорости формируются сигналы разрешения тактового сигнала (Clock enable).

В компоненте физического уровня реализованы два автомата, для передачи данных в SD-карту и для приема данных от нее.

Код автомата для передачи данных: github.

  • Состояние sDummy обеспечивает начальное формирование частоты, 128 переключений.
  • Состояние sTxBits обеспечивает передачу данных в SD-карту.


Код автомата для приема данных:

  • Состояние sRxBits обеспечивает прием данных от SD-карты.
  • Состояние sBusy обеспечивает ожидание готовности SD-карты (карта отпускает линию MISO к уровню единицы).
  • Состояние sResp реализует чтение ответа при записи данных.
  • Состояние sToken реализует ожидание токена при чтении данных.

2 Компонент командного уровня

entity SdCommand is
	generic	(	gTCQ		: time := 2 ns );
	port	(	-- Command from host
			oSdInitComp	: out	std_logic;
			oSdInitFail	: out	std_logic;
			iSdAddress	: in	std_logic_vector(31 downto 0);
			iSdStartErase	: in	std_logic;
			iSdStartRead	: in	std_logic;
			iSdStartWrite	: in	std_logic;
			oSdCmdFinish	: out	std_logic_vector( 1 downto 0);
			oSdhcPresent	: out	std_logic;
			-- Data
			oSdReadData	: out	std_logic;
			iSdDataR	: in	std_logic_vector(31 downto 0);
			oSdWriteData	: out	std_logic;
			oSdDataW	: out	std_logic_vector(32 downto 0);
			-- Spi
			oSdCS		: out	std_logic;
			oSdClk		: out	std_logic;
			oSdMosi		: out	std_logic;
			oSdMosiT	: out	std_logic;
			iSdMiso		: in	std_logic;
			-- system
			pclk		: in	std_logic;
			sclk		: in	std_logic;
			rst		: in	std_logic );

Где:

  • oSdInitComp признак завершение инициализации SD-карты.
  • oSdInitFail признак неудачного завершения инициализации.
  • iSdAddress адрес в SD-карте для выполнения команды.
  • iSdStartErase запуск выполнения команды стирания.
  • iSdStartRead запуск выполнения команды чтения.
  • iSdStartWrite запуск выполнения команды записи.
  • oSdCmdFinish статус завершения команды. Нулевой бит равен единице, команда завершена успешно. Первый бит равен единице, команда завершена с ошибкой.
  • oSdhcPresent признак обнаружения карты SDHC/SDXC.
  • oSdReadData чтение данных для записи в SD-карту.
  • iSdDataR данные для записи в SD-карту.
  • oSdWriteData признак записи данных, прочитанных с SD-карты.
  • oSdDataW данные, прочитанные с SD-карты.

Остальные сигналы совпадают с сигналами физического уровня.

В компоненте реализованы 5 автоматов.

:/>  Развертывание антивирусной защиты через Kaspersky Security Center

3 Компонент общения с внешним миром

entity SdHost is
	generic	(	gTCQ		: time := 2 ns );
	port	(	-- Sd Host command
			iSdCommand	: in	std_logic_vector( 2 downto 0);
			iSdAddress	: in	std_logic_vector(31 downto 0);
			iSdStart	: in	std_logic;
			oSdStatus	: out	std_logic_vector( 1 downto 0);
			oSdInitFail	: out	std_logic;
			-- Write data to card
			iSdTxData	: in	std_logic_vector(31 downto 0);
			iSdTxValid	: in	std_logic;
			iSdTxLast	: in	std_logic;
			oSdTxReady	: out	std_logic;
			-- Read data from card
			oSdRxData	: out	std_logic_vector(31 downto 0);
			oSdRxValid	: out	std_logic;
			oSdRxLast	: out	std_logic;
			iSdRxReady	: in	std_logic;
			-- Spi
			oSdCS		: out	std_logic;
			oSdClk		: out	std_logic;
			oSdMosi		: out	std_logic;
			oSdMosiT	: out	std_logic;
			iSdMiso		: in	std_logic;
			-- system
			pclk		: in	std_logic;
			sclk		: in	std_logic;
			rst		: in	std_logic );

Где:

  • iSdCommand код команды для выполнения.
  • iSdAddress адрес для выполнения команды.
  • iSdStart запуск выполнения команды.
  • oSdStatus статус завершения команды. Нулевой бит равен единице — команда завершена. Первый бит равен единице — команда завершена с ошибкой.
  • oSdInitFail признак неудачного завершения инициализации.
  • iSdTxData. Интерфейс Axi-Stream для записи данных в SD-карту. Порт с данными.
  • iSdTxValid. Интерфейс Axi-Stream для записи данных в SD-карту. Порт с сигналом записи.
  • iSdTxLast. Интерфейс Axi-Stream для записи данных в SD-карту. Порт с признаком последнего dw в данных.
  • oSdTxReady. Интерфейс Axi-Stream для записи данных в SD-карту. Порт с признаком готовности к приему данных.
  • oSdRxData. Интерфейс Axi-Stream для чтения данных из SD-карты. Порт с данными.
  • oSdRxValid. Интерфейс Axi-Stream для чтения данных из SD-карты. Порт с сигналом записи.
  • oSdRxLast. Интерфейс Axi-Stream для чтения данных из SD-карты. Порт с признаком последнего dw в данных.
  • iSdRxReady. Интерфейс Axi-Stream для чтения данных из SD-карты. Порт с признаком готовности к приему данных.

Остальные сигналы совпадают с сигналами физического уровня.

В компоненте реализован один автомат smSdControl (github).

  • Состояние sIdle. Ожидание завершения инициализации и команды на выполнение.
  • Состояние sWaitCmd. Проверка типа команды.
  • sReadCmd. Проверка места в FIFO, что поместится пакет, который будет прочитан из SD-карты и формирование сигнала запуска команды чтения.
  • sWriteCmd. Проверка, что в FIFO есть пакет для записи в SD-карту, и формирование сигнала запуска команды записи.
  • sEraseCmd. Формирование сигнала запуска команды стирания.
  • sWaitEnd. Ожидание завершения выполнения команды от командного уровня.
  • sFinish. Выход из автомата, команда выполнена.

Проверка в железе

Алгоритм написан, в симуляторе проверен. Нужно проверить теперь в железе. Из того, что имелось в наличии, подошла плата

На ней имеются свободные выводы ПЛИС в банке с напряжением 3,3В, к которому можно легко подключить внешнее устройство. Да еще и тип используемой ПЛИС — Zynq-7000, значит там есть процессорное ядро. Можно написать тест на языке С, что несколько упросит задачу тестирования.

Итак, подключаем реализованный алгоритм к процессорному ядру через порт GP (возможна работа по 4 байта, похоже на PIO). С прерываниями заморачиваться не будем, реализуем опрос по таймеру.

При работе на процессорном модуле алгоритм записи данных будет следующим:

  • Задать адрес в SD-карте.
  • Задать команду, код 2.
  • Записать данные в буфер, расположенный в программируемой логике.
  • Запустить выполнение команды.
  • Дождаться завершения выполнения команды.
  • Сбросить статус завершения команды.

Реализованный тест:

for (SectorAddress = 0; SectorAddress < 1048576; SectorAddress   )
{
	if ((SectorAddress % 1024) == 0)
	{
		xil_printf("Data write to %d sector nr", SectorAddress);
	}

	/** Set address */
	Xil_Out32(0x43c00008, SectorAddress);

	/** Set command */
	Xil_Out32(0x43c00004, 2);

	/** Write data to PL */
	for (int32_t i = 0; i < 1024; i  )
	{
		Xil_Out32(0x43c00014, cntrData);
		cntrData  ;
	}

	/** Start */
	Xil_Out32(0x43c00000, 1);

	/** Wait end of operation */
	for (;;)
	{
		status = Xil_In32(0x43c0000c);
		if (status == 0x01 || status == 0x03)
		{
			if (status == 0x03)
			{
				xil_printf("Error in write nr");
			}
			break;
		}
		else
		{
			cntrDuration  ;
			usleep(100);
		}
	}

	/** Duration operation */
	durationWrite  = cntrDuration;

	if (cntrDuration > MaxWrite )
	{
		MaxWrite = cntrDuration;
	}

	cntrDuration = 0x00;

	/** Clear start */
	Xil_Out32(0x43c00000, 0);

	SectorAddress  = 7;
}

На вопрос, почему в цикле используется внешняя граница 1024.

Размер одного блока равен 512 байт. Общий размер 8 блоков данных составляет 8 * 512 байт = 4096 байт. Шина между процессорным модулем и программируемой логикой имеет размер 4 байта. Получается, чтобы переслать из процессорного модуля в программируемую логику 4096 байт по 4 байта необходимо выполнить 4096 / 4 = 1024 операции записи.

При работе на процессорном модуле алгоритм чтения данных будет следующим:

  • Задать адрес в SD-карте.
  • Задать команду, код 1.
  • Запустить выполнение команды.
  • Дождаться завершения выполнения команды.
  • Сбросить статус завершения команды.
  • Прочитать данные из буфера в программируемой логике.

Реализованный тест:

for (SectorAddress = 0; SectorAddress < 1048576; SectorAddress  )
{
	if ((SectorAddress % 1024) == 0)
	{
		xil_printf("Data read from %d sector nr", SectorAddress);
	}

	/** Set address */
	Xil_Out32(0x43c00008, SectorAddress);

	/** Set command */
	Xil_Out32(0x43c00004, 1);

	/** Start */
	Xil_Out32(0x43c00000, 1);

	/** Wait end of operation */
	for (;;)
	{
		status = Xil_In32(0x43c0000c);
		if (status == 0x01 || status == 0x03)
		{
			 if (status == 0x03)
			{
				xil_printf("Error in read nr");
			}
			break;
		}
		else
		{
			cntrDuration  ;
			usleep(100);
		}
	}

	 /** Duration operation */
	 durationRead  = cntrDuration;

	 if (cntrDuration > MaxRead )
	 {
		 MaxRead = cntrDuration;
	 }

	cntrDuration = 0x00;

	/** Clear start */
	Xil_Out32(0x43c00000, 0);

	/** Read data from PL */
	for (int32_t i = 0; i < 1024; i  )
	{
		DataR = Xil_In32(0x43c0001c);
		if (DataR != cntrData)
		{
			xil_printf("Data corrupt! nr");
		}
		DataR = Xil_In32(0x43c00020);
		cntrData  ;
	}

	SectorAddress  = 7;
}

При работе на процессорном модуле алгоритм стирания данных будет следующим:

  • Задать адрес в SD-карте.
  • Задать команду, код 4.
  • Запустить выполнение команды.
  • Дождаться завершения выполнения команды.
  • Сбросить статус завершения команды.


Реализованный тест:

for (SectorAddress = 0; SectorAddress < 1048576; SectorAddress  )
{
	if ((SectorAddress % 1024) == 0)
	{
		xil_printf("Data erase from %d sector nr", SectorAddress);
	}

	/** Set address */
	Xil_Out32(0x43c00008, SectorAddress);

	/** Set command */
	Xil_Out32(0x43c00004, 4);

	/** Start */
	Xil_Out32(0x43c00000, 1);

	/** Wait end of operation */
	for (;;)
	{
		status = Xil_In32(0x43c0000c);
		if (status == 0x01 || status == 0x03)
		{
			if (status == 0x03)
			{
				xil_printf("Error in write! nr");
			}
			break;
		}
		else
		{
			cntrDuration  ;
			usleep(100);
		}
	}

	/** Duration operation */
	durationErase  = cntrDuration;

	if (cntrDuration > MaxErase )
	{
		MaxErase = cntrDuration;
	}

	cntrDuration = 0x00;

	/** Clear start */
	Xil_Out32(0x43c00000, 0);

	SectorAddress  = 7;
}

Результаты

Использовалась карта на 16 Гбайт. При тестировании было записано 2 Гбайта данных, прочитано 2 Гбайта данных, стерто 2 Гбайта данных.

Выводы неутешительны. При использовании FPGA нет смысла использовать SD-карту в режиме SPI, кроме того случая, когда очень нужно хранить большие объемы данных без предъявления требований к скорости.

Запись данных на карту


Итак, после успешной инициализации мы находимся в состоянии

tran

, во всяком случае, должны находиться. Смотрим по диаграмме: для того, чтобы перейти на состояние

rcv

, нужно послать команду CMD24 с адресом 512 байтной ячейки, которую хотим записать. Послали. Карта перешла в режим ожидания данных. Далее начинаем кидать ей информацию по шине данных, пока не перекинем все 512 байт или не пошлем команду CMD12 (стоп передачи). После завершения акта, карточка сама переедет в состояние

prg

и пробудет там некоторое время (пока данные запишутся). Ждем.… Как имено ждем? А посылаем ей в цикле CMD13 с адресом карты в аргументе, до тех пор, пока не вернется в отклике R1 типа статус

tran

. Когда это, наконец, случилось можно слать очередную пачку данных, вновь послав CMD24. Кроме того, существует еще режим записи несколькими блокам сразу (CMD25) и другие режимы – за подробностью – в спецификацию.

Ну, а теперь к протоколу!


Бывает несколько вариантов обмена информацией хост – карта.

1) Команды без данных.Все команды делятся на требующие и не требующие отклик.

Как видно из рисунка, если нам (хосту) нужно послать команду, не требующую отклика – просто шлем ее. Если же, команда подразумевает некий ответ, шлем, а затем ждем ответа. Почти все команды и отклики проверяются контрольной суммой, как со стороны хоста, так и со стороны карты. Ну, посмотрим на формат команды:

Кадр состоит из 48 бит. Первый – старт бит – всегда нуль. Затем, говорим, что данные направляются от хоста к карте и посылаем команду с аргументом. Да, да, команда состоит из индекса и аргумента. После команды обязательно шлем 7-битную контрольную сумму, вычисляемую по алгоритму циклически избыточного кода (CRC) и завершаем посылку стоп битом.

Команды бывают двух типов: CMD (базовые команды) и ACMD (Application-Specific Command). Они могут быть с аргументом и без, иметь отклик и не иметь. Всего существует порядка 80 команд (не считал точно, может и больше) и каждая из них подробно описана в спецификации.

Мы остановимся лишь на некоторых, необходимых для основной работы с карточкой (инициализация, чтение, запись). Индекс команды – это та цифра, которая идет после символов CMD или ACMD. Под него отведено 6 бит и 32 бита аргумента команды, если таковой требуется.

Важное пояснение по поводу ACMD: пространство их индексов пересекается с индексами CMD команд, поэтому, чтобы контроллер воспринял команду именно, как Application-Specific, ей должна предшествовать CMD55!

Отклик (если требуется) – тоже целая тема, хотя бы, потому что их пять типов.

         • R1 (normal response command) – длина 48 бит. Пожалуй, самый популярный отклик.
Работа с SD картой памяти по SDIO STM32 F4 Discovery | Blablacode
Содержит в себе старт бит, бит направления передачи (от карты к хосту), 6 битов индекса команды, побудившей на генерацию отклика, статус карты и, конечно же, контрольную сумму со стоп битом. Всю информацию в отклике этого типа несет 32 битное поле статуса карты. В спецификации тщательно и добросовестно расписано, что означает каждый бит этого статуса (карта занята/свободна, блокирована/разблокирована, текущее состояние автомата передачи данных, готовность к тому или иному действию и многое другое).

         • R1b – такой же формат, как и в случае R1 отклика, только передает еще флаг занятости (busy) по линии данных.

:/>  1. Расшифровка загадочных сигналов

         • R2 (CID, CSD register) – длинной в 136 бит отклик передает хосту содержимое CID и CSD регистров контроллера карточки.
Работа с SD картой памяти по SDIO STM32 F4 Discovery | Blablacode
Здесь вся полезная информация содержится в 127 битном поле, в которое помещается либо содержимое CID (в случае, если это отклик на CMD2 или CMD10 команду), либо содержимое CSD регистра (в случает посыла CMD9 команды). Так что же это за регистры такие, что под них специальные команды придуманы, да еще и с таким длинным откликом?
CID (Card identification data) – как видно из названия, содержит всю идентификационную информацию о карте (серийный номер, производитель, дата изготовления и др…). CSD (Card-specific data) – вся техническая информация о карте (объем памяти, размер блоков чтения/записи, максимальные скоростные характеристики, максимальные характеристики по потребляемому току в различных режимах и многое другое). Именно эту информацию использует хост мобилы или камеры для получения всей информации о вставленной карточке.         • R3 – длиной в 48 бит, приходит как ответ на команду ACMD41 и несет в себе информацию о содержимом OCR (Operation Conditions Register) регистра карты.
Работа с SD картой памяти по SDIO STM32 F4 Discovery | Blablacode
ACMD41 – команда инициализации карты. После ее посыла необходимо ожидать данного отклика, который будет говорить об успешном завершении процесса инициализации и сообщать содержимое регистра OCR (доступный диапазон напряжений, тип карты памяти, и флаг занятости).          • R6 (Published RCA response) – содержит в себе RCA (Relative card address) карты и некоторые статус биты.
Работа с SD картой памяти по SDIO STM32 F4 Discovery | Blablacode
Шина предполагает подключение нескольких карт к одному хосту. Поэтому очень важно такое понятие, как собственный адрес карты на шине. Это и есть содержимое RCA регистра.         • R7 (Card interface condition) – 48 битовый отклик на команду CMD8.
Работа с SD картой памяти по SDIO STM32 F4 Discovery | Blablacode
Карта оперирует определенным напряжением, ни больше не меньше. До инициализации необходимо это валидировать (об этом позже). В ответе карта посылает само напряжение (точнее значение, соответствующее этому диапазону) и некий чек паттерн (об это тоже позже).

2) Данные. Напомню (это было сказано давно…), мы рассмотрели посыл команд и получение отклика от карты. Теперь самое время разобраться с тем, как же слать, собственно, данные. Повторюсь, делается это блоками по 512 байт (для SDHC карт) — все адресное пространство карты разбито на 512 байтовый ячейки.

Пример инициализации sdhc карты


В данном примере используется самодельная функция посылки команды, написанная под периферию Stm32F4.

char SDIO_send_command(char index, unsigned int arg, char resp_type, unsigned int *resp); 

         • index – индекс команды;

         • arg — аргумент;

         • resp type – тип отклика (0 – без отклика, 1 – короткий (48 бит) отклик, 2 – длинный (136 бит) отклик);

         • resp — массив откликов (в случае короткого отклика информацию несет первый элемент массива, в случае длинного – 4 элемента).

         • Команда возвращает 0, в случае успешной операции посыла команды и приема ответа и код ошибки в противном случае.

char SDHC_card_initialization(unsigned int *RCA)
{
	char result;
	unsigned int RESP[4];
	result = SDIO_send_command(0, 0, 0, RESP);     //Посылаем CMD0, дабы обнулить карты
	if (result != 0) return result;      //Чекаем на успех
	result = SDIO_send_command(8, 0x1AA, 1, RESP);      //Посылаем CMD8 с аргументом 110101010
	if ( (result != 0) || (RESP[0] != 0x1AA) ) return 4;      //Чекаем на успех
	while( !(RESP[0]&(1<<31) ) )      //Ждем, пока флаг бизи не слезет
	{
		result = SDIO_send_command(55, 0, 1, RESP);      //Шлем CMD55, тем самым, говоря, что потом будет ACMD
		if (result != 0) return result;
		result = SDIO_send_command(0x29, 0x40020000, 1, RESP);     //Шлем ACMD41
		if (result != 0) return result;
	}
       result = SDIO_send_command(2, 0, 3, RESP);     //Шлем CMD2 и получаем инфу о карте
	if (result != 0) return result;
	result = SDIO_send_command(3, 0, 1, RESP);     //Шлем CMD3 и получаем RCA номер
	if (result != 0) return result;
	SDIO->CLKCR = (0x02<<0)|(1<<11)|(1<<8)|(1<<14);     //Наращиваем клок (в части 2 - подробнее)
	*RCA = ( RESP[0] & (0xFFFF0000) );      //Маскируем отклик и получаем RCA
	result = SDIO_send_command(7, *RCA, 1, RESP);     //Выбираем нашу карту				
	if (result != 0) return result;
	result = SDIO_send_command(55, *RCA, 1, RESP);      //Шлем CMD55, тем самым, говоря, что потом будет ACMD
	if (result != 0) return result;
	result = SDIO_send_command(6, 0x02, 1, RESP);      //Шлем ACMD6 c аргументом 0x02, установив 4-битный режим
	if (result != 0) return result;
	if (RESP[0] != 0x920) return 1; else return 0;    //Убеждаемся, что карта находится в готовности работать с трансфером
	return 0;
}


Запускаем код, убеждаемся, что в ответе пришел НУЛЬ и завершаем инициализацию. Все, можем работать с памятью и писать/считывать информацию.

Обмен данными


Здесь всем рулит SD Memory Card State Diagram (data transfer mode).
Работа с SD картой памяти по SDIO STM32 F4 Discovery | Blablacode
Существует 6 статусов карты в этом режиме и узнать их можно в отклике R1 на месте 12:9 битов. Обратимся к спецификации.
Работа с SD картой памяти по SDIO STM32 F4 Discovery | Blablacode
Stand by State (stby) – устанавливается после инициализации вместо Idle State.
Transfer State (tran) – режим передачи данных.
Receive Data State (rcv) – ожидание пачки данных от хоста.
Programming State (prg) – запись принятой пачки во flash.
Sending Data State (data) – посылка пачки данных хосту.
Disconnect State (dis) – используется для выбора другой карты командой CMD7.

Работа с sd картой памяти по sdio stm32 f4 discovery | blablacode

Как Вы уже наверное знаете, SDIO – это интерфейс для передачи данных в/из карт памяти.
В этой статье речь пойдет о работе с microSD флеш картой памяти по SDIO, который имеется в контроллере stm32f407vgt6 платы stm32f4 discovery.

Работать с картами флеш памяти можно при помощи SPI интерфейса, чем большинство радиолюбителей и занимались последние пару лет (в следствии чего, если вы поищите как подключить карту памяти к контроллеру, сразу получите результат в виде схемы подключения SPI). Но техника не стоит на месте и у нас в руках не дорогие 32 битные контроллеры, которые имеют на борту модуль специально предназначенный для работы с картами памяти – SDIO, который существенно упрощает и ускоряет работу.

Сначала о контактах.

One-Bit SD Bus Mode – Однобитный режим SDIO
MMC
Pin
SD
Pin
miniSD
Pin
microSD
Pin
ИмяI/OLogicОписание
1112NC..Не используется
2223CMDI/OPP,
OD
Command, Response
333VSSSSGround
4444VDDSSPower
5555CLKIPPSerial Clock
6666VSSSSGround
7777DAT0I/OPPSD Serial Data 0
888NC
nIRQ
.
O
.
OD
Не используется (memory cards)
Interrupt (SDIO cards) (Negative Logic)
991NC..Не используется
10NC..Зарезервировано
11NC..Зарезервировано

С картой можно работать по SDIO в режиме однобитной шины данных и 4ех битной (вроде бы уже есть и 8). Далее речь пойдет об одно битной. Никакой принципиальной разницы, кроме количества используемых проводников, это не играет.
Как видно контактов на флешке довольно много. Из них нам кроме питания и земли понадобятся только три.

  • CLK – тактирование карты.
  • CMD – по этой линии передаются команды.
  • DAT0 – линия данных (в случае 4-ех битной шины их будет 4).

Соединяем с контроллером:

  • PC8 — SDIO_D0 (DAT0)
  • PC12 — SDIO_CK (CLK)
  • PD2 — SDIO_CMD (CMD)

Я не использовал никаких подтяжек и все работало нормально. Правда иногда случаются сбои и карта не инициализируется.

Теперь перейдем к принципу работы.
Все черные дела за нас делает модуль SDIO. Мы просто должны знать команды, которые нужно посылать и читать данные.
Передача данных с/на карту неотъемлемо связана с вычислением контрольных сумм. Это происходит при каждой передаче.

Рассмотрим функцию инициализации модуля SDIO и GPIO:

void SD_LowLevel_Init(void) {
  uint32_t tempreg;
  
  GPIO_InitTypeDef GPIO_InitStructure;
  
  // GPIOC and GPIOD Periph clock enable
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC | RCC_AHB1Periph_GPIOD, ENABLE);

  //Initialize the pins
  GPIO_PinAFConfig(GPIOC, GPIO_PinSource8, GPIO_AF_SDIO);
  GPIO_PinAFConfig(GPIOC, GPIO_PinSource9, GPIO_AF_SDIO);
  GPIO_PinAFConfig(GPIOC, GPIO_PinSource10, GPIO_AF_SDIO);
  GPIO_PinAFConfig(GPIOC, GPIO_PinSource11, GPIO_AF_SDIO);
  GPIO_PinAFConfig(GPIOC, GPIO_PinSource12, GPIO_AF_SDIO);
  GPIO_PinAFConfig(GPIOD, GPIO_PinSource2, GPIO_AF_SDIO);

  // Configure PC.08, PC.09, PC.10, PC.11 pins: D0, D1, D2, D3 pins
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 ; // | GPIO_Pin_9 | GPIO_Pin_10 | GPIO_Pin_11; // раскомментируйте для 4ех битной шины
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_25MHz;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
  GPIO_Init(GPIOC, &GPIO_InitStructure);

  // Configure PD.02 CMD line
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
  GPIO_Init(GPIOD, &GPIO_InitStructure);

  // Configure PC.12 pin: CLK pin
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
  GPIO_Init(GPIOC, &GPIO_InitStructure);
  
  //Enable the SDIO APB2 Clock
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_SDIO, ENABLE);

  // Enable the DMA2 Clock
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);
  
  //Инициализируем SDIO (с начальным тактированием  HW_Flow Disabled, Rising Clock Edge, Disable CLK ByPass, Bus Width=0, Power save Disable
  SDIO->CLKCR=tempreg;

  // Включаем SDIO
  SDIO->>POWER = 0x03;

Думаю тут все более менее ясно.
Вот функция передачи команды:

static uint32_t SD_Command(uint32_t cmd, uint32_t resp, uint32_t arg) {
  //Response must be:
  //0,2:No response (expect cmdsent) ->NORESP
  //1:Short Response (expect cmdrend and ccrcfail) ->SHRESP
  //3:Long Response (expect cmdrend and ccrcfail) ->LNRESP
  
  //Clear the Command Flags
  SDIO->ICR=(SDIO_STA_CCRCFAIL | SDIO_STA_CTIMEOUT | SDIO_STA_CMDREND | SDIO_STA_CMDSENT);
  
  SDIO->ARG=arg; //First adjust the argument (because I will immediately enable CPSM next)
  SDIO->CMD=(uint32_t)(cmd & SDIO_CMD_CMDINDEX) | (resp & SDIO_CMD_WAITRESP) | (0x0400); //The last argument is to enable CSPM

  //Block till we get a response
  if (resp==NORESP) {
    //We should wait for CMDSENT
    while (!(SDIO->STA & (SDIO_STA_CTIMEOUT | SDIO_STA_CMDSENT))) {};
  }
  else {//SHRESP or LNRESP or R3RESP
    //We should wait for CMDREND or CCRCFAIL
    while (!(SDIO->STA & (SDIO_STA_CTIMEOUT | SDIO_STA_CMDREND | SDIO_FLAG_CCRCFAIL))) {};
  }
  
  //Check to see if the response is valid
  //We consider all R3 responses without a timeout as a valid response
  //It seems CMDSENT and CMDREND are mutually exlusive. (though I am not sure. Check this later)
  if (SDIO->STA & SDIO_STA_CTIMEOUT) {
    SD_Panic(cmd, "SDIO: Command Timeout Errorn");
  } else if ((SDIO->STA & SDIO_FLAG_CCRCFAIL) && (resp!=R3RESP)) {
    SD_Panic(cmd, "SDIO: Command CRC Errorn");
  }

  return SDIO->STA;
}

Тут уже все обстоит несколько сложнее.
На вход функции подаеться необходимая комана (cmd), идентификатор ответа (указывает ждать ли ответ после этой команды) и аргумент (arg).
Дальнейшая часть кода требует длительного “вкуривания” спецификации на SD карты.

:/>  Полное меню «Открыть с помощью» отсутствует в Windows 10, 8.1 и 7

Приведу также код функции инициализации карты (да, да.. её нужно еще и инициализировать).

void SD_Init(void){
  //uint32_t data;
  uint32_t response;
  uint32_t TimeOut=0xFFFF;
  uint32_t tempreg;
  
  //CMD0: GO_IDLE_STATE (No Response)
  SD_Command(CMD0, NORESP, 0);
      
  //CMD8: SEND_IF_COND //Response to CMD8 is R7. But I will ignore that response
  SD_Command(CMD8, SHRESP, 0x000001AA); //Non v2.0 compliant sd's will cause panic here due to the timeout
  SD_Response(&response, RESP_R7); //AA is the check pattern. If response does not match with it, execution will be blocked in panic
  
  
  while (1) {
    ////Send ACMD41
    //CMD55
    SD_Command(CMD55, SHRESP, 0); //Note that argument should be RCA. But at this point RCA of SD is 0. (It will be changed after cmd3)
    SD_Response(&response, RESP_R1);
    
    //ACMD41 (Response is R3 which does not contain any CRC)
    //Second argument in the argument indicates that host supports SDHC. We will check acmd41 response if the SD is SC or HC
    SD_Command(ACMD41, R3RESP, (uint32_t) 0x80100000 | (uint32_t) 0x40000000);
    SD_Response(&response, RESP_R3);
    
    //Check the ready status in the response (R3)
    if ((response >> 31) == 1) { //When card is busy this bit will be 0
      //Card is now initialized. Check to see if SD is SC or HC
      SDType=(response & 0x40000000) >> 30; //1=HC, 0=SC
      break;
    } else {
      TimeOut--;
      if (!TimeOut) {
        SD_Panic(ACMD41, "SDIO:ACMD41 Timeoutn");
      }
    }
   }
  
  //Now we are in the Ready State. Ask for CID using CMD2
  //Response is R2. RESP1234 are filled with CID. I will ignore them
  SD_Command(CMD2, LNRESP, 0);
  
  //Now the card is in the identification mode. Request for the RCA number with cmd3
  SD_Command(CMD3, SHRESP, 0);
  SD_Response(&response, RESP_R6);
  //Read the RCA
  RCA=response>>16;
  
  //Now the card is in stand-by mode. From this point on I can change frequency as I wish (max24MHz)
  
  
  //Use cmd9 to read the card specific information
  //Response is R2 with CSI. I will ignore the response
  SD_Command(CMD9, LNRESP, (RCA << 16));
  
  
  //Put the Card in the tranfer mode using cmd7. (I will change the clock spped later together with bus width)
  //Bus width can only be changed in transfer mode
  SD_Command(CMD7, SHRESP, (RCA << 16));
  SD_Response(&response, RESP_R1);
  
  //Change the bus-width with cmd6
  //CMD55
  SD_Command(CMD55, SHRESP, (RCA << 16)); //Note the real RCA in the argument
  SD_Response(&response, RESP_R1);
  //ACMD6
  SD_Command(ACMD6, SHRESP, 0x02);
  SD_Response(&response, RESP_R1);
  
  //Configure SDIO->CLKCr for wide-bus width and new clock
  tempreg=0; //Reset value

  // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
  //tempreg|=(0x01)<<11; //4 bit Bus Width
  // ОСТАВЛЯЕМ ПО УМОЛЧАНИЮ 1бит шины
  // биты 11-12 = 00
  
  tempreg|=SDIO_CLKCR_CLKEN; //Clock is enabled
  //Keep the rest at 0=> HW_Flow:Disabled, Rising Edge, Disable bypass, Power save Disable, Clock Division=0
  //As the clock divider=0 => New clock=48/(Div 2)=48/2=24
  SDIO->CLKCR=tempreg;
  
  //Now we can start issuing read/write commands
}

В ходе данной функции карта сбрасывается и настраивается для передачи данных.
После этого я выводил данные с карты в USART stm32.

Вот такой вывод:

0 block:
	
0000: 00 00 00 00 00 00 00 00 	00 00 00 00 00 00 00 00 	
0010: 00 00 00 00 00 00 00 00 	00 00 00 00 00 00 00 00 	
0020: 00 00 00 00 00 00 00 00 	00 00 00 00 00 00 00 00 	
0030: 00 00 00 00 00 00 00 00 	00 00 00 00 00 00 00 00 	
0040: 00 00 00 00 00 00 00 00 	00 00 00 00 00 00 00 00 	
0050: 00 00 00 00 00 00 00 00 	00 00 00 00 00 00 00 00 	
0060: 00 00 00 00 00 00 00 00 	00 00 00 00 00 00 00 00 	
0070: 00 00 00 00 00 00 00 00 	00 00 00 00 00 00 00 00 	
0080: 00 00 00 00 00 00 00 00 	00 00 00 00 00 00 00 00 	
0090: 00 00 00 00 00 00 00 00 	00 00 00 00 00 00 00 00 	
00a0: 00 00 00 00 00 00 00 00 	00 00 00 00 00 00 00 00 	
00b0: 00 00 00 00 00 00 00 00 	00 00 00 00 00 00 00 00 	
00c0: 00 00 00 00 00 00 00 00 	00 00 00 00 00 00 00 00 	
00d0: 00 00 00 00 00 00 00 00 	00 00 00 00 00 00 00 00 	
00e0: 00 00 00 00 00 00 00 00 	00 00 00 00 00 00 00 00 	
00f0: 00 00 00 00 00 00 00 00 	00 00 00 00 00 00 00 00 	
0100: 00 00 00 00 00 00 00 00 	00 00 00 00 00 00 00 00 	
0110: 00 00 00 00 00 00 00 00 	00 00 00 00 00 00 00 00 	
0120: 00 00 00 00 00 00 00 00 	00 00 00 00 00 00 00 00 	
0130: 00 00 00 00 00 00 00 00 	00 00 00 00 00 00 00 00 	
0140: 00 00 00 00 00 00 00 00 	00 00 00 00 00 00 00 00 	
0150: 00 00 00 00 00 00 00 00 	00 00 00 00 00 00 00 00 	
0160: 00 00 00 00 00 00 00 00 	00 00 00 00 00 00 00 00 	
0170: 00 00 00 00 00 00 00 00 	00 00 00 00 00 00 00 00 	
0180: 00 00 00 00 00 00 00 00 	00 00 00 00 00 00 00 00 	
0190: 00 00 00 00 00 00 00 00 	00 00 00 00 00 00 00 00 	
01a0: 00 00 00 00 00 00 00 00 	00 00 00 00 00 00 00 00 	
01b0: 00 00 00 00 00 00 00 00 	00 00 00 00 00 00 80 ff 	
01c0: ff ff 0c ff ff ff 00 08 	00 00 00 24 76 00 00 00 	
01d0: 00 00 00 00 00 00 00 00 	00 00 00 00 00 00 00 00 	
01e0: 00 00 00 00 00 00 00 00 	00 00 00 00 00 00 00 00 	
01f0: 00 00 00 00 00 00 00 00 	00 00 00 00 00 00 55 aa 

В нулевом секторе всегда должны быть не нулевые данные.
Прикрепляю проект для IAR.
Не пугайтесь названия. Просто все это “выросло” из проекта для USART.

PS. Основа работы с картой в файлах sdcard.c и sdcard.h

Чтение данных

Дабы выполнить обратную процедуру, в первую очередь, убеждаемся, что карта стоит в

tran

. Шлем CMD17 с адресом RCA в аргументе. Если все пройдет успешно – карточка переедет в состояние

data

и начнет выдавать на линии данных информацию, опять же 512 байтным блоком. Задача хоста в это время внимательно слушать линию и считывать данные. Как только посылка закончится, карта сама переедет в статус

tran

. Думаю, не стоит и говорить о том, что считывание так же как и запись возможна несколькими блоками сразу.

Не буду приводить в этой статье листинг программы чтения/записи, так как он, в отличии от подпрограммы инициализации слишком сильно завязан на железе SDIO модуля микроконтроллера Stm32f4, а это – тема второй части статьи.

Литература:1) SD_Physical_Layer_Spec 2.0 2) SD_Physical_Layer_Spec 4.13) STM32F4 CortexM4 Reference manual

Оставьте комментарий