Сводный список записей блога

--->>>> Сводный список записей блога <<<<---

12 января 2024

MicroMenu и его настройка


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

И возникает вопрос - как это все организовать малой кровью с небольшими затратами ресурсов МК. Когда то на easyelectronics я встретил статейку Steel.ne про организацию древовидного меню. Steel.ne использовал MicroMenu, допилив его под себя.

Идея меню проста. Каждый элемент меню - это запись с четырьмя ссылками - на предыдущую и последующую запись, на своего родителя и на потомка. Если записи по ссылке нет (например, это первый или последний элемент), соответствующая ссылка оставляется пустая (точнее там вставляется ссылка на фиктивный пустой элемент).

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

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

Элементы меню сохраняются во флеше, не используя оперативную память микроконтроллера.
Каждый элемент меню хранит 4 ссылки, 1 байт команды и нуль-терминированную строку. Минимальная строка - это один байт 0x00.
Итого для семейства AVR, где адрес умещается в 2 байта, элемент меню минимально занимает 4 * 2 + 1 + 1 = 10 байт. Для STM-ок, где адресация 32-битная - 4 * 4 + 1 + 1 = 18 байт. 

Идея меню мне понравилась и я взял проект Steel.ne и допилил, соответственно, уже под себя. 
Достаточно долгое время я просто таскал код меню из проекта в проект методом Ctrl+C - Ctrl+V... Но потом надоело. И в 2020 году я оформил наконец то это меню в виде отдельной библиотечки.
Стало легче подключать меню в свои проекты. Заодно библиотека обрела некоторую универсальность - для семейства AVR, например, там автоматически подставляется модификатор PROGMEM хранения во флеше, процедуры чтения данных из флеша (pgm_read_xxxx).

Подключается меню в проект до банальности просто:

#include "menu.h"

Дальше можно в основном файле программы или в отдельном заголовочнике добавить элементы меню.
Элементы меню добавляются макросом MAKE_MENU, определенным в вышеуказанном файле.
В макросе указывается уникальное имя элемента меню, имя следующего элемента меню, имя предыдущего, имя родительского элемента, имя дочернего элемента, код команды и текстовая строка для отображения меню.
Если по какой то из связей нет элемента меню - для такой связи ставится фиктивный элемент NULL_ENTRY, так же определенный в menu.h.

Пример задания элементов меню:

//      имя элемента, следующий, предыдущий, родительский, дочерний, команда, текст
MAKE_MENU( MenuItem1, MenuItem2,  NULL_ENTRY
, NULL_ENTRY, NULL_ENTRY, mCmd1, "Item1" );
MAKE_MENU( MenuItem2, MenuItem3,  MenuItem1,  NULL_ENTRY, NULL_ENTRY, mCmd2, "Item2" );
MAKE_MENU( MenuItem3, NULL_ENTRY, MenuItem2,  NULL_ENTRY, NULL_ENTRY, mCmd3, "Item3" );

Это пример одноуровневого меню из трех элементов.
mCmd1 ... mCmd3 - это какие то константы, определяющие номера команд. Их удобно вынести в какой то перечислимый тип.

Для простого перемещения по элементам меню есть макросы
MENU_PREVIOUS
MENU_NEXT
MENU_PARENT
MENU_CHILD

которые возвращают ссылку на соответствующего соседа текущего пункта меню.

Макрос MENU_COMMAND возвращает команду из текущего пункта меню.

Ссылка на текущий пункт меню хранится в переменной selectedMenuItem.

Функция menuChange делает активным пункт меню, который передан её аргументом. Функция возвращает 1 при успешном переходе или 0, если переход невозможен.

Стартовая инициализация меню - та же функция, в которую передается имя первого элемента меню. 
Например, вот так: menuChange(&MenuItem1);

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

Доступ к строке текущего пункта меню - selectedMenuItem->Text.

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

Но в то же время - это достаточно неудобный и нудный процесс. Еще и не визуальный ни разу.

Вот пример простой менюшки какого то гипотетического таймера.

Здесь черные линии - ссылки между элементами меню. Красные линии - это сохраненные в элементах меню команды. 
Видно, что для нескольких однотипных элементов меню можно задавать одну общую команду.

Всего 16 пунктов меню. И это нужно 16 раз прописать MAKE_MENU и заполнить все поля макроса. И не ошибиться. Т.е. куча Ctrl+C - Ctrl+V, что б, как минимум, прописать предыдущего и следующего соседа...

