Содержание

Константы, переменные и арифметика [Амперка / Вики]

Рассмотрим пример кода «железнодорожного светофора», то есть программу, которая заставляет поочерёдно мигать два светодиода:

void setup()
{
    pinMode(5, OUTPUT);
    pinMode(6, OUTPUT);
}
 
void loop()
{
    digitalWrite(5, HIGH);
    digitalWrite(6, LOW);
    delay(1000);
 
    digitalWrite(5, LOW);
    digitalWrite(6, HIGH);
    delay(1000);
}

Как видно, предполагается, что светодиоды подключаются к пинам 5 и 6 на Arduino, а переключение происходит раз в 1000 миллисекунд, т.е. ежесекундно.

Представьте теперь, что в силу обстоятельств, стало необходимо перенести светодиоды с этих пинов на 12-й и 13-й пины. К тому же стало понятно, что устройство смотрится лучше при переключении не раз в секунду, а раз в 2 секунды. Что делать?

Можно модифицировать программу и всюду, где речь шла о 5-м пине, использовать 12-й; 6-й пин заменить на 13-й, а в функциях delay изменить задержку с 1000 на 2000.

void setup()
{
    pinMode(12, OUTPUT);
    pinMode(13, OUTPUT);
}
 
void loop()
{
    digitalWrite(12, HIGH);
    digitalWrite(13, LOW);
    delay(2000);
 
    digitalWrite(12, LOW);
    digitalWrite(13, HIGH);
    delay(2000);
}

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

В данном случае мы имеем дело с довольно простой программой и проделать изменения не так сложно. Но что если устройство довольно сложное и скетч расписан на сотни строк?! В этом случае сделать все изменения правильно, не ошибиться и не пропустить ни одного изменения становится крайне сложно: мы люди и нам свойственно ошибаться из-за невнимательности.

Кроме того, если устройство управляет кучей индикаторов, моторов, считывает данные с нескольких сенсоров, запоминать что к какому пину подключено становится сложно. При чтении сложной программы, когда видим вызов digitalWrite(4, HIGH), понять имели ли мы в виду включение светодиода или остановку двигателя, или что-то ещё уже не так просто. Что можно сделать, чтобы решить проблему?

Макроопределения

Мы можем единожды указать, что левый светодиод — это пин 13, правый — пин 12, а переключать состояния нужно каждые X миллисекунд. Для этого каждому значению назначается понятное имя, которое затем и используется для обращения:

#define LEFT_LED      13
#define RIGHT_LED     12
#define SWITCH_TIME   2000
 
void setup()
{
    pinMode(LEFT_LED, OUTPUT);
    pinMode(RIGHT_LED, OUTPUT);
}
 
void loop()
{
    digitalWrite(LEFT_LED, HIGH);
    digitalWrite(RIGHT_LED, LOW);
    delay(SWITCH_TIME);
 
    digitalWrite(LEFT_LED, LOW);
    digitalWrite(RIGHT_LED, HIGH);
    delay(SWITCH_TIME);
}

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

digitalWrite(RIGHT_LED, LOW) гораздо более информативно нежели digitalWrite(12, LOW) и даёт чёткое понимание того, что имел в виду автор.

Конструкция #define называется макроопределением. Она говорит компилятору о том, что всякий раз, когда он видит указанное имя, стоит использовать на этом месте указанное значение.

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

Если бы вы поставили ; после #define, в результате обработки компилятор увидел бы такой код:

// Если завершать директивы точками с запятой…
#define LEFT_LED      13;
#define RIGHT_LED     12;
#define SWITCH_TIME   2000;
 
// …они появятся в самом неподходящем месте
void setup()
{
    pinMode(12;, OUTPUT);
    pinMode(13;, OUTPUT);
}
 
void loop()
{
    digitalWrite(12;, HIGH);
    digitalWrite(13;, LOW);
    delay(2000;);
 
    digitalWrite(12;, LOW);
    digitalWrite(13;, HIGH);
    delay(2000;);
}

Попытка скомпилировать такой скетч приведёт к ошибке.

Встроенные макроопределения

Вы могли догадаться, что уже знакомые нам значения HIGH, LOW, OUTPUT — это также не что иное как макроопределения. Просто #define

для них написан в некотором другом месте и мы можем просто сразу ими пользоваться.

На самом деле код мигания светодиодом:

void setup()
{
    pinMode(13, OUTPUT);
}
 
void loop()
{
    digitalWrite(13, HIGH);
    delay(100);
    digitalWrite(13, LOW);
    delay(900);
}

с точки зрения компилятора есть ни что иное как:

void setup()
{
    pinMode(13, 1);
}
 
void loop()
{
    digitalWrite(13, 1);
    delay(100);
    digitalWrite(13, 0);
    delay(900);
}

Если вы откроете файл hardware/arduino/cores/arduino/Arduino.h в вашем дистрибутиве Arduino IDE, вы сможете увидеть, что HIGH — это макроопределение 1, LOW0, OUTPUT — 1 и т.д. Эти значения используются настолько часто, что они встроены таким образом.

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

Об именах макроопределений

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

// плохо
#define ledpin  13
#define led_pin 13
#define LedPin  13
 
// хорошо
#define LED_PIN 13

Это настолько привычное правило, что вы можете ввести в заблуждение других, если не будете его придерживаться. Опять же, следование общепринятым канонам — признак профи.

Стоит отметить, что язык C++, как и многие другие считает строчные и заглавные буквы различными, поэтому если к макроопределению LED_PIN вы затем попытаетесь обратиться, написав led_pin будет выдана ошибка компилятора о том, что он не понимает что такое led_pin. То же самое касается имён функций и всего остального.

Переменные

Макроопределения хороши для именования значений, которые не могут измениться по ходу выполнения программы. Мы вряд ли захотим на лету, без изменения кода и перезагрузки Arduino, перенести светодиод маячка с одного пина Arduino на другой. Но что делать, если какие-то параметры программы всё же должны изменяться с течением времени?

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

Например, давайте рассмотрим программу «помирающего маячка». Первый раз он мигает через 1000 мс, затем через 1100 мс, затем через 1200 мс и так далее до бесконечности:

