что означает команда define

Arduino define и const

Инструкция define в ардуино, как и в языке C++, нужна для того, чтобы упростить написание скетчей. Мы можем один раз определить название какого-то фрагмента кода, а затем везде использовать только это название. В этой статье мы на конкретных примерах разберемся с такими вопросами, как правильно использовать #define, что такое препроцессор, в каких случаях надо использовать define, а в каких – лучше const.

Синтаксис define ардуино

Синтаксис использования инструкции достаточно прост:

Обратите внимание, что в конце строки не нужно ставить знак точки с запятой.

Примеры использования define:

Тело макроса должно заканчиваться в той же строке. Но если мы хотим сделать многострочный блок, то добавляем символ “/” в конце. Например:

Описание define

#define является одной из инструкций препроцессора ардуино и C++. Само слово препроцессор означает, что работа с такими инструкциями проходит до основного процесса компиляции кода. Во время такого «нулевого» этапа препроцессор компилятора пробегает по исходному коду, находит все наши инструкции и производит замену прямо в том же исходном коде. Все это делается незаметно от нас, мы результатов этого отдельного шага не видим. И только после работы препроцессора запускается сам компилятор, который будет анализировать и собирать код с учетом тех изменений, который уже сделал препроцессор.

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

Все это нужно для того, чтобы во время создания программы тратить меньше времени на написание команд и их изменение. Мы можем «закодировать» коротким словом большую «длинную» конструкцию и писать в коде ее («короткую»), а не длинную. А компилятор сам подставит «длинную» в нужные места перед компиляцией. Также мы можем один раз изменить значение подстановки в начале программы, и новое значение само подставится во всех нужных местах. Возможностей использования #define – много. Как это работает – посмотрим на примерах ниже.

Примеры define в arduino

define pin

Самое частое использование define в Arduino – это определение констант для номеров пинов. С помощью инструкции define можем «дать название» какому то числу, и потом везде в коде использовать именно это название. Вот самый простой фрагмент примера со светодиодами:

В данном случае мы определили новое слово PIN_LED и можем его использовать повсюду в коде. Перед тем, как компилировать программу ардуино пробежится по коду и везде, где встретит словосочетание PIN_LED, заменит его на цифру 13. Т.е. в конечном итоге команде pinMode все равно придет в параметрах номер пина 13. И функция digitalWrite тоже получит 13. Но при этом мы явно в коде цифру 13 не использовали. А это очень хорошо: если нам нужно будет переключить светодиод на другой порт (например, 12), нам не придется бегать в ужасе по коду и менять цифры.

