ARDUINO Меню на LCD экране

edkotinsky

★✩✩✩✩✩✩
5 Мар 2021
19
17
21
Алматы
Тема посвящена нарождающемуся проекту легкого, простого в обращении и не слишком простого по функционалу меню, использующему для вывода LCD экран типа 1602.

Несколько дней назад я создавал тему "Меню на LCD". Тогда проект уже наполовину работал и с каждым днем прогрессировал, потому я его не бросил и не пошел рассматривать готовый код. Тогда у меня возникла проблема с передачей меню названий пунктов, и быстро решилась при помощи массива структур. На данный момент код имеет версию 1.2 (все, что было до 1.0 - писалось без класса, подряд, взахлеб и зачастую необдуманно, и потому криво).

Меню можно представить как дерево, в самом корне которого - функция control. Она всегда крутится в цикле loop, к ней так или иначе привязаны главные переменные и в ней вызываются функции, определяющие работу меню. Ей передается один параметр - значение кнопки. Это может быть на самом деле что угодно, что может передавать значения от 1 до 4. Функций, в ней вызываемых, несколько: menuDisplay - функция, в которой реализуется вывод названия вроде "меню такой-то версии", вывод названий пунктов и их листание; pointDisplay - в ней содержится код, отвечающий за вывод и функционирование пунктов меню; а также displaySleep и displayClean - они, как понятно из их названий, отвечают за сон (отключение подсветки) и обновление экрана.

Кроме функций, доступных только внутри класса и напрямую связанных с control, есть публичные функции, дающие возможность управлять меню пользователю. Это: mainDisp, которой через запятую передаются данные для вывода на главном экране, secondValue, которая позволяет запретить или разрешить вывод второго значения в пункте, и begin, которая существует в качестве костыля.

Названия пунктов меню задаются при помощи массива структур, определяемого пользователем. В этом массиве нулевой элемент хранит название меню. Кроме названий, в структуре хранятся два значения, ограничивающие диапазон переменных, задаваемых в пункте. Сам массив кладется в FLASH память.

Массив структур:
const menuStruct PROGMEM points[MENU_points] = {
  {"MENU v 1.2", 0, 0},     //0
  {"point1", 0, 12},        //1
  {"point2", 0, 12},        //2
  {"point3", 1, 2},         //3
  {"point4", 0, 10},        //4
  {"point5", 0, 7},         //5
  {"point6", 0, 10},        //6
};
Переменные, связанные с пунктами, хранятся в трех массивах в EEPROM, что позволяет сохранять их значения. Почему массива три? В первом массиве значения определяют, можно ли выводить второе значение пункта и менять его. Во втором и третьем - первое и второе значения пункта.

Что за второе значение пункта, спросите вы? Это лучше показать фотографией:

Yfpx7IZSx3k.jpg

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

Наверное, стоит пару слов сказать про передачу параметров главному экрану - это такая небольшая крутая фича меню. Функция реализована на вариативном шаблоне, поэтому ей можно передать различные значения через запятую (не знаю, насколько различные на самом деле, но int и char - точно). Переход на следующую строку осуществляется передачей ей аргумента “\r”.

Передача строк и чисел для вывода на главном экране:
menu.mainDisplay("abba", "is a great", "\r",
                 'g', 'r', 'o', 'u', 'p', 1234);
Теперь об управлении. Как я уже сказал выше, главной функции передается значение кнопки или чего угодно, от 1 до 4. Этим значениям соответствуют ESC, DOWN, UP и ENTER. Если экран спит, то нажатием на любую клавишу он выводится из сна. Нажатие на ENTER, сделанное в главном экране, переключает его на экран меню. Здесь можно листать пункты клавишами UP и DOWN. Переход в пункт осуществляется ENTER’ом. Но в пункте ENTER выполняет только одно действие - переключает текущее изменяемое значение, что индицируется курсором. Как менять значения, думаю, понятно. Выход из пункта и из меню - это ESC. При выходе из пункта значение обоих переменных обновляется (если было изменено) в EEPROM.


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

Код прилагаю (тут лежит еще моя библиотека myButton)
 

Вложения

Изменено:

edkotinsky

★✩✩✩✩✩✩
5 Мар 2021
19
17
21
Алматы
Сегодня добавил версию кода 1.5, но потом нашел критический косяк и удалил.

Версия 1.6, библиотека юзает microWire и microLiquidCrystal_I2C, пример - мою библиотеку myButton, весь код прилагаю. Если кому не лень, поиграйтесь с получившимся проектом, может быть, найдутся еще недочеты или появятся идеи по улучшению.

menuLCD v1.6

В этой версии:
  1. Убран костыль begin
  2. Дефайны настроек убраны в файл config.h (да, неудобно, я знаю, впоследствии исправлю)
  3. Добавлена функция getPointVal(номер пункта, номер значения), позволяющая брать значения переменных пункт
  4. Добавлен дефайн MENU_structNum
  5. Ну, вследствие небольшой оптимизации и вырезания лишнего код стал занимать 13% flash и 6% ram, что я считаю неплохо, учитывая, что подключаются четыре библиотеки. Позже сделаю подробный отчет об использовании памяти библиотекой.
В примере можно менять яркость светодиодов, задавая значение в пунктах. Также внутри примера лежит маленькая библиотека myButton, так что ничего дополнительно подключать не надо.
 

Вложения

edkotinsky

★✩✩✩✩✩✩
5 Мар 2021
19
17
21
Алматы
menuLCD v 1.7

В этой версии:
  1. Описание функций перенесено в общий .h файл, что сделало возможной (по крайней мере, очень упростило) настройку меню через дефайны
  2. Вследствие первого пункта, добавлены настройки через define
  3. Исправлен небольшой баг с выходом на главный экран при засыпании
В будущем добавлю дефайн MENU_expanded и несколько видов пунктов (наверное). Все так же используются библиотеки microWire и microLiquidCrystal_I2C.

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

Что умеет menuLCD v1.7:
#define MENU_points 2       //количество пунктов считается от 1
#define LCD_sleepTime 10000 //по истечению этого времени экран уснет
#define MENU_cleanTime 1000 //период очистки экрана
#define MENU_cursor "<"     //вид курсора

//так задаются настройки пунктов
const menuStruct PROGMEM points[MENU_structNum] = {
  {"MENU v 1.5", 0, 0},     //0   <-  нулевой элемент массива - это название меню, как пункт его использовать нельзя
  {"point1", 0, 10},        //1       
  {"point2", 0, 20},        //2       
};

//первый элемент структуры это название, второй - нижнее ограничение диапазона переменных
//третий - верхнее ограничение диапазона переменных. То есть в пункте 1 переменные могут меняться от 0 до 10,
//в пункте 2 от 0 до 20

//создание объекта меню
menuControl имя-объекта (массив-структур, объект-LCD);

//вызывается в loop и принимает значение от 1 до 4
имя-объекта.control(значение); 

//то, что передается функции mainDisplay - выводится на главный экран
//аргумент "\r" переносит строку
имя_объекта.mainDisplay("строка char 1", "\r", "строка char 2", 1234);

//возвращает значение переменной пункта
имя-объекта.getPointVal(номер-пункта, номер-переменной); 

//можно вывести вторую переменную и разрешить ее изменение, а можно спрятать
//значение 1 или 0
имя-объекта.secondValue(номер-пункта, разрешение-на-вывод);
 

Вложения

  • Лойс +1
Реакции: kostyamat

edkotinsky

★✩✩✩✩✩✩
5 Мар 2021
19
17
21
Алматы
MenuLCD v1.9

Работа идет, тема не умерла, ни в коем случае так не думайте. Добавлено так много всего, что даже писать лень. Не буду пока описывать все новые возможности. Но меню теперь умеет вызывать пользовательские функции и имеет два дополнительных пункта: пункт пользовательской функции и пункт с выбором значения (типа да или нет). Ненужные пункты можно отключать и включать при помощи дефайнов.

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

Вложения

kDn

★★★★★✩✩
18 Ноя 2019
1,109
437
Код не нужен, но сообщения читаю :).
Вообще рекомендовал бы вам размещать наработки на гите, так и проще смотреть и вам же полезнее будет осваивать общепринятые инструменты/хранилища.
 

edkotinsky

★✩✩✩✩✩✩
5 Мар 2021
19
17
21
Алматы
SimpleMenu v 2.2

Что ж, наверное, это пока последняя версия. В ней переделано хранение переменных для пунктов - раньше были адреса-массивы со спецификатором EEMEM, теперь - свой механизм хранения. Помимо привычного пункта, где можно менять значение, инкрементируя и декрементируя его на 1, были добавлены тип changeVal, где можно выбирать между двумя предопределенными значениями, functionCaller, содержание которого определяется пользовательской функцией и onlyView, которому можно передавать параметр для демонстрации. Еще я добавил "включение" и "отключение" пунктов attachPoint и detachPoint.

По памяти - четыре библиотеки, две - Гайвера (microWire и microLiquidCrystal) и две мои - (myButton и меню) занимают 7% ОЗУ и 15% ПЗУ. Думаю, это достаточно хороший результат.

Частично перевел на английский и привел в порядок комментарии в коде - готовлю выкладывать на ГитХаб.

В общем, если кому не лень - пошерстите код (а лучше не надо, во избежание нервных потрясений). С библиотекой идет пример ее использования, и, по крайней мере, на моем железе все работает.
 

Вложения

Изменено:
  • Лойс +1
Реакции: kostyamat

SPS

★✩✩✩✩✩✩
6 Дек 2020
78
10
Интересно, а варианты с энкодером и на OLED будут?
 

DaemoNPro

✩✩✩✩✩✩✩
8 Сен 2018
1
0
Меню текстовое, по сути если написать враппер под функции отображения на экране, прекрасно заработает и на oled