#define LED_PIN 13
int blinkDelay = 900;
 
void setup()
{
    pinMode(LED_PIN, OUTPUT);
}
 
void loop()
{
    digitalWrite(LED_PIN, HIGH);
    delay(100);
 
    digitalWrite(LED_PIN, LOW);
    delay(blinkDelay);
 
    blinkDelay += 100;
}

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

int blinkDelay = 900;

Это называется определением переменной. Мы таким образом заявили, что хотим иметь ячейку памяти, к которой будем обращаться по имени blinkDelay и изначально, при старте Arduino, в ней должно лежать значение 900.

Перед именем переменной в определении указывается тип данных для этой переменной. В нашем случае — это int, что означает «целое число» (int — сокращение от английского «integer»: целочисленный).

Обратите внимание: объявление переменной, в отличие от макроопределения — это обычное выражение, поэтому оно должно завершаться точкой с запятой.

Итак, мы сообщили компилятору, что у нас есть переменная целочисленного типа, с именем blinkDelay и начальным значением 900. Теперь мы можем ей пользоваться.

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

delay(blinkDelay);

В нашей программе это выражение означает «уснуть на столько миллисекунд, сколько сейчас записано в переменной с именем blinkDelay». При первом исполнении этого выражения значение будет изначальным, таким образом мы уснём на 900 мс.

Далее в нашем скетче мы можем видеть:

blinkDelay += 100;

Это так называемое арифметическое выражение (англ. expression). Символ += называется оператором и означает в C++ «увеличить на». Таким образом после исполнения этого выражения, значение переменной blinkDelay станет на сотню больше и сохранится в ней до следующего изменения.

Как результат:

  • При первом исполнении функции loop мы выжидаем с выключенным светодиодом 900 мс

  • К началу второго исполнения blinkDelay уже равна 1000, поэтому мы будем выжидать 1000 мс

  • При третьем исполнении мы будем выжидать 1100 мс

  • …и так далее до бесконечности

В итоге мы получаем что и хотели: «помирающий» маячок.

Об именах переменных

Как и в случае с макроопределениями существует общепринятая конвенция о том как нужно называть переменные. Их принято именовать в так называемом «верблюжьем стиле» (camelCase). То есть, начинать строчными буквами, а каждое новое слово писать слитно, с заглавной буквы.

// плохо
int BLINK_DELAY;
int BlinkDelay;
int blink_delay;
 
// хорошо
int blinkDelay;

Также отличительным признаком профессионализма является использование понятных, лаконичных имён из которых чётко понятно зачем нужна конкретная переменная в программе.

Ничто так не выносит мозг, как использование одно- или двухбуквенных имён без смысла или применение транслита.

// плохо
int d;
int asd;
int zaderzhkaMigalki;
 
// хорошо
int blinkDelay;
int offDelay;
int blinkInterval;
int blinkTimeout;

Составление арифметических выражений

В нашем примере мы использовали оператор += для увеличения целочисленного значения нашей переменной.

Конечно же, это не единственный оператор в C++. Кроме того += — это так называемый синтаксический сахар (syntax sugar) — удобная и короткая запись полного выражения. На самом деле выражение:

blinkDelay += 100;

эквивалентно такой, полной записи:

blinkDelay = blinkDelay + 100;

Не стоит воспринимать символ = дословно, как «равно». В C++ этот символ называется оператором присваивания или просто присваиванием. Нужно читать это выражение так: присвоить переменной blinkDelay (то, что слева от =) вычисленное значение blinkDelay + 100 (то, что справа от =).

Вас не должно смущать, что blinkDelay упоминается и в левой и в правой части выражения. Опять же, это не означает «900 = 1000», это означает «записать в переменную blinkDelay новое значение: результат сложения текущего значения

blinkDelay и числа 100.

Зацикленная змейка

В одном арифметическом выражении может быть сколько угодно операторов и участвовать сколько угодно переменных. Для демонстрации давайте напишем программу для устройства-гирлянды. Допустим, вы подключили к Arduino 10 светодиодов, к пинам с 4-го по 13-й и хотите, чтобы они включались поочерёдно, как бегущая змейка. Тогда скетч может выглядеть так:

#define FIRST_LED   4
#define TOTAL_LEDS  10
 
int ledPin = FIRST_LED;
 
void setup()
{
    pinMode(4,  OUTPUT);
    pinMode(5,  OUTPUT);
    pinMode(6,  OUTPUT);
    pinMode(7,  OUTPUT);
    pinMode(8,  OUTPUT);
    pinMode(9,  OUTPUT);
    pinMode(10, OUTPUT);
    pinMode(11, OUTPUT);
    pinMode(12, OUTPUT);
    pinMode(13, OUTPUT);
}
 
void loop()
{
    digitalWrite(ledPin, HIGH);
    delay(100);
 
    digitalWrite(ledPin, LOW);
    ledPin = (ledPin - FIRST_LED + 1) % TOTAL_LEDS + FIRST_LED;
}

Да, код в setup выглядит несколько громоздко, но не будем пока заострять на нём внимание. Как сделать «правильно» будет показано при изучении понятия циклов. Сосредоточимся на функции loop.

Первым делом мы поджигаем светодиод на пине номер которого записан в переменной ledPin. При старте программы, как видно из определения, ей присваивается значение FIRST_LED, которое в свою очередь мы определили как 4. То есть, при первом проходе loop мы зажигаем светодиод на 4-м пине.

Далее мы выжидаем 100 мс и выключаем его.

Затем нам нужно переписать значение ledPin, чтобы к следующему проходу основного цикла она приняла значение следующего по порядку пина. При этом, если текущий пин был последним, т.е. 13-м, следующий за ним должен быть 4-й, чтобы всё началось заново. Всё это делается с помощью арифметического выражения:

ledPin = (ledPin - FIRST_LED + 1) % TOTAL_LEDS + FIRST_LED;

Давайте поймём что здесь происходит. Выражение даёт предписание процессору присвоить переменной ledPin вычисленное значение выражения, стоящего справа от =. В этом выражении используются операторы -, +, %, скобки, текущее значение переменной ledPin, макроопределения FIRST_LED и TOTAL_LEDS.

Итоговое значение вычисляется по тем же правилам, что и в обычной математике: сначала то, что в скобках; затем умножение и деление; и наконец, сложение и вычитание. В языке C++ символ % — это оператор «модуло» или оператор остатка от деления:

  • 4 % 10 = 4

  • 9 % 10 = 9

  • 10 % 10 = 0

  • 11 % 10 = 1

  • 19 % 10 = 9

  • 1259 % 10 = 9

  • …и т.д.

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

  • (4 — 4 + 1) % 10 + 4 =

  • (1) % 10 + 4 =

  • 1 + 4 =

  • 5

То есть, после четвёртого пина переменная ledPin примет значение 5-го.

Аналогично, после 5-го последует 6-й, после 6-го — 7-й и так далее до 13-го. Теперь давайте посмотрим что присвоится переменной ledPin, если на текущем проходе она равна 13, то есть мы только что мигнули последним светодиодом нашей змейки:

  • (13 — 4 + 1) % 10 + 4 =

  • (10) % 10 + 4 =

  • 0 + 4 =

  • 4

Таким образом после 13-го пина снова последует 4-й, а это то, что нам нужно! Оператор остатка от деления часто используют как раз для зацикливания чего-либо.

Пульсирующий маячок

Давайте теперь напишем программу для пульсирующего маячка: светодиода, который поочерёдно то набирает яркость от нуля до максимума, то так же плавно гаснет до нуля. Скетч для этого может выглядеть так:

#define LED_PIN  5
 
int step = 0;
int brightness = 0;
int sign;
 
void setup()
{
    pinMode(LED_PIN, OUTPUT);
}
 
void loop()
{
    analogWrite(LED_PIN, brightness);
    step++;
 
    sign = (step / 256) % 2;
    brightness = sign ? brightness + 1 : brightness - 1;
 
    delay(5);
}

Давайте разберёмся что здесь происходит. Мы определили 3 переменные: step, sign, brightness. При этом начальное значение присвоили только переменным step и brightness.

В C++ можно не указывать начальное значение. В этом случае переменная при появлении примет значение абстрактного мусора: случайного значения, оставшегося в памяти от прошлой программы. Если по нашему замыслу перед чтением переменной мы точно сначала установим её значение — это не проблема.

В первой строке loop мы использовали функцию analogWrite, а не digitalWrite, как делали раньше. analogWrite не просто выдаёт логический ноль или единицу на заданный пин, а выдаёт на него ШИМ-сигнал (PWM signal). То есть, пин начинает очень быстро (32 000 раз в секунду) переключаться между 0 и 5 вольтами и если подключить к такому пину светодиод, создастся иллюзия, что он горит в пол силы.

Функция analogWrite принимает 2 аргумента: пин, о котором идёт речь и значение скважности. Значение скважности — это целое число от 0 до 255, которое определяет отношение длительности ступеньки в 0 В к длительности ступеньки в 5 В. Например:

  • 0 — это всегда 0 В (светодиод не горит)

  • 64 — это четверть времени 5 В и три четверти — 0 В (свечение в четверть силы)

  • 128 — это половина времени 5 В и половина 0 В (свечение в пол силы)

  • 255 — это всегда 5 В (свечение в полную силу)

Как известно, не все пины Arduino поддерживают ШИМ. Нужно выбрать тот, который отмечен символом ~ на плате. Мы выбрали 5-й, что и определили именем LED_PIN.

Ещё раз взглянем на наш loop:

void loop()
{
    analogWrite(LED_PIN, brightness);
    step++;
 
    sign = (step / 256) % 2;
    brightness = sign ? brightness + 1 : brightness - 1;
 
    delay(5);
}

В переменной step мы хотим хранить порядковый номер прохождения функции loop: 1, 2, 3, …, 2345, 2346 и т.д. При её определении мы присвоили начальное значение 0 и хотим, чтобы оно увеличивалось каждый вызов loop. Это мы и делаем в первом выражении. Символ ++ означает в C++ оператор инкремента, оператор, который увеличивает значение на единицу. Теперь вы догадываетесь откуда взялось название языка «C++», надстроенного над «C». Оператор инкремента можно ставить как перед переменной, так и после. Следующие строки эквивалентны:

step++;
++step;
step += 1;
step = step + 1;

В третьей строке основного цикла мы вычисляем и присваиваем новое значение переменной sign. Как видите, оно рассчитывается на основе значения другой переменной step.

В C++ символ / означает оператор деления. Поскольку мы оперируем целыми числами оператор деления всегда отсекает дробную часть:

  • 40 / 10 = 4

  • 41 / 10 = 4

  • 49 / 10 = 4

  • 0 / 10 = 0

  • 1 / 10 = 0

  • 9 / 10 = 0

Таким образом значение step / 256 будет становиться больше на единицу всякий раз, когда происходит 256 вызовов loop. Из этого значения мы дополнительно берём остаток от деления на 2. В итоге, значение sign будет становится поочерёдно то единицей, то нулём, переключаясь каждые 256 вызовов loop.

Далее идёт, вероятно, немного пугающая по началу конструкция:

brightness = sign ? brightness + 1 : brightness - 1;

Это на самом деле обычное арифметическое выражение, в котором мы вычисляем и присваиваем новое значение переменной brightness. Символы ? и : в C++ называются тернарным условным оператором. Его суть такова:

  • Выражение, стоящее перед символом ? называется условием. В нашем случае всё условное выражение — это просто переменная sign

  • Если значение условия не равно нулю, результатом оператора является выражение, стоящее между символами ? и :

  • Если значение условия равно нулю, результатом оператора является выражение, стоящее после символа :

То есть тернарный оператор образует выражение, в котором участвуют 3 подвыражения: условие, «что если не ноль» и «что если ноль». Если рассматривать наш пример, то:

  • Если только что посчитанный sign — не ноль, brightness примет значение выражения brightness + 1

  • А если sign — ноль, brightness примет значение выражения brightness - 1

Таким образом, если вспомнить о том, как ведёт себя sign в нашем алгоритме, brightness будет рассчитываться то по одной формуле, то по другой. Сама формула переключается как и sign, каждые 256 циклов.

В итоге, brightness с начала программы, с каждым новым loop будет то расти от 0 до 255, то уменьшаться обратно от 255 до 0. А это то, что нам нужно!

Нам остаётся лишь чуть задержаться, чтобы дать светодиоду посветиться на текущем уровне яркости. В примере сделана задержка на 5 мс. Вы можете легко посчитать: при таком значении полное нарастание или затухание занимает 5 мс × 256 = 1280 мс.

О компактной записи и области видимости

В примере с маячком мы объявляли переменную sign рядом с остальными. Однако на самом деле мы использовали её исключительно как временную, вспомогательную переменную для вычисления brightness. Мы рассчитывали её каждый раз непосредственно перед вычислением brightness и она не зависела от собственного значения на предыдущем проходе loop.

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

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

Если применить это к нашему примеру, получится такой код:

#define LED_PIN  5
 
int step = 0;
int brightness = 0;
 
void setup()
{
    pinMode(LED_PIN, OUTPUT);
}
 
void loop()
{
    analogWrite(LED_PIN, brightness);
    delay(5);
 
    step++;
 
    int sign = (step / 256) % 2;
    brightness = sign ? brightness + 1 : brightness - 1;
}

Как видите, переменная sign теперь определяется непосредственно в том месте, где начинает использоваться.

Мы не можем сделать то же самое с step и brightness, т.к. в этом случае, при каждом новом вызове loop они будут создаваться заново, а потому не сохранят своё значение с предыдущего прохода loop. Что касается sign — это не важно: мы всё равно каждый loop рассчитываем её заново.

Любая объявленная переменная «живёт» только в рамках того блока кода {…}, где она определена. Это называется областью видимости (scope). Попытка использовать переменную sign из другой функции, например из setup приведёт к ошибке компиляции: мы объявили её в рамках loop, поэтому использовать её можем только там.

Переменные объявленные вне функций, такие как step и brightness называются глобальными переменными. Они инициализируются в самом начале исполнения программы и доступны в скетче отовсюду.

Встраивание выражений

Если снова посмотреть на наш пример можно увидеть, что мы рассчитываем sign с помощью довольно простого выражения, а затем используем всего в одном месте, при расчёте brightness на следующей строке.

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

void loop()
{
    analogWrite(LED_PIN, brightness);
    delay(5);
 
    step++;
 
    brightness = (step / 256 % 2) ? brihtness + 1 : brightness - 1;
}

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

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

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

void loop()
{
    analogWrite(LED_PIN, (step++ / 256 % 2) ? brightness++ : brightness--);
    delay(5);
}

Да, это весь наш loop и он делает всё то же самое, что и раньше. Конструкция с analogWrite находится уже на грани читаемости, но для демонстрации вполне подходит. Разберёмся что же здесь написано.

Компилятор видит, что мы вызываем функцию analogWrite. Он знает, что для неё нужно 2 аргумента и ищет их в круглых скобках. С первым аргументом, LED_PIN всё понятно. Это просто 5. Но на месте второго аргумента компилятор видит выражение. Поэтому перед непосредственным вызовом, значение выражения вычисляется и уже результат используется в качестве аргумента.

В нашем случае на месте второго аргумента стоит тернарное выражение, поэтому первым делом вычисляется условие, то что стоит перед символом ?. Там мы видим: step++ / 256 % 2. Всё так же, как и раньше, но к step добавлен оператор инкремента ++. Всё вместе это означает:

  1. Взять значение step и запомнить его

  2. Увеличить значение step на единицу

  3. Целочисленно разделить запомненное в пункте (1) значение на 256

  4. Взять остаток от деления результата на 2

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

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

int x = 5;
int y = x++;
 
// x = 6
// y = 5

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

int x = 5;
int y = ++x;
 
// x = 6
// y = 6

Вернёмся к нашему вызову:

analogWrite(LED_PIN, (step++ / 256 % 2) ? brightness++ : brightness--);

С расчётом условия теперь всё понятно. Далее, как уже говорилось об операторе ? :, в случае если условие не оказалось равным нулю, берётся значение выражения между символами ? и :. В нашем случае — это brightness++. То есть, если условие с step оказалось не равным нулю, функция analogWrite в качестве аргумента получит значение выражения brightness++.

Как уже говорилось, выражение brightness++ означает: «использовать текущее значение, но сразу после использования увеличить его на 1».

Аналогичная ситуация со сценарием, когда условие оказывается равным нулю. Просто вместо оператора инкремента ++, мы используем оператор декремента для уменьшения значения на единицу, а не увеличения.

Функции с возвращаемыми значениями

До сих пор мы использовали только переменные, которые так или иначе зависели только от номера вызова функции loop, т.е. так или иначе зависящие от времени, прошедшего с момента старта Arduino.

Это интересно, но не даёт таких возможностей к написанию программ, как получение значений из-вне. Допустим, к Arduino подключён какой-то сенсор: датчик освещённости, датчик газа, простой потенциометр или что-то ещё. Как получить его показания и использовать их в программе для чего-то полезного?

Если говорить о сенсорах с аналоговым сигналом, для получения показаний с них существует встроенная функция analogRead. Давайте воспользуемся ей, чтобы сделать программу для устройства, которое изменяет яркость свечения светодиода, подключённого к 5-му пину в зависимости от поворота ручки потенциометра, подключённого к пину A0.

#define LED_PIN  5
#define POT_PIN  A0
 
void setup()
{
    pinMode(LED_PIN, OUTPUT);
}
 
void loop()
{
    int value = analogRead(POT_PIN);
    analogWrite(LED_PIN, value / 4);
}

Первое, что мы видим — это макроопределение пина с потенциометром:

#define POT_PIN  A0

В качестве значения используется не просто число, а нечто с именем A0. Оно написано большими буквами, поэтому можно предположить, что это встроенное макроопределение. И это действительно так! A0 для Arduino Uno, например, определено как 14. Для других плат значение может быть другим. Мы не хотим помнить об этих тонкостях, поэтому просто использовали A0. Это всегда означает «нулевой аналоговый вход».

Таким образом в качестве значения макроопределения мы использовали другое макроопределение. Так делать можно и это довольно распространённая практика.

Теперь рассмотрим loop. В первой строке определяется переменная value, а в качестве значения ей присваивается значения выражения analogRead(POT_PIN). Но ведь это вызов функции, а не арифметическое выражение!

Совершенно верно. Некоторые функции помимо того, что делают что-то полезное умеют так же возвращать значение обратно, в вызывающий код. Функции вроде pinMode или analogWrite не возвращают ничего, по задумке их автора, а вот analogRead возвращает некоторое целочисленное значение.

Чтобы понять какие аргументы функция принимает, возвращает ли она что-нибудь и если возвращает, то что, следует обращаться к документации на эту функцию.

Что касается analogRead, она принимает один аргумент: номер пина, с которого необходимо считать значение аналогового сигнала. А возвращает эта функция значение от 0 до 1023, где:

  • Входное напряжение в 0 В возвращается как 0

  • 2 В возвращается как 409

  • 2,5 В возвращается как 512

  • И так далее, до напряжения в 5 В, которому ставится в соответствие 1023

Таким образом в первой строке loop мы просто считываем сигнал с потенциометра, получая угол поворота его ручки в виде целого числа в пределах от 0 до 1023.

Как мы помним, функция analogWrite, которой мы пользуемся для управления яркостью светодиода ожидает целое число от 0 до 255 в качестве второго аргумента. Но у нас оказалась переменная с другим диапазоном. Что делать? Просто поделить значение на 4. Так наш диапазон будет смасштабирован до того, который подходит для analogWrite. Максимуму из одного диапазона равному 1023 станет соответствовать максимум из другого диапазона: 1023 / 4 = 255.

Вспоминая о компактной записи, мы можем сделать наш loop чуть лаконичнее:

void loop()
{
    analogWrite(LED_PIN, analogRead(POT_PIN) / 4);
}

Итак, вы научились работать со сложными выражениями, макроопределениями и переменными. Использовать функции с возвращаемыми значениями и встраивать вычисления. Этих знаний уже достаточно для создания нехитрых устройств. Пробуйте, экспериментируйте, учитесь!

wiki.amperka.ru

Arduino Область видимости переменной

В языке программирования C, использующемся при программировании Ардуино, переменные имеют свойство, называемое область видимости, чего нельзя сказать про первые языки программирования (подобные BASIC), в которых все переменные являются глобальными.

Глобальная переменная — эта та переменная, которая может быть доступна («видна«) из любой функции программы. Локальные переменные доступны только внутри тех функций, в которых они объявлены. При программировании Ардуино, любая переменная, объявленная за пределами функции (таких как, setup(), loop(), и т.д.), является глобальной переменной.

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

Также иногда удобно объявить и инициализировать переменную внутри цикла for. В этом случае переменная будет доступна только в пределах скобок цикла for.

Пример:

int gPWMval; // эта переменная будет доступна из любой функции void setup() { // … } void loop() { int i; // переменная «i» «видна» только внутри «loop» float f; // переменная «f» «видна» только внутри «loop» // … for (int j = 0; j <100; j++){ // переменная j доступна только внутри скобок цикла for } }

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

int gPWMval;  // эта переменная будет доступна из любой функции

 

void setup()

{

  // …

}

 

void loop()

{

  int i;    // переменная «i» «видна» только внутри «loop»

  float f;  // переменная «f» «видна» только внутри «loop»

  // …

 

  for (int j = 0; j <100; j++){

  // переменная j доступна только внутри скобок цикла for

  }

 

}

all-arduino.ru

Arduino int

Описание

Целочисленный тип int — это основной тип данных для хранения чисел.

В Arduino Uno (и других платах на базе микроконтроллеров ATmega) переменные типа int хранят 16-битные (2-байтовые) значения. Такая размерность дает диапазон от -32768 до 32767 (минимальное значение -2^15 и максимальное значение (2^15 )-1).

В Arduino Due переменные типа int — 32-битные (4-байта), что дает возможность хранить значения в диапазоне от -2 147 483 648 до 2 147 483 647 (минимальное значение -2^31 и максимальное значение (2^31)-1).

В переменных типа int отрицательные числа представляются с помощью техники дополнительного кода. Старший бит, который иногда называют «знаковым битом», указывает на то, является ли данное число отрицательным. Остальные биты инвертируются, после чего к результату добавляется 1.

Ардуино берет на себя обработку отрицательных чисел, поэтому арифметические операции с ними выглядят так, как вы этого ожидаете. Неожиданные сложности могут возникнуть только при работе с оператором сдвига вправо >>. 

Пример:

int ledPin = 13;

       int ledPin = 13;

Синтаксис

  • var — имя вашей переменной типа int
  • val — значение, присваиваемое этой переменной

Подсказка

В ситуациях, когда значение переменной стремится превысить свой максимум, оно сбрасывается в минимальное значение, причем данный принцип работает в оба направления. Например, для 16-битной переменной int:

int x; x = -32768; x = x — 1; // в x теперь хранится 32767 — произошел сброс в отрицательном направлении x = 32767; x = x + 1; // в результате сброса в x теперь хранится -32768

   int x;

   x = -32768;

   x = x — 1;       // в x теперь хранится 32767 — произошел сброс в отрицательном направлении

 

   x = 32767;

   x = x + 1;       // в результате сброса в x теперь хранится -32768

Смотрите также

all-arduino.ru

Arduino:Основы/Переменные — Онлайн справочник

Перевод: Максим Кузьмин (Cubewriter) Перевел 3377 статей для сайта.

Контакты:

Проверка/Оформление/Редактирование: Мякишев Е.А.

Переменная – это «место», в котором хранится кусочек данных. У нее есть название, значение и тип. К примеру, строчка кода (называемая «объявлением»)…

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

…значение переменной pin (т.е. «13») передается функции pinMode(). Впрочем, в данном случае переменную использовать необязательно, можно просто вписать в качестве аргумента необходимое значение, как здесь…

В данном случае преимущество переменой в том, что вам нужно указать номер контакта лишь единожды, в самом начале скетча, а затем использовать вместо него переменную – столько раз, сколько потребуется. То есть если позднее вы решите поменять «12» на «13», вам не нужно будет искать в скетче каждую строчку, где вы написали «13». Вы просто перемещаетесь в начало скетча и меняете значение всего один раз. Кроме того, название переменной можно сделать максимально наглядным – как в скетче, где для управления RGB-светодиодом используются переменные redPin, greenPin и bluePin.

У переменных есть и другие преимущества. Самое важное – возможность менять значение переменной при помощи операции присваивания (она выполняется при помощи знака «=»).

Например…

…изменит значение переменной на «12». Обратите внимание, что мы не указали тип переменной, потому что операцией присваивания он не меняется. То есть название переменной навсегда привязывается к определенному типу, а меняется лишь значение. Также имейте в виду, что перед присвоением значения переменную нужно сначала объявить. Если вы добавите в свой скетч строчку кода выше, но не добавите в него объявление переменной, то получите сообщение вроде «error: pin was not declared in this scope», что значит «ошибка: переменная pin не была объявлена в данной области видимости переменных».

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

  1. int pin = 13;

  2. int pin2 = pin;

  3. pin = 12;

… значение «12» будет только у переменной pin, а у pin2 по-прежнему останется «13».

Теперь вы думаете, возможно, что означает фраза «область видимости переменных» (в оригинале – «scope») из сообщении об ошибке выше… Это фраза обозначает часть скетча, в которой можно использовать нашу переменную. Это определяется тем, где мы ее объявили. К примеру, если вы хотите использовать переменную в любом месте, ее нужно объявить в самой верхней части скетча. Такая переменная называется «глобальной». Вот пример…

  1. int pin = 13;

  2.  

  3. void setup()

  4. {

  5.   pinMode(pin, OUTPUT);

  6. }

  7.  

  8. void loop()

  9. {

  10.   digitalWrite(pin, HIGH);

  11. }

Как видите, переменная pin используется и в блоке setup(), и в блоке loop(). Оба блока отсылают к одной и той же переменной, поэтому если поменять ее в одной функции, это изменит ее и в другой. Как здесь…

  1. int pin = 13;

  2. void setup()

  3. {

  4.   pin = 12;

  5.   pinMode(pin, OUTPUT);

  6. }

  7.  

  8. void loop()

  9. {

  10.   digitalWrite(pin, HIGH);

  11. }

Здесь функции digitalWrite() в блоке loop() передается значение «12», поскольку это значение было присвоено переменной pin выше, в блоке setup().

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

Например….

  1. void setup()

  2. {

  3.   int pin = 13;

  4.   pinMode(pin, OUTPUT);

  5.   digitalWrite(pin, HIGH);

  6. }

В данном случае переменную pin можно использовать только в блоке setup(). Если вы попытаетесь сделать так…

  1. void loop()

  2. {

  3.   digitalWrite(pin, LOW); // ошибка: переменная pin находится вне области видимости

  4. }

…то получите то же сообщение, что и раньше – «error: pin was not declared in this scope». То есть, пускай вы и объявили переменную pin, но пытаетесь использовать ее вне области видимости.

Сейчас вы, вероятно, задаетесь вопросом: почему бы просто не сделать все переменные глобальными? В конце концов, если я не знаю, где мне может пригодиться переменная, почему я должен ограничивать место, где она может пригодиться? Ответ прост – так проще понять суть проблемы, если что-то пойдет не так. Если это глобальная переменная, ее значение может измениться в любом месте скетча, и это значит, что вам нужно хорошо знать весь скетч для того, чтобы понять, в чем проблема. К примеру, если переменная хранит какое-то странное значение, будет намного проще понять, почему оно получилось таким странным, если это значение будет храниться в переменной, живущей в таком вот «гетто», т.е. в ограниченной области видимости.

Примечание: В некоторых языках (вроде Python) типы присваиваются значениям, а не названиям переменных, и вы, к тому же, можете присвоить переменной значения любого типа. Это называется «динамической типизацией».

  1. ↑ www.arduino.cc — Variables

wikihandbk.com

Разбираемся с Arduino IDE | AlexGyver Technologies

Работа с Arduino IDE


Рассмотрим основную программу, с которой будем работать – Arduino IDE. IDE расшифровывается как интегрированная среда разработки, и в нашем случае представляет собой блокнот, в котором мы пишем код, препроцессор и компилятор, которые проверяют и компилируют код, и инструменты для загрузки, которые загружают код выбранным способом. IDE написана на java, поэтому не удивляйтесь её долгому запуску, большому объему занимаемой оперативки и медленной работе. Несмотря на огромный опыт работы с ардуино я до сих пор продолжаю писать код в Arduino IDE, потому что я к ней привык. Помимо перечисленных проблем стандартная IDE выделяется на фоне “взрослых” сред разработки отсутствием дерева/файловой структуры проекта (что не нужно для простых проектов), отсутствием рефакторинга, отсутствием автоматического дополнения кода (хотя его обещают вот-вот добавить и уже есть beta) и очень медленной компиляцией кода. Этих недостатков лишены аналоги Arduino IDE, о них поговорим в конце урока. Помимо отсутствия озвученных недостатков там есть некоторые полезные фишки, например все define-ы файла с кодом отображаются в отдельном блоке и с ними очень удобно работать.

Интерфейс


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

  • Проверить – компиляция (сборка, проверка на ошибки…) кода без его загрузки в плату. То есть код можно написать и проверить на ошибки даже не подключая плату к компьютеру
  • Загрузить – компиляция и загрузка прошивки в плату
  • Создать/открыть/сохранить – тут всё понятно
  • Монитор порта – кнопка открывает монитор последовательного пора для общения с платой
  • Меню вкладок – работа с вкладками, о них поговорим ниже
  • Текущее состояние – тут выводится краткая информация о последнем действии: загрузка завершена, ошибка загрузки, автоформатирование завершено и т.д.
  • Лог работы – тут выводится лог компиляции и вообще все системные сообщения, отчёты об ошибках и размере скомпилированного кода
  • Конфигурация оборудования – выводится название выбранной платы, версии микроконтроллера и номер выбранного COM порта
Arduino IDE

