#include <Wire.h>
#include <SSD1306Ascii.h>
#include <SSD1306AsciiWire.h>
// Шрифты
//#include "fonts/Stang5x7.h"
//#include "fonts/Arial14.h"
//#include "fonts/lcd5x7.h"
// --- ПИНЫ ---
#define DATA_PIN 2
#define BUTTON_PIN 3
#define PWR_SWITCH_PIN 4
// ==========================================
// КАСТОМНАЯ ГРАФИКА ПО ЧЕРТЕЖАМ (6 байт заголовка)
// ==========================================
const uint8_t customGlyphs[] PROGMEM = {
0x00, 0x01, // Индексы 0 и 1: Тип шрифта (00 00 = фиксированный)
0x05, // Индекс 2: Ширина (5)
0x08, // Индекс 3: Высота (8)
32, // Индекс 4: Стартовый символ (ASCII 32 = 'SPACE')
26, // Индекс 5: Количество символов в массиве (3 штуки)
// --- ДАЛЬШЕ ИДУТ ДАННЫЕ (с индекса 6) ---
// 32 - space
0x00, 0x00, 0x00, 0x00, 0x00,
// Код 33 ('!') - Залитый блок (Кирпич) 5*7
0x7F, 0x7F, 0x7F, 0x7F, 0x7F,
// Код 34 ('"') - Пустая рамка 5*7
0x7F, 0x41, 0x41, 0x41, 0x7F,
// Код 35 ('#') - Дымчатый сегмент (Шахматка) 5*7
0x55, 0x2A, 0x55, 0x2A, 0x55,
// Код 36 ('$') - Граница верх/низ (пустая шкала) 5*7
0x41, 0x41, 0x41, 0x41, 0x41,
// Код 37 ('%') - Граница право (правая граница шкалы) 5*7
0x00, 0x00, 0x00, 0x00, 0x7F,
// Код 38 ('&') - Граница лево (левая граница шкалы) 5*7
0x7F, 0x00, 0x00, 0x00, 0x00,
// глазки 39 - Залитый блок (Кирпич) 5*8
0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
// глазки 40 - "зрачёк влево" 5*8
0xFF, 0x80, 0x80, 0x80, 0x80,
// глазки 41 - "зрачёк вправо" 5*8
0x80, 0x80, 0x80, 0x80, 0xFF,
// 42 - Дымчатый сегмент (Шахматка) 5*8
0x55, 0xAA, 0x55, 0xAA, 0x55,
// 43 - Граница верх/низ (пустая шкала) 5*8
0x81, 0x81, 0x81, 0x81, 0x81,
// 44 - Граница лево (левая граница шкалы) 5*8
0xFF, 0x00, 0x00, 0x00, 0x00,
// 45 - QUAL 1/3
0x00, 0x47, 0x7D, 0x47, 0x40,
// 46 - QUAL 2/3
0x47, 0x40, 0x0F, 0x00, 0x1F,
// 47 - QUAL 3/3
0x00, 0x3F, 0x00, 0x7F, 0x00,
// 48 - BATT 1/3
0x00, 0x7F, 0x7F, 0x41, 0x41,
// 49 - BATT 2/3
0x7F, 0x41, 0x41, 0x7F, 0x41,
// 50 - BATT 3/3
0x41, 0x7F, 0x00, 0x3E, 0x00,
// 51 - RAMF 1/3
0x00, 0x3C, 0xBD, 0x3C, 0xBD,
// 52 - RAMF 2/3
0x3C, 0xBD, 0x3C, 0xBD, 0x3C,
// 53 - RAMF 3/3
0xBD, 0x3C, 0xBD, 0x3C, 0x00,
// 54 - FLOW 1/3
0x08, 0x08, 0x09, 0x7F, 0x48,
// 55 - FLOW 2/3
0x48, 0x48, 0x7F, 0x09, 0x09,
// 56 - FLOW 3/3
0x09, 0x7F, 0x48, 0x08, 0x08,
// 57 - ------------------------
0x08, 0x08, 0x08, 0x08, 0x08
};
//////////////////
const uint8_t customNumber[] PROGMEM = { //big numbers for TIME/DATE
0x00, 0x00, // Индексы 0 и 1: Тип шрифта (00 00 = фиксированный)
0x05, // Индекс 2: Ширина (5)
0x08, // Индекс 3: Высота (8)
45, // Индекс 4: Стартовый символ (ASCII 46 = '-')
14, // Индекс 5: Количество символов в массиве (14 шт.)
// --- ДАЛЬШЕ ИДУТ ДАННЫЕ (с индекса 6) ---
// 45 - "-"
0x00, 0x08, 0x08, 0x08, 0x00,
// 46 - "."
0x00, 0x00, 0xC0, 0x00, 0x00,
// 47 - "/"
0x00, 0xC0, 0x3C, 0x03, 0x00,
// 48 - "0"
0x7E, 0x81, 0x81, 0x81, 0x7E,
// 49 - "1"
0x00, 0x81, 0xFF, 0x80, 0x00,
// 50 - "2"
0xF3, 0x91, 0x91, 0x91, 0x9F,
// 51 - "3"
0x42, 0x81, 0x89, 0x89, 0x76,
// 52 - "4"
0x38, 0x24, 0x22, 0x21, 0xFF,
// 53 - "5"
0xCF, 0x89, 0x89, 0x89, 0xF9,
// 54 - "6"
0xFF, 0x89, 0x89, 0x89, 0xF8,
// 55 - "7"
0x03, 0xC1, 0x21, 0x11, 0x0F,
// 56 - "8"
0x76, 0x89, 0x89, 0x89, 0x76,
// 57 - "9"
0x1F, 0x91, 0x91, 0x91, 0xFF,
// 58 - ":"
0x00, 0x00, 0xCC, 0x00, 0x00
};
/////////////////
const uint8_t customMeandr[] PROGMEM = { //Meandr for FLOW
0x00, 0x01, // Индексы 0 и 1: Тип шрифта (00 00 = фиксированный)
0x05, // Индекс 2: Ширина (5)
0x08, // Индекс 3: Высота (8)
48, // Индекс 4: Стартовый символ (ASCII 46 = '-')
4, // Индекс 5: Количество символов в массиве (14 шт.)
// --- ДАЛЬШЕ ИДУТ ДАННЫЕ (с индекса 6) ---
// 48 - "0"
0x40, 0x40, 0x40, 0x40, 0x40,
// 49 - "1"
0x40, 0x7F, 0x7F, 0x7F, 0x7F,
// 50 - "2" error
0x00, 0x7C, 0x54, 0x44, 0x00,
// 51 - "3" minutes flag
0x40, 0x7F, 0x00, 0x7F, 0x40
};
SSD1306AsciiWire oled;
// --- ПЕРЕМЕННЫЕ ДЛЯ ПРЕРЫВАНИЙ ---
volatile unsigned long pulseStartTime = 0;
volatile unsigned long pulseDuration = 0;
volatile bool pulseReady = false;
volatile bool lastState = HIGH;
// --- ПЕРЕМЕННЫЕ UI ---
byte currentScreen = 9;
int batteryPercent = 100;
float batteryVoltage = 4.2;
float qualityPercent = 0.0;
float sdramPercent = 0.0;
int dcfHour = 12, dcfMinute = 37, dcfSecond = 59;
int dcfDay = 18, dcfMonth = 04, dcfYear = 26;
// --- АНИМАЦИИ ---
const char* robotAnim[] = {"O_o ", "o_O "};
byte RobotAnimIndex = 0;
unsigned long lastRobotAnimTime = 0;
char spinnerAnim[] = {'|', '/', '-', '\\'};
byte SpinnerAnimIndex = 0;
// --- ТЕРМИНАЛ ---
// 6 строк (индексы 0...5), ширина 21 (20 символов + 1 скрытый нуль-терминатор)
char termBuf[6][21] = {0};
byte termCol = 0;
// --- ДИАГНОСТИКА ПАМЯТИ ---
int getFreeMemory() {
extern int __heap_start, *__brkval;
int v;
return (int)((size_t)&v - (__brkval == 0 ? (size_t)&__heap_start : (size_t)__brkval));
}
// логика терминала
void addTerminalChar(char c) {
if (termCol >= 20) { // как только набрали 20 символов
for (byte i = 0; i < 5; i++) { // свигаем строки вверх (строки 1...6 копируем в 0...5)
memcpy(termBuf[i], termBuf[i + 1], 21);
}
// очищаем ПОСЛЕДНЮЮ строку (ее индекс 6, т.к. строк всего 7)
memset(termBuf[5], 0, 21);
termCol = 0;
}
// пишем в активную строку (индекс 7)
termBuf[5][termCol] = c;
termCol++;
}
// --- ПРЕРЫВАНИЕ DCF77 ---
void handlePulse() {
unsigned long now = micros();
bool state = digitalRead(DATA_PIN);
digitalWrite(LED_BUILTIN, !state);
if (state != lastState) {
if (state == LOW) {
pulseStartTime = now;
} else {
pulseDuration = now - pulseStartTime;
pulseReady = true;
}
lastState = state;
}
}
void setup() {
Serial.begin(9600); // Включаем отладку для поиска пропавших данных!
Serial.println("Система стартует...");
analogReference(INTERNAL);
pinMode(PWR_SWITCH_PIN, INPUT);
pinMode(BUTTON_PIN, INPUT_PULLUP);
pinMode(LED_BUILTIN, OUTPUT);
pinMode(DATA_PIN, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(DATA_PIN), handlePulse, CHANGE);
Wire.begin();
Wire.setClock(400000L);
oled.begin(&Adafruit128x64, 0x3C);
oled.setFont(System5x7);
sdramPercent = 100 - ((getFreeMemory() * 100L) / 2048L);
//Глазки
oled.setFont(customGlyphs);
oled.set2X();
delay(1000); oled.clear();
oled.setCursor(0, 2); oled.write(33); oled.write(33); oled.write(33); oled.print(" "); oled.write(33); oled.write(33); oled.write(33);
delay(1000); oled.clear();
oled.setCursor(0, 0); oled.write(39); oled.write(39); oled.write(39); oled.print(" "); oled.write(39); oled.write(39); oled.write(39);
oled.setCursor(0, 2); oled.write(39); oled.write(39); oled.write(39); oled.print(" "); oled.write(39); oled.write(39); oled.write(39);
oled.setCursor(0, 4); oled.write(40); oled.write(39); oled.write(39); oled.print(" "); oled.write(40); oled.write(39); oled.write(39);
delay(1000);
oled.clear();
oled.setCursor(0, 2); oled.write(33); oled.write(33); oled.write(33); oled.print(" "); oled.write(33); oled.write(33); oled.write(33);
delay(1000); oled.clear();
oled.setCursor(0, 0); oled.write(39); oled.write(39); oled.write(39); oled.print(" "); oled.write(39); oled.write(39); oled.write(39);
oled.setCursor(0, 2); oled.write(39); oled.write(39); oled.write(39); oled.print(" "); oled.write(39); oled.write(39); oled.write(39);
oled.setCursor(0, 4); oled.write(39); oled.write(39); oled.write(41); oled.print(" "); oled.write(39); oled.write(39); oled.write(41);
delay(1000); oled.clear(); delay(1000);
oled.setFont(System5x7);
//
runBootSequence();
updateBatteryStatus();
}
void loop() {
handleButton();
// --- ОБРАБОТКА ЭФИРА (С ЗАЩИТОЙ ПАМЯТИ) ---
if (pulseReady) {
// Безопасное чтение данных из прерывания
noInterrupts();
unsigned long currentDuration = pulseDuration;
pulseReady = false;
interrupts();
unsigned long dur = currentDuration / 1000;
// ОТЛАДКА: Отправляем длительность импульса в консоль
Serial.print("Импульс пойман! Длительность (мс): ");
Serial.println(dur);
char newBit = '?';
// логика длительностей dcf77
if (dur >= 70 && dur <= 130) newBit = '0'; // 0
else if (dur >= 170 && dur <= 210) newBit = '1'; // 1
else if (dur > 800) newBit = '*'; // отсечка в пакете данных 58я-59я сек.
else newBit = 'e'; // ошибка
addTerminalChar(newBit);
}
// Обновляем батарею раз в минуту
static unsigned long lastBatTime = 0;
if (millis() - lastBatTime >= 58000) {
lastBatTime = millis();
updateBatteryStatus();
}
// --- АНИМАЦИИ И ЭКРАН ---
if (millis() - lastRobotAnimTime >= 500) {
lastRobotAnimTime = millis();
RobotAnimIndex = (RobotAnimIndex + 1) % 2; // Исправлен баг выхода за пределы массива
}
static unsigned long lastDrawTime = 0;
if (millis() - lastDrawTime >= 100) {
lastDrawTime = millis();
SpinnerAnimIndex = (SpinnerAnimIndex + 1) % 4;
switch (currentScreen) {
case 0: drawMonitoring(); break;
case 1: drawService(); break;
case 2: drawWordTime1(); break;
case 3: drawWordTime2(); break;
case 4: drawWordTime3(); break;
case 5: drawWordTime4(); break;
case 6: drawWordTime5(); break;
case 7: drawWordTime6(); break;
case 8: drawWordTime7(); break;
case 9: drawTerminal(); break;
}
}
}
void handleButton() {
static bool btnPressed = false;
bool reading = !digitalRead(BUTTON_PIN);
if (reading && !btnPressed) {
btnPressed = true;
delay(50);
} else if (!reading && btnPressed) {
btnPressed = false;
currentScreen = (currentScreen + 1) % 10;
oled.clear();
}
}
void updateBatteryStatus() {
pinMode(PWR_SWITCH_PIN, OUTPUT);
digitalWrite(PWR_SWITCH_PIN, LOW);
delay(10);
analogRead(A0);
int raw = analogRead(A0);
pinMode(PWR_SWITCH_PIN, INPUT);
batteryPercent = constrain(map(raw, 270, 355, 0, 100), 0, 99);
batteryVoltage = 3.2 + (batteryPercent / 100.0) * 1.0;
}
void drawMonitoring() {
oled.setFont(customNumber); oled.set2X(); oled.setCursor(0, 0); //oled.print(F(" "));
//oled.print(F("Time "));
print2Digits(dcfHour); oled.print(millis() % 1000 < 500 ? ':' : ' '); print2Digits(dcfMinute); oled.print(millis() % 1000 < 500 ? ':' : ' '); print2Digits(dcfSecond);
oled.setFont(System5x7); oled.set1X(); oled.setInvertMode(true); oled.print(F(" CEST ")); oled.setInvertMode(false);
oled.setFont(System5x7); oled.set1X(); //oled.print(F("CEST"));
oled.set1X(); oled.setCursor(0, 2);
for (int i = 0; i < 25; i++) {
oled.setFont(customGlyphs);
oled.write(57); // 57 - --------------------
}
oled.setFont(customNumber); oled.set2X(); oled.setCursor(0, 3);
//oled.print(F("Date "));
print2Digits(dcfDay); oled.print('.'); print2Digits(dcfMonth); oled.print('.'); oled.print(2000 + dcfYear);
//oled.print(F(" ")); oled.print(robotAnim[RobotAnimIndex]);
oled.set1X(); oled.setCursor(0, 5);
for (int i = 0; i < 25; i++) {
oled.setFont(customGlyphs);
oled.write(57); // 57 - --------------------
}
oled.set2X(); oled.setCursor(0, 6); //FLOW
//oled.setFont(customGlyphs); oled.write(54); oled.write(55); oled.write(56); oled.write(32); //icon flow
oled.setFont(customMeandr); oled.write(49); oled.write(48); oled.write(48);
oled.write(48); oled.write(50); oled.write(48); oled.write(49); oled.write(48);
oled.write(48); oled.write(51); oled.write(49); oled.write(50); oled.write(48);
//oled.write(48); oled.write(51); oled.write(49); oled.write(48); oled.write(48);
//oled.write(48); oled.write(49); oled.write(50);
//
//oled.set1X(); oled.setCursor(0, 7); //oled.print(F("QUAL"));
//oled.setFont(customGlyphs);
//oled.write(45); oled.write(46); oled.write(47); oled.write(37); //icom QUAL
//drawBar(qualityPercent, 20); oled.setFont(customGlyphs); oled.write(38); //oled.setFont(System5x7); print2Digits(qualityPercent); oled.print('%');
//
//oled.setCursor(0, 6); //oled.print(F("BATT"));
//oled.setFont(customGlyphs);
//oled.write(48); oled.write(49); oled.write(50); oled.write(37); //icon BATT
//drawBar(batteryPercent, 20); oled.setFont(customGlyphs); oled.write(38); //oled.setFont(System5x7); print2Digits(batteryPercent); oled.print('%');
//
//oled.setCursor(0, 7); //oled.print(F("RAMF"));
//oled.setFont(customGlyphs);
//oled.write(51); oled.write(52); oled.write(53); oled.write(37); //icon RAM
//drawBar(sdramPercent, 20); oled.setFont(customGlyphs); oled.write(38); //oled.setFont(System5x7); print2Digits(sdramPercent); oled.print('%');
oled.setFont(System5x7);
}
void drawService() {
oled.setFont(customGlyphs); oled.set2X(); oled.setCursor(0, 0);
oled.write(45); oled.write(46); oled.write(47); oled.write(32); oled.write(32); oled.write(32); //icon QUAL
//oled.print(F("QUAL "));
oled.setFont(customNumber);
if (qualityPercent < 10) oled.print('0'); oled.print(qualityPercent, 1); oled.setFont(System5x7); oled.print('%');
oled.set1X(); oled.setCursor(0, 2);
for (int i = 0; i < 25; i++) {
oled.setFont(customGlyphs);
oled.write(57); // 57 - --------------------
}
//oled.write(33); drawBar(qualityPercent, 24); oled.setFont(customGlyphs); oled.write(38);
//
oled.setFont(customGlyphs); oled.set2X(); oled.setCursor(0, 3);
oled.write(48); oled.write(49); oled.write(50); oled.write(32); oled.write(32); oled.write(32); //icon BATT
//oled.print(F("BATT "));
oled.setFont(customNumber);
oled.print(batteryVoltage, 2); oled.setFont(System5x7); oled.print('V');
oled.set1X(); oled.setCursor(0, 5);
for (int i = 0; i < 25; i++) {
oled.setFont(customGlyphs);
oled.write(57); // 57 - --------------------
}
//oled.write(33); drawBar(batteryPercent, 24); oled.setFont(customGlyphs); oled.write(38);
//
oled.setFont(customGlyphs); oled.set2X(); oled.setCursor(0, 6);
oled.write(51); oled.write(52); oled.write(53); oled.write(32); oled.write(32); oled.write(32); //icon RAM
///oled.print(F("RAMF "));
oled.setFont(customNumber);
if (sdramPercent < 10) oled.print('0'); oled.print(sdramPercent, 1); oled.setFont(System5x7); oled.print('%');
}
void drawWordTime1() {
drawGenericWordTime(-2, "LONDON.UTC ");
}
void drawWordTime2() {
drawGenericWordTime(2, " DUBAI ");
}
void drawWordTime3() {
drawGenericWordTime(3, " MOCKBA ");
}
void drawWordTime4() {
drawGenericWordTime(8, " BEIJING ");
}
void drawWordTime5() {
drawGenericWordTime(9, " TOKYO ");
}
void drawWordTime6() {
drawGenericWordTime(10, " SYDNEY ");
}
void drawWordTime7() {
drawGenericWordTime(-5, " NEW-YORK ");
}
void drawGenericWordTime(int offset, const char* city) {
//TIME
int displayHour = (dcfHour + offset + 24) % 24;
oled.set2X(); oled.setCursor(0, 0); oled.print(' '); oled.setFont(customNumber); print2Digits(displayHour); oled.print(millis() % 1000 < 500 ? ':' : ' '); print2Digits(dcfMinute); oled.print(millis() % 1000 < 500 ? ':' : ' '); print2Digits(dcfSecond);
oled.set1X(); oled.setCursor(0, 2);
for (int i = 0; i < 25; i++) {
oled.setFont(customGlyphs);
oled.write(57); // 57 - --------------------
}
//DATE
oled.set2X(); oled.setCursor(0, 3); oled.setFont(customNumber); print2Digits(dcfDay); oled.print('.'); print2Digits(dcfMonth); oled.print('.'); oled.print(2000 + dcfYear);
oled.set1X(); oled.setCursor(0, 5);
for (int i = 0; i < 25; i++) {
oled.setFont(customGlyphs);
oled.write(57); // 57 - --------------------
}
//CITY
oled.set2X(); oled.setCursor(0, 6); oled.setFont(System5x7); oled.print(city);
oled.set1X(); oled.setFont(System5x7);
}
// отрисовка терминала
void drawTerminal() {
// Вывод 8 строк (проходим по индексам 0...7)
oled.setFont(customMeandr);
for (byte i = 0; i < 6; i++) {
oled.setCursor(0, i + 1); //+ 1); // выводим чуть ни}|{е заголовка
oled.print(termBuf[i]);
// курсор ТОЛЬКО на последней строке (индекс 7) + очистка EOL
if (i == 5) oled.setFont(System5x7); oled.print(spinnerAnim[SpinnerAnimIndex]); //oled.clearToEOL();
//oled.clearToEOL();
}
oled.clearToEOL(); // Обязательно стираем "призраков" для ка}|{дой строки
}
void print2Digits(int n) {
if (n < 10) oled.print('0');
oled.print(n);
}
// прогресс бар для вывода %
void drawBar(int val, int maxChars) {
int numFilled = map(val, 0, 100, 0, maxChars);
oled.setFont(customGlyphs); // Включаем нашу кастомную графику
for (int i = 0; i < maxChars; i++) {
oled.write(i < numFilled ? 33 : 36); // 33 - кирпич 5*7, 36 - верх/низ 5*7
}
oled.setFont(System5x7); // Возвращаем стандартный текст
}
// прогресс бар для QUAL/BATT
void drawBigBar(int val, int maxChars) {
int numFilled = map(val, 0, 100, 0, maxChars);
oled.setFont(customGlyphs); // Включаем нашу кастомную графику
for (int i = 0; i < maxChars; i++) {
oled.write(i < numFilled ? 39 : 43); // 39 - кирпич 5*8, 43 - верх/низ 5*8
}
oled.setFont(System5x7); // Возвращаем стандартный текст
}
//
void runBootSequence() {
oled.clear();
oled.set2X(); oled.setCursor(0, 1); oled.println(F("RD&P Lab "));
oled.set1X(); oled.setCursor(0, 3); oled.println(F(" with"));
oled.set2X(); oled.setCursor(0, 5); oled.println(F(" Gemini"));
oled.set1X(); oled.setCursor(0, 7); oled.println(F(" [O_o]")); delay(1000);
oled.setCursor(0, 7); oled.println(F(" [o_O]")); delay(1000);
oled.setCursor(0, 7); oled.println(F(" [O_o]")); delay(1000);
oled.clear();
oled.set1X(); oled.setCursor(0, 0); oled.println(F("v.0002_UI+raw"));
oled.set2X(); oled.setCursor(0, 2); oled.println(F("W-MONITOR"));
oled.set1X(); oled.setCursor(0, 4); oled.println(F(" DCF77"));
oled.setCursor(0, 6);
for (int i = 0; i < 25; i++) {
oled.setFont(customGlyphs);
oled.write(42); // 42 - Дымчатый сегмент (Шахматка) 5*8
//oled.setFont(System5x7);
delay(50);
}
oled.setCursor(0, 6);
for (int i = 0; i < 25; i++) {
oled.write(39); // 39 - кирпич 5*8
delay(50);
}
delay(500); oled.clear();
oled.setFont(System5x7);
}