MMC(SD) и AVR. Часть 1. Инициализация и идентификация.

AVR
Пришло время поговорить о картах памяти формата MMC и SD(а также все их варианты типа mini, micro и т.д.). Использую их в своих проектах довольно таки давно, поэтому вроде как для меня в этом вопросе ничего сложного нет. Поэтому данная статья рассчитана на тех, кому этот вопрос кажется интересным(новым, незнакомым, сложным). Если такие комрады имеются, милости прошу под кат…
Долго чесались руки написать данную статейку, да все не было времени. Вот сейчас на работе вроде бы расслабон, поэтому начну излагать материал. Сразу оговорюсь, что на звание ОБУЧАТЕЛЯ не претендую, но постараюсь донести материал как можно доступнее.
И так, карта MMC как SD, по своей природе имеют собственный протокол общения с внешним миром, но уважаемые товарищи разработчики этого формата карт вложили одну очень приятную плюшку в эти карты, а именно работа по SPI. Однажды узнав об этом, я подумал, а не использовать ли мне эту феньку и не заюзать это в своем курсовом? Сказано — сделано!!! Первым делом обратился к datasheet'у на данные девайсы и все вроде как стало понятно, но без гвоздей, как говориться не обошлось.
На рисунке 1 представлена стандартная карта памяти MMC и SD, а также назначение их выводов.
SD MMC pinout
Как можно увидеть из рисунка, обе эти карты имеют выводы для подключения по SPI. Долго тянуть не будем и подключим все это дело по схеме, указанной ниже:
SD MMC schematic
Как обычно печатку не прилагаю, поскольку использую давно провереный девайсик, фотку которого можно увидеть здесь
Вот мы вроде бы все подключили как указано выше. Сейчас перейдем к описанию самой работы с картой памяти. Режим SPI является вторичным (опциональный) протоколом связи с картой памяти. Этот режим является, как бы, подмножеством MMC протокола и разработан для связи по каналу SPI, который присутствует в большинстве современных МК.
Сам по себе SPI стандарт подразумевает только физическую связь между устройствами и не является полноценным протоколом передачи данных. Поэтому SPI интерфейс в картах MMC и SD имеет свои особенности. Стоит отметить, что при работе с картой памяти по интерфейсу SPI доступны не все возможности карты, но, как правило, эти возможности и не нужны.
И так, в то время как протокол MMC основывается на командах и битовых потоках, которые начинаются со старт бита и заканчиваются стоп битом, SPI режим является байт-ориентированным. Это значит, что каждая команда или блок данных состоят из 8-ми битных байтов. Так же как и в протоколе MMC в SPI режиме общение с картой состоит из команды, ответа на команду и знаков(tokens) от блоков данных. МК контролирует весь обмен информацией между собой любимым и картой памяти.
Есть несколько основных аспектов при работе с картой памяти в режиме SPI:
— Выбранная карта памяти всегда отвечает на посланную ей команду
— Используются дополнительные 8-ми, 16-ти и 40-ка байтные структуры как ответ на посланную команду
— В случае ошибки при получение команды или данных от МК, карта памяти ответит структурой содержащей описание ошибки

Следует знать, что в режиме SPI поддерживаются только команды чтения/записи в режиме одного блока или нескольких блоков (в режиме MMC поддерживается режим последовательной чтения/записи). Размер блока для чтения/записи может быть размером в сектор карты памяти и размером в 1 байт. Поддержка операций частичного чтения/записи блока данных хранится в регистре CSD карты памяти.
Чуть не забыли поговорить о регистрах, которые содержит карта памяти стандарта MMC(SD). Этих регистров чуть больше, чем перечислю я, но те, которые я не укажу либо не особо нужны, либо не доступны в режиме SPI. Основными регистрами, которые представляют для нас интерес являются следующие:

— CID (Card identification data): содержит данные, по которым можно идентифицировать карту памяти (серийный номер, ID производителя, дату изготовления и т.д.)
— CSD (Card-specific data): содержит всевозможную информацию о карте памяти (от размера сектора карты памяти до потребления в режиме чтения/записи).
— OCR (Operation Conditions Register): содержит напряжения питания карты памяти, тип питания карты памяти, статус процесса инициализации карты.

