Директивы препроцессора


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

Великий и ужасный #define


Мы с вами уже сталкивались с #define в предыдущих уроках, сейчас хочу рассказать о некоторых частных случаях. Напомню, define – это команда препроцессору заменить один порядок символов на другой, например #define MOTOR_SPEED 50 заменит все встречающиеся в коде MOTOR_SPEED цифрой 50 при компиляции. Если не писать ничего после указания первого набора символов, препроцессор заменит их на “ничего”. То есть #define MOTOR_SPEED просто удалит из кода все сочетания MOTOR_SPEED. Также define позволяет создавать макро-функции, об этом мы говорили в уроке про функции.

В чём же состоит сила и опасность #define? Он распространяется на все документы, которые подключаются в код после него, а также работают в коде, будучи описанными в другом документе. Рассмотрим подробнее:

Если ПЕРЕД подключением файла вы объявите define, то данный define будет распространяться на этот файл и заменит указанный текст.

Если что-то в подключаемом файле (имена функций и переменных) совпадёт в вашим дефайном – будет ошибка компиляции. Например, в библиотеке FastLED есть цвет DarkMagenta, внутри библиотеки цвета объявлены как enum. Если я сделаю дефайн на такое имя – получу ошибку:

Но, если в подключаемом файле есть свой define с таким же именем, то работать будет define файла!

Важный момент: наш скетч в Arduino IDE по сути является .cpp файлом, и define из него могут распространяться только на заголовочные файлы .h! То есть в файле .h подключаемой библиотеки дефайн будет “видно”, а вот в .cpp – уже нет!

Как решить эту проблему? Например, мы хотим управлять компиляцией библиотеки при помощи define-ов, расположенных не в заголовочном файле библиотеки (потому что из заголовочного можно, это и так понятно). Есть два несложных варианта (сложные не рассматриваем):

  • Поместить исполнительный код библиотеки в заголовочном .h файле (.cpp не создавать вообще), тогда дефайном из скетча можно будет влиять на компиляцию исполнительного кода. Этот пример мы рассматривали в самом первом скриншоте.
  • Создать в  папке с библиотекой отдельный заголовочный файл, например config.h, в нём собрать необходимые дефайны “настроек”, и этот файл подключать во все файлы библиотеки, в этом случае .cpp файл библиотеки сможет подхватить нужный define. Так сделано, например, в библиотеке FastLED.

На этом сложности не заканчиваются: define из одной библиотеки может пролезть в другую библиотеку, которая подключена после первой! Вернёмся к тому же примеру с DarkMagenta – если в моей библиотеке я задефайню это слово и подключу библиотеку до подключения FastLED – я получу ошибку компиляции! Если поменять подключение местами – ошибки не будет. Но, если я захочу использовать DarkMagenta в своём скетче, я буду неприятно удивлён =)

Что я хочу сказать в итоге: define – гораздо более мощный инструмент, чем может показаться на первый взгляд. Использование define с невнимательным отношением к именам может привести к ошибке, которую будет непросто отловить. Это палка о двух концах: с одной стороны хочется использовать в своей библиотеке define, чтобы никто другой случайно не пролез со своими дефайнами. В то же время, своя библиотека может начать конфликтовать с другими библиотеками. Какой тут выход? Очень простой! Делать имена дефайнов максимально уникальными: если это библиотека – оставлять префикс библиотеки, если это скетч – делать префикс с именем скетча. Также можно отказаться от define в пользу констант или enum, enum кстати удобнее define в плане создания набора констант, а места занимает совсем немного!

Ещё хочу напомнить, что в противовес дефайну есть андеф – #undef, директива, которая разопределяет набор символов.

Условная компиляция


Условная компиляция является весьма мощным инструментом, при помощи которого можно вмешиваться в компиляцию кода и делать его очень универсальным как для пользователя, так и для железа. Рассмотрим директивы условной компиляции:

  • #if – если
  • #ifdef – если определено
  • #ifndef – если не определено
  • #else – иначе
  • #elif – иначе если
  • #endif – конец условия
  • defined – проверка, определён ли

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

#define USE_DISPLAY 1 // настройка для пользователя

#if (USE_DISPLAY == 1)
#include <библиотека дисплея.h>
#endif

void setup() {
#if (USE_DISPLAY == 1)
  // дисплей.инициализация
#endif

}
void loop() {
}

#define SENSOR_TYPE 3   // настройка для пользователя

// подключение выбранной библиотеки
#if (SENSOR_TYPE == 1 || SENSOR_TYPE == 2)
#include <библиотека сенсора 1 и 2.h>
#elif (SENSOR_TYPE == 3)
#include <библиотека сенсора 3.h>
#else <библиотека сенсора 4.h>
#endif

#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
// код для ATmega1280 и ATmega2560
#elif defined(__AVR_ATmega32U4__)
// код для ATmega32U4
#elif defined(__AVR_ATmega1284__)
// код для ATmega1284
#else
// код для остальных МК
#endif

Сообщения от компилятора


В последней главе рассмотрим директивы, при помощи которых можно выводить текст в окно информации (лог) о компиляции – те самые рыжие буквы в логе.

Для вывода сообщения можно использовать директиву #pragma message , выглядит вот так:

Также есть директива #error, она тоже выводит текст, но вызывает ошибку компиляции:

И pragma message и error можно вызывать при помощи условной компиляции, рассмотренной в предыдущей главе.

Важные страницы


  • Каталог ссылок на дешёвые Ардуины, датчики, модули и прочие железки с AliExpress у проверенных продавцов
  • Подборка библиотек для Arduino, самых интересных и полезных, официальных и не очень
  • Полная документация по языку Ардуино, все встроенные функции и макро, все доступные типы данных
  • Сборник полезных алгоритмов для написания скетчей: структура кода, таймеры, фильтры, парсинг данных
  • Видео уроки по программированию Arduino с канала “Заметки Ардуинщика” – одни из самых подробных в рунете
Последнее обновление Сентябрь 12, 2019
2019-09-12T01:33:10+03:00