§ Распиновка

Я использую SD-карту для подключения ее через SPI, и причем для Arduino. На картинке (не знаю, откуда взято), приведена распиновка для разных карт разных форм-факторов.
Для разных карт существуют разные выводные пины. Порт DI - это MOSI с точки зрения контроллера, а порт DO - это MISO. То, как расположены контакты, жутко странно, но что поделать, такова жизнь.
PINMicroSDSD
SDSPISDSPI
1DAT2 DAT3/CDCS
2DAT3/CD CSCMDDI
3CMD DIGnd2
4(+) VCC
5CLK
6(-) GND
7DAT0 DODAT0DO
8DAT1 DAT1
9 DAT2

§ Инициализация SPI

Что требуется для того, чтобы начать общаться с микроконтроллером, всаженном в sd-карту?
  • Для начала, надо настроить SPI на скорость 125 кГц (вообще от 100 кГц до 400 кГц разрешено).
  • Во-вторых, подать на пин CS=1 (высокий уровень), а также на MOSI=1 (т.е. DI у карты)
  • Далее, сделать минимум 74 тактов на CLK=0 и 1, подать LOW и HIGH аж 74 раза, и больше. Я делаю 80 тактов.
Когда эти все действия сделаны, то можно с некоторой уверенностью считать, что карта поняла, что мы перешли в режим SPI.

§ Определение типа карты

Это очень сложный и трудоемкий этап, который не нужно запоминать, но нужна конкретная шпаргалка. Здесь важен обмен командами, речь о которых позже. Вот так происходит определение.
ШАГОписание шага
1 Отсылка CMD0 с аргументом 0. Должен вернуть ответ 0x01. Если его не вернул, то амбец, карта не работает.
2 Отсылка CMD8 с аргументом 0x000001AA . Если в ответе установлен бит 2 (проверить через & 0x04), то это жестоко устаревшая в хлам карта версии 1, которая не поддерживает чтение и запись целыми блоками по 512 байт. Перейти к шагу 4
3 Принять 4 байта ответа от команды. Если последний байт ответа не равен 0xAA, то куница накрыла медным тазом карту и пора задуматься о будущем. Но если там есть эти 0xAA то карта считается версии 2.
4 Сначала отослать сначала CMD55 с аргументом 0. Если карта версии 2, то отослать CMD29 с аргументом 0x40000000 (ух ничего себе число, а), иначе отослать аргумент 0. Если ответ от карты не 0x00, то еще раз отослать ту же самую команду (повторить шаг 4), и повторять так, пока не опупеешь, или пока время не выйдет (например 2 секунды)
5 Если у нас карта версии 2, то отослать команду CMD58 с аргументом 0, потом прочесть байт, и если что-то вернется с карты, то приключилась какая-то байда и пора вырубать всё и ложиться - карта не работает.
6 Прочитать байт, если биты 6 и 7 будут установлены - это не просто версия 2 карты, это еще и SDHC (High Capacity), а именно современная карта, которая поддерживает чуть ли не 128 Гб памяти, вот так вот.
7 Прочесть 3 байта, игнорировать их
8 Установить CS=1 (деактивировать на время, чтобы не жрало питалово)
Вот такой вот лютый ад предстоит выполнить, чтобы все было хорошо (или не очень хорошо). Лично я проверил на своей Arduino Uno с Datalogging Shield, все работает. Код в студию:

§ Исходные коды

