Оглавление:

Меню в Arduino и как использовать кнопки: 10 шагов (с изображениями)
Меню в Arduino и как использовать кнопки: 10 шагов (с изображениями)

Видео: Меню в Arduino и как использовать кнопки: 10 шагов (с изображениями)

Видео: Меню в Arduino и как использовать кнопки: 10 шагов (с изображениями)
Видео: Уроки Arduino #5 - работа с цифровыми портами и подключение кнопки 2024, Ноябрь
Anonim
Меню в Arduino и как использовать кнопки
Меню в Arduino и как использовать кнопки

В моем руководстве по Arduino 101 вы узнаете, как настроить среду в Tinkercad. Я использую Tinkercad, потому что это довольно мощная онлайн-платформа, которая позволяет мне демонстрировать студентам различные навыки построения схем. Не стесняйтесь создавать все мои руководства, используя Arduino IDE и настоящий Arduino!

В этом уроке мы узнаем о кнопках! Нам нужно знать:

  • Как их подключить
  • Чтение их ценности
  • Debounce, и почему это важно
  • Практическое применение (создание меню)

Большинство людей думают, что наиболее практичным способом использования кнопки является включение и выключение света. Мы не будем здесь! Мы собираемся использовать наш, чтобы создать меню и установить некоторые параметры на Arduino.

Готовый? Давайте начнем!

Шаг 1: Настройте доску

Настроить доску
Настроить доску
Настроить доску
Настроить доску

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

Макетная плата Mini имеет две шины питания, верхнюю и нижнюю. Мы подключаем их к Arduino, чтобы обеспечить питание большего количества компонентов. Позже в этом уроке мы будем использовать 3 кнопки, поэтому нам понадобится больше энергии. Следует отметить, что на небольшой макетной плате шины питания проходят через плату горизонтально. Это отличается от столбцов в основной области прототипирования посередине; они работают вертикально. Вы можете использовать любой из выводов питания для подачи питания на любую колонну в основной области посередине.

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

Шаг 2: Добавьте кнопку и резистор

Добавьте кнопку и резистор
Добавьте кнопку и резистор
Добавьте кнопку и резистор
Добавьте кнопку и резистор
Добавьте кнопку и резистор
Добавьте кнопку и резистор

Добавьте небольшую кнопку из лотка для компонентов. Он должен выглядеть так, как на картинке. Убедитесь, что это не переключатель! Также добавьте резистор. Щелкните его и установите значение 10 кОм. Этого достаточно, чтобы вывести вывод, когда он не подключен, что очень важно позже в коде.

Поместите компонент посередине макета. Как работает кнопка:

  • Угол к углу, кнопка не связана. Нажатие на кнопку замыкает контакты и соединяет углы.
  • Бока кнопки соединены. Если вы подключите провод к верхнему левому и нижнему левому краям, цепь будет замкнута.

Вот почему мы помещаем компонент через пространство посередине. Он следит за тем, чтобы углы не попадали под штыри на плате.

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

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

Шаг 3: подключение кнопок

Кнопочные соединения
Кнопочные соединения
Кнопочные соединения
Кнопочные соединения

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

Теперь добавим провода.

  • Поместите красный вывод от положительного вывода питания к тому же столбцу, что и нижний правый вывод на кнопке.
  • Подключите черный провод от отрицательного вывода питания к той же колонке, что и резистор.
  • Поместите цветной провод (не красный / черный) от верхнего левого контакта к цифровому контакту 2 на Arduino.

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

Шаг 4. Код…

Код…
Код…
Код…
Код…

Давайте посмотрим на код базовой кнопки.

Откройте редактор кода и перейдите с блоков на текст. Удалите появившееся предупреждение. Мы довольны текстом!

Вы знаете базовую настройку, поэтому давайте определим кнопку и выполним базовое чтение. Мы напечатаем вывод в Serial.

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

// Определяем константы

#define button 2 void setup () {pinMode (button, ВХОД); Serial.begin (9600); } void loop () {// Считываем цифровой вывод, чтобы проверить состояние кнопки int Press = digitalRead (button); // Кнопка возвращает ВЫСОКИЙ, если нажата, НИЗКИЙ, если нет, если (нажата == ВЫСОКАЯ) {Serial.println ("Нажата!"); }}

Хорошо, это работает!

По сути, все, что мы делаем, - это проверяем состояние цифрового вывода каждый раз, когда код зацикливается. Если вы нажмете Start Simulation и нажмете кнопку, вы увидите, что Serial Monitor (нажмите кнопку под кодом) отображает "Pressed!" неоднократно.

