Подключаем SD карту памяти к STM32 по SPI

«Много информации не бывает —
бывает мало места для её хранения»

Много информации не бывает - бывает мало места для её хранения.
какая-то реклама в каком-то популярном
компьютерном журнале 90-х годов.

Если в проекте нужно выводить или получать большие объемы данных то на помощь придут различные flash микросхемы памяти. Если же нежно получать или выводить ОГРОМНЫЕ объемы данных, то стоит задуматься о более вместительном хранилище информации. На роль такого вместилища подходит SD карта памяти. Это – огромный носитель информации, по сравнению с обычными микросхемами памяти, объём которых редко превышает 64Мбита.

Подключение SD карты к STM32vlDiscovery

Подключение SD карты к STM32vlDiscovery

В статье рассмотрим подключение и диалог SD карты памяти с микроконтроллером STM32, через SPI. Без использование файловой системы.

В качестве контроллера и платы выступит демонстрационная плата STM32vlDiscovery с контроллером STM32F100RB на борту. Диалог с платой будет производиться по UART, точнее, через преобразователь USB-UART будем общаться через COM терминальную программу AL Terminal. Связь с картой памяти будет осуществляться через SPI.

Подключение карты памяти к STM32F100RB установленному в Discovery будет выглядеть следующим образом:

Схема

Схема

Контроллер установлен на демонстрационной плате и его подключение расписывать не буду. Преобразователь USB-UART описан в статье — USB-UART на cp2102 , можете применить и любой другой, например ft232 . Карта памяти подключается напрямую к микроконтроллеру, т.к. напряжение питание и логические уровни сигналов, в 3.3В, у них одинаковые.

Распиновка SD карты в SPI режиме

Распиновка SD карты в SPI режиме

С подключением ничего сложного нет, переходим к коду.

Проект написан в Keil с использованием CMSIS и Standard Peripheral Library. Из SPL применены GPIO, RCC И USART библиотеки. Библиотеку для работы с картой памяти воспользовался готовой, взял с сайта «My Controller» — http://mycontroller.ru/category/vneshnie-ustroystva/karta-pamyati-sd/ .

Возможности библиотеки:

SD_init() – инициализация SPI и карты памяти
в случае удачи возвращает 0.

SD_sendCommand(cmd, arg) – посылка команды SD карте
Где: cmd – команда, arg – аргумент
Основные команды:
GO_IDLE_STATE 0 – программная перезагрузка
SEND_IF_COND 8 – для SDC V2 — проверка диапазона напряжений
READ_SINGLE_BLOCK 17 – чтение указанного блока данных
WRITE_SINGLE_BLOCK 24 – запись указанного блока данных
SD_SEND_OP_COND 41 – начало процесса инициализации
APP_CMD 55 – главная команда из ACMD команд
READ_OCR 58 – чтение регистра OCR
Команд для работы с картой памяти на самом деле гораздо больше, но в библиотеки задействованы только эти, чего для минимальной работы достаточно.

SD_ReadSector(BlockNumb,*buff) – прочитать сектор
в случае неудачи возвращает 0
Где: BlockNumb – адрес блока карты памяти, *buff – ссылка для буфера

SD_WriteSector(BlockNumb,*buff) – записать сектор
в случае неудачи возвращает 0
Где: BlockNumb – адрес блока карты памяти, *buff – ссылка для буфера

На основе библиотеки написал небольшую оболочку для ознакомления с картой памяти:

unsigned long address,i;
unsigned char c;

init();											//	инициализация переферии
if(SD_init()==0)								//	инициализация SD карты памяти
{
	send_Uart_str(USART1,"init sd ok\n");
}
else
{
	send_Uart_str(USART1,"init sd fail\n");
}

send_Uart_str(USART1,"alex-exe.ru");			//	выводим сообщение в UART
while(1)
{
	send_Uart_str(USART1,"\n----------------------------\n");
	send_Uart_str(USART1,"Read or write sd card r/w = ");
	c=getch_Uart(USART1);						//	читаем байт с uart
	while(getch_Uart(USART1)!=13){}				//	дожидаемся ввода enter с uart
	send_Uart_str(USART1,"\nPress enter address = ");
	buff_clear();								//	очистка буфера
	address=read_int_uart(USART1);				//	читаем число с uart, адрес сектора на sd карте
	send_Uart_str(USART1,"\nAddress = ");
	send_int_Uart(USART1,address);				//	выводим для проверки введенное число	
	send_Uart(USART1,'\n');
	if(c=='w')									//	проверка на запись или чтение карты памяти
	{
		send_Uart_str(USART1,"Press enter data blok (max 512B), to exit press enter\n");
		read_str_uart(USART1,Buff);				//	читаем строку с uart, окончание enter
		i=0;
		while((i < 512)&&(Buff[i]!=0))i++;		//	ищем конец текстовой строки
		send_Uart_str(USART1,"Length text data = ");
		send_int_Uart(USART1,i);				//	выводим длину текстового послания
		send_Uart(USART1,'\n');
		if(SD_WriteSector(address, Buff)==0)	//	запись буфера на SD карту
		{
			send_Uart_str(USART1,"write sd ok\n");
		}
		else
		{
			send_Uart_str(USART1,"write sd fail\n");
		}
	}
	else
	{
		if(SD_ReadSector(address, Buff)==0)	//	чтения SD карты в буфер
		{
			send_Uart_str(USART1,"read sd ok\n");
		}
		else
		{
			send_Uart_str(USART1,"read sd fail\n");
		}
		for(i=0;i < 512;i++)				//	вывод содержимого буфера в терминал
		{
			send_Uart(USART1,Buff[i]);
		}
	}
//	на последок мигнём светодиодом
	GPIO_SetBits(GPIOC, GPIO_Pin_8);	//	включить синий светодиод на PC8
	delay(1000000);
	GPIO_ResetBits(GPIOC, GPIO_Pin_8);	//	выключить синий светодиод на  PC8
}

В коде реализована возможность чтения и записи блоков карты памяти через терминал.

Эксперименты проводил с картами памяти SD Transcend 2 GB и SDHC Qumo 8GB.

Скачать исходник

В статье использовались материалы с сайта «My Controller» — http://mycontroller.ru/category/vneshnie-ustroystva/karta-pamyati-sd/.

Схема обновлена 2 октября 2013 года

39 комментариев »

Alex_EXE | 14.08.2013 | STM32 | 24 347 просмотров

