Подписаться на получение новых статей на почту:

Шаг №19. Подключение карты SD к AVR. Протокол SPI. Часть 4

 Обновлено18.12.15. Всем привет. Сегодня мы продолжим разработку контроллера сбора данных, а именно сохранение информации непосредственно на карту SD. В прошлой статье была налажена работа термометра. Теперь эту информацию по времени, при подключении в дальнейшем часов реального времени(статья №29), мы будем заносить на карту памяти, получив своеобразную базу данных.

А также в дальнейшем перенесем эту информацию на ПК (статья №42), в базу данных под управлением MySQL (статья №48), через небольшое приложение на Java (статья №44). Но сперва разберемся что такое SD – карта и как с ней работать.Начнем с краткого обзора истории. Предшественником Flash-памяти является одна из энергонезависимых видов памяти, типа EEPROM, которая зарекомендовала себя и используется в микроконтроллерах. Flash-память возникла в ходе потребности увеличения емкости и изменения технологии стирания (в случае с памятью EPROM). Поэтому в 1984 году инженер компании Toshiba Фудзио Масуокой изменил технологию стирания, что в свою очередь решил недостатки предшественников Flash-памяти. Хочется добавить, что далее данная память начала делится по внутреннему устройству соединения ячеек в массив и алгоритмами чтения-записи – это NOR- и NAND-технология. А также различие по количеству хранимых битов в элементарной ячейке. Это SLC-устройства (single-levelcell), т.е. однобитовые ячейки различают только два уровня заряда на плавающем затворе. И MLC- устройства (multi–levelcell) — многобитовые ячейки различают больше уровней заряда. Второй тип приборов дешевле и более ёмкий, чем SLC-приборы, однако с большим временем доступа и меньшим максимальным количеством перезаписей (около 10 тыс. и 100 тыс. — SLC).

Вообще устройства технологии NOR — это двумерная матрица проводников, что позволяет получить более быстрый доступ к каждой ячейки памяти, но при этом площадь ячейки считается большой, поэтому данная технология используется для памяти программ микропроцессоров и для хранения небольших вспомогательных данных, сюда же можно включить и специализированные микросхемы начальной загрузки компьютеров
(POST и BIOS), процессоров ЦОС и программируемой логики.Типовые объёмы — от 1 кбайта до 1 Мбайта.
                  Второй тип устройства — NAND-технология — трехмерный массив имеет малую площадь ячейки, но относительно длительный доступ сразу к большой группе ячеек. Используется для больших объемов памяти. Вот с этой памятью мы и будем работать.
                   Но перед этим хочется сказать об недостатке. Как и у всего есть свой срок использования, так и у памяти есть ресурс износа. Производители в гонке за емкостью и лидерством на рынке, всегда упускают такой показатель как качество, т.к. он не совместим с высокой ценой. Так возвращаясь к износу хочется отметить что срок хранения информации при использовании MLC-устройств составляет примерно 5 лет, что связанно с накоплением необратимых изменений при изменении заряда. Если брать память NAND c SLC-устройства, то они являются более качественными, и соответственно дорогими. Стоит отметить что срок хранения информации очень во многом зависит от температуры, гамма-радиации и частиц высокой энергии.
                   Выше было сказано, что недостаток карты это ограниченное количество циклов перезаписей. Когда мы будем использовать файловую систему для управления файлами, то должны знать что такие системы записывают данные в одно место, естественно расходую ресурс выделенной области в итоге вывода ее из строя и соответственно уменьшая емкость. Для этого типа памяти используется NAND-контроллер, который должен равномерно распределять износ. Однако для удешевления устройств контроллер может и не использоваться, а его работу будет выполнять программный NAND-драйвер в операционной системе. После этого открытия, многие компании занялись разработкой своих стандартов портативных карт.

Далее перейдем непосредственно к рассмотрению карты.
Secure Digital Memory Card (SD) — формат карт памяти, разработанный для использования в основном в портативных устройствах. Чтобы разобраться в ее работе мы будем использовать спецификацию, которая описывает данный стандарт и называется SD Specifications ver3.01.
                Первое что нам необходимо, так это разобраться как работать с этой картой, как подключить и прочее. Сначала выберем карту. Для экспериментов я взял microSD емкостью 2Гб, стандарт емкости SDSC. Шина карты может работать по двум протоколам SD и SPI. Хочется отметить что данная карта это своего рода модификация карты MMC, где (в карте SD) основное внимание было уделено системе безопасности. Поэтому алгоритм работы по протоколу SPI такой же, ну и конечно же они односторонне совместимы. Т.е мы можем в слот SD карты вставить MMC, но не наоборот.

На рисунке ниже представлена схема подключения карты SD  по протоколу SPI.
Данный интерфейс позволяет обмениваться данными на высокой скорости, задействовав при этом минимальное количество выводов микроконтроллера, которые оснащеныПодключение SD-карты к микроконтролеру модулем SPI. С этого момента начнем использовать спецификацию. Первое что нас интересует- выбор режима. Разберемся в тонкостях на рис. ниже из раздела 6.4.1.1 представлена диаграмма напряжения питания и последовательность посылки команды. Здесь четко видно что после включения карты необходимо выждать несколько миллисекунд (1мс + от 0.1 до 35 мс(нарастание)) на стабилизацию. В течении этого времени на CS, MOSI линии должна быть подана 1.  Далее происходит задержка инициализации максимум 1 мс, при подаче на вход CLK 74 импульсов (тактов), после чего должна идти команда CMD0. Перейдем к главе 7 где четко описана последовательность действий.

Диаграмма напряжения питания
Стабилизация напряжения SD-карты

                   SPI протокол выбирается после включения питания и подачи команды сброса CMD0. Сама по себе карта SD работает в режиме SD. Вход в режим осуществляется если сигнал SC при подаче команды CMD0 будет 0. При переходе в режим SPI карта ответит форматом R1 (рисунок ниже).  Формат ответа представляет собой байт (зависит от команды см. таблицу 7.3 в спецификации) с флагами определяющие состояние карты. Правильные ответы для нас это будет 1 (в случае команды CMD0) и 0 во всех других случаях.
Формат ответа SD-карты1-й бит – режим ожидания
2-й – ошибка стирания
3- й – неизвестная команда
4-й – ошибка команды
5-й – ошибка в последовательности стирания
6-й –ошибка адреса
7-й – ошибка аргуента

В процессе сброса, карта должна ответить 0×01, что соответствует первому биту.

                 В спецификации есть четкая последовательность инициализации для SPI. Для чего используется команда CMD8 для проверки рабочего состояния карты, где происходит довольно не простой алгоритм проверки. Далее команда CMD58 для определения типа карты SDSD или SDHC и SDXC. А также команда CMD41 для запуска и проверки инициализации. Довольно не простой процесс инициализации с проверками, но я думаю что для простой записи данных можно использовать более упрощенный процесс. В разделе 7.2.7. говорится, что в режиме ожидания единственно допустимые команды для карточки  CMD41, CMD8, CMD58, CMD59 , а также для карт (толстых 2.1мм) памяти SD CMD1, который идентичен команде CMD41. В стандарте эта команда считается запрещенной для инициализации, и используется исключительно для различия карт 1,4мм и 2,1мм.
                  Пойдем более простым путем и используем команду CMD1.  Все выше описанное отобразим в коде в функции инициализации, но перед этим рассмотрим формат команды. Каждая команда или блок данных состоят из восьми битных байтов, которые выравниваются по сигналу CLK. Т.е. каждая команда выравнивается по границе 8 тактов. Сообщения SPI состоят из команды, ответа и данных. Вся связь контролируется микроконтроллером. Все команды имеют длину 6 байт. Передача начинается с первого левого бита.

На рисунке ниже представлен формат команды.

Формат команды SD-карты

Старт бит – с 0 начинается любая команда.Передаваемый бит – тоже всегда равна 1.
Индекс – непосредственно передаваемая команда.
Аргумент- для каждой команды аргумент указан в таблице спецификации.
CRC – проверка избыточности кода. По умолчанию в режиме SPI она отключена. Поэтому мы ее используем только для команды  CMD0, которая посылается до входа в режим и имеет значение CRC 0×95.
Стоп бит -  конец передаваемой команды.
 
Что ж приступим к написанию кода.
Начнем с необходимых 2-х функций: передача и прием байта.
1. Передача байта карте.
void trans_byte_sd (unsigned char data)// передаем массив битов
{
        for (unsigned char i=0;i<8;i++) //Перебираем байт
                {
                        if ((data&0×80)==0×00) //Если старший бит = 0
                                PORTB&=~_BV (PB3); //Выставить  MOSI (DI) -0
                    else
                                PORTB|=_BV (PB3);   //1
                   data=data<<1;        // сдвиг влево
                   PORTB|=_BV (PB5); //Импульс или строб
                   asm («nop»); //Пауза в 1 такт
                   PORTB&=~_BV (PB5);
            }
}

 

2. Прием байта микроконтроллером.
unsigned char receive_byte_sd (void)    // Возвращаем ответ
{
         unsigned char data = 0; // инициализируем массив
         for (unsigned char i=0; i<8; i++)
        {
            PORTB|=_BV (PB5); //Фронт импульса
                  data=data<<1; // Сдвигаем влево
            if ((PINB&_BV (PB4))!=0×00) // Если состояние пина 1
                  data=data|0×01;
           PORTB&=~_BV (PB5);     //0
           asm («nop»);
       }
       return data; // Возвращаем ответ
}

           Из выше описанных, основных, функций начнем писать дальнейший код. Далее пропишем функцию передачи команды. Здесь хочется обратить внимания, на то, что Вы можете передавать все 5-ть аргументов: непосредственно саму команду и 4-аргумента отвечающих за адрес ячеек памяти самой карты. Что касается 6-го байта, то CRC при входе в режим SPI отключается (по умолчанию) и  значение постоянно равно 0×95, которое используется только для CMD0, когда карта не в режиме. Включить проверку кода можно командой CMD58. Для экспериментов я передаю два аргумента.

3.Передача команды.
unsigned char comand_sd (char CMD, char arg) /*передаем команду и адрес к которому обращаемся и возвращаем ответ*/
{
        long int i=0; // переменная для счетчика
        unsigned char r1; // ответ карты
        trans_byte_sd (CMD); // команда
        trans_byte_sd (0×00);
        trans_byte_sd (0×00);
        trans_byte_sd (arg); // передача адреса
        trans_byte_sd (0×00);
        trans_byte_sd (0×95); // Передача CRC
/* После передачи команды ждем ответа формата R1.Каждой команде соответствует свой ответ*/
/* Цикл для ожидания получения ответа за определенное время*/
       do
      {
           r1=receive_byte_sd ();
           i++;
      }while (((r1&0×80)!=0×00)&&(i<0xffff)); /* Как только старший бит байта не равен 0 и i не превышает 65 535 тактов*/
      return r1; // Возвращаем ответ
}
4. Инициализация карты.

              Теперь мы можем прописать инициализацию карты. Кратко программа описывается следующим образом:  первое что необходимо, так это перевести карту в режим SPI. При подаче питания карта устанавливается в режим SD. Для выбора режима SPI на вход CS подается логический 0, в это же время подается команда сброса CMD0 и инициализации CMD1 на вход карты MOSI. Обратим внимание что команда начинается от шестнадцатеричного 0×40, к которому необходимо прибавить номер команды CMD в шестнадцатеричном виде.

unsigned char spi_card_init (void) // функция возвращает ответ
{            
                unsigned char r1; // переменная для приема ответа
                long int i =0; // переменная для счетчика
                _delay_ms (10); // небольшая задержка для стабилизации напряж.
                PORTB|=_BV (PB1); //CS, устанавливаем 1, при подаче тактов
                PORTB|=_BV (PB3); //линия подачи команд — 1 MOSI (DI)
                for (unsigned char i=0; i<80;i++) //посылаем более 74 импульса
                {
                               PORTB|=_BV (PB5); //CLK — 1
                               asm («nop»);      //задержка в один такт
                               PORTB&=~_BV (PB5); //CLK — 0
                }     
                PORTB&=~_BV (PB1); /* условие для входа в режим SPI линия CS должна быть равна 0 */
                r1=comand_sd (0×40,0×00); //CMD0=0×40, адрес без разницы
                if (r1!=0×01) return 4; //коды ошибок можете ставить любые
                trans_byte_sd (0xff); /*  посылаем строб, своеобразная пауза перед приемом ответа */
                do  // цикл приема ответа от карты
                {            
                               r1=comand_sd (0×41,0×00); /* посылаем команду инициализации */
                               trans_byte_sd (0xff); // пауза
                               i++; // счетчик
                }while ((r1!= 0)&&(i<65535)); /*пока не получен ответ 0 и количество циклов не превышает 0xffff */
                if (i>=0xffff) return 5; /*  возвращаем ошибку если превысило время опроса */
                return 0;//Возвращаем 0 в случае успешной инициализации
}

          Следующий важный момент, в спецификации пишется, что информация передается блоками, по 512 бит, причем если карта SDSC как в нашем случае, то длину блока можн0 установить от 1 до 512 бит командой CMD16. По умолчанию 512 бит. Далее опишем две функции приема и передачи блоков. В спецификации даны блок-диаграммы, опираясь на которые мы напишем код.
Передача блока информации на карту SD

Передача блока информации на карту.

За передачу ЕДИНСТВЕННОГО блока отвечает команда CMD24. После подачи команды, ждем ответ  После чего следует стартовый байт, который подготавливает контроллер карты к приему информации, по окончанию карта отвечает байтом о состоянии передачи, который описан в главе 7.3.3.1. Т.е. правильный ответ должен быть= 5.   Также ждем освобождения шины для дальнейшей передачи.