1// Инициализация карты
2void SD_init() {
3
4    unsigned char i, status = 0xFF;
5    unsigned long arg;
6
7    // Вначале все вроде как ОК
8    SD_error = SD_OK;
9    SD_type  = SD_CARD_TYPE_ERR;
10
11    // Отключить устройство
12    SPI_ce(1);
13
14    // Подать 80 тактов, которые переведут устройство в режим SPI
15    for (i = 0; i < 10; i++) SPI_put(0xFF);
16
17    // Сброс. Должен быть ответ 01h
18    if (SD_command(SD_CMD0, 0) != R1_IDLE_STATE) {
19        SD_error = SD_UnknownError; SPI_ce(1); return;
20    }
21
22    // Определить тип карты (SD1)
23    if (SD_command(SD_CMD8, 0x1AA) & R1_ILLEGAL_COMMAND) {
24        SD_type = SD_CARD_TYPE_SD1;
25
26    } else {
27
28        // Прочесть 4 байта, последний будет важный
29        for (i = 0; i < 4; i++) status = SPI_get();
30
31        // Неизвестный тип карты
32        if (status != 0xAA) {
33            SD_error = SD_UnknownCard; SPI_ce(1); return;
34        }
35
36        // Это тип карты SD2
37        SD_type = SD_CARD_TYPE_SD2;
38    }
39
40    // Инициализация карты и отправка кода поддержки SDHC если SD2
41    i   = 0;
42    arg = (SD_type == SD_CARD_TYPE_SD2 ? 0x40000000 : 0);
43
44    // Отсылка ACMD41 = 0x29. Отсылать и ждать, пока не придет корректный ответ
45    while ((status = SD_acmd(0x29, arg)) != R1_READY_STATE) {
46
47        // Если таймаут вышел
48        if (i++ > SD_TIMEOUT_CNT) {
49            SD_error = SD_AcmdError; SPI_ce(1); return;
50        }
51    }
52
53    // Если SD2, читать OCR регистр для проверки SDHC карты
54    if (SD_type == SD_CARD_TYPE_SD2) {
55
56        // Проверка наличия байта в ответе CMD58 (должно быть 0)
57        if (SD_command(SD_CMD58, 0)) {
58            SD_error = SD_Unknown58CMD; SPI_ce(1); return;
59        }
60
61        // Прочесть ответ от карты и определить тип (SDHC если есть)
62        if ((SPI_get() & 0xC0) == 0xC0) {
63            SD_type = SD_CARD_TYPE_SDHC;
64        }
65
66        // Удалить остатки от OCR
67        for (i = 0; i < 3; i++) SPI_get();
68    }
69
70    // Выключить чип
71    SPI_ce(1);
72}

§ Отсылка команды

Я не стал никого томить и код сразу оказался в студии:
1// Результат выполнения команды
2unsigned char SD_command(unsigned char cmd, unsigned long arg) {
3
4    int i = 0;
5    unsigned char status;
6    unsigned char crc = 0xFF;
7
8    // Перевести в активный режим
9    SPI_ce(0);
10
11    // Подождать ответ в течении определенного времени
12    while (i < SD_TIMEOUT_CNT) { status = SPI_get(); if (status == 0xff) break; i++; }
13
14    // Ошибка
15    if (i >= SD_TIMEOUT_CNT) { SD_error = SD_TimeoutError; return 0xff; }
16
17    // Отсылка команды
18    SPI_put(cmd | 0x40);
19
20    // Отослать 32-х битную команду
21    for (i = 24; i >= 0; i -= 8) SPI_put(arg >> i);
22
23    // Отправка CRC
24    if (cmd == SD_CMD0) crc = 0x95;  // CMD0 with arg 0
25    if (cmd == SD_CMD8) crc = 0x87;  // CMD8 with arg 0x1AA
26
27    SPI_put(crc);
28
29    // Ожидать снятия флага BUSY
30    for (i = 0; ((status = SPI_get()) & 0x80) && i != 0xFF; i++);
31
32    return status;
33}
34
35// Расширенная команда
36unsigned char SD_acmd(unsigned char cmd, unsigned long arg) {
37
38    SD_command(SD_CMD55, 0);
39    return SD_command(cmd, arg);
40}
А теперь объяснения насчет всего этого кода сверху. Он, если разобраться, не сложный
  • Сначала ставится CS=0 (LOW) Активация Чипа (и Дейла)
  • Далее принимаются байты, и принимаются до тех пор, пока а) не будет таймаут, что будет говорить о неготовности карты к близкому сотрудничеству, б) будет 0xFF на выходе - это говорит о готовности приема команд
  • Отсылается команда, как видно, команда может быть только от 0 до 63, потому что там старшие 2 бита это "01", такие дела.
  • Отсылается 4 байта (от старшего к младшего) от 32-х битного аргумента
  • Отсылается 1 байт CRC. Он для всех команд равен 0xFF но только для команды 0 и 8 он разный. Какой именно - смотри в коде.
  • Ну и собственно, ждем ответ, в котором старший бит будет равен 0. Может, кстати наступить таймаут (256 чтений ничего не дали - считай, закрывай гараж).
Вот тут, собственно, и всё. С инициализацией карты покончено. Теперь самое важное - чтение с карты. Ну или запись, кому как удобнее. С записью я пока ничего не делал, влом, но там все очень просто, там так же, как на чтение, только наоборот.

§ Чтение блока

Вот он, код на чтение пока что:
1// Читать блок 512 байт в память
2char SD_read(unsigned long lba) {
3
4    int i = 0;
5    unsigned char status;
6
7    // Кроме SDHC ничего не поддерживается
8    if (SD_type != SD_CARD_TYPE_SDHC)
9        return SD_UnsupportYet;
10
11    // Отослать команду поиска блока
12    if (SD_command(SD_CMD17, lba)) {
13        SPI_ce(1); return SD_BlockSearchError;
14    }
15
16    // Ожидание ответа от SD
17    while ((status = SPI_get()) == 0xFF)
18        if (i++ > SD_TIMEOUT_CNT) {
19            SPI_ce(1); return SD_TimeoutError;
20        }
21
22    // DATA_START_BLOCK = 0xFE
23    if (status != 0xFE) {
24        SPI_ce(1); return SD_BlockSearchError;
25    }
26
27    // Прочесть данные
28    for (i = 0; i < 512; i++) SD_data[i] = SPI_get();
29
30    SPI_ce(1);
31    return SD_OK;
32}
А теперь объяснения
  • Отсылка CMD17 с аргументом номера сектора (начинается с 0)
  • Ожидание ответа от карты, который был бы равен 0xFE (или вылетает с дикой ошибкой таймаута). То есть пока 0xFF идет ответ, ждет, пока не появится заветный 0xFE
  • Производится чтение 512 байт в память
Ну да, так просто. Ничего не скажешь. Но это вроде как работает нормально.

§ Запись

Запись очень похоже на чтение, но нужно вот что сделать
  • Сначала отослать команду CMD24 с аргументом номера блока (начиная с 0). Если команда возвращает отличный от 0 ответ, то ошибка!
  • Отослать байт 0xFE (токен DATA_START_BLOCK)
  • Отослать 512 байт данных
  • Выслать 2 байта 0xFF и 0xFF (это токены CRC)
  • Прочесть статус (1 байт) из SPI, и если (status & 0x1F) != 0x05, то это фейл - данные не записались
  • Ждать ответа от карты (0xFF), или таймаута
  • Отослать команду CMD13 с аргументом 0.
  • Если ответ команды будет не 0, ошибка, завершить миссию
  • Прочесть байт SPI, если будет не 0, то ошибка
  • Установить CS=1
Не очень жирный кот прилагается:
1// Писать блок 512 байт в память
2char SD_write(unsigned long lba) {
3
4    int i = 0;
5    unsigned char status;
6
7    // Кроме SDHC ничего не поддерживается
8    if (SD_type != SD_CARD_TYPE_SDHC)
9        return SD_UnsupportYet;
10
11    // Отослать команду поиска блока
12    if (SD_command(SD_CMD24, lba)) {
13        SPI_ce(1); return SD_BlockSearchError;
14    }
15
16    // DATA_START_BLOCK
17    SPI_put(0xFE);
18
19    // Запись данных
20    for (int i = 0; i < 512; i++) SPI_put(SD_data[i]);
21
22    // Dummy 16-bit CRC
23    SPI_put(0xFF);
24    SPI_put(0xFF);
25
26    status = SPI_get();
27
28    // Сверить результат
29    if ((status & 0x1F) != 0x05) {
30        SPI_ce(1); return SD_WriteError;
31    }
32
33    // Ожидание окончания программирования
34    while ((status = SPI_get()) == 0xFF)
35        if (i++ > SD_TIMEOUT_CNT) {
36            SPI_ce(1); return SD_TimeoutError;
37        }
38
39    // Проверить 2 байта на ненулевое значение
40    if (SD_command(SD_CMD13, 0) || SPI_get()) {
41        SPI_ce(1); return SD_WriteError;
42    }
43
44    SPI_ce(1);
45    return SD_OK;
46}

§ Заголовочный файл

Без этого файла не было бы малины никакой.
1#define SD_TIMEOUT_CNT      4095
2
3enum SD_errors {
4
5    SD_OK               = 0,
6    SD_UnknownError     = 1,
7    SD_TimeoutError     = 2,
8    SD_UnknownCard      = 3,
9    SD_AcmdError        = 4,
10    SD_Unknown58CMD     = 5,
11    SD_BlockSearchError = 6,
12    SD_UnsupportYet     = 7,
13    SD_WriteError       = 8
14};
15
16enum SD_types {
17
18    SD_CARD_TYPE_ERR    = 0,
19    SD_CARD_TYPE_SD1    = 1,
20    SD_CARD_TYPE_SD2    = 2,
21    SD_CARD_TYPE_SDHC   = 3
22
23};
24
25enum SD_results {
26
27    R1_READY_STATE      = 0x00,
28    R1_IDLE_STATE       = 0x01,
29    R1_ILLEGAL_COMMAND  = 0x04
30
31};
32
33enum SD_Commands {
34
35    SD_CMD0     = 0,    // Сброс
36    SD_CMD8     = 8,    // Проверка вольтажа SD2
37    SD_CMD13    = 13,   // Проверка
38    SD_CMD17    = 17,   // Чтение
39    SD_CMD24    = 24,   // Запись
40    SD_CMD55    = 55,   // ACMD
41    SD_CMD58    = 58    // Чтение регистра OCR
42};
43
44// Данные
45char            SD_type;
46char            SD_error;
47unsigned char   SD_data[512];