Вот так выглядит текстовое описание этих 16 пунктов:
//         имя элемента,  следующий,    предыдущий,   родительский, дочерний,      команда,              текст
MAKE_MENU( mmOn         , mmOff       , NULL_ENTRY   , NULL_ENTRY , NULL_ENTRY   , mcmdOn              , "Включить" );
MAKE_MENU( mmOff        , mmSchedule  , mmOn         , NULL_ENTRY , NULL_ENTRY   , mcmdOff             , "Выключить" );
MAKE_MENU( mmSchedule   , mmDateTime  , mmOff        , NULL_ENTRY , mmMonday     , 0                   , "Расписание" );
MAKE_MENU( mmMonday     , mmThuesday  , NULL_ENTRY   , mmSchedule , NULL_ENTRY   , mcmdShedule         , "Понедельник" );
MAKE_MENU( mmThuesday   , mmWednesday , mmMonday     , mmSchedule , NULL_ENTRY   , mcmdShedule         , "Вторник" );
MAKE_MENU( mmWednesday  , mmThursday  , mmThuesday   , mmSchedule , NULL_ENTRY   , mcmdShedule         , "Среда" );
MAKE_MENU( mmThursday   , mmFriday    , mmWednesday  , mmSchedule , NULL_ENTRY   , mcmdShedule         , "Четверг" );
MAKE_MENU( mmFriday     , mmSaturday  , mmThursday   , mmSchedule , NULL_ENTRY   , mcmdShedule         , "Пятница" );
MAKE_MENU( mmSaturday   , mmSunday    , mmFriday     , mmSchedule , NULL_ENTRY   , mcmdShedule         , "Суббота" );
MAKE_MENU( mmSunday     , NULL_ENTRY  , mmSaturday   , mmSchedule , NULL_ENTRY   , mcmdShedule         , "Воскресенье" );
MAKE_MENU( mmDateTime   , mmSetup     , mmSchedule   , NULL_ENTRY , NULL_ENTRY   , mcmdDateTime        , "Время, дата" );
MAKE_MENU( mmSetup      , NULL_ENTRY  , mmDateTime   , NULL_ENTRY , mmSound      , 0                   , "Настройки" );
MAKE_MENU( mmSound      , mmDisplay   , NULL_ENTRY   , mmSetup    , NULL_ENTRY   , mcmdSoundSetup      , "Звук" );
MAKE_MENU( mmDisplay    , NULL_ENTRY  , mmSound      , mmSetup    , mmBrightness , 0                   , "Дисплей" );
MAKE_MENU( mmBrightness , mmContrast  , NULL_ENTRY   , mmDisplay  , NULL_ENTRY   , mcmdBrightnessSetup , "Яркость" );
MAKE_MENU( mmContrast   , NULL_ENTRY  , mmBrightness , mmDisplay  , NULL_ENTRY   , mcmdContrastSetup   , "Контраст" );

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

Я подумал, что это не совсем верно. И набросал "на коленке" программульку, в которой структура меню задается визуально, можно для каждого элемента меню прописать свое имя, команду, текст элемента меню. И можно легко перемещать элементы меню вверх-вниз, к родителю или к потомку...
А структура меню, включая все взаимосвязи, сформируется автоматически. 

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

Вот какая то вот такая программа получилась: 

Здесь как раз виден проект меню вышеописанного гипотетического таймера.

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

Здесь как раз видно, что формируется список макросов, описывающий меню. Для читабельности производится выравнивание по ширине элементов.
Этот список команд можно экспортировать прямо в проект, указав файл, где должно быть описание меню. Это может быть как *.c - программный, так *.h - заголовочный файл. 

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

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

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

Ну а теперь то , ради чего была написана эта простыня текста - файлы.

Архив МикроМеню

Архив с программой

Демонстрационный проект меню

Следует заметить, что все что здесь описано - это всего лишь организация описания и хранения иерархии меню в программе МК.

Вопросы отрисовки меню, управления и хождения по меню - они индивидуальны и к теме данной статьи не относятся.

Как какой то пример - можно посмотреть работу с меню в статье Steel.ne на изиэлектрониксе.

14 комментариев:

  1. Анонимный10 апреля, 2024 23:42

    Здравствуйте. Что то со шрифтами в Menu Creator v1.0.152
    Левая часть читаеться (Добавить ниже; Добавить дочерний... ) а правая часть в виде в непонятной кодировке.
    Операционная система Windows10.
    Спасибо за проделанную работу!

    ОтветитьУдалить
    Ответы
    1. Проверьте в региональных настройках, вкладка Администрирование - язык для программ, которые не поддерживают юникод. Там кириллица должна быть.

      Удалить
    2. Анонимный11 апреля, 2024 23:38

      Стоит руский. :(

      Удалить
    3. Скрин сделайте, пожалуйста..

      Удалить
    4. Анонимный12 апреля, 2024 12:28

      Я оставил сообщение на коте, а ЛС.
      Я не вижу, как прикрепить здесь скрин или файл.

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

      Удалить
    6. Анонимный12 апреля, 2024 15:24

      https://drive.google.com/file/d/1bDxgyC6YwGMuRobgiLYLPpQOwlpldM-4/view?usp=drive_link
      Выложил

      Удалить
    7. Попробуйте загрузить обновленную версию (ссылка та же) - 1.0.159.

      Удалить
    8. Анонимный14 апреля, 2024 09:13

      Всё как и раньше. (
      Попробовал запустить в windows11- он просто прибил файл, написав что это вредоносное ПО.

      Удалить
    9. Что я делаю не так?
      https://drive.google.com/file/d/1t5y5_VpuA7oABHt5uQGeECQPZOe1PP5z/view?usp=sharing

      Удалить
    10. Анонимный14 апреля, 2024 18:31

      https://drive.google.com/file/d/1yyZWk0lIKu6R-FodyhX1q-NqO72loE43/view?usp=sharing

      Удалить
    11. Region Germany пробовали менять?

      Удалить
    12. Анонимный14 апреля, 2024 23:21

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

      Удалить
    13. Ну вот мне тоже непонятно. TLabel такой же , как и соседние...

      Удалить

======= !!! ВНИМАНИЕ !!! ======================================================================
Гугл умный и боится спама. Поэтому иногда ваши комментарии Гугл отправляет мне на премодерацию. Отправлять или нет - решаю не я, а алгоритмы Гугла. Если ваш комментарий не появился сразу, значит я получу уведомление и опубликую ваш комментарий через некоторое время. Я стараюсь это делать достаточно оперативно.