§ Распиновка
Я использую SD-карту для подключения ее через SPI, и причем для Arduino. На картинке (не знаю, откуда взято), приведена распиновка для разных карт разных форм-факторов.Для разных карт существуют разные выводные пины. Порт DI - это MOSI с точки зрения контроллера, а порт DO - это MISO. То, как расположены контакты, жутко странно, но что поделать, такова жизнь.

PIN | MicroSD | SD | ||
---|---|---|---|---|
SD | SPI | SD | SPI | |
1 | DAT2 | DAT3/CD | CS | |
2 | DAT3/CD | CS | CMD | DI |
3 | CMD | DI | Gnd2 | |
4 | (+) VCC | |||
5 | CLK | |||
6 | (-) GND | |||
7 | DAT0 | DO | DAT0 | DO |
8 | DAT1 | DAT1 | ||
9 | DAT2 |
§ Инициализация SPI
Что требуется для того, чтобы начать общаться с микроконтроллером, всаженном в sd-карту?- Для начала, надо настроить SPI на скорость 125 кГц (вообще от 100 кГц до 400 кГц разрешено).
- Во-вторых, подать на пин CS=1 (высокий уровень), а также на MOSI=1 (т.е. DI у карты)
- Далее, сделать минимум 74 тактов на CLK=0 и 1, подать LOW и HIGH аж 74 раза, и больше. Я делаю 80 тактов.
§ Определение типа карты
Это очень сложный и трудоемкий этап, который не нужно запоминать, но нужна конкретная шпаргалка. Здесь важен обмен командами, речь о которых позже. Вот так происходит определение.ШАГ | Описание шага |
---|---|
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 (деактивировать на время, чтобы не жрало питалово) |
§ Исходные коды
// Инициализация карты 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-2023 Жесть в том, что Земфира закусывает имбирем