Инструкцияdefineв ардуино, как и в языке C++, нужна для того, чтобы упростить написание скетчей. Мы можем Pin-Up раз определить название какого-то фрагмента кода, а затем везде использовать только это название. В этой статье мы на конкретных примерах разберемся с такими вопросами, как правильно использовать #define, что такое препроцессор, в каких случаях надо использовать define, а в каких - лучше const.
Содержание
Синтаксис define ардуино
Синтаксис использования инструкции достаточно прост:
#define <что меняем> <на что Pin-Up
- Знак # означает начало Pin-Up препроцессора.
- define - название инструкции.
- <что меняем> - имя макроса: словосочетание, которое Pin-Up находить препроцессор
- <на что меняем> - тело макроса: строка, которая будет подставлена в то место, где будет найдено Pin-Up меняем>
Обратите Pin-Up что в конце строки не нужно ставить знак точки с запятой.
Примеры использования define:
- #define PIN_LED 13
- #define RED 1
- #define BUTTON_LEFT 5
- #define test Serial.println(“test”)
- #define test(a) Serial.println(a)
Большой интерес вызывает последний Pin-Up Мы можем попросить ардуино подставить во фрагмент кода тот аргумент, который мы указали в качестве аргумента для параметра <что меняем>. Мы поговорим об этом в статье ниже.
Тело макроса должно заканчиваться в той же Pin-Up Но если мы хотим сделать многострочный блок, то добавляем символ “ Например:
Pin-Up LONG_STRING "Очень длинный текст \ Который мы смогли разбить на две части"
Описание define
#defineявляется одной из инструкций препроцессора Pin-Up и C++. Само словопрепроцессор означает, что работа с такими инструкциями проходит до основного процесса компиляции Pin-Up Во время такого «нулевого» этапа препроцессор компилятора пробегает по исходному коду, находит все наши инструкции и производит замену прямо в том же исходном коде. Все это делается незаметно от нас, мы результатов этого отдельного шага не видим. И только после работы препроцессора запускается сам компилятор, который будет анализировать и собирать код с учетом тех изменений, который уже сделал препроцессор.
В чем-то это напоминает механизм макросов и шаблонов. Расставив определенные с помощью #define Pin-Up по всему коду, мы даем возможность компилятору автоматически заменить их перед компиляцией на то, что нам нужно. Единственное исключение - если идентификатор находится внутри скобок “”. Тогда подстановки не будет.
Все это нужно для того, чтобы во время создания Pin-Up тратить меньше времени на написание команд и их изменение. Мы можем «закодировать» коротким словом большую «длинную» конструкцию и писать в коде ее («короткую»), а не длинную. А компилятор сам подставит «длинную» в нужные места перед компиляцией. Также мы можем один раз изменить значение подстановки в начале программы, и новое значение само подставится во всех нужных местах. Возможностей использования #define - много. Как это работает - посмотрим на примерах ниже.
Примеры define в arduino
define pin
Самое частое использование define в Arduino - это определение констант для номеров пинов. С помощью инструкции define можем «дать название» какому то числу, и потом везде в коде использовать именно это название. Вот самый простой фрагмент примера со Pin-Up
// Arduino define pin #define PIN_LED 13 // Мы сказали Arduino, что будем использовать слово PIN_LED, но на самом деле это цифра 13 void setup(){ pinMode(PIN_LED, OUTPUT); } void loop(){ digitalWrite(PIN_LED,HIGH); digitalWrite(PIN_LED, Pin-Up // Вопрос внимательным читателям - что в этой функции маячка не так? J }В данном случае мы определили новое слово PIN_LED и можем его использовать повсюду в коде. Перед тем, как компилировать программу ардуино пробежится по коду и везде, где Pin-Up словосочетание PIN_LED, заменит его на цифру 13. Т.е. в конечном итоге команде pinMode все равно придет в параметрах номер пина 13. Ифункция digitalWriteтоже получит 13. Но при этом мы явно в коде цифру 13 не использовали. А это очень хорошо: если нам нужно будет переключить светодиод на другой порт (например, 12), нам не придется Pin-Up в ужасе по коду и менять цифры.
В итоге мы просто поменяем цифру один раз (в блоке #define ) и теперь уже во все функции вместо PIN_LED будет передаваться цифра 12. Представьте, Pin-Up у вас скетч длиной 1000 строк и в 50 разных местах вы обращаетесь к пину 13, а нужно поменять на 12. С помощью #define вы вместе с компилятором аруино сделаете все за 1 секунду. А вручную, да без ошибок, вам придется провозиться гораздо дольше.
define константы
С помощью #define мы можем определять «псевдо константы». Они будут работать как обычные константы, но самих переменных создаваться при этом не будет. Например, создав макрос BUTTON_UP со значением 1, мы можем использовать его в коде как константу. Например, как только мы определили, что была нажата кнопка вверх, мы можем запомнить код нажатой кнопки и передать его другим функциям, которые будут сравнивать этот код и делать какие-то действия. Сам код кнопки мы можем придумать любым: хотим - 1, хотим - 345 (такие цифры часто Pin-Up «магическими», т.к. их придумывают сами программисты и не всегда понятно, почему придумали именно так), главное, чтобы этот код оставался неизменным в тексте программы. Лучшим решением будет закодировать этот код в константе (например, BUTTON_UP) и использовать вместо цифры именно название константы.
Вложенные define
Вы вполне можете использовать в блоке инструкции define другие макросы. Например:
#define pin 13
#define delay delay(pin)
Во второе определение вместо pin подставится цифра 13 и в итоге мы Pin-Up
#define delay delay(13)
Ошибки define
Главное при Pin-Up define - это выбрать такое название макроса, которое не совпадает с другими конструкциями языка. Потому что ардуино найдет все совпадающие фрагменты и заменит тем, что мы определил в define. Компилятору все равно, какие конструкции он заменяет, поэтому он с легкостью выполнит подстановку любого кода. Результат будет непредсказуемым.
Например, определив
#define setup 12345
,вы Pin-Up ардуино заменить команду setup и безобидная функция превратится в
void 12345(){}.
В таком случае, когда ардуино приступит к компиляции, он уже не найдет обязательнуюфункцию setupи очень рассердится. К тому же, он обнаружит соврешенно странную строку 12345, нарушающe правила Pin-Up переменных и функций.
Вы также можете создать трудноуловимую проблему, Pin-Up нечаянно добавив символ точки с запятой в конец строки:
#define pin 13;
//…
digitalWrite(pin, HIGH);
После подстановки эта конструкция превратится в digitalWrite(13;, HIGH). Pin-Up внутри блока аргументов появится точка с запятой - а это, без сомнения, нарушение синтаксиса.
Еще одной серьезной ошибкой может стать несоответствие Pin-Up которое вы не сможете сразу же распознать. Например, если вы определите
#define PIN_LED pin12
То при использовании в функции
digitalWrite(PIN_LED, HIGH);
возникнет ошибка, т.к. на Pin-Up вы подадите не цифру, а строку «pin12». Компилятор подсветит вам строку с ошибкой, но вы в ней не увидите текста «pin12», а всю ту же строку digitalWrite(PIN_LED, HIGH); . В результате вы можете надолго зависнуть, разбираясь, что, откуда и куда подставилось. Поэтому при работе с #define нужно быть очень аккуратным и без надобности не использовать эту инструкцию.
#Ifdef, #ifndef и Pin-Up - дополнительные команды препроцессора
Иногда бывает полезным знать, объявили ли мы уже в #define какую-либо инструкцию ранее. Это может Pin-Up полезным, если у нас много файлов в проекте и мы не всегда понимаем, в какой последовательности в итоге будут подключаться наши модули. Для всего этого мы и используем специальные инструкции #ifdef или #ifndef, которые проверят, было ли встречено данное определение ранее и, если было (или не было - для второго варианта), то оставит блок кода с последующей строки и до места встречи #endif.
Описание синтаксиса:
#ifdef имя_макроса
последовательность команд, которые будут оставлены в коде, только если Pin-Up макрос был определен ранее инструкцией #define. В противном случае данный участок кода будет Pin-Up
#endif
#ifndef имя_макроса
последовательность команд, которые будут оставлены в коде, только если до данного Pin-Up макрос не был определен
#endif
Пример для #ifdef
Мы проверяем, определили ли ранее в #define признак отладки, и если да, то код будет выполнен, если Pin-Up то ничего выводиться не будет.
#define DEBUG 1 // Если закоментировать всю эту строку, то отладочные сообщения Pin-Up отключены
#ifdef DEBUG Serial.println Pin-Up messge»); #endf
Пример для ifndef. Мы объявляем константу, только если не делали этого Pin-Up
#ifndef Pin-Up #define PIN_LED 13 #endif
Все Pin-Up понятно и похоже на обычные конструкции
define как альтернатива функциям
Сейчас мы рассмотрим более сложный вариант использования define - мы Pin-Up передавать внутрь конструкции define какие-то параметры. Но сначала давайте разберемся, зачем это нужно.
Допустим, мы Pin-Up упростить чрезмерно сложную для начального понимания функцию digitalWrite. Исходя , мы можем определить такую инструкцию и использовать ее в коде:
#define on digitalWrite(13, HIGH)
…
void loop(){
on;
}
В этом примере мы достаточно длинную команду digitalWrite Pin-Up одним коротким словом on. Теперь везде в коде, где встретитсяon, компилятор (препроцессор компилятора) вставит строку digitalWrite(13, HIGH). И в Pin-Up итоге функция loop будет выглядеть так:
void loop(){
digitalWrite(13, HIGH);
}
Все будет Pin-Up до того момента, когда мы захотим добавить второй светодиод и помигать им. Использовать инструкцию on уже не получится, т.к. на ее месте появится вызов для того же светодиода на 13 порту. И у нас есть два выхода:
- Написать еще Pin-Up макрос, например, #define on12 digitalWrite(12, HIGH)
- Воспользоваться замечательным механизмов Pin-Up параметров в препроцессорные инструкции.
Первый вариант - это путь в никуда. Потому что нам опять придется делать однообразную работу по копированию одного и того же Pin-Up А вот второй вариант мы сейчас рассмотрим.
В препроцессоре С++ (и, следовательно, ардуино), можно определить параметры, которые препроцессор будет использовать при замене одного фрагмента на другой. Можно указать, что в первом случае on должен вставить digitalWrite для 13 пина, а во втором - для 12 и т.д. Для этого нужно сделать следующее:
- Определить define следующим образом:
#define on(pin) digitalWrite(pin, HIGH)
- Использовать в коде такой вариант:
on(13);
В инструкции #define мы ввели параметр, который сами назвали a(можно выбрать любое имя) и прописали его явно с Pin-Up скобок: on(a). Во второй части инструкции мы просто используем название параметра в нужном месте: digitalWrite (pin, HIGH).
Теперь, встретив конструкцию on(13), препроцессор вставит Pin-Up digitalWrite (pin, HIGH), а вместо pin добавит то, что мы передали в скобках: цифру 13. В итоге в код вставится строка digitalWrite (13, HIGH).
Указав on(12), мы заставим вставить в код функцию digitalWrite (12, Pin-Up И так далее, в зависимости от наших нужд.
Таким же образом мы можем сделать макрофункцию (псевдофункцию) Pin-Up которая будет выполнять подстановку digitalWrite (pin, LOW).
В итоге код маячка со Pin-Up может стать до чрезвычайности простым:
void loop() { on(13); delay(1000); Pin-Up delay(1000); }Можно Pin-Up еще сильнее, потому что на вход макрофункции можно передавать несколько параметров:
#define out(pin) pinMode(pin, OUTPUT) #define on(pin, d) digitalWrite(pin, HIGH); delay(d) #define off(pin, d) digitalWrite(pin, Pin-Up delay(d) void setup() { out(13); out(12); } void loop() { on(13, 1000); off(13, 500); }Достаточно Pin-Up не правда ли? Также можно придумать очень много разных упрощающих инструкций. Но давайте немного остановимся и поговорим об ограничениях.
Все дело в том, что создавая такую «библиотеку» из собственных инструкций мы очень сильно Pin-Up
- Во-первых, мы запутываем код, потому что on(13) выглядит как функция, но она не является функцией! Она просто подставляет в код текст, ничего не вызывая, не помещая переменные в стек, не сохраняя их между Pin-Up И в более сложных примерах мы можем создавать трудноуловимые и опасные ошибки, которые могут возникнуть случайно на определенных наборах.
- Во-вторых, мы можем случайно переопределить уже Pin-Up инструкции и потом долго мучиться или от ошибок компиляции или, что еще опаснее, ошибок времени исполнения. В вашем проекте могут быть десятки различных скетчей и подключаемых библиотек. Что-то незаметно поменять в этой сложной каше из подключаемого кода- проще простого.
- Мы не проверяем параметры на входе, что тоже может привести к Pin-Up на этапе компиляции или же нарушению приоритетов выполнения операций на этапе исполнения.
Ради справедливости Pin-Up и о плюсах:
- Созданные с помощью#defineпсевдофункции будут выполняться быстрее, т.к. обрабатываются на этапе компиляции и не приводят к реальным вызовам функций, что уменьшает Pin-Up на такие затратные операции как работа со стеком. Если данный фрагмент кода используется часто, это может быть важным преимуществом.
- Макрофункции быстрее пишутся: при их создании не нужно указывать Pin-Up аргументов и инструкцию возврата результата (return).
- Созданные функции можно включать
В общем случае совет такой: используйте конструкцию #define для псевдофункций только в том случае, если структура вашего проекта проста, сама псевдофункция умещается в одну строчку, вызывается часто, вы не используете вложенные вызовы макросов и функций и всегда можете визуально оценить, какие изменения в коде Pin-Up подстановки.
Define или const
Альтернативой #define является использование констант. Константа в Pin-Up и C++ - это переменная, которая определяется c модификатором const. Пример:
сonst int PIN_LED = 10;
То же Pin-Up можно сделать с помощью define:
#define PIN_LED 10
Результат одинаковый - в коде вы Pin-Up используете конструкцию типа digitalRead(PIN_LED) и получите желаемое: в нужное место вставится цифра 10.
Но использование константы все-таки предпочтительней по следующим причинам:
- Мы не сможем непреднамеренно разрушить код программы, т.к. константы, в отличие от макросов, не вставляются во все подряд Pin-Up слова.
- Компилятор сообщит об ошибке, если мы пытаемся использовать константу не того Pin-Up
- На константы действуют Pin-Up правила области видимости переменных.
Если оценивать Pin-Up памяти необходимой для скетча с использованием #define или const, то никаких преимуществ ни тот, ни другой способ не дают - компилятор выделит одинаковый объем памяти для переменной, объявленной явно или встроенной в виде макроса. Исходя из всего этого, старайтесь при использовании констант в коде все-таки брать за основу const.













Вот это я понимаю объяснение!) Спасибо. Гляну, что еще тут в таком ключе есть!
Если я правильно понял, всё что следует за #defune, вставляется препроцессором в текст кода как простой его “кусок”. Если так, то это очень удобно. Например у Pin-Up есть участи кода где нужно вставлять одно и то же значение без привязки к типу. Пример для USART:
modem = SoftwareSerial(RX, TX);
modem.begin(57600); // здесь тип long
modem.println(“AT+IPR=57600”); // здесь тип String
Поддерживаю!