Что такое переменные в ардуино
Arduino.ru
Переменные
Переменная – это место хранения данных. Она имеет имя, значение и тип. Например, данное объявление (называется декларацией):
имеется значение вывода (13), которое будет передаваться в функцию pinMode(). В данном случае нет необходимости использовать переменную. Утверждение может работать и в таком виде:
Преимущество переменной заключается в том, что необходимо определить значение вывода однажды и потом использовать его многократно. В последствии при изменении вывода 13 на 12 достаточно будет поменять только одну строку в программном коде. Также можно использовать специальные имена для подчеркивания значения переменной (напр., программа, управляющая светодиодом RGB, может содержать переменные redPin, greenPin и bluePin).
Переменные имеют другие преимущества перед такими значениями как число. Имеется возможность изменить значение переменной, используя присвоение. Например:
изменит значение переменной на число 12. В данном примере не определяется тип переменной, т.к. он не меняется операцией присвоения. Имя переменной постоянно связано с типом, меняется только значение. [1] Перед присвоением значения необходимо декларировать переменную. Присвоение значения переменной без ее декларации вызовет следующее сообщение: error: pin was not declared in this scope».
При присвоении одной переменной другой происходит копирование значения первой переменной во вторую. Изменение значение одной переменной не влияет на другую. Например, после записи:
только pin имеет значение 12, а pin2 еще равен 13.
Из примера видно, что pin используется в обеих функциях setup() и loop(). Обе функции ссылаются на одну переменную, таким образом, изменение ее значения в одной функции повлияет на значение в другой:
Функции digitalWrite(), вызываемой из loop(), будет передано значение 12, т.к. оно было присвоено переменной в функции setup().
Если переменная используется только один раз в функции, то ее декларируют в данной части программного кода, ограниченной скобками функции. Например:
В данном примере переменная может использоваться только внутри функции setup(). При написании данного кода:
будет выведено сообщение: «error: ‘pin’ was not declared in this scope». Данное сообщение будет выводиться, даже если вы задекларировали переменную где-то в программе, но пытаетесь ее использовать вне области видимости.
Почему не сделать все переменные глобальными? Если неизвестно где будет еще использоваться переменная, то почему ее надо ограничивать одной функцией? Когда переменная ограничена легче найти источник ее изменения. Если переменная глобальная, то ее значение может быть изменено в любом месте программного кода, что означает необходимость проследить ее изменение по всей программе. Например, когда переменная имеет некорректное значение, то гораздо легче найти причину если область видимости ограничена.
[1] В некоторых языках программирования, как Python, типы ассоциируются со значениями, а не с именами переменных. Таким образом, имеется возможность присвоит значение любого типа переменной. Это называется динамической типизацией.
Переменные в Arduino
Переменные в Arduino
Переменные — это способ именовать и хранить числовые значения для последующего использования программой.
Их суть заключается в том, что можно однажды определить (задекларировать её), и дальше в программе многократно её использовать, проводя с ней различные манипуляции.
Переменные нужно декларировать (объявлять). Следующий код объявляет переменную inputVariabie, присваивает ей начальное значение, равное нулю, а затем присваивает ей значение, полученное от 2-го аналогового порта:
Переменные могут быть названы любыми именами, которые не являются ключевыми словами языка программирования Arduino.
Объявление переменных
Все переменные должны быть задекларированы до того, как они могут использоваться. Объявление переменной означает определение типа ее значения int, long, float и т. д.
Задание уникального имени переменной, и дополнительно ей можно присвоить начальное значение. Все это следует делать только один раз в программе, но значение может меняться в любое время при использовании арифметических или других разных операций.
Следующий пример показывает, что объявленная переменная inputVariabie имеет тип int, и ее начальное значение равно нулю. Это называется простым присваиванием.
Переменная может быть объявлена в разных местах программы, и то, где это сделано, определяет, какие части программы могут использовать переменную.
Границы переменных
Границы переменных в некоторых языках програмирования могут называться областями видимости. Переменные могут быть объявлены в начале программы перед void setup(), локально внутри функций и иногда в блоке выражений, таком как цикл for. То, где объявлена переменная, определяет ее границы, т. е. возможность некоторых частей программы ее использовать.
Глобальные переменные таковы, что их могут видеть и использовать любые функции и выражения программы. Такие переменные декларируются в начале программы перед функцией setup().
Локальные переменные определяются внутри функций или таких частей, как цикл for. Они видимы и могут использоваться только внутри функции, в которой объявлены. Таким образом, могут существовать несколько переменных с одинаковыми именами в разных частях одной программы, которые содержат разные значения. Уверенность, что только одна функция имеет доступ к ее переменной, упрощает программу и уменьшает потенциальную опасность возникновения ошибок.
Урок 3. Переменные
Наконец добрались до одного из главных вопросов- переменные. Часть из них уже была описана в первом и втором уроках, настало время описать их все. Начнем по возрастанию.
byte
Первый тип переменных, занимает 1 байт памяти и может принимать целые значения от 0 до 255. Им хорошо описывать номера пинов и вообще любые переменные, значения которых находятся в данных пределах. Преимущества- занимает мало места, недостатки- слишком малый диапазон значений.
Записывается как byte n1=100;— переменной n1 типа byte присваивается значение 100.
boolean
Второй тип, занимает 1 байт памяти и может принимать только два значения- TRUE или FALSE. Очень удобен для переключения цифровых входов/ выходов в логические состояния.
Записывается как boolean run = false;— переменной run типа boolean присваивается значение false.
char
Таблица ASCII- кодов.
char myChar = 65; // обе записи эквивалентны — переменной myChar типа char присваивается значение A.
char errorMessage = «choose another option»;- переменной errorMessage присваивается значение choose another option.
unsigned char
Четвертый тип, это копия типа byte и в программировании предпочитают использовать именно тип byte. unsigned char используется крайне редко.
Записывается как unsigned char myChar = 240;— переменной myChar типа unsigned char присваивается значение 240.
integer
Записывается как int n1=100;— переменной типа integer присваивается значение 100.
В нормальном виде тип integer отображается в десятичном виде, но можно отображать и в бинарном, восьмеричном, шестнадцатиричном коде.
Кроме того константы типа integer можно преобразовывать в другие типы, если переменная не входит в диапазон integer:
unsigned integer
Шестой тип, записывается как unsigned int, имеет размер 2 байта и принимает целые значения от 0 до 65535. В контроллере ARDUINO DUE занимает 4 байта и может принимать целые значения от 0 до 4294967295.
Записывается как unsigned int n1=100;— переменной n1 типа unsigned integer присваивается значение 100.
word
Седьмой тип, записывается так же, занимает 2 байта и принимает значения от 0 до 65535. Применяется редко, вместо него обычно используют unsigned integer.
Записывается как word w = 10000;— переменной w типа word присваивается значение 10000.
long
Записывается как long var = 120000; переменной var типа long присваивается значение 120000.
Может использоваться в константах для преобразования типа integer в тип long, для этого в конце числа нужно дописать L и например
int n1=100000L; — преобразует константу n1 типа integer в тип long и присваивает ей значение 100000.
unsigned long
Девятый тип, занимает как и long 4 байта но, в отличии от него, не может принимать отрицательные значения, хотя верхний предел выше в 2 раза. Принимает значения от 0 до 4294967295.
Записывается как unsigned long var = 120000; переменной var типа unsigned long присваивается значение 120000.
short
Записывается как short var = 12000; переменной var типа short присваивается значение 12000.
float
Записывается как float var = 3.14157; переменной var типа float присваивается значение 3.14157.
double
Записывается как long var = 3.14157; переменной var типа long присваивается значение 3.14157.
string
Тринадцатый тип. Вообще то встречается 2 типа string- string array (текстовый массив) и String (текстовая переменная).
char Str3[8] = <‘a’, ‘r’, ‘d’, ‘u’, ‘i’, ‘n’, ‘o’, ‘\0’>; — объявляет массив из 8 разрядов с 0 до 7, недостающий 7 разряд- присваивается null.
String stringOne = «Hello String» ; — переменной stringOne типа String присваивается значение Hello String.
array
Четырнадцатый тип. Массив. Можно указывать размер массива в квадратных скобках. Счет начинается с 0, недостающим значениям присваивается null.
Можно не указывать разрядность массива, компилятор ARDUINO сам посчитает элементы и выставит правильный размер начиная с 0.
int myPins[] = <2, 4, 8, 3, 6>; — аналогично записи int myPins[4] = <2, 4, 8, 3, 6>;
Область применения:
Где же можно применять переменные и где можно обойтись без них.
Прежде всего стоит определиться- сколько раз будет использована переменная. Если она будет использоваться один раз то не стоит ее вводить, гораздо проще просто вставить формулу в то место где должна быть эта переменная. МК просто просчитает формулу и двинется дальше. Если же переменная будет использоваться несколько раз- лучше ввести ее в программу один раз и зарезервировать под нее память нежели МК будет несколько раз высчитывать одни и те же значения- этим вы повысите скорость работы. Еще один совет: при определении переменных желательно указывать в названии ее тип- так вы будете сразу определять какой тип переменной вы используете. Например:
int n1=10000;
Когда вы будете писать программу вы каждый раз будете думать- а какой тип имеет переменная n1? Каждый раз придется искать в коде где она объявлена, это занимает время и поднимает нервы, в конце концов где нибудь закрадется ошибка, опять отлаживать и т.д. Поэтому гораздо проще было бы написать:
int intn1=10000;
Три первых буквы уже указали ее тип- integer, можете сами определить для себя- указывать только первые буквы типов, например in1— что значит integer n1. Так будет гораздо проще и считается хорошим тоном в программировании.
Основные ошибки при работе с переменными:
int x=12;
x= «Hello String» ; — Присваиваем переменной типа integer переменную типа String.
int x= 12568+50000; — максимальное значение integer 32768.
imt x= 12568; — imt вместо int.
Добавить комментарий Отменить ответ
Для отправки комментария вам необходимо авторизоваться.
Константы, переменные и арифметика
Рассмотрим пример кода «железнодорожного светофора», то есть программу, которая заставляет поочерёдно мигать два светодиода:
Как видно, предполагается, что светодиоды подключаются к пинам 5 и 6 на Arduino, а переключение происходит раз в 1000 миллисекунд, т.е. ежесекундно.
Представьте теперь, что в силу обстоятельств, стало необходимо перенести светодиоды с этих пинов на 12-й и 13-й пины. К тому же стало понятно, что устройство смотрится лучше при переключении не раз в секунду, а раз в 2 секунды. Что делать?
Теперь программа работает как нужно, но для того, чтобы проделать эти небольшие изменения, нам пришлось так или иначе изменить абсолютно все строки в наших функциях!
В данном случае мы имеем дело с довольно простой программой и проделать изменения не так сложно. Но что если устройство довольно сложное и скетч расписан на сотни строк?! В этом случае сделать все изменения правильно, не ошибиться и не пропустить ни одного изменения становится крайне сложно: мы люди и нам свойственно ошибаться из-за невнимательности.
Макроопределения
Мы можем единожды указать, что левый светодиод — это пин 13, правый — пин 12, а переключать состояния нужно каждые X миллисекунд. Для этого каждому значению назначается понятное имя, которое затем и используется для обращения:
Всё, теперь для изменения параметров устройства достаточно изменить нужные значения в начале программы и не думать об изменениях в самой логике. Кроме того выражение вроде digitalWrite(RIGHT_LED, LOW) гораздо более информативно нежели digitalWrite(12, LOW) и даёт чёткое понимание того, что имел в виду автор.
Конструкция #define называется макроопределением. Она говорит компилятору о том, что всякий раз, когда он видит указанное имя, стоит использовать на этом месте указанное значение.
Обратите внимание: макроопределения #define не завершаются точкой с запятой в конце строки. Дело в том, что #define — это не обычное выражение, а так называемая препроцессорная директива. Подстановка конкретных значений вместо имён происходит ещё до компиляции, на стадии предварительной обработки исходного файла. На этой стадии, по сути, компилятор проделывает операцию как в текстовом редакторе: «Найти все и заменить». Просто результат этой операции не сохраняется в файл, а тут же им используется для непосредственной компиляции.
Попытка скомпилировать такой скетч приведёт к ошибке.
Встроенные макроопределения
На самом деле код мигания светодиодом:
с точки зрения компилятора есть ни что иное как:
Использование понятных имён вместо магических чисел — это один из признаков профессионализма. Это делает код более понятным и простым для изменений, что всегда уважается.
Об именах макроопределений
По негласному соглашению все макроопределения должны иметь имена, написанные заглавными буквами с символом нижнего прочерка _ на месте пробелов.
Это настолько привычное правило, что вы можете ввести в заблуждение других, если не будете его придерживаться. Опять же, следование общепринятым канонам — признак профи.
Переменные
Макроопределения хороши для именования значений, которые не могут измениться по ходу выполнения программы. Мы вряд ли захотим на лету, без изменения кода и перезагрузки Arduino, перенести светодиод маячка с одного пина Arduino на другой. Но что делать, если какие-то параметры программы всё же должны изменяться с течением времени?
Для этого существуют переменные — именованные значения, которые могут изменяться при исполнении программы процессором. Как и когда они должны изменяться зависит от вас, от того какой алгоритм вы задумали и как написали программу для этого.
Например, давайте рассмотрим программу «помирающего маячка». Первый раз он мигает через 1000 мс, затем через 1100 мс, затем через 1200 мс и так далее до бесконечности:
Обратите внимание: объявление переменной, в отличие от макроопределения — это обычное выражение, поэтому оно должно завершаться точкой с запятой.
Пользоваться переменной можно в двух смыслах: получать её значение и изменять её значение. Получение значения выглядит ровно так же, как и использование макроопределений: мы просто используем её имя в коде, а в результате, при исполнении программы в действительности используется её значение:
В нашей программе это выражение означает «уснуть на столько миллисекунд, сколько сейчас записано в переменной с именем blinkDelay ». При первом исполнении этого выражения значение будет изначальным, таким образом мы уснём на 900 мс.
Далее в нашем скетче мы можем видеть:
Это так называемое арифметическое выражение (англ. expression). Символ += называется оператором и означает в C++ «увеличить на». Таким образом после исполнения этого выражения, значение переменной blinkDelay станет на сотню больше и сохранится в ней до следующего изменения.
В итоге мы получаем что и хотели: «помирающий» маячок.
Об именах переменных
Как и в случае с макроопределениями существует общепринятая конвенция о том как нужно называть переменные. Их принято именовать в так называемом «верблюжьем стиле» (camelCase). То есть, начинать строчными буквами, а каждое новое слово писать слитно, с заглавной буквы.
Также отличительным признаком профессионализма является использование понятных, лаконичных имён из которых чётко понятно зачем нужна конкретная переменная в программе.
Ничто так не выносит мозг, как использование одно- или двухбуквенных имён без смысла или применение транслита.
Составление арифметических выражений
В нашем примере мы использовали оператор += для увеличения целочисленного значения нашей переменной.
Конечно же, это не единственный оператор в C++. Кроме того += — это так называемый синтаксический сахар (syntax sugar) — удобная и короткая запись полного выражения. На самом деле выражение:
эквивалентно такой, полной записи:
Не стоит воспринимать символ = дословно, как «равно». В C++ этот символ называется оператором присваивания или просто присваиванием. Нужно читать это выражение так: присвоить переменной blinkDelay (то, что слева от = ) вычисленное значение blinkDelay + 100 (то, что справа от = ).
Вас не должно смущать, что blinkDelay упоминается и в левой и в правой части выражения. Опять же, это не означает «900 = 1000», это означает «записать в переменную blinkDelay новое значение: результат сложения текущего значения blinkDelay и числа 100.
Зацикленная змейка
В одном арифметическом выражении может быть сколько угодно операторов и участвовать сколько угодно переменных. Для демонстрации давайте напишем программу для устройства-гирлянды. Допустим, вы подключили к Arduino 10 светодиодов, к пинам с 4-го по 13-й и хотите, чтобы они включались поочерёдно, как бегущая змейка. Тогда скетч может выглядеть так:
Далее мы выжидаем 100 мс и выключаем его.
Итоговое значение вычисляется по тем же правилам, что и в обычной математике: сначала то, что в скобках; затем умножение и деление; и наконец, сложение и вычитание. В языке C++ символ % — это оператор «модуло» или оператор остатка от деления:
То есть, после четвёртого пина переменная ledPin примет значение 5-го.
Таким образом после 13-го пина снова последует 4-й, а это то, что нам нужно! Оператор остатка от деления часто используют как раз для зацикливания чего-либо.
Пульсирующий маячок
Давайте теперь напишем программу для пульсирующего маячка: светодиода, который поочерёдно то набирает яркость от нуля до максимума, то так же плавно гаснет до нуля. Скетч для этого может выглядеть так:
В C++ можно не указывать начальное значение. В этом случае переменная при появлении примет значение абстрактного мусора: случайного значения, оставшегося в памяти от прошлой программы. Если по нашему замыслу перед чтением переменной мы точно сначала установим её значение — это не проблема.
Функция analogWrite принимает 2 аргумента: пин, о котором идёт речь и значение скважности. Значение скважности — это целое число от 0 до 255, которое определяет отношение длительности ступеньки в 0 В к длительности ступеньки в 5 В. Например:
Как известно, не все пины Arduino поддерживают ШИМ. Нужно выбрать тот, который отмечен символом
Ещё раз взглянем на наш loop :
В C++ символ / означает оператор деления. Поскольку мы оперируем целыми числами оператор деления всегда отсекает дробную часть:
Далее идёт, вероятно, немного пугающая по началу конструкция:
То есть тернарный оператор образует выражение, в котором участвуют 3 подвыражения: условие, «что если не ноль» и «что если ноль». Если рассматривать наш пример, то:
В итоге, brightness с начала программы, с каждым новым loop будет то расти от 0 до 255, то уменьшаться обратно от 255 до 0. А это то, что нам нужно!
Нам остаётся лишь чуть задержаться, чтобы дать светодиоду посветиться на текущем уровне яркости. В примере сделана задержка на 5 мс. Вы можете легко посчитать: при таком значении полное нарастание или затухание занимает 5 мс × 256 = 1280 мс.
О компактной записи и области видимости
При программировании потребность в таких временных переменных «на выброс» возникает очень часто. Для плюс-минус сложных устройств в скетче вам могут понадобиться десятки или сотни таких переменных. Представьте себе, что все они будут определены в одном месте большим рулоном.
Разобраться в том, какая переменная для чего предназначена, будет крайне сложно. Для решения этой и других проблем в C++ переменные могут объявляться непосредственно в том месте, где используются.
Если применить это к нашему примеру, получится такой код:
Как видите, переменная sign теперь определяется непосредственно в том месте, где начинает использоваться.
Переменные объявленные вне функций, такие как step и brightness называются глобальными переменными. Они инициализируются в самом начале исполнения программы и доступны в скетче отовсюду.
Встраивание выражений
Если снова посмотреть на наш пример можно увидеть, что мы рассчитываем sign с помощью довольно простого выражения, а затем используем всего в одном месте, при расчёте brightness на следующей строке.
В таких случаях можно встроить арифметическое выражение непосредственно в то место, где используется его результат и обойтись без отдельной промежуточной переменной вовсе:
По итогам компиляции мы получим абсолютно идентичный результат. Мы просто написали то же самое, но другими словами. Промежуточная переменная всё равно будет создана, но это уже закадровая работа компилятора и нам не стоит об этом задумываться.
Не стоит злоупотреблять подобным склеиванием чрезмерно: код может потерять стройность и понятность. Но для простых случаев, как наш, это вполне уместно.
Кроме такого встраивания, выражения вообще можно составлять при вызове функции, на месте соответствующего аргумента. Если воспользоваться этим для нашего кода, получится вот такая сверхкомпактная запись:
Да, это весь наш loop и он делает всё то же самое, что и раньше. Конструкция с analogWrite находится уже на грани читаемости, но для демонстрации вполне подходит. Разберёмся что же здесь написано.
То есть мы мало того, что встроили тернарное выражение прямо в вызов функции, мы ещё и операцию по увеличению step на единицу каждый loop разместили там же.
Обратите внимание, что если оператор ++ расположен после имени переменной, то в расчёте значения арифметического значения выражения используется старое значение этой переменной: то, что было до увеличения на единицу.
Если ++ стоял бы перед переменной, то она сначала увеличилась бы на единицу и только затем участвовала в вычислениях:
Вернёмся к нашему вызову:
Как уже говорилось, выражение brightness++ означает: «использовать текущее значение, но сразу после использования увеличить его на 1».
Функции с возвращаемыми значениями
Это интересно, но не даёт таких возможностей к написанию программ, как получение значений из-вне. Допустим, к Arduino подключён какой-то сенсор: датчик освещённости, датчик газа, простой потенциометр или что-то ещё. Как получить его показания и использовать их в программе для чего-то полезного?
Первое, что мы видим — это макроопределение пина с потенциометром:
Таким образом в качестве значения макроопределения мы использовали другое макроопределение. Так делать можно и это довольно распространённая практика.
Совершенно верно. Некоторые функции помимо того, что делают что-то полезное умеют так же возвращать значение обратно, в вызывающий код. Функции вроде pinMode или analogWrite не возвращают ничего, по задумке их автора, а вот analogRead возвращает некоторое целочисленное значение.
Чтобы понять какие аргументы функция принимает, возвращает ли она что-нибудь и если возвращает, то что, следует обращаться к документации на эту функцию.
Таким образом в первой строке loop мы просто считываем сигнал с потенциометра, получая угол поворота его ручки в виде целого числа в пределах от 0 до 1023.
Вспоминая о компактной записи, мы можем сделать наш loop чуть лаконичнее:
Итак, вы научились работать со сложными выражениями, макроопределениями и переменными. Использовать функции с возвращаемыми значениями и встраивать вычисления. Этих знаний уже достаточно для создания нехитрых устройств. Пробуйте, экспериментируйте, учитесь!