Одна из функций, которые вы увидите в приведенном выше коде, - это оценка условия if (). Все, что делает код, в данном случае - это задает вопрос и оценивает, правда ли он. Мы используем равно (двойные знаки равенства, например: ==), чтобы проверить, равно ли значение переменной определенному значению. DigitalRead () возвращает HIGH или LOW.

Используя if () else if / else, мы можем проверить многие условия или все условия, и если вы вернетесь к основам Arduino, вы увидите некоторые сравнения, которые вы можете сделать.

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

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

Давайте исправим!

Шаг 5: Небольшой отказ

Немного дебаунс
Немного дебаунс

Мы используем процедуру под названием debounce, чтобы решить нашу проблему с кнопкой. По сути, это ждет определенное количество времени между нажатием кнопки и фактическим ответом на нажатие. Для пользователя это все еще кажется естественным (если только вы не затянете время слишком долго). Вы также можете использовать его для проверки продолжительности печати, чтобы каждый раз реагировать по-разному. Никакую проводку менять не нужно!

Посмотрим на код:

# определить кнопку 2 # определить debounceTimeout 100

Первое изменение касается глобального масштаба. Вы помните, что здесь мы определяем переменные, которые могут использоваться многими нашими функциями или те, которые нельзя сбрасывать каждый раз при запуске цикла. Итак, мы добавили debounceTimeout к определенным константам. Мы сделали это 100 (что позже будет преобразовано в 100 мс), но это могло быть короче. Еще немного, и это будет неестественно.

long int lastDebounceTime;

Эта переменная объявлена под константами. Это тип long int, который в основном позволяет хранить в памяти длинные числа. Мы назвали это lastDebounceTime.

Нам не нужно ничего менять в функции void setup (). Оставим это.

