Снова меню для LCD1602 и 2004 - прошу проверить опытных товарищей
- Войдите на сайт для отправки комментариев
Добрый день, корифеи
Попросил родственник сваять ему контроллер нагревателя, нахожусь в начальное стадии реализации проекта. Пришлось "изобрести" меню с навигацией для LCD, который будет применяться в проекте. Код можно условно считать работающим (по крайней мере симуляция в протеусе работает стабильно, ошибок не выдает, и я сам пристрастно тыкал во все что можно на экране). Но поскольку интерес для меня не столько сваять железку, сколько научиться новому, выношу свой "индусский" скетч (пока нарисовал только меню, навигацию и редактирование параметров) на суд опытных товарищей. Прошу посмотреть и подсказать: что сделано не оптимально, что ректально, а что вообще недопустимо. Ну и соответственно прошу задать направления для размышлений (если это сделано плохо, то как это делать правильно).
Что конкретно смущает меня: пытаюсь разобраться с указателями, не уверен что применяю их правильно; переменные настроек типа bool хочу упаковать в один байт (их как раз 8), но не соображу как это сделать при идеологии использования одной (универсальной) функции editParam () (сейчас на мой взгляд красиво, но не оптимально исходя из занимаемой переменными памяти). В общем буду рад любой конструктивной критике.
#include <Wire.h> #include <LiquidCrystal_I2C.h> #define BUZZER_OUT 3 // buzzer #define BUTTON_PLUS A0 // + #define BUTTON_MINUS A1 // - #define BUTTON_OK A2 // OK #define BUTTON_CANCEL A3 // CANCEL #define BUTTON_QTY 4 // количество кнопок const byte BUTTON_PIN[] PROGMEM = {BUTTON_PLUS, BUTTON_MINUS, BUTTON_OK, BUTTON_CANCEL}; // выводы, к которым подключены кнопки byte buttonState[BUTTON_QTY]; // состояние кнопок, доступные другим функциям (0 - событие отработано, 1 - короткое нажатие, 2 - длинное нажатие, 3 - удержание); #define TEMP_DEFAULT 600 // температура по умолчанию 60 градусов (/10) #define TIME_DEFAULT 60 // время по умолчанию 1 час (в минутах) volatile int setTime = TIME_DEFAULT; // таймер приготовления unsigned int setTemp = TEMP_DEFAULT; // температура готовки int EEMEM threePhase_addr; int EEMEM waterValve_addr; int EEMEM triacControl_addr; int EEMEM triacCooling_addr; int EEMEM pumpControl_addr; int EEMEM param_6_addr; int EEMEM param_7_addr; int EEMEM param_8_addr; int threePhase; int waterValve; int triacControl; int triacCooling; int pumpControl; int param_6; int param_7; int param_8; //Экран #define DISPLAY_LINES 4 // количество строк дисплея #define DISPLAY_CHARS 20 // количество символов в строке дисплея #define DISPLAY_ADDRESS 0x27 // адрес дисплея на шине I2C #define BACKLIGHT_TIMER_DELAY 30 // задержка гашения подстветки (сек) LiquidCrystal_I2C lcd(DISPLAY_ADDRESS, DISPLAY_CHARS, DISPLAY_LINES); // инициализируем библиотеку byte editChar[8] = {0x4, 0xE, 0x15, 0x4, 0x4, 0x15, 0xE, 0x4}; // создаем символ редактирования параметра volatile byte backightTimer; // переменная таймера задержки отключения дисплея //Меню #define MENU_MAX_POSITION 14 // всего пунктов в меню #define MENU_TYPE_FOLDER 0 // Тип элемента меню - меню #define MENU_TYPE_INT 1 // Тип элемента меню - целое число #define MENU_TYPE_TIME 2 // Тип элемента меню - время (HH:MM, целое число, но отображается как время) #define MENU_TYPE_TEMP 3 // Тип элемента меню - температура (целое число разделенное на 10) #define MENU_TYPE_BOOL 4 // Тип элемента меню - bool #define MENU_TYPE_APP 5 // Тип элемента меню - функция #define MENU_ACTION_UPDATE 0 #define MENU_ACTION_NEXT 1 #define MENU_ACTION_PREVIOUS 2 #define MENU_ACTION_CANCEL 3 #define MENU_ACTION_CONFIRM 4 #define MODE_MENU_NAVIGATION 1 #define MODE_EDIT_PARAM 2 struct menuStruct { const byte type; // тип элемента const byte parent; // код родителя const byte param1; // child first для MENU_TYPE_FOLDER, increment/decrement slow для MENU_TYPE_INT, MENU_TYPE_TIME, MENU_TYPE_TEMP, MENU_TYPE_BOOL const byte param2; // child last для MENU_TYPE_FOLDER increment/decrement fast для MENU_TYPE_INT, MENU_TYPE_TIME, MENU_TYPE_TEMP, MENU_TYPE_BOOL const int param3; // min_value для MENU_TYPE_INT, MENU_TYPE_TIME, MENU_TYPE_TEMP, MENU_TYPE_BOOL const int param4; // max_value для MENU_TYPE_INT, MENU_TYPE_TIME, MENU_TYPE_TEMP, MENU_TYPE_BOOL }; const menuStruct menu[MENU_MAX_POSITION] PROGMEM = { {MENU_TYPE_FOLDER, 0, 1, 5, 0, 0}, // 0 - Main menu {MENU_TYPE_TIME, 0, 10, 30, 0, 1440}, // 1 - Set time {MENU_TYPE_TEMP, 0, 10, 50, 400, 1000}, // 2 - Set temp {MENU_TYPE_APP, 0, 0, 0, 0, 0}, // 3 - Start {MENU_TYPE_APP, 0, 0, 0, 0, 0}, // 4 - Cancel {MENU_TYPE_FOLDER, 0, 6, 13, 0, 0}, // 5 - Settings {MENU_TYPE_BOOL, 5, 1, 1, 0, 1}, // 6 - Param 1 {MENU_TYPE_BOOL, 5, 1, 1, 0, 1}, // 7 - Param 2 {MENU_TYPE_BOOL, 5, 1, 1, 0, 1}, // 8 - Param 3 {MENU_TYPE_BOOL, 5, 1, 1, 0, 1}, // 9 - Param 4 {MENU_TYPE_BOOL, 5, 1, 1, 0, 1}, // 10 - Param 5 {MENU_TYPE_BOOL, 5, 1, 1, 0, 1}, // 11 - Param 6 {MENU_TYPE_BOOL, 5, 1, 1, 0, 1}, // 12 - Param 7 {MENU_TYPE_BOOL, 5, 1, 1, 0, 1}, // 13 - Param 8 }; const char name_0[] PROGMEM = "Main menu"; const char name_1[] PROGMEM = "Set time"; const char name_2[] PROGMEM = "Set temp"; const char name_3[] PROGMEM = "Start"; const char name_4[] PROGMEM = "Cancel"; const char name_5[] PROGMEM = "Settings"; const char name_6[] PROGMEM = "3 phase"; const char name_7[] PROGMEM = "Water valve"; const char name_8[] PROGMEM = "Triac control"; const char name_9[] PROGMEM = "Triac cooling"; const char name_10[] PROGMEM = "Pump control"; const char name_11[] PROGMEM = "Param 6"; const char name_12[] PROGMEM = "Param 7"; const char name_13[] PROGMEM = "Param 8"; int* menuInts[][MENU_MAX_POSITION] = { {0, &setTime, &setTemp, 0, 0, 0, &threePhase, &waterValve, &triacControl, &triacCooling, &pumpControl, ¶m_6, ¶m_7, ¶m_8}, // указатель на переменные, которые изменяют функции {0, 0, 0, 0, 0, 0, &threePhase_addr, &waterValve_addr, &triacControl_addr, &triacCooling_addr, &pumpControl_addr, ¶m_6_addr, ¶m_7_addr, ¶m_8_addr}, // указатель на адреса переменных в EEPROM }; const char* const menuNames[] PROGMEM = { name_0, name_1, name_2, name_3, name_4, name_5, name_6, name_7, name_8, name_9, name_10, name_11, name_12, name_13 // указатель на имена переменных }; byte menuActualPosition = 1; // текущее положение в меню byte menuMode = MODE_MENU_NAVIGATION; // состояние меню ISR (TIMER1_COMPA_vect) // функция вызываемая таймером-счетчиком T1 { if (backightTimer > 0) { // задержка отключения подстветки backightTimer--; } } void initTimer1() { // настраиваем таймер 1 TCCR1B = 0; TCCR1A = 0; TCCR1B |= (1 << WGM12); // Режим CTC (сброс по совпадению) TCCR1B |= (1 << CS10) | (1 << CS12); // CLK/1024 OCR1A = 15624; // Частота прерываний A будет 1 раз в сек TIMSK1 |= (1 << OCIE1A); // Разрешить прерывание по совпадению A } void setup() { Serial.begin(9600); Serial.println(F("System started")); for (byte button = 0; button < BUTTON_QTY; button++) { pinMode(pgm_read_byte(&BUTTON_PIN[button]), INPUT_PULLUP); } pinMode(BUZZER_OUT, OUTPUT); for (byte i = 6; i <= 13; i++) { int readval = eeprom_read_word(&*menuInts[1][i]); // читаем настройки из EEPROM if (readval != 0 && readval != 1) { // обрабатываем первый запуск readval = 0; } *menuInts[0][i] = readval; } initTimer1(); lcd.init(); lcd.createChar(0, editChar); // lcd.backlight(); // lcd.clear(); // lcd.print(F("SousVide ver.1.0")); // lcd.setCursor(1, 1); // lcd.print(F("System started")); // backightTimer = BACKLIGHT_TIMER_DELAY; // delay(1000); // menuDraw(0); } void beep(int lenght) { // ФУНКЦИЯ УПРАВЛЕНИЯ ДИНАМИКОМ tone(BUZZER_OUT, 3000, lenght); // воспроизводим сигнал частотой 3 кгц заданной длительностью } void menuDraw(byte value) { byte menuFirstLine = pgm_read_byte(&menu[pgm_read_byte(&menu[menuActualPosition].parent)].param1); // отбираем пунты меню для отображения на текущем уровне byte menuLastLine = pgm_read_byte(&menu[pgm_read_byte(&menu[menuActualPosition].parent)].param2); static byte displayFirstLine = menuFirstLine; // первый отображающийся на экране пукт меню if (value == MENU_ACTION_NEXT) { // обрабатываем + if (menuMode == MODE_MENU_NAVIGATION) { // если мы в режиме навигации menuActualPosition++; // переходим к следующему пункту меню if (menuActualPosition >= displayFirstLine + DISPLAY_LINES) { // прокрутка вперед displayFirstLine++; } if (menuActualPosition > menuLastLine) { // обрабатываем переход выше максимального значения menuActualPosition = menuFirstLine; displayFirstLine = menuFirstLine; } } } if (value == MENU_ACTION_PREVIOUS) { // обрабатываем декремент if (menuMode == MODE_MENU_NAVIGATION) { // если мы в режиме навигации menuActualPosition--; // возвращаемся к предыдущему пункту меню if (menuActualPosition < displayFirstLine) { // прокрутка назад displayFirstLine--; } if (menuActualPosition < menuFirstLine) { // обрабатываем переход ниже минимального значения menuActualPosition = menuLastLine; displayFirstLine = menuLastLine - DISPLAY_LINES + 1; } } } if (value == MENU_ACTION_CONFIRM) { // обрабатываем OK if (pgm_read_byte(&menu[menuActualPosition].type) == MENU_TYPE_FOLDER) { // если пункт меню = папка menuActualPosition = pgm_read_byte(&menu[menuActualPosition].param1); // переходим к первому дочернему пункту меню displayFirstLine = menuActualPosition; } else if (pgm_read_byte(&menu[menuActualPosition].type) != MENU_TYPE_FOLDER && pgm_read_byte(&menu[menuActualPosition].type) != MENU_TYPE_APP) { // если пукт меню не папка и не приложение editParam(101); // запускаем редактирование параметра } } if (value == MENU_ACTION_CANCEL) { // обрабатываем CANCEL if (menuMode == MODE_MENU_NAVIGATION) { // если мы в режиме навигации if (pgm_read_byte(&menu[menuActualPosition].parent) > 0) { // если у пункта меню есть родитель menuActualPosition = pgm_read_byte(&menu[menuActualPosition].parent); // возврат к родителю displayFirstLine = menuActualPosition - DISPLAY_LINES + 1; } } } for (byte i = 0; i < DISPLAY_LINES; i++) { String displayString; // тут формируем строку для вывода на экран if (i + displayFirstLine == menuActualPosition && menuMode == MODE_MENU_NAVIGATION) { // рисуем указатель для активного пункта меню в режиме навигации displayString = String("\x7E"); } else { displayString = String(" "); } char arrayBuf[DISPLAY_CHARS]; // создаём для считывания наименования из Progmem strcpy_P(arrayBuf, pgm_read_byte(&(menuNames[i + displayFirstLine]))); // считываем наименование из Progmem displayString += String(arrayBuf); // добавляем название пункта меню if (pgm_read_byte(&menu[i + displayFirstLine].type) == MENU_TYPE_TIME) { // Тип элемента меню - время (HH:MM, целое число, но отображается как время) byte displayHours = *menuInts[0][i + displayFirstLine] / 60; // раскладываем время на часы byte displayMins = *menuInts[0][i + displayFirstLine] % 60; // и минуты byte timeLenght; // считаем количество символов во времени if (displayHours < 10) { timeLenght = 4; } else { timeLenght = 5; } byte totalSpaces = DISPLAY_CHARS - displayString.length() - timeLenght; // считаем требуемое кол-во пробелов для выравнимания строки по ширине экрана for (byte i = 0; i < totalSpaces; i++) { // добавляем требуемое количество пробелов после названия displayString += String(" "); } displayString += (String(displayHours) + String(":")); // добавляем количество часов и разделитель if (displayMins == 0) { // добавляем количество минут displayString += (String(displayMins) + String("0")); } else if (displayMins > 0 && displayMins < 10 ) { displayString += (String("0") + String(displayMins)); } else { displayString += String(displayMins); } } if (pgm_read_byte(&menu[i + displayFirstLine].type) == MENU_TYPE_TEMP) { // Тип элемента меню - темература (XXX°С, целое число) byte displayTemp = *menuInts[0][i + displayFirstLine] / 10; // считаем температуру для отображения на экране byte tempLenght; // считаем количество символов в температуре для отображения на экране if (displayTemp < 10) { tempLenght = 3; } else if (displayTemp >= 10 && displayTemp < 100) { tempLenght = 4; } else { tempLenght = 5; } byte totalSpaces = DISPLAY_CHARS - displayString.length() - tempLenght; // считаем требуемое кол-во пробелов для выравнимания строки по ширине экрана for (byte i = 0; i < totalSpaces; i++) { // добавляем требуемое количество пробелов после названия displayString += String(" "); } displayString += (String(displayTemp) + String("\xDF") + String("C")); // добавляем температуру, знак градуса и символ температуры } if (pgm_read_byte(&menu[i + displayFirstLine].type) == MENU_TYPE_BOOL) { // Тип элемента меню - bool byte boollenght; String boolstring; if (*menuInts[0][i + displayFirstLine]) { boollenght = 3; boolstring = "YES"; } else { boollenght = 2; boolstring = "NO"; } byte totalSpaces = DISPLAY_CHARS - displayString.length() - boollenght; // считаем требуемое кол-во пробелов для выравнимания строки по ширине экрана for (byte i = 0; i < totalSpaces; i++) { // добавляем требуемое количество пробелов после названия displayString += String(" "); } displayString += String(boolstring); // добавляем YES или NO } if (i == 0 ) { // если выводим первую строку lcd.clear(); // очищаем экран } lcd.setCursor(0, i); // ставим курсор в начало строки lcd.print(displayString); if (i + displayFirstLine == menuActualPosition && menuMode != MODE_MENU_NAVIGATION) { // рисуем указатель для редактируемого пункта меню lcd.setCursor(0, i); lcd.write((byte)0); } } if (backightTimer <= 1) { //если подсветка не включена lcd.backlight(); // включаем подсветку } backightTimer = BACKLIGHT_TIMER_DELAY; // перезапускаем тамймер отключения подсветки } void editParam (int value) { // Serial.print(F("editParam - ")); // Serial.println(value); /* 101 - вход в режим редактирования параметра и обновление экрана 201 - выход из режима редактирования параметра с сохранением изменения 301 - выход из режима редактирования параметра с отменой сделанных изменений */ static int previousParamValue = 0; // тут будем запоминать состояние переменной для отмены сделанных изменений if (value == 101) { // Переход в режим настройки menuMode = MODE_EDIT_PARAM; // ставим флаг редактирования параметров } else if (value == 201) { // OK - выход из режима настройки previousParamValue = 0; // сбрасываем переменную Serial.print("address - "); Serial.print(*menuInts[1][menuActualPosition]); Serial.print(", data - "); Serial.println(*menuInts[0][menuActualPosition]); eeprom_update_word(&*menuInts[1][menuActualPosition], *menuInts[0][menuActualPosition]); menuMode = MODE_MENU_NAVIGATION; // сбрасываем флаг редактирования параметров } else if (value == 301) { // CANCEL - выход с отменой сделанных изменений *menuInts[0][menuActualPosition] = previousParamValue; // отменяем сделанные изменения previousParamValue = 0; // сбрасываем переменную menuMode = MODE_MENU_NAVIGATION; // возвращаемся в меню } else { if (!previousParamValue) { // если переменная пустая previousParamValue = *menuInts[0][menuActualPosition]; // запомнили значение переменной до редактирования параметра } *menuInts[0][menuActualPosition] += value; // увеличили/уменьшили состояние переменной if (*menuInts[0][menuActualPosition] > (int)pgm_read_word(&menu[menuActualPosition].param4)) { *menuInts[0][menuActualPosition] = pgm_read_word(&menu[menuActualPosition].param3); } if (*menuInts[0][menuActualPosition] < (int)pgm_read_word(&menu[menuActualPosition].param3)) { *menuInts[0][menuActualPosition] = pgm_read_word(&menu[menuActualPosition].param4); } } menuDraw(0); // обновляем картинку } bool buttonRead () { // функция чтения кнопок #define BUTTON_DEBOUNCE 100 // антидребезг #define BUTTON_LONG_PRESS 2000 // время длинного нажатия кнопки byte returnvalue = 0; // флаг нажатия кнопки static bool _buttonINTstate[BUTTON_QTY]; // состояние кнопки (нажата, не нажата) static bool _buttonTMPstate[BUTTON_QTY]; // временная переменная (нажата, не нажата - пока не определись дребезг или нажатие) static bool _buttonLongPress[BUTTON_QTY]; // флаг длинного нажатия кнопки static unsigned long _buttonChangeStateTime[BUTTON_QTY]; // время изменения состояния кнопки unsigned long actualTime = millis(); // запоминаем время вызова функции for (byte buttonNumber = 0; buttonNumber < BUTTON_QTY; buttonNumber++) { bool buttonNewState = !digitalRead(pgm_read_byte(&BUTTON_PIN[buttonNumber])); // читаем состояние кнопки if (_buttonTMPstate[buttonNumber] != buttonNewState) { // если состояние кнопки изменилось _buttonChangeStateTime[buttonNumber] = actualTime; // запомнили время изменения состояния _buttonTMPstate[buttonNumber] = buttonNewState; // и запомнили новое состояние кнопки во временной переменной (вдруг дребезг) } if (!_buttonINTstate[buttonNumber] && buttonNewState && (actualTime - _buttonChangeStateTime[buttonNumber] > BUTTON_DEBOUNCE)) {// если кнопка не была нажата ранее, но осталась нажата в течение времени больше чем дребезг _buttonINTstate[buttonNumber] = buttonNewState; // ставим флаг нажатия кнопки buttonState[buttonNumber] = 1; // ставим флаг короткого нажатия кнопки для внешней обработки returnvalue = 1; // событие } if (!_buttonLongPress[buttonNumber] && _buttonINTstate[buttonNumber] && buttonNewState && (actualTime - _buttonChangeStateTime[buttonNumber] > BUTTON_LONG_PRESS)) { _buttonLongPress[buttonNumber] = buttonNewState; // ставим флаг нажатия кнопки buttonState[buttonNumber] = 2; // ставим флаг длинного нажатия кнопки для внешней обработки returnvalue = 1; // событие } if (buttonLongPress[buttonNumber] && _buttonINTstate[buttonNumber] && buttonNewState && (actualTime - _buttonChangeStateTime[buttonNumber] > BUTTON_LONG_PRESS + 100)) { // _buttonLongPress[buttonNumber] = buttonNewState; // ставим флаг нажатия кнопки buttonState[buttonNumber] = 3; // ставим флаг длинного нажатия кнопки для внешней обработки returnvalue = 1; // событие } if (_buttonINTstate[buttonNumber] && !buttonNewState && (actualTime - _buttonChangeStateTime[buttonNumber] > BUTTON_DEBOUNCE)) { _buttonINTstate[buttonNumber] = buttonNewState; _buttonLongPress[buttonNumber] = buttonNewState; if (buttonState[buttonNumber] == 3) { buttonState[buttonNumber] = 0; returnvalue = 1; // событие } } } return returnvalue; } void buttonShortPress(byte buttonNumber) { beep(50); // пикаем коротко switch (buttonNumber) { case 0: // кнопка "+" if (menuMode == MODE_MENU_NAVIGATION) { // если бродим по меню menuDraw(MENU_ACTION_PREVIOUS); // переход к следуюшему пункту } else if (menuMode == MODE_EDIT_PARAM) { // если настраиваем параметр editParam(pgm_read_byte(&menu[menuActualPosition].param1)); // увеличиваем значение на один шаг } break; case 1: // кнопка "-" if (menuMode == MODE_MENU_NAVIGATION) { // если бродим по меню menuDraw(MENU_ACTION_NEXT); // переход к предыдущему пункту } else if (menuMode == MODE_EDIT_PARAM) { // если настраиваем параметр editParam(-pgm_read_byte(&menu[menuActualPosition].param1)); // уменьшаем значение на один шаг } break; case 2: // кнопка "OK" if (menuMode == MODE_MENU_NAVIGATION) { // если бродим по меню menuDraw(MENU_ACTION_CONFIRM); // входим в пункт меню } else if (menuMode == MODE_EDIT_PARAM) { // если настраиваем параметр editParam(201); // сохраняем сделанные изменения } break; case 3: // кнопка "CANCEL" if (menuMode == MODE_MENU_NAVIGATION) { // если бродим по меню menuDraw(MENU_ACTION_CANCEL); // возвращаемся к предыдущему пункту } else if (menuMode == MODE_EDIT_PARAM) { // если настраиваем параметр editParam(301); // отменяем сделанные изменения } break; } buttonState[buttonNumber] = 0; } void buttonLongPress(byte buttonNumber) { // beep(300); // пикаем длинно buttonState[buttonNumber] = 0; } void buttonHold(byte buttonNumber) { #define COUNT_DELAY 1000 // задержка между счетом static unsigned long changeTime; // время изменения меременной unsigned long actualTime = millis(); // запоминаем время вызова функции if (actualTime - changeTime > COUNT_DELAY) { // если задержка вышла beep(10); // пикаем коротко switch (buttonNumber) { case 0: // кнопка "+" if (menuMode == MODE_EDIT_PARAM) { // если настраиваем параметр editParam(pgm_read_byte(&menu[menuActualPosition].param2)); // увеличиваем значение на один быстрый шаг } break; case 1: // кнопка "-" if (menuMode == MODE_EDIT_PARAM) { // если настраиваем параметр editParam(-pgm_read_byte(&menu[menuActualPosition].param1)); // уменьшаем значение на быстрыйодин шаг } break; } changeTime = actualTime; } } void loop() { if (buttonRead()) { // если было событие нажатия кнопки for (byte buttonNumber = 0; buttonNumber < BUTTON_QTY; buttonNumber++) { if (buttonState[buttonNumber] == 1) { // если была коротко нажата кнопка buttonShortPress(buttonNumber); // запускаем обработчик нажатия кнопок } if (buttonState[buttonNumber] == 2) { buttonLongPress(buttonNumber); // запускаем обработчик нажатия кнопок } if (buttonState[buttonNumber] == 3) { buttonHold(buttonNumber); // запускаем обработчик нажатия кнопок } } } if (backightTimer == 1) { lcd.noBacklight(); } }
Классно. Ачо "кнопки" в одну функцию не свёл? И, скока занимает памяти скетч?
Иногда использую аналоговые кнопки, тогда просто меняю функцию считывания на другую, а обработка событий (вынесенные в отдельную), остаются неизменными
переменные настроек типа bool хочу упаковать в один байт (их как раз 8)
Я тут когда-то уже выкладывал маленькую библиотечку для массива из восьми флагов в одном байте (можно и для большего количества флагов в соответствующем количестве байтов если надо - у меня есть).
Библиотечка вот такая (файл называется "BoolArray.h")
Вот пример/тест использования (в примере для упрощения печати используется библиотека Printing)
Евгений, добрый день. Рад что Вы присоединились. Посмотрите пожалуйста на этот кусок:
У меня ощущение что я с EEMEMом и указателями сильно перемудрил (хотя и работает)?
Ну, двухмерный массив указателей - похоже на перемудрение, но я же не знаю, что Вы хотели сделать.
Первая строка - указатель на переменную, а вторая стока - указатель на адрес этой же переменной в eeprom (как я писал выше, с указателями я пока на "вы", да и с eemem/progmem тоже). Вот тоже чувствую что это масло масляное, как и вот это:
Убрал вторую строку из массива указателей (действительно она там нафиг не нужна), убрал кучу int переменных, использовавшихся ранее для хранения bool значений, упаковал bool настройки в один байт:
Результат - Скетч использует 10872 байт (35%) памяти устройства. Всего доступно 30720 байт. Глобальные переменные используют 550 байт (26%) динамической памяти, оставляя 1498 байт для локальных переменных. Максимум: 2048 байт. Вроде и не космическая экономия, но на душе тепло :)