В устройствах на микроконтроллерах для хранения больших объемов данных используется внешняя память. Если требуется хранить единицы мегабайт, то подойдут микросхемы последовательной флэш памяти. Однако для больших объемов (десятки -сотни мегабайт) обычно применяются какие-нибудь карты памяти. В настоящий момент наибольшее распространение получили SD и microSD карты, о них я и хотел бы поговорить в серии материалов. В этой статье речь пойдет о подключении SD карт к микроконтроллеру, а в следующих мы будет разбираться как читать или записывать на них данные.
SD карты могут работать в двух режимах - SD и SPI . Назначение выводов карт и схема подключения зависит от используемого режима. У 8-и разрядных микроконтроллеров AVR нет аппаратной поддержки SD режима, поэтому карты с ними обычно используются в режиме SPI. В 32-х разрядных микроконтроллерах на ядре ARM, например AT91SAM3, интерфейс для работы с картами в SD режиме есть, поэтому там можно использовать любой режим работы.
Назначение контактов SD карты в SD режиме
Назначение контактов SD карты в SPI режиме
Назначение контактов microSD карты в SD режиме
Назначение контактов microSD карты в SPI режиме
Напряжение питания SD карт составляет 2.7 - 3.3 В. Если используемый микроконтроллер запитывается таким же напряжением, то SD можно подключить к микроконтроллеру напрямую. Расово верная схема, составленная путем изучения спецификаций на SD карты и схем различных отладочных плат, показана на рисунке ниже. По такой схеме подключены карты на отладочных платах фирм Olimex и Atmel .
На схеме обозначены именно выводы SD карты, а не разъема.
L1 - феррит или дроссель, рассчитанный на ток >100 мА. Некоторые его ставят, некоторые обходятся без него. А вот чем действительно не стоит пренебрегать, так это полярным конденсатором C2. Потому что при подключении карты происходит бросок тока, напряжение питания "просаживается" и может происходить сброс микроконтроллера.
По поводу подтягивающих резисторов есть некоторая неоднозначность. Поскольку SD карты выпускаются несколькими производителями, на них существует несколько спецификаций. В одних документах четко указана необходимость подтягивающих резисторов (даже для неиспользуемых линий - 8, 9), в других документах этих указаний нет (или я не нашел).
Упрощенный вариант схемы (без подтягивающих резисторов) показан на рисунке ниже. Эта схема проверена на практике и используется в платах фирмы Microelectronika. Также она используется во многих любительских проектах, которые можно найти в сети.
Здесь сигнальные линии SD карты удерживаются в высоком состоянии микроконтроллером, а неиспользуемые линии (8, 9) никуда не подключены. По идее они должны быть подтянуты внутри SD карты. Далее я буду отталкиваться от этой схемы.
Если микроконтроллер запитывается напряжением отличным от напряжения питания SD карты, например 5 В, то нужно согласовать логические уровни . На схеме ниже показан пример согласования уровней карты и микроконтроллера с помощью делителей напряжения. Принцип согласования уровней простой - нужно из 5-и вольт получить 3.0 - 3.2 В.
Линия MISO - DO не содержит делитель напряжения, так как данные по ней передаются от SD карты к микроконтроллеру, но для защиты от дурака можно добавить аналогичный делитель напряжения и туда, на функционировании схемы это не скажется.
Если использовать для согласования уровней буферную микросхему, например CD4050 или 74AHC125, этих недостатков можно избежать. Ниже приведена схема, в которой согласование уровней выполняется с помощью микросхемы 4050. Это микросхема представляет собой 6 неинвертирующих буферов. Неиспользуемые буферы микросхемы "заглушены".
Подключение microSD карт аналогичное, только у них немного отличается нумерация контактов. Приведу только одну схему.
На схемах я рассматривал подключение SD карт к микроконтроллеру напрямую - без разъемов. На практике, конечно, без них не обойтись. Существует несколько типов разъемов и они друг от друга немного отличаются. Как правило, выводы разъемов повторяют выводы SD карты и также содержать несколько дополнительных - два вывода для обнаружения карты в разъеме и два вывода для определения блокировки записи. Электрически эти выводы с SD картой никак не связаны и их можно не подключать. Однако, если они нужны, их можно подключить как обычную тактовую кнопку - один вывод на землю, другой через резистор к плюсу питания. Или вместо внешнего резистора использовать подтягивающий резистор микроконтроллера.
Ну и для полноты картины приведу схему подключения SD карты в ее родном режиме. Он позволяет производить обмен данными на большей скорости, чем SPI режим. Однако аппаратный интерфейс для работы с картой в SD режиме есть не у всех микроконтроллеров. Например у Atmel`овских ARM микроконтроллеров SAM3/SAM4 он есть.
Шина данных DAT может использоваться в 1 битном или 4-х битном режимах.
Продолжение следует...
SD cards are based on the older Multi Media Card (MMC) format, but most are physically slightly thicker than MMC cards. They also boast higher data transfer rates. DRM features are available but are little-used. SD cards generally measure 32 mm × 24 mm × 2.1 mm, but can be as thin as 1.4 mm, just like MMC cards.
There are different speed grades available. They are referred to with the same nx notation as CD-ROMs; a multiple of 150 kB/s. Devices with SD slots can use the thinner MMC cards, but the standard SD cards will not fit into the thinner MMC slots. MiniSD and MicroSD cards can be used directly in SD slots with an adapter. There are readers which allow SD cards to be accessed via many connectivity ports such as USB, FireWire.
Pin | SD Mode | SPI Mode | ||||
Name | Type | Description | Name | Type | Description | |
1 | CD/DAT3 | I/O/PP | Card detection / Connector data line 3 | CS | I | Chip selection in low status |
2 | CMD | PP | Command/Response line | DI | I | Data input |
3 | Vss1 | S | GND | VSS | S | GND |
4 | Vdd | S | Power supply | VDD | S | Power supply |
5 | CLK | I | Clock | SCLK | I | Clock |
6 | Vss2 | S | GND | VSS2 | S | GND |
7 | DAT0 | I/O/PP | Connector data line 0 | DO | O/PP | Data output |
8 | DAT1 | I/O/PP | Connector data line 1 | RSV | ||
9 | DAT2 | I/O/PP | Connector data line 2 | RSV |
SD cards interface is compatible with standard MMC card operations. All SD memory and SDIO cards are required to support the older SPI/MMC mode which supports the slightly slower four-wire serial interface (clock, serial in, serial out, chip select) that is compatible with SPI ports on many microcontrollers. Many digital cameras, digital audio players, and other portable devices probably use MMC mode exclusively. MMC mode does not provide access to the proprietary encryption features of SD cards, and the free SD documentation does not describe these features. As the SD encryption exists primarily for media producers, it is not of much use to consumers who typically use SD cards to hold unprotected data.
There are three transfer modes supported by SD: SPI mode (separate serial in and serial out), one-bit SD mode (separate command and data channels and a proprietary transfer format), and four-bit SD mode (uses extra pins plus some reassigned pins) to support four bit wide parallel transfers. Low speed cards support 0 to 400 kbit/s data rate and SPI and one-bit SD transfer modes. High speed cards support 0 to 100 Mbit/s data rate in four-bit mode and 0?25 Mbit/s in SPI and one-bit SD modes.
SD cards security features includes:
Урок 33
Часть 1
Сегодня мы продолжим нашу любимую тему по интерфейсу SPI. Закончили мы с данной шиной друг к другу контроллеров Atmega8a и ATTtiny2313. А сегодня мы по данному интерфейсу попробуем подключить к микроконтроллеру по данной шине карту памяти SD (Secure Digital) .
Данная карта может подключаться также по интерфейсу SDIO, но так как такой интерфейс не поддерживается аппаратно нашим контроллером, то в рамках данного занятия мы его касаться не будем. Нам интересен именно тип подключения по шине SPI , так как у нас уже есть неплохие накопленные знания по данной теме, а также аппаратная поддержка в контроллере, который мы программируем.
Тем не менее мы посмотрим распиновку ножек карты по обоим типам
Ну, так как нас интересует второй тип, с ним и будем разбираться.
А разбираться тут особо не в чем. Все эти аббревиатуры нам известны. Здесь все стандартные ножки интерфейса SPI и ничего тут лишнего нет.
Теперь вообще про карту. Данная карта нам позволяет хранить данные, тип памяти у неё FLASH, который по сравнению с памятью типа EEPROM также является энергонезависимым, то есть при отключении питания данные никуда не пропадают, а остаются храниться. Также данная память имеет отличия, мы с ними познакомимся в процессе программирования. Одно из главных отличий то, что мы уже как в память EEPROM в данную память не можем записать один байт. Теоретически то конечно можем, но только запишутся туда либо только единички из нашего байта либо только нули в зависимости от типа FLASH — NOR или NAND. То есть прежде чем писать байт, нужно его стереть, а в силу организации данной памяти, стирать мы можем только блоками, вот и писать следовательно также только блоками. Но зато есть величайшее отличие от EEPROM — это цена. Она в разы дешевле, даже порой на порядки за одну хранящуюся единицу инфорамции (за мегабайт, за гигабайт). Поэтому у памяти FLASH как правило всегда гораздо больший объём информации.
Существуют 4 типа SD, но это мы изучим немного позднее.
Подключим данную карту пока в протеусе
Здесь всё просто. На самом деле не совсем так. Нужны ещё резисторы
Данные резисторы нужны для того, чтобы обеспечить соответствующие уровни, так как карта питается от 3,3 вольт. Вообще по технической документации от 2,7 до 3,6 вольт.
Также в протеусе не указано, а на самом деле мы будем питать нашу карту от отдлеьного питания, поставив микросхему, преобразующую 5 вольт в 3,3 вольт.
Вернее, мы не будем ничего ставить, а будем использовать готовый модуль, в котором уже всё установлено.
Также у нас подключен дисплей, как и на по расширению функционала библиотеки дисплея.
Вот так у нас всё выглядит в практической схеме
Вот так вот выглядит модуль с держателем
Найти такой модуль можно везде, стоит он копейки. Тот модуль, который конектится по SDIO, стоит дороже. Мы видим также, что на модуле уже установлена микросхема для понижения напряжения до 3,3 вольта. А подключаем питание мы только на контакт 5 вольт, а на 3,3 не подключаем ничего
Также на модуле установлены все делители для уровней, то есть данный модуль рассчитан именно на подключение к 5-вольтовым устройствам.
А флеш-карту для тестов я откопал на 32 мегабайта, именно мегабайта а не гигабайта
Данная флеш-карта была подарена вместе с каким-то фотоаппаратом и она нам лучше всего подойдёт для наших тестов, по крайней мере мы не будем думать, что тот или иной глюк у нас из-за слишком большого размера памяти на карте.
Код был весь взят также с прошлого занятия вместе с библиотекой дисплея, так как функцию, которую мы создали на прошлом уроке, мы будем очень активно использовать, только был конечно создан проект новый и назват соответственно MYSD_SPI .
Удалим ненужные строки, в main() у нас останется только во это
int main ( void )
unsigned int i ;
Port_ini ();
LCD_ini (); //инициализируем дисплей
Clearlcd (); //очистим дисплей
Setpos (0,0);
Str_lcd ( "String 1" );
Setpos (2,1);
Str_lcd ( "String 2" );
Setpos (4,2);
Str_lcd ( "String 3" );
Setpos (6,3);
Str_lcd ( "String 4" );
Delay_ms (2000);
// for (i=0;i<=22;i++) {str80_lcd(buffer2+i*20);_delay_ms(1000);}
While (1)
Так как мы посимвольно не будем выводить текст, то можно будет в переменной обойтись типом char
unsigned char i ;
Теперь ещё один нюанс.
Чтобы нам работать с SD-картой в протеусе, нам мало добавить сам держатель с картой, необходимо также в его свойствах прикрепить файл образа флеш-карты.
Создать его не сложно. Одним из способов является создание с помощью программы WinImage.
Мы в ней стандартно создаём новый файл с помощью пункта меню File — > New. Выбираем в диалоге самый последний пункт и жмём "OK"
Для теста в протеусе нам вполне хватит размера 4 мегабайта, поэтому поменяем в следующем диалоге поле с номером секторов, а также выберем формат FAT12/16, потому что с 32-битной файловой системой немного другая специфика работы, и также нажмём "OK"
Вообще мы конечно можем оставить и FAT32, так как мы с файловой системой пока не работает, но в дальнейших частях занятия будет работа с файловой системой и мы будем именно работать с 12/16.
Затем мы сохраняем наш созданный файл с помощью пункта меню File -> Save As. И сохраняем мы его в ту папку, где у нас находится сохранённый проект протеуса. Назовём файл и нажмём "Сохранить"
Также затем нужно будет убедиться, что данный файл у нас получился не с аттрибутом "только для чтения" и после этого мы уже сможем его подключить в протеусе. Надо будет вручную вписать имя файла, так как протеус требует какой-то свой формат и наш файл будет просто не виден
Путь нам никакой не нужен, так как файл у нас находится в папке с проектом. Жмём "ОК".
Инициализация шины нам не нужна, так как у нас SPI будет программный, с аппаратным флеш-карты работают корректно не все, то нам не надо будет использовать никаких регистров. Аппаратный конечно, лучше, но чтобы уяснить работу протокола досконально, надо ещё поработать и с программным, то есть подрыгать ножками портов. Вообще, глядя на схему, может показаться, что у нас всё аппаратно, так как я именно такие ножки выбрал, это потому, что я просто так выбрал, чтобы впоследствии когда-то может быть кто-то попытается всё-таки поработать с аппаратной реализацией шины.
Добавим макроподстановки для ножек порта
#include "main.h"
#define MOSI 3
#define MISO 4
#define SCK 5
#define SS 2
Добавим код для инициализации ножек в функцию инициализации портов
void port_ini ( void )
PORTD =0x00;
DDRD =0xFF;
PORTB |=(1<< SS )|(1<< MISO )|(1<< MOSI );
DDRB |=(1<< SS )|(1<< MOSI )|(1<< SCK );
Мы оставляем на вход ножку MISO, так как по умолчанию все биты в регистре равны нулю, и мы его просто не трогает. Также мы включаем сразу высокий уровень в MOSI и SS, а к MISO подтягиваем резистор.
Напишем функцию передачи байта по шине SPI
void SPI_SendByte ( unsigned char byte )
{
}
Добавим переменную для цикла и сам цикл
void SPI_SendByte ( unsigned char byte )
unsigned char i ;
for ( i =0; i <8; i ++) //движемся по битам байта
{
}
Я думаю, понятно почем мы считаем до 8, так как битов мы передаём именно 8.
Ну и начнём их передавать потихоньку.
Проверим сначала самый левый бит, выделив его из всего байта маскированием, и, если он у нас равен 1, то выставим 1 и на шине MOSI, а если 0 — то не трогаем шину
for ( i =0; i <8; i ++) //движемся по битам байта
Как видно из рисунка после передачи кадра команды необходимо продолжать чтение байтов (Ncr) от microSD до получения ответа (R1), при этом уровень CS должен быть активным "0".
В зависимости от индекса команды ответ может быть не только R1 (см. набор основных команд) на CMD58 ответ R3 (R1 и завершающее 32-битное значение OCR), а некоторым командам нужно больше времени NCR и они ответ будет R1b . Это ответ R1, за которым идет флаг занятости (сигнал на линии "DO" удерживается картой в низком уровне, пока продолжается внутренний процесс). Контроллер хоста должен ждать окончания процесса, пока "DO" не перейдет в состояние высокого уровня (т.е. дождаться 0xFF). А так же R2 при запросе состояния регистра STATUS.
Ответ R1 содержит 1 байт, его структуру можно посмотреть в таблице ниже. Ответ R2 состоит из двух байт, первый байт R1 и второй R2 (см. таблицу структуры R2). А ответ R3 соответственно из 5 байт.
Ответ R1 при значении 0х00 означает успешное завершение команды, иначе будет установлен соответствующий флаг.
Структура ответа R1.
Структура ответа R2.
Инициализации в режиме SPI.
После сброса и подачи питания карта по умалчиванию устанавливается в режим работы по протоколу MMC (Serial Peripheral Interface), для перевода в режим SPI необходимо сделать следующее:
Напомню, что команда CMD0 должна содержать корректное поле CRC. Рассчитывать нет смысла, так как аргументов в этой команде нет, по этому оно постоянно и имеет значение 0х95. Когда карта войдет в режим SPI, функция CRC будет отключена и не будет проверяться. Опция CRC может быть снова включена командой CMD59.
В результате команда CMD0 будет выглядеть так: 0х40,0х00,0х00,0х00,0х00,0х95.
Что касается 80 импульсов, то их можно сформировать передавая по SPI значение 0хFF 10 раз подряд при установленных высоких уровнях на линиях DI и CS.
После простоя более 5 мс карта памяти переходит в энергосберегающий режим, и способна принимать только команды CMD0, CMD1 и CMD58. По этому процесс инициализации (CMD1) необходимо практически каждый раз повторять при чтении/записи блока данных или делать проверку состояния карты.
Для SDC-карт в случае отклонения команды CMD1 рекомендуется использовать команду ACMD41.
Сам процесс инициализации может занять относительно длительное время (в зависимости от объема карты) и может достигать сотен миллисекунд.
Чтение и запись блока данных.
По умолчанию в режиме SPI обмен между микроконтроллером и картой ведется блоками по 512 байт, по этому для записи даже одного байта придется сначала прочитать весь блок и изменив байт перезаписать обратно. Размер блока может быть изменен в регистре CSD карты памяти.
Воизбежания ошибки адресации при выполнении команд чтения/записи необходимо что бы адрес указывался четко начала сектора. Для этого можно сбрасывать бит "0" 3 байта адреса сектора, т.е. делать его четным, а младший всегда должен иметь значение 0х00.
Чтение блока данных.
Алгоритм чтения блока данных следующий:
Значение CRC не обязательно, но процедура принятия (передача 0хFF от МК) необходима.
Чтение блока.
Запись блока данных.
Алгоритм записи блока данных следующий:
Блок данных может быть меньше 512 байт при изменении длины блока командой CMD16.
Значение CRC не обязательно, но процедура передачи любыми значениями необходима.
Оценку простоя можно программно и не делать, а сразу давать команду инициализации. При программной реализации столкнулся с некорректной записью, почему то все байты были записаны в сектор со сдвигом влево. Проблему удалось решить, только передавая стартовый бит (0xFЕ) два раза.
Запись блока.
Байт подтверждения при записи блока данных.
Запись/чтение нескольких блоков подряд.
При помощи команд CMD18 , CMD25 можно прочитать/записать несколько блоков подряд или так называемое многоблочное чтение/запись. Если не было задано количество блоков, то процесс чтения/записи можно остановить командами CMD12 при чтении, а так же передачей маркера "Stop Tran " при записи соответственно.
Практическое применение.
Практическое применение карт памяти довольно широко. В последней своей конструкции задействовал microSD для записи показаний с различных датчиков (температуры, сигнализации) в течении дня каждый час. Данные сохраняются следующим образом:
В результате упрощается поиск данных по дате, достаточно просто перевести запрос в адрес сектора и выполнить чтение с карты. При таком методе данные можно хранить в течении нескольких лет. Правда есть и недостатки, остается достаточно много неиспользованного места. Хотя при желании можно использовать для других задач.
Кому надо скину фрагмент кода на ассемблере для 18 пиков.
Вопросы можно задать на ..
Сегодня SD-карты используются повсюду. Они втыкаются в ноутбуки, планшеты, телефоны, видеокамеры, роутеры, фоторамки, диктофоны, электронные книги, mp3-плееры, одноплатные компьютеры и даже квадрокоптеры — словом, они везде. Часто о них думают, как об относительно медленных устройствах, способных хранить пару гигабайт информации. Однако в наши дни уже доступны SD-карты объемом 512 Гб и скоростью чтения-записи 90 Мбайт/сек (не мегабит!). Теоретически же объем хранимой информации ограничен 2 Тб. А чем еще прекрасны SD-карты, это тем, что с ними можно работать по незамысловатому протоколу, основанному на SPI.
«SD» расшифровывается как «Secure Digital». Причем тут безопасность не знает никто. Внутри SD-карты находится обычная flash-память и микроконтроллер, осуществляющий общение с внешним миром. То есть, в первом приближении, это точно такая же non-volatile память, как и SPI flash .
SD-карты бывают трех типов. Карты SDSC (SC = Standard Capacity) позволяют хранить до 2 Гб информации и используют файловую систему FAT12 или FAT16. Эти карты морально устарели, в магазинах их найти непросто, да и по цене они сопоставимы с картами большего объема. Кроме того, они используют протокол, несколько отличающийся от протокола SDHC/SDXC-карт. В силу названных причин, с этого момента про существование SDSC мы забудем. К современным типам карт относятся SDHC (HC = High Capacity), использующие файловую систему FAT32 и способные хранить до 32 Гб данных, а также SDXC (XC = eXtended capacity), использующие exFAT и имеющие объем до 2 Тб. С точки зрения протокола эти карты неотличимы. Разница заключается только в файловой системе, выбор которой диктуется спецификацией.
Разумеется, ничто не мешает отформатировать SDHC карту под exFAT, или SDXC карту под какой-нибудь ZFS. Но ваш смартфон или фотоаппарат, вероятно, не сможет работать с такой картой.
Fun fact! Встречаются поддельные SDHC карты, которые на самом деле являются SDSC. В обычном магазине вы такие, скорее всего, не найдете, а вот на eBay налететь можно. Если вам предлагают купить типа SDHC карту объемом всего лишь 1 Гб , она наверняка на самом деле является SDSC.
Карты разделяют на различные классы, в зависимости от минимальной последовательной скорости записи (обратите внимание на выделение курсивом). Класс скорости обозначают в стиле C4 (class 4) или V30 (class 30). В обоих случаях цифра означает скорость в Мбайт/сек. Отличие C от V заключается только в том, что V намекает на пригодность карты для записи видео высокого разрешения. Еще встречаются маркировки U1 и U3 для 10 Мбайт/сек и 30 Мбайт/сек соответственно, где U означает Ultra High Speed. C10, V10 и U1 — это одно и то же.
SD-карты бывают разных форм-факторов — SD, MiniSD и MicroSD. MiniSD сегодня практически не встречаются. Многие карты выпускаются в форме MicroSD с переходником в обычный SD-формат. Это позволяет покупателям использовать карты с различными устройствами.
На следующем фото изображена моя небольшая коллекция SD и MicroSD-карт, а также модулей для подключения их к отладочным платам (Arduino, Nucleo и подобным):
Все представленные здесь модули работают одинаково хорошо. Если сомневаетесь, какой брать — берите тот, что изображен слева внизу. Он позволяет работать как с SD, так и с MicroSD-картами (через переходник), а также имеет дополнительные пины для подключения логического анализатора . Модуль не составляет труда найти на eBay. Еще встречаются модули вообще без резисторов, стабилизаторов напряжения и так далее, имеющие только слот для подключения карты и пины. С ними некоторые карты работать не будут! Далее станет понятно, почему. Наконец, модуль легко спаять из адаптера для MicroSD-карт. Далее будет рассказано, как.
Ниже изображена распиновка SD и MicroSD-карт (иллюстрация позаимствована отсюда):
Наибольший интерес для нас представляет правая колонка. На первый вгляд, все просто — смотрим на картинку, хоть напрямую припаиваемся к карте проводами, и начинаем слать и принимать байты по SPI. Но есть ряд важных моментов:
Теперь становится понятно, почему простые модули, имеющие только слот для подключения карты, не очень подходят. Также теперь ясно, как сделать модуль для подключения MicroSD-карт из адаптера. Отмечу, что пины с землей (VSS1 и VSS2) в адаптере, как правило, уже соединены между собой, поэтому дополнительно соединять их проводочком не требуется. На всякий случай стоит перепроверить, соединены ли пины, прозвонив их мультиметром.
Хорошее описание протокола было найдено в статье How to Use MMC/SDC на сайте elm-chan.org. Здесь я не вижу смысла ее пересказывать. Заинтересованные читатели могут ознакомиться с оригиналом, а также с полной реализацией протокола для микроконтроллеров STM32 в исходниках к данному посту. Вместо пересказа я лишь пробегусь по основным моментам. Также отмечу, что в статье я не нашел упоминание нескольких крайне важных нюансов, про которые будет рассказано далее.
Итак, типичная команда выглядит как-то так:
Команды всегда имеют формат 01xxxxxx, и в соответствии со значением битов xxxxxx называются CMD0, CMD1, и так далее до CMD63. Следом за командой идут 4 байта аргумента, за которыми идет байт в формате yyyyyyy1 с семибитным CRC. Контрольные суммы по умолчанию выключены и проверяются только для первых нескольких команд на этапе инициализации. В остальных же случаях CRC заполняется единицами.
Большинство команд получают в ответ один байт, так называемый R1:
/*
R1: 0abcdefg
||||||`- 1th bit (g): card is in idle state
|||||`-- 2th bit (f): erase sequence cleared
||||`--- 3th bit (e): illigal command detected
|||`---- 4th bit (d): crc check error
||`----- 5th bit (c): error in the sequence of erase commands
|`------ 6th bit (b): misaligned addres used in command
`------- 7th bit (a): command argument outside allowed range
(8th bit is always zero)
*/
Если старший бит ответа равен единице, значит SD-карта еще обрабатывает запрос. Иногда за R1 следуют дополнительные данные. Также в определенных ситуациях в протоколе фигурируют data tokens (байты 0 x FC, 0 x FE), stop transaction token (0 x FD), error token и data response. Детали не слишком захватывающие, к тому же, они хорошо описаны на elm-chan.org и их можно изучить по коду. Куда интереснее то, чего в статье нет или обозначено не слишком явно.
Во-первых, вы можете помнить, что в SPI за один такт SCLK одновременно принимается и передается один бит информации. Так вот, оказывается, что если при чтении ответа от SD-карты случайно послать по SPI что-то отличное от единиц, некоторым SD-картам это рвет башню. Поэтому прием данных от карты выглядит как-то так:
static
int
SDCARD_ReadBytes(uint8_t
*
buff,
size_t
buff_size)
{
// make sure FF is transmitted during receive
uint8_t
tx =
0xFF
;
while
(buff_size >
0
)
{
HAL_SPI_TransmitReceive(&
SDCARD_SPI_PORT,
&
tx,
buff,
1
,
HAL_MAX_DELAY)
;
buff++;
buff_size--;
}
return
0
;
}
Во-вторых, в статье верно описано, что в определенных случаях карта может помечать себя занятой (busy), притягивая MISO к земле. В таких ситуациях нужно дождаться готовности карты. Но на практике оказалось, что проверку на готовность нужно выполнять перед каждой командой, даже если в текущих обстоятельствах карта не может быть занятой. То есть, по сути, перед каждой командой нужно посылать 0 x FF (на иллюстрации с форматом команд этот момент опущен). Иначе некоторые карты отказываются работать. В частности, я наблюдал такое поведение у карт производства SanDisc.
Соответствующая проверка:
static
int
SDCARD_WaitNotBusy()
{
uint8_t
busy;
do
{
if
(SDCARD_ReadBytes(&
busy,
sizeof
(busy)
)
<
0
)
{
return
-
1
;
}
}
while
(busy !=
0xFF
)
;
return
0
;
}
Fun fact! Понять я это смог, подглядев в Arduino-библиотеку SD. Пользуясь случаем, отмечу, что библиотека эта в целом довольно скверная. Мне не кажется очень хорошей идеей мешать в одну кучу код для SDSC и SDHC/SDXC карт, как сделано в этой библиотеке. Также я заметил, что в ней почему-то отсутствует поддержка CMD18 (READ_MULTIPLE_BLOCK), несмотря на то, что CMD25 (WRITE_MULTIPLE_BLOCK) реализована. И еще библиотека отказалась работать с некоторыми имеющимися у меня картами, несмотря на то, что код, написанный мной с нуля, прекрасно с ними работает. Вот и пользуйся после этого готовыми библиотеками!
Наконец, в третьих, карта может делить SPI-шину с другими устройствами. Понятно, что в этом случае первым делом после запуска прошивки нужно пометить все устройства, как неактивные, подав соответствующее напряжение, обычно высокое, на пины CS. После чего уже можно спокойно общаться с каждым устройством по отдельности, не беспокоясь, что какое-то другое устройство по ошибке решит, что обращались с нему. Но проблема заключается в том, что SD-карта определенным образом интерпретирует данные, передаваемые по SPI, даже не являясь выбранным устройством. Если конкретнее, то при инициализации карты нужно передать 74 или больше единицы (например, 10 байт 0 x FF) с высоким напряжением на CS. По этой причине карта либо должна жить на отдельной шине, либо инициироваться перед всеми остальными устройствами. Иначе карта может отказаться работать, я проверял.
В ходе изучения мной протокола SD-карт была написана библиотека для STM32, реализующая этот протокол. Библиотека основана на HAL и имеет следующий интерфейс:
#define SDCARD_SPI_PORT hspi1
#define SDCARD_CS_Pin GPIO_PIN_5 // Arduino shield: D4
#define SDCARD_CS_GPIO_Port GPIOB
extern
SPI_HandleTypeDef SDCARD_SPI_PORT;
// call before initializing any SPI devices
void
SDCARD_Unselect()
;
// all procedures return 0 on success, < 0 on failure
// size of block == 512 bytes
int
SDCARD_Init()
;
int
SDCARD_GetBlocksNumber(uint32_t
*
num)
;
int
SDCARD_ReadSingleBlock(uint32_t
blockNum,
uint8_t
*
buff)
;
int
SDCARD_WriteSingleBlock(uint32_t
blockNum,
const
uint8_t
*
buff)
;
// Read Multiple Blocks
int
SDCARD_ReadBegin(uint32_t
blockNum)
;
int
SDCARD_ReadData(uint8_t
*
buff)
;
int
SDCARD_ReadEnd()
;
// Write Multiple Blocks
int
SDCARD_WriteBegin(uint32_t
blockNum)
;
int
SDCARD_WriteData(const
uint8_t
*
buff)
;
int
SDCARD_WriteEnd()
;
SD-карты могут реализовывать дополнительные функции, такие, как очистка блоков и защита блоков от записи. Но они поддерживаются не всеми картами, и потому не реализованы. Также протокол позволяет включить проверку контрольных сумм. Но эта возможность не реализована, поскольку данные могут испортиться не только во время передачи, и потому их целостность должна проверяться выше уровня протокола, на уровне конкретного приложения. Дополнительно вычисляя и проверяя CRC на уровне протокола мы, скорее всего, только зря скушаем миллиамперы и займем flash-память. Да и вообще, я не убежден в надежности семибитных CRC.
В качестве источников дополнительной информации я бы рекомендовал следующие:
Полную версию исходников к этому посту вы найдете на GitHub . Обратите внимание, что тамошний пример кода пишет на карту на уровне блоков, ничего не зная ни о каких файловых системах. Поэтому, если решите его запускать, советую выбрать SD-карту без особо ценных данных.
Вооружившись полученными сегодня знаниями, можно реализовать много безумных идей. Например, можно сделать RAID из SD-карточек, или устройство с интерфейсом SD-карты, сжимающее и/или шифрующее данные. Или вообще отправляющее их на сервер по беспроводной связи. Конечно же, совершенно не был затронут вопрос работы с файловыми системами. Ему будет посвящена одна из следующих заметок.
На этом у меня пока все. А доводилось ли вам использовать SD-карты в своих проектах, и если да, то для каких задач?