Память заканчивается. Варианты решения.

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

AsNik пишет:

( Задачу не представляю возможности в кратце рассказать здесь, а у себя в голове эта задача четкая, но не маленькая ;)

Так не бывает.

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

 

Upd. Вот, кстати, и вырисовался конкретный совет: начните с четкого и подробного изложения задачи на бумаге (в текстовом файле). Это - первый шаг к тому, чтобы все, что надо, влезло. Т.е. надо начинать не с исходника, а немного раньше.

AsNik
Offline
Зарегистрирован: 24.10.2020

Изначально вопрос темы был такой:

Цитата:

В общем вопрос такой: Решил программу разбить на два МК(нано). В одном только работа с экраном, а во втором основной алгоритм работы устройства. Соединить их думаю уже по Serial. Собс-но вопрос - имеет смысл моя идея. Никогда раньше не объединял ардуины для совместной работы. С чем мне придется столкнуться?

Но сейчас мне интересен ответ про стратегическую ошибку в коде.

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

AsNik
Offline
Зарегистрирован: 24.10.2020

andriano пишет:

#51

Согласен. Но это больше подходит для профессионального решения, а не...

Отличие тут есть. Когда ты проф, то ты точно знаешь, что тебе надо и что у тебя будет. А тут ставишь себе задачу, пока ее решаешь, оказывается что так вообще нельзя и начинаешь все с нуля... новая задача и... примерно так. Но это не мое основное занятие. Я понимаю, что форум больше на профессионалов расчитан, а любителей тут не любят).

wdrakula
wdrakula аватар
Offline
Зарегистрирован: 15.03.2016

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

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

Ты жеманничаешь, как пожилая куртизанка. Доставай уже свой ...код! ;)))))

Green
Offline
Зарегистрирован: 01.10.2015
- Ах, ты старая проститутка!
- Сара! Я тебя не понимаю, при чем здесь возраст!
 
AsNik
Offline
Зарегистрирован: 24.10.2020
byte activeItem = 0, topItem = 0;
void CalckMenu(bool _isR, bool _isL, SBUTTON_CLICK _sbc) {
  bool f_full = false;
  if (_isL && activeItem < Menu->ItemCount - 1) {
    activeItem++;
    if ((activeItem - topItem) > Menu->ShowItemCount - 1) {
      topItem++;
      f_full = true;
    }
    UpdateMenu(f_full);
  }
  if (_isR && activeItem > 0) {
    activeItem--;
    if (activeItem < topItem) {
      topItem--;
      f_full = true;
    }
    UpdateMenu(f_full);
  }

  if (_sbc == SB_LONG_CLICK) {
    if (Menu == &ChannelsMenu && activeItem < Menu->ItemCount - 1) {
      channels[activeItem].chEnable = !channels[activeItem].chEnable;
      UpdateMenu(false);
    }
  }

  if (_sbc == SB_CLICK) {
    Menu->SelectItem = activeItem + 1;
    Menu->LastActiveItem = activeItem;
    Menu->LastTopItem = topItem;
    if (Menu == &MainMenu) {
      if (Menu->SelectItem == 1) {
        isScreens = true;
        ScreenIndex = InfoScreenIndex;
        ScreenCount = InfoScreenCount;
        UpdateScreens(true);
      }
      if (Menu->SelectItem == 2) {
        Menu = &SensMenu;
        Menu->SelectItem = 0;
        activeItem = SensMenu.LastActiveItem;
        topItem = SensMenu.LastTopItem;
        UpdateCaptMenu();
      }
      if (Menu->SelectItem == 3) {
        Menu = &ChannelsMenu;
        Menu->SelectItem = 0;
        activeItem = ChannelsMenu.LastActiveItem;
        topItem = ChannelsMenu.LastTopItem;
        UpdateCaptMenu();
      }
      if (Menu->SelectItem == 4) {
        isScreens = true;
        ScreenIndex = ServiceScreenIndex;
        ScreenCount = ServiceScreenCount;
        UpdateScreens(true);
      }
    } else {
      if (activeItem == Menu->ItemCount - 1) { //Пункт (Back) SensMenu и ChannelsMenu
        Menu = &MainMenu;
        Menu->SelectItem = 0;
        activeItem = MainMenu.LastActiveItem;
        topItem = MainMenu.LastTopItem;
      } else {
        if (Menu == &SensMenu) {
          isScreens = true;
          ScreenIndex = SensScreenIndex;
          ScreenCount = SensScreenCount;
          UpdateScreens(true);
        }
        if (Menu == &ChannelsMenu && channels[activeItem].chEnable) {     //Открываем экраны только если канал включен
          isScreens = true;
          ScreenIndex = ChannelScreenIndex;
          ScreenCount = ChannelScreenCount;
          UpdateScreens(true);
        } else return;
      }
    }
    if (!isScreens) UpdateMenu(true);
  }
}

void UpdateCaptMenu() {
  if (isScreens) return;
  lcd.setCursor(0, 0); Print_Space(20);
  lcd.setCursor(0, 0); lcd.print(MainMenu.Items[MainMenu.LastActiveItem]);
  if (Menu == &SensMenu) PrintTextLCD(9, 0, "Title");
  if (Menu == &ChannelsMenu) PrintTextLCD(10, 0, "Enb  Stat");
}

void UpdateMenu(bool _isFull) {
  if (isScreens) return;
  byte i = 4 - Menu->ShowItemCount;
  for (byte n = 0; n < Menu->ShowItemCount; n++) {
    lcd.setCursor(0, n + i);
    if (_isFull) {
      for (byte k = 0; k < 20; k++) Print_Space();
      lcd.setCursor(0, n + i);
    }
    byte drawItemN = topItem + n;        //Номер рисуемого элемента меню
    if (drawItemN == activeItem) PrintArrowR(); else if (!_isFull) Print_Space();

    if (Menu == &ChannelsMenu && drawItemN < Menu->ItemCount - 1) {
      if (channels[drawItemN].chEnable) {
        PrintTextLCD(9, i + n, "<ON >"); Print_Space(2);
        channels[drawItemN].chIsWork ? PrintTextLCD("On ") : PrintTextLCD("Off");
      } else {
        PrintTextLCD(9, i + n, "<DIS>"); Print_Space(6);
      }
    }

    if (!_isFull) continue;
    lcd.setCursor(1, i + n);
    lcd.print(Menu->Items[drawItemN]);
    if (drawItemN == Menu->ItemCount - 1) break;
    if (Menu == &SensMenu) {
      lcd.setCursor(10, n + i);
      lcd.print(SensTitle[drawItemN]);
    }
  }
}
#define PERIODPOWER   15000//25000     //Интервал включения питания
#define STARTREADING  5000//5000      //Интервал начала чтения после включения питания
#define PERIODREAD    2500//2500      //Интервал между чтениями сенсоров
#define NUM_READS     5         //Кол-во чтений сенсоров для усреднения значения
bool powerON = false;           //Включено при подаче питания на сенсоры
bool reading = false;           //Включено при чтении сенсоров

byte nRead = NUM_READS;         //Счетчик считываний сенсоров (уменьшается до нуля)
uint32_t msLast = PERIODPOWER;  //переменная времени для таймеров
void readSens() {       //Чтение сенсоров
  uint32_t ms = millis();

  if (!powerON && (ms - msLast) > PERIODPOWER) {
    powerON = true; msLast = ms;
    digitalWrite(TESTPOWERPIN, HIGH);      //Подаем питание на сенсоры
  }

  if (!reading && powerON && (ms - msLast) > STARTREADING) {
    for (byte k = 0; k < SENS_COUNT; ++k) SetSensorStatus(k, ssReading);
    reading = true; UpdateScreens (false);
  }

  if (reading && (ms - msLast) > PERIODREAD) {
    for (byte k = 0; k < SENS_COUNT; ++k) readSensor(k);          //Тут читаем сенсор(k)
    msLast = ms;
    --nRead;
  }

  if (!nRead) {
    nRead = NUM_READS;
    powerON = reading = false;
    msLast = ms;
    digitalWrite(TESTPOWERPIN, LOW);       //Отключаем питание на сенсоры
    CalculatingSensorsData();      //Усреднили данные с сенсоров <<<<<<<<<<
    //GetDinamics();                //Посчитали динамику <<<<<<<<<<<<<<<<<<<
    UpdateScreens (false);
  }
}

void CalculatingSensorsData() { //Переделать!!!!
  for (byte n = 0; n < SENS_COUNT; ++n) {
    if (arSens[n].sStatus != ssReading) continue;
    arSens[n].data.T = arSens[n].data.tmpT / NUM_READS;
    if (arSens[n].data.lastT != NO_DATA && abs(arSens[n].data.T - arSens[n].data.lastT) > 70) { //Если температура скакнула больше чем на 7 градусов, то выставить ошибку
      SetSensorStatus(n, ssNoData); continue;
    }
    
    arSens[n].data.H = arSens[n].data.tmpH / NUM_READS;
    if (arSens[n].data.lastH != NO_DATA && abs(arSens[n].data.H - arSens[n].data.lastH) > 150) { //Если влажность скакнула больше чем на 15 процентов, то выставить ошибку
      SetSensorStatus(n, ssNoData); continue;
    }
    
    if (arSens[n].isPressure) if (arSens[n].data.tmpP != NO_DATA) arSens[n].data.P = arSens[n].data.tmpP / NUM_READS;
    
    if (arSens[n].data.T < arSens[n].data.minT.t) {
      arSens[n].data.minT.t = arSens[n].data.T;
      arSens[n].data.minT.h = arSens[n].data.H;
    }
    if (arSens[n].data.T > arSens[n].data.maxT.t) {
      arSens[n].data.maxT.t = arSens[n].data.T;
      arSens[n].data.maxT.h = arSens[n].data.H;
    }
    if (arSens[n].data.H < arSens[n].data.minH.h) {
      arSens[n].data.minH.h = arSens[n].data.H;
      arSens[n].data.minH.t = arSens[n].data.T;
    }
    if (arSens[n].data.H > arSens[n].data.maxH.h) {
      arSens[n].data.maxH.h = arSens[n].data.H;
      arSens[n].data.maxH.t = arSens[n].data.T;
    }
    arSens[n].sStatus = ssOk;
  }
}

void SetSensorStatus(const byte n_sens, const byte _sStatus) {
  arSens[n_sens].sStatus = _sStatus; if (_sStatus == ssOk) return;
  if (_sStatus == ssReading) {
    if (arSens[n_sens].data.T != NO_DATA) arSens[n_sens].data.lastT = arSens[n_sens].data.T;
    if (arSens[n_sens].data.H != NO_DATA) arSens[n_sens].data.lastH = arSens[n_sens].data.H;
    if (arSens[n_sens].isPressure && arSens[n_sens].data.P != NO_DATA) arSens[n_sens].data.lastP = arSens[n_sens].data.P;
    arSens[n_sens].data.tmpT = 0;
    arSens[n_sens].data.tmpH = 0;
    if (arSens[n_sens].isPressure) arSens[n_sens].data.tmpP = 0;
    return;
  }
  arSens[n_sens].data.T = arSens[n_sens].data.tmpT = arSens[n_sens].data.lastT = NO_DATA;
  arSens[n_sens].data.H = arSens[n_sens].data.tmpH = arSens[n_sens].data.lastH = NO_DATA;
  if (arSens[n_sens].isPressure) arSens[n_sens].data.P = arSens[n_sens].data.tmpP = arSens[n_sens].data.lastP = NO_DATA;
}

void ResetMinMaxDay() {
  for (byte n = 0; n < SENS_COUNT; ++n) {
    arSens[n].data.minT.t = NO_DATA;
    arSens[n].data.maxT.t = -NO_DATA;
    arSens[n].data.minH.h = NO_DATA;
    arSens[n].data.maxH.h = -NO_DATA;
  }
}

float calculationAbsH(float t, float h) {
  float temp    = pow(2.718281828, (17.67 * t) / (t + 243.5));
  return (6.112 * temp * h * 2.1674) / (273.15 + t);
}

void readSensor(const byte n_sens) {
  if (arSens[n_sens].sStatus != ssReading) return;
  switch (arSens[n_sens].sensType) {
    case stDHT11:
      readDHT(n_sens);
      break;
    case stDHT22:
      readDHT(n_sens);
      break;
    case stAM:
      readAM(n_sens);
      break;
    case stBME:
      readBME(n_sens);
      break;
  }
}

void readDHT(const byte n) {
  switch (static_cast<iarduino_DHT*>(arSens[n].sensor)->read()) {  // читаем показания датчика
    case DHT_OK:
      arSens[n].data.tmpT += round(static_cast<iarduino_DHT*>(arSens[n].sensor)->tem * 10);
      arSens[n].data.tmpH += round(static_cast<iarduino_DHT*>(arSens[n].sensor)->hum * 10);
      break;
    case DHT_ERROR_CHECKSUM:   //Serial.println(         "CEHCOP B KOMHATE: HE PABEHCTBO KC");                     break;
      SetSensorStatus(n, ssNoData);
      break;
    case DHT_ERROR_DATA:       //Serial.println(         "CEHCOP B KOMHATE: OTBET HE COOTBETCTB. CEHCOPAM 'DHT'"); break;
      SetSensorStatus(n, ssNoData);
      break;
    case DHT_ERROR_NO_REPLY:   //Serial.println(         "CEHCOP B KOMHATE: HET OTBETA");                          break;
      SetSensorStatus(n, ssNoSens);
      break;
    default:
      SetSensorStatus(n, ssUnknownErr);      //Любая неучтеная ошибка
      break;
  }
}

void readAM(const byte n) {
  switch (static_cast<iarduino_AM2320*>(arSens[n].sensor)->read()) {    // Читаем показания датчика.
    case AM2320_OK:
      arSens[n].data.tmpT += round(static_cast<iarduino_AM2320*>(arSens[n].sensor)->tem * 10);
      arSens[n].data.tmpH += round(static_cast<iarduino_AM2320*>(arSens[n].sensor)->hum * 10);
      break;
    case AM2320_ERROR_LEN:  //Serial.println("OTIIPABKA HEBO3M.");     break; // Объем передаваемых данных превышает буфер I2C
      SetSensorStatus(n, ssNoData);
      break;
    case AM2320_ERROR_ADDR:     //Serial.println("HET CEHCOPA");           break; // Получен NACK при передаче адреса датчика
      SetSensorStatus(n, ssNoSens);
      break;
    case AM2320_ERROR_DATA: //Serial.println("OTIIPABKA HEBO3M.");     break; // Получен NACK при передаче данных датчику
      SetSensorStatus(n, ssNoData);
      break;
    case AM2320_ERROR_SEND: //Serial.println("OTIIPABKA HEBO3M.");     break; // Ошибка при передаче данных
      SetSensorStatus(n, ssNoData);
      break;
    case AM2320_ERROR_READ: //Serial.println("HET OTBETA OT CEHCOPA"); break; // Получен пустой ответ датчика
      SetSensorStatus(n, ssNoSens);
      break;
    case AM2320_ERROR_ANS:  //Serial.println("OTBET HEKOPPEKTEH");     break; // Ответ датчика не соответствует запросу
      SetSensorStatus(n, ssNoData);
      break;
    case AM2320_ERROR_LINE: //Serial.println("HEPABEHCTBO CRC");       break; // Помехи в линии связи (не совпадает CRC)
      SetSensorStatus(n, ssNoData);
      break;
    default:
      SetSensorStatus(n, ssUnknownErr);      //Любая неучтеная ошибка
      break;
  }
}

void readBME(const byte n) { /// Проверить ??????????
  //SetSensorStatus(n, ssNoSens); return;
  //Если вернется NaN то выставить ошибку
  //lcd.setCursor(0,0); lcd.print(static_cast<BME280I2C*>(arSens[n].sensor)->temp(BME280::TempUnit_Celsius));
  arSens[n].data.tmpT += round(static_cast<BME280I2C*>(arSens[n].sensor)->temp(BME280::TempUnit_Celsius) * 10);
  arSens[n].data.tmpH += round(static_cast<BME280I2C*>(arSens[n].sensor)->hum() * 10);
  arSens[n].data.tmpP += round(static_cast<BME280I2C*>(arSens[n].sensor)->pres(BME280::PresUnit_torr) * 10);
}
void PrintSensor(const byte _nSens, const TSensorParam _sParam, const bool _isShortFormat = true);

const char * ChScrName[3] = {"Counters", "TimeWorks", "Powers"};
void CalckScreens(bool _isR, bool _isL, SBUTTON_CLICK _sbc) {
  if (_isR && ScreenIndex < ScreenCount - 1) {
    ScreenIndex++;
    UpdateScreens(true);
  }
  if (_isL && ScreenIndex > 0) {
    ScreenIndex--;
    UpdateScreens(true);
  };

  if (_sbc == SB_CLICK) {
    isScreens = false;
    if (Menu == &MainMenu && Menu->SelectItem == 1) InfoScreenIndex = ScreenIndex; else ServiceScreenIndex = ScreenIndex;
    UpdateCaptMenu();
    UpdateMenu(true);
  }

  if (_sbc == SB_LONG_CLICK) {          //Долгое нажатие на экранах
    if (Menu == &ChannelsMenu)
      ShowYN("ClearData?", CannelsTitle[Menu->SelectItem - 1], ChScrName[ScreenIndex]);
  }

}

void UpdateScreens (bool f_full) {
  if (!isScreens) return;
  if (f_full) lcd.clear();
  if (Menu == &MainMenu)
    switch (MainMenu.SelectItem) {
      case 1: UpdateInfoScreen(f_full);
        break;
      case 4: UpdateServiceScreen(f_full);
        break;
    }
  if (Menu == &SensMenu) UpdateSensorsScreen(f_full);
  if (Menu == &ChannelsMenu) UpdateChannelsScreen(f_full);
}

void UpdateInfoScreen (bool f_full) {
  switch (ScreenIndex) {
    case 0:
      if (f_full) {
        PrintTextLCD(0, 0, "Sens"); PrintColon(); Print_Space(); PrintTextLCD(SensTitle[sens_ulica]);
        lcd.setCursor(0, 1); Print_TextTemp(false); lcd.setCursor(11, 1); Print_TextHumi(false);
        PrintTextLCD(0, 2, "Humi(ABS)"); PrintColon(); Print_Space(7); PrintTextLCD("gm3");
        PrintTextLCD(0, 3, "Pressure"); PrintColon(); Print_Space(9); PrintTextLCD("mm");
      }
      PrintMeasuring(19, 0, "*");
      lcd.setCursor(2, 1); PrintSensor(sens_ulica, spTemp, false);
      lcd.setCursor(13, 1); PrintSensor(sens_ulica, spHumi, false);
      lcd.setCursor(11, 2); PrintSensor(sens_ulica, spHumiABS); //lcd.print(" gm3"); // НЕ Затирается предыдуще значение !!!!!!!!!!!!!!!! VVVVVVVVVV
      lcd.setCursor(10, 3); PrintSensor(sens_ulica, spPress); //lcd.print(" mm");
      break;
    case 1:
      if (f_full) {
        lcd.setCursor(0, 0); lcd.print(F("View Sens 1-3"));
        lcd.setCursor(0, 1); lcd.print(F("Sens:  S1   S2   S3"));
//          PrintTextLCD(0, 0, "View Sens 1-3");
//          PrintTextLCD(0, 1, "Sens:  S1   S2   S3");
//        PrintTextLCD(0, 0, "View"); Print_Space(); PrintTextLCD("Sens"); Print_Space(); PrintTextLCD("1-3");
//        PrintTextLCD(0, 1, "Sens"); PrintColon(); Print_Space(2); PrintTextLCD("S"); PrintTextLCD("1"); Print_Space(3);
//        PrintTextLCD("S"); PrintTextLCD("2"); Print_Space(3); PrintTextLCD("S"); PrintTextLCD("3");
        lcd.setCursor(0, 2); Print_TextTemp(true);
        lcd.setCursor(0, 3); Print_TextHumi(true);
      }
      PrintMeasuring(14, 0, "<meas>");
      lcd.setCursor(6, 2); PrintSensor(0, spTemp);
      lcd.setCursor(6, 3); PrintSensor(0, spHumi);
      lcd.setCursor(11, 2); PrintSensor(1, spTemp);
      lcd.setCursor(11, 3); PrintSensor(1, spHumi);
      lcd.setCursor(16, 2); PrintSensor(2, spTemp);
      lcd.setCursor(16, 3); PrintSensor(2, spHumi);
      break;
    case 2:
      if (f_full) {
        lcd.setCursor(0, 0); lcd.print(F("View Sens 4-6"));
        lcd.setCursor(0, 1); lcd.print(F("Sens:  S4   S5   S6"));
//        PrintTextLCD(0, 0, "View"); Print_Space(); PrintTextLCD("Sens"); Print_Space(); PrintTextLCD("4-6");
//        PrintTextLCD(0, 1, "Sens"); PrintColon(); Print_Space(2); PrintTextLCD("S"); PrintTextLCD("4"); Print_Space(3);
//        PrintTextLCD("S"); PrintTextLCD("5"); Print_Space(3); PrintTextLCD("S"); PrintTextLCD("6");        
        lcd.setCursor(0, 2); Print_TextTemp(true);
        lcd.setCursor(0, 3); Print_TextHumi(true);
      }
      PrintMeasuring(14, 0, "<meas>");
      lcd.setCursor(6, 2); PrintSensor(3, spTemp);
      lcd.setCursor(6, 3); PrintSensor(3, spHumi);
      lcd.setCursor(11, 2); PrintSensor(4, spTemp);
      lcd.setCursor(11, 3); PrintSensor(4, spHumi);
      lcd.setCursor(16, 2); PrintSensor(5, spTemp);
      lcd.setCursor(16, 3); PrintSensor(5, spHumi);
      break;
    case 3:
      if (f_full) {
        lcd.setCursor(0, 0); lcd.print(F("View Channels: 1-4"));
        lcd.setCursor(0, 1); lcd.print(F("ChN: Ch1 Ch2 Ch3 Ch4"));
        lcd.setCursor(0, 2); lcd.print(F("Stat:"));
        lcd.setCursor(0, 3); lcd.print(F("Cnt:"));
      }
      PrintChannelsState(true);   //Печатаем состояния каналов
      break;
    case 4:
      if (f_full) {
        lcd.setCursor(0, 0); lcd.print(F("View Channels: 5-8"));
        lcd.setCursor(0, 1); lcd.print(F("ChN: Ch5 Ch6 Ch7 Ch8"));
        lcd.setCursor(0, 2); lcd.print(F("Stat:"));
        lcd.setCursor(0, 3); lcd.print(F("Cnt:"));
      }
      PrintChannelsState(false);   //Печатаем состояния каналов
      break;
    case 5:
      lcd.print(F("InfoPadval"));
      lcd.setCursor(5, 2); lcd.print(F("NO SENSOR"));
      break;
    case 6:
      //      lcd.print(F("Info1"));
      //      lcd.setCursor(2, 1); lcd.print(F("!SENSOR ERROR!"));
      break;
  }
}

void PrintChannelsState(bool isFirst) {
  byte shift;
  if (isFirst) shift = 0; else shift = 4;
  for (byte n = 0; n < 4; n++) {
    lcd.setCursor(5 + n * 4, 2);
    lcd.print(channels[n + shift].chEnable ? (channels[n + shift].chIsWork ? "On" : "Off") : "---");  //<+> <->

    //if (channels[n + shift].workCnt[0] < 10) lcd.setCursor(6 + n * 4, 5); else lcd.setCursor(5 + n * 4, 5);
    if (channels[n + shift].chEnable) {
      lcd.setCursor((channels[n + shift].workCnt[0] < 10 ? 6 : 5) + n * 4, 5);
      lcd.print(channels[n + shift].workCnt[0]);
    }
  }
}

void UpdateServiceScreen (bool f_full) {
  switch (ScreenIndex) {
    case 0:
      if (f_full) {
        lcd.setCursor(0, 0); lcd.print("Device Info");
        lcd.setCursor(0, 1); lcd.print("  Time:");
        lcd.setCursor(0, 3); lcd.print("UpTime:");
      }
      PrintMeasuring(14, 0, "<meas>");
      lcd.setCursor(8, 1); lcd.print("22:53");
      lcd.setCursor(8, 3); PrintTimeWorkCh(millis() / 1000); //Сделать по другому вывод аптайма
      break;
    case 1:
      lcd.setCursor(0, 0); lcd.print(F("SetParam Kesson"));
      lcd.setCursor(0, 1); lcd.print(F("Using Humi: Yes"));
      lcd.setCursor(1, 2); lcd.print(F("TOn:"));
      lcd.setCursor(0, 3); lcd.print(F("TOff:"));
      lcd.setCursor(11, 2); lcd.print(F("HOn:"));
      lcd.setCursor(10, 3); lcd.print(F("HOff:"));
      break;
    case 2:
      lcd.print("Service");
      lcd.setCursor(0, 2); lcd.print(arSens[2].data.T); //Убрать
      lcd.setCursor(0, 3); lcd.print(arSens[2].data.H); //Убрать
      break;
  }
  //lcd.print("   Err: "); lcd.print(arSens[ScreenIndex].sStatus);
}

void UpdateSensorsScreen(bool f_full) {
  if (f_full) {
    lcd.print(F("ViewSens: "));
    lcd.print(SensTitle[Menu->SelectItem - 1]);
    lcd.setCursor(0, 1);
    switch (ScreenIndex) {
      case 0:
        lcd.print(F("Type:")); lcd.print(SensTypeStr[arSens[Menu->SelectItem - 1].sensType]);
        lcd.print(F(" Pin:"));  if (arSens[Menu->SelectItem - 1].pinSens == pin_I2C) lcd.print("<I2C>"); else {
          if (arSens[Menu->SelectItem - 1].pinSens < 10) {
            lcd.print("< "); lcd.print(arSens[Menu->SelectItem - 1].pinSens); lcd.print(" >");
          }
          else lcd.print("< "); lcd.print(arSens[Menu->SelectItem - 1].pinSens); lcd.print(">");
        }
        lcd.setCursor(0, 2); Print_TextTemp(false); lcd.setCursor(10, 2); lcd.print(F("P:"));
        lcd.setCursor(0, 3); Print_TextHumi(false); lcd.setCursor(9,  3); lcd.print(F("Ha:"));
        break;
      case 1:
        lcd.setCursor(5, 1); lcd.print(F("TEMP of Day"));
        lcd.setCursor(0, 2); lcd.print(F("Max:")); lcd.setCursor(12, 2); lcd.print(F("h:"));
        lcd.setCursor(0, 3); lcd.print(F("Min:")); lcd.setCursor(12, 3); lcd.print(F("h:"));
        break;
      case 2:
        lcd.setCursor(5, 1); lcd.print(F("HUMI of Day"));
        lcd.setCursor(0, 2); lcd.print(F("Max:")); lcd.setCursor(11, 2); lcd.print(F("t:"));
        lcd.setCursor(0, 3); lcd.print(F("Min:")); lcd.setCursor(11, 3); lcd.print(F("t:"));
        break;
      case 3:
        //        lcd.print("     TEMP    HUMI");
        //        lcd.setCursor(0, 2); lcd.print("Max:                ");
        //        lcd.setCursor(0, 3); lcd.print("Min:                ");
        break;
    }
  }
  PrintMeasuring(9, 0, "*");
  switch (ScreenIndex) {
    case 0:
      lcd.setCursor(2, 2); PrintSensor(Menu->SelectItem - 1, spTemp, false);
      lcd.setCursor(13, 2); PrintSensor(Menu->SelectItem - 1, spPress, false);
      lcd.setCursor(2, 3); PrintSensor(Menu->SelectItem - 1, spHumi, false);
      lcd.setCursor(13, 3); PrintSensor(Menu->SelectItem - 1, spHumiABS);
      break;
    case 1: PrintSensorMinMax(Menu->SelectItem - 1, spTemp); break;   //Вывод MaxMin по температуре
    case 2: PrintSensorMinMax(Menu->SelectItem - 1, spHumi); break;   //Вывод MaxMin по влажности
    case 3:
      //lcd.setCursor(4, 2); lcd.print(" 99.9oC  99.9%");
      //lcd.setCursor(4, 3); lcd.print("-99.9oC  99.9%");
      break;
  }
}

void UpdateChannelsScreen(bool f_full) {
  if (f_full) {
    lcd.print(CannelsTitle[Menu->SelectItem - 1]); lcd.print(F(" - ")); lcd.print(ChScrName[ScreenIndex]);
    PrintCommonText();
  }
  switch (ScreenIndex) {
    case 0:                 //Вывод счетчиков работы канала
      lcd.setCursor(10, 1); lcd.print(F("0"));
      lcd.setCursor(10, 2); lcd.print(F("12"));
      lcd.setCursor(10, 3); lcd.print(F("24"));
      break;
    case 1:                 //Вывод времени работы канала
      lcd.setCursor(10, 1); PrintTimeWorkCh(100);
      lcd.setCursor(10, 2); PrintTimeWorkCh(712);
      lcd.setCursor(10, 3); PrintTimeWorkCh(459);
      break;
    case 2:
      lcd.setCursor(10, 1); PrintTimeWorkCh(00);
      lcd.setCursor(10, 2); PrintTimeWorkCh(618);
      lcd.setCursor(10, 3); PrintTimeWorkCh(0);
      break;
    case 3:

      break;
    case 4:

      break;
  }
}

void PrintTimeWorkCh(const uint32_t i) {
  if (i == 0) {
    lcd.print(F("-NoData-")); return;
  }
  char _str[9];
  int h = (i / 3600ul);
  int m = (i % 3600ul) / 60ul;
  int s = (i % 3600ul) % 60ul;
  sprintf(_str, "%d:%02d:%02ds", h, m, s);
  lcd.print(_str);
}

void PrintCommonText() {
  lcd.setCursor(0, 1); lcd.print(F("Curr Day:"));
  lcd.setCursor(0, 2); lcd.print(F("LastDay1:"));
  lcd.setCursor(0, 3); lcd.print(F("LastDay2:"));
}

void PrintFormatTemp(const int _temp, const bool _isShort) {
  char str[8];
  if (_isShort) {
    //_temp = _temp / 10;
    if (_temp < -99 || _temp > 99) sprintf(str, "%3dC", _temp / 10);
    else if (_temp < 0) sprintf(str, "%d\1C", _temp / 10);
    else sprintf(str, "%2d\1C", _temp / 10);
    str[4] = '\0';
  } else {
    if (_temp < -99) sprintf(str, "%d.%d\1C", _temp / 10, abs(_temp % 10));
    else sprintf(str, "%3d.%d\1C", _temp / 10, abs(_temp % 10));
    //str[7] = '\0';
  }
  lcd.print(str);
}

void PrintFormatHumi(const int _humi, const bool _isShort) {
  char str[6];
  if (_isShort) sprintf(str, "%3d", _humi / 10);
  else sprintf(str, "%3d.%d", _humi / 10, abs(_humi % 10));
  lcd.print(str); lcd.print('%');
}

void PrintFormatHumiABS(int t, int h) {
  float f = calculationAbsH((float)t  / 10.0, (float)h / 10.0);
  lcd.print(f); if (f < 10.0) lcd.print(' ');
}

void PrintFormatPressure(const int _press, const bool _isShort) {
  lcd.print((float)_press / 10.0, 1);
}

void PrintSensor(const byte _nSens, const TSensorParam _sParam, const bool _isShortFormat = true) {
  if (_nSens >= SENS_COUNT) {
    lcd.print(_isShortFormat ? " -- " : " ----- "); return;
  }
  if (arSens[_nSens].sStatus == ssInit) {
    lcd.print(_isShortFormat ? "-is-" : "  -is- "); return;
  }
  if (arSens[_nSens].sStatus == ssNoSens) {
    lcd.print(_isShortFormat ? "-ns-" : "  -ns- "); return;
  }
  if (_sParam == spPress && !arSens[_nSens].isPressure) {
    lcd.print(_isShortFormat ? " -- " : " ----- "); return;
  }
  if (arSens[_nSens].sStatus == ssNoData) {
    lcd.print(_isShortFormat ? "-nd-" : "  -nd- "); return;
  }
  if (arSens[_nSens].sStatus == ssUnknownErr) {
    switch (_sParam) {
      case spTemp:    lcd.print(_isShortFormat ? "TErr" : " -TErr-"); break;
      case spHumi:    lcd.print(_isShortFormat ? "HErr" : " -HErr-"); break;
      case spHumiABS: lcd.print(_isShortFormat ? "HErr" : " -HErr-"); break;
      case spPress:   lcd.print(_isShortFormat ? "PErr" : " -PErr-"); break;
    }
    return;
  }
  if (arSens[_nSens].sStatus == ssReading) {
    bool f;
    switch (_sParam) {
      case spTemp:    f = (arSens[_nSens].data.T == NO_DATA); break;
      case spHumi:    f = (arSens[_nSens].data.H == NO_DATA); break;
      case spHumiABS: f = (arSens[_nSens].data.H == NO_DATA); break;
      case spPress:   f = (arSens[_nSens].data.P == NO_DATA); break;
    }
    if (f) {
      lcd.print(_isShortFormat ? "-rs-" : "  -rs- ");
      return;
    }
  }
  if (arSens[_nSens].sStatus != ssOk) if (arSens[_nSens].sStatus != ssReading) return;
  switch (_sParam) {
    case spTemp:    PrintFormatTemp(arSens[_nSens].data.T, _isShortFormat); break;
    case spHumi:    PrintFormatHumi(arSens[_nSens].data.H, _isShortFormat); break;
    case spHumiABS: PrintFormatHumiABS(arSens[_nSens].data.T, arSens[_nSens].data.H); break;
    case spPress:   PrintFormatPressure(arSens[_nSens].data.P, _isShortFormat); break;
  }
}

bool VerDataMaxMin (const int _maxmin) {
  return _maxmin < 1000 && _maxmin > -1000;
}

void PrintErrMaxMin(const TSensStatus _status) {
  if (_status != ssOk && _status != ssReading)
    lcd.print(F(" - - "));
  else lcd.print(F(" -- "));
}

void PrintSensorMinMax(const byte _nSens, const TSensorParam _sParam) {
  switch (_sParam) {
    case spTemp:  // maxT
      if (VerDataMaxMin(arSens[_nSens].data.maxT.t)) {
        lcd.setCursor(4, 2);  PrintFormatTemp(arSens[_nSens].data.maxT.t, false);
        lcd.setCursor(14, 2); PrintFormatHumi(arSens[_nSens].data.maxT.h, false);
      } else {
        lcd.setCursor(4, 2);  PrintErrMaxMin(arSens[_nSens].sStatus);
        lcd.setCursor(14, 2); PrintErrMaxMin(arSens[_nSens].sStatus);
      }           // minT
      if (VerDataMaxMin(arSens[_nSens].data.minT.t)) {
        lcd.setCursor(4, 3);  PrintFormatTemp(arSens[_nSens].data.minT.t, false);
        lcd.setCursor(14, 3); PrintFormatHumi(arSens[_nSens].data.minT.h, false);
      } else {
        lcd.setCursor(4, 3);  PrintErrMaxMin(arSens[_nSens].sStatus);
        lcd.setCursor(14, 3); PrintErrMaxMin(arSens[_nSens].sStatus);
      }
      break;
    case spHumi:  // maxH
      if (VerDataMaxMin(arSens[_nSens].data.maxH.h)) {
        lcd.setCursor(4, 2);  PrintFormatHumi(arSens[_nSens].data.maxH.h, false);
        lcd.setCursor(13, 2); PrintFormatTemp(arSens[_nSens].data.maxH.t, false);
      } else {
        lcd.setCursor(4, 2);  PrintErrMaxMin(arSens[_nSens].sStatus);
        lcd.setCursor(13, 2); PrintErrMaxMin(arSens[_nSens].sStatus);
      }           // minH
      if (VerDataMaxMin(arSens[_nSens].data.minH.h)) {
        lcd.setCursor(4, 3); PrintFormatHumi(arSens[_nSens].data.minH.h, false);
        lcd.setCursor(13, 3); PrintFormatTemp(arSens[_nSens].data.minH.t, false);
      } else {
        lcd.setCursor(4, 3);  PrintErrMaxMin(arSens[_nSens].sStatus);
        lcd.setCursor(13, 3); PrintErrMaxMin(arSens[_nSens].sStatus);
      }
      break;
  }
}

bool ShowYN(char * str1, char * str2, char * str3) {
  long now = millis();
  long endTime = now + 15000;
  long tmUpd;
  bool fspace = true;           //Для моргания параметра. true - затирает
  bool fYes = false;            //Отображаемый параметр на экране true - Yes false - No
  bool fUpdScr = true;          //Для обновления экрана
  bool Result = false;          //Результат диалога
  lcd.clear(); lcd.print(str1);  //Вывод сообщения
  lcd.setCursor(0, 1); lcd.print(str2);
  lcd.setCursor(0, 2); lcd.print(str3);
  lcd.setCursor(0, 3); lcd.print(F("AutoExit in:    sec"));
  tmUpd = now;
  while (now < endTime) {
    now = millis();
    //fspace = (now / 350) % 2; //Каждые 350 мск переключать fspace - "тяжелый" вариант
    if (now - tmUpd >= 350) {
      fspace = !fspace;
      fUpdScr = true;
      tmUpd = now;
    }
    if (encTick) {              //Обработка энкодера (вращение)
      fYes = encTick == 1;
      endTime = now + 10000;
      fUpdScr = true;
    }
    encTick = 0;
    if (fUpdScr) {
      lcd.setCursor(13, 3); lcd.print((endTime - now) / 1000 + 1); lcd.print(' ');
      lcd.setCursor(15, 0);
      lcd.print(fspace ? "   " : (fYes ? "Yes" : "No "));
      fUpdScr = false;
    }
    if (swEnc.Loop() == SB_CLICK) {
      Result = fYes;
      break;
    }
    //тут вызывать обработку датчиков
  }
  UpdateScreens(true);
  return Result;
}

//------------------------------ Print Helpers ----------------------------
void PrintMeasuring(const byte _col, const byte _row, char * str) {
  if (reading) PrintTextLCD(_col, _row, str);
  else {
    lcd.setCursor(_col, _row); Print_Space(strlen(str));
  }
}

void PrintArrowR() {
  PrintChLCD('>');
}

void PrintArrowL() {
  PrintChLCD('<');
}

void PrintSpaceArrowR() {
  PrintTextLCD(" >");
}

void PrintSpaceArrowL() {
  PrintTextLCD(" <");
}

void PrintColon() {
  PrintChLCD(':');
}

void Print_TextTemp (bool isLong) {
  if (isLong) PrintTextLCD("Temp"); else PrintTextLCD("T");
  PrintColon();
}

void Print_TextHumi (bool isLong) {
  if (isLong) PrintTextLCD("Humi"); else PrintTextLCD("H");
  PrintColon();
}

void PrintTextLCD(const byte _col, const byte _row, char * str) {
  lcd.setCursor(_col, _row); PrintTextLCD(str);
}

void PrintTextLCD(char * str) {
  lcd.print(str);
}

void PrintChLCD(char ch) {
  lcd.print(ch);
}

void Print_Space(const byte _cnt = 1) {
  if (_cnt < 1) return;
  if (_cnt == 1) PrintChLCD(' ');
  else for (byte n = _cnt; n; --n) PrintChLCD(' ');
}

 

wdrakula
wdrakula аватар
Offline
Зарегистрирован: 15.03.2016

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

Оценка беглая - нормально. У нас тут есть люди из Делфи вышедшие - по неймингу переменных видны за километр! ;)) Это как раз очень достойная привычка, тут только похвалить можно!

Ошибка та, что по кусочку было видно. Попытка писать для МК, как для ПК. То есть заложенная в код универсальность аж до выбора типа датчиков. В таком виде в МК - по крайней мере в Нану - почти нереально.

Если ты разрабатываешь полукоммерческий проект, в котором планируется настройка параметров пользователями без квалификации, то придется пойти одним из двух путей:

1. Выбрать другой МК, в котором будет достаточно дури для ран-тайм переключения датчиков и изменения настроек.

2. Удобный генератор прошивки, по параметрам и выведенный разъем ISCP для перешивки. В прочем можно и через USB, хоть это и варварство.

--------------------

Про системы МК в проекте.

Что CAN, что RS-485 прекрасно подойдут для организации связи на короткие расстояния в условиях промышленных помех. Их для этого и создавали. На судах, вот прям Больших Железных Пароходах, RS-485 называется NMEA-0183, а CAN - NMEA-2000. И этими шинами соединяются все датчики, экраны, эхолоты, радары и черт-знает что на десятки метров по судну.

Реализация на RS-485 будет чутка подешевле, но очень желательно не использовать Нано, из-зи USB трансивера на контактах UART. ТО есть взять Мини, если вот прям охуительно нужен atmega328. Для 485 потребуется трансивер MAX485 - 50 руб на Али. Модуль занимает UART, что понятно.

Реализация CAN - чуть сложнее и чуть дороже. Модуль MCP2515 содержит и контроллер и трансивер CAN в одном флаконе. И встроенный терминатор, то есть вообще полный сервис ;). Стоит примерно 200р на Али. Занимает SPI.

На 485 реализация шины - программная, как понятно из протокола. Менее устойчива к сбоям. CAN - само-собой уже "мерседес", по сравнения с "запорожцем"-485. Но в твоей задаче разделение на функциональные блоки просматривается очень натянутым.

В теории, если рассматривать более гибкую и очччч-чень масштабируемую систему, то можно делать такие модули (на КАН или на 485)

1. модуль сенсоров, либо автоопределяет либо получает от Центра Управления (ЦУ) типы сенсоров, период опроса, номера контактов. Передает данные сенсоров.

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

3. Модуль GUI - тут ясно, экран и кнопки/джойстик/энкодер. Настраиваются типы сообщений.

4. Ну и модуль ЦУ ;))) - Кольцо Всевластия! ;))

------------------------

Ты заикнулся про I2C - это внутрисхемеая шина, не предназначена для соединения модулей вне одной платы, или хотя бы одного корпуса ;).

sadman41
Offline
Зарегистрирован: 19.10.2016

DHT11, DHT22, AM2320 в сущности один и тот же датчик с одним и тем же протоколом. Достаточно выбрать один получше и не тащить в код саппорт всего.

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

AsNik
Offline
Зарегистрирован: 24.10.2020

wdrakula пишет:

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

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

AsNik
Offline
Зарегистрирован: 24.10.2020

wdrakula пишет:

Если ты разрабатываешь полукоммерческий проект

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

AsNik
Offline
Зарегистрирован: 24.10.2020

wdrakula пишет:

У нас тут есть люди из Делфи вышедшие - по неймингу переменных видны за километр! ;))

Мною уважаемый дед ...например)

sadman41
Offline
Зарегистрирован: 19.10.2016

На snprintf_p легко заменяются все подобные гроздья: PrintTextLCD(0, 0, "Sens"); PrintColon(); Print_Space(); PrintTextLCD(SensTitle[sens_ulica]);

Шаблон для snprintf_p так же закладывается в progmem достаточно просто. Тем более, что всякие printf-ы в коде есть и они уже отожрали свой кусок.

AsNik
Offline
Зарегистрирован: 24.10.2020

sadman41 пишет:
DHT11, DHT22, AM2320 в сущности один и тот же датчик с одним и тем же протоколом. Достаточно выбрать один получше и не тащить в код саппорт всего.

Ну плюс/минус (больше плюс) - согласен. Тем более текущая либа сама определяет тип датчика.

sadman41 пишет:
Много строк в интерфейсе, даже повторяющиеся есть. Достаточно в PROGMEM залепить их и пользоваться указателем. Плюс - сократить число вариаций написания.

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

Экспериментировал с F() и "одинаковыми" константами, результат не очень... туда сюда прыгает размер.

А мне хочется и ОЗУ освободить и флеш не занимать) ...шутка

AsNik
Offline
Зарегистрирован: 24.10.2020

sadman41 пишет:
На snprintf_p легко заменяются все подобные гроздья: PrintTextLCD(0, 0, "Sens"); PrintColon(); Print_Space(); PrintTextLCD(SensTitle[sens_ulica]); Шаблон для snprintf_p так же закладывается в progmem достаточно просто. Тем более, что всякие printf-ы в коде есть и они уже отожрали свой кусок.

Если не сложно, можно какойнить пример по этому делу? Если я суть пойму, я разберусь...

sadman41
Offline
Зарегистрирован: 19.10.2016

F() интересен только если строки разные. На одинаковых он экономии не даст, даже будет плюсовать. А вот char progmem поможет.

wdrakula
wdrakula аватар
Offline
Зарегистрирован: 15.03.2016

AsNik пишет:

wdrakula пишет:

Если ты разрабатываешь полукоммерческий проект

Вообще нисколько.... так, для себя

Тогда и правда перестань валять дурака! Закажи Мегу, в форм факторе mini pro вот такую:

На ней достаточно всего и форма удобная. У меня такая сейчас отопителем на Кемпере управляет:

датчики темпераруры и NTC и Далласы, датчик оборотов двигателя продувки камеры сгорания, датчик 220, датчик тока основного вентилятора, термопредохранители, ионный датчик пламени. Каналы управления: и ТЭНы на 220 и искра поджига и клапаны газа, ШИМ на вентилятор и ШИМ на продувку. ГУИ на Некстионе. И все это включено 10 сентября и более не выключается до сих пор. ;))) То есть и в дороге и на рыбалке и сейчас стоит на зиму укрыт, но не слита вода пока, отопление в нем работает. Это очень хороший контроллер - Честное слово! ;)

==================================

А на Нанках детям гирлянду на НГ сделаешь на лампочках WS2811. Ну или какой диммер для дома для семьи! Не пропадут.

 

sadman41
Offline
Зарегистрирован: 19.10.2016

AsNik пишет:

Если не сложно, можно какойнить пример по этому делу? Если я суть пойму, я разберусь...


Тут должно быть:
http://arduino.ru/forum/programmirovanie/progmem-tricks

AsNik
Offline
Зарегистрирован: 24.10.2020

sadman41 пишет:
F() интересен только если строки разные. На одинаковых он экономии не даст, даже будет плюсовать. А вот char progmem поможет.

Нашел пример использования:

const char string_0[] PROGMEM = "Temp";   // "String 0" и т.д. - строки для хранения - измените на подходящие.
const char string_1[] PROGMEM = "Humi";
const char string_2[] PROGMEM = "Min";
const char string_3[] PROGMEM = "Max";
const char string_4[] PROGMEM = "On";
const char string_5[] PROGMEM = "Off"; //...и другие... так же


const char* const string_table[] PROGMEM = {string_0, string_1, string_2, string_3, string_4, string_5};

char buffer[30];    // убедитесь, что буфер достаточно велик для самой большой строки, которую он должен удерживать

void setup()
{
  Serial.begin(9600);
  while(!Serial);
  Serial.println("OK");
}

void PrintTextIndex(const byte _i) {
  Serial.println(strcpy_P(buffer, (char*)pgm_read_word(&(string_table[_i]))));
}

void loop()
{
  for (int i = 0; i < 6; i++)
  {
    PrintTextIndex(i); //Вместо сериал lcd.print....
    delay( 500 );
  }
}

Нормальный пример? (немного исправил)

AsNik
Offline
Зарегистрирован: 24.10.2020

wdrakula пишет:

 Закажи Мегу, в форм факторе mini pro вот такую:

Обязательно.... и без шуток. Давно уже на нее гляжу.... И как назло сейчас цены на всю эту электронику немного скаканули :( ...Но пока всеж потренирую мозг на нане... (или нанах)

AsNik
Offline
Зарегистрирован: 24.10.2020

sadman41 пишет:
AsNik пишет:

Если не сложно, можно какойнить пример по этому делу? Если я суть пойму, я разберусь...

Тут должно быть: http://arduino.ru/forum/programmirovanie/progmem-tricks

Спасибо.

wdrakula
wdrakula аватар
Offline
Зарегистрирован: 15.03.2016

AsNik пишет:
... надо?

Мне??  ;))

Поясню: я сижу на кухне, за компом, за спиной стоит ректификационная колонна, за которой я предпочитаю следить. На колонне автоматика, но мерные щелчки клапана отбора действуют на меня умиротворяюще! ;)))

Следовательно я привязан к кухне на несколько часов. Если тебе надо, чтобы я посмотрел на код - сделай мне удобно, если тебе не надо, то у меня есть анекдоты и порнуха в сети ...и котеги, конечно!;))) 

Библиотеки не нужно, если у тебя нет привязки к особенной версии. (Её не должно быть в нормальном коде)

-----------------

Ну и конечно нужно предупредить, что через несколько часов результат работы колонны будет подвергнут вдумчивому тестированию, несовместимому с оценкой программного кода. ;)

AsNik
Offline
Зарегистрирован: 24.10.2020

AsNik пишет:

Нашел пример использования:

const char string_0[] PROGMEM = "Temp";   // "String 0" и т.д. - строки для хранения - измените на подходящие.
const char string_1[] PROGMEM = "Humi";
const char string_2[] PROGMEM = "Min";
const char string_3[] PROGMEM = "Max";
const char string_4[] PROGMEM = "On";
const char string_5[] PROGMEM = "Off"; //...и другие... так же


const char* const string_table[] PROGMEM = {string_0, string_1, string_2, string_3, string_4, string_5};

char buffer[30];    // убедитесь, что буфер достаточно велик для самой большой строки, которую он должен удерживать

void setup()
{
  Serial.begin(9600);
  while(!Serial);
  Serial.println("OK");
}

void PrintTextIndex(const byte _i) {
  Serial.println(strcpy_P(buffer, (char*)pgm_read_word(&(string_table[_i]))));
}

void loop()
{
  for (int i = 0; i < 6; i++)
  {
    PrintTextIndex(i); //Вместо сериал lcd.print....
    delay( 500 );
  }
}

Сделал так:

const char string_0[] PROGMEM = "Temp";   // "String 0" и т.д. - строки для хранения - измените на подходящие.
const char string_1[] PROGMEM = "Humi";
const char string_2[] PROGMEM = "Min";
const char string_3[] PROGMEM = "Max";
const char string_4[] PROGMEM = "On";
const char string_5[] PROGMEM = "Off";

const char * const string_table[] PROGMEM = {string_0, string_1, string_2, string_3, string_4, string_5};

void setup() {
  Serial.begin(9600); while (!Serial); Serial.println("OK");
}

char bufStr[10];
char * GetStringIntex(const byte _i) {
  return strcpy_P(bufStr, (char*)pgm_read_word(&(string_table[_i])));
}

void PrintTextIndex(const byte _i) {
  char buffer[10];
  Serial.println(strcpy_P(buffer, (char*)pgm_read_word(&(string_table[_i])))); //Вместо сериал lcd.print....
}

void loop() {
  Serial.println("PrintTextIndex");
  for (int i = 0; i < 6; i++)  {
    PrintTextIndex(i);
    delay( 500 );
  }
  Serial.println("GetStringIntex");
  for (int i = 0; i < 6; i++)  {
    Serial.println(GetStringIntex(i)); //Вместо сериал lcd.print....
    delay( 500 );
  }
}

Правильно ли я сделал? Указатели, ...я в них путаюсь(

И вопрос еще. Тут получается мне нужно помнить какой индекс у строки. Мне сразу увиделся вариант добавить enum типа (strTemp, strHumi ...) и вызывать PrintTextIndex(strTemp). Но это опять расход памяти... Или не парится и вызывать по индексу?

А напрямую нельзя обратится к string_0 (переименовав ее например strTemp т.е.:

const char strTemp[] PROGMEM = "Temp";)?

AsNik
Offline
Зарегистрирован: 24.10.2020

wdrakula пишет:

AsNik пишет:
... надо?

Мне??  ;))

Ок. Сейчас слеплю

Цитата:
ректификационная колонна
За ней глаз до глаз и периодические пробы...)

Так, ну вот в таком виде у меня скомпилировалось:

#include <LiquidCrystal_I2C.h>

#define NO_DATA   9999          //Нет данных
#define NO_SUPP   9998          //Не поддерживается

//---------------------- PINS ---------------------------------

#define ENC_A   2               //ПинА энкодера
#define ENC_B   3               //ПинВ энкодера
#define ENC_SW  4               //Пин кнопки энкодера

//Входы датчиков
#define IND_1   5
#define IND_2   6
#define INA_1   15
#define INA_2   16

#define TESTPOWERPIN 8          // Для ТЕСТА Питание!!!!!!!!!!!!!!!!!!!!!!
//---------------------- END PINS -----------------------------

#define START_MENU 0            // 1 - при старте отображать меню, 0 - Отображать инфо экран датчика

//---------------------- СЕНСОРЫ ------------------------------
#include <iarduino_DHT.h>
#include <iarduino_AM2320.h>
#include <BME280I2C.h>
#include <Wire.h>


extern bool reading;    //Объявлена в файле Sensors

enum TPins {pin_I2C = 0, pin_IND1 = IND_1, pin_IND2 = IND_2, pin_INA1 = INA_1, pin_INA2 = INA_2};   //Возможно этот enum лишний
enum TSensStatus {ssOk = 0, ssReading, ssInit, ssNoSens, ssNoData, ssUnknownErr};
enum TSensorParam {spTemp, spHumi, spHumiABS, spPress};

enum TSensType {stDHT11, stDHT22, stAM, stBME, COUNT_TYPES};                    //Тип сенсора
const char * SensTypeStr[COUNT_TYPES] = {"<D11>", "<D22>", "<AM >", "<BME>"};   //Тип сенсора строка

//#define SENS_MAX   6            //Максимальное число подключаемых сенсоров
enum TSensLocate {sens_ulica, sens1_kesson1, sens2_kesson1, /*podval, sens5, sens6*/ SENS_COUNT};     //Размещение сенсора и индекс в массиве сенсоров. SENS_COUNT Кол-во подключенных сенсоров
const char * SensTitle[SENS_COUNT] = {"Ulica", "Kes1Sens1", "Kes1Sens2", /*"Podval", "s5", "s6"*/};   //Строковое описание сенсора. Макс 10 символов

struct TMaxMinTH {
  int t, h;
};

struct TSensorData {                    //Структура данных сенсора
  int T, tmpT, lastT;                   //Данные по температуре
  int H, tmpH, lastH;                   //Данные по влажности
  TMaxMinTH minT, maxT, minH, maxH;     //Данные по минимальной и максимальной температуре и влажности
  uint16_t P, tmpP, lastP, minP, maxP;  //Данные по давлению
};

struct TSensor {              //Структура сенсора
  void * sensor;                  //Указатель на сенсор
  byte sStatus;                   //Состояние сенсора. 0 - OK
  const bool isPressure;          //Поддержка у сенсора датчика атмосферного давления
  TSensType sensType;             //Тип сенсора
  TPins pinSens;              //Возможно это лишнее (избыточно TPins)
  TSensorData data;               //Данные сенсора
};

TSensor arSens [SENS_COUNT] = {                       //Массив сенсоров
  //{new iarduino_DHT(pin_IND1), ssInit, false, stDHT22, pin_IND1, {}}, //Убрать ТЕСТ
  {new BME280I2C, ssInit, true, stBME, pin_I2C, {}},                    //(sens_ulica) Сенсор улица
  {new iarduino_DHT(pin_IND1), ssInit, false, stDHT22, pin_IND1, {}},    //(sens1_kesson1)
  {new iarduino_DHT(pin_IND2), ssInit, false, stDHT11, pin_IND2, {}},    //(sens2_kesson1)
};

//-------------------------- end SENSORS ------------------------------

//------------------------ КАНАЛЫ ------------------------
#define CANNELS_COUNT 8
const char * CannelsTitle[CANNELS_COUNT] = {"Channel1", "Channel2", "Channel3", "Channel4", "Channel5", "Channel6", "Channel7", "Channel8"};      //Названия каналов макс 8 символов
struct Channel {                       //Данные канала
  //char chTitle[10];                  //Описание канала
  bool chEnable: 1;                    //Общее состояние канала
  bool chIsWork: 1;                    //Признак работы канала
  byte workCnt[3];                     //Счетчики срабатываний канала сегодня/вчера/позавчера
  uint32_t workTime;                   //Продолжительность работы канала, по завершению плюсуется к timesWork[0]
  uint32_t workTimes[3];               //Общее время работы канала сегодня/вчера/позавчера
  byte chError;                        // 0 = Ok, 1 - отсутствует, 2 - ошибка чтения.... в этом духе
};
Channel channels[CANNELS_COUNT];
//--------------------------------------------------------

// ------------------------ МЕНЮ -------------------------

struct TMenu {
  byte ItemCount;               //Кол-во итемов в меню
  byte ShowItemCount;           //Кол-во итемов отображаемых на экране. Может быть 2, 3, 4
  byte SelectItem;              //Выбранный пункт меню
  byte LastActiveItem;          //Активный пункт меню перед сменой меню
  byte LastTopItem;             //Верхний видимый пункт меню перед сменой меню
  const char **Items;           //Названия пунктов меню
};
TMenu *Menu;

const char *forMainMenu[] = {     //Названия пунктов главного меню
  "Info",
  "Sensors",
  "Channels",
  "Service"
};
TMenu MainMenu {sizeof(forMainMenu) / sizeof(forMainMenu[0]), 4, 0, 0, 0, forMainMenu};  //Главное меню

const char *forSensMenu[SENS_COUNT + 1] = {     //Названия пунктов меню сенсоров
  "Sens1",
  "Sens2",
  "Sens3",
  //"Sens4",
  //"Sens5",
  //"Sens6",
  "(Back)"
};
TMenu SensMenu {sizeof(forSensMenu) / sizeof(forSensMenu[0]), 3, 0, 0, 0, forSensMenu};   //Меню сенсоров

const char *forChannelsMenu[] = {     //Названия пунктов меню каналов
  "Chnl_1",
  "Chnl_2",
  "Chnl_3",
  "Chnl_4",
  "Chnl_5",
  "Chnl_6",
  "Chnl_7",
  "Chnl_8",
  "(Back)"
};
TMenu ChannelsMenu {sizeof(forChannelsMenu) / sizeof(forChannelsMenu[0]), 3, 0, 0, 0, forChannelsMenu};   //Меню каналов
//--------------------------------------------------------

//Экраны
bool isScreens = false;                               //true когда показаны экраны, а не меню
byte ScreenIndex, ScreenCount = 4;                    //Индекс текущего экрана, количество текущих экранов
byte InfoScreenIndex = 0, InfoScreenCount = 6;        //Индекс и кол-во информационных экранов
byte ServiceScreenIndex = 0, ServiceScreenCount = 3;  //.... сервисных экранов
byte SensScreenIndex = 0, SensScreenCount = 4;        //... экранов сенсоров
byte ChannelScreenIndex = 0, ChannelScreenCount = 3;  //.... экранов каналов

typedef enum {
  SB_NONE = 0,
  SB_DOWNED,
  SB_CLICK,
  SB_LONG_CLICK,
  SB_RELEASE
} SBUTTON_CLICK;


class SButton {
  private :
    uint8_t  Pin;
    bool     Downed;
    bool     State;
    bool     Long_press_state;
    uint32_t tmpMs;
    uint16_t TM_bounce;
    uint16_t TM_casual_click;
    uint16_t TM_long_click;
  public :
    SButton(uint8_t Button_pin, uint16_t tm_Bounce = 50, uint16_t tm_Casual = 200, uint16_t tm_Long = 1500);
    void begin();
    SBUTTON_CLICK Loop();
};

SButton::SButton(uint8_t button_Pin, uint16_t tm_Bounce, uint16_t tm_Casual, uint16_t tm_Long) {
  Pin               = button_Pin;
  Downed            = false;
  State             = false;
  Long_press_state  = false;
  tmpMs             = 0;
  TM_bounce         = tm_Bounce;
  TM_casual_click   = tm_Casual;
  TM_long_click     = tm_Long;
}

LiquidCrystal_I2C lcd(0x27, 20, 4);
SButton swEnc(ENC_SW, 10, 50, 1000);

byte gradus[8] = {B00010, B00101, B00010, B00000, B00000, B00000, B00000, B00000};    //Символ градуса
//byte slesh[8]  = {B00000, B10000, B01000, B00100, B00010, B00001, B00000, B00000};    //Символ обратного слеша;
byte arup[8]   = {B00100, B01110, B10101, B00100, B00100, B00000, B00000, B00000};    //Стрелка вверх
byte ardown[8] = {B00000, B00000, B00000, B00100, B00100, B10101, B01110, B00100};    //Стрелка вниз

void Print_Space(const byte _cnt = 1);

