§ Распиновка
Я использую 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 тактов.
Когда эти все действия сделаны, то можно с некоторой уверенностью считать, что карта поняла, что мы перешли в режим 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
15 for (i = 0; i < 10; i++) SPI_put(0xFF);
16
17
18 if (SD_command(SD_CMD0, 0) != R1_IDLE_STATE) {
19 SD_error = SD_UnknownError; SPI_ce(1); return;
20 }
21
22
23 if (SD_command(SD_CMD8, 0x1AA) & R1_ILLEGAL_COMMAND) {
24 SD_type = SD_CARD_TYPE_SD1;
25
26 } else {
27
28
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
37 SD_type = SD_CARD_TYPE_SD2;
38 }
39
40
41 i = 0;
42 arg = (SD_type == SD_CARD_TYPE_SD2 ? 0x40000000 : 0);
43
44
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
54 if (SD_type == SD_CARD_TYPE_SD2) {
55
56
57 if (SD_command(SD_CMD58, 0)) {
58 SD_error = SD_Unknown58CMD; SPI_ce(1); return;
59 }
60
61
62 if ((SPI_get() & 0xC0) == 0xC0) {
63 SD_type = SD_CARD_TYPE_SDHC;
64 }
65
66
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
21 for (i = 24; i >= 0; i -= 8) SPI_put(arg >> i);
22
23
24 if (cmd == SD_CMD0) crc = 0x95;
25 if (cmd == SD_CMD8) crc = 0x87;
26
27 SPI_put(crc);
28
29
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
2char SD_read(unsigned long lba) {
3
4 int i = 0;
5 unsigned char status;
6
7
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
17 while ((status = SPI_get()) == 0xFF)
18 if (i++ > SD_TIMEOUT_CNT) {
19 SPI_ce(1); return SD_TimeoutError;
20 }
21
22
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
2char SD_write(unsigned long lba) {
3
4 int i = 0;
5 unsigned char status;
6
7
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
17 SPI_put(0xFE);
18
19
20 for (int i = 0; i < 512; i++) SPI_put(SD_data[i]);
21
22
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
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,
37 SD_CMD13 = 13,
38 SD_CMD17 = 17,
39 SD_CMD24 = 24,
40 SD_CMD55 = 55,
41 SD_CMD58 = 58
42};
43
44
45char SD_type;
46char SD_error;
47unsigned char SD_data[512];