Байт состоянии передачи SD-карты

Байт отзыва о состоянии передачи.

В разделе 7.3.3.2  описывается формат передаваемого блока

Формат передаваемого блока. 

5. Передача блока информации.
unsigned char trans_block_sd (char* block, char arg) /* передаем массив данных и адрес, возвращаем ответ*/
{
        long int i; // счетчик
        unsigned char r1; // ответ
        r1=comand_sd (0×58,arg); //CMD24
        if (r1!=0×00)  return 6; // выход если ответ не 0
               trans_byte_sd (0xff);  // небольшая пауза
       trans_byte_sd (0xfe); // передаем стартовый байт
       for (int i=0;i<512;i++)    //передаем данные
              trans_byte_sd (block[i]);
      trans_byte_sd (0xff);  //байт CRC
      trans_byte_sd (0xff);  //байт CRC
      r1= receive_byte_sd (); //получаем байт подтверждения
      if ((r1&0×05) != 0×05) return 7; // проверку на правильную передачу
      do                         //Ждем освобождения шины
      {
              r1=receive_byte_sd ();
              i++;
       }while ((r1!= 0xff)&&(i<65535));
       if (i>=0xffff) return 8;
       return 0; // возвращаем 0 в случае успешной передачи
}

6.          И последняя функция — чтение блока.  Процедура в принципе та же что и с передачей, только на прием: сначала стартовый байт, далее 512 бит и  2 байта на проверку кода. Помните, даже если CRC отключено мы все равно обязаны отправлять и принимать 2 байта. Такой формат блока.

Чтение блока SD-карты

unsigned char receive_block_sd (char* block, char arg)  /* передаем массив для записи данных и адрес к которому обращаемся*/
{
            long int  i = 0;
            unsigned char r1;
            r1=comand_sd (0X51,arg);//CMD17
            if (r1!=0×00) return 5;          //Выйти, если ответ не 0×00
                   trans_byte_sd (0xff);
            do                                                 //Ждем начала пакета данных
            {
                     r1=receive_byte_sd ();
                     i++;
             }while ((r1!= 0xfe)&&(i<65535));
            if (i>=0xffff) return 5;
            for (int i=0;i<512;i=i+1)         //прием данных
                  block[i] = receive_byte_sd ();
            receive_byte_sd ();  //байт CRC
            receive_byte_sd ();   //байт CRC
            return 0;
}

        Перед тем как использовать программу, рассмотрим аппаратную часть. Как мы говорили, выше, что карта совместима с микроконтроллером в режиме SPI. Отметим следующие нюансы работы с картой:
1. Сопряжение логических уровней, необходимо при разном напряжении питания SD-карты и микроконтроллера AVR.  Можно использовать резистивный делитель напряжения, который является линейным,т.е. напряжение на выходе зависит от напряжения на входе. А можно параллельный параметрический стабилизатор напряжения на стабилитроне, тоже что и первый вариант ,только в нижнем плече используется стабилитрон, который является нелинейным делителем, и следит за опорным напряжением за счет своих свойств при повышении входного напряжения уменьшать внутреннее сопротивление ,и наоборот.
         Я использовал второй вариант. В схеме ниже на сигнальной линии сопротивления являются балластными(токоограничители), на вход делителя поступает напряжение 4,5 – 5 В, а выходное снимается с нижнего плеча делителя.   Токоограничители необходимы для защиты карты и другой периферии при сбоях микроконтроллера. При хорошо отлаженном устройстве в них нет необходимости. Схема питания SD-карты
Заметьте, что линия MISO не нуждается в согласовании, т.к. работает только в одну сторону от карты к микроконтроллеру. 
2. Второй момент, я не использую проверку наличия карты и защиты записи. У кого то есть эти контакты в слотах, у кого то нет.
3. Последний момент- питание. Либо ВЫ питаете 3.3 вольта всю схему, включительно с микроконтроллером, либо ставите делитель на вход схемы, не очень надежно. Либо стабилизатор 3.3 вольта, как я и сделал на микросхеме LP2980. Важным моментом здесь является электролитический (танталовый) конденсатор, который защищает  микроконтроллер от сброса при просадках напряжения. 
Ниже представлена программа и результат.   Как всегда, я стараюсь использовать одну программу постоянно ее изменяя. Данный код взят из статьи №5 (семисегментный индикатор).

#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include <math.h>
//макросы для работы с индикатором
#define a 128
#define b 32
#define c 8
#define d 2
#define e 1
#define f 64
#define g 16
#define dp 4