Подробнее об этих регистрах мы поговорим чуть позже, когда коснемся написания прошивки для МК.
Теперь наступило время узнать о том, как эти самые карты включить в режиме SPI. По умолчанию, карта MMC(SD), при подаче на ее напряжения питания, запускается в режиме протокола MMC. Карта перейдет в режим SPI в случае, когда сигнал на выводе _CS будет иметь низкий уровень (карта памяти выбрана) и при этом на карту будет послана команда сброса CMD0. В случае приема CMD0, карта проверяет состояние линии _CS. Если логический уровень на этом выводе равен «1», то на посланную команду карта памяти не ответит и останется в режиме MMC. Если же карта, приняв CMD0, определит, что на выводе _CS низкий уровень, то она переключиться в режим SPI и ответит на посланную команду ответом R1 (По всем видам ответов и token’ов поговорим позже). Единожды перейдя в режим SPI, карта памяти останется в этом режиме до следующего выключения и включения питания, т.е. перейти в режим MMC из режима SPI программным способом не получится!
Вроде бы основные вступительные моменты мы обговорили и теперь коснемся набора команд, доступных в режиме SPI. Велосипед изобретать не буду, а просто приведу таблицу из datasheet’а.

Многие, наверное, заметили, что в таблице пропущены некоторые команды. Как я говорил ранее, в режиме SPI недоступны некоторые функции, которые доступны в режиме MMC. Поэтому некоторые команды так же не доступны. Так же из таблицы видно, что у всех команд в поле «Resp» присутствует аббревиатура R1(2, 3, 7 и др.). Это и есть так называемый ответ карты на посылаемую ей команду. Приведу небольшой пример обмена информацией с картой памяти для иллюстрации описанного выше.

MMC SD cmd resp

Рассмотрим подробнее данный пример. Мы видим, что мы отправляем карте (линия DataIn) некоторою команду, приняв которую, карта через некоторый промежуток времени выдаст ответ (линия DataOut). Размер ответа зависит от посылаемой команды и имеет свой формат. Приведу формат ответов, которые мы можем получить от карты памяти.

SD MMC R1

SD MMC R2

SD MMC R3

Теперь коснемся самих команд для карты памяти. Из таблицы команд видно, что у каждой команды есть свой индекс. Это значение используется для получения истинного значения команды, которую необходимо отправить в карту памяти. Общий вид команды представлен ниже (команда в режиме SPI имеет длину в 6 байт):

{{0x40 + CMDx}, {0x??, 0x??, 0x??, 0x??}, {0x??}}
Номер команды Аргумент команды CRC

Стоит отметить, что в протоколе MMC весь обмен данными завершается полем CRC, которое является необходимым. Что касается режима SPI, то по умолчанию при переходе в этот режим, контроль CRC отключен. Исключение составляют команды CMD0 и CMD8, поскольку они отправляются в карту, которая еще находится в режиме MMC, поэтому поле CRC для этих команд должно быть верным. Поскольку CMD0 отправляется единожды и все 6 байт этой команды известны заранее и не меняются, то в поле CRC для любой команды мы будем отправлять CRC для команды CMD0 (оно равняется 0x95). Что касается CMD8, то поле CRC в ней не является константой и зависит от передаваемых параметров. Хочу заметить, что проверку поля CRC можно активировать и в режиме SPI. Делается это при помощи соответствующей команды (CMD59).
Немного разобравшись с теорией, посмотрим на процесс инициализации карты памяти. Попробую представить это в виде блок-схемы. Вот что получилось у меня вымутить из datasheet’а (излагаю только суть, за вычетом некоторых моментов, которые я распишу после блок-схемы):

SD MMC Init strap

Ну вот, скажут некоторые, нарисовал не пойми чего, а вы тут разбирайтесь;) Но все не так уж и плохо. Первое, что необходимо пояснить в этой схеме это то, что карты как бы того не хотелось, бывают разные (поэтому наверное они и называются по разному). И на этой схеме мы видим, что бывает их ни много ни мало, а целых 4: MMC, SD версии 1.х стандартной емкости, SD версии 2.х стандартной емкости и карта SD версии 2.х повышенной емкости (SD карты расширенной емкости или SDHC). Все бы ничего, но все они требуют разной инициализации и это самое обидное. В принципе, есть общий способ инициализации всех их кроме SDHC, но он не есть правильный, поскольку работать с картой SD версии 2.х стандартной емкости и картой MMC как с одинаковыми картами неправильно (отличие в структурах CID и CSD). Конечно, можно построить алгоритм для работы с каждой картой по отдельности, но мы пойдем универсальной дорогой(и самой трудной на первый взгляд).
Из приведенной структуры намечается следующий путь работы (я буду указывать основные моменты, которые необходимо выполнять, но они не указаны на данной схеме). Мы определили, что в разъем картоприемника вставили какую-то фигню. Мы в свою очередь делаем следующее: подаем питание в пределах от 2.7-3.6 В, ожидаем ~1мс (точно не знаю сколько, но чтобы питание устаканилось). SPI настроен как полагается (я думаю все умеют это делать) и вывод _CS карты памяти выставлен в логическую “1”. После этого нам необходимо подать минимум 74 тактовых импульса на линию SCLK SPI. Выполнив все это мы выставляем логический “0” на вывод _CS карты памяти и отсылаем команду CMD0. Из таблицы команд видим, что ответом на CMD0 является R1, структуру которого мы знаем. Немного отступлю от мысли и обращу внимание на то, что все ответы содержат в себе первым байтом R1, 7-й бит которого всегда является 0. Таким образом, мы можем отличать ответы от идущий по линии MISO байтов 0xFF. Итак, приняв R1, проверяем бит «In idle state» на равенство «1». Если это так, то карта находится на этапе инициализации. А вот теперь пришел первый этап определения типа карты памяти. Посылаем команду CMD8, которая указывает карте поддерживаемые МК напряжения питания для ее и спрашивает у выбранной карты может ли она работать в данном диапазоне напряжений, дожидаемся ответа R7. Как видно из блок-схемы, карты памяти стандарта MMC и SD версии 1.х эту команду не поддерживают и, соответственно, в своем ответе будут содержать бит «illegal command». Если сказанное ранее верно, то установленная карта либо MMC, либо SD версии 1.х. Теперь пришло время распознать, какая именно из этих двух типов карт вставлена в картоприемник. Для этого отправим карте памяти команду ACMD41, которая инициирует процесс инициализации карты. Эта команда посылается в цикле либо для ее выполнения взводится таймер, по которому проверяется ответ на эту команду. В любом случае, карта MMC не поддерживает ACMD41 и вернет «illegal command» в своем ответе. В таком случае вставленная карта есть MMC и для ее инициализации потребуется команда CMD1 (так же посылается в цикле, пока ответ на нее не будет равен 0). Получив ответ на CMD1 равный 0х00 карта MMC готова к работе. Если ответ на ACMD41 не содержит никаких установленных битов (т.е. равен 0х00), то карта SD версии 1.х и она готова к работе. Теперь вернемся чуть выше и предположим, что в ответ на команду CMD8 не содержал бит «illegal command», т.е. у нас карта памяти формата SD версии 2.х стандартной емкости(SDSC версии 2.х) или SDHC. Следующим шагом в таком случае есть отправка команды ACMD41 с параметром, указывающим карте памяти, поддерживает ли наше устройство карты памяти SDHC. Вне зависимости от того, есть поддержка SDHC или ее нет, мы циклически отправляем эту команду карте то тех пор, пока она (карта) не закончит процесс инициализации. Когда ответ от ACMD41 будет равен 0х00, карта памяти проинициализирована и готова к работе. Но для того, чтобы узнать, какая у на карта, мы отправим ей команду CMD58. Ответом от этой команды есть R3, который в свою очередь содержит регистр OCR. Проанализировав OCR на установку бита CSS можно определить тип карты: CCS == 1 – карта SDHC или SDXC, CCS == 0 – карта SDSC. Чтобы не быть голословным, приведу мой участок кода инициализации карты памяти:

MMC_PowerUp();
    
    // As in datasheet min 74 clk before init
    SPI_SendConst(0xFF, 10);

    m_CardStatus = ecsNOCARD;
    m_CardType = ectNOTSUPPORT;

    // Set _CS pin to low
    MMC_ACTIVATE();

    // Send CMD0. Switch up SPI mode
    m_ucCrc = 0x95;
    MMC_SendCommand(MMC_GO_IDLE_STATE, (unsigned long) 0x00, ertR1);
    SPI_ReadByte();
    
    
    // Card in IDLE state ?
    if (((CCardR1*)&ucRespData)->ucData == 0x01)        
    {
      m_CardStatus = ecsERROR;
      
      // Send CMD8. Check for SDC ver.2+ card
      m_ucCrc = 0x87;
      MMC_SendCommand(MMC_SEND_IF_COND, 0x01AA, ertR7);
      SPI_ReadByte();
      
      if (((CCardR1*)&ucRespData)->ucData == 0x01)
      {
        
        // Card type is SDC ver.2+

        // Check the card support Vdd 2.7-3.6V
        
        if ((0xAA == ((CCardR7*)&ucRespData)->R7Data.bits.bitCheckPattern) && (0x01 == ((CCardR7*)&ucRespData)->R7Data.bits.bitVoltageAccepted))
        {
          uiMaxErrorsCMD = 0xFFFF;
          
          // ACMD41
          do
          {
            MMC_SendCommandA(MMC_CMD_SD_SEND_OP_COND, (unsigned long)1 << 30, ertR1);
            SPI_ReadByte();
          }
          while((((CCardR1*)&ucRespData)->ucData) && (uiMaxErrorsCMD--));
          
          // CMD58
          if (0x00 == ((CCardR1*)&ucRespData)->ucData)
          {
            uiMaxErrorsCMD = 0xFF;
            do
            {
              MMC_SendCommand(MMC_READ_OCR, 0x00, ertR3);
              SPI_ReadByte();
            }
            while((((CCardR1*)&ucRespData)->ucData) && (uiMaxErrorsCMD--));
            
            if (0x00 == ((CCardR1*)&ucRespData)->ucData)
            {
              m_CardStatus = ecsOK;
              
              if (0x01 == ((CCardR3*)&ucRespData)->R3Data.bits.bitCCS)
              {
                m_CardType = ectSDHC;
              }
              else
              {
                m_CardType = ectSDCv2x;
                
              }
            }
          }
        }
      }// end CMD8
      else
      {
        // Card is MMC or SDC v1.x
        uiMaxErrorsCMD = 0x7FF;

        // ACMD41
        do
        {
          MMC_SendCommandA(MMC_CMD_SD_SEND_OP_COND, 0x00, ertR1);
          SPI_ReadByte();
        }
        while((((CCardR1*)&ucRespData)->ucData) && (uiMaxErrorsCMD--));
        
        // Check for SDC v1.0 card
        if (0x00 == ((CCardR1*)&ucRespData)->ucData)
        {
          // Card is SDC v1.0
          m_CardStatus = ecsOK;
          m_CardType = ectSDCv1x;
        }
        else
        {
          // Check for MMC card
          uiMaxErrorsCMD = 0x7FF;

          // CMD1
          do
          {
            MMC_SendCommand(MMC_SEND_OP_COND, 0x00, ertR1);
            SPI_ReadByte();
          }
          while((((CCardR1*)&ucRespData)->ucData) && (uiMaxErrorsCMD--));
          
          if (0x00 == ((CCardR1*)&ucRespData)->ucData)
          {
            // Card is MMC
            m_CardStatus = ecsOK;
            m_CardType = ectMMC;
          }
        }    
      }
    }


    MMC_Finish();

    if (m_CardStatus != ecsOK)
    {
      MMC_PowerDown();
    }
    else
    {
      MMC_SendCommand(MMC_SET_BLOCKLEN, MMC_BLOCK_SIZE, ertR1);
      SPI_ReadByte();
      MMC_Finish();

Закончив процесс идентификации(тип карты памяти) и инициализации, можно приступать к работе с картой памяти: вычисление объема, чтение и запись данных и т.д.Но это уже вопрос следующей статьи. Жду комментариев и вопросов. Критика и советы оч приветствуются!

P.S. Забегая немного вперед, пару скринов чтения информации с карты памяти:

SD MMC PC Info

Да. и если можно, объясните мне, чайнику, как залить видео? СПС =)
  • +9
  • 13 сентября 2011, 00:39
  • lleeloo
  • 1
Файлы в топике: Simplified_Physical_Layer_Spec.pdf

Комментарии (27)

RSS свернуть / развернуть
Спасибо за статью. Много имел дела с картами MMC/SD в свзязке с AVR, много подводных камней встречал, но статья некоторые моменты помогла освежить.
З.Ы. А таких моментов масса. Встречались карты, которые сами по себе порой включали проверку CRC. Таким приходилось принудительно его отключать в процессе инициализации.
0
А программа, скрины которой представлены в конце статьи — это твоя терминалка или специальная тулза для картридеров?
0
в устройстве используется софтварный усб. Реализован вендорный класс и при подключении к пк определяется как усб устройство. Работаю с ним через обертку к драйверам. Во второй статье выложу исходники
0
С нетерпением жду остальных частей.
0
Очень хорошо. Перенес в коллективный блог. Видео заливается на ютуб и полученная от него ссылка вставляется в теги для видео.
0
Познавательная статья +1
0
Из таблицы команд видно, что у каждой команды есть свой индекс
Так и не понял куда смотреть.
0
Прошу прощения, рисунок с таблицей по размерам не влез. Сейчас поправлю. Индекс это номер команды: например чтобы отправить команду CMD9 (индекс равен 9), нужно прибавить к 0x40 этот индекс.
0
Во второй части статьи я выложу исходники для AVR'ки и datasheet'ы на карты памяти
0
Спасибо!
Читал где-то слухи, что SDHC по спецификации не обязаны поддерживать SPI. Надеюсь это брехня?
0
Во-первых, зачем важему контроллеру 4Гб+ памяти ?? фильмы что ли хранить?
Во-вторых, если какая и не поддерживает, то можно спокойно пойти в магаз и за разумные деньги купить длугую карточку.
Так что не шибко парьтесь по этому поводу. единственное чем оно вам грозит — это при запуске она не проинициализируется, что вполне можно обнаружить.
0
Китайские карточки иногда не поддерживают спи несмотря на спецификации. Тут уж как повезёт.
0
Все что я юзал с SPI дружили. По спецификации положено всем, но как говорят товарищи по сообществу, то видимо бывают и поделки которые не держат. Но, опять же я таких не встречал
0
Где-то я читал, что поддержка SPI не обязательна для microSD и что microSDHC и выше часто ее не имеют (microSDSC большинство SPI поддерживает).
0
  • avatar
  • Vga
  • 16 сентября 2011, 14:25
Согласно докам SanDisk, Toshiba и чего-то там еще обязательно есть SPI причем в любой. Если производитель придерживается документации (не зряже стандар написан), то SPI должен быть
0
Возможно это связано с тем, что microSD (вроде бы) сначала были отдельным стандартом T-Flash. Ну и источник, откуда я инфу брал — нечто в духе википедии, так что не уверен. Ну и инфа выглядит в духе «бывают microSD без SPI и для microSDHC на такую нарваться шансов больше» (хотя я когда читал так понял, что microSDHC с SPI вообще сложно найти). Сам не проверял. Из того, что с SD по SPI работает у меня только NanoDSO, а он по любому SDHC не держит. Хотя нет, еще анлокер/ресеттер карт, но на SDHC его тоже еще не приходилось натравливать.
0
  • avatar
  • Vga
  • 16 сентября 2011, 14:57
Тоже подключал разные карты к ATMEGA8515, аппаратному SPI. MMC карты инициировались нормально, а SD никак, то вообще молчат, то мусор какой-то гонят. Уже подумал, что имеющиеся у меня SD карты SPI и не поддерживают. А оказалось важным следующее: битик SPHA при инициализации SPI. То есть по какому фронту CLK считываются данные в МК. у меня все заработало при SPHA=0. может кому пригодится…
0
Читайте внимательно статью!) После CMD0 получили 0х01. Карта перешла в режим инициализации или занята! Если CMD8 возвращает 0x05 то эта команда не поддерживается а карта еще в процессе инициализации=(
0
Да, и с какого перепуга вы шлете CMD55 как отдельную команду? Два раза отправлять CMD0 это так нельзя=) Вы же получили ответ на ее выполнение 0х01, поэтому еще раз на ее выполнение будет иллигал комманд=)
0
Да, и:
1)
Появился какой-то новый стандарт что ли?
или чей-то такое?)
надобно прочитать старый для начала, чтоп говорить о появлении нового
2)
Можно еще чего-нибудь попробовать отправить?)
можно отправить все что угодно, хоть «Войну и мир» Толстого, важно понимать к чему это может привести =)
0
Пардон, это относилось к тов. BeloBird
0
работаю с microSD картой, емкостью 2 гб
я отправляю на карту cmd0, а в ответ получаю ответ R1=0
чтото не так или повторить отправку cmd0?
0
Повторить однозначно=)
0
здрасьте всем! Автору респект!!!
не могу понять, как команду послать один раз, но в цикле????
«Для этого отправим карте памяти команду ACMD41, которая инициирует процесс инициализации карты. Эта команда посылается в цикле либо для ее выполнения взводится таймер, по которому проверяется ответ на эту команду.»
0
Спасибо=)
Вот так наверное, не?
// ACMD41
        do
        {
          MMC_SendCommandA(MMC_CMD_SD_SEND_OP_COND, 0x00, ertR1);
          SPI_ReadByte();
        }
        while((((CCardR1*)&ucRespData)->ucData) && (uiMaxErrorsCMD--));


Да, и не заметил где про 1 раз написано?
С уважением!
0
Скажи, пожалуйста, в какой программе рисовал схему для статьи?
0
  • avatar
  • NBS
  • 28 ноября 2012, 23:40
Вроде sPlan, правда было давно…
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.