Всем добрый день! Пишу скетч для автополива двух горшков, из компонентов у меня энкодер с кнопкой (круглый модуль с пинами S1, S2, KEY), LCD 1602 с подключением по i2c и собсно Arduino Uno 328p.
Написал такой скетч:
	
	
		
			
	
	
	
	
	
	
		
			
			
			
			
			
		
	
	
	
		
	
	
		
	
		
	
Смысл такой:
1. Выводим на дисплей некую страницу меню, в каждой странице может быть 2 строки. Строка состоит из названия параметра (метрики) и значения, которое берётся из переменной (время пока захардкодил, ещё не разбирался с либой time и часами реального времени).
2. Слева от пункта меню, с которым хотим взаимодействовать, выводим указатель ">"
3. Опрашиваем по прерыванию энкодер. Если повернулся и изменился encCounter, то переставляем указатель в зависимости от значения item_pointer, которое берётся из encCounter, но обрезается так, чтобы получить infinity scroll в меню (находясь в п.1 меню, крутим влево и падаем в п.8 и наоборот).
4. По нажатию на кнопку (опрос по прерыванию) мы должны падать в режим редактирования параметра (если параметр подлежит редактированию, например дата, время, целевая влажность, длительность dur_goal -- показания с датчиком не подлежат редактированию).
4.1 Режим редактирования параметра -- указатель печатается не слева, а справа, скролл блокируется (см. getItemPointer()).
4.2 ииии тут мы подошли к проблеме: вращая энкодер, я хочу изменять (пусть даже инкрементом и декрементом 1) значение параметра. И никак, с какой бы стороны не подошёл, не могу понять, как это сделать. Даже меняя функционал принципиально.
Мои догадки:
1. Написать класс для хранения страницы меню, в который добавить методы редактирования параметров и печати.
2. Переписать функцию encTick(), чтоб она учитывала режим работы...? // хз, на самом деле как эта функция работает я не очень понимаю, так как там используется зачем-то побайтовый сдвиг и операции сравнения с байтовыми значениями, которые я не понимаю пока. Если кто-то хочет и готов объяснить -- буду очень благодарен. Вот это я писал сам по урокам и тут мне всё понятно:
	
	
		
			
	
	
	
	
	
	
		
			
			
			
			
			
		
	
	
	
		
	
	
		
	
		
	
3. Переписать логику распечатки меню и указателя. Блокировать скролл другим способом?
								Написал такой скетч:
			
				C++:
			
		
		
		#include <Arduino.h>
