Простое меню

Дим-мычъ
Offline
Зарегистрирован: 20.03.2021

Всем привет! Продолжаю знакомиться с Ардуино и языком Си на примере опроса датчика DS18B20 и симуляции в Proteus. С числами и протоколом 1-wire разобрался, всё норм. В завершении этой темы, как и планировал, решил сделать простое меню, что-бы, так сказать, "углубить" и "расширить"))) Всё получилось, всё работает, но получилось довольно-таки громоздко.

"Простое меню" оказалось не очень-то простым для меня. Программа чтения из датчика с индикацией занимала 7% памяти устройства и 4% динамической памяти. С добавлением меню (плюс немного коррекция и контроль пороговых значений) уже 15% и 7% соответственно. Т.е по объёму прога выросла в два раза(!) Это нормально для такого простого меню?

Если есть те, кому не лень глянуть на мои "старания", то прошу конструктивной критики. Заранее извиняюсь за возможные перерывы в ответах

Возможности меню скромные:

1.Коррекция значений температуры в пределах -0.9С...+0.9С

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

Управление - одной кнопкой:

1. Долгое удержание, более 6 сек - вход в меню

2. Короткое нажатие - переключение позиции / фиксация значений

3. Долгое нажатие, более 3 сек - смена значений меню

Чётное - единиц, нечётное - десятых .





// программа для чтения из датчика ds18b20
// значений температуры от -55С до +125С
// с разрешением  0.0625
// с возможностью использования в качестве термостата
// для поддержания темп-ры
// в пределах от 0.0С до 99.9С,
// коррекцией +- 0.9С, выводе данных текущей темп-ры
// (до десятых вкл.)каждые 8сек, и данных
// меню на 8-ми разрядный 7-сегментный индикатор с общим
// катодом и инвертирующим буфером (транзистором)
// УПРОЩЁННАЯ ВЕРСИЯ - ТОЛЬКО МЕНЮ + ИНДИКАЦИЯ с выводом
// данных меню в терминал
// Плата  Arduino Pro Mini
// Внутренний генератор 8 Мгц
// щщщщщщщщщщщщщщщщщщщщщщщщщщщщщщщщщ
const byte KN = 10; //пин кнопки меню.
const byte DATASEG[] = {18, 19, 2, 3, 4, 5, 6, 7}; // пины для вывода
// данных на  7 сегментов и точку
const byte RAZR[] = {A0, A1, A2,  13, 12, 11, 9, A3}; // пины для
// выбора разряда индикатора
const byte WIRE = 8; //  пин шины 1-wire
const byte RELE = 18; // управляющий выход на реле
// (временно занят (см. DATASEG[0])
// для подключения терминала к пинам 0, 1  RX,TX)
// если терминал не нужен, данные можно подавать
// сразу в порт: PORTD = CYFRY[a[i]];
const byte CIFRY[] = {
  0b00111111,//0
  0b00000110,//1
  0b01011011,//2
  0b01001111,//3
  0b01100110,//4
  0b01101101,//5
  0b01111101,//6
  0b00000111,//7
  0b01111111, //8
  0b01101111, //9
  0b01000000, // "-"(10)
  0b00000000, // маска(11)
  0b00111001, // "С" (12)если пишем
  0b01110110, // "Х" (13) "СОХР"
  0b01110011, //  "Р" (14)
  0b01011100, //  "o" (15)
  0b01010100, //  "n" (16)
  0b01110001, //  "F" (17)
  0b01100011,  //символ градуса(18)
  0b01110111,  //  "A"(19)
  0b01011110,   //  "d"(20)
  0b01101101,  //  "S"(21) если пишем
  0b00111110,  //  "U"(22) "SAVE"
  0b01111001,  //  "E"(23)
  0b00110111,  //  "П" (24)
  0b00111000  //   "L" (25)
};                            //  цифры и символы на индикатор

byte a[] = {0, 0, 0, 0, 0, 0, 0, 0}; // байты для разрядов индикатора
// расположение разрядов не индикаторе:
//         a[7] a[0] a[1] a[2] a[3] a[4] a[5] a[6}
/* РАСКОММЕНТИРОВАТЬЭТУ ЧАСТЬ ДЛЯ ПОЛНОЙ ВЕРСИИ ПРОГРАММЫ
  byte temp_tx[] = {0xCC, 0x44, 0xCC, 0xBE}; // последовательность
  // команд к датчику:
  // пропуск команды поиск ROM, конвертировать температуру ,
  // пропуск команды поиск ROM, чтение памяти.
  byte temp_rx[] = {0, 0, 0, 0, 0, 0, 0, 0, 0}; // массив для записи
  // результата чтения из датчика
  byte temp_0 = 0;     // байт для промежуточного хранения данных при чтении
  byte low_byte = 0;   // младший байт значения температуры
  byte high_byte = 0;  // старший байт  значения температуры
  byte w = 0;           // переменная для определения очерёдности команд к датчику
*/
//--- ПЕРЕМЕННЫЕ ДЛЯ МЕНЮ -----------
byte b[14] = {11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; // байты для
// временного  хранения значений меню.
// пользовательские данные хранятся в двоично-десятичном виде
// b[0] - знак коррекции(10-минус или 11-маска)
//b[1]-b[2] - байты для хранения значений коррекции
//b[3]-b[5] - нижнее пороговое значение темп-ры
//b[6]-b[8] - верхнее пороговое значение темп-ры

//b[9]-b[13] - значения счётчиков меню

byte Nmenu = 0; // байт для хранения номера позиции(пункта) меню
byte c_corr1 = 0; // счётчик длительности чётного и нечётного
//долгого нажатия кнопки 1 поз.
byte c_corr2 = 0; // счётчик дл-ти чётного долгого нажатия кнопки 2 поз.
byte c_corr3 = 0; // счётчик дл-ти нечётного долгого нажатия кнопки 2 поз.
byte c_corr4 = 0; // счётчик дл-ти чётного долгого нажатия кнопки 3 поз.
byte c_corr5 = 0; // счётчик дл-ти нечётного долгого нажатия кнопки 3 поз.

byte s = 0;         // байт счёта коротких нажатий кнопки меню
byte tdreb = 0;      // байт циклов антидребезга при отпускании кнопки в меню
byte Nkn;           // байт числа долгих нажатий кнопки меню
byte a_temp[] = {0, 0, 0}; // байты временного хранения для мигания
// разрядов в меню
int mig = 0;        //счётчик циклов для мигания в меню
byte mig1 = 0;        //счётчик количества миганий в меню
byte rdeepr_cycl;  // счётчик интервала чтения из eeprom
byte tik_go1 = 0;  // счётчик антидребезга при входе в меню

bool KNflgS = false; // флаг короткого нажатия кн.
bool KNflgNEW = false; // флаг нового нажатия кн.
bool KNflgL = false; // флаг долгого нажатия кн.
bool MENu = false;   // флаг разрешающий вход в меню
bool long_kn = false; //флаг чётного/нечётного долгого нажатия
bool MIGfl = false;   //флаг мигания в меню
bool go = true; // флаг разрешения начального входа в меню
bool out = false; // флаг выхода из меню
bool save = false; // флаг сохранения данных меню
bool null_0 = false; // флаг обнуления данных меню
unsigned int tik = 0;  // счётчик циклов для антидребезга и
// определения длительности нажатия кнопки меню
unsigned int tik_go = 0; //счётчик циклов для началного входа в меню
//------------

unsigned int synchr = 0;// общий счётчик синхронизации событий программы
byte k = 0;          // счётчик для schynhr
bool tochka = true;   // флаг разрешения индикации точки
bool flg = false;     // флаг обновления schynhr
bool hot = false;     // флаг понижения/повышения темп-ры

unsigned long last0_millis = 0; // переменная для таймера динамической
// индикации, опроса кнопки и счётчика synchr
unsigned long KNschet = 0; // переменная для таймера долгого нажатия кнопки
//-------------

// объявляем функции:
void menu();            // меню коррекции темп-ры и ввода значения термостата
void wr_eepr();         // запись в eeprom
void read_eepr();       // чтение из eeprom
void vhod();            // вход в меню по долгому нажатию кнопки(более 6сек)

//---------------------


void setup() {
  pinMode(KN, INPUT); // пин кнопки на вход
  // pinMode(RELE, OUTPUT); //
  //  digitalWrite(RELE, LOW);//настройка пина для управления реле
  // high();                   // настройка шины 1-wire /временно отключено/
  for (int i = 0; i < 8; i++) {
    pinMode(DATASEG[i], OUTPUT);
  }
  for (int i = 0; i < 8; i++) {
    pinMode(RAZR[i], OUTPUT); // настройка выходов динамической индикации
  }
  delay(300); // задержка для нормального запуска датчика

  //==============================
  Serial.begin(115200);
  read_eepr(); // читаем данные меню из eeprom
}

void loop() {

  //======================================================
  //  интервал 4мс для обновления
  // динамической индикации, счётчика synchr, и
  // опроса кнопки
  if ((millis() - last0_millis) >= 4) {
    last0_millis = millis();

    //---- каждые 4мс выполняем цикл loop() ---

    //------- опрос кнопки ---------
    if ((digitalRead(KN) == true) || MENu) {//если нажата кнопка или уже в меню
      vhod(); // первый вход в меню. если уже вошли - пропускаем(внутри функции)
      menu();                     // входим в меню
    }                             // если кнопка отпущена и сброшен флаг меню -
    // - в меню не входим
    //------- исключаем ложный вход в меню -----------
    if ((digitalRead(KN) == false) && !MENu) {//
      tik_go1++;          // антидребезг при отпускании кнопки
      if (tik_go1 >= 2) { //одинокая помеха - ещё не повод для сброса счётчика входа
        tik_go = 0;      // фиксация отпускания кнопки и сброс счётчика входа
        // при недостаточно долгих нажатиях для входа в меню
        tik_go1 = 0;
      }
    }
    //============
    // организация мигания разрядов,
    //доступных для изменения в меню, 1-я часть, перед иникацией

    if ((s == 1 || s == 3 || s == 5) && (!KNflgL) && MIGfl) {
      //если находимся в позиции меню,где используется мигание
      mig++;   // и не происходит долг. нажатие , запускаем счётчик циклов
      if (mig < 80) { // если не прошло 80 циклов(80х4=320мс),
        // (это время ,на которое гасим индикацию ) сохраняем
        a_temp[0] = a[1]; // текущие значения индикации
        a_temp[1] = a[2]; // для замены их на маску (11)
        a_temp[2] = a[3]; //
        //
        if (s == 1) { // в первой позиции меню всегда мигает
          //только разряд десятых(а[3])
          a[3] = 11; // маскируем разряд
        }
        if ((s == 3) || (s == 5)) { // во второй и третьей позиции меню

          if (( Nkn % 2 ) == 0) { // нечётное завершённое долгое нажатие
            a[2] = 11;            // мигают единицы и десятки
            a[1] = 11;
          }
          else {
            a[3] = 11;    // чётное нажатие, мигают десятые
          }
        }
      }
    }
    //щщщщщщщщщщщщщщщщщщщщщщщщщщщщщщщщщщ

    // счётчик synchr и динамическая индикация

    digitalWrite(RAZR[k], LOW); // гасим разряд для динамической индикации
    PORTD = PORTD & 0b01111111; // гасим точку

    k++; // Здесь спасибо, счётчик synchr с кодом Alexey_Rem помогал
    if (k > 7) { //  каждые 8 циклов
      k = 0;     // 8х4=32ms  счётчик синхронизации всех
      synchr++; // +32ms  частей программы
      flg = true;   //флаг обновения synchr
      // разрешающий выпонение некоторых блоков программы
    }

    // вывод данных на индикатор:

    digitalWrite(RAZR[k], HIGH); // выбор разряда

    int chis = CIFRY[a[k]];   //сохраняем число для индикатора для тек.разряда
    for (int j = 0; j < 8; j++) { // выводим 8 сегментов исходя из текущего
      if ((chis & (1 << j)) == 0) { // числа для разряда
        digitalWrite(DATASEG[j], LOW); //
      }
      else {
        digitalWrite(DATASEG[j], HIGH);
      }
    }
    if ((k == 2) && (tochka == true))// если текущий разряд 2-ой и нет запрета
      PORTD = PORTD | 0b10000000; // зажигаем точку
    //щщщщщщщщщщщщщщщщщщщщщщщщщщщщщщщщщ

    // мигание в меню 2-я часть, после индикации
    if ((s == 1 || s == 3 || s == 5) && (!KNflgL) && MIGfl) { //
      if (mig < 80) {                // если прошли циклы
        // маскировки разрядов
        a[1] = a_temp[0]; // возвращаем значение разрядов
        a[2] = a_temp[1]; // после мигания в меню
        a[3] = a_temp[2];
      }
    }
    if (mig > 400) { //400х4=1600мс сбрасываем счётчик циклов одного мигания
      mig1++;  // и считаем количество миганий
      mig = 0;
    }

    if (!KNflgL) { // если кнопка не удерживается
      if (mig1 > 5) {  // число миганий в одной позиции
        long_kn = !long_kn; // смена доступных для изменения разр-ов
        Nkn += 1;   // смена позиции мигания
        mig1 = 0;   //обнуляем счётчик
      }
    }

    //========================================================

    switch (synchr) {

      case 218:     // 218х32 = 6976мс

        //----- ВЫХОД ИЗ МЕНЮ по флагу out ----------

        if (out) { // если поднят флаг выхода из меню
          MENu = false; // выходим из меню
          go = true; // разрешаем новый вход в меню
          out = false; // сбрасываем флаг выхода
          s = 0; // сбрасываем счётчик позиций меню
          if (save) { //по флагу, сохраняем
            wr_eepr(); // новые или восстанавливаем
          }
          else { // старые данные меню из
            read_eepr();// eeprom
          }
          if (null_0) { // по флагу
            for (byte r = 0; r < 14; r++) { // обнуляем  данные
              b[r] = 0; // меню
              c_corr1 = 0;
              c_corr2 = 0;
              c_corr3 = 0; // обнуляем счётчики меню
              c_corr4 = 0;
              c_corr5 = 0;
              wr_eepr();
              null_0 = false;
            }
          }
          tochka = false;
        }
        //--------------------------------
        // если не установлен флаг пребывания в меню
        // выполняется основная часть программы
        // распределение времени для
        // общения с датчиком 1раз в 8сек, преобразование чисел, подсчёт crc

        if (flg && (!MENu)) { // если не в меню
          // и поднят флаг обновления shyncr
          // здесь передача команд датчику
          flg = false;
          break;
        }

      case  250: //  250х32 = 8000 ожидание преобразования и выполнение
        // второй части : чтение ,счёт ,crc_8
        if (flg && (!MENu)) { // если не в меню
          synchr = 0;
          // здесь получение данных из датчика
          // преобразование чисел
          // проверка crc
          // контроль пороговых значений темп-ры
          // в упр. версии  просто выводим
          a[7] = 11; // -0.7C
          a[0] = 11;
          a[1] = 10;
          a[2] = 0;
          a[3] = 7;
          a[4] = 11;
          a[5] = 18;
          a[6] = 12;
          // выводим пользовательские данные меню в терминал
          Serial.println(b[0]);
          Serial.println(b[1]);
          Serial.println(b[2]);
          Serial.println(b[3]);
          Serial.println(b[4]);
          Serial.println(b[5]);
          Serial.println(b[6]);
          Serial.println(b[7]);
          Serial.println(b[8]);

          rdeepr_cycl++;
          if (rdeepr_cycl > 150) { // обновляем данные из eeprom
            read_eepr(); // каждые 150 измерений
            rdeepr_cycl = 0; // число с "потолка", просто "бережём"
          } // eeprom)))
          tochka = true;
          flg = false;
          break;
        }
    }
  }
}

//====== ВХОД В МЕНЮ ===========
void vhod() { //
  if ((!MENu) && go) { //если ещё не в меню, но поднят флаг
    tik_go++; // разрешающий вход, начинаем счёт
    if (( tik_go >= 1500) && (digitalRead(KN))) { //1500х4=6000 после удержании
      tik_go = 0; // кнопки более 6ceк сброс флага, разрешающего вход
      go = false;
      KNflgNEW = true; //  фиксируем факт нового нажатия кнопки
      // ( для того, чтобы можно было фиксировать факт отпускания кнопки )))
      MENu = true; // и будем входить в меню (после отпускания кнопки)
      a[7] = 11; //до отпускания кнопки,когда уже возможен вход - выводим нули
      a[0] = 11; // можно заморочится и написать "UHOD"
      a[1] = 0;  // entry не получается)))
      a[2] = 0;
      a[3] = 0;
      a[4] = 11;
      a[5] = 11;
      a[6] = 11;
    }
  }
}
// ============ МЕНЮ ===========
void menu() {

  if (MENu && !go) {
    // если разрешён вход в меню
    // и если не происходит первый вход в меню
    // разбираемся с антидребезгом, длительностью и
    // числом нажатия кнопки
    // ==== фиксация долгих нажатий кнопки ===
    if ( digitalRead(KN) && ( !KNflgL)) { // если кнопка нажата
      // но ещё не зафиксировано
      tik++;                              // долгое нажатие
      if (tik >= 2)  // ждём подтверждение антидребезга
        KNflgNEW = true; // и фиксируем факт нового нажатия кнопки
      if (tik >= 750) {  //750x4ms=3сек если удержание более 3 сек
        KNflgS = false; // фиксируем долгое нажатие
        KNflgL = true;
        long_kn = !long_kn; // фиксируем новое долгоое нажатие
        // (чётное или нечётное)
        tik = 0;
      }
    }
    //=== фиксация коротких нажатий кнопки ===
    if (((digitalRead(KN)) == LOW)  && (!KNflgS) && KNflgNEW && (!KNflgL)) {
      //если было зафиксировано новое нажатие,затем кнопка отпущена,
      //  но ещё не было зафиксировано ни долгое ни короткое нажатие
      tdreb++;  //  ждём подтверждения антидребезга
      if (tdreb > 2) { // и считаем, что кнопка отпущена
        KNflgS = true; // и фиксируем  короткое нажатие
        KNflgNEW = false;
        s++;// счётчик пунктов меню
        // переходим в следующий пункт(позицию) меню
        if (s > 9)
          s = 1;
        KNflgL = false;  //
        tdreb = 0; //
        tik = 0; // обнуляем счётчики
      }
    }
    //========================
    //== ОТРАБОТКА КОРОТКИХ НАЖАТИЙ ========

    if (KNflgS) { // если произошло новое

      switch (s) { // короткое нажатие кнопки

        case 1: //
          Nmenu = 1; //1-я позиция меню
          MIGfl = true; // уст. флаг мигания
          Nkn = 0; //сброс кол-ва долг.нажатий
          long_kn = true; //первое долгое нажатие - нечётное
          a[7] = Nmenu; //отображение тек.позиция меню
          a[0] = 11; // маска
          if (b[0] == 0) {
            a[1] = 11; //маскируем, если надо,лишний разряд
          }
          else { //
            a[1] = b[0]; //выводим значения для 1 поз.
          }
          a[2] = b[1]; //из массива b[], считанные
          a[3] = b[2]; // из eeprom
          a[4] = 11; // маска
          a[5] = 19; //"A"
          a[6] = 20; //"d"
          tochka = true;
          out = false; // запрещаем выход и вход
          go = false; // в меню
          null_0 = false; // запрещаем обнуление данных меню
          mig1 = 0; // сбрасываем счётчик миганий
          break;

        case 2:  // фиксируем значения
          MIGfl = false; // сбр. флаг мигания
          break;

        case 3: // 3-е коротк. нажатие
          Nmenu = 2; // 2-я позиция меню
          Nkn = 0;
          MIGfl = true;
          long_kn = true;
          a[7] = Nmenu; //отображение тек.позиция меню
          a[1] = b[3]; // выводим значения для 2 поз.
          a[2] = b[4]; // из массива b, считанного
          a[3] = b[5]; // из eeprom
          a[4] = 11; // маска
          a[5] = 15; // символы "on"
          a[6] = 16;
          tochka = true;
          mig1 = 0; // сбрасываем счётчик миганий
          break;

        case 4: //фиксируем значения
          MIGfl = false; //
          break;

        case 5: // 5-е коротк. нажатие
          Nmenu = 3; //3-я позиция меню
          Nkn = 0; //
          MIGfl = true; //
          long_kn = true; //
          a[7] = Nmenu; //отображение тек.позиция меню
          a[1] = b[6]; //выводим значения для 3 поз.
          a[2] = b[7]; // из массива b, считанного
          a[3] = b[8]; // из eeprom
          a[4] = 11; // маска
          a[5] = 15; // символы "of"(без 2-ой f)
          a[6] = 17;
          tochka = true;
          mig1 = 0; // сбрасываем счётчик миганий
          break;

        case 6: //фиксируем значения
          MIGfl = false; //
          break;

        case 7:  // 7-е коротк. нажатие
          Nmenu = 4; // 4-я поз. меню
          out = true;
          tik_go = 0;
          a[7] = Nmenu; // отображение тек.позиция меню
          a[0] = 11; // выводим символы
          a[1] = 16; // "noSAVE"
          a[2] = 15; //
          a[3] = 21; //
          a[4] = 19; //
          a[5] = 22;
          a[6] = 23;
          PORTD = PORTD & 0b01111111; // гасим точку
          tochka = false;
          save = false;
          synchr = 0; //  через 8с выходим из меню
          KNflgL = false; // не реагируем на долгие нажатия
          break;

        case 8:  // 8-е коротк. нажатие
          Nmenu = 5; // 5-я позиция меню
          out = true; // флаг выхода из меню
          tik_go = 0;
          a[7] = Nmenu; // отображение тек.позиция меню
          a[0] = 11; // выводим символы "СОХР"
          a[1] = 21; //12; или "SAVE"
          a[2] = 19; //0;
          a[3] = 22; //13;
          a[4] = 23; //14;
          a[5] = 11;
          a[6] = 11;
          PORTD = PORTD & 0b01111111; // гасим точку
          tochka = false;
          synchr = 0; // через 8с выходим из меню
          save = true;
          KNflgL = false; // не реагируем на долгие нажатия
          break;

        case 9: // 9-е коротк. нажатие
          Nmenu = 6; //6-я позиция меню
          out = true; //
          tik_go = 0;
          a[7] = Nmenu; //
          a[0] = 11; // "nULL"
          a[1] = 16; // обнуление
          a[2] = 22; //
          a[3] = 25;
          a[4] = 25;
          a[5] = 11;
          a[6] = 11;
          PORTD = PORTD & 0b01111111; // гасим точку
          tochka = false;
          synchr = 0; // через 8с выходим из меню
          null_0 = true; // разрешаем обнуление данных меню
          save = false;
          KNflgL = false; // не реагируем на долгие нажатия
          break;
      }

      KNflgS = false; // сброс флага короткого нажатия кнопки
      KNflgNEW = false; // сброс флага нового нажатия кнопки
    }

    //== ОТРАБОТКА ДОЛГИХ НАЖАТИЙ ========

    if (KNflgL) { // если было зафиксировано долгое нажатие кнопки,
      if (digitalRead(KN)) { // при продолжении удержания кнопки

        a[7] = Nmenu; // отображение тек.позиции меню

        if ((millis() - KNschet) > 375) { //375х4=1500 примерно
          // каждые 1.5 секунды
          KNschet = millis(); // обновляем счётчики меню при удержании кнопки

          // ===================================
          if (long_kn == true) { //установка десятых, чётное долг.нажатие ==
            // ===================================

            if (s == 1) {      // первая позиция меню,чётное долгое
              c_corr1++;        // нажатие
              a[1] = b[0]; // присваиваем сохранённые значения первой позиции
              a[2] = b[1]; //
              a[3] = b[2];
              // установка десятых коррекции
              // в первой позиции меню
              if (c_corr1 < 10) { //  положительные числа
                a[3] = c_corr1;
                a[1] = 11;

              }
              if (c_corr1 > 9) {
                a[3] = c_corr1 - 10;

                if (c_corr1 == 10) { // если а[3]=0
                  a[1] = 11; // то минус не выводим
                }
                else {
                  a[1] = 10; // отрицательные числа
                } // выводим "-"
              }
              if (c_corr1 == 20) {
                c_corr1 = 0;
                a[3] = 0;
                a[1] = 11;
              }
              b[0] = a[1]; // сохраняем знак
              b[1] = a[2];
              b[2] = a[3]; //сохраняем (в операт. памяти)
              // значения десятых коррекции
            }
            if (s == 3) { // вторая позиция меню, чётное
              c_corr3++; // долгое нажатие
              a[1] = b[3]; // присваиваем сохранённые значения второй позиции
              a[2] = b[4]; //
              a[3] = b[5];
              if (c_corr3 < 10) // установка десятых во второй позиции меню
                a[3] = c_corr3;
              if (c_corr3 == 10) {
                c_corr3 = 0;
                a[3] = 0;
              }
              b[5] = a[3];

            }
            if (s == 5) { // третья позиция меню, чётное
              c_corr4++; // долгое нажатие
              a[1] = b[6]; // присваиваем сохранённые значения третьей позиции
              a[2] = b[7]; //
              a[3] = b[8];
              if (c_corr4 < 10) // установка десятых в третьей позиции меню
                a[3] = c_corr4;
              if (c_corr4 == 10) {
                c_corr4 = 0;
                a[3] = 0;
              }
              b[8] = a[3];   // сохр. значение в оперативной памяти
            }

          }  // ====================================================
          else { //установка едениц и десятков, нечётное долгое нажатие кнопки
            // ===================================================

            if (s == 1) { // нечётное долгоe нажатие в первой позиции меню
              c_corr1++; // продолжает действие чётного нажатия
              a[1] = b[0]; // присваиваем сохранённые значения первой позиции
              a[2] = b[1]; //
              a[3] = b[2];

              if (c_corr1 < 10) { //  положительные числа
                a[3] = c_corr1;
                a[1] = 11;    //
              }
              if (c_corr1 > 9) {
                a[3] = c_corr1 - 10;

                if (c_corr1 == 10) {
                  a[1] = 11;
                }
                else {
                  a[1] = 10; // отрицательные числа
                }
              }
              if (c_corr1 == 20) {
                c_corr1 = 0;
                a[3] = 0;
                a[1] = 11;
              }
              b[0] = a[1]; // сохраняем знак
              b[1] = a[2];
              b[2] = a[3]; //сохраняем значения десятых коррекции

            }
            if (s == 3) {   // вторая позиция меню, нечётное
              // долгое нажатие
              a[1] = b[3]; // присваиваем сохранённые значения второй позиции
              a[2] = b[4];
              a[3] = b[5];
              c_corr2++;
              if (c_corr2 < 10) {
                a[2] = c_corr2;
                a[1] = 0;
              }
              if (c_corr2 > 9) {
                a[1] = c_corr2 / 10;
                a[2] = c_corr2 % 10;
              }
              if (c_corr2 > 99) {
                a[1] = 0;
                a[2] = 0;
                c_corr2 = 0;
              }
              b[3] = a[1];
              b[4] = a[2];
              b[5] = a[3]; // сохраняем нижнее пороговое значение температуры
            }
            if (s == 5) {   // третья позиция меню,нечётное
              // долгое нажатие
              a[1] = b[6]; // присваиваем сохранённые значения третьей позиции
              a[2] = b[7];
              a[3] = b[8];
              c_corr5++;
              if (c_corr5 < 10) {
                a[2] = c_corr5;
                a[1] = 0;
              }
              if (c_corr5 > 9) {
                a[1] = c_corr5 / 10;
                a[2] = c_corr5 % 10;
              }
              if (c_corr5 > 99) {
                a[1] = 0;
                a[2] = 0;
                c_corr5 = 0;
              }
              b[6] = a[1];
              b[7] = a[2];
              b[8] = a[3]; // сохраняем верхнее пороговое значение температуры
            }
          }
        }

      }
      else { // ЕСЛИ КНОПКА БОЛЬШЕ НЕ УДЕРЖИВАЕТСЯ
        KNflgNEW = false; // сброс флага нового нажатия кнопки
        KNflgL = false; // сброс флага долгого нажатия
        Nkn++;      // считаем количество завершённых долгогих нажатий
        mig1 = 0; // сброс счётчика количества миганий
      }
    }
  }
}

//================= EEPROM =================
void wr_eepr() {
  EEARH &= 0b11111110; // записываем 0 в старший разряд адреса
  b[9] = c_corr1;
  b[10] = c_corr2; // сохраняем счётчики меню
  b[11] = c_corr3;
  b[12] = c_corr4;
  b[13] = c_corr5;

  byte eeadr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13};
  //адреса eeprom
  for (byte i = 0; i < 14; i++) { // для каждого байта
    while (EECR & (1 << EEPE)); // ждём готовности к записи

    EEARL = eeadr[i]; //подготавливаем адрес и данные
    EEDR = b[i];

    EECR |= (1 << EEMPE);// уст. в лог. единицу бит EEMPE
    // начинаем запись установкой бита EEPE
    EECR |= (1 << EEPE);
  }
}

void read_eepr() {

  EEARH &= 0b11111110; // записываем 0 в старший разряд адреса
  byte eeadr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13};
  // адреса eeprom
  for (byte i = 0; i < 14; i++) { // для каждого байта
    while (EECR & (1 << EEPE));  // ждём готовности
    EEARL = eeadr[i]; // задаём адрес
    EECR |= (1 << EERE); // устанавливаем бит  EERE
    // начинаем чтение
    b[i] = EEDR; // читаем байт
  }
  c_corr1 = b[9];
  c_corr2 = b[10]; // возвращаем
  c_corr3 = b[11]; // счётчики меню
  c_corr4 = b[12];
  c_corr5 = b[13];
}

Доступные для изменения значения выделяются миганием

4. Выход из меню - 4, 5, 6 позиции "noSAVE", "SAVE", "nULL"- без изменений, с сохранением изменений и обнуление всех значений меню соответственно. Выход из этих позиций происходит автоматически,по истечении 8 секунд

Данные меню сохраняются в EEPROM в двоично - десятичном виде, один десятичный разряд или знак минуса - один байт.

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

Прилагаю упрощённый код, где оставил только меню и индикацию постоянного значения измеряемой темп-ры -0.7С.Итак на страницу не влазит))). Временно добавил вывод данных eeprom в терминал. Оказывается, в Протеусе это сделать не сложно.

И спасибо lilik-у, за то, что в одной из своих тем показал, как можно выложить гифку на форуме

b707
Offline
Зарегистрирован: 26.05.2017

Дим-мычъ пишет:

"Простое меню" оказалось не очень-то простым для меня. Программа чтения из датчика с индикацией занимала 7% памяти устройства и 4% динамической памяти. С добавлением меню (плюс немного коррекция и контроль пороговых значений) уже 15% и 7% соответственно. Т.е по объёму прога выросла в два раза(!) Это нормально для такого простого меню?

это даже очень мало...

Нормально, когда основной код программы (работа с железом и логика) занимает 5%, а меню и плюшки - оставшиеся 95%. И ничего удивительного в этом нет, работа с железом много кода и памяти не требует, а например работа с экраном? :)

Код не смотрел

Дим-мычъ
Offline
Зарегистрирован: 20.03.2021

b707 пишет:

Нормально, когда основной код программы (работа с железом и логика) занимает 5%, а меню и плюшки - оставшиеся 95%. И ничего удивительного в этом нет

Спасибо за ответ, не знал этого. Моё первое меню

 

rkit
Offline
Зарегистрирован: 23.11.2016

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

Эти массивы типа a и b - через месяц невозможно будет даже самому разобраться, зачем нужны и как работает.

Дим-мычъ
Offline
Зарегистрирован: 20.03.2021

rkit пишет:

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

Эти массивы типа a и b - через месяц невозможно будет даже самому разобраться, зачем нужны и как работает.

Спасибо, подумаю об этом. Видел пару примеров меню, но они на С++, а я пока в этом "ни в зуб ногой",

ещё только Си пробую.

Решил делать, согласно своему пониманию

BOOM
BOOM аватар
Offline
Зарегистрирован: 14.11.2018

Как по мне, если критика приветствуется, длинные долгие нажатия разной длины лучше бы заменить. Я как то делал несколько простых устройств с библиотекой kakmyc_btn.h (есть на форуме, ещё раз ему спасибо). Там использовался энкодер со встроенной кнопкой. Мне было это достаточно и для простого меню и для изменений в нем. Использовал долгое удержание, одинарные/двойные и тройные нажатия, ну и энкодер для изменения значений. 

Дим-мычъ
Offline
Зарегистрирован: 20.03.2021

BOOM пишет:

Как по мне, если критика приветствуется, длинные долгие нажатия разной длины лучше бы заменить. Я как то делал несколько простых устройств с библиотекой kakmyc_btn.h (есть на форуме, ещё раз ему спасибо). Там использовался энкодер со встроенной кнопкой. Мне было это достаточно и для простого меню и для изменений в нем. Использовал долгое удержание, одинарные/двойные и тройные нажатия, ну и эндодермах для изменения значений. 

Критика именно приветствуется! Долгое(очень) нажатие при входе работает один раз. Я его ввёл специально, чтоб исключить случайный вход по короткому(сначала было так)

Об остальном, спасибо, буду думать

Дим-мычъ
Offline
Зарегистрирован: 20.03.2021

rkit пишет:

Логику нужно разделить.

Сейчас логика такая:

 в loop каждые 4мс происходит индикация, первичный опрос кнопки и запущен общий счётчик synchr

Если меню активно(по нажатию кнопки), то в loop поступают из меню данные об индикации а[] и счётчиков  и флагов для мигания. Так же там, по флагу, и выход из  меню(вот его легко можно сделать отдельной функцией)

Если меню не активно, по счётчику synchr, происходит опрос датчика , crc, и.т.д. и в loop передаются данные об индикации

Т.е. кнопка и так в отдельной функции(menu), и с loop связана минимально.

Как разделить ещё логику на том , что есть, пока не представляю, разве писать с чистого листа, по новой?

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

Ну, надо сказать, что меню - это элемент пользовательского интерфейса, а пользовательский интерфейс - всегда головная боль разработчика и масса пожираемых ресурсов. Кстати, именно по критерию ресурсоемкости мне пришлось оптимизировать меню, описанное здесь: http://arduino.ru/forum/proekty/menyu-dlya-dvukhstrochnogo-displeya  Там же есть ссылка на видео.

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

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

