Arduino: Сравнение "классического" С и Arduino на примере SD-карты

топ 100 блогов ru_radio_electr29.07.2010 Начало эпопеи тут:
Arduino часть 1: Arduino
Arduino часть 2: Shield для Arduino

Некоторое время назад я приобрел Arduino Duemilanove на ATMEGA328P и начал с ним "играть". Некоторое из результатов покажу своему ЖЖ и сообществам :) Пользуясь случаем, хочу сказать спасибо Arduino: Сравнение telegamochka, за позитив :) Она смогла дать мне импульс мотивации оторвать задницу от дивана и начать снова программировать под микроконтроллеры. В предыдущих записях я писал, что сделал небольшую платку для Arduino, с помощью которой подключил к нему SD-карту, которую теперь настало время испробовать в действии.

Лирическое отступление: по идее можно было бы взять готовую библиотеку для работы с SD картами, например SdFat. Но мне с одной стороны хотелось попробовать самому (процесс обучения), с другой - написать код, не используя Hardware-SPI. Кроме того, хочется написать очень минималистичную библиотеку.

Чтобы начать нужна документация, а еще лучше готовый пример. В качестве примера я взял код немецкого радиолюбителя Ulrich Radig (http://www.ulrichradig.de / много интересных проектов, но по-немецки), документацию на SPI-режим работы SD-карты можно взять тут.


Лирическое отступление 2: Уже не в первый раз я сталкиваюсь с иррациональным неприятием Arduino, примерно как и в свое время столкнулся с неприятием Visual Basic. Этого я в принципе не понимаю. У каждого языка есть свои задачи и своя область применения. Никто же в здравом уме и твердой памяти не лезет писать вебсайты на C++, для этого берут Perl, PHP, ASP и еще черт знает что... Так же как и написание программы с GUI намного проще на Visual Basic (в современности .NET-языки), чем на "голом" С++. На самом деле Arduino использует обьектно-ориентированный avrgcc в качестве основного компилятора, а значит и код не сильно отличается. Я попробую в тексте поста это наглядно показать, сравнивая исходный код на С и код на ардуиновом "С++".

Библиотека Arduino имеет вид пары файлов (*.CPP, *.H), лежащих в отдельной папке в директории "libraries" среды Arduino. Задача - их написать. Далее в тексте я буду выделять синим цветом код на С, и красным - код для Arduino, сравнивая их друг с другом.


Логика работы программы: инициализация карты происходит командами 0 и 1, после чего карта до выключения питания находится в последовательном режиме. Код будет давать возможность чтения отдельных секторов карты по их номеру, а также служебных данных карты (CID и CSD блоков). Для инициализации используется функция mmc_init(). Ее обьявление, конечно же, есть в *.h файле:


extern unsigned char mmc_init(void);

Но мы же работаем с обьектами! Поэтому мы в нашем *.h файле делаем обьявление класса, более того, вместо работы с константами заводим private элементы для сохранения номеров ног, к которым подключена карточка. Номера ног передаем параметрами в Init, что позволит нам в основной программе легко конфигурировать обьект, не прибегая к помощи констант, а также иметь несколько полностью независимых SPI-интерфейсов:


class SD {
  public:
    unsigned char Init(int pinCS, int pinCLK, int pinDO, int pinDI);
  private:
    unsigned char _SD;
    unsigned char _CLK;
    unsigned char _DO;
    unsigned char _DI;
};

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


Реализация функции mmc_init() делится на несколько этапов: инициализация ног контроллера, выполнение команды 0, выполнение команды 1. Если эти этапы пройдены и карта отвечает на команды - маркируем карту (обьект) как проинициализированную.


unsigned char mmc_init ()
{
  MMC_Direction_REG &=~(1<<SPI_DI); //MMC_DI - Input
  MMC_Direction_REG |= (1<<SPI_Clock); //MMC_Clock - Output
  MMC_Direction_REG |= (1<<SPI_DO); //MMC_DO - Output
  MMC_Direction_REG |= (1<<MMC_Chip_Select); //MMC_Chip_Select - Output
  MMC_Write |= (1<<MMC_Chip_Select);
  ...

По идее мы можем этот код перенять один к одному в код для Arduino, но это же не комильфо :) Определим в заголовочном файле пару макросов, которые нам понадобятся:


#define SD_Enable() digitalWrite(_CS, LOW);
#define SD_Disable() digitalWrite(_CS, HIGH);

А в файле с кодом используем их:


unsigned char SD::Init(int pinCS, int pinCLK, int pinDO, int pinDI)
{
  _CS = pinCS; // Заполняем поля обьекта
  _CLK = pinCLK;
  _DO = pinDO;
  _DI = pinDI;
  pinMode(_CS, OUTPUT); // Устанавливаем режимы работы портов
  pinMode(_CLK, OUTPUT);
  pinMode(_DI, INPUT);
  pinMode(_DO, OUTPUT);
  SD_Enable();
  ...

pinMode конфигурирует пин на вход или выход, digitalWrite используется для установки одного выходного пина в HIGH или LOW. Обе функции - стандартные функции Arduino. По идее сделано все то же самое, разве что читабельность кода слегка возросла, но это - дело привычки.


Теперь займемся инициализацией карты. Для начала нужно послать 74 или более "пустышек"-битов (скорее всего для очистки буфера карты).


  ...
  for (unsigned char a=0;a<200;a++){ nop(); }; //Small pause 200µs
  for (unsigned char b=0;b<0x0f;b++) //74+ Clocks to MMC/SD-Card
  {
    mmc_write_byte(0xff);
  }
  ...

Сама же функция mmc_write_byte() определена в заголовочном файле:


extern unsigned char mmc_write_byte(unsigned char Byte);

И ее реализация:


void mmc_write_byte(unsigned char Byte)
{
  for (unsigned char a=8; a>0; a--) //MSB First
  {
    if (bit_is_set(Byte,(a-1))>0) {                          {
      MMC_Write |= (1<<SPI_DO); //Set Output High
    } else {
      MMC_Write &= ~(1<<SPI_DO); //Set Output Low
    }   
    MMC_Write &= ~(1<<SPI_Clock); //Clock LOW
    MMC_Write |= (1<<SPI_Clock); //Clock High
}
MMC_Write |= (1<<SPI_DO); //Output High at the end        
}

Arduino не отстает. В процедуре Init делаем то же самое:


  ...
  delayMicroseconds(200);
  for (unsigned char a = 0; a < 15; a++) { WriteByte(0xff); }
  ...

WriteByte будет использоваться только внутри класса, потому определяем его в заголовочном файле как private:


class SD {
  public:
    unsigned char Init(int pinCS, int pinCLK, int pinDO, int pinDI);
  private:
    unsigned char _SD;
    unsigned char _CLK;
    unsigned char _DO;
    unsigned char _DI;
    void WriteByte(unsigned char Byte);
};

Ee реализация выглядит немного проще, чем "оригинал":


void SD::WriteByte(unsigned char Byte) 
{
  for (unsigned char a = 8; a > 0; a--) 
  {
    digitalWrite(_DO, bitRead(Byte, a-1)); //Выставляем очередной бит
    digitalWrite(_CLK, LOW); //Делаем строб выходом CLK
    digitalWrite(_CLK, HIGH);
  }
  digitalWrite(_DO, HIGH); //В конце выставляем выход DO в HIGH чтобы не блокировать линию
}

Как видим, все абсолютно то же самое...


Особенно элегантно выглядит использование нами созданного класса в коде скетча:


#include "SD.h"

SD sd_card;

void setup() 
{
  unsigned char r;
  //Инициализация карты на цифровых пинах 3=CS, 2=CLK, 4=DO, 5=DI (DO/DI с точки зрения Arduino)
  r = sd_card.Init(3,2,4,5);
  if (r == 0) {
    //Карта инициализирована корректно - дальнейшая работа, например инициализация FAT16
  } else {
    //Реагируем на ошибки - что-то не так
  }
}

void loop() {}



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

Пожалуй, чтобы сделать несколько выводов информации достаточно. Во-первых, не так страшен черт - Arduino является самым обыкновенным контроллером семейства АТМЕГА и все, что контроллеру доступно, доступно и тут. Среду разработки можно сравнить с некоторого рода фреймворком, облегчающим процесс входа и работу с периферией. Возвращаясь к идее, что для каждой задачи есть свой наиболее подходящий инструмент, можно сказать, что для выполнения 90% задач, реализуемых на микроконтроллерах Arduino подойдет как нельзя лучше - задача выполняется быстрее за счет меньшего количества настроек. Недостаток - ограничение на определенные типы контроллеров (в принципе решается расширением среды разработки). Так что, не нужно holywar-ов, пользуйте на здоровье!

Оставить комментарий

Архив записей в блогах:
Евнухи прочно ассоциируются с дворцами китайских императоров и гаремами турецких султанов. Куда меньше известно о евнухах, занимавших видные должности при дворах древнерусских князей. Евнухи на Руси Согласно летописи, «ненасытный в блуде» князь Владимир имел колоссальное число ...
в бескрайних степях интернета мне иногда встречаются беснующиеся антисемиты, которые стыдливо себя называют антисионистами, и их ближайшие сородичи - великороссы, они же русские имперцы, которые до дрожи в голосе ненавидят и боятся украинцев. Зачастую антисемит и великоросс это одно и ...
5 мая в 22:30 в вагоне метро едущем от станции метро "Сухаревская" к станции "Проспект Мира" зарезали моего очень близкого друга Диму Сало. Сегодня 9 дней, и у меня есть все основания полагать, что наши правоохранительные органы пытаться все ...
В 23-м февраля не было бы ничего странного, если бы его отмечали так, как нам рекомендовало советское правительство и рекомендует российское. Они (правительства) установили профессиональный праздник для людей, которые выбрали своей профессией службу в армии. У всех: пекарей, строителей, ...
В Египте разные культуры тесно переплетены и невольно встречаются. Мой друг полковник встретил осьминога. Стоимость поездки сразу показалась разумной. Жена полковника плавала рядом. Вложения в неё тоже хотелось отбить. Полковник показал жестом «Поплыли со мной, покажу осьминога!» Рот его ...