Финальная версия кода, прошедшая месяцы полировки:
Изначальный пост (сохранён для истории):
Ежедневная лень мешает жить?
Нужны всего одна кнопка и дисплей
(Ножки кнопки разогнуты и воткнуты в порты. Дисплей с I2C модулем впаян прямо в Ардуино в подходящие отверстия, которые даже располагаются в нужном порядке. Пришлось выпаять аналоговые порты Ардуино, чтобы дисплей поместился)
Что делает устройство: Когда нажимаешь на кнопку, выбирается случайное задание и запускается таймер на 25 минут. После выполнения задания на счёт прибавляются деньги (и загорается лампочка, но пока ещё не реализовал), и начинается 5-минутный перерыв.
Как работать с этим устройством:
Код программы: (обратите внимание на верхние строки - в них можно выбирать параметры под свой вкус, но научные исследования не советуют это делать)
Это реально мой первый написанный выстраданный код. Если честно, я пишу этот пост только чтобы не потерять волшебные функции записи String в EEPROM.
Комментарии по коду: все данные записываются в EEPROM - поэтому список заданий и денег не удаляется при отключении от USB. Код закомментирован по максимуму, чтобы даже я вспомнил, как что работает.
Обратите внимание на первую закомментированную строку в void setup() - используйте, если вдруг пригодится сбросить устройство до заводских настроек (хотя не должно).
Если захотите добавить зажигание светодиодов с каждым выполненным заданием, для вас переменная tasksCompleted . Она принимает значения от 0 до 4. При 4 сразу же обнуляется. (Как придут детали, я допишу эту часть кода и удалю этот комментарий)
Отдельно скопирую фрагмент кода, который я месяц искал, чтобы всё заработало. Запись String в EEPROM:
Буду очень благодарен вашим отзывам по использованию, авось и научную статейку напишем
Изначальный пост (сохранён для истории):
Ежедневная лень мешает жить?
Нужны всего одна кнопка и дисплей
(Ножки кнопки разогнуты и воткнуты в порты. Дисплей с I2C модулем впаян прямо в Ардуино в подходящие отверстия, которые даже располагаются в нужном порядке. Пришлось выпаять аналоговые порты Ардуино, чтобы дисплей поместился)
Что делает устройство: Когда нажимаешь на кнопку, выбирается случайное задание и запускается таймер на 25 минут. После выполнения задания на счёт прибавляются деньги (и загорается лампочка, но пока ещё не реализовал), и начинается 5-минутный перерыв.
Я с нуля учил Java, купил Arduino, изучал его синтаксис, провёл недели, пытаясь написать код, и ещё месяцы, пытаясь понять, почему не компилируется. Я читал научные статьи по теме прокрастинации, изучал существующие методы борьбы с ленью, сам боролся с желанием всё бросить, но, пройдя через весь этот ад, создал этот проект.
За основу взял Метод Помидора. Умные дядьки доказали, что наиболее продуктивно человек работает в таком режиме: 25 минут работы, 5 минут перерыва. Один такой цикл называется "Помидор". Каждые 4 "Помидора" нужно делать 30-минутный перерыв.
Чтобы замотивировать, я прочитал с десяток научных статей, и объединил их советы по-своему: за выполнение заданий начисляются деньги. Тут магии нет, деньги берутся из копилки, но с одним условием. Те деньги, которые заработаны на устройстве, можно потратить на ВСЁ, ЧТО УГОДНО. Это уточнение очень важно. Хоть на премиум аккаунт, хоть на алкоголь, хоть на подписку на Спотифай - на любую "хрень" (или не хрень, а реально полезную вещь), на которую в обычной жизни жалко потратить деньги. Тут всё просто: деньги, которые тут можно заработать, имеют небольшую ценность (ну около 250 рублей в день), но когда ты понимаешь, что на эти деньги ты можешь купить ВООБЩЕ всё, что хочешь, без малейшего зазрения совести, то они обретают огромную ценность. Да даже элементарно Киндер Сюрприз я не ел 10 лет, потому что жаба душила потратить 60 рублей на 10 грамм шоколада.
Также тут работает математика. Чтобы получить бесплатно премиумный танк в условной игре про танки, авиацию и флот нужно неделю играть в игру. Тут же ты получишь его за 1 день, так ещё и не будешь гореть с команды, не будешь нервничать, не будешь портить зрение и позвоночник, ещё и саморазвитием занимаешься.
А если собрать это устройство для ребёнка - это будет учить его ценить своё время, и ценить деньги (вот это почти дословная цитата из научного журнала). Ребёнок 1 час делает задания и получает 50 рублей - ребёнок может потратить эти 50 рублей на час игры на компьютере, а может поучиться ещё 1 час и на 125 рублей купить игрушку или сходить в Макдак.
Ещё одна важная черта устройства: задания из списка выбираются в случайном порядке. Нигде нет таких аналогов с такой функцией, а она очень важна. Ну ну могу я выбирать из 10 скучных заданий то, которое менее скучное. А тратя даже по 25 минут на задание, ты приближаешься к его завершению.
И финальная функция (которая содержится в улучшенной версии проекта) - это световая индикация. В проекте есть один RGD светодиод. Когда ты не сделал ни одного задания светится красный светодиод. Он и светит, и бесит. Когда выполнил одно задание, цвет меняется. Даже выбор цветов основан на литературных данных. Световая индикация один из лучших способов наградить психику человека. Так это ещё не всё: когда выполнены 4 задания, загорается красивый зелёный цвет, и за крайнее выполненное задание даётся в 2 раза больше денег.
Чтобы замотивировать, я прочитал с десяток научных статей, и объединил их советы по-своему: за выполнение заданий начисляются деньги. Тут магии нет, деньги берутся из копилки, но с одним условием. Те деньги, которые заработаны на устройстве, можно потратить на ВСЁ, ЧТО УГОДНО. Это уточнение очень важно. Хоть на премиум аккаунт, хоть на алкоголь, хоть на подписку на Спотифай - на любую "хрень" (или не хрень, а реально полезную вещь), на которую в обычной жизни жалко потратить деньги. Тут всё просто: деньги, которые тут можно заработать, имеют небольшую ценность (ну около 250 рублей в день), но когда ты понимаешь, что на эти деньги ты можешь купить ВООБЩЕ всё, что хочешь, без малейшего зазрения совести, то они обретают огромную ценность. Да даже элементарно Киндер Сюрприз я не ел 10 лет, потому что жаба душила потратить 60 рублей на 10 грамм шоколада.
Также тут работает математика. Чтобы получить бесплатно премиумный танк в условной игре про танки, авиацию и флот нужно неделю играть в игру. Тут же ты получишь его за 1 день, так ещё и не будешь гореть с команды, не будешь нервничать, не будешь портить зрение и позвоночник, ещё и саморазвитием занимаешься.
А если собрать это устройство для ребёнка - это будет учить его ценить своё время, и ценить деньги (вот это почти дословная цитата из научного журнала). Ребёнок 1 час делает задания и получает 50 рублей - ребёнок может потратить эти 50 рублей на час игры на компьютере, а может поучиться ещё 1 час и на 125 рублей купить игрушку или сходить в Макдак.
Ещё одна важная черта устройства: задания из списка выбираются в случайном порядке. Нигде нет таких аналогов с такой функцией, а она очень важна. Ну ну могу я выбирать из 10 скучных заданий то, которое менее скучное. А тратя даже по 25 минут на задание, ты приближаешься к его завершению.
И финальная функция (которая содержится в улучшенной версии проекта) - это световая индикация. В проекте есть один RGD светодиод. Когда ты не сделал ни одного задания светится красный светодиод. Он и светит, и бесит. Когда выполнил одно задание, цвет меняется. Даже выбор цветов основан на литературных данных. Световая индикация один из лучших способов наградить психику человека. Так это ещё не всё: когда выполнены 4 задания, загорается красивый зелёный цвет, и за крайнее выполненное задание даётся в 2 раза больше денег.
Как работать с этим устройством:
0) Собираете устройство по схеме. Подключаете плату к компьютеру. Загружаете скетч на Ардуино. Не отключаете плату от компьютера.
1) Добавить задание. Открываете консоль (монитор COM порта, ну лупа справа вверху в окне с кодом Ардуино) и печатаете туда на английском языке задание, можно использовать пробелы. В длину оно должно быть не более 13 символов. (И отключите в низу в окне консоли добавление новой линии в конце строки.) (Задание можно добавлять только когда НЕ запущен таймер)
2) Нажимаете на кнопку
3) Программа выбирает задание. Вы видите его название на экране. 25 минут усердно выполняете задание. Если в это время нажать кнопку, то задание прервётся. Если нажать 3 раза кнопку, то текущее задание удалится из памяти
4) После выполнения задания вам зачисляется зарплата и начинается отдых (я назвал его "REST"). Если нажать кнопку, то отдых закончится
5) Если хотите выполнить ещё задание - нажимаете на кнопку (пункт 2)
-) Если, во время того, как таймер выключен (другими словами, когда на экране отображается только количество денег) нажать 3 раза кнопку, то количество денег обнулится. Это нужно в тех случаях, когда вы тратите эти деньги в реальном мире
1) Добавить задание. Открываете консоль (монитор COM порта, ну лупа справа вверху в окне с кодом Ардуино) и печатаете туда на английском языке задание, можно использовать пробелы. В длину оно должно быть не более 13 символов. (И отключите в низу в окне консоли добавление новой линии в конце строки.) (Задание можно добавлять только когда НЕ запущен таймер)
2) Нажимаете на кнопку
3) Программа выбирает задание. Вы видите его название на экране. 25 минут усердно выполняете задание. Если в это время нажать кнопку, то задание прервётся. Если нажать 3 раза кнопку, то текущее задание удалится из памяти
4) После выполнения задания вам зачисляется зарплата и начинается отдых (я назвал его "REST"). Если нажать кнопку, то отдых закончится
5) Если хотите выполнить ещё задание - нажимаете на кнопку (пункт 2)
-) Если, во время того, как таймер выключен (другими словами, когда на экране отображается только количество денег) нажать 3 раза кнопку, то количество денег обнулится. Это нужно в тех случаях, когда вы тратите эти деньги в реальном мире
Код программы: (обратите внимание на верхние строки - в них можно выбирать параметры под свой вкус, но научные исследования не советуют это делать)
POMODORO:
#include <LiquidCrystal_I2C.h>
#include <GyverButton.h>
#include <EEPROM.h>
LiquidCrystal_I2C lcd(0x27, 16, 2);
GButton startButt(2); //кнопка подключена на 9 пин одной ногой, и на землю (Ground, GND) другой ногой
int workTime = 25; //время одного задания в минутах (по умолчанию: 25)
int idleTime = 5; //время отдыха в минутах (по умолчанию: 5)
byte tasksBeforeBigIdle = 4; //сколько нужно выполнить заданий до большого перерыва (по умолчанию: 4)
int bigIdleTime = 30; //время большого перерыва в минутах (по умолчанию 30);
bool doubleMoney = true; //давать ли двойные деньги за каждое выполненное четвёртое задание (по умолчанию: true) (на 20% повышает средний доход)
int goldPerMinute = 1; //денег за минуту выполнения задания (по умолчанию: 1)
int gold;
int tasksCompleted;
int selectedTaskNumber = 1;
int previouslySelectedTaskNumber = 11; // 11 - код в первый прогон выбросит из выдачи 11 задание (но у нас их всего 10, поэтому мы не заметим этого). Это костыль
bool countDownCompleted;
char selectedTaskName[18];
int selectedTaskAddress;
byte Filler1[] = { //закрашенный на 1/5 прямоугольник (нужен для красивого заполнения шкалы в таймере)
B10000,
B10000,
B10000,
B10000,
B10000,
B10000,
B10000,
B10000
};
byte Filler2[] = { //закрашенный на 2/5 прямоугольник (нужен для красивого заполнения шкалы в таймере)
B11000,
B11000,
B11000,
B11000,
B11000,
B11000,
B11000,
B11000
};
byte Filler3[] = { //закрашенный на 3/5 прямоугольник (нужен для красивого заполнения шкалы в таймере)
B11100,
B11100,
B11100,
B11100,
B11100,
B11100,
B11100,
B11100
};
byte Filler4[] = { //закрашенный на 4/5 прямоугольник (нужен для красивого заполнения шкалы в таймере)
B11110,
B11110,
B11110,
B11110,
B11110,
B11110,
B11110,
B11110
};
byte Filler5[] = { //закрашенный на 5/5 прямоугольник (нужен для красивого заполнения шкалы в таймере)
B11111,
B11111,
B11111,
B11111,
B11111,
B11111,
B11111,
B11111
};
void setup() {
//EEPROM.write(555, 1); //заскомментируй, если зачем-то хочешь полностью очистить память. После загрузки на плату закомментируй и загрузи заново.
pinMode(8, OUTPUT);
digitalWrite(8, HIGH);
if (EEPROM.read(555) != 2) { //проверяем, происходит ли запуск в первый раз
for (int i = 1; i <= 20; i++) {
EEPROM.put(i * 20, ""); //очищаем ячейки, в которых будут храниться задания
}
EEPROM.put(1000, ""); //очищаем ячейку хранения годы
EEPROM.put(555, 2); //записываем в проверяемую ячейку заданное число. Если прога увидит это число, она будет знать, что программу уже запускали
}
Serial.begin(9600); //включаем доступ к консоли компа
lcd.init(); //запускаем дисплей
lcd.backlight(); //включаем подсветку
lcd.createChar(1, Filler1); //создаём символы прямоугольников, чтобы они заполняли шкалу таймера
lcd.createChar(2, Filler2);
lcd.createChar(3, Filler3);
lcd.createChar(4, Filler4);
lcd.createChar(5, Filler5);
randomSeed(millis()); //создаём новое зерно для рандома, чтобы задания каждый раз выбирались в новом порядке
gold = EEPROM.read(1000); //загружаем значение голды из внутренней памяти ардуино (1000 - это адрес значения голды в памяти. Выбрал число от балды)
printGold(); //выводим на экран деньги
lcd.backlight();
}
void loop() {
importDataFromConsole(); //если в консоль пишем слово, оно добавляется в список заданий
startButt.tick(); //опрашиваем кнопку СТАРТ
if (startButt.isSingle()) { //если кнопка нажата 1 раз
selectNewTask(); //выбрать новое задание
startNewTask(); //начать новое задание //если нажать СТАРТ, то таймер выключится
if (countDownCompleted == 1) { //если время задания подошло к концу
giveGold(); //дать деньги
countDownCompleted = 0; //костыль, потому что countDown() после выполнения переключает флаг countDownCompleted = 1
++tasksCompleted; //увеличить счетчик выполненных заданий
if (tasksCompleted < tasksBeforeBigIdle) { //если выполнено меньше 4 заданий
lcd.setCursor(13, 0);
lcd.print(gold); //вывести на экран обновленную сумму денег
startIdle(idleTime); //начать отдых //если нажать СТАРТ, то таймер выключится
countDownCompleted = 0;
}
else { //если выполнены 4 задания, то начинается большой отдых
if (doubleMoney) {
giveGold();
}; //если включены двойные деньги за серию заданий - дать деньги ещё раз
tasksCompleted = 0; //обнуляем число выполненных заданий
lcd.setCursor(13, 0);
lcd.print(gold); //вывести на экран обновленную сумму денег
startIdle(bigIdleTime); //начать большой отдых //если нажать СТАРТ, то таймер выключится
countDownCompleted = 0;
}
}
};
if (startButt.isTriple()) { //если кнопка старт нажата 3 раза, то обнуляем деньги
gold = 0;
EEPROM.put(1000, gold); //записываем голд в энергонезависимую память;
printGold();
}
}
void importDataFromConsole() {
if (Serial.available() > 0) { //если в консоли есть текст
// тогда читаем этот текст:
String incomingText = Serial.readString();
for (int i = 1; i <= 20; i++) { //максимум можно записать 20 заданий
int firstLetter = EEPROM.read((i * 20));
if (firstLetter == 0) { //если ячейка пустая, то импортируем текст
char incomingTask[18];
strcpy(incomingTask, incomingText.c_str());
EEPROM.put((i * 20), incomingTask);
lcd.setCursor(0, 0);
lcd.backlight();
lcd.print(incomingText); //на одну секунду выводим на экран название нового задания
delay(1000);
lcd.noBacklight();
lcd.clear();
//часть кода, которая пишет в консоль список заданий в памяти устройства, и сколько осталось свободного места
int freeSpaceOfTasks = 20;
for (int j = 1; j <= 20; j++) {
if (EEPROM.read(j * 20) != 0) {
char existingTask[18];
EEPROM.get((j * 20), existingTask);
Serial.print(j);
Serial.print(" задание: ");
Serial.println(existingTask);
freeSpaceOfTasks = freeSpaceOfTasks - 1;
}
}
Serial.print("Можно добавить ещё ");
Serial.print(freeSpaceOfTasks);
Serial.println(" заданий"); //окончание кода вывода списка заданий в консоль
break;
}
}
}
}
void selectNewTask() {
int i = 0;
while ((EEPROM.read(selectedTaskNumber * 20) == 0) || selectedTaskNumber == previouslySelectedTaskNumber) { //если выбралось пустое задание, или то, которое уже было, то заново "бросаем кубик"
selectedTaskNumber = random(1, 11); //от 1(включительно) до 21(не включительно)
++i;
if (i > 40) { //если в списке всего одно задание, то выберется оно (по сути, если код выполнится "впустую" более 40 раз)
selectedTaskNumber = previouslySelectedTaskNumber;
break;
}
}
previouslySelectedTaskNumber = selectedTaskNumber;
selectedTaskAddress = selectedTaskNumber * 20;
EEPROM.get(selectedTaskAddress, selectedTaskName);
}
void startNewTask() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(selectedTaskName); //выводим на дисплей название задания
lcd.setCursor(12, 0);
lcd.print("P");
lcd.setCursor(13, 0);
lcd.print(gold); //выводим на дисплей деньги
countDown(workTime, 5, 1, selectedTaskName);
}
void countDown(int Time, byte cursorX, byte cursorY, char printedText[18]) { //таймер обратного отсчёта с записью данных на LCD
lcd.backlight();
lcd.setCursor(0, 0);
lcd.print(printedText);
long startTime = millis();
while (((Time * 60000) + startTime) > millis()) {
long TimeLeft = (((Time * 60000) + startTime) - millis()) / 1000; //сколько осталось времени в секундах
drawLowLine(Time, TimeLeft);
lcd.setCursor(cursorX, cursorY);
if (TimeLeft / 60 < 10) {
lcd.print("0");
lcd.setCursor(cursorX + 1, cursorY);
lcd.print(TimeLeft / 60); //перевод в минуты
} else {
lcd.print(TimeLeft / 60);
};
lcd.setCursor(cursorX + 2, cursorY);
lcd.print(":");
lcd.setCursor(cursorX + 3, cursorY);
if (TimeLeft % 60 < 10) { //перевод в секунды
lcd.print("0");
lcd.setCursor(cursorX + 4, cursorY);
lcd.print(TimeLeft % 60);
} else {
lcd.print(TimeLeft % 60);
};
if (TimeLeft <= 0) {
countDownCompleted = 1;
}; //флаг, позволяет отследить, прерывался ли таймер. Деньги выдаются только если он не прерывался
startButt.tick();
if (startButt.isSingle()) { //если кнопка СТАРТ нажата, то таймер прерывается...
break;
}
else if (startButt.isTriple()) { //если кнопка СТАРТ нажата 3 раза, то это задание удаляется
EEPROM.put((previouslySelectedTaskNumber * 20), 0);
break;
}
};
printGold();
}
void drawLowLine(int Time, long TimeLeft) {
float accurateLowLine = (((Time * 60) - TimeLeft) * 16) / (Time * 60) ; //16 - это количество символов на дисплее;
int lowLine = floor(accurateLowLine);
lcd.setCursor(lowLine, 1);
float accurateLittleLowLine = ((((Time * 60) - TimeLeft) * 16 * 10) / (Time * 60)) % 10 ; //16 - это количество символов на дисплее;
int littleLowLine = ceil(accurateLittleLowLine);
if (littleLowLine >= 0 && littleLowLine < 2) {
lcd.write(1);
}
else if (littleLowLine >= 2 && littleLowLine < 4) {
lcd.write(2);
}
else if (littleLowLine >= 4 && littleLowLine < 6) {
lcd.write(3);
}
else if (littleLowLine >= 6 && littleLowLine < 8) {
lcd.write(4);
}
else if (littleLowLine >= 8 && littleLowLine < 10) {
lcd.write(5);
};
}
void giveGold() {
gold = gold + goldPerMinute * workTime;
EEPROM.put(1000, gold); //записываем голд в энергонезависимую память
}
void startIdle(int idleTimeVal) {
char rest[18] = "REST";
countDown(idleTimeVal, 5, 0, rest);
}
void printGold() {
lcd.clear(); //стирается всё на экране
lcd.setCursor(12, 0); //и печатаются деньги
lcd.print("P");
lcd.setCursor(13, 0);
lcd.print(gold);
lcd.noBacklight();
}
}
Комментарии по коду: все данные записываются в EEPROM - поэтому список заданий и денег не удаляется при отключении от USB. Код закомментирован по максимуму, чтобы даже я вспомнил, как что работает.
Обратите внимание на первую закомментированную строку в void setup() - используйте, если вдруг пригодится сбросить устройство до заводских настроек (хотя не должно).
Если захотите добавить зажигание светодиодов с каждым выполненным заданием, для вас переменная tasksCompleted . Она принимает значения от 0 до 4. При 4 сразу же обнуляется. (Как придут детали, я допишу эту часть кода и удалю этот комментарий)
Отдельно скопирую фрагмент кода, который я месяц искал, чтобы всё заработало. Запись String в EEPROM:
String to EEPROM:
#include <EEPROM.h>
// Constants:-
String myString="Arduino UNO";
void setup()
{
Serial.begin(9600);
char myBoard[15];
strcpy(myBoard,myString.c_str());
EEPROM.put(0,myBoard);
char theBoard[15];
EEPROM.get(0,theBoard);
Serial.println(theBoard);
}
Схема стала намного сложнее, и требует пайки, либо макетной платы, но только так можно оценить самый полный и лучший опыт использования устройства
Световая индикация помогает следить, сколько заданий выполнено. Когда заканчивается таймер, светодиод несколько раз мигает - и это удобно даёт понять, что начался перерыв.
Еле уместил всё на макетной плате. Когда спаял - вспомнил, что не выпаял из ардуино встроенные светодиоды, пришлось заклеивать изолентой. Не забудьте.
Световая индикация помогает следить, сколько заданий выполнено. Когда заканчивается таймер, светодиод несколько раз мигает - и это удобно даёт понять, что начался перерыв.
POMODORO_PRO:
#include <LiquidCrystal_I2C.h>
#include <GyverButton.h>
#include <EEPROM.h>
#include <GyverRGB.h>
GRGB diode(10, 9, 6); // куда подключены цвета светодиода (R, G, B)
LiquidCrystal_I2C lcd(0x27, 16, 2);
GButton startButt(2); //кнопка подключена на 9 пин одной ногой, и на землю (Ground, GND) другой ногой
int workTime = 25; //время одного задания в минутах (по умолчанию: 25)
int idleTime = 5; //время отдыха в минутах (по умолчанию: 5)
byte tasksBeforeBigIdle = 4; //сколько нужно выполнить заданий до большого перерыва (по умолчанию: 4)
int bigIdleTime = 30; //время большого перерыва в минутах (по умолчанию 30);
bool doubleMoney = true; //давать ли двойные деньги за каждое выполненное четвёртое задание (по умолчанию: true) (на 20% повышает средний доход)
int goldPerMinute = 1; //денег за минуту выполнения задания (по умолчанию: 1)
int maxDelay = 2; //через сколько часов после выполнения последнего задания счётчик заданий сбросится (по умолчанию: 2) (например, чтобы через 2 часа отдыха снова загорелась красная лампочка, намекая, что нужно поработать)
int gold;
int tasksCompleted;
int selectedTaskNumber = 1;
int previouslySelectedTaskNumber = 11; // 11 - код в первый прогон выбросит из выдачи 11 задание (но у нас их максимум 20, а обычно не более 5, поэтому мы не заметим этого). Это костыль
bool countDownCompleted;
long timeOfLastTask;
char selectedTaskName[18];
int selectedTaskAddress;
byte Filler1[] = { //закрашенный на 1/5 прямоугольник (нужен для красивого заполнения шкалы в таймере)
B10000,
B10000,
B10000,
B10000,
B10000,
B10000,
B10000,
B10000
};
byte Filler2[] = { //закрашенный на 2/5 прямоугольник (нужен для красивого заполнения шкалы в таймере)
B11000,
B11000,
B11000,
B11000,
B11000,
B11000,
B11000,
B11000
};
byte Filler3[] = { //закрашенный на 3/5 прямоугольник (нужен для красивого заполнения шкалы в таймере)
B11100,
B11100,
B11100,
B11100,
B11100,
B11100,
B11100,
B11100
};
byte Filler4[] = { //закрашенный на 4/5 прямоугольник (нужен для красивого заполнения шкалы в таймере)
B11110,
B11110,
B11110,
B11110,
B11110,
B11110,
B11110,
B11110
};
byte Filler5[] = { //закрашенный на 5/5 прямоугольник (нужен для красивого заполнения шкалы в таймере)
B11111,
B11111,
B11111,
B11111,
B11111,
B11111,
B11111,
B11111
};
void setup() {
//EEPROM.write(555, 1); //заскомментируй, если зачем-то хочешь полностью очистить память. После загрузки на плату закомментируй и загрузи заново.
pinMode(8, OUTPUT);
digitalWrite(8, HIGH);
if (EEPROM.read(555) != 2) { //проверяем, происходит ли запуск в первый раз
for (int i = 1; i <= 20; i++) {
EEPROM.put(i * 20, ""); //очищаем ячейки, в которых будут храниться задания
}
EEPROM.put(1000, ""); //очищаем ячейку хранения годы
EEPROM.put(555, 2); //записываем в проверяемую ячейку заданное число. Если прога увидит это число, она будет знать, что программу уже запускали
}
Serial.begin(9600); //включаем доступ к консоли компа
lcd.init(); //запускаем дисплей
lcd.backlight(); //включаем подсветку
lcd.createChar(1, Filler1); //создаём символы прямоугольников, чтобы они заполняли шкалу таймера
lcd.createChar(2, Filler2);
lcd.createChar(3, Filler3);
lcd.createChar(4, Filler4);
lcd.createChar(5, Filler5);
randomSeed(millis()); //создаём новое зерно для рандома, чтобы задания каждый раз выбирались в новом порядке
gold = EEPROM.read(1000); //загружаем значение голды из внутренней памяти ардуино (1000 - это адрес значения голды в памяти. Выбрал число от балды)
printGold(); //выводим на экран деньги
lcd.backlight();
diode.setRGB(2, 0, 0); //пока задания не начались, светодиод горит красным
}
void loop() {
importDataFromConsole(); //если в консоль пишем слово, оно добавляется в список заданий
startButt.tick(); //опрашиваем кнопку СТАРТ
if (startButt.isSingle()) { //если кнопка нажата 1 раз
selectNewTask(); //выбрать новое задание
startNewTask(); //начать новое задание //если нажать СТАРТ, то таймер выключится
if (countDownCompleted == 1) { //если время задания подошло к концу
giveGold(); //дать деньги
countDownCompleted = 0; //костыль, потому что countDown() после выполнения переключает флаг countDownCompleted = 1
++tasksCompleted; //увеличить счетчик выполненных заданий
lcdBlink(); //мигаем светодиодом, когда задание закончено
startLCD(); //переключить цвет светодиода
if ((tasksCompleted % tasksBeforeBigIdle) > 0) { //если выполнено меньше 4 заданий
lcd.setCursor(13, 0);
lcd.print(gold); //вывести на экран обновленную сумму денег
startIdle(idleTime); //начать отдых //если нажать СТАРТ, то таймер выключится
countDownCompleted = 0;
timeOfLastTask = millis(); //начинаем отсчёт со времени последнего задания
}
else { //если выполнены 4 задания, то начинается большой отдых
if (doubleMoney) {
giveGold();
}; //если включены двойные деньги за серию заданий - дать деньги ещё раз
tasksCompleted = 0; //обнуляем число выполненных заданий
lcd.setCursor(13, 0);
lcd.print(gold); //вывести на экран обновленную сумму денег
startIdle(bigIdleTime); //начать большой отдых //если нажать СТАРТ, то таймер выключится
countDownCompleted = 0;
timeOfLastTask = millis(); //начинаем отсчёт со времени последнего задания
}
}
};
if (startButt.isTriple()) { //если кнопка старт нажата 3 раза, то обнуляем деньги
gold = 0;
EEPROM.put(1000, gold); //записываем голд в энергонезависимую память;
printGold();
};
if ((millis() - timeOfLastTask) >= (maxDelay * 3600000)) { //если с последнего задания прошло очень много времени
tasksCompleted = 0; //обнуляем количество выполненых заданий
diode.setRGB(2, 0, 0); //включаем красный светодиод
};
}
void importDataFromConsole() {
if (Serial.available() > 0) { //если в консоли есть текст
// тогда читаем этот текст:
String incomingText = Serial.readString();
for (int i = 1; i <= 20; i++) { //максимум можно записать 20 заданий
int firstLetter = EEPROM.read((i * 20));
if (firstLetter == 0) { //если ячейка пустая, то импортируем текст
char incomingTask[18];
strcpy(incomingTask, incomingText.c_str());
EEPROM.put((i * 20), incomingTask);
lcd.setCursor(0, 0);
lcd.backlight();
lcd.print(incomingText); //на одну секунду выводим на экран название нового задания
delay(1000);
lcd.noBacklight();
lcd.clear();
//часть кода, которая пишет в консоль список заданий в памяти устройства, и сколько осталось свободного места
int freeSpaceOfTasks = 20;
for (int j = 1; j <= 20; j++) {
if (EEPROM.read(j * 20) != 0) {
char existingTask[18];
EEPROM.get((j * 20), existingTask);
Serial.print(j);
Serial.print(" задание: ");
Serial.println(existingTask);
freeSpaceOfTasks = freeSpaceOfTasks - 1;
}
}
Serial.print("Можно добавить ещё ");
Serial.print(freeSpaceOfTasks);
Serial.println(" заданий"); //окончание кода вывода списка заданий в консоль
break;
}
}
}
}
void selectNewTask() {
int i = 0;
while ((EEPROM.read(selectedTaskNumber * 20) == 0) || selectedTaskNumber == previouslySelectedTaskNumber) { //если выбралось пустое задание, или то, которое уже было, то заново "бросаем кубик"
selectedTaskNumber = random(1, 11); //от 1(включительно) до 21(не включительно)
++i;
if (i > 40) { //если в списке всего одно задание, то выберется оно (по сути, если код выполнится "впустую" более 40 раз)
selectedTaskNumber = previouslySelectedTaskNumber;
break;
}
}
previouslySelectedTaskNumber = selectedTaskNumber;
selectedTaskAddress = selectedTaskNumber * 20;
EEPROM.get(selectedTaskAddress, selectedTaskName);
}
void startNewTask() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(selectedTaskName); //выводим на дисплей название задания
lcd.setCursor(12, 0);
lcd.print("P");
lcd.setCursor(13, 0);
lcd.print(gold); //выводим на дисплей деньги
countDown(workTime, 5, 1, selectedTaskName);
}
void countDown(int Time, byte cursorX, byte cursorY, char printedText[18]) { //таймер обратного отсчёта с записью данных на LCD
lcd.backlight();
lcd.setCursor(0, 0);
lcd.print(printedText);
long startTime = millis();
while (((Time * 60000) + startTime) > millis()) {
long TimeLeft = (((Time * 60000) + startTime) - millis()) / 1000; //сколько осталось времени в секундах
drawLowLine(Time, TimeLeft);
lcd.setCursor(cursorX, cursorY);
if (TimeLeft / 60 < 10) {
lcd.print("0");
lcd.setCursor(cursorX + 1, cursorY);
lcd.print(TimeLeft / 60); //перевод в минуты
} else {
lcd.print(TimeLeft / 60);
};
lcd.setCursor(cursorX + 2, cursorY);
lcd.print(":");
lcd.setCursor(cursorX + 3, cursorY);
if (TimeLeft % 60 < 10) { //перевод в секунды
lcd.print("0");
lcd.setCursor(cursorX + 4, cursorY);
lcd.print(TimeLeft % 60);
} else {
lcd.print(TimeLeft % 60);
};
if (TimeLeft <= 0) {
countDownCompleted = 1;
}; //флаг, позволяет отследить, прерывался ли таймер. Деньги выдаются только если он не прерывался
startButt.tick();
if (startButt.isSingle()) { //если кнопка СТАРТ нажата, то таймер прерывается...
break;
}
else if (startButt.isTriple()) { //если кнопка СТАРТ нажата 3 раза, то это задание удаляется
EEPROM.put((previouslySelectedTaskNumber * 20), 0);
break;
}
};
printGold();
}
void drawLowLine(int Time, long TimeLeft) {
float accurateLowLine = (((Time * 60) - TimeLeft) * 16) / (Time * 60) ; //16 - это количество символов на дисплее;
int lowLine = floor(accurateLowLine);
lcd.setCursor(lowLine, 1);
float accurateLittleLowLine = ((((Time * 60) - TimeLeft) * 16 * 10) / (Time * 60)) % 10 ; //16 - это количество символов на дисплее;
int littleLowLine = ceil(accurateLittleLowLine);
if (littleLowLine >= 0 && littleLowLine < 2) {
lcd.write(1);
}
else if (littleLowLine >= 2 && littleLowLine < 4) {
lcd.write(2);
}
else if (littleLowLine >= 4 && littleLowLine < 6) {
lcd.write(3);
}
else if (littleLowLine >= 6 && littleLowLine < 8) {
lcd.write(4);
}
else if (littleLowLine >= 8 && littleLowLine < 10) {
lcd.write(5);
};
}
void giveGold() {
gold = gold + goldPerMinute * workTime;
EEPROM.put(1000, gold); //записываем голд в энергонезависимую память
}
void startIdle(int idleTimeVal) {
char rest[18] = "REST";
countDown(idleTimeVal, 5, 0, rest);
}
void printGold() {
lcd.clear(); //стирается всё на экране
lcd.setCursor(12, 0); //и печатаются деньги
lcd.print("P");
lcd.setCursor(13, 0);
lcd.print(gold);
lcd.noBacklight();
}
void lcdBlink() { //мигаем светодиодом, когда задание закончено
diode.setRGB(255, 255, 100);
delay(200);
diode.setRGB(0, 0, 0);
delay(200);
diode.setRGB(255, 255, 100);
delay(200);
diode.setRGB(0, 0, 0);
delay(200);
diode.setRGB(255, 255, 100);
delay(200);
diode.setRGB(0, 0, 0);
delay(200);
diode.setRGB(255, 255, 100);
delay(200);
diode.setRGB(0, 0, 0);
delay(200);
}
void startLCD() { //меняем цвет светодиода в зависимости от количества выполненых заданий
int i = (tasksCompleted % tasksBeforeBigIdle) ;
switch (i) {
case 0:
diode.setRGB(0, 2, 0); //ставим зеленый
break;
case 1:
diode.setRGB(3, 1, 0); //ставим жёлтый
break;
case 2:
diode.setRGB(1, 0, 1); //ставим фиолетовый
break;
case 3:
diode.setRGB(0, 0, 2); //ставим синий
break;
}
}
Буду очень благодарен вашим отзывам по использованию, авось и научную статейку напишем
Вложения
-
299.6 KB Просмотры: 9
Изменено: