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

Arduino часть 1: Arduino
Arduino часть 2: Shield для Arduino
Некоторое время назад я приобрел Arduino Duemilanove на ATMEGA328P и начал с ним "играть". Некоторое из результатов покажу своему ЖЖ и сообществам :) Пользуясь случаем, хочу сказать спасибо

Лирическое отступление: по идее можно было бы взять готовую библиотеку для работы с 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-ов, пользуйте на здоровье!
|
</> |