#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
#define X(x) (x)
#define Y(y) (y)
#define W(w) (w)
#define H(h) (h)
// Timer class (конструкція працює приблизно 49 днів після чого потрібен перезапуск мікроконтролера)
class Timer {
private:
uint32_t tmr = 0;
bool timerFlag = 0;
uint32_t restartTimer = 0;//час перезапуску
public:
//!!!!!таймер запускається коли викликається один з методів час не запускається одразу коли вказати його в конструкторі або методі st()
//конструктор вказуємо час і з чого починати правда брехня
Timer(uint32_t rT = 0, bool firstCall = 0) { //екземпляри таймерів можуть створюватися одразу з вказаним часом або вказувати його пізніше
restartTimer = rT;//якщо не вказати час тоді буде постійно повертатись значення яке є в timerFlag
timerFlag = firstCall;//якщо вказати час тоді стан буде мінятися Якщо ні тоді стан цієї змінної буде повертатись постійно допоки не вкажемо час через метод
}
//
//таймер одноразово поверне правда з вказаною частотою
bool gp() { // gt = get pulsation з вказаною частотою повертає правда
if (restartTimer) {
if (tmr < millis()) {
tmr = millis() + restartTimer;
if (timerFlag) {//одноразово повертаємо те що вказали в настройках
return true;
}
else{
timerFlag = true;//повертаємося до стандартного значення яке буде повертатись
}
}
}
else{//якщо змінна restartTimer брехня тоді будемо постійно повертати те що вказали в другому параметрі
return timerFlag;
}
return false;
}
//
//таймер одноразовий сигнал (брехня один раз правда по завершенню таймера, брехня до перезапуску)
bool ts() { // ts = timer single одноразова повернуть правда і буде чекати перезапуску
if (restartTimer) {
if (tmr < millis()){
if (!tmr){//це означає перший запуск
tmr = millis() + restartTimer;//
int temp = timerFlag;//одноразово повертаємо значення вказане в другому параметрі
timerFlag = true;//встановлюєm стандартне значення
return temp;
}
else{//це означає що таймер спрацював вдруге завершуємо роботу очікуємо перезапуск
timerFlag = restartTimer = false;//забороняємо виконання основної умови
return true;//одноразово повертаємо правда
}
}
}
else{//Дана умова виконується якщо ми не вказали перший параметр або функція завершила роботу
return timerFlag;//(0, false || true)=буде постійно повертати другий параметр//якщо завершила роботу буде постійно повертати брехня
}
return false;
}
//
//таймер (секунда правда секунда брехня)
bool ta() {//ta = timer auto постійно повертає правда брехня використовується там де не потрібна точність
if (restartTimer){
if (tmr < millis()){
tmr = millis() + restartTimer;
timerFlag = !timerFlag;//
}
return !timerFlag;//потрібно виконати ще раз інверсію щоб було правильнo підлаштовуємося під стандартний спосіб запуску-зупинки таймера
}
return timerFlag;
}
//
//повертає постійно по завершенню часу правда або брехня залежно від настройки
bool tm() { // tm = timer manual
if (restartTimer){
if (tmr < millis()){
if (!tmr){//спрацювання 1 запуск таймера
tmr = millis() + restartTimer;
int temp = timerFlag;//одноразово повертаємо значення вказане в другому параметрі
timerFlag = true;//встановлюєm стандартне значення
return temp;
}
else{//друге спрацювання завершуємо роботу
timerFlag = true;//ставимо значення правда щоб вона постійно поверталася
restartTimer = false;//забороняє виконання основної умови
return true;
}
}
}
else{//якщо Друга умова неправда То будемо постійно повертати те значення яке знаходиться в зміній
return timerFlag;
}
return false;
}
//
//відправляємо в новий час або зупиняємо таймер
void st(uint32_t rT = 0, bool firstCall = 1){ // вказуємо новий час спрацювання для tmr І що перше повертати правда брехня
tmr = false;
timerFlag = firstCall;//
restartTimer = rT;//вказуємо новий час або зупиняємо роботу таймера якщо вказати false
}
//
// берем стан таймера
int gmt() {//get millis timer
if (restartTimer && millis() < tmr) {
return tmr - millis();
}
return 0;
}
int gst() {//get seconds timer
if (restartTimer && millis() < tmr) {
return (tmr - millis()) / 1000;
}
return 0;
}
//
};
// Tc
void af_centerText(int x, int y, int input, bool minusNo = false) {
// Перетворюємо вхідне значення у рядок
static String text;
static int adjustedLength;
static int textWidth;
// Якщо треба ігнорувати мінус, коригуємо довжину тексту
text = String(input);
adjustedLength = text.length();
if (minusNo && text[0] == '-') {
adjustedLength--; // Ігноруємо мінус
}
// Обчислюємо ширину тексту
textWidth = adjustedLength * 6; // 6 пікселів на символ
// Встановлюємо курсор
display.setCursor(x - (textWidth / 2), y);
// Виводимо текст
display.print(text);
}
void fd_drawCenteredFilledRect(int centerX, int centerY, int width, int height, uint16_t color = WHITE) {//прямокутник білий або BLACK
int startX = centerX - (width / 2);
int startY = centerY - (height / 2);
display.fillRect(startX, startY, width, height, color);//якщо непарна кількість зміщення вправо вниз
}
void fd_drawCenteredHollowRect(int centerX, int centerY, int width, int height, uint16_t color = WHITE) {//прямокутник рамка
// Обчислюємо початкові координати для верхнього лівого кута, щоб прямокутник був по центру
int startX = centerX - (width / 2);
int startY = centerY - (height / 2);
// Викликаємо стандартний метод для малювання прямокутника з рамкою
display.drawRect(startX, startY, width, height, color);
}
void af_drawCountdown(bool start) {
static Timer tmr(4000, false);
static int inspectionLine = 114;//лінія перевірки
static bool digitJump; // цифра стрибок
static bool displayPermission = true; // дозвіл відображення
if (displayPermission){
fd_drawCenteredFilledRect(X(64), Y(56), W(inspectionLine), H(3));//Смужка яка зменшується в центр
inspectionLine = map(tmr.gmt(), 0, 4000, 14, 114);//зменшуємо розмір смужки до мінімума
if (inspectionLine % 2 != 0) {//робимо це рівномірно відрізаємо по одному Пікселю Зліва і справа
inspectionLine = inspectionLine - 1;
}
fd_drawCenteredHollowRect(X(64), Y(56), W(14), H(13));//прямокутник в центрі в якому відображаються цифри
fd_drawCenteredFilledRect(X(64), Y(56), W(12), H(11), BLACK);//робимо чорне полотно в центрі прямокутника накриваємо смужку
int temp = tmr.gst();
if (temp) {// якщо таймер не вийшов відображаємо секунди
af_centerText(X(64+digitJump), Y(53), temp);
digitJump = !digitJump;
}
else{fd_drawCenteredHollowRect(X(64), Y(56), W(6), H(7));}//якщо час вийшов відображаємо прямокутник замість Нолика так красивіше
if (tmr.ts()){displayPermission = false; delay(500);}//час вийшов завершуємо додатково трошки зупиняємося щоб було видно наш Нулик
}
if (!start){
tmr.st(4000, false);
inspectionLine = 114;
displayPermission = true;
}
}
void setup() {
Serial.begin(115200);
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { for (;;) ; }
display.setTextSize(1);
display.setTextColor(WHITE);
display.clearDisplay();
display.display();
Serial.println("Введи 1, щоб запустити, або 0, щоб перезапустити");
}
void loop() {
display.clearDisplay();
af_drawCountdown(true);
if (Serial.available()) {
char cmd = Serial.read();
if (cmd == '0') {
// Перезапускаємо (скидаємо стан)
af_drawCountdown(false);
}
}
display.display();
}