Добавить в корзинуПозвонить
Найти в Дзене
За_тех_кто_в_коде();

Многокнопочное управление на Ардуино.

Продолжение кнопочной программы. Здесь более расширенный функционал. Кнопок теперь 6, 4 стрелки для навигации по странице и страницам и 2 управляющие. Все так же организовано с помощью страниц. Используется меню для перехода на другие страницы. На одной из страниц присутствуют счетчики, значения которых можно изменять с помощью кнопок, а результат будет сохранятся в EEPROM. Добавлено так же внешнее (скажем так аппаратное) звуковое сопровождение нажатой кнопки. Оформление страниц программы минималистичное, для сохранения компактности кода и наглядности в целом. Для дальнейшего движении уже необходимо иметь хоть какую-то системную организацию. Самый минимум это организовать работу таймера. Если более конкретно, то необходимо заменить delay(), на метод который не будет останавливать выполнение программы, отправляя её в цикл ожидания, а будет запускаться, если подошло его время. Это необходимо например и для обработки нажатий клавиш, так как только лишь устранения дребезга не решает проб
Оглавление

Продолжение кнопочной программы. Здесь более расширенный функционал. Кнопок теперь 6, 4 стрелки для навигации по странице и страницам и 2 управляющие. Все так же организовано с помощью страниц. Используется меню для перехода на другие страницы. На одной из страниц присутствуют счетчики, значения которых можно изменять с помощью кнопок, а результат будет сохранятся в EEPROM. Добавлено так же внешнее (скажем так аппаратное) звуковое сопровождение нажатой кнопки.

Оформление страниц программы минималистичное, для сохранения компактности кода и наглядности в целом.

1.Системная часть.

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

Это необходимо например и для обработки нажатий клавиш, так как только лишь устранения дребезга не решает проблему двойного-тройного нажатия, из-за того, что полный цикл программы быстрее прокручивается, чем физически отжимается кнопка. Так же это полезно для организации опроса датчиков. Да, многие переходят на схему постоянной работы, когда при опросе получаешь свежие данные (монитор тока INA219, магнитометр), но многие датчики работают по схеме запрос->ожидание->данные. И их библиотеки работают таким образом, что процесс ожидания реализован через delay в самой функции опроса датчика. Делать иначе, это задействовать один из трех счетчиков чтобы он генерировал прерывание, когда подошло время получения данных, которые в свою очередь можно складировать в поля объекта класса например.

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

Реализация класса, который по отношению к остальным, является базовым, максимально простая. Оба метода прям в описании класса. При таком подходе каждый производный класс уже имеет свой таймер.

-2

При создании объекта производного класса, заводится Timer_Set(), можно прям в конструкторе. Далее метод класса, требующий периодической работы, начинается со строки if (Timer_Check()) {…}

В примере описана работа с библиотекой часов (RTC), но она просто ограничивает период опроса. Чтобы написать полноценную реализацию библиотеки датчика с использованием системного таймера, нужно разбирать готовую библиотечную функцию на отдельные составляющие, например как для барометра: Get_Pressure, Get_Temperature, и т. д. Между которыми и должен был бы стоять delay.Таймер можно использовать один, просто перевзводить на другое время, ну и держать переменную-флаг, которая будет указывать, а какой именно метод нужно запускать в данный момент времени.

Само собой, класс-таймер можно использовать не только для датчиков. Производные классы могут реализовывать различные события, типа включения светодиодов или задержки для каких-то внутренних задач. Если одного таймера мало, в конструкторе производного класса, всегда можно наклепать необходимое количество объектов класса System.

Достаточно простой подход, по сути это привычный и понятный delay(), только чуть в ином исполнении. Подход к алгоритму кода остается прежний. Нужно лишь учитывать, что линейность выполнения кода, которое в основном и обеспечивает delay() работает лишь внутри метода класса, который использует таймер. «Снаружи», в бесконечном цикле программы, методы варятся условно в произвольном порядке, в зависимости от того чьё время на исполнение подошло. Можно даже сказать, что: как в операционной системе.

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

2.Кнопочное управление. (Аппаратная часть. Кнопки + Buzzer).

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

Схема с форума emu-land.net

 https://www.emu-land.net/forum/index.php/topic,33010.msg460649.html#msg460649
https://www.emu-land.net/forum/index.php/topic,33010.msg460649.html#msg460649

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

Либо, как в моем случае поставить перед регистром еще и триггер. Нажатая кнопка зафиксирует триггер и опрашивать регистр можно через какие-то промежутки времени, не опасаясь что нажатие не будет обработано. Недостаток — использование лишних микросхем. Из преимуществ, триггер это идеальное средство борьбы с дребезгом кнопок. В моем же случае, используется не готовая микросхема триггера, а собранная из 3х 2И-НЕ.

-4

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

Общая схема. Компиляция двух предыдущих.

-5

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

Buzzer.

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

Но есть небольшая сложность его включения. Приемлемый звук требует небольшого периода включения, десятки миллисекунд. Использовать delay() слишком расточительно, особенно когда используется динамическая графика, нажатие будет вызывать поддтормаживание картинки. Использовать вариант системного таймера описанного выше, тоже не вариант, так как время звучания довольно небольшое и другие задачи будут постоянно отодвигать окончание «мелодичного пения».

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

https://dzen.ru/a/aFlvCN2XCSSV8CU8

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

D1 простой выпрямительный, даже не Шоттки. Для увеличения громкости, его заменить на Шоттки и понизить R3.
D1 простой выпрямительный, даже не Шоттки. Для увеличения громкости, его заменить на Шоттки и понизить R3.

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

3.Обработка нажатий. (Программная часть. Class Control).

Данный пункт находится в активной форме реализации, поэтому не совсем понятно как он будет выглядеть в итоге. Можно лишь зафиксировать его текущее состояние. Используются три ключевые переменные. current_page, cursor_position и cursor_navi_state. В отличии от прошлой программы я решил что лучше пусть они будут только внутри класса. Номер текущей страницы и номер позиции курсора на текущей странице, тут всё понятно. Третья переменная, это в каком состоянии находится страница. В режиме перелистывания, и тогда стрелки влево-вправо листают страницы, а стрелки вверх-вниз пункты меню, либо была нажата управляющая кнопка напротив одно из пунктов меню и теперь стрелки вверх-вниз изменяют например значение счетчика. Повторное нажатие управляющей кнопки сохраняет значение счетчика и возвращает стрелкам способность перелистывать страницы.

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

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

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

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

Пример доступен здесь, и позже в примерах библиотеки.

Prog_2_Display_Buttons.ino — Яндекс Диск