18+
реклама
18+
Бургер менюБургер меню

Чарльз Платт – Электроника для начинающих (2-е издание) (страница 89)

18

Функция генератора случайных чисел

После того как цикл for погасит светодиоды, мы переходим к функции random(), выбирающей число, которое заключено в пределах, указанных в круглых скобках. Нам необходимо значение от 1 до 6, но почему же этот диапазон указан от 1 до 7? Потому что на самом деле эта функция выбирает величины с дробной частью, от 1,00000001 до 6,99999999, а затем отбрасывает ту часть числа, которая следует за десятичной запятой. Поэтому 7 – это предел, который никогда не будет достигнут, и значение на выходе окажется от 1 до 6.

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

Оператор сравнения if

Теперь пришло время узнать, какое сейчас значение у переменной spots, и включить соответствующие светодиоды.

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

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

Следующий оператор if использует символ прямой черты, о котором я упоминал ранее. Пара символов || на языке программирования С означает ИЛИ. Поэтому данная функция говорит: «Если у нас есть значение 1, ИЛИ 3, ИЛИ 5, мы включаем центральный светодиод, переводя вывод 2 в высокое состояние».

Третий оператор if говорит о том, что если значение spots больше трех, следует включить два светодиода, расположенных по диагонали. Это необходимо для отображения конфигурации точек для числа 4, 5 или 6.

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

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

Скорость мигания

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

Создание новой функции

Теперь мы переходим к важной части. В написанном мною алгоритме мы дошли до Шагов 3, 4 и 4а. Напомню:

• Шаг 3. Проверить, нажата ли кнопка.

• Шаг 4. Проверить, достигло ли системное время значения переменной ignore.

• Шаг 4а. Если кнопка не была нажата ИЛИ если системное время не достигло значения переменной ignore, вернуться к Шагу 1. Иначе…

Эти шаги можно скомбинировать в одной функции сравнения if. Алгоритм выглядел бы так:

• Если (кнопка не нажата ИЛИ системное время меньше значения ignore), вернуться к Шагу 0.

Но здесь есть проблема. Фраза «вернуться к» предполагает отсылку микроконтроллера к указанной части программы. Казалось бы, естественная команда, но когда вы программируете на языке С, следует избегать передачи управления из одной части программы в другую.

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

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

Такие блоки обычно называются функциями, что немного сбивает с толку, потому что мы уже имели дело с функциями setup() и loop(). Фактически, вы можете написать собственную функцию, которая будет работать в целом по такому же принципу.

Я решил, что будет правильнее написать эту программу, выделив функцию проверки статуса в отдельную… хм… функцию. Я назвал ее checkbutton(), но мог бы назвать как угодно, если только ее название уже не зарезервировано для другой цели.

Вы видите функцию checkbutton() в нижней части листинга 5.3 с предшествующим ей словом void, потому что эта функция не возвращает никакого значения в остальную часть программы.

Слова void checkbutton() – это заголовок функции, после которого, как обычно, в фигурных скобках содержится сама процедура. Эта функция выполняет следующее:

• Ждет 50 мс, пока прекратится дребезг контактов.

• Ожидает, пока будет отпущена кнопка.

• Ждет еще 50 мс, пока прекратится дребезг контактов отпущенной кнопки.

• Ждет, пока кнопка будет нажата снова (другими словами, ожидает завершения отпущенного состояния).

• Сбрасывает переменную ignore.

Когда микроконтроллер доходит до конца этой функции, куда он идет дальше? Все просто: он возвращается к строке, расположенной сразу за той, из которой была вызвана функция. Где она? Сразу под функцией сравнения, выше. Так и происходит вызов функции: вы просто указываете ее имя (включая круглые скобки, внутри которых иногда содержатся параметры, хотя в данном случае их нет).

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

Структура программы

Строка, которая начинается с if (millis() > ignore, предназначена для того же, что и Шаг 4 в моем алгоритме, но теперь все работает по-другому. Вместо того чтобы решить, отправлять ли микроконтроллер обратно к началу программы, принимается решение, вызывать ли функцию checkbutton(). Ранее я резюмировал ее логику так: «Если (кнопка не нажата ИЛИ системное время меньше значения ignore), вернуться к Шагу 0». Пересмотренный вариант гласит: «Если превышен период игнорирования кнопки И кнопка нажата, перейти к функции к checkbutton()».

После того как микроконтроллер выполнит это и вернется, он достигнет конца основной функции loop, которая всегда повторяется автоматически.

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

Поэтому, естественная структура для этой программы – основной цикл, который выбирает и отображает цифры, и если кнопка нажата, микроконтроллер отправляется к функции checkbutton(), а затем возвращается к основному циклу.

Документация среды Arduino ничего не говорит о структуре программы, потому что подразумевает наличие базовых знаний программирования. Поэтому среда Arduino просто требует указать обязательную функцию setup, за которой следует функция loop, и все.

Но как только программа увеличится в размере, вам обязательно понадобится разделить ее на подходящие функции, чтобы она не превращалась в запутанный «клубок» операторов. Стандартное руководство по языку С объяснит все более детально.

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

Есть еще одно преимущество разделения программы на функции. Можно сохранить функции отдельно и использовать их в других программах в дальнейшем. Функция checkbutton() пригодится в любой игре, где вы хотите останавливать ход путем нажатия кнопки и возобновлять игру повторным нажатием.

Подобным же образом вы можете в своих программах использовать функции других людей, при условии, что авторы не запрещают вам это, контролируя свое авторское право. Большое количество функций на языке С доступно бесплатно в онлайн-источниках, многие из них написаны специально для среды Arduino. Например, есть функции для управления почти всеми алфавитно-цифровыми дисплеями. Это приводит к очень важному, но часто игнорируемому совету для программистов: не изобретайте велосипед. Вам не нужно тратить время, чтобы создавать свою функцию, если кто-либо разрешает вам взять уже готовую. Это еще одна причина, по которой понятие функции так важно в языке С.