В итоге мы просто поменяем цифру один раз (в блоке #define ) и теперь уже во все функции вместо PIN_LED будет передаваться цифра 12. Представьте, если у вас скетч длиной 1000 строк и в 50 разных местах вы обращаетесь к пину 13, а нужно поменять на 12. С помощью #define вы вместе с компилятором аруино сделаете все за 1 секунду. А вручную, да без ошибок, вам придется провозиться гораздо дольше.

define константы

С помощью #define мы можем определять «псевдо константы». Они будут работать как обычные константы, но самих переменных создаваться при этом не будет. Например, создав макрос BUTTON_UP со значением 1, мы можем использовать его в коде как константу. Например, как только мы определили, что была нажата кнопка вверх, мы можем запомнить код нажатой кнопки и передать его другим функциям, которые будут сравнивать этот код и делать какие-то действия. Сам код кнопки мы можем придумать любым: хотим – 1, хотим – 345 (такие цифры часто называют «магическими», т.к. их придумывают сами программисты и не всегда понятно, почему придумали именно так), главное, чтобы этот код оставался неизменным в тексте программы. Лучшим решением будет закодировать этот код в константе (например, BUTTON_UP) и использовать вместо цифры именно название константы.

Вложенные define

Вы вполне можете использовать в блоке инструкции define другие макросы. Например:

#define delay delay(pin)

Во второе определение вместо pin подставится цифра 13 и в итоге мы получим:

#define delay delay(13)

Ошибки define

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

#define setup 12345

,вы заставите ардуино заменить команду setup и безобидная функция превратится в

В таком случае, когда ардуино приступит к компиляции, он уже не найдет обязательную функцию setup и очень рассердится. К тому же, он обнаружит соврешенно странную строку 12345, нарушающe правила наименование переменных и функций.

Вы также можете создать трудноуловимую проблему, просто нечаянно добавив символ точки с запятой в конец строки:

После подстановки эта конструкция превратится в digitalWrite(13;, HIGH). Т.е. внутри блока аргументов появится точка с запятой – а это, без сомнения, нарушение синтаксиса.

Еще одной серьезной ошибкой может стать несоответствие типов, которое вы не сможете сразу же распознать. Например, если вы определите

#define PIN_LED pin12

То при использовании в функции

#Ifdef, #ifndef и #endif – дополнительные команды препроцессора

Иногда бывает полезным знать, объявили ли мы уже в #define какую-либо инструкцию ранее. Это может быть полезным, если у нас много файлов в проекте и мы не всегда понимаем, в какой последовательности в итоге будут подключаться наши модули. Для всего этого мы и используем специальные инструкции #ifdef или #ifndef, которые проверят, было ли встречено данное определение ранее и, если было (или не было – для второго варианта), то оставит блок кода с последующей строки и до места встречи #endif.

#ifdef имя_макроса
последовательность команд, которые будут оставлены в коде, только если данный макрос был определен ранее инструкцией #
define. В противном случае данный участок кода будет исключен
#endif

#ifndef имя_макроса
последовательность команд, которые будут оставлены в коде, только если до данного момента макрос не был определен
#endif

Мы проверяем, определили ли ранее в #define признак отладки, и если да, то код будет выполнен, если нет, то ничего выводиться не будет.

#define DEBUG 1 // Если закоментировать всю эту строку, то отладочные сообщения будут отключены

Пример для ifndef. Мы объявляем константу, только если не делали этого ранее.

Все достаточно понятно и похоже на обычные конструкции

define как альтернатива функциям

Сейчас мы рассмотрим более сложный вариант использования define – мы попробуем передавать внутрь конструкции define какие-то параметры. Но сначала давайте разберемся, зачем это нужно.

#define on digitalWrite(13, HIGH)

В этом примере мы достаточно длинную команду digitalWrite закодировали одним коротким словом on. Теперь везде в коде, где встретится on, компилятор (препроцессор компилятора) вставит строку digitalWrite(13, HIGH). И в конечном итоге функция loop будет выглядеть так:

void loop() <
digitalWrite(13, HIGH);
>

Все будет замечательно до того момента, когда мы захотим добавить второй светодиод и помигать им. Использовать инструкцию on уже не получится, т.к. на ее месте появится вызов для того же светодиода на 13 порту. И у нас есть два выхода:

Первый вариант – это путь в никуда. Потому что нам опять придется делать однообразную работу по копированию одного и того же текста. А вот второй вариант мы сейчас рассмотрим.

В препроцессоре С++ (и, следовательно, ардуино), можно определить параметры, которые препроцессор будет использовать при замене одного фрагмента на другой. Можно указать, что в первом случае on должен вставить digitalWrite для 13 пина, а во втором – для 12 и т.д. Для этого нужно сделать следующее:

#define on(pin) digitalWrite(pin, HIGH)

В инструкции #define мы ввели параметр, который сами назвали a(можно выбрать любое имя) и прописали его явно с помощью скобок: on(a). Во второй части инструкции мы просто используем название параметра в нужном месте: digitalWrite (pin, HIGH).

Теперь, встретив конструкцию on(13), препроцессор вставит слово digitalWrite (pin, HIGH), а вместо pin добавит то, что мы передали в скобках: цифру 13. В итоге в код вставится строка digitalWrite (13, HIGH).

Указав on(12), мы заставим вставить в код функцию digitalWrite (12, HIGH). И так далее, в зависимости от наших нужд.

Таким же образом мы можем сделать макрофункцию (псевдофункцию) off(pin), которая будет выполнять подстановку digitalWrite (pin, LOW).

В итоге код маячка со светодиодами может стать до чрезвычайности простым:

Можно сократить еще сильнее, потому что на вход макрофункции можно передавать несколько параметров:

Достаточно интересно, не правда ли? Также можно придумать очень много разных упрощающих инструкций. Но давайте немного остановимся и поговорим об ограничениях.

Все дело в том, что создавая такую «библиотеку» из собственных инструкций мы очень сильно рискуем:

Ради справедливости скажем и о плюсах:

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

Define или const

Альтернативой #define является использование констант. Константа в arduino и C++ – это переменная, которая определяется c модификатором const. Пример:

сonst int PIN_LED = 10;

То же самое можно сделать с помощью define:

Результат одинаковый – в коде вы просто используете конструкцию типа digitalRead(PIN_LED) и получите желаемое: в нужное место вставится цифра 10.

Но использование константы все-таки предпочтительней по следующим причинам:

Если оценивать объем памяти необходимой для скетча с использованием #define или const, то никаких преимуществ ни тот, ни другой способ не дают – компилятор выделит одинаковый объем памяти для переменной, объявленной явно или встроенной в виде макроса. Исходя из всего этого, старайтесь при использовании констант в коде все-таки брать за основу const.

Источник

#define

Директива #define определяет идентификатор и последовательность символов, которой будет за­мещаться данный идентификатор при его обнаружении в тексте программы. Идентификатор так­же называется именем макроса, а процесс замещения называется подстановкой макроса. Стандар­тный вид директивы следующий:

#define имя_макроса последовательность_символов

Обратим внимание, что в данном операторе отсутствует точка с запятой. Между идентификато­ром и последовательностью символов может быть любое число пробелов. Макрос завершается только переходом на новую строку.

Например, если необходимо использовать TRUE для значения 1, a FALSE для 0 то можно объявить следующие два макроса:

#define TRUE 1
#define FALSE 0

В результате, если компилятор обнаружит в тексте программы TRUE или FALSE, то он заменит их на 1 и 0 соответственно. Например, следующая строка выводит на экран «0 1 2»:

printf («%d %d %d», FALSE, TRUE, TRUE + 1);

В случае, если макрос определен, он может использоваться для определения других макросов. Например, следующий код сопоставляет с именами ONE, TWO и THREE их численные значения:

#define ONE 1
#define TWO ONE + ONE
#def ine THREE ONE + TWO

В результате макроподстановки идентификаторы замещаются указанными строками. Если не­обходимо определить стандартное сообщение об ошибке, то можно написать что-то вроде следу­ющего:

#define E_MS «Standart error on input.\n»
/*. */
printf(E_MS);

Если компилятор обнаруживает идентификатор E_MS, то он замещает его строкой «Standart error on input.» На самом деле компилятор увидит оператор в виде

printf(«Standart error on input.\n»);

Если идентификатор находится в строке, то подстановка не происходит. Например:

#define XYZ this is a test
/*. */
printf(«XYZ»);

выведет не «this is a test», a «XYZ».

Если строка не вмещается в одной строке, то ее можно продолжить на следующей строке, поместив в конце строки обратный слэш, как показано в следующем примере:

#define LONG_STRING «This is a very long» \
string that is used as an example.»

Программисты, пишущие на С, часто используют заглавные буквы для определения идентифика­торов. Данное соглашение помогает любому человеку, читающему программу, бросив на нее один взгляд, узнать, что он имеет дело с макросом. Также вce #define лучше помещать в начале файла или вообще в отдельный заголовочный файл.

Очень часто макросы используют для определения «магических чисел», используемых в про­грамме. Например, программа может определять массив и иметь несколько процедур для работы с ним. Вместо того, чтобы жестко кодировать размер массива, лучше определить макрос, соответ­ствующий размеру массива, и использовать его в тех местах, где необходимо использование раз­мера. Таким образом, если необходимо изменить размер массива, единственное, что требуется сделать, — это изменить оператор #define и перекомпилировать программу. Везде, где использо­вался данный макрос, произойдут автоматические изменения. Рассмотрим пример:

#define MAX_SIZE 100
/*. */
float balance[MAX_SIZE];
/*. */
float temp[MAX_SIZE];

Для изменения размеров обоих массивов просто изменим определение MAX_SIZE.

Директива #define имеет еще одну возможность: макрос может иметь аргументы. Каждый раз при встрече такого макроса аргументы макроса будут замещаться реальными аргументами про­граммы. Такой тип макроса называется макрос типа функция. Например:

Из-за способа подстановки данная программа работает неправильно. В результате компиляции программы EVEN(9 + 1) расширится до

Как известно, оператор взятия по модулю имеет более высокий приоритет, чем оператор сло­жения. Это означает, что сначала выполнится взятие по модулю с числом 1, а затем результат прибавится к 9, что, естественно, не может быть равно 0. Для устранения данной проблемы сле­дует заключить а в макросе EVEN в круглые скобки, как показано в следующей правильной вер­сии программы:

Обратим внимание, что 9+1 вычисляется до взятия по модулю. В целом заключение параметров макроса в скобки — это достаточно хорошая идея, и она позволяет избежать множества проблем.
Использование макроподстановок вместо реальных функций имеет одно большое преимуще­ство — существенно увеличивается скорость работы программы, поскольку нет необходимости тратить время на вызов функции и возврат из нее. Тем не менее, за данное увеличение скорости работы следует платить увеличением размера исполнимого кода программы, поскольку програм­ма вынуждена дублировать код макроса.

Источник

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

что означает команда define. Смотреть фото что означает команда define. Смотреть картинку что означает команда define. Картинка про что означает команда define. Фото что означает команда define

Препроцессор

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

#include – подключить файл

Также можно указать путь к файлу, который нужно подключить. Например у нас в папке со скетчем есть папка libs, а в ней – файл mylib.h. Чтобы подключить такой файл, пишем:

Компилятор будет искать его в папке со скетчем, в подпапке libs.

#define / undef

Или быстрого и удобного отключения отладки в коде:

Или даже задефайнить целый кусок кода, используя переносы и обратный слэш

Если DEBUG задефайнен, то DEBUG_PRINT – это макро-функция, которая выводит значение в порт. А если не задефайнен – все вызовы DEBUG_PRINT просто убираются из кода и экономят память!

Если DEBUG_ENABLE задефайнен – все вызовы DEBUG() в коде будут заменены на вывод в порт. Если не задефайнен – они будут заменены НИЧЕМ, то есть просто “вырежутся” из кода! Также по DEBUG_ENABLE можно запустить сериал и получить полный контроль над отладкой: если она не нужна – убрали DEBUG_ENABLE и из кода убрался запуск порта и все выводы, что резко сокращает объём занимаемой памяти:

Проблемы

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

#if – условная компиляция

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

При помощи условной компиляции можно буквально включать и выключать целые части кода из компиляции, то есть из финальной версии программы, которая будет загружена в микроконтроллер. Рассмотрим несколько конструкция для примера: [fusion_accordion type=”” boxed_mode=”” border_size=”1″ border_color=”” background_color=”” hover_color=”” divider_line=”” title_font_size=”” icon_size=”” icon_color=”” icon_boxed_mode=”” icon_box_color=”” icon_alignment=”” toggle_hover_accent_color=”” hide_on_mobile=”small-visibility,medium-visibility,large-visibility” title=”Пример 1″ open=”no”] [/fusion_toggle][fusion_toggle title=”Пример 2″ open=”no”] [/fusion_toggle][fusion_toggle title=”Пример 3″ open=”no”] [/fusion_toggle][/fusion_accordion]

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

#pragma

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

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

Конструкция с #pragma pack и #pragma pop позволяет более рационально распределять структуры в памяти. Тема сложная, читайте на Хабре.

Макросы

У препроцессора есть несколько интересных макросов, которыми можно пользоваться в своём коде. Рассмотрим некоторые полезные из них, которые работают на Arduino (точнее, на компиляторе avr-gcc).

__func__ и __FUNCTION__

Макросы __func__ и __FUNCTION__ “возвращают” в виде символьного массива (строки) название функции, внутри которой они вызваны. Являются аналогом друг друга. Например:

__DATE__ и __TIME__

__DATE__ возвращает дату компиляции по системному времени в виде символьного массива (строки) в формате __TIME__ возвращает время компиляции по системному времени в виде символьного массива (строки) в формате ЧЧ:ММ:СС

Работать напрямую с этим макросом очень неудобно, это ведь просто набор символов. У меня есть библиотека buildTime, которая позволяет получать отдельно каждый параметр (день, месяц, год, часы, минуты, секунды). Скачать/почитать можно здесь.

__FILE__ и __BASE_FILE__

__FILE__ и __BASE_FILE__ возвращают полный путь к текущему файлу, опять же как строку. Являются аналогами друг друга.

__LINE__

__LINE__ возвращает номер строки в документе, в которой вызван этот макрос что означает команда define. Смотреть фото что означает команда define. Смотреть картинку что означает команда define. Картинка про что означает команда define. Фото что означает команда define

__COUNTER__

__COUNTER__ возвращает значение, начиная с 0. Значение __COUNTER__ увеличивается на единицу с каждым вызовом макроса в коде.

__COUNTER__ можно использовать для генерации уникальных имён переменных, но об этом мы поговорим когда нибудь в другой раз.

Источник

Директива #define

Обратите внимание, что в этом выражении нет точки с запятой. Между идентификатором и последовательностью символов последовательность_символов может быть любое количество пробелов, но признаком конца последовательности символов может быть только разделитель строк.

Предположим, например, что вместо значения 1 нужно использовать слово LEFT (левый), а вместо значения 0 — слово RIGHT (правый). Тогда можно сделать следующие объявления с помощью директивы #define :

В результате компилятор будет подставлять 1 или 0 каждый раз, когда в вашем файле исходного кода встречается идентификатор соответственно LEFT или RIGHT. Например, следующий код выводит на экран 0 1 2:

После определения имя макроса можно использовать в определениях других имен макросов. Вот, например, код, определяющий значения ONE (один), TWO (два) и three (три):

Макроподстановка — это просто замена какого-либо идентификатора связанной с ним последовательностью символов. Поэтому если требуется определить стандартное сообщение об ошибке, то можно написать примерно следующее:

Если идентификатор находится внутри строки, заключенной в кавычки, то замены не будет. Например, при выполнении кода

Если последовательность_символов не помещается в одной строке, то эту последовательность можно продолжить на следующей строке, поместив в конце предыдущей, как показано ниже, обратную косую черту:

Программисты, пишущие программы на языке С, в именах определяемых идентификаторов часто используют буквы верхнего регистра. Если разработчики программ следуют этому правилу, то тот, кто будет читать их программу, с первого взгляда поймет, что будет происходить макрозамена. Кроме того, все директивы #define обычно лучше всего помещать в самом начале файла или в отдельном заголовочном файле, а не разбрасывать по всей программе.

Определение макросов с формальными параметрами

после макрозамены будет преобразовано в

и может привести к неправильному результату.

Использование вместо настоящих функций макросов с формальными параметрами дает одно существенное преимущество: увеличивается скорость выполнения кода, потому что в таких случаях не надо тратить ресурсы на вызов функций. Однако если у макроса с формальными параметрами очень большие размеры, то тогда из-за дублирования кода увеличение скорости достигается за счет увеличения размеров программы.

На заметкуВ С99 можно определить макрос с переменным количеством формальных параметров; об этом рассказывается в части II этой книги.

Источник

Реклама

Инсталлятор CreateInstall
Бесплатные и коммерческие инсталляторы

что означает команда define. Смотреть фото что означает команда define. Смотреть картинку что означает команда define. Картинка про что означает команда define. Фото что означает команда define

Команда define

Команда define предназначена для описания макросов. Макросу можно присвоить значение одного из следующих типов: число, строка, двоичные данные или имя идентификатора. Также макросу можно присвоить значение другого макроса. В дальнейшем, для подстановки его значения, имя макроса должно быть указано как $имямакроса или $имямакроса$. Макрос может быть переопределен в другом define. Макросы описываются внутри фигурных скобок, на каждой строке может быть описан один макрос. Для инициализации макроса, после его имени ставится = и соответствующая константа или выражение. Мы рекомендуем использовать в именах макросов только заглавные буквы.

Атрибуты

Вы можете указать у define атрибуты export и namedef. Используйте атрибут export если вы распространяете модуль в виде байт-кода (.ge файл) и хотите чтобы эти макросы можно было использовать в других программах. Если у define определения указан атрибут namedef, то все его макросы можно использовать без указания символа ‘$‘.

Определение имени define

Вы можете указать имя у define определения. В этом случае обращение к макросам возможно как напрямую, так и с указанием define имени. Это сделано для исключения конфликтов между макросами из разных модулей. В этом случае обращение к макросу имеет следующий вид: $имяdefine.имямакроса.

Перечисление

В Gentee нет отдельной команды для определения перечислений. Вы можете использовать для этого команду define. Если макросы не присвоено никакое значение, то его значение становится на единицу больше значения предыдущего макроса. Если предыдущий макрос отсутствует или не целое число, то значение текущего макроса становится 0. В случае перечисления макросы могут разделяться пробелами.

Выражения

Макросам могут быть присвоены не только числа, но и результаты выражений. Операндами выражений могут быть или константы или другие макросы. Вы можете посмотреть полный список возможных операция на странице Макровыражения.

Источник

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

Ваш адрес email не будет опубликован. Обязательные поля помечены *