§ Распиновка

Я использую 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, все работает. Код в студию:

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

// Инициализация карты
void SD_init() {

    unsigned char i, status = 0xFF;
    unsigned long arg;

    // Вначале все вроде как ОК
    SD_error = SD_OK;
    SD_type  = SD_CARD_TYPE_ERR;

    // Отключить устройство
    SPI_ce(1);

    // Подать 80 тактов, которые переведут устройство в режим SPI
    for (i = 0; i < 10; i++) SPI_put(0xFF);

    // Сброс. Должен быть ответ 01h
    if (SD_command(SD_CMD0, 0) != R1_IDLE_STATE) {
        SD_error = SD_UnknownError; SPI_ce(1); return;
    }

    // Определить тип карты (SD1)
    if (SD_command(SD_CMD8, 0x1AA) & R1_ILLEGAL_COMMAND) {
        SD_type = SD_CARD_TYPE_SD1;

    } else {

        // Прочесть 4 байта, последний будет важный
        for (i = 0; i < 4; i++) status = SPI_get();

        // Неизвестный тип карты
        if (status != 0xAA) {
            SD_error = SD_UnknownCard; SPI_ce(1); return;
        }

        // Это тип карты SD2
        SD_type = SD_CARD_TYPE_SD2;
    }

    // Инициализация карты и отправка кода поддержки SDHC если SD2
    i   = 0;
    arg = (SD_type == SD_CARD_TYPE_SD2 ? 0x40000000 : 0);

    // Отсылка ACMD41 = 0x29. Отсылать и ждать, пока не придет корректный ответ
    while ((status = SD_acmd(0x29, arg)) != R1_READY_STATE) {

        // Если таймаут вышел
        if (i++ > SD_TIMEOUT_CNT) {
            SD_error = SD_AcmdError; SPI_ce(1); return;
        }
    }

    // Если SD2, читать OCR регистр для проверки SDHC карты
    if (SD_type == SD_CARD_TYPE_SD2) {

        // Проверка наличия байта в ответе CMD58 (должно быть 0)
        if (SD_command(SD_CMD58, 0)) {
            SD_error = SD_Unknown58CMD; SPI_ce(1); return;
        }

        // Прочесть ответ от карты и определить тип (SDHC если есть)
        if ((SPI_get() & 0xC0) == 0xC0) {
            SD_type = SD_CARD_TYPE_SDHC;
        }

        // Удалить остатки от OCR
        for (i = 0; i < 3; i++) SPI_get();
    }

    // Выключить чип
    SPI_ce(1);
}

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

Я не стал никого томить и код сразу оказался в студии:
// Результат выполнения команды
unsigned char SD_command(unsigned char cmd, unsigned long arg) {

    int i = 0;
    unsigned char status;
    unsigned char crc = 0xFF;

    // Перевести в активный режим
    SPI_ce(0);

    // Подождать ответ в течении определенного времени
    while (i < SD_TIMEOUT_CNT) { status = SPI_get(); if (status == 0xff) break; i++; }

    // Ошибка
    if (i >= SD_TIMEOUT_CNT) { SD_error = SD_TimeoutError; return 0xff; }

    // Отсылка команды
    SPI_put(cmd | 0x40);

    // Отослать 32-х битную команду
    for (i = 24; i >= 0; i -= 8) SPI_put(arg >> i);

    // Отправка CRC
    if (cmd == SD_CMD0) crc = 0x95;  // CMD0 with arg 0
    if (cmd == SD_CMD8) crc = 0x87;  // CMD8 with arg 0x1AA

    SPI_put(crc);

    // Ожидать снятия флага BUSY
    for (i = 0; ((status = SPI_get()) & 0x80) && i != 0xFF; i++);

    return status;
}

// Расширенная команда
unsigned char SD_acmd(unsigned char cmd, unsigned long arg) {

    SD_command(SD_CMD55, 0);
    return SD_command(cmd, arg);
}
А теперь объяснения насчет всего этого кода сверху. Он, если разобраться, не сложный
  • Сначала ставится CS=0 (LOW) Активация Чипа (и Дейла)
  • Далее принимаются байты, и принимаются до тех пор, пока а) не будет таймаут, что будет говорить о неготовности карты к близкому сотрудничеству, б) будет 0xFF на выходе - это говорит о готовности приема команд
  • Отсылается команда, как видно, команда может быть только от 0 до 63, потому что там старшие 2 бита это "01", такие дела.
  • Отсылается 4 байта (от старшего к младшего) от 32-х битного аргумента
  • Отсылается 1 байт CRC. Он для всех команд равен 0xFF но только для команды 0 и 8 он разный. Какой именно - смотри в коде.
  • Ну и собственно, ждем ответ, в котором старший бит будет равен 0. Может, кстати наступить таймаут (256 чтений ничего не дали - считай, закрывай гараж).
