Главная > AVR > Низкоуровневый драйвер для SD-карты под AVR

Низкоуровневый драйвер для SD-карты под AVR

Взял тут меня на «слабо» один мой старый товарищ. В беседе трехдневной давности я поведал ему, что, если игнорировать файловую систему, работа с SD-карточкой легка и непринужденна. После этого он попросил пруф. Ну что, вот он, пруф.🙂

Что я читал.

Итак, для начала о том, что я читал перед тем, как взяться за реализацию (и в ее процессе, конечно).

1. Статья Elm Chan’а про SD-карточки. 90% полезной информации можно почерпнуть из нее.

2. Официальная спецификация SD-карт от SanDisk. Там можно прочесть про некоторые специфичные моменты.

3. Отличная иллюстрация режимов SPI. Его я реализовывал софтово, потому поглядывал на картинки по этой ссылке.

Желающим работать с SD-карточками я настоятельнейшим образом рекомендую целиком прочесть первую статью и ознакомиться со вторым документом, ибо здесь я не буду подробно останавливаться на том, что уже понятнейшим образом расписано в упомянутых источниках.

Что я слушал.

В процессе кодинга я слушал треки товарища по имени Tom Day. Ну и еще вот такую прикольную девушку-шептунью. На мой вкус, это именно то, что надо слушать при реализации обмена с SD-карточкой.😀 Для написания статьи хорошо идет радио http://smoothjazz.com/.

Что у меня получилось.

Прежде всего я написал софтовую реализацию протокола SPI. Конечно, в ATmega48 (мой основной контроллер для экспериментов) есть аппаратный SPI. Однако софтовый гораздо более гибок в применении за счет того, что позволяет как угодно распределять функции шины по ножкам контроллера. Это позволило мне не загибать провода на макетке в немыслимые узлы, а подсоединить как есть, сконфигурировав все в софте. Впрочем, модуль реализован отдельно от API, так что, если кому-то не понравится такой подход (или я передумаю), всегда можно поменять его на другую реализацию по вкусу.

Ну и, собственно, основной результат — модуль для работы с SD-карточкой. Поддерживает чтение блока и запись блока. Само собой, содержит функцию инициализации. Предполагая использование модуля в совсем небольших контроллерах, я не стал реализовывать многоблочные передачи. Причина проста — минимальный размер блока для записи составляет 512 байт. Учитывая, что это полный размер RAM ATmega48, я не могу предположить, что многоблочный режим может быть полезен.

Как я это делал.

Сначала прочитал документацию, разумеется.😀

В принципе, общаться с SD-карточкой можно либо по родному протоколу SD, либо по уже упомянутому SPI. Чтобы перевести ее в режим SPI, необходимо выполнить простую последовательность действий, описанную в [1]. Дальше все просто: посылаем команду — получаем ответ. Формат команды всегда фиксирован, формат ответа тоже. Адресация в байтах.

Единственный подводный камень, разобраться в котором мне помог [2], состоял в ограничениях на адреса. Суть сего феномена состоит в следующем: сама карточка физически разбита на блоки по 512 байт, и пересекать эти границы нельзя. А писать так и вообще можно только по 512 байт.

Например, можно начать читать со второго байта и прочитать сто байт, остановившись на сто втором байте. Но нельзя начать с пятисотого байта и прочитать сто байт — карточка вернет ошибку, потому что регион чтения пересекает физическую границу.

Что касается записи, писать можно только по 512 байт и только выравнивая по этой границе. То есть, для записи доступен только физический сегмент целиком. Учитывая ограничения на память МК, в функции записи реализовано автоматическое дополнение буфера до необходимого размера в том случае, когда он до него не дотягивает. Оставшиеся байты заполняются значениями 0xFF, и, понятное дело, к сожалению, не могут быть использованы при размерах буфера менее 512 байт.

Итак, читать можно любое количество байт с любого адреса, если это любое количество не приводит к пересечению ближайшей границы физического блока.

Писать можно только начиная с границы физического блока и только блок целиком. Моя реализация позволяет писать меньшее количество, автоматически заполняя оставшееся место пустыми данными.

Еще один специфичный момент. Поскольку размер буфера чтения можно произвольно менять, а размер буфера записи фиксирован, при переходе к режиму записи необходимо явно сменить размер на корректный соответствующей командой, иначе карта вернет ошибку. Это стоило мне пары часов раздумий. Конечно, я реализовал автоматическое переключение внутри функции записи, так что на верхнем уровне можно не беспокоиться об этом.

Как пользоваться модулем.

Модуль состоит из четырех файлов:

soft_spi.h
soft_spi.c

sdcard_api.h
sdcard_api.c

Все настройки содержатся в заголовочных файлах.

При необходимости можно поменять определения функций выводов в soft_spi.h. У меня они выглядят так:

/*
Hardware I/O config
*/
#define SWSPI_PORT					PORTC
#define SWSPI_PIN					PINC
#define SWSPI_DDR					DDRC
 
#define SWSPI_MISO					_BV(PC3)
#define SWSPI_MOSI					_BV(PC1)
#define SWSPI_SCLK					_BV(PC2)
#define SWSPI_CSEL					_BV(PC0)

Карта подключается соответственно.

Модуль SPI обеспечивает корректные тайминги для SD-карты в диапазоне тактовых частот МК от 4 МГц до 12 МГц.

Далее можно поменять размер буфера чтения/записи в sdcard_api.h. По-умолчанию — 128 байт:

//In bytes, truly valid only for read blocks.
#define SDCARD_RW_BLOCK_SIZE		128

Как уже говорилось, истинный смысл это значение имеет только для чтения. При записи блок все равно будет дополнен до корректной длины.

Можно поменять таймаут, который дается карте на инициализацию:

//1 sec @ 8MHz
#define SDCARD_INIT_TIMEOUT			5208

Как и написано в комментарии, при тактовой частоте 8МГц эта константа даст карте около 1 секунды. Если за это время инициализация не завершится — функция вернет ошибку.

Функции API могут возвращать два значения — SDCARD_OK и SDCARD_ERROR. Очевидно, для успешного завершения операции и для ошибки соответственно.

Работа с модулем проста. При подключении карты необходимо вызвать SDCARD_Init(). После ее успешного завершения можно читать и писать блоки функциями SDCARD_ReadBlock(…) и SDCARD_WriteBlock(…). Запись блоков размером более 512 байт не реализована. При задании адреса и выборе размера буфера необходимо учитывать упомянутые выше ограничения.

Демо.

Я использовал microSD карточку. Тесты проводились на макетке с ATmega48, работающей на частоте 8 МГц. Результат работы программы наблюдал через UART (я использовал готовый модуль ввода/вывода от своей консоли).

Чтобы подключить карточку, я подпаялся к переходнику.

P4060043

Потом залил контакты термоклеем и воткнул в макетку.

P4070045

В нулевой сектор SD-карточки с помощью HxD записал тестовый текст.

hex1

Это никак не отражается на способоности карточки корректно распознаваться при втыкании в компьютер, так как (если кто не знает) эта область в FAT не используется. Точнее, там boot-сектор, но кому он нужен в карточке?🙂

Поглядел, чего в первом секторе:

hex2

Собственно, ничего. После этого я начал экспериментировать.

P4070044

И таки допилил модуль. Тестовая программа читает из нулевого сектора, прочитанное выводит через UART (9600/8N1) и записывает в первый сектор такой массив:

uint8_t sdcard_wr_block_buffer[SDCARD_RW_BLOCK_SIZE]={"Write test block here.\r\n"};

Скомпилил, залил, запустил:

putty

После этого проверил, чего в первом секторе…

hex3

Бинго! Все работает.

Выводы.

G.G., как я и говорил тебе, все просто.🙂 Неспеша я осилил это за три вечера, если сесть плотно — можно осилить за один день. Пользуйся на здоровье.😉 Скачать демо-проект можно тут.

