/*********************************************************************
* LED‑MATRIX CLOCK + SCROLL INFO
* (DS3231 + 4 × FC‑16 / MAX7219) – minimalistický, pod 32 KB flash
*
* • 15 s zobrazenia času (HH:MM) s blikajúcou dvojbodkou
* • potom scroll: DD.MM.YYYY DAY xx.xC
* • nastavenie času cez sériový port: SET YYYY MM DD HH MM
*
* Autor: ChatGPT – 2025‑12‑28 (upravené pre RTClib + MD_MAX72XX)
*********************************************************************/
#include <MD_MAX72xx.h>
#include <SPI.h>
#include <Wire.h>
#include "RTClib.h"
/* -------------------------- HARDVÉR -------------------------- */
#define HARDWARE_TYPE MD_MAX72XX::FC16_HW // typ matíc FC‑16
#define MAX_DEVICES 4 // 4 × 8×8 = 32×8 pixelov
#define CLK_PIN 13 // SPI SCK
#define DATA_PIN 11 // SPI MOSI
#define CS_PIN 10 // SPI SS
MD_MAX72XX mx(HARDWARE_TYPE, DATA_PIN, CLK_PIN, CS_PIN, MAX_DEVICES);
RTC_DS3231 rtc;
/* -------------------------- KONSTANTY ------------------------ */
const unsigned long TIME_SHOW = 15000UL; // 15 s s hodinami
const uint16_t SCROLL_SPEED = 40; // ms / krok (pomaly)
const uint16_t SCROLL_PAUSE = 1000; // ms pauza po skončení scrollu
/* -------------------------- GLOBALS -------------------------- */
bool showClock = true; // true = čas, false = scroll
bool colonOn = true; // stav dvojbodky
unsigned long lastModeChange = 0; // posledná zmena režimu
unsigned long lastBlink = 0; // posledná zmena dvojbodky
char timeBuf[6]; // "HH:MM"
char scrollBuf[34]; // max 33 znakov + '\0'
/* -------------------------- DNI ----------------------------- */
const char weekdayName[7][9] PROGMEM = {
"Nedela", "Pondelok", "Utorok",
"Streda", "Stvrtok", "Piatok", "Sobota"
};
/* -------------------------- POMÔCKE ------------------------- */
static uint8_t bcd2dec(uint8_t v) { return ((v >> 4) * 10) + (v & 0x0F); }
static uint8_t dec2bcd(uint8_t v) { return ((v / 10) << 4) | (v % 10); }
/* dve číslice (00‑99) – zápis do bufferu -------------------- */
void twoDigits(uint8_t v, char *dst) {
dst[0] = (v / 10) + '0';
dst[1] = (v % 10) + '0';
}
/* -------------------------- FORMÁT ČASU ------------------- */
void makeClockString(const DateTime &dt, bool colon, char *dst) {
twoDigits(dt.hour(), dst); // hh
dst[2] = colon ? ':' : ' '; // dvojbodka / medzera
twoDigits(dt.minute(), dst + 3); // mm
dst[5] = '\0';
}
/* -------------------------- TEPOŤ -------------------------- */
int16_t readTemperature() {
/* DS3231 – registre 0x11 (MSB) a 0x12 (LSB) */
Wire.beginTransmission(0x68);
Wire.write(0x11);
Wire.endTransmission();
Wire.requestFrom((uint8_t)0x68, (uint8_t)2); // 2 bajty
int8_t msb = (int8_t)Wire.read(); // celé stupne (signed)
uint8_t lsb = Wire.read(); // 2 najvyššie bity – .25 .5 .75
int16_t temp10 = msb * 10; // *10 → celá časť
uint8_t fraction = (lsb >> 6) & 0x03; // 0‑3
if (fraction == 1) temp10 += 2; // .25 → +2 (0.2)
else if (fraction == 2) temp10 += 5; // .5 → +5
else if (fraction == 3) temp10 += 8; // .75 → +8
return temp10; // 10×°C (napr. 235 → 23.5 °C)
}
/* -------------------------- TEXT PRE SCROLL ---------------- */
void makeScrollString(const DateTime &dt, int16_t temp10) {
char *p = scrollBuf;
/* DD.MM.YYYY */
twoDigits(dt.day(), p); p += 2; *p++ = '.';
twoDigits(dt.month(), p); p += 2; *p++ = '.';
uint16_t y = dt.year(); // 4‑ciferný rok
p[0] = (y / 1000) + '0';
p[1] = ((y / 100) % 10) + '0';
p[2] = ((y / 10) % 10) + '0';
p[3] = (y % 10) + '0';
p += 4; *p++ = ' ';
/* DEŇ V TÝŽDNI */
char dayStr[9];
strcpy_P(dayStr, (char *)pgm_read_word(&(weekdayName[dt.dayOfTheWeek()])));
strcpy(p, dayStr); p += strlen(dayStr);
*p++ = ' ';
/* TEPOŤ (xx.xC) – temp10 = 10×°C */
bool neg = false;
int16_t t = temp10;
if (t < 0) { neg = true; t = -t; }
uint8_t whole = t / 10; // celé stupne
p[0] = (whole / 100) % 10 + '0';
p[1] = (whole / 10) % 10 + '0';
p[2] = (whole % 10) + '0';
p[3] = '.';
p[4] = (t % 10) + '0';
p[5] = 'C';
p[6] = '\0';
if (neg) {
memmove(p-1, p, 7); // posuň a dopíš '-'
p[-1] = '-';
}
}
/* -------------------------- ZOBRAZENIE ČASU ----------------- */
void drawClock() {
mx.clear();
/* 5 znakov („HH:MM“) – 40 pixelov, displej má 32 → posunieme o 4 */
const uint8_t startCol = 4; // (32‑40)/2 = -4 → 4
for (uint8_t i = 0; i < 5; ++i) {
uint8_t ch = timeBuf[i];
if (ch < 32) ch = 32; // nahradíme neznámym („space“)
uint8_t font[8];
mx.getChar(ch, 8, font); // načítame 8‑stĺpcov bitmapy
for (uint8_t col = 0; col < 8; ++col)
mx.setColumn(startCol + i * 8 + col, font[col]);
}
}
/* -------------------------- SCROLL ------------------------ */
void scrollText() {
static int16_t offset = (MAX_DEVICES * 8); // začína mimo pravý okraj
static uint8_t charIdx = 0; // od ktorého znaku začíname
/* ak je celý text mimo displeja → koniec scrollu */
if (offset < -8) { // posledný znak už odišiel
offset = (MAX_DEVICES * 8);
charIdx = 0;
return;
}
mx.clear();
/* prejdeme všetky znaky, ktoré ešte nie sú úplne mimo */
for (uint8_t i = charIdx; scrollBuf[i] != '\0'; ++i) {
uint8_t ch = scrollBuf[i];
if (ch < 32) ch = 32; // space ak je < 32
uint8_t font[8];
mx.getChar(ch, 8, font); // bitmapa 8×8
for (uint8_t col = 0; col < 8; ++col) {
int16_t x = offset + (i - charIdx) * 8 + col;
if (x >= 0 && x < (MAX_DEVICES * 8))
mx.setColumn(x, font[col]);
}
}
offset--; // posuň o 1 pixel vľavo
}
/* -------------------------- SÉRIOVÉ NASTAVENIE ČASU ---------------- */
void processSerial() {
if (!Serial.available()) return;
String line = Serial.readStringUntil('\n');
line.trim();
if (line.length() == 0) return;
if (line.startsWith(F("SET"))) {
int y, mo, d, h, mi;
int parsed = sscanf(line.c_str() + 3, "%d %d %d %d %d", &y, &mo, &d, &h, &mi);
if (parsed == 5 && y >= 2000 && y < 2100 && mo >= 1 && mo <= 12 &&
d >= 1 && d <= 31 && h >= 0 && h < 24 && mi >= 0 && mi < 60) {
DateTime dt(y, mo, d, h, mi, 0);
rtc.adjust(dt); // nastavíme DS3231
colonOn = true;
makeClockString(dt, true, timeBuf);
drawClock();
lastModeChange = millis();
showClock = true;
Serial.println(F("RTC nastavený."));
} else {
Serial.println(F("Nesprávny formát. Použite: SET YYYY MM DD HH MM"));
}
return;
}
Serial.println(F("Neznámy príkaz. Použite: SET YYYY MM DD HH MM"));
}
/* -------------------------- SETUP ------------------------ */
void setup() {
Serial.begin(9600);
Wire.begin();
if (!rtc.begin()) {
Serial.println(F("RTC nenájdený!"));
while (true);
}
// Ak chcete len pri prvom spustení nastaviť čas od PC:
// rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
mx.begin();
mx.control(MD_MAX72XX::INTENSITY, 5);
mx.clear();
DateTime now = rtc.now();
makeClockString(now, true, timeBuf);
drawClock();
lastModeChange = millis();
lastBlink = millis();
}
/* -------------------------- LOOP -------------------------- */
void loop() {
processSerial(); // kontrola sériových príkazov
unsigned long nowMs = millis();
/* ----- REŽIM: HODINY ----- */
if (showClock) {
/* Blikajúca dvojbodka každú sekundu */
if (nowMs - lastBlink >= 1000UL) {
lastBlink = nowMs;
colonOn = !colonOn;
DateTime dt = rtc.now();
makeClockString(dt, colonOn, timeBuf);
drawClock();
}
/* Po 15 s prejdeme na scroll */
if (nowMs - lastModeChange >= TIME_SHOW) {
lastModeChange = nowMs;
showClock = false;
DateTime dt = rtc.now();
int16_t temp10 = readTemperature();
makeScrollString(dt, temp10);
}
}
/* ----- REŽIM: SCROLL ----- */
else {
scrollText(); // posúvanie o 1 pixel
/* Keď posledný stĺpec (vpravo) je prázdny → text už mimo displeja */
static bool scrollFinished = false;
if (!scrollFinished && mx.getColumn((MAX_DEVICES * 8) - 1) == 0) {
scrollFinished = true;
}
if (scrollFinished) {
scrollFinished = false; // reset pre ďalší cyklus
showClock = true;
colonOn = true;
DateTime dt = rtc.now();
makeClockString(dt, true, timeBuf);
drawClock();
lastModeChange = nowMs;
}
}
/* Malé oneskorenie – zabezpečí 40 ms pauzu medzi krokmi scrollu */
delay(SCROLL_SPEED);
}