// ==================== ISR обработка прерываний ========================
volatile bool f_isrA = false;
volatile char encTick = 0;
void isrA() {
  f_isrA = true;
}

void isrB() {
  if (f_isrA) {        //Если было Прерывание Энодера A
    f_isrA = false;
    digitalRead(ENC_A) == digitalRead(ENC_B) ? encTick = 1 : encTick = -1;
  }
}
// ========================================= END ISR ====================================

void setup() {
  attachInterrupt(0, isrA, FALLING);    // прерывание на 2 пине! A у энка
  attachInterrupt(1, isrB, FALLING);    // прерывание на 3 пине! B у энка

  Wire.begin();
  static_cast<BME280I2C*>(arSens[0].sensor)->begin();
  swEnc.begin();
  lcd.init(); lcd.backlight();
  lcd.createChar(1, gradus);

  Menu = &MainMenu;
  if (START_MENU) UpdateMenu(true);
  else {
    Menu->SelectItem = 1;
    isScreens = true;
    ScreenIndex = InfoScreenIndex;
    ScreenCount = InfoScreenCount;
    UpdateScreens(true);
  }

  for (byte n = 0; n < SENS_COUNT; ++n) SetSensorStatus(n, ssInit); //Инициализация датчиков
  ResetMinMaxDay();

  //убрать. для теста
  channels[0].workCnt[0] = 9;
  channels[6].workCnt[0] = 79;
  channels[7].workCnt[0] = 253;
  channels[2].chIsWork = true;
  channels[7].chIsWork = true;

  pinMode(TESTPOWERPIN, OUTPUT);
}

void loop() {
  readEnc();
  readSens();
}

SBUTTON_CLICK CorrectButtonClick() {
  static bool fclick = false;         //Сделали клик кнопкой, а обрабатывается при отпускании кнопки
  switch (swEnc.Loop()) {
    case SB_DOWNED:
      break;
    case SB_CLICK:
      fclick = true;
      return SB_NONE;
      break;
    case SB_LONG_CLICK:
      fclick = false;
      return SB_LONG_CLICK;
    case SB_RELEASE:
      if (!fclick) break;
      fclick = false;
      return SB_CLICK;
      break;
    case SB_NONE:
      break;
  }
  return SB_NONE;
}

void readEnc() {
  bool isR = encTick == 1;    //Энкодер повернули вправо
  bool isL = encTick == -1;   //Энкодер повернули влево
  encTick = 0;
  if (!isScreens) CalckMenu(isR, isL, CorrectButtonClick());
  else CalckScreens(isR, isL, CorrectButtonClick());
}

byte activeItem = 0, topItem = 0;
void CalckMenu(bool _isR, bool _isL, SBUTTON_CLICK _sbc) {
  bool f_full = false;
  if (_isL && activeItem < Menu->ItemCount - 1) {
    activeItem++;
    if ((activeItem - topItem) > Menu->ShowItemCount - 1) {
      topItem++;
      f_full = true;
    }
    UpdateMenu(f_full);
  }
  if (_isR && activeItem > 0) {
    activeItem--;
    if (activeItem < topItem) {
      topItem--;
      f_full = true;
    }
    UpdateMenu(f_full);
  }

  if (_sbc == SB_LONG_CLICK) {
    if (Menu == &ChannelsMenu && activeItem < Menu->ItemCount - 1) {
      channels[activeItem].chEnable = !channels[activeItem].chEnable;
      UpdateMenu(false);
    }
  }

  if (_sbc == SB_CLICK) {
    Menu->SelectItem = activeItem + 1;
    Menu->LastActiveItem = activeItem;
    Menu->LastTopItem = topItem;
    if (Menu == &MainMenu) {
      if (Menu->SelectItem == 1) {
        isScreens = true;
        ScreenIndex = InfoScreenIndex;
        ScreenCount = InfoScreenCount;
        UpdateScreens(true);
      }
      if (Menu->SelectItem == 2) {
        Menu = &SensMenu;
        Menu->SelectItem = 0;
        activeItem = SensMenu.LastActiveItem;
        topItem = SensMenu.LastTopItem;
        UpdateCaptMenu();
      }
      if (Menu->SelectItem == 3) {
        Menu = &ChannelsMenu;
        Menu->SelectItem = 0;
        activeItem = ChannelsMenu.LastActiveItem;
        topItem = ChannelsMenu.LastTopItem;
        UpdateCaptMenu();
      }
      if (Menu->SelectItem == 4) {
        isScreens = true;
        ScreenIndex = ServiceScreenIndex;
        ScreenCount = ServiceScreenCount;
        UpdateScreens(true);
      }
    } else {
      if (activeItem == Menu->ItemCount - 1) { //Пункт (Back) SensMenu и ChannelsMenu
        Menu = &MainMenu;
        Menu->SelectItem = 0;
        activeItem = MainMenu.LastActiveItem;
        topItem = MainMenu.LastTopItem;
      } else {
        if (Menu == &SensMenu) {
          isScreens = true;
          ScreenIndex = SensScreenIndex;
          ScreenCount = SensScreenCount;
          UpdateScreens(true);
        }
        if (Menu == &ChannelsMenu && channels[activeItem].chEnable) {     //Открываем экраны только если канал включен
          isScreens = true;
          ScreenIndex = ChannelScreenIndex;
          ScreenCount = ChannelScreenCount;
          UpdateScreens(true);
        } else return;
      }
    }
    if (!isScreens) UpdateMenu(true);
  }
}

void UpdateCaptMenu() {
  if (isScreens) return;
  lcd.setCursor(0, 0); Print_Space(20);
  lcd.setCursor(0, 0); lcd.print(MainMenu.Items[MainMenu.LastActiveItem]);
  if (Menu == &SensMenu) PrintTextLCD(9, 0, "Title");
  if (Menu == &ChannelsMenu) PrintTextLCD(10, 0, "Enb  Stat");
}

void UpdateMenu(bool _isFull) {
  if (isScreens) return;
  byte i = 4 - Menu->ShowItemCount;
  for (byte n = 0; n < Menu->ShowItemCount; n++) {
    lcd.setCursor(0, n + i);
    if (_isFull) {
      for (byte k = 0; k < 20; k++) Print_Space();
      lcd.setCursor(0, n + i);
    }
    byte drawItemN = topItem + n;        //Номер рисуемого элемента меню
    if (drawItemN == activeItem) PrintArrowR(); else if (!_isFull) Print_Space();

    if (Menu == &ChannelsMenu && drawItemN < Menu->ItemCount - 1) {
      if (channels[drawItemN].chEnable) {
        PrintTextLCD(9, i + n, "<ON >"); Print_Space(2);
        channels[drawItemN].chIsWork ? PrintTextLCD("On ") : PrintTextLCD("Off");
      } else {
        PrintTextLCD(9, i + n, "<DIS>"); Print_Space(6);
      }
    }

    if (!_isFull) continue;
    lcd.setCursor(1, i + n);
    lcd.print(Menu->Items[drawItemN]);
    if (drawItemN == Menu->ItemCount - 1) break;
    if (Menu == &SensMenu) {
      lcd.setCursor(10, n + i);
      lcd.print(SensTitle[drawItemN]);
    }
  }
}
void PrintSensor(const byte _nSens, const TSensorParam _sParam, const bool _isShortFormat = true);

const char * ChScrName[3] = {"Counters", "TimeWorks", "Powers"};
void CalckScreens(bool _isR, bool _isL, SBUTTON_CLICK _sbc) {
  if (_isR && ScreenIndex < ScreenCount - 1) {
    ScreenIndex++;
    UpdateScreens(true);
  }
  if (_isL && ScreenIndex > 0) {
    ScreenIndex--;
    UpdateScreens(true);
  };

  if (_sbc == SB_CLICK) {
    isScreens = false;
    if (Menu == &MainMenu && Menu->SelectItem == 1) InfoScreenIndex = ScreenIndex; else ServiceScreenIndex = ScreenIndex;
    UpdateCaptMenu();
    UpdateMenu(true);
  }

  if (_sbc == SB_LONG_CLICK) {          //Долгое нажатие на экранах
    if (Menu == &ChannelsMenu)
      ShowYN("ClearData?", CannelsTitle[Menu->SelectItem - 1], ChScrName[ScreenIndex]);
  }

}

void UpdateScreens (bool f_full) {
  if (!isScreens) return;
  if (f_full) lcd.clear();
  if (Menu == &MainMenu)
    switch (MainMenu.SelectItem) {
      case 1: UpdateInfoScreen(f_full);
        break;
      case 4: UpdateServiceScreen(f_full);
        break;
    }
  if (Menu == &SensMenu) UpdateSensorsScreen(f_full);
  if (Menu == &ChannelsMenu) UpdateChannelsScreen(f_full);
}

void UpdateInfoScreen (bool f_full) {
  switch (ScreenIndex) {
    case 0:
      if (f_full) {
        PrintTextLCD(0, 0, "Sens"); PrintColon(); Print_Space(); PrintTextLCD(SensTitle[sens_ulica]);
        lcd.setCursor(0, 1); Print_TextTemp(false); lcd.setCursor(11, 1); Print_TextHumi(false);
        PrintTextLCD(0, 2, "Humi(ABS)"); PrintColon(); Print_Space(7); PrintTextLCD("gm3");
        PrintTextLCD(0, 3, "Pressure"); PrintColon(); Print_Space(9); PrintTextLCD("mm");
      }
      PrintMeasuring(19, 0, "*");
      lcd.setCursor(2, 1); PrintSensor(sens_ulica, spTemp, false);
      lcd.setCursor(13, 1); PrintSensor(sens_ulica, spHumi, false);
      lcd.setCursor(11, 2); PrintSensor(sens_ulica, spHumiABS); //lcd.print(" gm3"); // НЕ Затирается предыдуще значение !!!!!!!!!!!!!!!! VVVVVVVVVV
      lcd.setCursor(10, 3); PrintSensor(sens_ulica, spPress); //lcd.print(" mm");
      break;
    case 1:
      if (f_full) {
        lcd.setCursor(0, 0); lcd.print(F("View Sens 1-3"));
        lcd.setCursor(0, 1); lcd.print(F("Sens:  S1   S2   S3"));
//          PrintTextLCD(0, 0, "View Sens 1-3");
//          PrintTextLCD(0, 1, "Sens:  S1   S2   S3");
//        PrintTextLCD(0, 0, "View"); Print_Space(); PrintTextLCD("Sens"); Print_Space(); PrintTextLCD("1-3");
//        PrintTextLCD(0, 1, "Sens"); PrintColon(); Print_Space(2); PrintTextLCD("S"); PrintTextLCD("1"); Print_Space(3);
//        PrintTextLCD("S"); PrintTextLCD("2"); Print_Space(3); PrintTextLCD("S"); PrintTextLCD("3");
        lcd.setCursor(0, 2); Print_TextTemp(true);
        lcd.setCursor(0, 3); Print_TextHumi(true);
      }
      PrintMeasuring(14, 0, "<meas>");
      lcd.setCursor(6, 2); PrintSensor(0, spTemp);
      lcd.setCursor(6, 3); PrintSensor(0, spHumi);
      lcd.setCursor(11, 2); PrintSensor(1, spTemp);
      lcd.setCursor(11, 3); PrintSensor(1, spHumi);
      lcd.setCursor(16, 2); PrintSensor(2, spTemp);
      lcd.setCursor(16, 3); PrintSensor(2, spHumi);
      break;
    case 2:
      if (f_full) {
        lcd.setCursor(0, 0); lcd.print(F("View Sens 4-6"));
        lcd.setCursor(0, 1); lcd.print(F("Sens:  S4   S5   S6"));
//        PrintTextLCD(0, 0, "View"); Print_Space(); PrintTextLCD("Sens"); Print_Space(); PrintTextLCD("4-6");
//        PrintTextLCD(0, 1, "Sens"); PrintColon(); Print_Space(2); PrintTextLCD("S"); PrintTextLCD("4"); Print_Space(3);
//        PrintTextLCD("S"); PrintTextLCD("5"); Print_Space(3); PrintTextLCD("S"); PrintTextLCD("6");        
        lcd.setCursor(0, 2); Print_TextTemp(true);
        lcd.setCursor(0, 3); Print_TextHumi(true);
      }
      PrintMeasuring(14, 0, "<meas>");
      lcd.setCursor(6, 2); PrintSensor(3, spTemp);
      lcd.setCursor(6, 3); PrintSensor(3, spHumi);
      lcd.setCursor(11, 2); PrintSensor(4, spTemp);
      lcd.setCursor(11, 3); PrintSensor(4, spHumi);
      lcd.setCursor(16, 2); PrintSensor(5, spTemp);
      lcd.setCursor(16, 3); PrintSensor(5, spHumi);
      break;
    case 3:
      if (f_full) {
        lcd.setCursor(0, 0); lcd.print(F("View Channels: 1-4"));
        lcd.setCursor(0, 1); lcd.print(F("ChN: Ch1 Ch2 Ch3 Ch4"));
        lcd.setCursor(0, 2); lcd.print(F("Stat:"));
        lcd.setCursor(0, 3); lcd.print(F("Cnt:"));
      }
      PrintChannelsState(true);   //Печатаем состояния каналов
      break;
    case 4:
      if (f_full) {
        lcd.setCursor(0, 0); lcd.print(F("View Channels: 5-8"));
        lcd.setCursor(0, 1); lcd.print(F("ChN: Ch5 Ch6 Ch7 Ch8"));
        lcd.setCursor(0, 2); lcd.print(F("Stat:"));
        lcd.setCursor(0, 3); lcd.print(F("Cnt:"));
      }
      PrintChannelsState(false);   //Печатаем состояния каналов
      break;
    case 5:
      lcd.print(F("InfoPadval"));
      lcd.setCursor(5, 2); lcd.print(F("NO SENSOR"));
      break;
    case 6:
      //      lcd.print(F("Info1"));
      //      lcd.setCursor(2, 1); lcd.print(F("!SENSOR ERROR!"));
      break;
  }
}

void PrintChannelsState(bool isFirst) {
  byte shift;
  if (isFirst) shift = 0; else shift = 4;
  for (byte n = 0; n < 4; n++) {
    lcd.setCursor(5 + n * 4, 2);
    lcd.print(channels[n + shift].chEnable ? (channels[n + shift].chIsWork ? "On" : "Off") : "---");  //<+> <->

    //if (channels[n + shift].workCnt[0] < 10) lcd.setCursor(6 + n * 4, 5); else lcd.setCursor(5 + n * 4, 5);
    if (channels[n + shift].chEnable) {
      lcd.setCursor((channels[n + shift].workCnt[0] < 10 ? 6 : 5) + n * 4, 5);
      lcd.print(channels[n + shift].workCnt[0]);
    }
  }
}

void UpdateServiceScreen (bool f_full) {
  switch (ScreenIndex) {
    case 0:
      if (f_full) {
        lcd.setCursor(0, 0); lcd.print("Device Info");
        lcd.setCursor(0, 1); lcd.print("  Time:");
        lcd.setCursor(0, 3); lcd.print("UpTime:");
      }
      PrintMeasuring(14, 0, "<meas>");
      lcd.setCursor(8, 1); lcd.print("22:53");
      lcd.setCursor(8, 3); PrintTimeWorkCh(millis() / 1000); //Сделать по другому вывод аптайма
      break;
    case 1:
      lcd.setCursor(0, 0); lcd.print(F("SetParam Kesson"));
      lcd.setCursor(0, 1); lcd.print(F("Using Humi: Yes"));
      lcd.setCursor(1, 2); lcd.print(F("TOn:"));
      lcd.setCursor(0, 3); lcd.print(F("TOff:"));
      lcd.setCursor(11, 2); lcd.print(F("HOn:"));
      lcd.setCursor(10, 3); lcd.print(F("HOff:"));
      break;
    case 2:
      lcd.print("Service");
      lcd.setCursor(0, 2); lcd.print(arSens[2].data.T); //Убрать
      lcd.setCursor(0, 3); lcd.print(arSens[2].data.H); //Убрать
      break;
  }
  //lcd.print("   Err: "); lcd.print(arSens[ScreenIndex].sStatus);
}

void UpdateSensorsScreen(bool f_full) {
  if (f_full) {
    lcd.print(F("ViewSens: "));
    lcd.print(SensTitle[Menu->SelectItem - 1]);
    lcd.setCursor(0, 1);
    switch (ScreenIndex) {
      case 0:
        lcd.print(F("Type:")); lcd.print(SensTypeStr[arSens[Menu->SelectItem - 1].sensType]);
        lcd.print(F(" Pin:"));  if (arSens[Menu->SelectItem - 1].pinSens == pin_I2C) lcd.print("<I2C>"); else {
          if (arSens[Menu->SelectItem - 1].pinSens < 10) {
            lcd.print("< "); lcd.print(arSens[Menu->SelectItem - 1].pinSens); lcd.print(" >");
          }
          else lcd.print("< "); lcd.print(arSens[Menu->SelectItem - 1].pinSens); lcd.print(">");
        }
        lcd.setCursor(0, 2); Print_TextTemp(false); lcd.setCursor(10, 2); lcd.print(F("P:"));
        lcd.setCursor(0, 3); Print_TextHumi(false); lcd.setCursor(9,  3); lcd.print(F("Ha:"));
        break;
      case 1:
        lcd.setCursor(5, 1); lcd.print(F("TEMP of Day"));
        lcd.setCursor(0, 2); lcd.print(F("Max:")); lcd.setCursor(12, 2); lcd.print(F("h:"));
        lcd.setCursor(0, 3); lcd.print(F("Min:")); lcd.setCursor(12, 3); lcd.print(F("h:"));
        break;
      case 2:
        lcd.setCursor(5, 1); lcd.print(F("HUMI of Day"));
        lcd.setCursor(0, 2); lcd.print(F("Max:")); lcd.setCursor(11, 2); lcd.print(F("t:"));
        lcd.setCursor(0, 3); lcd.print(F("Min:")); lcd.setCursor(11, 3); lcd.print(F("t:"));
        break;
      case 3:
        //        lcd.print("     TEMP    HUMI");
        //        lcd.setCursor(0, 2); lcd.print("Max:                ");
        //        lcd.setCursor(0, 3); lcd.print("Min:                ");
        break;
    }
  }
  PrintMeasuring(9, 0, "*");
  switch (ScreenIndex) {
    case 0:
      lcd.setCursor(2, 2); PrintSensor(Menu->SelectItem - 1, spTemp, false);
      lcd.setCursor(13, 2); PrintSensor(Menu->SelectItem - 1, spPress, false);
      lcd.setCursor(2, 3); PrintSensor(Menu->SelectItem - 1, spHumi, false);
      lcd.setCursor(13, 3); PrintSensor(Menu->SelectItem - 1, spHumiABS);
      break;
    case 1: PrintSensorMinMax(Menu->SelectItem - 1, spTemp); break;   //Вывод MaxMin по температуре
    case 2: PrintSensorMinMax(Menu->SelectItem - 1, spHumi); break;   //Вывод MaxMin по влажности
    case 3:
      //lcd.setCursor(4, 2); lcd.print(" 99.9oC  99.9%");
      //lcd.setCursor(4, 3); lcd.print("-99.9oC  99.9%");
      break;
  }
}

void UpdateChannelsScreen(bool f_full) {
  if (f_full) {
    lcd.print(CannelsTitle[Menu->SelectItem - 1]); lcd.print(F(" - ")); lcd.print(ChScrName[ScreenIndex]);
    PrintCommonText();
  }
  switch (ScreenIndex) {
    case 0:                 //Вывод счетчиков работы канала
      lcd.setCursor(10, 1); lcd.print(F("0"));
      lcd.setCursor(10, 2); lcd.print(F("12"));
      lcd.setCursor(10, 3); lcd.print(F("24"));
      break;
    case 1:                 //Вывод времени работы канала
      lcd.setCursor(10, 1); PrintTimeWorkCh(100);
      lcd.setCursor(10, 2); PrintTimeWorkCh(712);
      lcd.setCursor(10, 3); PrintTimeWorkCh(459);
      break;
    case 2:
      lcd.setCursor(10, 1); PrintTimeWorkCh(00);
      lcd.setCursor(10, 2); PrintTimeWorkCh(618);
      lcd.setCursor(10, 3); PrintTimeWorkCh(0);
      break;
    case 3:

      break;
    case 4:

      break;
  }
}

void PrintTimeWorkCh(const uint32_t i) {
  if (i == 0) {
    lcd.print(F("-NoData-")); return;
  }
  char _str[9];
  int h = (i / 3600ul);
  int m = (i % 3600ul) / 60ul;
  int s = (i % 3600ul) % 60ul;
  sprintf(_str, "%d:%02d:%02ds", h, m, s);
  lcd.print(_str);
}

void PrintCommonText() {
  lcd.setCursor(0, 1); lcd.print(F("Curr Day:"));
  lcd.setCursor(0, 2); lcd.print(F("LastDay1:"));
  lcd.setCursor(0, 3); lcd.print(F("LastDay2:"));
}

void PrintFormatTemp(const int _temp, const bool _isShort) {
  char str[8];
  if (_isShort) {
    //_temp = _temp / 10;
    if (_temp < -99 || _temp > 99) sprintf(str, "%3dC", _temp / 10);
    else if (_temp < 0) sprintf(str, "%d\1C", _temp / 10);
    else sprintf(str, "%2d\1C", _temp / 10);
    str[4] = '\0';
  } else {
    if (_temp < -99) sprintf(str, "%d.%d\1C", _temp / 10, abs(_temp % 10));
    else sprintf(str, "%3d.%d\1C", _temp / 10, abs(_temp % 10));
    //str[7] = '\0';
  }
  lcd.print(str);
}

void PrintFormatHumi(const int _humi, const bool _isShort) {
  char str[6];
  if (_isShort) sprintf(str, "%3d", _humi / 10);
  else sprintf(str, "%3d.%d", _humi / 10, abs(_humi % 10));
  lcd.print(str); lcd.print('%');
}

void PrintFormatHumiABS(int t, int h) {
  float f = calculationAbsH((float)t  / 10.0, (float)h / 10.0);
  lcd.print(f); if (f < 10.0) lcd.print(' ');
}

void PrintFormatPressure(const int _press, const bool _isShort) {
  lcd.print((float)_press / 10.0, 1);
}

void PrintSensor(const byte _nSens, const TSensorParam _sParam, const bool _isShortFormat = true) {
  if (_nSens >= SENS_COUNT) {
    lcd.print(_isShortFormat ? " -- " : " ----- "); return;
  }
  if (arSens[_nSens].sStatus == ssInit) {
    lcd.print(_isShortFormat ? "-is-" : "  -is- "); return;
  }
  if (arSens[_nSens].sStatus == ssNoSens) {
    lcd.print(_isShortFormat ? "-ns-" : "  -ns- "); return;
  }
  if (_sParam == spPress && !arSens[_nSens].isPressure) {
    lcd.print(_isShortFormat ? " -- " : " ----- "); return;
  }
  if (arSens[_nSens].sStatus == ssNoData) {
    lcd.print(_isShortFormat ? "-nd-" : "  -nd- "); return;
  }
  if (arSens[_nSens].sStatus == ssUnknownErr) {
    switch (_sParam) {
      case spTemp:    lcd.print(_isShortFormat ? "TErr" : " -TErr-"); break;
      case spHumi:    lcd.print(_isShortFormat ? "HErr" : " -HErr-"); break;
      case spHumiABS: lcd.print(_isShortFormat ? "HErr" : " -HErr-"); break;
      case spPress:   lcd.print(_isShortFormat ? "PErr" : " -PErr-"); break;
    }
    return;
  }
  if (arSens[_nSens].sStatus == ssReading) {
    bool f;
    switch (_sParam) {
      case spTemp:    f = (arSens[_nSens].data.T == NO_DATA); break;
      case spHumi:    f = (arSens[_nSens].data.H == NO_DATA); break;
      case spHumiABS: f = (arSens[_nSens].data.H == NO_DATA); break;
      case spPress:   f = (arSens[_nSens].data.P == NO_DATA); break;
    }
    if (f) {
      lcd.print(_isShortFormat ? "-rs-" : "  -rs- ");
      return;
    }
  }
  if (arSens[_nSens].sStatus != ssOk) if (arSens[_nSens].sStatus != ssReading) return;
  switch (_sParam) {
    case spTemp:    PrintFormatTemp(arSens[_nSens].data.T, _isShortFormat); break;
    case spHumi:    PrintFormatHumi(arSens[_nSens].data.H, _isShortFormat); break;
    case spHumiABS: PrintFormatHumiABS(arSens[_nSens].data.T, arSens[_nSens].data.H); break;
    case spPress:   PrintFormatPressure(arSens[_nSens].data.P, _isShortFormat); break;
  }
}

bool VerDataMaxMin (const int _maxmin) {
  return _maxmin < 1000 && _maxmin > -1000;
}

void PrintErrMaxMin(const TSensStatus _status) {
  if (_status != ssOk && _status != ssReading)
    lcd.print(F(" - - "));
  else lcd.print(F(" -- "));
}

void PrintSensorMinMax(const byte _nSens, const TSensorParam _sParam) {
  switch (_sParam) {
    case spTemp:  // maxT
      if (VerDataMaxMin(arSens[_nSens].data.maxT.t)) {
        lcd.setCursor(4, 2);  PrintFormatTemp(arSens[_nSens].data.maxT.t, false);
        lcd.setCursor(14, 2); PrintFormatHumi(arSens[_nSens].data.maxT.h, false);
      } else {
        lcd.setCursor(4, 2);  PrintErrMaxMin(arSens[_nSens].sStatus);
        lcd.setCursor(14, 2); PrintErrMaxMin(arSens[_nSens].sStatus);
      }           // minT
      if (VerDataMaxMin(arSens[_nSens].data.minT.t)) {
        lcd.setCursor(4, 3);  PrintFormatTemp(arSens[_nSens].data.minT.t, false);
        lcd.setCursor(14, 3); PrintFormatHumi(arSens[_nSens].data.minT.h, false);
      } else {
        lcd.setCursor(4, 3);  PrintErrMaxMin(arSens[_nSens].sStatus);
        lcd.setCursor(14, 3); PrintErrMaxMin(arSens[_nSens].sStatus);
      }
      break;
    case spHumi:  // maxH
      if (VerDataMaxMin(arSens[_nSens].data.maxH.h)) {
        lcd.setCursor(4, 2);  PrintFormatHumi(arSens[_nSens].data.maxH.h, false);
        lcd.setCursor(13, 2); PrintFormatTemp(arSens[_nSens].data.maxH.t, false);
      } else {
        lcd.setCursor(4, 2);  PrintErrMaxMin(arSens[_nSens].sStatus);
        lcd.setCursor(13, 2); PrintErrMaxMin(arSens[_nSens].sStatus);
      }           // minH
      if (VerDataMaxMin(arSens[_nSens].data.minH.h)) {
        lcd.setCursor(4, 3); PrintFormatHumi(arSens[_nSens].data.minH.h, false);
        lcd.setCursor(13, 3); PrintFormatTemp(arSens[_nSens].data.minH.t, false);
      } else {
        lcd.setCursor(4, 3);  PrintErrMaxMin(arSens[_nSens].sStatus);
        lcd.setCursor(13, 3); PrintErrMaxMin(arSens[_nSens].sStatus);
      }
      break;
  }
}

bool ShowYN(char * str1, char * str2, char * str3) {
  long now = millis();
  long endTime = now + 15000;
  long tmUpd;
  bool fspace = true;           //Для моргания параметра. true - затирает
  bool fYes = false;            //Отображаемый параметр на экране true - Yes false - No
  bool fUpdScr = true;          //Для обновления экрана
  bool Result = false;          //Результат диалога
  lcd.clear(); lcd.print(str1);  //Вывод сообщения
  lcd.setCursor(0, 1); lcd.print(str2);
  lcd.setCursor(0, 2); lcd.print(str3);
  lcd.setCursor(0, 3); lcd.print(F("AutoExit in:    sec"));
  tmUpd = now;
  while (now < endTime) {
    now = millis();
    //fspace = (now / 350) % 2; //Каждые 350 мск переключать fspace - "тяжелый" вариант
    if (now - tmUpd >= 350) {
      fspace = !fspace;
      fUpdScr = true;
      tmUpd = now;
    }
    if (encTick) {              //Обработка энкодера (вращение)
      fYes = encTick == 1;
      endTime = now + 10000;
      fUpdScr = true;
    }
    encTick = 0;
    if (fUpdScr) {
      lcd.setCursor(13, 3); lcd.print((endTime - now) / 1000 + 1); lcd.print(' ');
      lcd.setCursor(15, 0);
      lcd.print(fspace ? "   " : (fYes ? "Yes" : "No "));
      fUpdScr = false;
    }
    if (swEnc.Loop() == SB_CLICK) {
      Result = fYes;
      break;
    }
    //тут вызывать обработку датчиков
  }
  UpdateScreens(true);
  return Result;
}

//------------------------------ Print Helpers ----------------------------
void PrintMeasuring(const byte _col, const byte _row, char * str) {
  if (reading) PrintTextLCD(_col, _row, str);
  else {
    lcd.setCursor(_col, _row); Print_Space(strlen(str));
  }
}

void PrintArrowR() {
  PrintChLCD('>');
}

void PrintArrowL() {
  PrintChLCD('<');
}

void PrintSpaceArrowR() {
  PrintTextLCD(" >");
}

void PrintSpaceArrowL() {
  PrintTextLCD(" <");
}

void PrintColon() {
  PrintChLCD(':');
}

void Print_TextTemp (bool isLong) {
  if (isLong) PrintTextLCD("Temp"); else PrintTextLCD("T");
  PrintColon();
}

void Print_TextHumi (bool isLong) {
  if (isLong) PrintTextLCD("Humi"); else PrintTextLCD("H");
  PrintColon();
}

void PrintTextLCD(const byte _col, const byte _row, char * str) {
  lcd.setCursor(_col, _row); PrintTextLCD(str);
}

void PrintTextLCD(char * str) {
  lcd.print(str);
}

void PrintChLCD(char ch) {
  lcd.print(ch);
}

void Print_Space(const byte _cnt = 1) {
  if (_cnt < 1) return;
  if (_cnt == 1) PrintChLCD(' ');
  else for (byte n = _cnt; n; --n) PrintChLCD(' ');
}

#define PERIODPOWER   15000//25000     //Интервал включения питания
#define STARTREADING  5000//5000      //Интервал начала чтения после включения питания
#define PERIODREAD    2500//2500      //Интервал между чтениями сенсоров
#define NUM_READS     5         //Кол-во чтений сенсоров для усреднения значения
bool powerON = false;           //Включено при подаче питания на сенсоры
bool reading = false;           //Включено при чтении сенсоров

byte nRead = NUM_READS;         //Счетчик считываний сенсоров (уменьшается до нуля)
uint32_t msLast = PERIODPOWER;  //переменная времени для таймеров
void readSens() {       //Чтение сенсоров
  uint32_t ms = millis();

  if (!powerON && (ms - msLast) > PERIODPOWER) {
    powerON = true; msLast = ms;
    digitalWrite(TESTPOWERPIN, HIGH);      //Подаем питание на сенсоры
  }

  if (!reading && powerON && (ms - msLast) > STARTREADING) {
    for (byte k = 0; k < SENS_COUNT; ++k) SetSensorStatus(k, ssReading);
    reading = true; UpdateScreens (false);
  }

  if (reading && (ms - msLast) > PERIODREAD) {
    for (byte k = 0; k < SENS_COUNT; ++k) readSensor(k);          //Тут читаем сенсор(k)
    msLast = ms;
    --nRead;
  }

  if (!nRead) {
    nRead = NUM_READS;
    powerON = reading = false;
    msLast = ms;
    digitalWrite(TESTPOWERPIN, LOW);       //Отключаем питание на сенсоры
    CalculatingSensorsData();      //Усреднили данные с сенсоров <<<<<<<<<<
    //GetDinamics();                //Посчитали динамику <<<<<<<<<<<<<<<<<<<
    UpdateScreens (false);
  }
}

void CalculatingSensorsData() { //Переделать!!!!
  for (byte n = 0; n < SENS_COUNT; ++n) {
    if (arSens[n].sStatus != ssReading) continue;
    arSens[n].data.T = arSens[n].data.tmpT / NUM_READS;
    if (arSens[n].data.lastT != NO_DATA && abs(arSens[n].data.T - arSens[n].data.lastT) > 70) { //Если температура скакнула больше чем на 7 градусов, то выставить ошибку
      SetSensorStatus(n, ssNoData); continue;
    }
    
    arSens[n].data.H = arSens[n].data.tmpH / NUM_READS;
    if (arSens[n].data.lastH != NO_DATA && abs(arSens[n].data.H - arSens[n].data.lastH) > 150) { //Если влажность скакнула больше чем на 15 процентов, то выставить ошибку
      SetSensorStatus(n, ssNoData); continue;
    }
    
    if (arSens[n].isPressure) if (arSens[n].data.tmpP != NO_DATA) arSens[n].data.P = arSens[n].data.tmpP / NUM_READS;
    
    if (arSens[n].data.T < arSens[n].data.minT.t) {
      arSens[n].data.minT.t = arSens[n].data.T;
      arSens[n].data.minT.h = arSens[n].data.H;
    }
    if (arSens[n].data.T > arSens[n].data.maxT.t) {
      arSens[n].data.maxT.t = arSens[n].data.T;
      arSens[n].data.maxT.h = arSens[n].data.H;
    }
    if (arSens[n].data.H < arSens[n].data.minH.h) {
      arSens[n].data.minH.h = arSens[n].data.H;
      arSens[n].data.minH.t = arSens[n].data.T;
    }
    if (arSens[n].data.H > arSens[n].data.maxH.h) {
      arSens[n].data.maxH.h = arSens[n].data.H;
      arSens[n].data.maxH.t = arSens[n].data.T;
    }
    arSens[n].sStatus = ssOk;
  }
}

void SetSensorStatus(const byte n_sens, const byte _sStatus) {
  arSens[n_sens].sStatus = _sStatus; if (_sStatus == ssOk) return;
  if (_sStatus == ssReading) {
    if (arSens[n_sens].data.T != NO_DATA) arSens[n_sens].data.lastT = arSens[n_sens].data.T;
    if (arSens[n_sens].data.H != NO_DATA) arSens[n_sens].data.lastH = arSens[n_sens].data.H;
    if (arSens[n_sens].isPressure && arSens[n_sens].data.P != NO_DATA) arSens[n_sens].data.lastP = arSens[n_sens].data.P;
    arSens[n_sens].data.tmpT = 0;
    arSens[n_sens].data.tmpH = 0;
    if (arSens[n_sens].isPressure) arSens[n_sens].data.tmpP = 0;
    return;
  }
  arSens[n_sens].data.T = arSens[n_sens].data.tmpT = arSens[n_sens].data.lastT = NO_DATA;
  arSens[n_sens].data.H = arSens[n_sens].data.tmpH = arSens[n_sens].data.lastH = NO_DATA;
  if (arSens[n_sens].isPressure) arSens[n_sens].data.P = arSens[n_sens].data.tmpP = arSens[n_sens].data.lastP = NO_DATA;
}

void ResetMinMaxDay() {
  for (byte n = 0; n < SENS_COUNT; ++n) {
    arSens[n].data.minT.t = NO_DATA;
    arSens[n].data.maxT.t = -NO_DATA;
    arSens[n].data.minH.h = NO_DATA;
    arSens[n].data.maxH.h = -NO_DATA;
  }
}

// Функция переводит относительную влажность в абсолютную
// t-температура в градусах Цельсия h-относительная влажность в процентах
float calculationAbsH(float t, float h) {
  float temp    = pow(2.718281828, (17.67 * t) / (t + 243.5));
  return (6.112 * temp * h * 2.1674) / (273.15 + t);
}

void readSensor(const byte n_sens) {
  if (arSens[n_sens].sStatus != ssReading) return;
  switch (arSens[n_sens].sensType) {
    case stDHT11:
      readDHT(n_sens);
      break;
    case stDHT22:
      readDHT(n_sens);
      break;
    case stAM:
      readAM(n_sens);
      break;
    case stBME:
      readBME(n_sens);
      break;
  }
}

void readDHT(const byte n) {
  switch (static_cast<iarduino_DHT*>(arSens[n].sensor)->read()) {  // читаем показания датчика
    case DHT_OK:
      arSens[n].data.tmpT += round(static_cast<iarduino_DHT*>(arSens[n].sensor)->tem * 10);
      arSens[n].data.tmpH += round(static_cast<iarduino_DHT*>(arSens[n].sensor)->hum * 10);
      break;
    case DHT_ERROR_CHECKSUM:   //Serial.println(         "CEHCOP B KOMHATE: HE PABEHCTBO KC");                     break;
      SetSensorStatus(n, ssNoData);
      break;
    case DHT_ERROR_DATA:       //Serial.println(         "CEHCOP B KOMHATE: OTBET HE COOTBETCTB. CEHCOPAM 'DHT'"); break;
      SetSensorStatus(n, ssNoData);
      break;
    case DHT_ERROR_NO_REPLY:   //Serial.println(         "CEHCOP B KOMHATE: HET OTBETA");                          break;
      SetSensorStatus(n, ssNoSens);
      break;
    default:
      SetSensorStatus(n, ssUnknownErr);      //Любая неучтеная ошибка
      break;
  }
}

void readAM(const byte n) {
  switch (static_cast<iarduino_AM2320*>(arSens[n].sensor)->read()) {    // Читаем показания датчика.
    case AM2320_OK:
      arSens[n].data.tmpT += round(static_cast<iarduino_AM2320*>(arSens[n].sensor)->tem * 10);
      arSens[n].data.tmpH += round(static_cast<iarduino_AM2320*>(arSens[n].sensor)->hum * 10);
      break;
    case AM2320_ERROR_LEN:  //Serial.println("OTIIPABKA HEBO3M.");     break; // Объем передаваемых данных превышает буфер I2C
      SetSensorStatus(n, ssNoData);
      break;
    case AM2320_ERROR_ADDR:     //Serial.println("HET CEHCOPA");           break; // Получен NACK при передаче адреса датчика
      SetSensorStatus(n, ssNoSens);
      break;
    case AM2320_ERROR_DATA: //Serial.println("OTIIPABKA HEBO3M.");     break; // Получен NACK при передаче данных датчику
      SetSensorStatus(n, ssNoData);
      break;
    case AM2320_ERROR_SEND: //Serial.println("OTIIPABKA HEBO3M.");     break; // Ошибка при передаче данных
      SetSensorStatus(n, ssNoData);
      break;
    case AM2320_ERROR_READ: //Serial.println("HET OTBETA OT CEHCOPA"); break; // Получен пустой ответ датчика
      SetSensorStatus(n, ssNoSens);
      break;
    case AM2320_ERROR_ANS:  //Serial.println("OTBET HEKOPPEKTEH");     break; // Ответ датчика не соответствует запросу
      SetSensorStatus(n, ssNoData);
      break;
    case AM2320_ERROR_LINE: //Serial.println("HEPABEHCTBO CRC");       break; // Помехи в линии связи (не совпадает CRC)
      SetSensorStatus(n, ssNoData);
      break;
    default:
      SetSensorStatus(n, ssUnknownErr);      //Любая неучтеная ошибка
      break;
  }
}

void readBME(const byte n) { /// Проверить ??????????
  //SetSensorStatus(n, ssNoSens); return;
  //Если вернется NaN то выставить ошибку
  //lcd.setCursor(0,0); lcd.print(static_cast<BME280I2C*>(arSens[n].sensor)->temp(BME280::TempUnit_Celsius));
  arSens[n].data.tmpT += round(static_cast<BME280I2C*>(arSens[n].sensor)->temp(BME280::TempUnit_Celsius) * 10);
  arSens[n].data.tmpH += round(static_cast<BME280I2C*>(arSens[n].sensor)->hum() * 10);
  arSens[n].data.tmpP += round(static_cast<BME280I2C*>(arSens[n].sensor)->pres(BME280::PresUnit_torr) * 10);
}

/**
   Инициализация кнопки
*/
void SButton::begin() {
  pinMode(Pin, INPUT);
}

/**
   Действие производимое в цикле или по таймеру
   возвращает SB_NONE если кнопка не нажата и событие нажатие или динного нажатия кнопки
*/
SBUTTON_CLICK SButton::Loop() {
  uint32_t ms = millis();
  bool pin_state = digitalRead(Pin);

  // Фиксируем нажатие кнопки
  if ( pin_state == LOW && !Downed && (ms - tmpMs) > TM_bounce ) {
    Long_press_state = false;
    State            = false;
    Downed           = true;
    tmpMs              = ms;
    return SB_DOWNED;
  }

  // Фиксируем случайное нажатие
  if ( pin_state == LOW && !State && Downed && (ms - tmpMs ) > TM_casual_click ) {
    State            = true;
    return SB_CLICK;
  }

  // Фиксируем удержание кнопки
  if ( pin_state == LOW && State && !Long_press_state && ( ms - tmpMs ) > TM_long_click ) {
    Long_press_state = true;
    return SB_LONG_CLICK;
  }

  // Фиксируем отпускание кнопки
  if ( pin_state == HIGH && Downed && (ms - tmpMs) > TM_bounce ) {
    // Сбрасываем все флаги
    Downed = false;
    State  = false;
    tmpMs    = 0;
    return SB_RELEASE;
  }

  return SB_NONE;
}
Скетч использует 24580 байт (80%) памяти устройства. Всего доступно 30720 байт.
Глобальные переменные используют 1302 байт (63%) динамической памяти, оставляя 746 байт для локальных переменных. Максимум: 2048 байт.

 

AsNik
Offline
Зарегистрирован: 24.10.2020

...

wdrakula
wdrakula аватар
Offline
Зарегистрирован: 15.03.2016

Посмотрел. Просто ужасно много кода, написанного по ПК-шному. Все универсальности нужно выкидывать. Поток примитивов вида "печать стрелки" и подобное - убирать в константы.

Это вечная болезнь разработчика из мира ПК - делаем не решение, а сразу универсальный инструмент для будущих решений. Не нужно писать библиотеку для всех меню в мире, а сделать одно меню! (как в анекдоте про математика и физика: "Видел одну козу, черную сверху"). Так вот нужно "одну козу" и черную только сверху, аналогия понятна? Вся эта куча "универсальностей" и забила память.

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

Универсальный инструмент == другой контроллер. Нано - решать КОНКРЕТНУЮ задачу. Одну. Черную - сверху. ;))

И я бы еще советовал иную парадигму кода. Сам поток управления у тебя тоже ПК-шный немного.

Вот такая структура общей автоматики на МК, если использована парадигма "бесконечный цикл" - единственная принятая в Ардуино:

1. Чтение сенсоров, формирование сообщений от сенсоров.

2. ГУИ. Независимый модуль, общается сообщениями. ГУИ получает сообщения от основного потока и изменяет экран, читает ХИДы и изменяет экран и формирует сообщения для основного потока.

3. Чтение очереди входящих сообщений, установка флагов действий.

4. Исполнение действий по флагам, Действия могут быть многошаговые, например разгон/торможение привода и т.п. Тогда формируются промежуточные флаги и параметры развития действия.

---------------------------

Не нужно "отрисовывать" значения температур и влажностей. На этапе разбора сообщений, если что нужно - формируй сообщение для ГУИ. Ты получил данные с сенсора, проверил изменение температуры (к примеру) если есть - сформировал мессадж для ГУИ "Новая строка в поле ххх". Таким образом ГУИ проще и код не дублируется - например вызовы СетКурсор. У тебя есть номера полей ГУИ, все остальное - внутри объекта. Не строй экран в коде! Все экраны напиши константами с темплейтами - код уменьшится в разы. Подстановки вместо темплейтов их значений делаются стандартными строковыми функциями.

Кнопки, энкодеры и другие ХИДы - не должны быть вообще видны вне ГУИ. Зачем они там? На событие от ХИДа меняется экран и формируется мессадж в ЦУ.

Вот так причешешь постепенно,  глядишь и память освободится.

========================

Конкретно как я пишу нужно сказать. Я выше общий вариант привел, каждый программист думает по-своему.

У меня так:

1. чтение из гуи;

2. чтение сенсоров;

3. действия;

4. Передача в ГУИ.

Этап переработки сообщений во флаги я делаю одновременно с чтением сообщений и сенсоров.

AsNik
Offline
Зарегистрирован: 24.10.2020

К моему стыду, я не все понял из сказанного #75 :( Не программист я, просто маленький любитель.... многие термины мне непонятны.

Но суть понял. Универсальность - да. Была такая задумка. И это я еще от много чего отказался. Но все равно не уместил...)

Попробую лишнего по убирать, сделать константы в PROGMEM (здесь ниже очередной пример, разбираюсь с ПРОГМЕМ)

Цитата:
ГУИ. Независимый модуль, общается сообщениями.

Как такое делается? Конкретно по значениям с датчиков - у меня вроде как, так и делается. Обновляются данные на экране только когда изменились(считались) данные... Но повторяюсь, я в связи с низкими знаниями программирования МК, может чего понимаю не так...

у меня экраны обновляются только если произошло событие от энкодера или считались очередные данные с сенсоров...

3 и 4 пункт на очереди. Ну если в третьем не обращать внимание на сообщения. Что в понятиях МК - сообщения? Или это мне нужно придумать архитектуру сообщений типа как в винде. Утрированно. Тогда мне нужно перечеркивать этот проект и начинать заново.

 Такими темпами я его долго буду делать))

Вот экспериментирую с ПРОГМЕМ:

const char string_0[] PROGMEM = "Temp";   // "String 0" и т.д. - строки для хранения - измените на подходящие.
const char string_1[] PROGMEM = "Humi";
const char string_2[] PROGMEM = "Min";
const char string_3[] PROGMEM = "Max";
const char string_4[] PROGMEM = "On";
const char string_5[] PROGMEM = "Off";
const char myStr[] PROGMEM = "00ABCDEFG";

const char * const string_table[] PROGMEM = {string_0, string_1, string_2, string_3, string_4, string_5};

void setup() {
  Serial.begin(9600); while (!Serial); Serial.println("OK");
}

char bufStr[10];
char * GetStringIntex(const byte _i) {
  return strcpy_P(bufStr, (char*)pgm_read_word(&(string_table[_i])));
}

void PrintTextIndex(const byte _i) {
  char buffer[10];
  Serial.println(strcpy_P(buffer, (char*)pgm_read_word(&(string_table[_i])))); //Вместо сериал lcd.print....
}

void get_string(const unsigned char *data) {
  unsigned char cc;
  while (1) {
    cc = pgm_read_byte(data);
    if ((cc == 0) || (cc == 128)) return;
    Serial.print((char)cc);
    data++;
  }
}

void loop() {
  Serial.println("PrintTextIndex");
  for (int i = 0; i < 6; i++)  {
    PrintTextIndex(i);
    delay( 500 );
  }
  Serial.println("GetStringIntex");
  for (int i = 0; i < 6; i++)  {
    Serial.println(GetStringIntex(i)); //Вместо сериал lcd.print....
    delay( 500 );
  }
  get_string(myStr); Serial.println(" <-");
}

Где-то (в транзистортестере) подсмотрел как вытаскивать строки из флеша напрямую без массива (вопрос выше задавал)

AsNik
Offline
Зарегистрирован: 24.10.2020

wdrakula пишет:

Не нужно "отрисовывать" значения температур и влажностей. На этапе разбора сообщений, если что нужно - формируй сообщение для ГУИ. Ты получил данные с сенсора, проверил изменение температуры (к примеру) если есть - сформировал мессадж для ГУИ "Новая строка в поле ххх". Таким образом ГУИ проще и код не дублируется - например вызовы СетКурсор. У тебя есть номера полей ГУИ, 

Скорее всего это структура(архитектура, логика) программы должна быть для этого. Тем не менее разные экраны все равно придется рисовать, что бы потом в поля вставлять данные. Хотя у меня именно так и происходит

UpdateScreen(bool) вот от bool и зависит - полностью экран перерисовываем или только данные

И можно ли считать UpdateScreen(bool) - тем самым сообщением для ГУИ?

AsNik
Offline
Зарегистрирован: 24.10.2020

wdrakula пишет:
Таким образом ГУИ проще и код не дублируется - например вызовы СетКурсор. У тебя есть номера полей ГУИ, все остальное - внутри объекта. Не строй экран в коде! Все экраны напиши константами с темплейтами - код уменьшится в разы.

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

Цитата:
Подстановки вместо темплейтов их значений делаются стандартными строковыми функциями.

типа ("Temp: %d ", t) ? ....Хм, а константу хранить PROGMEM Line1 = "Temp: %d " . Надо попробовать. Точно!!!

Цитата:
Кнопки, энкодеры и другие ХИДы - не должны быть вообще видны вне ГУИ. Зачем они там? На событие от ХИДа меняется экран и формируется мессадж в ЦУ.

Вроде стараюсь к этому, но не всегда пока получается. Спасибо за помощь!!

AsNik
Offline
Зарегистрирован: 24.10.2020

AsNik пишет:

типа ("Temp: %d ", t) ? ....Хм, а константу хранить PROGMEM Line1 = "Temp: %d " . Надо попробовать. Точно!!!

Обрадовался... Сделал даже вот такой тест:

const char string_0[] PROGMEM = "Temp"; 
const char string_1[] PROGMEM = "Humi";
const char string_2[] PROGMEM = "Min";
const char string_3[] PROGMEM = "Max: %3d.%d C";
const char string_4[] PROGMEM = "On";
const char string_5[] PROGMEM = "Off";
const char myStr[] PROGMEM = "Temp: %3d.%d C";

const char * const string_table[] PROGMEM = {string_0, string_1, string_2, string_3, string_4, string_5};

void setup() {
  Serial.begin(9600); while (!Serial); Serial.println("OK");
}

const size_t SerialPrintf (const char *szFormat, ...) { //Евгений, Спасибо!
    va_list argptr;
    va_start(argptr, szFormat);
    char *szBuffer = 0;
    const size_t nBufferLength = vsnprintf(szBuffer, 0, szFormat, argptr) + 1;
    if (nBufferLength == 1) return 0;
    szBuffer = (char *) malloc(nBufferLength);
    if (! szBuffer) return - nBufferLength;
    vsnprintf(szBuffer, nBufferLength, szFormat, argptr);
    Serial.print(szBuffer);
    free(szBuffer);
    return nBufferLength - 1;
}


char bufStr[15];
char * GetStringIntex(const byte _i) {
  return strcpy_P(bufStr, (char*)pgm_read_word(&(string_table[_i])));
}

void loop() {  
  SerialPrintf(GetStringIntex(3), -99, 9); Serial.println(" <-----");
  SerialPrintf(GetStringIntex(3), 99, 9); Serial.println(" <-----");
  SerialPrintf(GetStringIntex(3), 9, 9); Serial.println(" <-----");
  delay(1500);
}

Но есть один нюанс. У меня в случае ошибки на месте где были цифры, выводятся строки... Это получается нужно делать два шаблона для одной строки или все данный конвертировать в строку используя в шаблоне %s

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

AsNik пишет:

MaksVV пишет:
Вот когда на меге заработает будешь оптимизировать.

Тут вступит в силу парадокс HDD... сколько бы не было все равно мало

А что это за парадокс такой?
У меня 1Тб диск (под систему 128Гб SSD) и ещё один на 1Тб под бэкап системы и данных. Так вот, для понимания, тот что под бэкап - заполнен на 60%. И это с учетом того, что на первом диске фильмов много. Старых, но хороших. Жаль удалять. Ещё со времён торрентов (когда скорости были по 128кб/с). 

ЗЫ: диски эти у меня уже лет 6-8 трудятся, сигейты. Данные со старого перенёс и все. Ну и свежие прибавляются по немного. Литература в основном. 

AsNik
Offline
Зарегистрирован: 24.10.2020

BOOM пишет:

А что это за парадокс такой?

У меня 1Тб диск (под систему 128Гб SSD) 

Во времена, когда зародился этот парадокс, таких аббревиатур как SSD не было и про такие объемы как 1 Тб просто даже и не думали, не то что не мечтали). При имеющемся в системнике 4Гб HDD думали, что если заменить на 32 Гб, то все туда поместится, но поимевши 32 Гб, все повторялось...

AsNik
Offline
Зарегистрирован: 24.10.2020

Как вариант, в случае если нужно вывести данные не подходящие по шаблону, то просто вывести их по setCursor'у поверх  шаблона...

sadman41
Offline
Зарегистрирован: 19.10.2016

AsNik пишет:

Нормальный пример? (немного исправил)

char buffer[25];
const char tplHeader[] PROGMEM = "%S: %03d %c <==  ";

const char strHumidity[] PROGMEM = "Humidity";
const char strTemperature[] PROGMEM = "Temperature";

byte valHumidity = 86;
byte valTemperature = 29;

snprintf_P(buffer, sizeof(buffer), tplHeader, strHumidity, valHumidity, '%');
lcd.print(buffer);

snprintf_P(buffer, sizeof(buffer), tplHeader, strTemperature, valTemperature, (char) 0x02); // 0x02 is user defined degree glyph loaded to LCD
lcd.print(buffer);

 

AsNik
Offline
Зарегистрирован: 24.10.2020

Спасибо. Сейчас буду экспериментировать, пробовать, придумывать, может и перепишу все(почти все) заново...

AsNik
Offline
Зарегистрирован: 24.10.2020

Второй день экспериментирую, но не с кодом ...пока, а с файлами. Решил сначала код разбить еще на файлы (в том числе *.h)

После моих действий компилятор(может линкер) начали ругаться... Методом отката до рабочего состояния я пришел к тому, что ошибка появляется сразу после одного единственного изменения в (рабочем/компилируемом) коде и структуре файлов, а именно - два файла sav_button.cpp и sav_button.h перенес в подкатолог myLib и строчку подключения в коде изменил с #include "sav_button.h" на #include "myLib/sav_button.h"

В итоге:

exit status 1
Ошибка компиляции для платы Arduino Nano.
 
IDE 1.8.10
Нельзя "личные/локальные" библиотеки проекта в отдельную папку?
andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

AsNik пишет:

IDE 1.8.10

Нельзя "личные/локальные" библиотеки проекта в отдельную папку?

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

AsNik
Offline
Зарегистрирован: 24.10.2020

С полным путем конечно не удобно, но сейчас попробую... в принципе если про это знать и помнить), то жить можно

Но в моем случае #include "E:\ELECTRONICS\MYPROJECT\MK\ARDUINO\SKETCHS\ASMClimat\ClimateControl2\TMP\myLib\sav_button.h" - не помогло :(

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

Что значит "не помогло"? Не включается указанный файл или не компилируются *.cpp файлы?

AsNik
Offline
Зарегистрирован: 24.10.2020

andriano пишет:

Что значит "не помогло"?

При компиляции последние две строки в логе IDE (внизу):

exit status 1
Ошибка компиляции для платы Arduino Nano.

 

Еще обратил внимание, что если в пути использовать \ - то строка не подсвечивается (синтаксис) , но если слеши поменять на /, то срабатывает подсветка, но результат тот же.

Уже проект перенес поближе к началу...

#include "E:/TMP_Climat/myLib/sav_button.h" (строка с обратными слешами, подсвечивается зеленым

А так #include "E:\TMP_Climat\myLib\sav_button.h" - в IDE она черным цветом.

но если намеренно указать неверный путь, например: #include "E:/TMP_Climat/NOmyLib/sav_button.h"

то:

exit status 1
E:/TMP_Climat/NOmyLib/sav_button.h: No such file or directory
 
что в общем-то логично...
AsNik
Offline
Зарегистрирован: 24.10.2020

Оказывается (получилось что-то найти в инете, пару веток даже на этом форуме) есть такая непонятная проблема с IDE, подкаталогами и #include... Ок. Верну пока в корень проекта. Но если будут у кого какие решения/мысли - делитесь)

sadman41
Offline
Зарегистрирован: 19.10.2016

Свои cpp и h можно складывать в projectdir/src/...

Komandir
Komandir аватар
Offline
Зарегистрирован: 18.08.2018

AsNik а что с уровнем оптимизации компилятора ?

Может надо ему явно указать #pragma GCC optimize ("Os") или #pragma GCC optimize ("O3") и он уменьшит код ???

AsNik
Offline
Зарегистрирован: 24.10.2020

sadman41 пишет:
Свои cpp и h можно складывать в projectdir/src/...

Быстрая проверка показала, что похоже вот этот вариант работает. Спасибо. Буду пробовать еще...

Komandir пишет:

AsNik а что с уровнем оптимизации компилятора ?

Может надо ему явно указать #pragma GCC optimize ("Os") или #pragma GCC optimize ("O3") и он уменьшит код ???

К сожалению моих знаний не хватило что бы понять о чем речь.... почитаю в инете, может разберусь...

ua6em
ua6em аватар
Offline
Зарегистрирован: 17.08.2016

У меня всё работает, подгружает нужные файлы из подкаталога скетча:

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

Если файлов меньше сотни, к примеру, то даже визуально никаких проблем нет если они в корне располагаются. А если ещё и называть файлы понятно - то и вопросов не возникнет. Зачем в подкаталоги перемещать? 
Понятно, что удобнее, но с ардуино это «такое себе удобство». 

AsNik
Offline
Зарегистрирован: 24.10.2020

ua6em пишет:

У меня всё работает, подгружает нужные файлы из подкаталога скетча:

Ну ненаююю... Может версию нужно обновить до 14, у меня 1.8.10

Хотя я пробовал и 2.0.0 (то еще г...) тоже самое (или не тоже, ну скомпилить не получилось)

А вот если каталог src назвать, то ОК.

BOOM пишет:

Зачем в подкаталоги перемещать? 
Понятно, что удобнее, но с ардуино это «такое себе удобство». 

Я уже понял, что IDE вообще не рассчитана на "серьезные вещи" с разбиением проекта на файлы, причем ошибки начинает выдавать странные и непонятно по какому принципу. Перенес один кусок кода из одного ino в другой а ошибку выдал:

exit status 1

'TButtonClick' has not been declared
 
Хотя в куске кода вообще нет упоминания о нем
 
Вот этот кусок:
// ==================== ISR обработка прерываний ========================
volatile bool f_isrA = false;
volatile char encTick = 0;
void isrA() {
  f_isrA = true;
}

void isrB() {
  if (f_isrA) {        //Если было Прерывание Энодера A
    f_isrA = false;
    digitalRead(ENC_A) == digitalRead(ENC_B) ? encTick = 1 : encTick = -1;
  }
}
// ========================================= END ISR ====================================

 

Возвращаю на место, все компилится... В общем странное поведение...(

Я читал сейчас и про include, заодно узнал про import - почему им не пользуются? Я так понял, это сделали чтоб в библиотеках не делали #pragma once и более длинную версию защиты от повторного использования библиотеки....

AsNik
Offline
Зарегистрирован: 24.10.2020

Сдохла ардуина? Не выдержала моих перепрошивок(

Вопрос, можно повредить загрузчик какими либо(неправильными) действиями из прошивки?  Ардуина вроде как в полуживом состоянии.. При загрузке начинает загружаться и в середине стопарится и начинает ребутится... Мог я своими экспериментами с PROGMEM и работай (неправильной) с указателями убить ардуину/загрузчик? Ну т.е. теоретически это возможно? Ничего хитрого я не делал... пытался в функцию чтения из прогмем передать массив... Вот в эту:

char bufStr[20];
char * GetStringIntex(const byte _i) {
  return strcpy_P(bufStr, (char*)pgm_read_word(&(string_table[_i])));
}

Хотелось бы вместе с _i передавать туда и ссылку на string_table... но что-то у меня пошло не так и ардуина сдохла...

Еще пару штук есть, но ...теперь боюсь) Ну и если убил загрузчик, то ....читаю как прошить optiboot. Одну ардуину буду в качестве программатора делать.. и ей пробовать восстановить дохлую. Если конечно это я убил загрузчик...

 

ddr2
Offline
Зарегистрирован: 27.12.2020

ЕМНИП, ПЗУ имеет ограниченное число перезаписей, меньшее чем флеш-память. ПЗУ что-то около тысячи перезаписей, а флеш около 10 тысяч. Если у вас старая Ардуина, могли израсходовать ресурс перезаписей. Поэтому многие используют эмуляторы. Либо не используют ПЗУ )

sadman41
Offline
Зарегистрирован: 19.10.2016

У меня ардуина первая с 16-го года. Чувствует себя хорошо, несмотря на все издевательства. Остальные горели только от повышенного напряжения. Штуки три лежат в качестве массогабаритных моделей.