void loop () {// Считываем цифровой вывод, чтобы проверить состояние кнопки int Press = digitalRead (button); long int currentTime = миллис (); // Код кнопки}

Первое изменение, которое мы вносим в функцию loop (), связано с вызовом чтения кнопки. Нам нужно следить за текущим временем. Функция millis () возвращает текущее время часов с момента загрузки Arduino в миллисекундах. Нам нужно сохранить это в переменной типа long int.

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

void loop () {// Считываем цифровой вывод, чтобы проверить состояние кнопки int Press = digitalRead (button); long int currentTime = миллис (); if (Press == LOW) {// Сбрасываем счетчик времени, пока кнопка не нажата lastDebounceTime = currentTime; } // Код кнопки}

Алгоритм if (Press == LOW) проверяет, не нажата ли кнопка. Если это не так, то код сохраняет текущее время с момента последнего устранения ошибки. Таким образом, каждый раз, когда нажимается кнопка, у нас есть момент времени, с которого мы можем проверить, когда была нажата кнопка. Затем мы можем выполнить быстрый математический расчет, чтобы узнать, как долго была нажата кнопка, и правильно отреагировать. Посмотрим на остальной код:

void loop () {// Считываем цифровой вывод, чтобы проверить состояние кнопки int Press = digitalRead (button); long int currentTime = миллис (); if (Press == LOW) {// Сбрасываем счетчик времени, пока кнопка не нажата lastDebounceTime = currentTime; } // Кнопка была нажата в течение заданного времени if (((currentTime - lastDebounceTime)> debounceTimeout)) {// Если время ожидания истекло, кнопка нажата! Serial.println («Нажато!»); }}

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

Запустите свой код и убедитесь, что он работает. Если у вас есть ошибки, проверьте свой код!

Теперь давайте посмотрим на практический пример.

Шаг 6: Создание меню

Составление меню
Составление меню

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

Итак, для этого проекта нам понадобятся:

  • Три кнопки
  • Три резистора установлены на 10 кОм

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

Три кнопки - это опция открытия / следующего меню, опция изменения (например, изменение настройки) и кнопка сохранения / закрытия меню.

Подключите, давайте посмотрим на код!

Шаг 7: Разбор кода - Глобальный

Хорошо, это будет долгий шаг, но я собираюсь пройтись по каждому разделу кода.

Во-первых, давайте посмотрим на необходимые глобальные переменные.

// Определение констант # define menuButton 2 #define menuSelect 3 # define menuSave 4 #define debounceTimeout 50 // Определение переменных int menuButtonPreviousState = LOW; int menuSelectPreviousState = НИЗКИЙ; int menuSavePreviousState = LOW; long int lastDebounceTime; // Параметры меню char * menuOptions = {"Проверить температуру", "Проверить свет"}; bool featureSetting = {ложь, ложь}; bool menuMode = false; bool menuNeedsPrint = false; int optionSelected = 0;

Эти три блока довольно похожи на то, что мы видели раньше. В первом я определил три кнопки и тайм-аут. Для этой части проекта я установил его на 50 мс, поэтому для его работы требуется целенаправленное нажатие.

Второй блок - это все переменные. Нам нужно отслеживать buttonPreviousState и отслеживать lastDebounceTime. Все это переменные типа int, но последний тип является длинным, потому что я предполагаю, что нам нужно место в памяти.

Блок опций меню имеет несколько новых функций. Во-первых, символ * (да, это намеренная звездочка), который является символьной / строковой буквальной переменной. Это указатель на статическое хранилище в памяти. Вы не можете его изменить (как, например, в Python). Эта строка char * menuOptions создает массив строковых литералов. Вы можете добавить столько пунктов меню, сколько захотите.

Переменная bool featureSetting - это просто массив значений, представляющий каждый пункт меню. Да, вы можете хранить все, что захотите, просто измените тип переменных (все они должны быть одного типа). Теперь, возможно, есть более эффективные способы управления этим, например словари или кортежи, но для этого приложения это просто. Я бы, наверное, создал один из последних в развернутом приложении.

Я следил за режимом меню, поэтому, если бы я хотел, чтобы на моем дисплее отображались другие вещи, я мог бы это сделать. Кроме того, если бы у меня была сенсорная логика, я мог бы приостановить ее во время работы с меню, на всякий случай, если что-то конфликтует. У меня есть переменная menuNeedsPrint, потому что я хочу печатать меню в определенное время, а не только все время. Наконец, у меня есть переменная optionSelected, поэтому я могу отслеживать выбранную опцию при доступе к ней в нескольких местах.

Давайте посмотрим на следующий набор функций.

Шаг 8: Разбор кода - настройка и пользовательские функции

Функция setup () достаточно проста, всего три объявления ввода:

void setup () {pinMode (menuSelect, ВВОД); pinMode (менюСохранить, ВВОД); pinMode (menuSelect, ВВОД); Serial.begin (9600); }

Далее идут три пользовательские функции. Давайте посмотрим на первые два, а затем на последний отдельно.

Нам нужны две функции, возвращающие некоторую информацию. Причина в том, что мы хотим, чтобы это было удобочитаемо. Это также поможет с отладкой кода, если у нас возникнет проблема. Код:

// Функция для возврата текущего выбранного optionchar * ReturnOptionSelected () {char * menuOption = menuOptions [optionSelected]; // Возврат optionSelected return menuOption; } // Функция для возврата статуса текущей выбранной опции char * ReturnOptionStatus () {bool optionSetting = featureSetting [optionSelected]; char * optionSettingVal; если (optionSetting == false) {optionSettingVal = "Ложь"; } еще {optionSettingVal = "True"; } // Вернуть optionSetting return optionSettingVal; }

Функция char * ReturnOptionSelected () проверяет выбранный параметр (если вы видите выше, мы устанавливаем переменную для отслеживания этого) и извлекает строковый литерал из массива, который мы создали ранее. Затем он возвращает его как тип char. Мы знаем это, потому что функция указывает тип возвращаемого значения.

Вторая функция, char * ReturnOptionStatus (), считывает состояние параметра, сохраненного в массиве, и возвращает строковый литерал, представляющий значение. Например, если значение, которое мы сохранили, равно false, я верну «False». Это потому, что мы показываем пользователю эту переменную, и лучше сохранить всю эту логику вместе. Я мог бы сделать это позже, но имеет смысл сделать это здесь.

// Функция для переключения текущего optionbool ToggleOptionSelected () {featureSetting [optionSelected] =! FeatureSetting [optionSelected]; вернуть истину; }

Функция bool ToggleOptionSelected () - это удобная функция для изменения значения параметра, выбранного в меню. Это просто переворачивает значение. Если бы у вас был более сложный набор опций, все могло бы быть совсем иначе. Я возвращаю true в этой функции, потому что мой обратный вызов (вызов позже в коде, который запускает эту функцию) ожидает ответа true / false. Я на 100% уверен, что это сработает, поэтому я не учел, что это не работает, но я бы сделал это в развернутом приложении (на всякий случай).

Шаг 9. Цикл…

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

void loop () {

// Работаем здесь <-----}

Хорошо, мы видели это раньше:

// Считываем кнопки int menuButtonPressed = digitalRead (menuButton); int menuSelectPressed = цифровое чтение (menuSelect); int menuSavePressed = digitalRead (сохранить меню); // Получить текущее время long int currentTime = millis (); if (menuButtonPressed == LOW && menuSelectPressed == LOW && menuSavePressed == LOW) {// Сбрасывает счетчик времени, пока кнопка не нажата lastDebounceTime = currentTime; menuButtonPreviousState = LOW; menuSelectPreviousState = LOW; menuSavePreviousState = LOW; }

Все, что мне нужно было сделать здесь, это добавить три вызова digitalRead () и убедиться, что я учел тот факт, что если все кнопки были на низком уровне, мы должны сбросить таймер (lastDebounceTime = currentTime) и установить все предыдущие состояния на низкое. Я также храню millis () в currentTime.

Следующая секция располагается внутри строки

if (((currentTime - lastDebounceTime)> debounceTimeout)) {

// Работаем здесь <----}

Есть три раздела. Да, я мог бы переместить их в их собственные функции, но для простоты я оставил здесь три основных алгоритма кнопок.

if ((menuButtonPressed == HIGH) && (menuButtonPreviousState == LOW)) {if (menuMode == false) {menuMode = true; // Сообщаем пользователю Serial.println («Меню активно»); } else if (menuMode == true && optionSelected = 1) {// Сбросить параметр optionSelected = 0; } // Распечатать меню menuNeedsPrint = true; // Переключаем кнопку пред. состояние для отображения только меню // если кнопка отпущена и нажата снова menuButtonPreviousState = menuButtonPressed; // Будет ВЫСОКОЕ}

Этот первый обрабатывается, когда menuButtonPressed имеет значение HIGH или когда нажата кнопка меню. Он также проверяет, было ли предыдущее состояние LOW, так что кнопку нужно было отпустить, прежде чем она была нажата снова, что не позволяет программе постоянно запускать одно и то же событие снова и снова.

Затем он проверяет, что если меню не активно, он активирует его. Он напечатает первый выбранный вариант (который по умолчанию является первым элементом в массиве menuOptions. Если вы нажмете кнопку второй или третий (и т. Д.) Раз, вы получите следующий вариант в списке. Я могу исправить это что когда он доходит до конца, он возвращается к началу. Это может считывать длину массива и упростить обратный цикл, если вы измените количество опций, но пока это было просто.

Последний небольшой раздел (// Распечатывает меню), очевидно, печатает меню, но он также устанавливает предыдущее состояние на ВЫСОКИЙ, чтобы та же функция не зацикливалась (см. Мое примечание выше о проверке того, была ли кнопка ранее НИЗКОЙ).

// menuSelect нажат, предоставить логику if ((menuSelectPressed == HIGH) && (menuSelectPreviousState == LOW)) {if (menuMode) {// Изменить выбранный параметр // На данный момент это просто истина / ложь // но может быть что угодно bool toggle = ToggleOptionSelected (); если (переключить) {menuNeedsPrint = true; } else {Serial.println («Что-то пошло не так. Повторите попытку»); }} // Переключаем состояние, чтобы переключаться только при отпускании и повторном нажатии menuSelectPreviousState = menuSelectPressed; }

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

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

if ((menuSavePressed == HIGH) && (menuSavePreviousState == LOW)) {// Выход из меню // Здесь вы можете сделать любую уборку // или сохранить в EEPROM menuMode = false; Serial.println («Меню закрыто»); // Переключаем состояние, чтобы из меню выходили только один раз menuSavePreviousState = menuSavePressed; }}

Эта функция обрабатывает кнопку menuSave, которая просто выходит из меню. Здесь у вас может быть опция отмены или сохранения, возможно, сделать некоторую очистку или сохранить в EEPROM. Я просто печатаю «Меню закрыто» и устанавливаю состояние кнопки ВЫСОКОЕ, чтобы оно не зацикливалось.

if (menuMode && menuNeedsPrint) {// Мы распечатали меню, поэтому, если // что-то не произойдет, нет необходимости печатать его снова menuNeedsPrint = false; char * optionActive = ReturnOptionSelected (); char * optionStatus = ReturnOptionStatus (); Serial.print ("Выбрано:"); Serial.print (optionActive); Serial.print (":"); Serial.print (optionStatus); Serial.println (); }

Это алгоритм menuPrint, который срабатывает только тогда, когда меню активно и когда для переменной menuNeedsPrint установлено значение true.

Это определенно можно переместить в отдельную функцию, но для простоты..!

Ну вот и все! См. Следующий шаг для всего блока кода.

Шаг 10: окончательный блок кода

// Определяем константы

#define menuButton 2 #define menuSelect 3 #define menuSave 4 #define debounceTimeout 50 int menuButtonPreviousState = LOW; int menuSelectPreviousState = НИЗКИЙ; int menuSavePreviousState = LOW; // Определяем переменные long int lastDebounceTime; bool lightSensor = true; bool tempSensor = true; // Параметры меню char * menuOptions = {"Проверить температуру", "Проверить свет"}; bool featureSetting = {ложь, ложь}; bool menuMode = false; bool menuNeedsPrint = false; int optionSelected = 0; // Настроить функцию

void setup () {pinMode (menuSelect, ВВОД); pinMode (менюСохранить, ВВОД); pinMode (menuSelect, ВВОД); Serial.begin (9600); }

// Функция для возврата текущей выбранной опции char * ReturnOptionSelected () {char * menuOption = menuOptions [optionSelected]; // Возврат optionSelected return menuOption; } // Функция для возврата статуса текущей выбранной опции char * ReturnOptionStatus () {bool optionSetting = featureSetting [optionSelected]; char * optionSettingVal; если (optionSetting == false) {optionSettingVal = "Ложь"; } еще {optionSettingVal = "True"; } // Вернуть optionSetting return optionSettingVal; } // Функция для переключения текущего параметра bool ToggleOptionSelected () {featureSetting [optionSelected] =! FeatureSetting [optionSelected]; вернуть истину; } // Основной цикл

void loop () {// Считываем кнопки int menuButtonPressed = digitalRead (menuButton); int menuSelectPressed = цифровое чтение (menuSelect); int menuSavePressed = digitalRead (сохранить меню); // Получить текущее время long int currentTime = millis (); if (menuButtonPressed == LOW && menuSelectPressed == LOW && menuSavePressed == LOW) {// Сбрасывает счетчик времени, пока кнопка не нажата lastDebounceTime = currentTime; menuButtonPreviousState = LOW; menuSelectPreviousState = LOW; menuSavePreviousState = LOW; } if (((currentTime - lastDebounceTime)> debounceTimeout)) {// Если время ожидания истекло, кнопка нажата!

// MenuButton нажат, обеспечиваем логику

// Срабатывает только тогда, когда кнопка была ранее отпущена if ((menuButtonPressed == HIGH) && (menuButtonPreviousState == LOW)) {if (menuMode == false) {menuMode = true; // Сообщаем пользователю Serial.println («Меню активно»); } else if (menuMode == true && optionSelected = 1) {// Сбросить параметр optionSelected = 0; } // Распечатать меню menuNeedsPrint = true; // Переключаем кнопку пред. состояние для отображения только меню // если кнопка отпущена и нажата снова menuButtonPreviousState = menuButtonPressed; // Будет ВЫСОКОЕ} // Нажата кнопка menuSelect, обеспечиваем логику if ((menuSelectPressed == HIGH) && (menuSelectPreviousState == LOW)) {if (menuMode) {// Изменить выбранный параметр // На данный момент это просто истина / ложь // но может быть что угодно bool toggle = ToggleOptionSelected (); если (переключить) {menuNeedsPrint = true; } else {Serial.print («Что-то пошло не так. Повторите попытку»); }} // Переключаем состояние, чтобы переключаться только при отпускании и повторном нажатии menuSelectPreviousState = menuSelectPressed; } if ((menuSavePressed == HIGH) && (menuSavePreviousState == LOW)) {// Выход из меню // Здесь вы можете сделать любую уборку // или сохранить в EEPROM menuMode = false; Serial.println («Меню закрыто»); // Переключаем состояние, чтобы из меню выходили только один раз menuSavePreviousState = menuSavePressed; }} // Распечатать текущий активный параметр меню, но распечатать его только один раз if (menuMode && menuNeedsPrint) {// Мы распечатали меню, поэтому, если // что-то не произойдет, нет необходимости печатать его снова menuNeedsPrint = false; char * optionActive = ReturnOptionSelected (); char * optionStatus = ReturnOptionStatus (); Serial.print ("Выбрано:"); Serial.print (optionActive); Serial.print (":"); Serial.print (optionStatus); Serial.println (); }}}

Схема доступна на сайте Tinkercad. Я встроил схему ниже, чтобы вы тоже увидели!

Как всегда, если у вас есть вопросы или проблемы, дайте мне знать!

Рекомендуемые: