Как сделать меню на ардуино

Обновлено: 03.07.2024

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

Меню пользователя Arduino на практике

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

  • двухстрочные модули ЖК-дисплея, где в каждой строке 16 знаков;
  • кнопки в количестве четыре штуки;
  • дополнение в виде линий GPIO в количестве восемь штук;
  • микроконтроллер PIС (обмен информации с Arduino UNO происходит по средствам интерфейса TWI);
  • библиотеки функций, значительно упрощающие весь рабочий процесс с модулем, что позволяет даже новичку в данном деле без труда разобраться как им пользоваться.

Arduino — принцип функционирования на практике примерного меню

Разобрав на примете то, как программа работает, даже неопытному пользователю всё станет предельно понятно. Предлагаемое решение оснащено несколькими уникальными функциями, которые также как и библиотеки Arduino могут без труда подстраиваться под свои потребности.

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

Рассматривая четырёх строчное меню, можно увидеть, что на сам ЖК-дисплей вмещается размещение только двух строк по шестнадцать знаков каждая. Управление при этом осуществляется посредством четырёх клавиш, нумерация которых начинается слева направо в такой последовательности: 1, 2, 3, 4, где:

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

Во взятом за пример меню экран ЖК-модуля показывает 2 строки, где в каждой по 16 знаком, но согласно условий примера у нас 4 строки. Учитывая это, отобразить все строки меню полностью на таком экране невозможно. Это и является причиной его разделения на фрагменты по две строки каждый.

Arduino — обозначение элементов меню

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

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

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

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

Ещё оно определение, а именно lcd_lines — тривиальное. В данном случае нужно только определить количество строк дисплея, где отображается меню. По рассматриваемому дисплею, это будет выглядеть так:

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

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

const menuitem menu_lines[menuoptions]=

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

signed char menu_position;

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

Arduino — применение меню на практике

Ознакомившись со всем меню, далее можно начинать процесс по их функциональному отображению в программе. Первое, что следует сделать, так это присвоить функции void setup() исходный показатель переменной, которая определяет действующую позицию в меню (menu_position = 0) и также посредством функции Display_Menu() отобразить меню.

В функции void loop() считываем номер нажатой кнопки с помощью вызова: pressed = Number_of_Button();

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

if (pressed > 0) user_choice = User_Menu(pressed);

Arduino — дополнительные данные в помощь по созданию меню

Одна переменная может содержать адрес иной переменной и в данном случае речь идёт о указателе (pointer). Применение указателей на практике позволяет в результате поучить код, значительно эффективнее того, который получается посредством применения иных методов. Некорректное их применение может привести к ухудшению читаемости программы.

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

char x = 1, y = 2, z[5];

указатель =&x; //переменная указатель указывает на x

y =*указатель; //теперь переменная y имеет значение 1

*указатель = 0; //теперь переменная x имеет значение 0

указатель = &z[0] //переменная указатель указывает на первый элемент массива z

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

void Замена (int*Tx, int *Ty)

int A = 10, B = 20;

Замена (&A,&B); //замена значений переменных A и B

При указании указателем Т на начало массива элемента типа int и при этом его тип так же *int, операция Т=Т+1 тут же передвинет указатель на последующий элемент массива. К самому адресу указателя не добавляется 1 или 2, а связано это с тем, что длина переменной типа int равна 2 байта. На сегодняшний день только определённые операции можно реализовать с помощью указателя, ведь все иные будут недействительны. К ним относятся:

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

typedef struct параметры

указатель->x; обеспечивает обращение к полю x

указатель->высота; обеспечивает обращение к полю высота

обеспечивает обращение к полю x-указатель->высота; обеспечивает обращение к полю высота

Создавая массив посредством применения структуры параметров, это непосредственно отразится на указателе, который изменится на количество байтов, которое будет равно размеру 1-го элемента массива, при этом указывая на отдельные элементы. Вычислять при этом количество байтов не будет необходимости.

параметры массив_параметров[10]; //определение массива-списка параметров

параметры*указатель = &tablica_parametrów[0]; //теперь указатель указывает

//на начало массива

(указатель+5)->x; //укажет на поле x пятого элемента массива

//позволяя получение или изменение переменной;

yul-i-an аватар

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


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

Andy аватар

Есть масса замечаний к коду, а значит есть еще куда развиваться.

1.Строка 37 очевидно от непонимания.

2.Антидребезга не увидел. Опрос кнопок оставляет желать лучшего.

3.Нет выхода из подменю без сохранения переменной.

4.Переменные разумнее организовать в виде структуры

5.В чем смысл вывода на экран каждые 50 мС? Индикация разве динамическая? Вывод нужно делать по событию.

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

yul-i-an аватар

Есть масса замечаний к коду, а значит есть еще куда развиваться.

1.Строка 37 очевидно от непонимания.

2.Антидребезга не увидел. Опрос кнопок оставляет желать лучшего.

3.Нет выхода из подменю без сохранения переменной.

4.Переменные разумнее организовать в виде структуры

5.В чем смысл вывода на экран каждые 50 мС? Индикация разве динамическая? Вывод нужно делать по событию.

1. Строка 37 - служит для того чтобы при переполнении millis() условие в 41 строке выполнилось (если устройство расчитано на непрерывнуюработу - более 50 дней).

Я думаю что пока будет выполнятся код от 16 строки при нажатии кнопки UP до 27 строки где проверяется отпущина кнопка или нет положение контактов устаканиться. (если можете предложите лучьший варийант опроса кнопок).

3. Если переключаться между экранами не меняя переменных, то они и не изменяться.

4. Это дело сугубо личное, хоть объектом опишите.

5. Так проще, чем отслеживать изменение каждой переменной и при её изменении перирисовывать экран.

И так, пока ждем посылки из Китая со всеми компонентами. Сделаем простенькое меню для Arduino и LCD Keyboard шилда.

Как выглядит готовый результат:


Программировать я больше привык в Visual Studio (сейчас у меня версия communit 2015) + Arduino для Visual Studio

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

Итак, готовая прошивка для простого меню с комментариями:

void ( * on_click ) ( int ) ; // Ссылка на функцию вызова обрабоки изменения значения, параметр +1 или -1)

sprintf ( str_format , "UT: %7lu s. " , Uptime / 1000 ) ; // Форматируем строку %lu это вывод unsigned long

if ( ButtonPressTime + ButtonInterval Uptime ) < // Если кнопка была нажата раньше чем ButtonInterval ms назад

ButtonPress = 0 ; // Отжимаем кнопку (это имитуреет многократное нажатие с интервалом ButtonInterval если кнопку держать)

if ( ButtonId == 4 ) MenuItems [ MenuCurent ] . on_click ( 1 ) ; // Клик [+] Увеличиваем значение выбранного параметра

if ( ButtonId == 5 ) MenuItems [ MenuCurent ] . on_click ( - 1 ) ; // Клик [-] Уменьшаем значение выбранного параметра

sprintf ( str_format , MenuItems [ MenuCurent ] . format , MenuItems [ MenuCurent ] . param ) ; // Форматируем вывод знечения

Надеюсь, этот код кому-то сэкономит время, прошу не стесняться комментировать =)

20 thoughts on “ Простое меню для Arduino ”

Пытаюсь использовать ваш код но выдает что переменнные не задекларированы

MenuSaveRam’ was not declared in this scope

Это проблема компилятора, я когда-то сталкивался с такой, надо объявление функций и структур вынести в заголовочный файл (.h)

подскажите для чайников как это сделать или где про это почитать. Спасибо!

Мне не помогло. Печально

Приветствую, хорошее меню. А можно ли его реализовать на дисплее LCD 1602 I2C и подключеным отдельно на макете кнопкам, если да то что поменять?

Конечно можно, нужно поменять обработку клавиш в этом месте на вашу, читая значение пинов к которым присвоены кнопки в этом месте

if (ButtonPress == 0) < // Если кнопки не были нажаты ранее
int ButtonPinValue = analogRead(0); // Проверяем значение, не нажата ли кнопка
if (ButtonPinValue

А приведёте пример изменения такой записи на 4-5 кнопок, сравнительно с вашим подключением кнопок на шилд (какие команды будут это заменять )? Буду очень благодарен! ^_^

Не совсем понятно как с int ButtonPinValue = analogRead(0) сделать приём с 4-5 выходов Ардуинки.

Ну если это не аналоговый вход а цифровые, тогда надо пользоваться digitalRead()
Ну как-то так:

if (ButtonPress == 0) < // Если кнопки не были нажаты ранее
if (digitalRead(pinButton1)) ButtonPress = 4; // Нажата [+]
else if (digitalRead(pinButton2)) ButtonPress = 2; // Нажата [Prev]
else if (digitalRead(pinButton3)) ButtonPress = 3; // Нажата [Next]
else if (digitalRead(pinButton4)) ButtonPress = 5; // Нажата [-]
else if (digitalRead(pinButton5)) ButtonPress = 1; // Нажата [Menu]
> else …

где pinButton соответственно пин к которому подключена конкретная кнопка

1) У функции void MenuSaveRam(int Concat) < …>потерялась закрывающая фигурная скобка
2)Для того чтобы sprintf(str_format, …) могла отображать результат во всю ширину экранчика, длина массива str_format должна быть 17 (на 1 длиннее ширины экранчика – для хранения нуля, завершающего строку)

Не совсем так =)
1) Скобочка закрывающая есть, одна после if… а вторая после процедуры
2) Длинна и так в 17, так как размерность у массив начинается с 0. От 0 до 16 – 17 символов.

Не получается запустить Ваш скетч. Пишет кучу ошибок:
menu:46: error: ‘MenuSaveRam’ was not declared in this scope

menu:47: error: ‘SettingDateDay’ was not declared in this scope

menu:48: error: ‘SettingDateMonth’ was not declared in this scope

menu:49: error: ‘SettingDateYear’ was not declared in this scope

menu:50: error: ‘SettingTimeHour’ was not declared in this scope

menu:51: error: ‘SettingTimeMin’ was not declared in this scope

C:\Users\Р?ван\Documents\Arduino\menu\menu.ino: In function ‘void loop()’:

menu:80: error: ‘pinButton11’ was not declared in this scope

menu:81: error: ‘pinButton8’ was not declared in this scope

C:\Users\Р?ван\Documents\Arduino\menu\menu.ino: In function ‘void MenuSaveRam(int)’:

menu:127: error: a function-definition is not allowed here before ‘

menu:131: error: a function-definition is not allowed here before ‘

menu:135: error: a function-definition is not allowed here before ‘

menu:139: error: a function-definition is not allowed here before ‘

menu:143: error: a function-definition is not allowed here before ‘

menu:145: error: expected ‘>’ at end of input
Помогите запустить.

Эти ошибки проблема компилятора. Если быть более правильным, то объявления функция должны содержаться в отдельном файле заголовка .h но visual-studio понимает код и в таком контексте, что по мне очень удобно. Тут два варианта или переписать код (возможно поможет вариант, если перенести функции в начало скетча) или попробовать как я компилировать в visual studio.

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

действительно одна фигурная скобка лишняя:
void MenuSaveRam(int Concat) < // SAVE & EXIT
if (Concat == 1) < //TODO Сохранение параметров в RAM >;
MenuEnter = false; // Выход из меню
>

А если всё, после “// Функции вызываемые для изменения значений переменных” перенести по тексту выше, до “// Главный цикл” то всё заработает.

точнее перенести выше “// Тип структуры данных описывающих пункт меню”, до описания пунктов меню

Спасибо! Скобка реально потерялась ))

Здравствуйте! Подскажите пожалуйста – после того как Вы скомпилировали код в VS, как и чем(как я понимаю что не через ардуино ИДЕ) его загружаете в контроллер? (Писать код в VS действительно удобней).


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

И вот, на основе данных, полученных из этих статей(или описанных в этих статьях, это уж кому как, лол) мы пришли к выводу что всё. Пора. Пора сделать хоть что то серьёзное для проекта GreyBox . Что у нас из этого вышло, об этом и будет данная статья. Вообще, в дальнейших планах, много работы с серым ящиком, и возможно это не последняя статья про него. И да, сразу хотим предупредить вас - в статье будет много видео, точнее больше чем обычно мы выкладывали в других статьях. Итак начнем, нижеследующий скетч послужил основой для базы создания меню, в нем реализовано корректное считывание нажатий кнопок, подавление дребезга контактов, работа с дисплеем LCD2004 и другим оборудованием установленном в сером ящике. Сразу же нужно отметить что, лист меню, реализованный в скетче, вызывается нажатием кнопки ENT на клавиатуре серого ящика, и выглядит он так:

Как работает этот скетч? Это можно увидеть из нижеследующего видео.

Конечно же автор проекта на этом не остановился, когда создана основа, можно только наращивать функционал и потенциал. Дальнейшие действия были направлены на то чтобы реализовать функционал для пунктов уже имеющегося одноуровневого меню, так сказать создать для каждого пункта свой подпункт. Следующим шагом стало добавление функционала к пунктам меню Set Date и Set Time, соответственно при помощи пункты открывают доступ к настройкам/установкам времени/даты.

Наверно все знают такой компонент как RadioButton из нашей всеми любимой "винды", реализация этого компонента показана в нижеследующем видео при входе в пункты меню Date Format, Date Divider, Time Format, Time Divider и Buzzer.

В одном из экранов установки опций было решено реализовать движение курсора не только вверх/вниз при выборе нужных подпунктов но и вправо/влево, эта функция была прописана и дополнена к кнопкам 4(влево) и 5(вправо).

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


Итак, однозначно можно сказать что мощность платы Arduino Uno в этом проекте не была использована на все 100%. Остались еще свободны 6 портов ввода/вывода и шина I2C. Но всё же, в качестве теста технологии создания меню и управления подключенным к плате оборудованием - эта разработка очень даже себя показала с позитивной стороны. На этом пока всё, мы надеемся это не последний наш проект с использованием GreyBox, если у вас появятся какие либо вопросы, либо предложения пишите на нашу электронную почту или в комментариях, мы обязательно ответим вам.


Фото

Видео
Отдельная благодарность Михаилу, в основе используются его труды )
Возникла задача организовать вывод информации, дисплеи TFT и графический 128х64 оказались избыточными, а в заначке был W1602 .
Да к тому же требовалось выводить информацию динамически и с возможностью просмотра различных параметров.
На таком дорогом (400 р.), большом по габаритам и абсолютно бестолковом по функционалу дисплее, это возможно только с помощью перехода по соответствующим параметрам в меню.
Для всего этого нужна структура, например:

перед этим должны быть объявлены вложенные структуры:

а так же заголовки, они будут отображаться в меню:

дальше напишем функцию

и функцию рисования заголовка:

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

Комментарии ( 6 )

Каким образом LCD_Print определяет сколько символов выводить? Она выводит строку, потому так делать нельзя

Строки лучше давать массивом, а не набором переменных. Еще я бы добавил проверку и установку рабочей области дисплея, чтобы код был универсальным: и для 2х16, и для 4х20.

Для заголовков меню нужно использовать prog_uint8_t (в случае AVR и GCC) или что-то подобное для других архитектур, а не const char. Иначе при инициализации все ваши заголовки будут засунуты в оперативку, которой и так кот наплакал. А при развитом меню, ни на что другое не останется.

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

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

Читайте также: