Мы постоянно пользуемся с вами какими-то функциями,
которые для нас подготовили создатели среды разработки Arduino
или какие-то еще добрые люди, которые сделали библиотеки.
Мы знаем, что делает каждая функция, вызываем ее,
передаем ей какие-то параметры и получаем от нее какие-то значения.
Согласитесь, это достаточно удобно.
И ничто не мешает нам самим создавать функции, чтобы делать
код для нашего устройства более структурированным, читаемым и удобным.
Функция может быть полезна для выполнения каких-то действий,
которые повторяются часто, она позволяет делать более логичную структуру программы,
то есть выделять в отдельный фрагмент какую-то логически
завершенную часть всей работы программы.
И в идеальном случае вы основной цикл, loop, можете сделать минимальным,
то есть расписать отдельные действия в различных функциях,
а в loop просто взаимоувязать их.
Мы по возможности будем стараться так и делать.
А сейчас узнаем, как функции создаются.
Давайте посмотрим на функцию, которую я создал для выполнения всех действий,
связанных с измерением расстояния, в том числе она же выполняет
усреднение, используя скользящую среднюю.
Описание этой функции называется ее определением.
Оно обычно помещается в конец вашего кода, то есть после setup,
после loop начинаются те функции, которые вы сами определяете.
Что должно присутствовать в определении функции?
Ну, во-первых, я дал ей имя — measure.
Оно не должно совпадать с каким-то уже имеющимися функциями,
предложенными средой, или библиотеками.
Затем я указываю тип данных, которые функция вернет.
У нас внутри нее будут проходить какие-то вычисления,
и значение, которое она будет возвращать, имеет какой-то тип.
В нашем случае это целое число.
Вы сталкивались с тем, как переменная возвращает значение,
когда, например, использовали тот же analogRead, да: он возвращает то,
что получилось на аналоговом входе.
Бывают функции, которые ничего не возвращают.
Они выполняют какие-то действия и никакого значения не возвращают.
Например, digitalWrite — просто включает или выключает напряжение на пине,
и мы от него ничего не ждем.
В этом случае мы будем использовать ключевое слово void,
вы его видели
в setup и в loop.
Затем в скобках, в круглых
скобках мы перечисляем параметры, которые функция будет принимать.
То есть, по сути, создаем переменные, указывая их тип, давая им имена,
и затем при вызове функции вы в скобках будете передавать соответствующие
значения, вот столько параметров, сколько эта функция ожидает.
Как, например, digitalWrite принимает 2 параметра:
номер пина и состояние, в которое нужно этот пин привести,
так и в нашем случае функция measure будет принимать
номер пина, на котором подключен дальномер.
Что же дальше будет происходить?
Я объявляю массив уже вот здесь,
к ключевому слову static вернемся чуть позже, и затем я сюда переписал
все те же действия, которые мы сейчас рассмотрели в связи с
вычислением скользящей средней — здесь все то же самое.
Единственное, что параметр pin у нас встречается вот здесь,
когда мы указываем analogRead что же нужно, с какого пина нужно считать.
Посмотрим еще на ключевое слово return.
Оно, соответственно, сообщает, что же функция вернет.
В нашем случае вот это вычисленное среднее значение функция
отдаст в то место программы, откуда она была вызвана.
У вас может быть несколько return в функцию.
Например, если внутри нее происходит ветвление,
в одном случае вы можете вернуть 0, например, как ошибку,
в другом случае вы можете вернуть какое-то значение, в третьем случае
еще что-то; либо этого ключевого слова может вообще не быть,
если ваша функция никакое значение не возвращает.
Ну и нужно понимать, что после возвращения значения функция прекращает свою работу,
то есть обычно это происходит в конце функции,
в конце всех действий, которые выполняет функция.
Теперь нужно обратить ваше внимание на область видимости переменных.
Что это такое?
По умолчанию все переменные, с которыми имеет дело та или иная функция,
если они объявлены внутри нее, видны только этой функции.
В частности, вот параметр int_pin виден только функции measure.
Например, из loop мы к ней обратиться не можем,
loop ничего про эту переменную не знает.
Точно также, если мы в loop объявим какую-то переменную,
measure про нее ничего не узнает, если мы не передадим ее в качестве параметра.
Например, вот это вот mean
теперь тоже доступно только функции measure,
и массив с набором измерений опять же локальный: он виден только здесь.
Зачем же здесь я использовал ключевое слово static?
Оно нужно для того, чтобы данные, хранящиеся в переменной,
помеченной этим словом, в нашем случае это массив measurements,
чтобы данные сохранялись между вызовами функции.
Если мы объявим переменную без этого слова, например,
как мы поступили с переменной mean, когда функция закончит работу,
переменная перестанет существовать, данные из нее потеряются.
При следующем вызове функции переменная создастся вновь и уже будет работать
с новыми данными.
Когда же мы используем ключевое слово static,
данные от прошлого вызова останутся,
то есть у нас массив measurements проживет до следующего вызова,
и там снова в следующий раз там сдвинутся все эти значения на 1 элемент.
Обращайте на это внимание.
Что делать, если вы хотите с какими-то данными работать
из разных мест программы, из разных функций?
Никаких сложностей здесь нет, вы просто можете создать глобальную переменную,
то есть объявить ее не в рамках какой-то функции, а вот где-то здесь же, в начале.
Ну мы, на самом деле, неоднократно так уже делали.
Если мы объявим переменную вот в этом месте, которое я выделил,
к ней можно будет обратиться и из setup, и из loop, и из нашей measure.
Но, на самом деле, это не лучший способ валить все данные в одну кучу.
Лучше, когда функции четко взаимодействуют между собой: одна
функция получает от другой данные в виде
параметров и возвращает данные в виде возвращаемого значения.
Если устроить свалку из глобальных переменных,
велик риск залезть туда не вовремя, в итоге получить какие-то ошибки, бардак.
Мы посмотрели на то, как функция определяется,
а как она вызывается, в общем-то, вы уже, наверное, представляете.
Вот здесь вот в loop я вызываю вновь созданную функцию measure и передаю ей
в качестве параметра вот этот вот IR_DIST_SENS, наше макроопределение,
да, и функция будет работать с этим как с номером пина.
А затем она вернет то, что вычислила, и это мы сохраним в distance.
Вот.
Теперь loop стал гораздо более компактным,
и мы можем вызывать measure из любого места, откуда захотим.
Что еще можно добавить про использование функции measure, которую мы создали,
и вообще вот этих всех измерений и усреднений?
Если по какой-то причине мы не хотим пользоваться скользящей средней и
просто хотим напрямую считывать показания дальномера,
ничего в функции менять не нужно.
Можно просто вот это макроопределение MA, которое обозначает количество периодов,
по которым мы усредняем, сделать равным 1.
Таким образом, когда функция будет выполняться,
массив будет создан длиной в 1 элемент, то есть,
по сути, это будет как обычная переменная, цикл работать не будет,
потому что ему нужно будет бежать от 0 до 0,
и мы просто будем считывать сигнал с
дальномера и записывать его в нулевой элемент массива,
и затем делить его на 1 и
возвращать непосредственно то, что мы измерили.