#include <GyverOLED.h> // Библиотеки
#include <EncButton.h>
#define EB_DEB_TIME 50 // таймаут гашения дребезга кнопки (кнопка)
Button btnOK(2); // Кнопка ОК (короткое нажатие - старт/стоп секундомера, удержание - сброс всех параметров)
Button btnUP(3); // Кнопка UP (короткое нажатие - выбор регистра для настройки таймера/сброс колокольчика, удержание - старт/полный сброс таймера)
Button btnDOWN(4); // Кнопка DOWN (короткое нажатие - зафиксировать время круга)
Button btnLEFT(5); // Кнопка LEFT (короткое нажатие или удержание - уменьшить время таймера)
Button btnRIGHT(6); // Кнопка RIGHT (короткое нажатие или удержание - увеличить время таймера)
GyverOLED<SSD1306_128x64, OLED_BUFFER> oled; // oled дисплей SSD1306 (бывают и 0,96", и 1,3") - с буфером
void setup() {
oled.init(); // Инициализация дисплея
oled.setContrast(100);
}
void loop () {
btnOK.tick(); // тикеры кнопок
btnUP.tick();
btnDOWN.tick();
btnLEFT.tick();
btnRIGHT.tick();
static uint32_t startTime; // Время начала замера секундомера кнопкой ОК
static uint32_t stopTime; // Время окончания замера секундомера кнопкой ОК
static uint32_t startTimer; // Время начала замера таймера кнопкой UP (удержание)
static uint32_t stopTimer; // Время окончания замера таймера кнопкой UP (удержание)
static uint32_t time = 0; // Время секундомера, отображаемое на дисплее крупными цифрами
static uint32_t timeMemory = 0; // Время секундомера запоминаемое (необходимо если используем паузы)
static uint32_t timeLap = 0; // Время круга для цикличного секундомера
static uint32_t timeLastLap = 0; // Общее время последнего круга без учета пауз для цикличного секундомера (если круг ещё не зафиксирован)
static uint32_t pauseTimeStart = 0; // Время начала паузы
static uint32_t pauseTimeFinish = 0; // Время окончания паузы
static uint32_t pauseTime = 0; // Общее время паузы
static bool flagStopwatch = false; // Флаг работы секундомера
static bool flagTimer = false; // Флаг работы таймера
static bool flagBell = false; // Флаг отображения колокольчика
static bool flagBellBlink = false; // Флаг мигания колокольчика
static bool flagPressDown = false; // Флаг нажатия на кнопку DOWN
static uint32_t timer = 0; // Время таймера
const uint8_t numArr = 5; // Размер массива цикличного секундомера
static uint32_t timeArr[numArr]; // Сам массив цикличного секундомера
static int8_t readIndex = 4; // Индекс последнего значения цикличного секундомера в массиве
static uint32_t total = 0; // Сумма всех значений массива
static int16_t counterArr; // Счетчик активной ячейки массива цикличного секундомера
static uint8_t counterTimer = 0; // Счетчик выбранного разряда таймера
uint32_t cykleTimeMs; // Миллисекунды для цикличного секундомера
uint32_t lapTimeMs; // Миллисекунды для времени круга
/*const uint8_t bell[] PROGMEM = { // Колокольчик (почему-то здесь в симуляторе работает некорректно)
0xc0, 0xfc, 0xfe, 0xff, 0xfe, 0xfc, 0xc0, 0x00, 0x00, 0x02, 0x03, 0x02, 0x00, 0x00
};*/
if (btnOK.click()) { // нажатие кнопки ОК запускает/останавливает секундомер
flagStopwatch = !flagStopwatch; // меняем значение флага
if (flagStopwatch) { // если флаг поднят...
startTime = millis(); // запоминаем время запуска секундомера
if (pauseTimeStart == 0) timeLap = millis(); // при первом нажатии запоминаем время millis заодно и для цикличного секундомера
else { // если в памяти есть время начала паузы и оно не равно 0...
pauseTimeFinish = millis(); // запоминаем время окончания паузы
pauseTime += (pauseTimeFinish - pauseTimeStart); // вычисляем длительность всех пауз
}
}
else { // если флаг опущен...
stopTime = millis(); // запоминаем время остановки секундомера
timeMemory += stopTime - startTime; // общее время работы секундомера до сброса постоянно запоминаем в отдельную переменную
timeLastLap = millis() - timeLap; // тут запомнили время последнего круга до нажатия на паузу
pauseTimeStart = millis(); // запоминаем время начала текущей паузы
}
}
if (btnDOWN.click() and flagStopwatch) { // нажатие кнопки DOWN запоминает время круга и выводит его на дисплей
total -= timeArr[readIndex]; // вычитаем последнее значение из массива цикличного секундомера
timeArr[readIndex] = millis() - timeLap - pauseTime; // заносим новое значение в массив...
timeLap = millis(); // запоминаем время начала последнего круга
counterArr = readIndex; // счетчику присваиваем значение текущего элемента массива
total += timeArr[readIndex]; // прибавляем новое значение к массиву
readIndex -= 1; // переставляем индекс массива
if (readIndex < 0) readIndex = 4; // проверка, если перескочили индекс, то начинаем с начала (по кругу)
pauseTime = 0; // обнуляем время всех пауз
flagPressDown = true; // поднимаем флаг, если было хоть одно нажатие кнопки
}
if (btnOK.hold()) { // Удержанием кнопки ОК сбрасываем все значения на стандартные
startTime = 0;
stopTime = 0;
time = 0;
timeMemory = 0;
timeLap = 0;
timeLastLap = 0;
pauseTimeStart = 0;
pauseTimeFinish = 0;
pauseTime = 0;
flagStopwatch = false;
readIndex = 4;
total = 0;
flagPressDown = false;
lapTimeMs = 0;
cykleTimeMs = 0;
for (int i = 0; i < 6; i++) {
timeArr[i] = 0;
}
startTimer = 0;
stopTimer = 0;
flagTimer = false;
timer = 0;
counterTimer = 0;
flagBellBlink = false;
flagBell = false;
}
if (btnUP.click() and !flagTimer and flagBell) { // Кнопкой Up (только по окончании таймера) отключаем колокольчик
flagBellBlink = false;
flagBell = false;
} else if (btnUP.click() and !flagTimer) { // либо (если колокольчик не был активен) выбираем разряд таймера для настройки
if (counterTimer == 2) counterTimer = 0;
else counterTimer++;
}
switch (counterTimer) {
case 0: // секунды
if (btnRIGHT.click() or btnRIGHT.holding()) timer += 1000; // Кнопкой Right увеличиваем значение таймера
if (btnLEFT.click() or btnLEFT.holding()) timer -= 1000; // Кнопкой Left уменьшаем значение таймера
break;
case 1: // минуты
if (btnRIGHT.click() or btnRIGHT.holding()) timer += 60000; // Кнопкой Right увеличиваем значение таймера
if (btnLEFT.click() or btnLEFT.holding()) timer -= 60000; // Кнопкой Left уменьшаем значение таймера
break;
case 2: // часы
if (btnRIGHT.click() or btnRIGHT.holding()) timer += 3600000; // Кнопкой Right увеличиваем значение таймера
if (btnLEFT.click() or btnLEFT.holding()) timer -= 3600000; // Кнопкой Left уменьшаем значение таймера
break;
}
if (btnRIGHT.click() or btnRIGHT.holding() or btnLEFT.click() or btnLEFT.holding()) {
flagBellBlink = false; // ...а также отключаем колокольчик, если он был активен
flagBell = false;
}
if (timer > 35999000 or timer < 0) timer = 0; // условие, чтобы таймер не перескочил больше 10 часов или меньше 0
if (btnUP.hold()) { // Удержанием кнопки Up запускаем обратный отсчет (таймер)
flagTimer = !flagTimer;
if (flagTimer) {
startTimer = millis();
stopTimer = millis() + timer;
flagBell = true; // активируем колокольчик, чтобы по окончании таймера он начал мигать
} else { // Повторное нажатие кнопки обнуляет и сбрасывает таймер...
startTimer = 0;
stopTimer = 0;
timer = 0;
flagBellBlink = false; // а также отключается колокольчик
flagBell = false;
}
}
oled.clear(); // очищаем дисплей
static uint32_t upd;
if (millis() - upd > 100) { // обновляем инфо на дисплее каждые 100мс
upd = millis();
if (flagStopwatch) time = (millis() - startTime + timeMemory); // пока флаг поднят, постоянно обновляем общее время секундомера
else time = timeMemory; // когда флаг опущен, время секундомера ставится на паузу и не обновляется
oled.setScale(2); // и рисуем основной секундомер на дисплее
oled.setCursorXY(0, 0);
oled.print(getHours(time));
oled.setCursorXY(18, 0);
if (getMinutes(time) < 10) {
oled.print(0);
oled.setCursorXY(30, 0);
}
oled.print(getMinutes(time));
oled.setCursorXY(48, 0);
if (getSeconds(time) < 10) {
oled.print(0);
oled.setCursorXY(60, 0);
}
oled.print(getSeconds(time));
oled.setCursorXY(78, 0);
oled.print(getMilliseconds(time));
oled.setScale(1);
for (int i = 0; i < 3; i++) {
oled.setCursorXY(i * 30 + 12, 3);
oled.print(F(":"));
}
oled.setScale(1);
for (int8_t i = 0; i < 5; i++) {
oled.setCursorXY(0, i * 9 + 20);
if (counterArr + i > 4) cykleTimeMs = timeArr[counterArr + i - 5]; // запоминаем значения цикличного секундомера
else cykleTimeMs = timeArr[counterArr + i];
oled.setCursorXY(0, i * 9 + 20); // и рисуем цикличный секундомер на дисплее
oled.print(getHours(cykleTimeMs));
oled.setCursorXY(11, i * 9 + 20);
if (getMinutes(cykleTimeMs) < 10) {
oled.print(0);
oled.setCursorXY(17, i * 9 + 20);
}
oled.print(getMinutes(cykleTimeMs));
oled.setCursorXY(28, i * 9 + 20);
if (getSeconds(cykleTimeMs) < 10) {
oled.print(0);
oled.setCursorXY(34, i * 9 + 20);
}
oled.print(getSeconds(cykleTimeMs));
oled.setCursorXY(45, i * 9 + 20);
oled.print(getMilliseconds(cykleTimeMs));
for (int n = 0; n < 3; n++) {
oled.setCursorXY(n * 17 + 6, i * 9 + 20);
oled.print(F("."));
}
}
if (flagPressDown and !flagStopwatch) lapTimeMs = timeLastLap - pauseTime; // запоминаем значение времени круга
else if (flagPressDown) lapTimeMs = millis() - timeLap - pauseTime;
oled.setCursorXY(63, 30); // и рисуем секундомер последнего круга на дисплее
oled.print(getHours(lapTimeMs));
oled.setCursorXY(74, 30);
if (getMinutes(lapTimeMs) < 10) {
oled.print(0);
oled.setCursorXY(80, 30);
}
oled.print(getMinutes(lapTimeMs));
oled.setCursorXY(91, 30);
if (getSeconds(lapTimeMs) < 10) {
oled.print(0);
oled.setCursorXY(97, 30);
}
oled.print(getSeconds(lapTimeMs));
oled.setCursorXY(108, 30);
oled.print(getMilliseconds(lapTimeMs));
for (int n = 0; n < 3; n++) {
oled.setCursorXY(n * 17 + 69, 30);
oled.print(F("."));
}
oled.setCursorXY(63, 20); // надпись "ВРЕМЯ КРУГА"
oled.print("ВРЕМЯ КРУГА");
oled.line(0, 17, 127, 17); // линии
oled.line(58, 18, 58, 63);
oled.line(59, 41, 127, 41);
if (timer <= 0) { // Когда таймер дошел до нуля - останавливаем его и обнуляем все переменные
flagTimer = false;
startTimer = 0;
stopTimer = 0;
timer = 0;
}
if (flagTimer) timer = stopTimer - millis(); // Пока поднят флаг - идет отсчет таймера
if (!flagTimer and counterTimer == 2) { // и рисуем таймер на дисплее
oled.textMode(1);
oled.rect(62, 55, 68, 63, OLED_FILL);
}
oled.setCursorXY(63, 56);
oled.print(getHours(timer));
oled.textMode(0);
if (!flagTimer and counterTimer == 1) {
oled.textMode(1);
oled.rect(73, 55, 85, 63, OLED_FILL);
}
oled.setCursorXY(74, 56);
if (getMinutes(timer) < 10) {
oled.print(0);
oled.setCursorXY(80, 56);
}
oled.print(getMinutes(timer));
oled.textMode(0);
if (!flagTimer and counterTimer == 0) {
oled.textMode(1);
oled.rect(90, 55, 102, 63, OLED_FILL);
}
oled.setCursorXY(91, 56);
if (getSeconds(timer) < 10) {
oled.print(0);
oled.setCursorXY(97, 56);
}
oled.print(getSeconds(timer));
oled.textMode(0);
oled.setCursorXY(108, 56);
oled.print(getMilliseconds(timer));
for (int n = 0; n < 3; n++) {
oled.setCursorXY(n * 17 + 69, 56);
oled.print(F("."));
}
oled.setCursorXY(63, 46); // надпись на дисплее "таймер"
oled.print("ТАЙМЕР");
if (flagBell and timer == 0) { // если таймер дошел до нуля...
static uint32_t bellBlink;
if (millis() - bellBlink >= 1000) {
bellBlink = millis();
flagBellBlink = !flagBellBlink; // начинаем мигать колькольчиком раз в секунду
}
}
if (flagBellBlink) { // сам колокольчик
//oled.drawBitmap(120, 46, bell, 7, 10); // иконка колокольчика (почему-то здесь в симуляторе работает некорректно)
oled.line(123, 47, 125, 47);
oled.rect(122, 48, 126, 50, OLED_FILL);
oled.rect(121, 51, 127, 53, OLED_FILL);
oled.line(124, 46, 124, 54);
oled.line(123, 55, 125, 55);
}
if (pauseTimeStart != 0 and !flagStopwatch) { // отображаем на дисплее символ паузы, если она активна
oled.roundRect(106, 0, 127, 14, OLED_STROKE);
oled.rect(113, 3, 115, 11, OLED_FILL);
oled.rect(118, 3, 120, 11, OLED_FILL);
}
}
oled.update();
}
uint16_t getMilliseconds(uint32_t ms) { // получить миллисекунды
uint16_t milliseconds = (ms % 1000) / 100;
return milliseconds;
}
uint16_t getSeconds(uint32_t ms) { // получить секунды
uint16_t seconds = (ms / 1000LL) % 60;
return seconds;
}
uint16_t getMinutes(uint32_t ms) { // получить минуты
uint16_t minutes = (ms / (1000LL * 60)) % 60;
return minutes;
}
uint16_t getHours(uint32_t ms) { // получить часы
uint16_t hours = ms / (1000LL * 60 * 60);
return hours;
}