// Переменные

char block [512]={};            //буфер записи/чтения данных на карту
short unsigned int j, k = 0; //в макросе прерывания
unsigned char Slot[11]; // Массив чисел для отображения на индикаторе

//Объявляем функции

void trans_byte_sd (unsigned char data); // функция передачи байта
unsigned char receive_byte_sd (void); //Функция приема байта
unsigned char comand_sd (char,char); // функция передачи команды
unsigned char spi_card_init (void); //Функция инициализации карты памяти
unsigned char receive_block_sd (char* block, char arg); //Функция приема блока
unsigned char trans_block_sd (char* block, char arg); //Функция передачи блока
// Инициализации индикатора
void Slot_init ()
{…………………….};
// Переменные для отображения цифр
char Elem1, Elem2, Elem3;       
// Вывод на индикатор
void Display (float i)
{  …………………………...   }

 

int main (void)              //начало основой программы
{
       DDRB = 0x2A;  //0010 1010 – PB1, PB3, PB5
       DDRD = 0xff;   //все выводы порта — выходы
       PORTD = 0×00;     //устанавливаем 0
       PORTB |= 0хdb; //1101 1011 (PB0,1,3,4,6,7)
       Slot_init ();
       sei ();  // либо SREG |= (1 << 7); разрешить общее прерывание
//инициализация таймера Т0
       TIMSK = (1<<TOIE0);    /*Флаг разрешения по переполнению таймера счетчика Т0*/
       TCCR0 = (0<<CS02)|(1<<CS01)|(0<<CS00);     //1000000/8 = 125000
       unsigned char temp;
       int i;   
       for (i=0;i<512;i=i+1)
       block[i]= i;   //записываем в буфер
       spi_card_init ();      //инициализация
       trans_block_sd (block,0×04);    //отправляем данные карте
//Обнуляем буфер
       for (int i=0;i<512;i=i+1)
       block[i]=0;  
  // Считаем данные с карты
       receive_block_sd (block, 0×04); ; //Функция приема байта
       for (int i=0;i<512;i=i+1)
      {
             char otv;
             otv = block[i];
             Display (otv);
             _delay_ms (100);
      }         
     //Запишем по адресу в память 0
       for (int i=0;i<512;i=i+1)
                 block[i]=0;
      unsigned char comand_sd (char,0×00); //функция передачи команды
       trans_block_sd (block,0×04);      //отправляем данные карте
}
//Вывод на индикатор
ISR (TIMER0_OVF_vect)
{    …………….    }

Важный момент — это таймауты. Важно следить за временем чтения записи и стирании карты, так как может зависнуть микроконтроллер в режиме ожидания ответа карты. В спецификации четко описаны таймауты карты. Простой карты длится 5 мс, после чего переходит в энергосберегающий режим, в котором допустимы следующие команды CMD0, CMD1, CMD41 и CMD58. Поэтому при превышении лимита простоя передаем CMD1, ответ и дальше работаем с картой.
          Внизу представлено два скриншота из программы WinHex, с помощью которой мы можем посмотреть содержимое ячеек памяти. Программа работает следующим образом: Записываем данные в буфер, оправляем карте, обнуляем буфер, считываем данные с карты в буфер и выводим на дисплей тем самым убеждаемся в передачи данных карте. Смотрим содержимое карты, обнуляем буфер, записываем 0 в карту и опять открываем содержимое карты, тем самым убеждаемся в работоспособности программы и схемы. Как всегда незабываем о мелочах, таких как не допайка, не большие трещенки в дорожках и др., что может забрать львинную долю времени. Поэтому если есть под руками осциллограф, то непременно используйте его для наладки. В статье №24 я привел небольшой пример диагностики карты на всех этапах ее работы.
Открываем карту-SD в WinHex 
WinHex - содержимое карты-SD
Подключение SD-карты к AVRНу вот в принципе и все. В следующей статье, перед тем как описывать файловую систему и создавать логгер мы познакомимся с датчиком влажности и температурыDHT11. После чего начнем записывать данные (температуру и влажность) в текстовый файл, своеобразную базу данных. Пока на этом все.  Всем пока.

Просмотрено 14791 раз.

Я на Google+

Шаг №19. Подключение карты SD к AVR. Протокол SPI. Часть 4: 5 комментариев

  1. Вместо стабилитронов в схеме подключения можно поставить подтягивающие резисторы на 10 кОм.

  2. Спасибо за информацию. Вот бы еще рассмотреть работу карты в режиме SD, как Вы здесь описали SPI.

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

Ваш e-mail не будет опубликован.

Можно использовать следующие HTML-теги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Subscribe without commenting