Skip to main content

7.7 Budilica

Ovaj projekt je prava budilica. Pločica se pridružuje tvojoj Wi-Fi mreži, pita vremenski server na internetu koliko je sati, i prikazuje to na LCD-u. Dvije tipke postavljaju alarm, a kada sat dođe do njega, buzzer se oglasi.

To je najveći sklop u kitu, i gotovo ništa u njemu nije novo. LCD je spojen točno kao u 4.1 Ispis poruke, tipke se čitaju točno kao u 2.2 Debounce tipke, buzzer se pobuđuje točno kao u 2.4 Piskanje buzzera, a mrežna veza je ona iz 6.1 Povezivanje i dohvat podataka. Novo je to što sva četiri rade u isto vrijeme.

U ovoj dokumentaciji naučit ćeš:

  • Kako pločica bez vlastitog sata nauči koliko je sati, pomoću NTP-a
  • Zašto vrijeme na tvom ekranu ovisi o pomaku vremenske zone koji moraš sam postaviti
  • Kako na istoj pločici pokrenuti Qwiic ekran i krug na breadboardu
  • Kako dvije varijable sprječavaju alarm da se oglašava iznova cijelu minutu

Potreban hardver:

  • 1x Soldered NULA MINI pločica
  • 1x Breadboard
  • 1x Soldered Qwiic 16x2 LCD ekran
  • 2x Tipke
  • 1x Pasivni buzzer
  • 10x Jumper žice
  • 1x Qwiic kabel
  • 1x USB-C kabel
ℹ️
Ovom primjeru ne trebaju otpornici uopće. Tipke koriste otpornike ugrađene u čip, uključene u kodu pomoću INPUT_PULLUP, a pin buzzera se uvijek kreće samo između 0 V i 3.3 V pa ga nema od čega štititi. Ako si vidio krug budilice nacrtan s dva otpornika od 10 kΩ, to je drugačiji način spajanja tipki i nije ono što ova skica očekuje.
⚠️
Pobrini se da u ruci držiš pasivni buzzer, a ne aktivni. tone() ispravno radi samo na pasivnom buzzeru, koji svira onu frekvenciju koja mu se pošalje. Aktivni buzzer sadrži vlastiti oscilator i piska na jednoj fiksnoj visini bez obzira što kod traži.

Spajanje komponenti

Ovaj sklop koristi tri pina plus masu, i sva četiri su na f–j strani pločice:

PinRedStranaŠto radi
IO225f–jTipka za sate
IO326f–jTipka za minute
IO427f–jBuzzer
GND30f–jMasa za sve

LCD-u ne trebaju pinovi uopće. Spaja se preko Qwiic utičnice pločice, koja je interno spojena na IO6 i IO7.

⚠️
Red 27 postoji s obje strane pločice i ta dva su različiti pinovi. Na f–j strani red 27 je IO4, buzzer. Na a–e strani red 27 je IO19, koji ovaj projekt ne koristi. Sjede na suprotnim krajevima istog broja reda i nisu spojeni međusobno. Čitaj ime ispisano duž ruba pločice, nikada broj reda sam.

Tijelo pločice prekriva srednje stupce, pa su uz nju slobodni samo stupac a na a–e strani i stupac j na f–j strani. Svaka žica koja dotiče pločicu ide zato u a… ili j… rupicu.

Prati jedanaest koraka ispod. Svaka fotografija snimljena je s istog položaja, pa svaku možeš usporediti s onom prije nje i vidjeti točno što se promijenilo.

1. Umetni NULA MINI pločicu na breadboard

ℹ️
Ovaj korak pretpostavlja da znaš kako je breadboard spojen iznutra i što su njegove naponske vodilice (power rails). Za uvod pogledaj stranicu dokumentacije Osnove breadboarda.

Utisni pločicu u jedan kraj breadboarda tako da njezina dva reda pinova sjednu s obje strane središnjeg kanala, s čipom okrenutim prema dolje. Pritisni je ravnomjerno dok svi pinovi ne sjednu.

NULA MINI pločica postavljena na jedan kraj breadboarda
Korak 1: pločica postavljena na breadboard, zauzima redove 25 do 30

Na fotografijama koje slijede pločica zauzima redove 25 do 30.

ℹ️
Tvoja pločica može sjediti u drugim redovima od one na fotografijama. Važno je u koji red sleti koji pin. Čitaj imena ispisana duž rubova pločice, a ne prepisuj brojeve.

2. Sagradi jednu dijeljenu masu

Masa je najzaposleniji spoj u ovom projektu: trebaju je i obje tipke i buzzer. Zato je prvi posao pretvoriti dvije plave vodilice u jednu liniju mase do koje obje strane pločice mogu doći.

Za to treba četiri kratke jumper žice:

IzUŠto radi
j30 (GND)f–j plava vodilicaIzvodi masu s pločice
j1f–j plava vodilicaVraća masu na red 1
e1f1Prenosi je preko središnjeg kanala
a1a–e plava vodilicaDovodi je u drugu vodilicu
Četiri plave jumper žice spajaju masu pločice na obje plave negativne vodilice preko reda 1
Korak 2: masa izvedena s pločice i prenesena okolo do daleke vodilice preko reda 1
ℹ️
Vodilice duž dva ruba breadboarda nisu spojene unutar njega: svaki pojas ide dužinom jednog ruba i tamo staje. Njihovo spajanje je ono što dopušta tipkama da masu uzimaju s bližeg ruba umjesto da vodiš duge žice natrag preko pločice. Red 1 se ovdje koristi kao kamen za prelaz samo zato što su jumper žice iz kita kratke; jedna dugačka žica oko kraja breadboarda radi točno isti posao.
ℹ️
Svaka žica vodilice mora ići u red rupica koji ide uz plavu liniju. Svaki rub breadboarda nosi dva reda vodilica, a onaj uz crvenu liniju je zaseban pojas koji nije spojen s njim.

3. Postavi dvije tipke

Utisni dvije tipke u sredinu breadboarda tako da svaka premosti središnji kanal, nekoliko redova dalje od pločice. Svaka tipka obuhvaća dva reda.

Na fotografijama sjede u redovima 17 i 19 za tipku sati, najbliža pločici, i redovima 13 i 15 za tipku minuta.

Dvije tipke postavljene preko središnjeg kanala u redovima 17 do 19 i 13 do 15
Korak 3: dvije tipke premošćuju središnji kanal
⚠️
Tipka ima četiri nožice, ali samo dvije su važne. Dvije nožice na istoj strani tipke trajno su spojene unutar nje. Pritisak tipke spaja jednu stranu s drugom. Zato za svaku tipku uvijek koristi jednu nožicu s a–e strane i jednu s f–j strane. Ako uzmeš obje žice s iste strane, te dvije su već spojene i pločica će se ponašati kao da je ta tipka zauvijek pritisnuta.

4. Spoji obje tipke na masu

Dvije kratke jumper žice, jedna po tipki, s a–e strane tipke do plave vodilice na tom rubu: a19 za tipku sati i a15 za tipku minuta.

Dvije jumper žice spajaju svaku tipku na plavu negativnu vodilicu na a-e rubu
Korak 4: obje tipke spojene na masu na plavoj − vodilici na a–e rubu
ℹ️
Ovdje se most iz koraka 2 isplati. Tipke masu uzimaju s a–e vodilice, ali je GND izveden s pločice na f–j strani. Bez tih žica preko reda 1 ta vodilica ne bi bila spojena ni na što, i obje tipke bile bi mrtve.

5. Spoji svaku tipku na svoj pin

Sada dvije signalne žice, obje na f–j strani. Svaka ide od pina pločice do nižeg reda para jedne tipke:

IzUTipkaBoja na fotografiji
j25 (IO2)j17SatiNarančasta
j26 (IO3)j13MinuteBijela
Narančasta i bijela jumper žica idu od IO2 i IO3 do dvije tipke
Korak 5: svaka tipka spojena na svoj pin, IO2 na tipku najbližu pločici

Primijeti da dvije žice idu paralelno i nikada se ne križaju. IO2, pin najbliži tipkama, ide na najbližu tipku. Njihova zamjena zamjenjuje što tipke rade: tipka sati postavljala bi minute a tipka minuta sate.

ℹ️
Svaka tipka sada ima masu s jedne strane središnjeg kanala i svoj pin s druge. To je isti dijagonalni par koji je za jednu tipku korišten u 2.1 Brojač pritisaka tipke, samo dvaput.

6. Postavi buzzer

Buzzer ide na f–j stranu na daleki kraj breadboarda, s nožicama u dva susjedna reda, na fotografiji redovi 7 i 8.

Pogledaj vrh njegovog kućišta prije nego ga utisneš. Uz utisnuta slova nalazi se mali + unutar kružića, i nožica na toj strani kućišta je pozitivna. Ta nožica ide u red 8, red bliži tipkama.

Pasivni buzzer utisnut u redove 7 i 8 na f-j strani breadboarda
Korak 6: buzzer u redovima 7 i 8, njegova ⊕ oznaka okrenuta prema strani reda 8
⚠️
Dvije nožice sjede u susjednim redovima, pa odvoji trenutak da provjeriš da su stvarno sletjele u dva različita reda. Ako obje nožice završe u istom redu, buzzer je kratko spojen i ostaje tih bez obzira što kod radi.

7. Spoji buzzer na IO4

Jedna jumper žica iz j27, a to je IO4, do j8, reda u kojem je + nožica buzzera. To je žica koja nosi pravokutni signal.

Zelena jumper žica ide od IO4 u j27 do pozitivne nožice buzzera u j8
Korak 7: IO4 spojen na + nožicu buzzera

8. Spoji buzzer na masu

Još jedna jumper žica, iz j7 (druga nožica buzzera) dolje do plave vodilice na istom rubu.

Plava jumper žica spaja drugu nožicu buzzera u j7 na plavu negativnu vodilicu
Korak 8: buzzer spojen na masu, čime se zatvara krug IO4 → buzzer → GND

Krug je sada zatvoren: IO4 → buzzer → GND. Kućište od 12 mm skriva nožice kada je buzzer utisnut, pa ako ikada budeš morao provjeriti ovo spajanje, čitaj umjesto toga dvije jumper žice.

Breadboard je gotov. Sve odsad se jednostavno utiskuje.

9. Spoji Qwiic kabel na pločicu

LCD uopće ne dotiče breadboard. Spaja se preko jednog jedinog Qwiic kabela, koji u jednom utikaču nosi napajanje i oba I2C signala.

Nađi bijelu Qwiic utičnicu na NULA MINI i utisni jedan kraj kabela u nju. Ulazi samo na jedan način.

Qwiic konektor istaknut na NULA MINI pločici
Qwiic konektor na NULA MINI
Qwiic kabel utisnut u NULA MINI s drugim krajem još slobodnim
Korak 9: Qwiic kabel u pločici, drugi kraj još slobodan

10. Spoji drugi kraj na LCD

Okreni ekran. Ljubičasta pločica na njegovoj pozadini je I2C LCD ADAPTER, i nosi dvije Qwiic utičnice, po jednu na svakom kraju. Bilo koja radi: to je isto spojište s dva utikača.

Drugi kraj Qwiic kabela utisnut u ljubičastu I2C LCD adapter pločicu
Korak 10: kabel utisnut u LCD adapter, sa silkscreenom I2C ADDR 0X20
ℹ️
I2C ADDR 0X20 ispisan na adapteru je adresa ekrana na sabirnici. Skica je nikada ne spominje jer je biblioteka već zna: LCD lcd(16, 2) ispuni 0x20 za tebe. Kontakti A0, A1 i A2 uz nju mogu tu adresu pomaknuti, a to bi koristio ako bi ikada htio dva ekrana na jednoj pločici.

11. Spoji pločicu na računalo

Utisni USB-C kabel u pločicu. PWR LED-ica se upali, a s njom i pozadinsko svjetlo ekrana.

Gotova budilica napajana preko USB-C, LCD upaljen i prikazuje red punih blokova
Korak 11: gotov sklop, napojen prije nego je ikakva skica pokrenuta
⚠️
Red punih blokova u gornjoj liniji je u ovom trenutku normalan, a tako i običan plavi ekran. Oboje znači da ekran ima napajanje ali nije inicijaliziran, što je točno tako i treba, jer nijedna skica još nije pokrenuta. Upaljeno pozadinsko svjetlo dokazuje samo da ekran prima napajanje; ne govori ništa o tome može li pločica s njim razgovarati. Tekst se pojavljuje kada preneseš kod.

Kako pločica zna koliko je sati

NULA MINI nema vlastiti sat. Iskopčaj je i zaboravi koliko je sati. Nema baterije koja drži sat da otkucava kako je ima u ručnom satu ili u računalu.

Zato pita. Kada je na mreži, pošalje kratak zahtjev vremenskom serveru i dobije natrag trenutno vrijeme. Protokol za to je NTP, Network Time Protocol, i tako gotovo svaki povezani uređaj na zemlji drži svoj sat ispravnim.

Skica koristi pool.ntp.org, besplatan svjetski servis koji tvoj zahtjev predaje serveru koji ti je najbliži. configTime() to pokreće, i od tog trenutka pločica vodi vlastito brojanje sekundi, provjeravajući ponovno svake minute tako da nikada ne odluta daleko.

Vremenske zone su tvoj posao

NTP server uvijek odgovara u UTC, svjetskom referentnom vremenu. Nema pojma gdje si, pa ne može znati koliko je sati za tebe. Pretvaranje UTC-a u lokalno vrijeme ostavljeno je dvama brojevima na vrhu skice:

const long gmtOffset_sec = 0;
const int daylightOffset_sec = 0;

Oba su u sekundama, pa je jedan sat 3600. Prvi je koliko je tvoja vremenska zona daleko od UTC-a. Drugi je dodatni sat za ljetno računanje vremena, ako ga tvoja zemlja koristi.

ℹ️
Hrvatska je zimi jedan sat ispred UTC-a a ljeti dva, pa je gmtOffset_sec = 3600 cijele godine a daylightOffset_sec = 3600 od kraja ožujka do kraja listopada. Skica dolazi s oba postavljena na 0, i zato ekran na fotografijama niže pokazuje 10:29 kada je sat na zidu pokazivao 12:29. Ako ti je sat pogrešan za cijeli broj sati, to je razlog. Nije ništa pokvareno.

Zašto alarmu treba pamćenje

Pločica prolazi kroz loop() oko deset puta u sekundi, a alarm se oglašava kada se sat poklopi s vremenom alarma. Ali minuta traje šezdeset sekundi, pa bi se obična usporedba poklopila oko šest stotina puta u nizu i buzzer nikada ne bi prestao.

To sprječavaju dvije varijable. alarmTriggered postavlja se onog trenutka kada se alarm oglasi i blokira svako daljnje oglašavanje dok ne pritisneš tipku. lastAlarmMinute pamti u kojoj se minuti oglasio, tako da pritisak tipke za brisanje ekrana ne može odmah ponovno pokrenuti alarm dok ta ista minuta još traje.


Kod

Ovdje je cijela skica. Postavi pločicu na Soldered NULA Mini ESP32C6 prije prijenosa, i upiši svoje ime mreže i lozinku u WIFI_SSID i WIFI_PASS.

/**
**************************************************
*
* @file 7.7_Alarm_Clock.ino
* @brief Projekt koji gradi budilicu povezanu na Wi-Fi. Pločica se pridružuje tvojoj mreži, pita NTP server na
* internetu koliko je sati, i prikazuje trenutno vrijeme na LCD ekranu. Dvije tipke postavljaju sat i minutu
* alarma, a kada se dosegne vrijeme alarma, buzzer se oglasi nizom pisaka.
* Spaja LCD iz sekcije 4, debounce tipke iz sekcije 2.2, buzzer iz
* sekcije 2.4 i Wi-Fi vezu iz sekcije 6.
* Za detalje, shemu spajanja i više, pogledaj dokumentaciju primjera na: <link placeholder>
* @author Soldered
***************************************************
*/

/*
Soldered biblioteka za LCD ekran.
*/
#include "LCD-SOLDERED.h"

/*
Biblioteka WiFi sadrži sve što je potrebno za pridruživanje Wi-Fi mreži.
*/
#include <WiFi.h>

/*
Biblioteka time daje nam funkcije za rad s datumima i vremenima, i za pitanje NTP servera koliko je sati.
NTP je skraćeno od Network Time Protocol, i tako gotovo svaki uređaj na internetu drži svoj sat ispravnim.
*/
#include <time.h>

/*
Ove dvije varijable drže ime tvoje Wi-Fi mreže (SSID) i njezinu lozinku. Zamijeni tekst između
navodnika svojim mrežnim podacima.
*/
const char* WIFI_SSID = "your ssid";
const char* WIFI_PASS = "your password";

/*
Ovo je adresa NTP servera kojeg pitamo koliko je sati. pool.ntp.org je besplatan, svjetski servis koji tvoj
zahtjev predaje onom vremenskom serveru koji ti je najbliži, pa je dobra zadana vrijednost koju treba ostaviti kakva je.
*/
const char* ntpServer = "pool.ntp.org";

/*
NTP serveri uvijek odgovaraju u UTC-u, svjetskom referentnom vremenu, pa pločici moramo reći koliko je naša vremenska zona
daleko od njega. Pomak se zadaje u sekundama, pa je jedan sat 3600. Hrvatska je zimi jedan sat ispred UTC-a, što bi
ovdje bilo 3600.
Druga vrijednost je dodatni pomak za ljetno računanje vremena. Postavi ga na 3600 tijekom ljetnog vremena u zemlji koja
ga koristi, a inače ga ostavi na 0.
*/
const long gmtOffset_sec = 0;
const int daylightOffset_sec = 0;

/*
Ovdje stvaramo naš objekt ekrana, koji smo nazvali "lcd". Objekt je naš način razgovora s ekranom: svaku funkciju
koju na njemu pozovemo, pozivamo preko ovog imena. Ne trebaju mu brojevi pinova jer koristi easyC, a to je Soldered ime za
I2C vezu preko jednog kabela, a I2C uvijek koristi ista dva pina na pločici (IO6 i IO7 na NULA pločici).
*/
LCD lcd(16, 2); // Qwiic 16x2 LCD

/*
Ovo su varijable kojima predajemo brojeve pinova na koje smo spojili dvije TIPKE i buzzer.
Jedna tipka nabraja sat alarma gore, druga nabraja minutu alarma gore.
NULA pločica ima logiku imenovanja pinova ovako: IO2, gdje je 2 broj koji dajemo varijabli.
*/
const int BUTTON_HOUR = 2;
const int BUTTON_MIN = 3;
const int BUZZER_PIN = 4;

/*
Ove varijable drže vrijeme alarma prema kojem brojimo, počevši od 07:00, i pamte je li se alarm
već oglasio, tako da se oglasi jednom a ne iznova tijekom iste minute.
*/
int alarmHour = 7;
int alarmMinute = 0;
bool alarmTriggered = false;

/*
Ova varijabla pamti u kojoj se minuti alarm posljednji put oglasio. Bez nje bi zaustavljanje alarma pritiskom tipke
unutar same minute u kojoj se oglasio dopustilo da se oglasi ponovno odmah, iznova dok minuta ne prođe. Počinjemo
na -1, vrijednosti koju nijedna prava minuta ne može imati, tako da prvi alarm nikada nije blokiran.
*/
int lastAlarmMinute = -1;

/*
Ovo su varijable koje se koriste za debounce tipki, ista tehnika objašnjena u primjeru 2.2. Budući da ovdje imamo dvije
tipke, svakoj treba vlastiti par varijabli: jedna koja pamti njezino prethodno stanje i jedna koja pamti kada
se to stanje posljednji put promijenilo.
*/
bool lastHourState = HIGH;
bool lastMinState = HIGH;
unsigned long lastHourChangeMs = 0;
unsigned long lastMinChangeMs = 0;
const unsigned long debounceMs = 25;

/*
Ove dvije varijable upravljaju koliko često ponovno pitamo NTP server koliko je sati. Vlastiti sat pločice polako odlutava, pa
provjeravanje s vremena na vrijeme drži njegovu točnost.
*/
unsigned long lastSync = 0;
const unsigned long syncInterval = 60000;

/*
Ovo je funkcija koju smo napisali sami. Pita za trenutno vrijeme i prijavljuje je li uspjela.
getLocalTime() ispunjava strukturu koju mu predamo i vraća true pri uspjehu ili false pri neuspjehu. Pri neuspjehu je
struktura ostavljena točno kakva je bila, a to bi bilo puno besmislenih vrijednosti, pa taj odgovor predajemo
onome koji poziva i puštamo ga da odluči što će, umjesto da koristimo brojeve kojima ne možemo vjerovati.
"*" u parametru znači da predajemo lokaciju strukture a ne njezinu kopiju, a to je ono što
funkciji dopušta da ispuni našu vlastitu varijablu umjesto neke svoje.
*/
bool getLocalTimeData(struct tm *timeinfo) {
if (!getLocalTime(timeinfo)) {
Serial.println("Failed to obtain time from NTP");
return false;
}
return true;
}

/*
Ova funkcija oglašava alarm. tone() tjera buzzer da proizvede zvuk zadane frekvencije, u ovom slučaju 1000 Hz,
a noTone() ga ponovno zaustavlja. Ponavljanje toga pet puta u nizu je ono što jedan dugi zvuk pretvara u niz pisaka.
Slobodno eksperimentiraj s frekvencijom, brojem ponavljanja i dvjema pauzama.
*/
void beepAlarm() {
for (int i = 0; i < 5; i++) {
tone(BUZZER_PIN, 1000);
delay(300);
noTone(BUZZER_PIN);
delay(100);
}
}

void setup() {

/*
Serial.begin() uspostavlja serijsku komunikaciju između tvoje pločice i tvog računala preko USB kabela. Ovdje ga koristimo
da pratimo povezivanje na mrežu i na vremenski server.
*/
Serial.begin(115200);

/*
Ovdje pripremamo ekran. begin() pokreće komunikaciju s njim a backlight() pali njegovo svjetlo da tekst
bude čitljiv.
*/
lcd.begin();
lcd.backlight();

/*
pinMode() je funkcija koja konfigurira zadani pin da se ponaša ili kao ulaz ili kao izlaz.
Buzzer je nešto u što pišemo, pa ide u OUTPUT mod, dok su dvije tipke stvari koje čitamo, pa
idu u INPUT_PULLUP mod. INPUT_PULLUP uključuje mali otpornik unutar čipa koji svaki pin veže na 3.3V,
što znači da pin sjedi na HIGH dok je njegova tipka otpuštena a ide na LOW dok je pritisnuta.
*/
pinMode(BUZZER_PIN, OUTPUT);
pinMode(BUTTON_HOUR, INPUT_PULLUP);
pinMode(BUTTON_MIN, INPUT_PULLUP);

/*
clear() briše sve što je ostalo na ekranu od prije, a setCursor() odabire gdje će se sljedeći tekst pojaviti:
prvi broj je stupac a drugi je red, oba brojeći od nule.
*/
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Connecting WiFi");

/*
WiFi.begin() pokreće pokušaj povezivanja. Funkcija samo pokreće proces, ne čeka da se dovrši,
pa na to čekamo sami u petlji ispod. WiFi.status() nam kaže trenutno stanje a WL_CONNECTED je vrijednost
koju prijavljuje kada smo na mreži.
*/
WiFi.begin(WIFI_SSID, WIFI_PASS);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}

Serial.println("\nWiFi connected!");
lcd.clear();
lcd.print("WiFi connected");
delay(1000);

/*
configTime() govori pločici koji NTP server koristiti i koliko je naša vremenska zona daleko od UTC-a, i pokreće prvi
zahtjev za vremenom u pozadini. Vraća se odmah, prije nego je ikakav odgovor došao.
*/
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);

/*
Zato ovdje čekamo taj prvi odgovor, jer dok ne dođe pločica nema pojma koliko je sati i na ekranu bi
prikazivala besmislice. Dajemo joj do deset sekundi i onda nastavljamo u svakom slučaju, tako da je nedostatak
veze s internetom ne može ostaviti zaglavljenu ovdje zauvijek.
*/
lcd.clear();
lcd.print("Syncing time...");
struct tm startupTime;
unsigned long syncStart = millis();
bool timeReady = false;
while (!timeReady && millis() - syncStart < 10000) {
timeReady = getLocalTime(&startupTime);
delay(200);
}

lcd.clear();
if (timeReady) {
Serial.println("Time synced!");
lcd.print("Time synced!");
lastSync = millis();
} else {
Serial.println("Time sync failed, the clock will keep trying.");
lcd.print("Sync failed!");
}
delay(1000);
lcd.clear();
}

void loop() {

/*
Ovdje pitamo za trenutno vrijeme preko naše funkcije iznad. Struct tm drži cijeli datum i vrijeme razdvojeno u
odvojena polja, od kojih koristimo samo dva: tm_hour i tm_min.
*/
struct tm timeinfo;

/*
Ako nismo mogli pročitati vrijeme, nema ničega smislenog za prikazati i sigurno ničega s čime bismo alarm usporedili,
pa počekamo trenutak i probamo ponovno u sljedećem prolazu. return rano završava ovaj prolaz kroz loop(), a budući da
pločica loop() poziva odmah ponovno, pravo smo natrag na sljedećem pokušaju.
*/
if (!getLocalTimeData(&timeinfo)) {
delay(1000);
return;
}

/*
sprintf() gradi komad teksta iz nekoliko vrijednosti, prema uzorku koji mu damo. "%02d" znači "cijeli broj
napisan s najmanje dvije cifre, dopunjen nulom ako treba", a to je ono što 7 minuta nakon punog sata pretvara u "07" umjesto
u "7". Rezultat se sprema u polje timeStr i onda se ispisuje na ekran.
*/
char timeStr[6];
sprintf(timeStr, "%02d:%02d", timeinfo.tm_hour, timeinfo.tm_min);
lcd.setCursor(0, 0);
lcd.print("Time: ");
lcd.print(timeStr);

/*
U drugoj liniji prikazujemo vrijeme alarma. Ovdje brojeve dopunjavamo ručno umjesto pomoću sprintf(): ako je vrijednost
manja od 10, prvo pred nju ispišemo nulu.
*/
lcd.setCursor(0, 1);
lcd.print("Alarm ");
if (alarmHour < 10) lcd.print("0");
lcd.print(alarmHour);
lcd.print(":");
if (alarmMinute < 10) lcd.print("0");
lcd.print(alarmMinute);

/*
Ovo je logika debouncea za tipku sati, ista ona objašnjena u primjeru 2.2. digitalRead() čita vrijednost
s pina, millis() vraća broj milisekundi koje su prošle od kada je pločica počela izvršavati trenutni program,
i zajedno nam dopuštaju da ignoriramo svaku promjenu koja dođe prerano nakon prethodne da bi bila stvarni pritisak.
Budući da tipke koriste pull-up otpornike, vrijednosti su obrnute od onoga što bi mogao očekivati: pritisak
je trenutak kada vrijednost ide s HIGH na LOW, a to je točno trenutak kada sat nabrajamo gore. Nakon 23
počinjemo iznova od 0, jer sata 24 nema.
*/
bool hourReading = digitalRead(BUTTON_HOUR);
unsigned long now = millis();
if (hourReading != lastHourState && (now - lastHourChangeMs) > debounceMs) {
lastHourChangeMs = now;
if (lastHourState == HIGH && hourReading == LOW) {
alarmHour++;
if (alarmHour > 23) alarmHour = 0;
}
lastHourState = hourReading;
}

/*
Ta ista logika za tipku minuta, nabrajajući do 59 prije nego počne iznova.
*/
bool minReading = digitalRead(BUTTON_MIN);
if (minReading != lastMinState && (now - lastMinChangeMs) > debounceMs) {
lastMinChangeMs = now;
if (lastMinState == HIGH && minReading == LOW) {
alarmMinute++;
if (alarmMinute > 59) alarmMinute = 0;
}
lastMinState = minReading;
}

/*
Ovdje provjeravamo je li vrijeme za alarm. Sva tri uvjeta moraju biti istinita odjednom: sat se mora poklopiti,
minuta se mora poklopiti, i alarm se ne smije već oglasiti. Ta posljednja provjera je ono za što varijabla
alarmTriggered služi, jer bi se bez nje alarm oglašavao iznova i iznova cijelu minutu.
*/
if (timeinfo.tm_hour == alarmHour && timeinfo.tm_min == alarmMinute && !alarmTriggered
&& timeinfo.tm_min != lastAlarmMinute) {
alarmTriggered = true;
lastAlarmMinute = timeinfo.tm_min;
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("ALARM!");
Serial.println("Alarm Triggered!");
beepAlarm();
}

/*
Pritisak bilo koje tipke nakon što se alarm oglasio briše upozorenje s ekrana i ponovno naoružava alarm za
sljedeći dan.
*/
if (alarmTriggered && (hourReading == LOW || minReading == LOW)) {
alarmTriggered = false;
lcd.clear();
}

/*
A ovdje s vremena na vrijeme ponovno pitamo NTP server koliko je sati, tako da sat ne odluta.
*/
if (millis() - lastSync > syncInterval) {
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
lastSync = millis();
}

/*
Kratka pauza da se ekran ne prepisuje tisućama puta u sekundi. Držanje kratke također drži tipke
odzivnima.
*/
delay(100);
}

Što bi trebao vidjeti

Pokretanje

Skica svoje pokretanje pripovijeda na ekranu, jedan ekran po ekran. Prvo najavljuje da se pridružuje mreži:

LCD prikazuje Connecting WiFi
Pločica pokušava pristupiti tvojoj mreži. Ovaj ekran stoji dok ne uspije
⚠️
Ovaj ekran nema vremensko ograničenje. Petlja čekanja nema istek vremena uopće, pa ako je ime mreže ili lozinka pogrešna, pločica ovdje sjedi zauvijek a da ti to nikada ne kaže. Ako se nakon desetak sekundi nije pomaknula, provjeri WIFI_SSID i WIFI_PASS za tipfelere, a ne spajanje.

Kada je na mreži, dobiješ sekundu potvrde:

LCD prikazuje WiFi connected
Povezano. Ovaj ekran traje jednu sekundu

Zatim pločica pita vremenski server koliko je sati. configTime() se vraća odmah, prije nego je ikakav odgovor došao, pa skica ovdje čeka do deset sekundi:

LCD prikazuje Syncing time...
Čekanje prvog odgovora s NTP servera

Ako odgovor dođe, vidiš Time synced! na sekundu i sat počinje. Ako ne, dobiješ Sync failed!, a sat ipak nastavlja, pokušavajući svake minute, pa spora mreža odgađa prvu vrijednost ali ne zaglavljuje projekt.

U Serial Monitoru

Otvori Serial Monitor na 115200 bauda i resetiraj pločicu. Cijelo pokretanje ulazi u nekoliko linija:

Serial Monitor prikazuje ROM boot poruke, tri četvorke, red točaka, WiFi connected i Time synced
Cijelo pokretanje: boot poruke, vlastiti ispis LCD biblioteke, dvanaest točaka i obje potvrde
ℹ️
Prvih devet linija nisu iz ove skice uopće. ESP-ROM:esp32c6-..., SPIWP, load: i entry ispisuje ugrađeni bootloader čipa pri svakom resetu, prije nego se tvoj program pokrene. Izgledaju uznemirujuće a znače da ništa nije u krivu.
ℹ️
Ni tri usamljene 4 nisu iz skice. Dolaze iz same LCD biblioteke: lcd.begin() ekranu pošalje tri poruke za postavljanje i ispiše rezultat svake od njih. Nisi ih vidio u 4.1 ni u 4.2 jer te skice nikada ne pozivaju Serial.begin(), pa brojevi nisu imali kamo otići. Ova ga poziva, pa se pojavljuju. Ignoriraj ih. Ekran radi, kako fotografije na ovoj stranici pokazuju.

Svaka točka je jedan krug petlje čekanja, a svaki krug traje 500 ms. Njihovo brojanje govori ti točno koliko je pločici trebalo da se pridruži mreži: dvanaest točaka ovdje, dakle šest sekundi. Ako ikada budeš htio znati je li tvoja mreža spora u dodjeljivanju adresa, taj red točaka je mjerenje.

Sat u radu

Nakon što se ekrani pokretanja očiste, ekran se smjesti u dvije linije koje će odsad prikazivati: trenutno vrijeme gore, vrijeme alarma ispod:

LCD prikazuje Time: 10:29 u gornjem redu i Alarm 10:29 u donjem redu
Sat u radu, s alarmom postavljenim na minutu koja upravo dolazi

Oba su dopunjena na dvije cifre, pa sedam minuta nakon punog sata pokazuje 07 a ne 7. Gornji red to radi pomoću sprintf() i %02d; donji red isti posao radi ručno, ispisujući 0 prvo kad god je broj ispod deset. Dva načina do istog rezultata, jedan uz drugi na jednom ekranu.

ℹ️
Zapamti vremensku zonu. Fotografija pokazuje 10:29 jer skica dolazi s gmtOffset_sec = 0 i zato prikazuje UTC. U prostoriji je bilo 12:29. Postavi pomak i gornji red postaje tvoje lokalno vrijeme.

Postavljanje i slušanje alarma

Alarm počinje na 07:00. Pritisni tipku na IO2 da nabrajaš sate gore i onu na IO3 da nabrajaš minute, i gledaj kako se donji red mijenja. Oba obilaze: 23 ide natrag na 0, a 59 natrag na 0.

Da alarm čuješ bez cjelodnevnog čekanja, postavi ga na minutu koja upravo dolazi. Kada ga sat sustigne, gornji red se zamjenjuje:

LCD prikazuje ALARM! dok se buzzer oglašava
Alarm se oglašava: pet pisaka, i ALARM! na ekranu
ℹ️
Taj ekran je gore samo oko dvije sekunde. beepAlarm() svira pet pisaka od 300 ms sa 100 ms tišine između njih, a ekran se ne dira ponovno dok ne završi. Onog trenutka kada piskanje prestane, loop() se izvrši još jednom i preko ALARM! upisuje vrijeme. Ako to želiš fotografirati, imaš jedan pokušaj kada piskanje počne.

Serial Monitor u istom trenutku ispisuje Alarm Triggered!, što je korisno ako si propustio ekran.

⚠️
Alarm pisne jednom i sam prestane. Ne nastavlja se oglašavati dok ne pritisneš tipku. Pet pisaka i gotovo je. Pritisak tipke nakon toga briše ALARM! s ekrana i ponovno naoružava alarm za sljedeći put.
ℹ️
Postoji jedna osobitost tog pritiska tipke koju vrijedi znati. Skica tipke čita prije nego provjeri treba li obrisati alarm, pa isti onaj pritisak koji briše ekran također nabraja vrijeme alarma gore za jedan. Utišaj ga tipkom sati i alarm se pomiče s 10:29 na 11:29. To je skica koja radi kako je napisana, a ne greška, ali ako želiš da ostane gdje je bio, umjesto toga resetiraj pločicu.

Ako nešto nije u redu

Ekran je upaljen ali prazan. To ima tri različita uzroka, a Serial Monitor ih razlikuje u nekoliko sekundi, pa ga otvori prije nego diraš spajanje.

  • Ničega uopće nakon boot poruka, ili red punih blokova. Skica ne dolazi do ekrana. Provjeri je li Qwiic kabel propisno na mjestu na oba kraja, i je li pločica odabrana u IDE-u Soldered NULA Mini ESP32C6. Druga ESP32 varijanta stavlja I2C na druge pinove, a skica će se savršeno prevesti i prenijeti a da nikada ne dođe do ekrana.
  • Monitor pokazuje točke i Time synced!, ali ekran ostaje taman. Pločica razgovara s ekranom a ekran je jednostavno previše blijed da bi se čitao. Okreni CONTRAST trimpot na ljubičastoj adapterskoj pločici.
  • Monitor ponavlja Failed to obtain time from NTP. Pločica je na mreži ali ne može dobiti vrijeme, pa loop() odustaje prije nego bilo što ispiše. Ekran je prazan jer nema što prikazati. Neke gostinske i korporativne mreže blokiraju vremenske servere.
ℹ️
Prvi slučaj vrijedi razmotriti. Upaljeno pozadinsko svjetlo nije dokaz ničega: njegovo uključivanje je i samo poruka poslana preko Qwiic kabela, ali izlazi ekspandera pri uključivanju sjede visoko, pa se svjetlo upali bez obzira je li ta poruka ikada došla. To je ista zamka kao u koraku 11, i zato je Serial Monitor brže mjesto za pogledati.

Sat je pogrešan za cijeli broj sati. To je pomak vremenske zone, a ne greška. Vidi iznad.

Jedna tipka ne radi ništa. Provjeri koristi li jednu nožicu s obje strane središnjeg kanala. Tipka spojena s obje žice na istoj strani čita se kao trajno pritisnuta, a ona s žicom mase u pogrešnom redu ne radi ništa uopće.

Buzzer je tih ali se ALARM! pojavljuje. Kod je u redu a greška je u posljednje dvije žice. Provjeri da su nožice buzzera stvarno u dva različita reda, i da je IO4 pin u j27. S druge strane pločice, a27 je IO19.


Cijeli primjer

Pogledaj cijeli kod primjera na linku ispod:

7.7_Alarm_Clock.ino

Budilica povezana na Wi-Fi: pločica vrijeme dobiva s NTP servera, prikazuje ga na Qwiic LCD-u i oglašava buzzer u vrijeme alarma postavljeno dvjema tipkama.