Дим-мычъ
Offline
Зарегистрирован: 20.03.2021

andriano пишет:

 оптимальным вариантом являются энкодер и пара кнопок. Кнопку на самом энкодере лучше либо вообще не использовать, либо использовать для ускорения пролистывания при широком диапазоне изменения. Например, если необходимо выбрать целое число в диапазоне 1000, то разумно шаг энкодера переключать с 1 при отпущенной кнопке до 30 при нажатой.

Спасибо, буду знать. Вот и BOOM то же говорит. Файлы скачал, мне на долго теперь хватит разбираться.

С энкодером , если что, попробую следующее меню. Сейчас хочу понять, если что не так, и "допилить" это,

если возможно.  Т.е. в принципе-то работает, но смутил объём , что даже на 5 страниц не влезает)))

 

Дим-мычъ
Offline
Зарегистрирован: 20.03.2021

rkit пишет:

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

Логику , наконец, разделил )).  Всё развёл по функциям, оставил в loop только счётчик synchr и вызов функций. Пришлось добавить один флаг. Но это всё только для удобства чтения, по сути, те же яйца, только в профиль. Даже добавилось 48байт кода и 1 байт оперативной памяти. Как я ни старался, существенно уменьшить объём программы уже не получается, по крайней мере на моём уровне "развития")).

На этом пока остановлюсь, буду учиться дальше, разбирать , что дал andriano, пока много ещё непонятно.

P.S. Меню с одной кнопкой, всё же считаю иногда будет к месту. Если оно маленькое, не надо часто

лазить , выставил, что надо и забыл. Да и нога МК  нужна лишь одна.

void loop() {

  //  интервал 4мс для обновления
  // динамической индикации, счётчика synchr, и
  // опроса кнопки
  if ((millis() - last0_millis) >= 4) {
    last0_millis = millis();

    read_button(); // опрос кнопки
    menu(); // вход в меню(если активно)
    one_mig1(); // организация мигания в меню 1 часть
    mask_ind(); // гасим разряд и точку

    k++; // Здесь спасибо, счётчик synchr с кодом Alexey_Rem помогал
    if (k > 7) { //  каждые 8 циклов
      k = 0;     // 8х4=32ms  счётчик синхронизации всех
      synchr++; // +32ms  частей программы
      flg = true;   //флаг обновения synchr
      // разрешающий выпонение некоторых блоков программы
    }
    new_ind(); //обновление индикации
    one_mig2(); // организация мигания 2 часть

    switch (synchr) { // распределение времени между
      // отдельными частями программы
      case 218:     // 218х32 = 6976мс
        if (out) { // если поднят флаг выхода из меню
          vihod();
        }
        //--------------------------------
        // если не установлен флаг пребывания в меню
        // выполняется основная часть программы
        // распределение времени для
        // общения с датчиком 1раз в 8сек, преобразование чисел, подсчёт crc

        if (flg && (!MENu)) { // если не в меню
          // и поднят флаг обновления shyncr
          // здесь передача команд датчику
          flg = false;
          break;
        }

      case  250: //  250х32 = 8000 ожидание преобразования и выполнение
        // второй части : чтение ,счёт ,crc_8
        if (flg && (!MENu)) { // если не в меню
          synchr = 0;
          // здесь получение данных из датчика
          // преобразование чисел
          // проверка crc
          // контроль пороговых значений темп-ры
          // в упр. версии  просто выводим
          a[7] = 11; // -0.7C
          a[0] = 11;
          a[1] = 10;
          a[2] = 0;
          a[3] = 7;
          a[4] = 11;
          a[5] = 18;
          a[6] = 12;
          // выводим пользовательские данные меню в терминал
          Serial.println(b[0]);
          Serial.println(b[1]);
          Serial.println(b[2]);
          Serial.println(b[3]);
          Serial.println(b[4]);
          Serial.println(b[5]);
          Serial.println(b[6]);
          Serial.println(b[7]);
          Serial.println(b[8]);

          rdeepr_cycl++;
          if (rdeepr_cycl > 150) { // обновляем данные из eeprom
            read_eepr(); // каждые 150 измерений
            rdeepr_cycl = 0; // число с "потолка", просто "бережём"
          } // eeprom)))
          tochka = true;
          flg = false;
          break;
        }
    }
  }
}

 

DetSimen
DetSimen аватар
Offline
Зарегистрирован: 25.01.2017

Дим-мычъ пишет:
P.S. Меню с одной кнопкой, всё же считаю иногда будет к месту. Если оно маленькое, не надо часто лазить , выставил, что надо и забыл. Да и нога МК  нужна лишь одна.

Ты не поверишь, наерно, но если сделать "лесенку" из резисторов и кнопок и подключить всё это на аналоговый вход, то кнопок можно поставить от 2 до 16, пользоваться будет не в пример удобнее, а вход будет занят тоже только один. Я себе последнее время схему из этого шилда ставлю куда не попадя, работает, с-ка. 

BOOM
BOOM аватар
Offline
Зарегистрирован: 14.11.2018

DetSimen пишет:

Ты не поверишь, наерно, но если сделать "лесенку" из резисторов и кнопок и подключить всё это на аналоговый вход, то кнопок можно поставить от 2 до 16....

Только работает вся эта "лестничная шелупень" неустойчиво (нестабильно). Пробовал и отказался. Навсегда.

DetSimen
DetSimen аватар
Offline
Зарегистрирован: 25.01.2017

BOOM пишет:

Только работает вся эта "лестничная шелупень" неустойчиво (нестабильно). Пробовал и отказался. Навсегда.

Ви, таки, п'госто не умеете её готовить.  :)  

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

Green
Offline
Зарегистрирован: 01.10.2015

А пгосто бывает, что схему ужЕ поменять невозможно, поэтому и приходится геморойничать.

Дим-мычъ
Offline
Зарегистрирован: 20.03.2021

DetSimen пишет:

Ты не поверишь, наерно, но если сделать "лесенку" из резисторов и кнопок и подключить всё это на аналоговый вход, то кнопок можно поставить от 2 до 16, пользоваться будет не в пример удобнее, а вход будет занят тоже только один. Я себе последнее время схему из этого шилда ставлю куда не попадя, работает, с-ка. 

Поверю и почти согласен! Это типовая схема многих мониторов и др. аудио/видео. Но , во первых, я делал больше ради обучения, а, во вторых, всё-таки кнопок надо больше))

Green
Offline
Зарегистрирован: 01.10.2015

andriano пишет:

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


Да, в первую очередь нужно думать о пользователе. А у нас об этом, зачастую, забывают. Правда, Дим-димыч?

DetSimen
DetSimen аватар
Offline
Зарегистрирован: 25.01.2017

Дим-мычъ пишет:
всё-таки кнопок надо больше))

больше чем скока?

Если в твоём проекте надо больше 16 кнопок, проще тогда взять клаву PS\2, под нее и биб-ки есть. 

Green
Offline
Зарегистрирован: 01.10.2015

Это да.) Бывало https://www.youtube.com/watch?v=BgdzBgIMACc

 

Дим-мычъ
Offline
Зарегистрирован: 20.03.2021

Green пишет:

Да, в первую очередь нужно думать о пользователе. А у нас об этом, зачастую, забывают. Правда, Дим-димыч?

Это да, есть такое... Просто , имел ввиду например, если надо поддерживать постоянную температуру в каком-нибудь помещении с помощью ТЭНа, раз в год выставить, ну там ещё раз подкорректировать и забыть. Зачем тогда 6 кнопок?

А так да, если надо постоянно пользоваться - неудобно.

Дим-мычъ
Offline
Зарегистрирован: 20.03.2021

DetSimen пишет:

Дим-мычъ пишет:
всё-таки кнопок надо больше))

больше чем скока?

Извиняюсь что нечётко выразился. Имел ввиду , что для "лесенки" всё-таки нужна больше чем одна

кнопка.

DetSimen
DetSimen аватар
Offline
Зарегистрирован: 25.01.2017

Дим-мычъ пишет:

Извиняюсь что нечётко выразился. Имел ввиду , что для "лесенки" всё-таки нужна больше чем одна

кнопка.

Мне всегда достаточно было 5.  Как в 1602 Keypad Shield.

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

В моем последнем проекте галетник на 8 положений. Это все равно что 8 кнопок, которые нельзя нажать одновременно. Никаких даже намеков на сбои и прочие "неустойчивости (нестабильности)". Нужно только зашунтировать аналоговый вход небольшой емкостью.

Kakmyc
Offline
Зарегистрирован: 15.01.2018

Очень часто встречается навигация по меню тремя кнопками. Вверх/вниз/ок

Kakmyc
Offline
Зарегистрирован: 15.01.2018

andriano пишет:

В моем последнем проекте галетник на 8 положений. Это все равно что 8 кнопок, которые нельзя нажать одновременно. Никаких даже намеков на сбои и прочие "неустойчивости (нестабильности)". Нужно только зашунтировать аналоговый вход небольшой емкостью.

8 это немного.
Я тут снимал как то с высечного станка от trumpf сигнал с регулятора скорости . Там 24 положения было.
Правда выходов тоже всего 8, замыкались в разной комбинации. Такое себе развлечение сидеть снимать диаграмму, а потом описывать ее кодом.

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

Kakmyc пишет:
Очень часто встречается навигация по меню тремя кнопками. Вверх/вниз/ок

Должна быть 4-я - отмена.

Kakmyc
Offline
Зарегистрирован: 15.01.2018

andriano пишет:

Kakmyc пишет:
Очень часто встречается навигация по меню тремя кнопками. Вверх/вниз/ок

Должна быть 4-я - отмена.

Нет не должно
Ок->сдвиг курсора вправо зацикленный.
+/- смена значений или пунктов меню

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

Kakmyc пишет:
8 это немного.

Да.

Надо было мне процитировать сообщение №12, чтобы было понятно о чем речь. 

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