Пробежимся по пунктам меню, которые я считаю первостепенно важными, с остальными можно познакомиться самостоятельно по ходу работы с программой. Версия моей IDE – 1.8.8, в новых что-то может отличаться

Вкладка “Файл”


  • Новый
  • Открыть
  • Открыть недавние
  • Папка со скетчами – список скетчей, которые сохранены туда, куда программа предлагает их сохранять по умолчанию (Документы/Arduino)
  • Примеры – список установленных библиотек с подсписком примеров к каждой. Весьма полезная штука
  • Закрыть
  • Сохранить
  • Сохранить как…
  • Настройки страницы (для печати)
  • Печать
  • Настройки
  • Выход

Далее сразу рассмотрим окно настроек:

Окно настроек


Настройки Arduino IDE

Куча интересных настроек на свой вкус. Из них отмечу

  • Размещение папки скетчей – куда по умолчанию сохраняются скетчи
  • Показать подробный вывод – показывает подробный лог при компиляции и загрузке, нужно при поиске багов и непонятных глюков/ошибок
  • Использовать внешний редактор – запрещает редактирование кода из Arduino IDE, чтобы редактировать его в других редакторах, например Notepad++. Редактируем там, а загружаем через IDE. Зачем это? Писать код в том же Notepad++ гораздо удобнее и приятнее, чем в Arduino IDE. К сожалению.
  • Дополнительные ссылки для менеджера плат – сюда вставляются ссылки на пакеты для работы с другими платами, например такими основанными на ESP8266 или ATtiny85.

Вкладка “Правка”


Во вкладке Правка ничего такого особенного нет, всё как в других программах

Вкладка “Скетч”


Вкладка “Скетч”
  • Проверить/компилировать – то же, что кнопка галочка
  • Загрузка – то же, что кнопка загрузка
  • Загрузить через программатор – загрузить скетч напрямую в МК, минуя загрузчик
  • Экспорт бинарного файла – сохраняет скомпилированный файл, который и загружается в МК. Бинарный файл хорош тем, что содержащийся в нём код практически невозможно не то что отредактировать, но даже прочитать как вменяемый код. Именно в таком виде обычно поставляются прошивки для цифровой техники, чтобы никто не видел исходник =)
  • Показать папку скетча
  • Подключить библиотеку – подключает в код библиотеку, с директивой include, всё как нужно
    • Управлять библиотеками… – открывает менеджер библиотек, из которого можно установить библиотеки из официального списка
    • Добавить .zip библиотеку – не рекомендую так делать, потом не найдёте, куда она установилась
  • Добавить файл… – подключает в код внешний файл

Вкладка “Инструменты”


Вкладка “Инструменты”
  • АвтоФорматирование – выравнивает код по табуляции. Крайне важная фишка, используйте её и комбинацию Ctrl+T как можно чаще
  • Архивировать скетч – сохраняет скетч как .zip архив
  • Исправить кодировку и перезагрузить – полезная штука, когда скачал чей-то код из интернета, а там поехала кодировка
  • Управлять библиотеками… – открывает менеджер библиотек, из которого можно установить библиотеки из официального списка 
  • Монитор порта
  • Плоттер по последовательному соединению – встроенный построитель графиков по идущим в порт данным
  • Плата – выбор платы, куда хотим загрузить прошивку
  • Процессор – выбор процессора, который стоит на плате. Обычно он один на выбор, но иногда есть варианты
  • Порт – COM порт, к которому подключена плата. Это всегда порт, отличный от COM1 (системный порт)
  • Программатор – выбор программатора для загрузки кода через программатор
  • Записать загрузчик – прошивает загрузчик, соответствующий выбранной плате и процессору в микроконтроллер при помощи программатора (который ISP)

Меню вкладок


Меню вкладок

Система вкладок в Arduino IDE работает крайне необычным образом и очень отличается от понятия вкладок в других программах:

  • Вкладки относятся к одному и тому же проекту, к файлам, находящимся с ним в одной папке
  • Вкладки просто разбивают общий код на части, то есть в одной вкладке фигурная скобка { может открыться, а в следующей – закрыться }. При компиляции все вкладки просто объединяются в один текст по порядку слева направо (с левой вкладки до правой). Также это означает, что вкладки должны содержать код, относящийся только к этому проекту, и сделать в одной вкладке void loop() и в другой – нельзя, так как loop() может быть только один
  • Вкладки автоматически располагаются в алфавитном порядке, поэтому создаваемая вкладка может оказаться между другими уже существующими. Это означает, что разбивать блоки кода по разным вкладкам (как во втором пункте, { на одной вкладке, } на другой вкладке) – крайне не рекомендуется.
  • Также не забываем, что переменная должна быть объявлена до своего вызова, то есть вкладка с объявлением переменной должна быть левее вкладки, где переменная вызывается. Создавая новую вкладку нужно сразу думать, где она появится с таким именем и не будет ли из за этого проблем. Также название вкладок можно начинать с цифр и таким образом точно контролировать их порядок. Во избежание проблем с переменными, все глобальные переменные лучше объявлять в самой первой вкладке.
  • Вкладки сохраняются в папке с проектом и имеют расширение .ino, при запуске любой вкладки откроется весь проект со всеми вкладками.
  • Помимо “родных” .ino файлов Arduino IDE автоматически подцепляет файлы с расширениями .h (заголовочный файл), .cpp (файл реализации) и .pde (старый формат файлов Arduino IDE). Эти файлы точно так же появляются в виде вкладок, но например заголовочный файл .h не участвует в компиляци до тех пор, пока не будет вручную подключен к проекту при помощи команды include. То есть он висит как вкладка, его можно редактировать, но без подключения он так и останется просто отдельным текстом. В таких файлах обычно содержатся классы или просто отдельные массивы данных.