//#include <EEPROM.h>
#include <Adafruit_Sensor.h>
#include <DHT.h>
#include <LiquidCrystal_I2C.h>
#define ENC_A 2
#define ENC_B 4
#define OK_BUTTON 3
#define DHT_PIN 6
#define DHTTYPE DHT22
DHT dht(DHT_PIN, DHTTYPE);
LiquidCrystal_I2C lcd(0x27,16,2);
volatile int encCounter;
volatile boolean flag, resetFlag;
volatile uint8_t curState, prevState;
char date[] = "21/10/05";
char time[] = "16:20";
float t_air = 0;
uint8_t hum_air = 0;
uint8_t hum_left = 0;
uint8_t hum_right = 0;
uint8_t hum_goal = 0;
uint8_t dur_goal = 0;
uint32_t timer;
bool param_edit = 0;
uint8_t page_id = 0;
uint8_t prev_page_id = 0;
uint8_t prev_item_ptr = 0;
uint8_t item_pointer = 0;
char menu_pointer = '>';
char param_pointer = '<';
char menu_blank = ' ';
char menu_items[][12] = {
  "Date: ",
  "Time: ",
  "T. air: ",
  "H. air: ",
  "H. L.pot: ",
  "H. R.pot: ",
  "Hum. Goal: ",
  "Dur. Goal: ",
};
void encTick() {
  curState = digitalRead(ENC_A) | digitalRead(ENC_B) << 1;
  if (resetFlag && curState == 0b11) {
    if (prevState == 0b10) encCounter++;
    if (prevState == 0b01) encCounter--;
    resetFlag = 0;
    flag = true;
  }
  if (curState == 0b00) resetFlag = 1;
  prevState = curState;
}
void int0() {
  encTick();
}
void readDht() {
  if (millis() - timer >= 2100) {
    hum_air = dht.readHumidity();
    t_air = dht.readTemperature();
    timer = millis();
  }
}
/*
Функция getItemPointer меняет значение переменных item_pointer и prev_item_ptr.
Если флаг param_edit установлен (была нажата кнопка энкодера), значит контроллер
перешёл в режим редактирования параметра. В этом режиме item_pointer всегда равен
prev_item_pointer. Это обеспечивает блокирование скролла меню, если указатель
меню установлен в 15,0 / 15,1 для редактирования параметра. encCounter при переходе
в режим редактирования параметра сбрасывается в prev_item_ptr -- иначе при выходе
из режима редактирования параметра меню может отрисоваться на произвольной странице.
*/
void getItemPointer() {
  if (!param_edit) {
    if (encCounter > 7) {
      item_pointer = 0;
      encCounter = 0;
    }
    else if (encCounter < 0) {
      item_pointer = 7;
      encCounter = 7;
    }
    else {
      item_pointer = encCounter;
    }
  }
  else {
    encCounter = prev_item_ptr;
    item_pointer = prev_item_ptr;
  }
}
void printMenuPage(){                                    // Prints pages of menu. Each page contains 2 items (rows).
  if (item_pointer >= 0 && item_pointer <= 1) {          // Page 1: Date & Time
    lcd.setCursor(1,0);
    lcd.print(menu_items[0]);
    lcd.print(date);
    lcd.setCursor(1,1);
    lcd.print(menu_items[1]);
    lcd.print(time);
    page_id = 0;
  }
  if (item_pointer >= 2 && item_pointer <= 3) {          // Page 2: Humidity Air & Temp Air
    char t_air_char[4];
    dtostrf(t_air, sizeof(t_air_char), 1, t_air_char);
    lcd.setCursor(1,0);
    lcd.print(menu_items[2]);
    lcd.print(t_air_char);
    lcd.print("*C");
    lcd.setCursor(1,1);
    lcd.print(menu_items[3]);
    lcd.print(hum_air);
    lcd.print("%");
    page_id = 1;
  }
  if (item_pointer >= 4 && item_pointer <= 5) {          // Page 3: Humidity Left Pot & Humidity Right Pot
    lcd.setCursor(1,0);
    lcd.print(menu_items[4]);
    lcd.print(hum_left);
    lcd.print("%");
    lcd.setCursor(1,1);
    lcd.print(menu_items[5]);
    lcd.print(hum_right);
    lcd.print("%");
    page_id = 2;
  }
  if (item_pointer >= 6 && item_pointer <= 7) {          // Page 4: Pot Humidity Goal & Daylight Duration Goal
    lcd.setCursor(1,0);
    lcd.print(menu_items[6]);
    lcd.print(hum_goal);
    lcd.print("%");
    lcd.setCursor(1,1);
    lcd.print(menu_items[7]);
    lcd.print(dur_goal);
    lcd.print("h");
    page_id = 3;
  }
}
/*
Функция printPointer отрисовывает один из двух видов указателей меню: ">" или "<".
Вид указателя зависит от булевой переменной param_edit. Если param_edit = false, то
печатается обычный указатель меню слева: ">". Если true, печатается указатель
редактируемого параметра "<".
*/
void printPointer() {
  if (!param_edit) {
    if (item_pointer % 2 == 0) {
        lcd.setCursor(0, 0);
        lcd.print(menu_pointer);
        lcd.setCursor(0, 1);
        lcd.print(menu_blank);
        lcd.setCursor(15, 0);
        lcd.print(menu_blank);
        lcd.setCursor(15, 1);
        lcd.print(menu_blank);
      }
    else {
        lcd.setCursor(0, 0);
        lcd.print(menu_blank);
        lcd.setCursor(0, 1);
        lcd.print(menu_pointer);
        lcd.setCursor(15, 0);
        lcd.print(menu_blank);
        lcd.setCursor(15, 1);
        lcd.print(menu_blank);     
      }
  }
  else {
    if (item_pointer % 2 == 0) {
      lcd.setCursor(0,0);
      lcd.print(menu_blank);
      lcd.setCursor(0,1);
      lcd.print(menu_blank);
      lcd.setCursor(15, 0);
      lcd.print(param_pointer);
      lcd.setCursor(15, 1);
      lcd.print(menu_blank);
    }
    else {
      lcd.setCursor(0,0);
      lcd.print(menu_blank);
      lcd.setCursor(0,1);
      lcd.print(menu_blank);
      lcd.setCursor(15, 0);
      lcd.print(menu_blank);
      lcd.setCursor(15, 1);
      lcd.print(param_pointer);   
    }
  }
}
void editParam() {
  switch (item_pointer)
  {
  case 6:
    encTick();
    break;
  case 7:
    break;
  }
}
void printMenu() {
  if (page_id != prev_page_id) {
    lcd.clear();
  }
  prev_page_id = page_id;
  getItemPointer();
  printPointer();
  printMenuPage();
  if (param_edit) {
    editParam();
  }
  prev_item_ptr = item_pointer;
}
void encButton() {
  uint8_t button_state = digitalRead(OK_BUTTON);
  delay(100);
  if (button_state == LOW) {
    param_edit = !param_edit;
    Serial.println(param_edit);
    Serial.println("Button was pressed\r\n");
  }
}
void setup() {
  Serial.begin(9600);
  attachInterrupt(0, int0, CHANGE);
  attachInterrupt(1, encButton, LOW);
  pinMode(DHT_PIN, INPUT);
  pinMode(OK_BUTTON, INPUT_PULLUP);
  dht.begin();
  lcd.init();
  lcd.clear();
  lcd.backlight();
  lcd.home();
  lcd.print("Welcome to Sol");
  lcd.setCursor(0,1);
  lcd.print("drip controller!");
  delay(1500);
  lcd.clear(); 
}
void loop() {
  printMenu();
  readDht();
  encButton();
  Serial.println(encCounter);
  Serial.println(flag);
  Serial.println(resetFlag);
  Serial.println(prevState);
  Serial.println(curState);
  Serial.println("");
}Смысл такой:
1. Выводим на дисплей некую страницу меню, в каждой странице может быть 2 строки. Строка состоит из названия параметра (метрики) и значения, которое берётся из переменной (время пока захардкодил, ещё не разбирался с либой time и часами реального времени).
2. Слева от пункта меню, с которым хотим взаимодействовать, выводим указатель ">"
3. Опрашиваем по прерыванию энкодер. Если повернулся и изменился encCounter, то переставляем указатель в зависимости от значения item_pointer, которое берётся из encCounter, но обрезается так, чтобы получить infinity scroll в меню (находясь в п.1 меню, крутим влево и падаем в п.8 и наоборот).
4. По нажатию на кнопку (опрос по прерыванию) мы должны падать в режим редактирования параметра (если параметр подлежит редактированию, например дата, время, целевая влажность, длительность dur_goal -- показания с датчиком не подлежат редактированию).
4.1 Режим редактирования параметра -- указатель печатается не слева, а справа, скролл блокируется (см. getItemPointer()).
4.2 ииии тут мы подошли к проблеме: вращая энкодер, я хочу изменять (пусть даже инкрементом и декрементом 1) значение параметра. И никак, с какой бы стороны не подошёл, не могу понять, как это сделать. Даже меняя функционал принципиально.
Мои догадки:
1. Написать класс для хранения страницы меню, в который добавить методы редактирования параметров и печати.
2. Переписать функцию encTick(), чтоб она учитывала режим работы...? // хз, на самом деле как эта функция работает я не очень понимаю, так как там используется зачем-то побайтовый сдвиг и операции сравнения с байтовыми значениями, которые я не понимаю пока. Если кто-то хочет и готов объяснить -- буду очень благодарен. Вот это я писал сам по урокам и тут мне всё понятно:
			
				C++:
			
		
		
		void tick() {
  S1CurrSate = digitalRead(S1);
  delay(50);
  if (S1PrevState != S1CurrSate && S1CurrSate == 1) {
    if (digitalRead(S2) != S1CurrSate) {
      Serial.print("LEFT");
      encCounter++;
    }
    else {
      Serial.print("RIGHT");
      encCounter--;
    }
  }
  S1PrevState = S1CurrSate;
}
								
									Изменено: 
									
							
						
						
	
					 
				
		 
 
		
