Для чего эта статья?
Все эмбеддеры, рано или поздно, сталкиваются с проблемой нехватки ПЗУ микроконтроллера для своих проектов. Ну банально, Вам нужно разработать систему управления простеньким ЧПУ станком, где управляющая программа хранится на самом девайсе или систему сбора данных, скажем, от датчиков какого-нибудь эксперимента – очевидно, что микроконтроллер изначально не предназначался для хранения таких массивов информации.
Решений сего кейса масса, начиная от приобретения и подключения микросхем EEPROM и заканчивая коннектом стандартной USB флешки к аппаратному USB хосту камня (если таковой имеется, конечно). Ну, а для хэнд-майд проектов, отличным вариантом будет самая, что ни на есть классическая SD’шная карта памяти. Они бывают разного типа, имеют различные механизмы инициализации и передачи данных и подключаются к хосту через различные интерфейсы (их, правда, только три, но об этом позже). Более того, многие современные микроконтроллеры имеют на своем борту аппаратные модули этих интерфейсов, и работа разработчика сводится лишь к их конфигу и посылу карточке нужных команд в соответствии с протоколом. Ну и еще карты памяти имеют приятное свойство элементарно покупаться на каждом шагу.
О Secure Digital (SD) картах
Не буду переписывать Википедию – приведу здесь основные сведения и виды SD карт памяти и их характеристики.
Secure Digital формат – популярный (пожалуй, самый популярный на сегодняшний день) формат flash памяти для использования, в основном, в портативных устройствах. Внутри каждой такой карточки имеется, собственно, микросхема flash памяти (Memory Core) и, связывающий ее с внешним миром контроллер, имеющий 8 регистров. Задачи последнего – аппаратная реализация внешних интерфейсов, поддержка информации о карте (тип, емкость, класс скорости еще куча других характеристик), контроль электропитания, и, конечно, управление самой микрухой памяти (адресация, чтение, запись, очистка и оганизация порядка 80 команд управления).
![](https://habrastorage.org/getpro/habr/post_images/d01/077/f9e/d01077f9e62e22b537edea45334fcd20.png)
Формат SD был основан компаниями Panasonic, SanDisk и Toshiba на основе MMC карт. Позже эти компании создали организацию SD Card Association, в настоящее время занимающуюся разработкой и продвижением технологии SD. Основной документ, в котором досконально описан интерфейс, протокол, команды, регистры карточек — Physical Layer Simplified Specification (Copyright 2001-2006 SD Group (Panasonic, SanDisk, Toshiba) and SD Card Association). Именно эту информацию используют всякие R&D центры при разработки аппаратного и программного обеспечения своих будущих девайсов. Сам файлик благополучно лежит в свободном доступе в инете, и скачать его не предоставляется никаких сложностей. Так вот, в соответствии с этим документом, существуют следующие типы карт памяти:
• SD карты (или еще SDSC (Secure Digital Standard Capacity) ) – первое поколение карт памяти. Ограничение по объему – 2 Гб. Минимальный размер адресуемого пространства – 1 байт.
• SDHC карты (Secure Digital High Capacity) – карты памяти повышенной емкости (до 32 Гб). Имеют существенное отличие от первого типа, а именно, адресация происходит блоками по 512 байт и никто в этом мире не может изменить это значение. Иными словами, нельзя просто так взять и записать, к примеру, 71 байт информации: минимальный размер пачки, повторюсь, 512 байт. Особо не копал, почему так, но есть личное мнение, что это из-за используемого 32-битного адресного пространства контроллера и из-за того, что карты памяти обычно форматируются под ту или иную файловую систему, размер кластера которой удобно сочетается с такими блоками. Еще у SDHC карт дугой процесс инициализации, о котором поговорим чутка по позже.
• SDXC (Secure Digital eXtended Capacity) – карты памяти расширенной емкости – теоретически аж до 2Tб памяти. Адресация тоже по 512 байт. Вот оно и получается при 32-битном пространстве: (2^32)*512 = 2 Тб.
На каждое поколение карт существуют спецификации, и при этом в каждом документе на более новое поколение описывается инфа о старых – то есть они «толстеют» с каждым обновлением продукта. Так что скачиваем Physical Layer Simplified Specification самой последней версии и находим там все, что надо для работы со всеми поколениями карт. Кроме этого, карты памяти делятся на несколько классов по скорости чтения/записи данных. Ну, а что касается всяких там mini-, microSD, microSDXC и т.д. – это всего лишь другой размер корпуса и распиновка – никаких внутренних отличий от карточек стандартных габарит.
А теперь важно: ВНЕ зависимости от типа карты, емкости, ее производителя, типа корпуса, цвета и магазина, где вы ее купили – все Security Digital карты имеют одинаковые интерфейсы взаимодействия с внешним миром. Команды, механизмы инициализации – разные, да, но интерфейсы – ОДИНАКОВЫЕ. Именно это позволяет напофиг воткнуть в фотик как SD, так и SDHC карту памяти. Ну, вот и пришел момент обсудить язык карточки, а точнее аж три: SD и UHS-II (нэйтив спикер) и «язык универсальной микропроцессорной коммуникации, который сейчас знает каждая
Интерфейс карты памяти
Как было сказано выше, Security Digital карты имеют три внешних интерфейса: SD, UHS-II и SPI. Первые являются «родными» каналами обмена данными с хостом, и, как следствие, позволяют реализовать полнофункциональное, полноскоростное взаимодействие. SPI же не поддерживает ряда команд и не дает максимальной скорости обмена данными, зато он есть во всех микроконтроллерах (и в современных и в старых моделях), что делает возможным без особых проблем приконнектить карточку ко всему, что плохо лежит. Существует масса статей о том, как это сделать. Но, с развитием микропроцессорной техники, с уменьшением нанометров в технологическом процессе производства камней, SPI интерфейс, как средство коммуникации с SD картой постепенно отмирает. Действительно, если ваш МК поддерживает аппаратную реализацию SD протокола, будите ли Вы связываться с менее функциональной альтернативой? Судьба послала мне на проект камень Stm32f4 от STMicroelectronics, в котором как раз таки и имеется периферийный модуль SDIO (Security Digital Input Output), аппаратно реализующий и интерфейс, и протокол карточки.
Так что же такое SD протокол и с чем его едят? Ключевых понятий тут три:
• команда – последовательность битов, воспринимаемых контроллером карточки и призывающих его к тому или иному действию;
• отклик – ответ контроллера карты на команду. Он может содержать как общую информацию (статус карты, текущее состояние различных внутренних модулей и т.д.), так и, собственно, ожидаемую хостом информацию (запросили в команде идентификатор карты – получили его в отклике);
• данные – ну тут без комментариев.
Но прежде, чем посмотрим на логику протокола, обратимся к физике интерфейса (очень обзорно).
![](https://habrastorage.org/getpro/habr/post_images/43e/148/cd7/43e148cd7223fe7c75fcf094f55a6e8e.jpg)
Pin 4 – питание карточки;
Pin 3, 6 – земля;
Pin 5 – тактовый сигнал;
Pin 2 – линия команд и откликов;
Pin 1, 7, 8, 9 – линии 4-битной шины данных.
Все посылки карточке и обратно есть последовательности битов, строго синхронизированные с тактовым сигналом, передаваемым по линии CLK. Рекомендуемые частоты описаны в спецификации на карту и имеют различное значение, в зависимости от ее типа и класса скорости. Отмечу только, что для любой карты инициализация проходит на очень малой (по сравнению с передачей данных) частоте. Шина данных может быть 1-битной (работает только D0) или 4-битной – это конфигурируется при инициализации. Важно, что для SD карт со стороны хоста линии данных и команд должны быть Push-Pull и быть подтянуты к питанию через резисторы 4.5 – 10 кОм. Тактовую шину тоже нужно подтянуть к питанию.
Ну, а теперь к протоколу!
Бывает несколько вариантов обмена информацией хост – карта.
1) Команды без данных.
Все команды делятся на требующие и не требующие отклик.
![](https://habrastorage.org/getpro/habr/post_images/438/48c/897/43848c89714048a608821f7da77163ee.png)
Как видно из рисунка, если нам (хосту) нужно послать команду, не требующую отклика – просто шлем ее. Если же, команда подразумевает некий ответ, шлем, а затем ждем ответа. Почти все команды и отклики проверяются контрольной суммой, как со стороны хоста, так и со стороны карты. Ну, посмотрим на формат команды:
![](https://habrastorage.org/getpro/habr/post_images/f0b/7d1/fff/f0b7d1fff857f9727146cc394b0ca288.png)
Кадр состоит из 48 бит. Первый – старт бит – всегда нуль. Затем, говорим, что данные направляются от хоста к карте и посылаем команду с аргументом. Да, да, команда состоит из индекса и аргумента. После команды обязательно шлем 7-битную контрольную сумму, вычисляемую по алгоритму циклически избыточного кода (CRC) и завершаем посылку стоп битом. Команды бывают двух типов: CMD (базовые команды) и ACMD (Application-Specific Command). Они могут быть с аргументом и без, иметь отклик и не иметь. Всего существует порядка 80 команд (не считал точно, может и больше) и каждая из них подробно описана в спецификации. Мы остановимся лишь на некоторых, необходимых для основной работы с карточкой (инициализация, чтение, запись). Индекс команды – это та цифра, которая идет после символов CMD или ACMD. Под него отведено 6 бит и 32 бита аргумента команды, если таковой требуется.
Важное пояснение по поводу ACMD: пространство их индексов пересекается с индексами CMD команд, поэтому, чтобы контроллер воспринял команду именно, как Application-Specific, ей должна предшествовать CMD55!
Отклик (если требуется) – тоже целая тема, хотя бы, потому что их пять типов.
• R1 (normal response command) – длина 48 бит. Пожалуй, самый популярный отклик.
![](https://habrastorage.org/getpro/habr/post_images/ed3/2d4/364/ed32d43643f56a686bbe7fcbffee42c8.png)
Содержит в себе старт бит, бит направления передачи (от карты к хосту), 6 битов индекса команды, побудившей на генерацию отклика, статус карты и, конечно же, контрольную сумму со стоп битом. Всю информацию в отклике этого типа несет 32 битное поле статуса карты. В спецификации тщательно и добросовестно расписано, что означает каждый бит этого статуса (карта занята/свободна, блокирована/разблокирована, текущее состояние автомата передачи данных, готовность к тому или иному действию и многое другое).
• R1b – такой же формат, как и в случае R1 отклика, только передает еще флаг занятости (busy) по линии данных.
• R2 (CID, CSD register) – длинной в 136 бит отклик передает хосту содержимое CID и CSD регистров контроллера карточки.
![](https://habrastorage.org/getpro/habr/post_images/c79/c22/dfa/c79c22dfa2ac865ad5fce2782c6c1a50.png)
Здесь вся полезная информация содержится в 127 битном поле, в которое помещается либо содержимое CID (в случае, если это отклик на CMD2 или CMD10 команду), либо содержимое CSD регистра (в случает посыла CMD9 команды). Так что же это за регистры такие, что под них специальные команды придуманы, да еще и с таким длинным откликом?
CID (Card identification data) – как видно из названия, содержит всю идентификационную информацию о карте (серийный номер, производитель, дата изготовления и др…). CSD (Card-specific data) – вся техническая информация о карте (объем памяти, размер блоков чтения/записи, максимальные скоростные характеристики, максимальные характеристики по потребляемому току в различных режимах и многое другое). Именно эту информацию использует хост мобилы или камеры для получения всей информации о вставленной карточке.
• R3 – длиной в 48 бит, приходит как ответ на команду ACMD41 и несет в себе информацию о содержимом OCR (Operation Conditions Register) регистра карты.
![](https://habrastorage.org/getpro/habr/post_images/64b/d51/098/64bd5109850f1e1b8a2eb8f3464784b1.png)
ACMD41 – команда инициализации карты. После ее посыла необходимо ожидать данного отклика, который будет говорить об успешном завершении процесса инициализации и сообщать содержимое регистра OCR (доступный диапазон напряжений, тип карты памяти, и флаг занятости).
• R6 (Published RCA response) – содержит в себе RCA (Relative card address) карты и некоторые статус биты.
![](https://habrastorage.org/getpro/habr/post_images/a3a/fa9/974/a3afa99740ae49763c0d96bc33a56bb9.png)
Шина предполагает подключение нескольких карт к одному хосту. Поэтому очень важно такое понятие, как собственный адрес карты на шине. Это и есть содержимое RCA регистра.
• R7 (Card interface condition) – 48 битовый отклик на команду CMD8.
![](https://habrastorage.org/getpro/habr/post_images/ade/27f/4f2/ade27f4f255c54039dfe616113db65d3.png)
Карта оперирует определенным напряжением, ни больше не меньше. До инициализации необходимо это валидировать (об этом позже). В ответе карта посылает само напряжение (точнее значение, соответствующее этому диапазону) и некий чек паттерн (об это тоже позже).
2) Данные.
Напомню (это было сказано давно…), мы рассмотрели посыл команд и получение отклика от карты. Теперь самое время разобраться с тем, как же слать, собственно, данные. Повторюсь, делается это блоками по 512 байт (для SDHC карт) — все адресное пространство карты разбито на 512 байтовый ячейки. Посылке данных всегда должна предшествовать специальная команда, говорящая контроллеру карты о том, что данные вот-вот уже пойдут. А идут они, как я уже говорил – по 1- или 4-битной шине. Посмотрим на формат посылки данных к хосту от карты (чтение).
![](https://habrastorage.org/getpro/habr/post_images/1c3/535/52d/1c353552d4f8793474d00f89cf6ea864.png)
Возможны два режима передачи данных: одним блоком (block read operation) и несколькими блоками сразу (multiple block read operation). В любом случае, старт передачи и ее завершение происходят по специальной команде, обратите внимание, с откликом.
![](https://habrastorage.org/getpro/habr/post_images/8d4/d4f/13d/8d4d4f13dd1e5154bcfa6c19a64f36f9.png)
Обратная процедура (запись) осуществляется аналогичным образом, только между пачками обязательно присутствует busy, сигнализирующий о неготовности карты принять следующий блок (данные еще не записались во флэш карты).
Инициализация SD Карты памяти
Ну мы, эмбеддеры, люди привыкшие, что все надо инициализировать, поэтому SD карта не является исключением из этого великолепнейшего правила. Нужно прочекать поддерживаемые напряжения, назначить адреса, и вообще, убедиться, что мы можем работать с данной картой. Посмотрим на алгоритм инициализации, вытащенный из спецификаии и пройдемся по нему по порядку, блок за блоком, дабы понять, что нужно сделать с девайсом перед использованием по назначению.
![](https://habrastorage.org/getpro/habr/post_images/b94/f95/5c6/b94f955c60bf21b9a4aa7e15da79c512.png)
ВАЖНО : инициализация проводится на низко скоростном режиме! Частота клока карты не более 400 кГц!!!
ЕЩЕ ВАЖНО: после подачи питания, не спешим грузить карту командами, дадим напряжению устаканиться, подождем 250 миллисекунд (можно чуть подольше, в зависимости от числа карт, подключенных к одной шине, ширины линии данных и параметров источник питания). Как только вольтаж вышел на необходимый уровень, зарядились все паразитные емкости и т.д. и т.п., можно творить процесс инициализации. А начинается он с обнуления всех карт и перевода их в Idle State.
• Шлем CMD0, обратим внимание, без аргумента и не ждем ничего в отклике. В результате все карты на линии передут в холостой режим.
• Помните, я говорил, что напряжение нужно валидировать? Правильно! Нужно сказать карте, на каком вольтаже мы работаем и выслушать от нее все по этому поводу. Шлем CMD8 с аргументом, в котором биты 11:8 означают напряжение хоста и биты 7:0 – check pattern (проверочный шаблон) – любой, спецификация рекомендует слать 10101010. Биты напряжения ставятся в соответствии с таблицей:
![](https://habrastorage.org/getpro/habr/post_images/f03/c95/778/f03c957783dc110e9a03ace2fe607f2e.png)
Ну, у нас все очень даже определено и далеко не Low Voltage Range. Stm32f4 выдает как раз напряжение в диапазоне 2.7 – 3.6 V, так что ставим 1 на восьмом бите аргумента. Итого, имеем команду с аргументом 110101010. Отправили. Прочекали, что все отправилось хорошо и ждем ответа, он не заставит нас делать это долго. В спецификации увидели, что ответ на эту команду – R7 типа.
Если мы его так и не дождались, то дальнейшая команда ACMD41 решит, как именно нас надули – подсунули карту версии 1.X стандартной емкости или вообще не SD карту. Правда есть вероятность, что мы просто что-то не так делаем. Но не будем о грустном, и предположим, что флешка все таки ответила. Если с напряжением все хорошо, карта довольна, мы довольны, ответ будет содержать в себе все то, что мы отправили в аргументе, то есть 110101010. Это называется valid esponse. Если так, переходим к дальнейшему шагу, иначе – опять же – либо надули, либо где-то косяк.
• Дождались 110101010, и пришло время непосредственной инициализации – команды ACMD41. И тут вспоминаем ВАЖНОСТЬ: чтобы сказать карточке, что команда не простая, а ACMD, отправим сперва CMD55. В аргументе обязательно указываем, адрес той карты, для которой эта команда предназначена. Но стоп, у нас, ведь, пока нет адреса, мы его не знаем. Ничего, узнаем … но по позже, а пока пишем нули и шлем. Получив ответ типа R1 удостоверяемся, что карта готова принимать ACMD и только после этого шлем 41 индекс! Команда идет с аргументом, в котором на месте 30-ого бита указываем 1, что говорит о поддерживаемости хостом SDHC карт и напряжения хоста на месте 23:0 битов (см. содержимое OCR регистра). Ответ ждем R3 типа. Здесь нам важно получить 1 на месте 31ого бита в пачке ответа, несущей содержимое OCR регистра карты (флаг busy). Это будет говорить о том, что карта завершила процесс инициализации. Виду того, что этот процесс длится долго (гораздо дольше, чем тактовый цикл микроконтроллера), необходимо слать ACMD41 в цикле до тех пор, пока не получим ответ со снятым флагом занятости. Как только это случилось, чекам 30 бит, и, если он единица, то имеем карту повышенной емкости SDHC или SDXC, и карту стандартной емкости SD в противном случае. Если же мы ждали, ждали, а флаг бизи все висит и висит, то, как и в описанном выше случае – либо неподходящая карта, либо (скорее всего) наш косяк.
• Далее шлем CMD2 — без аргумента и смотрим на ответ R2. В этом случае он будет нести информацию о содержимом CID регистра, и мы сможем вычитать ID производителя, серийный номер карты и прочую информацию.
• И, наконец, заключительный шаг – получение адреса карты (RCA — relative
address). Как оно уже упоминалось, к одной шине может быть подключено несколько карт, поэтому каждая должна иметь свой уникальный локальный адрес. Шлем CMD3 и получаем ответ типа R6, в котором в младших 16 битах содержится статус карты, а в старших – новый RCA адрес. Отныне, для доступа к нашей карточке, мы должны будем звать ее по имени, то есть по RCA адресу.
• Опциональный пункт. По умолчанию карта работает с 1-битной шиной данных, что, ясное дело, медленнее, чем с 4-х битной. Если мы хотим достичь максимального быстродействия – шлем ACMD6, с предшествующей CMD55, конечно же. Но прежде, нужно перевести карту в состояние Transfer State (см. ниже) командой CMD7 с RCA в качестве аргумента. В аргументе ACMD6 на месте самого первого бита пишем 1 – если хотим включить 4-битный мод и 0 – для отключение. Ответ R1 скажет об успешном проведении операции.
Пример инициализации 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).
![](http://habrastorage.org/files/24c/646/618/24c646618e3846339f426c3ce8695efa.png)
Существует 6 статусов карты в этом режиме и узнать их можно в отклике R1 на месте 12:9 битов. Обратимся к спецификации.
![](http://habrastorage.org/files/a86/221/cd2/a86221cd2617448799fc83c4d842ddc3.png)
• 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.
Запись данных на карту
Итак, после успешной инициализации мы находимся в состоянии tran, во всяком случае, должны находиться. Смотрим по диаграмме: для того, чтобы перейти на состояние rcv, нужно послать команду CMD24 с адресом 512 байтной ячейки, которую хотим записать. Послали. Карта перешла в режим ожидания данных. Далее начинаем кидать ей информацию по шине данных, пока не перекинем все 512 байт или не пошлем команду CMD12 (стоп передачи). После завершения акта, карточка сама переедет в состояние prg и пробудет там некоторое время (пока данные запишутся). Ждем.… Как имено ждем? А посылаем ей в цикле CMD13 с адресом карты в аргументе, до тех пор, пока не вернется в отклике R1 типа статус tran. Когда это, наконец, случилось можно слать очередную пачку данных, вновь послав CMD24. Кроме того, существует еще режим записи несколькими блокам сразу (CMD25) и другие режимы – за подробностью – в спецификацию.
Чтение данных
Дабы выполнить обратную процедуру, в первую очередь, убеждаемся, что карта стоит в tran. Шлем CMD17 с адресом RCA в аргументе. Если все пройдет успешно – карточка переедет в состояние data и начнет выдавать на линии данных информацию, опять же 512 байтным блоком. Задача хоста в это время внимательно слушать линию и считывать данные. Как только посылка закончится, карта сама переедет в статус tran. Думаю, не стоит и говорить о том, что считывание так же как и запись возможна несколькими блоками сразу.
Не буду приводить в этой статье листинг программы чтения/записи, так как он, в отличии от подпрограммы инициализации слишком сильно завязан на железе SDIO модуля микроконтроллера Stm32f4, а это – тема второй части статьи.
Литература:
1) SD_Physical_Layer_Spec 2.0
2) SD_Physical_Layer_Spec 4.1
3) STM32F4 CortexM4 Reference manual