Аналоги Arduino IDE


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

  • Notepad++ + Arduino IDE – вполне работоспособная связка – прогерский блокнот “на максималках”, в котором ОЧЕНЬ удобно писать код, и Arduino IDE, в которой удобно выбирать железо и загружать прошивку
  • PlatformIO – очень мощная взрослая среда разработки, подробнее можно почитать даже в статье у меня на сайте. Автор – не я. Лично мне платформио не понравилась. Да, есть автодополнение кода и всякие удобные фишки, но конфигурировать проект приходится вручную (в 2019 году не сделать пару кнопок для того же выбора порта – разработчики – АУ!!!), также есть проблемы с библиотеками.
  • Programino IDE – вот эта среда мне довольно таки понравилась, есть автодополнение и другие удобные фичи. Единственный минус – она платная, но на всё платное можно нагуглить кряк =) Подробнее на официальном сайте
  • MariaMole – интересная среда, которая вроде как больше не развивается. Количество “плюшек” примерно равно количеству “багов” и недоделок, по крайней мере так было в 2017 году. Подробнее
  • B4R – среда, позволяющая программировать Арудино на языке Basic. Кому интересно – вот официальный сайт
  • Visual Studio Micro – очень мощный и взрослый инструмент, являющийся плагином к Microsoft Visual Studio. Подробнее читать здесь
  • XOD – очень интересная среда разработки, в которой программа составляется из нод (node) – блоков. Полностью другая концепция программирования, посмотрите обязательно. Официальный сайт
  • Atmel Studio – официальная среда для программирования микроконтроллеров AVR. Никаких детских ардуиновских функций – только работа напрямую с МК, только хардкор! Подробнее можно почитать на сайте амперки

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


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

alexgyver.ru

Программирование Arduino: справочник разработчика программиста

Arduino+

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

Мы предлагаем вам самый полный и дополняемый справочник программиста Arduino. Справочник постоянно дополняется и обновляется.

Ардуино – платформа, позволяющая множеству инженеров со всего мира создавать свои проекты с минимальными вложениями. В первую очередь – это специальный микроконтроллер с одноимённой системой управления и библиотеками, построенными на языке С++. Соответственно, если вы планируете создавать что-то уникальное, вам следует изучить все нюансы, которые имеет программирование Arduino.

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

Основы Arduino

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

Для упрощения работы пользователей в Ардуино созданы готовые библиотеки функций, вам достаточно лишь вводить команды из них, чтобы добиться какой-то цели. Естественно, таким образом вы многого не добьётесь, но для создания собственных библиотек потребуется знание языка С++ на котором и построена прошивка чипа.

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

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

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

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

Чаще всего применяется ООП. Сам С++ имеет ядро из многочисленных библиотек и дополнительных функций или методов, поэтому, если вы собираетесь разобраться во всём кардинально, стоит начинать с освоения языка с нуля.

Особенности Arduino программирования

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

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

  1. Низкий порог вхождения. Этот пункт будет и в недостатках, так как из-за простоты системы и отсутствия требований к базису по программированию в сети гуляет множество библиотек, написанных ужасным образом. На то, чтобы разобраться, как они работают, уйдёт больше времени, чем на создание своей собственной. А стандартных функций от разработчиков не хватает для серьёзных задач.
  2. Обширное комьюнити. Это главное достоинство Ардуино перед его конкурентами, ведь вы найдёте пользователей, занимающихся созданием проектов на нём, как русскоязычных, так и англоязычных. Но если вы хотите получать действительно ценные советы и погрузиться в работу комьюнити, следует всё же изучить английский язык. Так как большая часть проблем, что вам встретятся, уже давно решены в Гугле, но, зачастую, ответы на английском.
  3. Большое количество библиотек, под разные случаи. Но, как уже описано чуть выше, у этого есть и свои недостатки.

Имеется у программирования на Ардуино и ряд весомых минусов:

  1. Низкая планка для вхождения превращает большую часть библиотек, коими наполнена сеть, в полностью бесполезный мусор. Ведь какие-то из них работают просто медленно и написаны без каких-либо знаний основ алгоритмизации, а часть – вовсе не работает, и непонятно, зачем авторы их создавали. Чтобы найти подспорье под конкретный проект, необходимо перелопатить несколько англоязычных форумов или же самостоятельно создать функции с нуля.
  2. Сложности программирования на С++. На деле – это один из сложнейших языков мультипарадигмального программирования, для создания прошивок и низкоуровневых задач. Однако, если вы имели опыт работы с ним и знаете хотя бы основные алгоритмы, а также работали хоть с одним другим мультипарадигмальным ЯП, тем более используя объектно-ориентированное программирование, вам будет значительно проще освоиться.
  3. Низкая скорость отклика самих чипов и их слабые характеристики. Да, микроконтроллеры Ардуино можно подстраивать под конкретную задачу, докупать компоненты и датчики, но это играет с ними злую шутку. Так как разработчики не знают, для чего будут использовать их детище, они усредняют все показатели, чтобы значительно уменьшить стоимость конечного продукта. В результате люди, создающие простейшие поделки, переплачивают за ненужную мощность, а тем, кто занимается робототехникой или автоматизацией каких-то процессов, приходится докупать и паять множество дополнительных плат.

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

Как начать правильно пользоваться Arduino

Если вы никогда ранее не программировали, и это ваш первый опыт, то программирование микроконтроллеров Arduino пойдёт куда проще, если вы начнёте с основ. Конечно, когда в планах у вас нет никаких сложных проектов, можете работать на готовых библиотеках и параллельно разбирать, из чего состоят их функции. Это один из хороших способов обучения, но тогда стоит искать наборы функций, которые писались профессионалами, чтобы быть уверенным в их правильности. Иначе вы можете увидеть неправильное решение задачи и, в результате, применять те в своих проектах.

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

Программирование микроконтроллеров

Само программирование Ардуино делится на три этапа:

  1. Создание или скачивание готовой библиотеки функций.
  2. Загрузка этих библиотек в постоянную память чипа. Это ещё называют прошивкой.
  3. Ввод этих функций в командную строку, например, АТ, чтобы плата выполнила те или иные действия.

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

Самые простые проекты с использованием Arduino

Примеров простых проектов с Ардуино множество, например, вы можете:

  1. Создать датчик освещённости, который будет подстраивать специальные LED лампы под ту яркость, которая необходима в комнате.
  2. Автоматизация любых вещей в вашем доме. Например, включения-выключения света, открытия дверей и прочее.
  3. Автоматизация оранжереи.

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

arduinoplus.ru

Отправить ответ

avatar
  Подписаться  
Уведомление о