Ну и, конечно, дисклаймер — я ничего не гарантирую, и все такое… Ведь в модуле есть и упрощения — например, я не проверяю, что прочитанные данные — правда данные. Так что если карточка залочена, чтение блока все равно завершится успешно, вернув буфер с непонятно чем. Точнее, первый байт его будет содержать error token, а остальные, скорее всего, 0xFF. С этим уже надо разбираться отдельно… Но, поскольку вероятность такого исхода мала, я пока не планирую ничего делать. Просто это надо иметь в виду.

Рубрики:AVR
  1. Михаид
    28/11/2016 в 08:21

    Уважаемый YS! Собрал LED 8x8x8 — все моргает, в общем лепота! Но вот возникла мысль и я её думаю — использовать SD карту для хранения данных, которые затем выводятся по тому же SPI на shiftRegistr. Погуглил, нашел Вашу программу — да вот беда, я с Си о-о-очень на Вы, как то все ассемблер. Подскажите да поподробней, что и где копать в Вашей программе, для достижения желаемого мной результата. Буду премного благодарен. Можно на xxxxxx@mail.ru

    • YS
      28/11/2016 в 12:59

      Вряд ли вам будет сильно полезен именно тот модуль, что описан в статье. Это просто низкоуровневый драйвер доступа к SD-карте как к микросхеме памяти. Вам же, судя по всему, нужна поддержка FAT. Посмотрите в сторону FatFS (ссылка на блог Elm ChaN’а есть в статье). Совместно с ней в качестве драйвера SD-карты можно использовать и мое творение, но вроде бы в комплекте FatFS есть подобный драйвер для AVR.

  2. volatile
    30/03/2016 в 02:49

    Инициализировать по cmd1 не по фен-шую )).

  3. Валерий
    02/10/2014 в 15:15

    Допустим, данные с АЦП заносятся непрерывным потоком в буфер — 512 байт. После заполнения буфера, переписываю его на флешку. В момент записи на флешку могу продолжать получать данные (не должен ничего пропустить)?

    • YS
      02/10/2014 в 15:57

      Сделать два буфера, один для накопления, второй для записи. Копить в прерывании, после заполнения буфера данными ставить флаг и менять буферы местами. В главном цикле отслеживать флаг и писать данные во флеш. Единственное ограничение — необходимо, чтобы время записи не превышало время накопления, тогда ничего не потеряется.

      • Валерий
        03/10/2014 в 14:15

        Да вот, в том-то и дело, что процесс записи на sd длится более 1 мс (по данным из Proteus). А мне необходимо данные получать более часто, чем 1 мс. Нельзя же ведь будет прерваться в момент записи на sd? Вот в чем вопрос.:)

        Atmega128, 14.7456 MHz

        • YS
          03/10/2014 в 14:45

          Во-первых, я бы не ориентировался на данные из Протеуса.

          Во-вторых, остановиться для снятия нового показания с АЦП в процессе записи данных на SD вполне можно — SPI это синхронный интерфейс, в карточке тоже есть буферы, так что требований к минимальным таймингам нет — можно писать хоть четыре байта подряд, а потом еще 508 через час (не отпуская CS, разумеется). Мой подход выше такое и предполагает — АЦП работает, прерывая неспешный процесс записи того, что уже готово. Накопление работает в прерывании, запись — в основном потоке.

          Единственное ограничение здесь — нельзя увеличить пропускную способность. ОК, пускай 1 мс на запись. Это (в упрощении) означает, что мы, например, можем писать 512 байт за миллисекунду, то есть, около 500 кБ/с. И вот если ваш поток с АЦП больше, чем 500 кБ/с, то как ни старайся, ничего не выйдет. Если меньше — все будет нормально.

          То есть, например, в этом случае все будет нормально работать в режиме непрерывной записи до частоты дискретизации около 500 кГц (в приближении, что на это идут все ресурсы МК и разрядность — 8 бит). Впрочем, АЦП AVR 500 килосемплов в секунду полюбому не потянет.🙂

  4. Дмитрий
    19/11/2013 в 13:25

    а как можно формировать в виде txt файла например?

    • YS
      19/11/2013 в 20:42

      Собственно, ответил ниже. Либо FatFs, либо изначально создать файл-заглушку…

  5. Дмитрий
    18/11/2013 в 11:22

    Малость не понял а в карте куда именно пишется текст …создается тхт файлик и в него все сливается …как с компьютера это все потом можно прочитать если воткнуть карточку в комп и считывать в нормальном сд режиме ?

    • YS
      18/11/2013 в 19:48

      В данном варианте текст пишется прямо на карту в обход файловой системы в неиспользуемое место, чтобы не рушить FAT. Просто по смещению 0x200, чисто для теста.

      Как прочитать?🙂 В таком варианте — никак без специальных программ типа HxD, которую использовал я (или своей программой, которая читает напрямую с диска).

      Если хочется совсем по-настоящему, чтобы с файлами и виделось прям в проводнике Windows, то есть два варианта:

      1. Использовать FatFs от chan’а. Можно скормить FatFs’у мой драйвер в качестве низкоуровневого модуля I/O. Но там, по-моему, уже свой написан.

      2. Маломощные МК (с рассчетом на которые и писалась моя библиотека) могут не потянуть даже урезанную версию FatFs. Тогда можно сделать так:

      2.1 Отформатировать карту.
      2.2 На чистой карте (чтобы не было фрагментации) создать пустой файл желаемого размера (например, заполнить нулями, а лучше 0xFF’ами);
      2.3 С помощью программы типа HxD вручную выяснить смещение его начала.
      2.4 Найденное смещение задать в качестве константы в программе МК, и писать, начиная с него. Тогда все записаные данные попадут аккурат в то место, где их ожидает увидеть ОС, и все будет читаться как надо.

      Я хотел написать еще одну статью на эту тему, но что-то сейчас работа совсем поглотила меня.

      • Vga
        19/11/2013 в 01:31

        Вместо хардкода из пункта 2.3 можно вычитать смещение нужного файла прямо из FAT. Это проще, чем полноценно поддерживать FAT — например, на x86 загрузчик, находящий по FAT’у нужный файл и загружающий его умещается менее чем в полкило (MBR). Да и вариант, ищущий по MFT тоже умещается в MBR.
        Такое решение применялось в ранних прошивках Nano DSO, когда все файлы, которые он мог сохранять, нуна было заранее кидать на карточку в виде заглушек. Ну, благо, потом пришел нормальный программист и вместе с прочими косяками (делавшими девайс практически бесполезным) поправил и это.
        Алсо, для того, чтобы файл не был фрагментирован — можно пройтись по карточке дефрагментатором. Например, утилиткой contig от sysinternals — ей можно дефрагментировать конкретный файл.

        • Дмитрий
          19/11/2013 в 13:27

          да получается придется выучить побитово файловую систему

          • YS
            19/11/2013 в 20:44

            Да FAT сам по себе не так сложен, на самом деле. Основные проблемы начинаются, когда в МК мало памяти.

            • Дмитрий
              25/11/2013 в 11:32

              я использую 16 и 162 -ую меги у них по 16 кило флеша думаю потянет вполне

              • YS
                26/11/2013 в 13:20

                Я имел в виду оперативную память, в основном. Хотя в мегах 16 и 162 по килобайту оперативки, один сектор карты считать можно. Думаю да, FatFs должен прокатить.

  6. G.G.
    08/04/2013 в 16:14

    Хм. Интересно: FF расшифровывается как «я» =) Странно.

  7. 08/04/2013 в 02:38

    Reblogged this on scienceasm and commented:
    Ваши мысли… (необязательно)

  8. 07/04/2013 в 19:49

    YS :
    В принципе, надо бы. Я даже думаю об этом. Но зачем, если работает?

    Зрение чтобы пожалеть…..
    Я когда долго уже сидел за ЖК, потом когда увидел картинку на трубочном мониторе — в шоке долго был…..

    • YS
      07/04/2013 в 19:52

      Ну, не знаю. У меня на работе ЖК. И ничего так, особой разницы не ощущаю… Видимо, тот трубочный ваш был плохо настроен.

      • 07/04/2013 в 20:02

        а может…..

        • YS
          07/04/2013 в 20:04

          Единственно только, мой домашний мне уже маловат — всего 1024 х 768. Некоторые программы и сайты не влезают. : ) Так что может и прикуплю ЖК.

          • 07/04/2013 в 20:13

            У меня 1366х768 вроде хватает, но иногда конечно тоже ощущается теснота….. Короче надо по максимуму 1920х1080, вот где будет разгулятся…..

            • Vga
              08/04/2013 в 00:10

              После 1920х1080 везде тесно) Так что да, по максимуму надо брать.

      • Vga
        08/04/2013 в 00:08

        Необязательно плохо настроен, они еще очень чувствительны к внешним полям. Я пару раз видел неудачно поставленные моники, у которых картинка на экране дрожжала с амплитудой под миллиметр (и частотой, вероятно, 50Гц, так что выглядело это как мерцающий блюр, но глаза от такого уставали за минуты).

  9. 07/04/2013 в 19:46

    Ну а ваапче — спасибо. Всегда в мыслях было — прикрутить карточку. Как представлю что у моего контроллера гиг памяти — аж дух захватывает…..

    • YS
      07/04/2013 в 19:51

      Да не за что. Только там, как видно, есть нюанс — памяти будет количество секторов * объем внутреннего буфера в МК, ибо, как написано, часть забивается пустотой для выравнивания. На гиговой карточке таких секторов 2 097 152, так что при буфере в 128 байт удастся использовать только 256 МБ.

    • YS
      07/04/2013 в 19:55

      Ну и, конечно, ФС. Если хочется, чтобы комп распознавал карточку в родном режиме, придется решать вопрос с FAT. На карточках до, вроде, 4 ГБ используется FAT16.

  10. 07/04/2013 в 19:43

    И кстати это….. монитор поменяй тоже, на дворе то 21 век….

    • YS
      07/04/2013 в 19:45

      В принципе, надо бы. Я даже думаю об этом. Но зачем, если работает?🙂

    • 07/04/2013 в 19:59

      А так на вскидку, сколько сожрет флэша библиотека с FAT (я просто вообще еще не интересовался этим вопросом)

      • 07/04/2013 в 20:01

        промазал….. хотел чуть выше это написать…..

      • YS
        07/04/2013 в 20:03

        ХЗ, можете у Elm Chan’а посмотреть, он такую писал.

        Но совсем необязательно реализовывать FAT целиком и честно. Если ввести ограничения, можно упростить задачу. Например, можно на отформатированной флешке создать один большой файл, заполнив его пустотой. В этом случае его кластеры будут идти подряд. Можно тем же HEX-редактором определить его начало, и писать начиная с этого смещения. Тогда можно будет прочесть его с компьютера.

        Если будет время, я напишу и про это.

        • Vga
          08/04/2013 в 00:05

          В этом случае его кластеры будут идти подряд.

          Вместо форматирования можно после создания файла дефрагментировать его программой contig из sysinternals suite.

          • YS
            08/04/2013 в 20:50

            Как вариант. Но если мы создаем флешку спецом под какой-то девайс, логично ее форматнуть перед использованием.

  11. 07/04/2013 в 19:42

    Опиши так же, что ты курил :)………

    • YS
      07/04/2013 в 19:43

      Я ничего не курил, просто мне еще делать курсач по радиоавтоматике и бакалаврскую работу, так что шлифовать стиль и верстку статьи некогда.😀

      • 07/04/2013 в 19:56

        да не, со стилем все нормально…. я просто продолжаю логический ряд: что читал, что слушал, и т.д.

        • YS
          07/04/2013 в 19:57

          А-а-а, от оно чо.🙂 Ненене, я обошелся без веществ, потому и главы про них нет.😀

  1. 27/12/2013 в 02:56
  2. 15/12/2013 в 20:39

Добавить комментарий

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход / Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход / Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход / Изменить )

Google+ photo

Для комментария используется ваша учётная запись Google+. Выход / Изменить )

Connecting to %s