39 комментариев на « Подключаем SD карту памяти к STM32 по SPI»

  1. Дмитрий пишет 15.12.2015 в 17:53 #

    И это снова я. И опять ошибка возникала из-за неправильной работы ножки. В качестве ножки SlaveSelect я выбрал ту, которая указана в ДШ как NSS для SPI2. Настроил ее на ее на управление программно. Сброс/установку ножки осуществлял с помощью функции SPI_NSSInternalSoftwareConfig() (так написано в библиотеке SPL). Так вот ножка не изменяла своего значения! Более того, когда я перестал использовать эту функцию и стал напрямую менять бит через регистр порта BSRR, то ножка все равно постоянно была выставлена! Причины я не знаю. Возможно, что все дело в режиме программного управления NSS. Поэтому когда я стал использовать для SlaveSelect другой пин, то все заработало — ответы стали приходить.
    P.S. Правда дальше все равно не заработало….Опять… 🙂
    Поэтому снова обращение за советом к уважаемому автору (когда он разгребётся с работой) 🙂
    Ситуация такая: на команду SEND_IF_COND приходит ответ 0x01 — значит флешка v2 — вроде бы логично — флешка куплена неделю назад и навряд ли она v1. Идем дальше: отсылаю ACMD41, т.е. APP_CMD (получаю ответ 0x01 — вроде тоже все норм) и APP_SEND_OP_COND — получаю ответ 0x05. Имеем illegal command error, значит флешка не воспринимает такую команду. Решил попробовать отослать просто CMD1(вдруг карта версии v1), т.е. SEND_OP_COND — получаю ответ 0x01. Т.е. карточка никак не хочет инициализироваться, т.к. ждем 0x00. Может ли быть еще другие варианты последовательности инициализации?

  2. Дмитрий пишет 16.12.2015 в 14:51 #

    Решил проблему 🙂 Долго гуглил, находил различные решения (причем некоторые были противоположны друг другу :О ) но ничего не помогало. Увидел вот эту ссылку http://stackoverflow.com/questions/2365897/initializing-sd-card-in-spi-issues — решил что это не поможет и стал дальше искать, а зря…. Так как позже, отчаявшись, решил пробовать все решения и добавил несколько команд spi_read(); в начале функции SD_sendCommand() и карточка наконец инициализировалась 🙂
    P.S. В той же ссылке есть другой совет — «send ACMD41 with the bit set for the voltage you’re supplying the card with», и этот человек утверждает, что ему это помогло, хотя в Physical Layer Simplified Specification Version 4.10 говорит про аргумент ACMD41 так:
    Argument [31]Reserved bit [30]HCS [29:0]Reserved bits
    Command Description — Sends host capacity support information and activates the card’s initialization process. Reserved bits shall be set to ‘0’
    что явно противоречит выше сказанному. Так что не всем советам стоит верить. Возможно, что и моим тоже 🙂

  3. Дмитрий пишет 23.12.2015 в 16:31 #

    И снова здравствуйте! 🙂 В ходе использования карточкой возник вопрос: а как отследить что мы пишем в неверный сектор? Я имею ввиду, например, на карте 1000000 секторов, а мы пытаемся записать в 1000001. Карта это спокойно воспринимает — присылает valid-ный R1-response на команду (0x00) и valid-ный Data-response после приема данных (0xE5, но это тоже самое что 0x05, ведь значимые только младшие 7 бит).
    P.S. Причем при попытке считать из неверного сектора R1-response 0x40, т.е. Parameter Error.

  4. Alex_EXE пишет 27.12.2015 в 01:14 #

    stm32f103 контроллер? У них столкнулся с подобной проблемой, только в другом ключе и использовал не SPI. Решение проблемы нашел, в рамках одного из будущих материалов опишу его на сайте.
    По остальным вопросам отправил Вам письмо.

  5. Дмитрий пишет 28.12.2015 в 19:38 #

    Alex_EXE, благодарю за письмо. Всеми ссылками, которые Вы указали, активно пользовался и до этого 🙂 Кроме первой 🙂 Но она тоже выглядит полезной и ее стоит добавить в закладки. Я так понимаю Вы давали ссылку в этой статье на нее, но только на нерабочую версию, поэтому решил добавить ее в комментарии: http://mycontroller.ru/old_site/category/vneshnie-ustroystva/karta-pamyati-sd/default.htm
    Теперь по поводу чем дело кончилось:) К сожалению, ответа на него я не нашел 🙁 Нет, проблему-то я решил, правда другим способом, но мне кажется что это лишние действия и было бы лаконичнее и красивее, если бы карта выдавала Parameter Error на попытку записать в неверный сектор. Возможно, где-то в закоулках даташита, все-таки скрывается ответ и его нужно просто очень внимательно прочитать 🙂 А вот об этом речь пойдет дальше, в следующем комменте 🙂

  6. Дмитрий пишет 28.12.2015 в 20:40 #

    Так вот. Как я думал решить проблему. Чтобы не писать в несуществующий сектор нужно просто знать их количество и проверять не вышли ли мы за предел 🙂 Знаю, банально, но решать проблему как-то надо 🙂
    Есть такой регистр CSD (The Card-Specific Data register) В нем определенные биты отвечают за размер карты C_SIZE, при чём : memory capacity = (C_SIZE+1) * 512KByte. Т.е. отсюда можно посчитать количество секторов. Читается он с помощью CMD9 (SEND_CSD). Нашел наверно с десяток ссылок (например https://my.st.com/public/STe2ecommunities/mcu/Lists/cortex_mx_stm32/Attachments/18065/stm32_eval_spi_sd.c) в которых был написан один и тот же(по смыслу, а не скопипащенный 🙂 ) алгоритм чтения этого регистра. Суть сводилась к следующему:
    Reading the contents of the CSD register in SPI mode is a simple read-block transaction (при чем в даташите есть точно такая же строка, но абзац на этом не заканчивается, но видимо дальше никто не читает 🙂 ). В итоге, во все ссылках такая последовательность действий:
    CS в ноль -> шлем команду CMD9 с пустым аргументом -> ждём R1(0x00) -> ждём Data Token(0xFE) -> читаем 16 байт регистра -> читаем 2 байта CRC -> CS в единицу. Всё, данные получили, осталось только их распарсить.
    ОК, пишу код, запускаю. На команду отвечает R1(0x00), всё норм. Вместо Data Token(0xFE) приходит 0x7F, а дальше идут 0xFF. Приехали. Начинаю гуглить, искать что означает 0x7F и кто с этим сталкивался. Вообщем потратил кучу времени и всё в пустую. Потом смотрю на свой код команды чтения сектора и стоп! — в CMD17 сначала шлем команду, а потом CS в ноль, а тут наоборот почему-то, хотя везде сказано что что CMD9 та же самая CMD17. Думаю дай-ка попробую так. Ииии…нет 🙁 всё равно не получаем 0xFE. НО, если раньше дальше шли 0xFF, то теперь какие-то непонятные байты. Странно, непонятно…И тут у меня появилась мысль и полез я в даташит. И что же я там увидел, в следующей части абзаца, после строки про simple read-block transaction. А вот что:
    The card will respond with a standard response token followed by a data block of 16 bytes suffixed with a 16-bit CRC.
    Т.е. в даташите для команды CMD9 ни про какой Data Token не сказано! Т.е. сразу после R1(0x00) идут байты регистра! Вот что значат эти непонятные байты. Проверил — действительно, 18 байт (16 + CRC), а потом уже пошли 0xFF. Распарсил байты и проверил провел по даташиту все сходится, это он — CSD. И размер карты тоже сошелся. Ну дальше дело техники посчитать количество секторов.
    Так что вот, мой так сказать опыт по этому вопросу, может кому пригодится, чтобы не трать (как мне дурачку) такое количество времени на поиски ошибок на пустом месте 🙁
    P.S. Мне вот что интересно. У людей, которые выкладывали приведенный выше алгоритм, действительно работает? И если да, то почему ? 🙂 Ведь это противоречит даташиту.

  7. Алексей пишет 08.02.2016 в 17:45 #

    Может кто подскажет как к данному проекту прикрутить FatFs. Бьюсь неделю и ничего не выходит. Среда разработки CooCox.

  8. Андрей пишет 13.10.2016 в 08:32 #

    Не знаю еще актуальна эта эта или нет, хотелось бы считывать изображение с SD карты и выводить его на дисплей. Загрузил пример от сюда http://cxem.net/mc/mc335.php (кстати в Cocox) но он почему-то нее «пошел» О чем и написал автору (без ответа). Там-же в примере есть и «прикрутка» FatFs. Посмотрите, может вам удастся разобраться в проблеме.
    И может напишите еще одну статью.

  9. Alex_EXE пишет 22.10.2016 в 20:28 #

    Сейчас нет времени разбираться, по той же причине снова временно прекратил написание статей; сейчас методички на работе пишу, они хорошо удовлетворяют желание что-либо писать.
    По FatFs — ни раз уже упомнила 2 хороших сайта:
    http://mycontroller.ru/old_site/category/vneshnie-ustroystva/karta-pamyati-sd/rabota-s-ffatfs/default.htm
    http://microsin.net/programming/file-systems/index.html
    источник библиотеки http://elm-chan.org/fsw/ff/00index_e.html

Комментарии RSS

Оставьте отзыв