Сделаем простенькое меню для Arduino и LCD Keyboard шилда. Которое будет в дальнейшем управлять параметрами настройки и сохранять их.
Как выглядит готовый результат:
Меню у меня просто плоское, без вложенных подменю, мне кажется так проще и понятнее. У каждого наименования параметра есть код, который поможет описать его в инструкциях или документации. Для начала у меня только параметры настройки даты и времени, которые будут сохраняться в модуль реального времени, при нажатии "save & exit". Код получился простой и я надеюсь понятный.
Итак, готовая прошивка для простого меню с комментариями:
#include <LiquidCrystal.h>
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);
char str_format[16]; //Временная переменная для sprintf
unsigned long Uptime; // Переменная хранит время работы в ms
// Это все параметры которые можем менять в меню
int Null = 0; // Этот параметр как заглушка, если менять нечего, костыль конечно ...
int DateDay = 1;
int DateMonth = 1;
int DateYear = 17;
int TimeHour = 0;
int TimeMin = 0;
int TimeSecond = 0;
// Переменные для кнопок
int const ButtonInterval = 200; // Интервал срабатывания кнопки при удержании
int ButtonPress = 0; // Код нажатой кнопки, или 0 если не нажата
unsigned long ButtonPressTime = 0; // Время на устройстве в которое была нажата кнопка
// Тип структуры данных описывающих пункт меню
typedef struct _MenuItem {
String title; // Имя меню
void(*on_click)(int); // Ссылка на функцию вызова обработки изменения значения, параметр +1 или -1)
int ¶m; // Ссылка на переменную значения
char format[16]; // Формат вывода значения переменной
} MenuItem;
// Меню, первый пункт это не параметр, а просто выход с сохранением
// Название пункта меню / Функция обработки нажатия / Переменная значения / Формат вывода
MenuItem MenuItems[] = {
{ "SAVE & EXIT ", MenuSaveRam, Null, " Press [+]" },
{ "D01: Date Day ", SettingDateDay, DateDay, "VAL: %02d" },
{ "D02: Date Mount ", SettingDateMonth, DateMonth, "VAL: %02d" },
{ "D03: Date Year ", SettingDateYear, DateYear, "VAL: 20%02d" },
{ "D04: Time Hour ", SettingTimeHour, TimeHour, "VAL: %02d" },
{ "D05: Time Min ", SettingTimeMin, TimeMin, "VAL: %02d" }
};
// Это переменные для работы меню
int MenuCurrent = 0; // Выбранный пункт меню
bool MenuEnter = false; // Мы находимся в меню?
int MenuCount = 6; // Количество пунктов меню
// Загрузка
void setup() {
lcd.begin(16, 2); // Инициализируем дисплей
}
// Главный цикл
void loop() {
Uptime = millis(); // Сохраняем время работы каждый цикл
if (!MenuEnter) { // Если мы не в меню то выводим всякую фигню =)
lcd.setCursor(0, 0);
lcd.print("DespVK rev.01.01"); // Версии Железа и Прошивки
lcd.setCursor(0, 1);
sprintf(str_format, "UT: %7lu s. ", Uptime / 1000); // Форматируем строку %lu это вывод unsigned long
lcd.print(str_format); // Время работы в секундах
};
if (ButtonPress == 0) { // Если кнопки не были нажаты ранее
int ButtonPinValue = analogRead(0); // Проверяем значение, не нажата ли кнопка
if (ButtonPinValue < 60) ButtonPress = 4; // Нажата [+]
else if (ButtonPinValue < 200) ButtonPress = 2; // Нажата [Prev]
else if (ButtonPinValue < 400) ButtonPress = 3; // Нажата [Next]
else if (ButtonPinValue < 600) ButtonPress = 5; // Нажата [-]
else if (ButtonPinValue < 800) ButtonPress = 1; // Нажата [Menu]
} else { // Кнопка была нажата ранее
if (ButtonPressTime == 0) { // Если не замеряли интервал нажатия кнопки
ButtonPressTime = Uptime; // Засекаем когда была нажата кнопка
ButtonClick(ButtonPress); // Вызывается функция обработки нажатия на кнопку
}
if (ButtonPressTime + ButtonInterval < Uptime) { // Если кнопка была нажата раньше чем ButtonInterval ms назад
ButtonPressTime = 0; // Сбрасываем время
ButtonPress = 0; // Отжимаем кнопку (это имитуреет многократное нажатие с интервалом ButtonInterval если кнопку держать)
}
}
}
// Обработка нажатия кнопки
void ButtonClick(int ButtonId) {
if (MenuEnter) { // Если мы в меню
if (ButtonId == 1) MenuEnter = false; // Клик [Menu] Выход из меню
if (ButtonId == 2) MenuCurrent--; // Клик [Prev] Позицию ниже
if (ButtonId == 3) MenuCurrent++; // Клик [Next] Позиция выше
MenuCurrent = constrain(MenuCurrent, 0, MenuCount - 1); // Ограничиваем меню
if (ButtonId == 4) MenuItems[MenuCurrent].on_click(1); // Клик [+] Увеличиваем значение параметра
if (ButtonId == 5) MenuItems[MenuCurrent].on_click(-1); // Клик [-] Уменьшаем значение параметра
} else {
if (ButtonId == 1) MenuEnter = true; // Вход в меню
}
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(MenuItems[MenuCurrent].title); // Выводим заголовок
lcd.setCursor(0, 1);
sprintf(str_format, MenuItems[MenuCurrent].format, MenuItems[MenuCurrent].param); // Форматируем вывод значения
lcd.print(str_format); // Выводим значение
}
// Функции вызываемые для изменения значений переменных
void MenuSaveRam(int Concat) { // SAVE & EXIT
if (Concat == 1) { //TODO Сохранение параметров в RAM };
MenuEnter = false; // Выход из меню
}
}
void SettingDateDay(int Concat) {
DateDay = constrain(DateDay + Concat, 1, 31); // Изменяем с ограничением
}
void SettingDateMonth(int Concat) {
DateMonth = constrain(DateMonth + Concat, 1, 12);
}
void SettingDateYear(int Concat) {
DateYear = constrain(DateYear + Concat, 0, 99);
}
void SettingTimeHour(int Concat) {
TimeHour = constrain(TimeHour + Concat, 0, 23);
}
void SettingTimeMin(int Concat) {
TimeMin = constrain(TimeMin + Concat, 0, 59);
}
Надеюсь, этот код кому-то сэкономит время =)