Вот тут, собственно, и всё. С инициализацией карты покончено. Теперь самое важное - чтение с карты. Ну или запись, кому как удобнее. С записью я пока ничего не делал, влом, но там все очень просто, там так же, как на чтение, только наоборот.

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

Вот он, код на чтение пока что:
// Читать блок 512 байт в память
char SD_read(unsigned long lba) {

    int i = 0;
    unsigned char status;

    // Кроме SDHC ничего не поддерживается
    if (SD_type != SD_CARD_TYPE_SDHC)
        return SD_UnsupportYet;

    // Отослать команду поиска блока
    if (SD_command(SD_CMD17, lba)) {
        SPI_ce(1); return SD_BlockSearchError;
    }

    // Ожидание ответа от SD
    while ((status = SPI_get()) == 0xFF)
        if (i++ > SD_TIMEOUT_CNT) {
            SPI_ce(1); return SD_TimeoutError;
        }

    // DATA_START_BLOCK = 0xFE
    if (status != 0xFE) {
        SPI_ce(1); return SD_BlockSearchError;
    }

    // Прочесть данные
    for (i = 0; i < 512; i++) SD_data[i] = SPI_get();

    SPI_ce(1);
    return SD_OK;
}
А теперь объяснения
  • Отсылка 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
Не очень жирный кот прилагается:
// Писать блок 512 байт в память
char SD_write(unsigned long lba) {

    int i = 0;
    unsigned char status;

    // Кроме SDHC ничего не поддерживается
    if (SD_type != SD_CARD_TYPE_SDHC)
        return SD_UnsupportYet;

    // Отослать команду поиска блока
    if (SD_command(SD_CMD24, lba)) {
        SPI_ce(1); return SD_BlockSearchError;
    }

    // DATA_START_BLOCK
    SPI_put(0xFE);

    // Запись данных
    for (int i = 0; i < 512; i++) SPI_put(SD_data[i]);

    // Dummy 16-bit CRC
    SPI_put(0xFF);
    SPI_put(0xFF);

    status = SPI_get();

    // Сверить результат
    if ((status & 0x1F) != 0x05) {
        SPI_ce(1); return SD_WriteError;
    }

    // Ожидание окончания программирования
    while ((status = SPI_get()) == 0xFF)
        if (i++ > SD_TIMEOUT_CNT) {
            SPI_ce(1); return SD_TimeoutError;
        }

    // Проверить 2 байта на ненулевое значение
    if (SD_command(SD_CMD13, 0) || SPI_get()) {
        SPI_ce(1); return SD_WriteError;
    }

    SPI_ce(1);
    return SD_OK;
}

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

Без этого файла не было бы малины никакой.
#define SD_TIMEOUT_CNT      4095

enum SD_errors {

    SD_OK               = 0,
    SD_UnknownError     = 1,
    SD_TimeoutError     = 2,
    SD_UnknownCard      = 3,
    SD_AcmdError        = 4,
    SD_Unknown58CMD     = 5,
    SD_BlockSearchError = 6,
    SD_UnsupportYet     = 7,
    SD_WriteError       = 8
};

enum SD_types {

    SD_CARD_TYPE_ERR    = 0,
    SD_CARD_TYPE_SD1    = 1,
    SD_CARD_TYPE_SD2    = 2,
    SD_CARD_TYPE_SDHC   = 3

};

enum SD_results {

    R1_READY_STATE      = 0x00,
    R1_IDLE_STATE       = 0x01,
    R1_ILLEGAL_COMMAND  = 0x04

};

enum SD_Commands {

    SD_CMD0     = 0,    // Сброс
    SD_CMD8     = 8,    // Проверка вольтажа SD2
    SD_CMD13    = 13,   // Проверка
    SD_CMD17    = 17,   // Чтение
    SD_CMD24    = 24,   // Запись
    SD_CMD55    = 55,   // ACMD
    SD_CMD58    = 58    // Чтение регистра OCR
};

// Данные
char            SD_type;
char            SD_error;
unsigned char   SD_data[512];
27 дек, 2020
© 2007-2022 Психичная фигня ползла