Всем добрый день! Пишу скетч для автополива двух горшков, из компонентов у меня энкодер с кнопкой (круглый модуль с пинами 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;
}
Изменено: