Radionički tajmer: podešavanje, pauza i zvučni kraj

Podesi tajmer od 10 do 300 sekundi potenciometrom, prikaži odbrojavanje na OLED-u i dodaj pauzu, nastavak i prioritetni RESET. Aktivni buzzer signalizira završetak uz jasan račun preostalog vremena i objašnjenje kašnjenja prikaza.

Integrisani projekti i korisničke komandeSrednji nivo120 min

Ciljevi učenja

  • Mapirati sirovo ADC očitavanje na trajanje u koracima od 10 sekundi.
  • Razlikovati READY, RUNNING, PAUSED i DONE i odrediti prioritet događaja.
  • Sačuvati tačan milisekundni ostatak tokom pauze i nastaviti njegov obračun.
  • Zaokružiti prikaz sekundi naviše bez mijenjanja vremenskog računa.
  • Povezati dodatni PN2222A/1N4148 buzzer drajver i ograničiti zvučni prozor.
  • Objasniti zašto sinhroni OLED prenos utiče na odziv tastera i izlaza.

Prije početka

Završi kurseve 11, 13 i 18: SSD1306 OLED, aktivni buzzer sa tranzistorom i obrada tastera u vremenskom zadatku.

Referentna postavka

Ploča u Arduino IDE-u
ESP32 Dev Module
Arduino-ESP32
3.3.12
Serial Monitor
115200 baud

Ovo je referentni profil, a ne identifikacija ploče sa fotografije. Za ESP32-C3/S2/S3 ili drugačije označenu ploču treba prilagoditi GPIO i provjeriti šemu prije spajanja.

Šta ćemo napraviti

Napravićemo radionički tajmer: potenciometrom biraš trajanje, jednim tasterom pokrećeš, pauziraš i nastavljaš odbrojavanje, a drugim ga poništavaš. OLED prikazuje stanje i preostalo vrijeme. Po isteku aktivni buzzer daje ponavljane kratke signale tokom deset sekundi, a završni ekran ostaje do potvrde.

Trajanje se bira od 10 do 300 sekundi, u koracima od 10 sekundi. Primjena može biti raspodjela vremena za zadatak, kratki eksperiment ili smjenjivanje članova grupe. Ovo je nastavna vremenska funkcija, bez tvrdnje o kalibrisanoj preciznosti. Cilj je povezati korisničke događaje, vremenski račun, ekran i zvuk u jedan razumljiv program.

Šta ćeš naučiti

  • Mapirati potenciometar na diskretna trajanja.
  • Razlikovati izbor trajanja od preostalog vremena aktivnog zadatka.
  • Sačuvati preostale milisekunde pri pauzi i pravilno nastaviti.
  • Dati RESET događaju prednost nad drugim komandama.
  • Zaokružiti prikaz sekundi naviše bez mijenjanja vremenskog računa.
  • Objasniti kako prenos slike utiče na odziv tastera i zvučni signal.

Predznanje

Završi kurseve 11, 13 i 18: OLED povezivanje, aktivni buzzer sa tranzistorom i obrada događaja u vremenskom zadatku. Potrebni su INPUT_PULLUP, razlika između sirovog i potvrđenog stanja tastera i računanje intervala pomoću millis(). Koristi klasični ESP32/ESP32-WROOM-32 i navedene GPIO oznake. Ovaj raspored nije potvrđen za druge ESP32 porodice.

Potrebna oprema i provjera varijanti

KomponentaKoličinaSpecifikacija i napomene
ESP32 razvojna ploča sa klasičnim ESP32-WROOM-32 modulom1GPIO nazivi moraju odgovarati referentnom profilu; fizički položaj izvoda provjeriti na ploči.
USB kabl za podatke i napajanje1Priključak prilagoditi ploči; komplet na slici navodi Micro-USB.
Eksperimentalna ploča1Komplet navodi 830 kontaktnih tačaka. Provjeriti eventualni prekid naponskih vodova.
OLED displej 0,96″ — referentni I2C SSD1306 128×641VCC/GND/SDA/SCL, napajanje 3,3 V, pull-up otpornici na modulu prema 3V3. Kontroler, interfejs i rezolucija sa fotografije nisu potvrđeni.
Potenciometar 10 kΩ1Dva krajnja izvoda i klizač W; raspored utvrditi prema stvarnoj komponenti.
Trenutni taster2Koristiti par priključaka koji se spaja pritiskom; ista strana četvoronožnog tastera može već biti spojena.
Aktivni buzzer — referenca 5 V, do 20 mA1Fotografija navodi aktivni buzzer, ali ne potvrđuje njegov napon i struju. Za ovu šemu izaberi dokumentovani 5 V model sa najviše 20 mA radne struje.
Dodatni NPN tranzistor PN2222A1Obavezno dodatno — nije na slici kompletaDODATNO, nije naveden u kompletu. Identifikuj B/C/E prema tačnom proizvođaču; PN2222A i P2N2222A mogu imati različit raspored.

DODATNO: tranzistor nije naveden na fotografiji kompleta.

Dodatna zaštitna dioda 1N41481Obavezno dodatno — nije na slici kompletaDODATNO, nije navedena u kompletu. Prsten označava katodu K. U buzzer krugu katoda ide prema +5 V.

DODATNO: dioda nije navedena na fotografiji kompleta.

Otpornik 1 kΩ1Otpornik baze; u kursu 14 i paralelni otpornik piezo elementa. Količina ove vrijednosti u kompletu nije potvrđena.
Otpornik 10 kΩ1R2 je bazni pull-down od Q1.B prema GND.
Spojni kablovi21M-M ili F-M prema priključcima ploče.

Okvirna količina; zavisi od rasporeda i vrste priključaka.

PN2222A i 1N4148 su dodatne obavezne komponente koje nisu navedene na fotografiji kompleta. Provjeri tačan B/C/E raspored tranzistora: slični nazivi PN2222A i P2N2222A ne garantuju jednak raspored izvoda. onsemi — PN2222A

Referenca je aktivni buzzer deklarisan za 5 V i radnu struju najviše 20 mA. Pasivni piezo ne daje isti kontinuirani zvuk na ovom programu. OLED mora biti I2C SSD1306, 128 × 64, pogodan za 3,3 V; fotografija ne potvrđuje kontroler. Provjeri R1 od 1 kΩ, R2 od 10 kΩ i potenciometar od 10 kΩ. Logički kontakti tastera moraju biti otvoreni prije pritiska; dva izvoda iste unutrašnje grane četvoronožnog tastera ne predstavljaju par koji se pritiskom spaja.

Stanja i pravila komandovanja

U READY biraš trajanje. Potvrđen START prelazi u RUNNING i pamti taj izbor. U RUNNING isti taster pauzira u PAUSED, a iz PAUSED nastavlja preostalo vrijeme. Okretanje potenciometra tokom rada ili pauze ne mijenja započeti zadatak.

Po isteku nastaje DONE: prikazuje se nula, a zvuk radi u prozoru od deset sekundi od logičkog roka isteka. Nakon toga DONE ostaje, ali buzzer ćuti. Novi START potvrđuje kraj i vraća READY; potreban je naredni novi pritisak za drugo odbrojavanje. RESET iz bilo kojeg radnog stanja vraća READY i gasi zvuk. Ako se START i RESET potvrde u istoj iteraciji, RESET ima prednost. Istek ima prednost nad pokušajem pauze na samoj granici: taj START se zanemaruje i ostaje DONE.

Šema i redoslijed spajanja

Šema povezivanja

OLED i potenciometar koriste 3V3. Buzzer napaja provjereni USB5V vod, preko Q1 i zaštitne D1. Oba tastera spajaju odabrani GPIO na GND. Q1 i D1 su dodatne komponente. Provjeri SSD1306 kontroler, adresu i oznake izvoda.

Šema povezivanja — električne veze navedene u tabeli ispod
Šema povezivanjaUvećajSVGPNG
Veze · Šema povezivanja
OdDoOpis veze
ESP32.3V3OLED1.VCCReferentni I2C SSD1306 128×64, napajanje 3,3 V.
ESP32.GNDOLED1.GNDZajednička masa.
ESP32.GPIO21OLED1.SDAI2C podaci.
ESP32.GPIO22OLED1.SCLI2C takt, referentno 100 kHz.
ESP32.3V3RV1.END_AJedan krajnji izvod potenciometra 10 kΩ.
ESP32.GNDRV1.END_BDrugi krajnji izvod.
RV1.WESP32.GPIO34Klizač W prema ADC1 ulazu GPIO34.
ESP32.GPIO26R1.1R1, 1 kΩ, ograničava struju baze.
R1.2Q1.BBaza dodatnog PN2222A; provjeri fizički raspored B/C/E.
Q1.BR2.1R2, 10 kΩ, povlači bazu prema masi.
R2.2ESP32.GNDDrugi kraj baznog pull-down otpornika.
Q1.EESP32.GNDEmiter prema zajedničkoj masi.
ESP32.USB5VBZ1.PLUSDokumentovani aktivni buzzer 5 V, do 20 mA radne struje.
BZ1.MINUSQ1.CNegativni izvod buzzera prema kolektoru.
D1.ABZ1.MINUSAnoda zaštitne diode 1N4148 na negativnom izvodu buzzera.
D1.KESP32.USB5VKatoda diode, označena prstenom, prema +5 V.
ESP32.GPIO27SW1.CONTACT_ATaster START/PAUSE; ulaz INPUT_PULLUP.
SW1.CONTACT_BESP32.GNDPar kontakata koji se spaja samo pritiskom.
ESP32.GPIO32SW2.CONTACT_ATaster RESET; ulaz INPUT_PULLUP.
SW2.CONTACT_BESP32.GNDPar kontakata koji se spaja samo pritiskom.
  1. Isključi USB i poveži zajednički GND svih grana.
  2. OLED spoji VCC → 3V3, GND → GND, SDA → GPIO21, SCL → GPIO22. Njegovi I2C pull-up otpornici moraju voditi na 3,3 V.
  3. Krajeve potenciometra spoji na 3V3 i GND, a klizač na GPIO34.
  4. START/PAUSE/RESUME taster SW1 poveži između GPIO27 i GND. RESET taster SW2 ide između GPIO32 i GND.
  5. Spoji GPIO26 preko R1 od 1 kΩ na Q1.B. R2 od 10 kΩ ide između Q1.B i GND; Q1.E na GND.
  6. Q1.C poveži na minus BZ1, a plus BZ1 na provjereni USB 5 V vod ploče.
  7. Diodu D1 postavi preko buzzera: anoda na BZ1 minus, katoda sa oznakom prstena na USB 5 V.
  8. Provjeri napajanje, polaritet diode i buzzer, B/C/E i sve spojeve prije uključivanja USB-a.

GPIO26 HIGH uključuje tranzistor, a struja buzzera dolazi sa 5 V voda. GPIO nije direktni izvor te struje. R2 drži bazu nisko kada izlaz još ne upravlja. Provjeri kapacitet USB kabla i napojnog puta ploče; ne dodaj nezavisno 5 V napajanje paralelno USB-u. Espressif — napajanje DevKitC ploče

Priprema Arduino okruženja

Koristi Arduino IDE 2.x, esp32 by Espressif Systems 3.3.12, ploču ESP32 Dev Module i Serial Monitor na 115200 baud. Instaliraj Adafruit SSD1306 2.5.17, Adafruit GFX Library 1.12.6 i Adafruit BusIO 1.17.4. Wire dolazi sa ESP32 paketom.

Raspakuj primjer i otvori workshop_timer.ino iz istoimenog foldera. Provjeri port, pokreni Verify pa Upload. Referentna OLED adresa je 0x3C; promijeni je na 0x3D samo ako je potvrđena. Program provjerava odgovor na adresi prije pripreme ekrana. Odgovor ne dokazuje da je kontroler SSD1306. Ako početna provjera ne uspije, tajmer se ne pokreće, a buzzer ostaje ugašen.

Library Manager — dokumentovane verzije i zavisnosti
Biblioteka i izdanjeVerzijaZavisnosti
Adafruit SSD13062.5.17Adafruit GFX Library 1.12.6
Adafruit GFX Library1.12.6Adafruit BusIO 1.17.4
Adafruit BusIO1.17.4Nema

Wire je uključen u ESP32 core; ne instalira se zasebno kroz Library Manager.

Kompletan program

workshop_timer.ino Arduino / C++
Preuzmi .ino

#include <Arduino.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

constexpr uint8_t BUZZER_PIN = 26;
constexpr uint8_t START_PIN = 27;
constexpr uint8_t RESET_PIN = 32;
constexpr uint8_t POT_PIN = 34;
constexpr uint8_t SDA_PIN = 21, SCL_PIN = 22;
constexpr uint8_t OLED_ADDRESS = 0x3C;
constexpr uint32_t DEBOUNCE_MS = 30;
constexpr uint32_t POT_SAMPLE_MS = 100;
constexpr uint32_t DISPLAY_MS = 200;
constexpr uint32_t SOUND_WINDOW_MS = 10000;
// A fresh event needs 30 ms at one level. A held or inhibited press must
// release stably before another event can be accepted.
struct DebouncedButton {
  uint8_t pin;
  bool rawHigh = true, stableHigh = true, armed = true;
  bool pressed = false, released = false;
  uint32_t rawChangedAt = 0;
  explicit DebouncedButton(uint8_t inputPin) : pin(inputPin) {}
  void begin(uint32_t now) {
    pinMode(pin, INPUT_PULLUP);
    rawHigh = stableHigh = digitalRead(pin) == HIGH;
    armed = rawHigh;
    rawChangedAt = now;
    pressed = released = false;
  }
  void update(uint32_t now) {
    pressed = released = false;
    const bool readingHigh = digitalRead(pin) == HIGH;
    if (readingHigh != rawHigh) {
      rawHigh = readingHigh;
      rawChangedAt = now;
    }
    if (static_cast<uint32_t>(now - rawChangedAt) < DEBOUNCE_MS) return;
    if (rawHigh != stableHigh) {
      stableHigh = rawHigh;
      if (stableHigh) released = true;
      else if (armed) {
        pressed = true;
        armed = false;
      }
    }
    // Also recovers from an inhibited LOW pulse too short to become stable.
    if (rawHigh && stableHigh) armed = true;
  }
  void inhibit() { armed = false; pressed = false; }
  bool isReleased(uint32_t now) const {
    return rawHigh && stableHigh &&
           static_cast<uint32_t>(now - rawChangedAt) >= DEBOUNCE_MS;
  }
};
Adafruit_SSD1306 display(128, 64, &Wire, -1, 100000UL, 100000UL);
bool ready = false;

bool initializeDisplay() {
  if (!Wire.begin(SDA_PIN, SCL_PIN)) {
    Serial.println("OLED ERROR: I2C initialization failed; reset to retry.");
    return false;
  }
  Wire.setClock(100000);
  Wire.setTimeOut(50);
  Wire.beginTransmission(OLED_ADDRESS);
  if (Wire.endTransmission() != 0) {
    Serial.println("OLED ERROR: address did not ACK; check wiring/address and reset.");
    return false;
  }
  Serial.print("OLED address ACK: 0x"); Serial.println(OLED_ADDRESS, HEX);
  Serial.println("An ACK does not identify the display controller.");
  if (!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDRESS, false, false)) {
    Serial.println("OLED ERROR: buffer initialization failed; reset to retry.");
    return false;
  }
  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE);
  display.setTextWrap(false);
  return true;
}

DebouncedButton startButton(START_PIN), resetButton(RESET_PIN);
enum TimerState { READY, RUNNING, PAUSED, DONE };
TimerState state = READY;
TimerState drawnState = READY;
bool screenDrawn = false;
uint16_t selectedSeconds = 10, drawnSeconds = 0;
uint32_t remainingAtStart = 0, runStartedAt = 0, doneAt = 0;
uint32_t lastPotAt = 0, lastDisplayAt = 0;

uint16_t durationFromRaw(uint16_t raw) {
  return static_cast<uint16_t>(10UL * (1UL + static_cast<uint32_t>(raw) * 29UL / 4095UL));
}

uint32_t remainingMs(uint32_t now) {
  if (state != RUNNING) return remainingAtStart;
  const uint32_t elapsed = static_cast<uint32_t>(now - runStartedAt);
  return elapsed >= remainingAtStart ? 0UL : remainingAtStart - elapsed;
}

void returnToReady(uint32_t now) {
  state = READY;
  remainingAtStart = 0;
  selectedSeconds = durationFromRaw(analogRead(POT_PIN));
  lastPotAt = now;
  digitalWrite(BUZZER_PIN, LOW);
}

void updateBuzzer(uint32_t now) {
  const uint32_t elapsed = static_cast<uint32_t>(now - doneAt);
  const bool on = state == DONE && elapsed < SOUND_WINDOW_MS && elapsed % 1000UL < 200UL;
  digitalWrite(BUZZER_PIN, on ? HIGH : LOW);
}

void drawTimer(uint32_t now) {
  const uint32_t remaining = remainingMs(now);
  const uint16_t seconds = state == READY ? selectedSeconds :
                           static_cast<uint16_t>((remaining + 999UL) / 1000UL);
  if (screenDrawn && drawnState == state && drawnSeconds == seconds) return;
  if (screenDrawn && drawnState == state &&
      static_cast<uint32_t>(now - lastDisplayAt) < DISPLAY_MS) return;
  display.clearDisplay();
  display.setCursor(0, 0); display.print("Workshop timer");
  display.setCursor(0, 12);
  switch (state) {
    case READY: display.print("READY: turn the knob"); break;
    case RUNNING: display.print("RUNNING"); break;
    case PAUSED: display.print("PAUSED"); break;
    case DONE: display.print("DONE"); break;
  }
  display.setTextSize(2);
  display.setCursor(0, 25); display.print(seconds); display.print(" s");
  display.setTextSize(1);
  display.setCursor(0, 46);
  if (state == READY) display.print("START: run");
  else if (state == RUNNING) display.print("START: pause");
  else if (state == PAUSED) display.print("START: resume");
  else display.print("START: acknowledge");
  display.setCursor(0, 56); display.print("RESET: ready / quiet");
  display.display();  // Synchronous I2C: time spent here is part of the timer.
  drawnState = state;
  drawnSeconds = seconds;
  screenDrawn = true;
  lastDisplayAt = now;
}

void setup() {
  Serial.begin(115200);
  pinMode(BUZZER_PIN, OUTPUT); digitalWrite(BUZZER_PIN, LOW);
  startButton.begin(millis()); resetButton.begin(millis());
  analogReadResolution(12);
  analogSetPinAttenuation(POT_PIN, ADC_11db);
  selectedSeconds = durationFromRaw(analogRead(POT_PIN));
  ready = initializeDisplay();
  if (!ready) return;
  lastPotAt = millis();
  drawTimer(millis());
  Serial.println("Timer uses elapsed millis; very brief taps can be missed during I2C.");
}

void loop() {
  if (!ready) return;
  const uint32_t now = millis();
  startButton.update(now); resetButton.update(now);
  if (resetButton.pressed) {
    // RESET wins over expiry, START, PAUSE and RESUME in this iteration.
    returnToReady(now);
  } else {
    bool justExpired = false;
    if (state == RUNNING && remainingMs(now) == 0) {
      // Anchor completion to its scheduled deadline, even after a slow loop.
      doneAt = runStartedAt + remainingAtStart;
      remainingAtStart = 0;
      state = DONE;
      justExpired = true;
    }
    // At the deadline, expiry wins over a newly qualified pause request.
    if (startButton.pressed && !justExpired) {
      if (state == READY) {
        remainingAtStart = static_cast<uint32_t>(selectedSeconds) * 1000UL;
        runStartedAt = now;
        state = RUNNING;
      } else if (state == RUNNING) {
        remainingAtStart = remainingMs(now);
        state = PAUSED;
      } else if (state == PAUSED) {
        runStartedAt = now;
        state = RUNNING;
      } else returnToReady(now);
    }
    if (state == READY && static_cast<uint32_t>(now - lastPotAt) >= POT_SAMPLE_MS) {
      lastPotAt = now;
      selectedSeconds = durationFromRaw(analogRead(POT_PIN));
    }
  }
  updateBuzzer(now);
  drawTimer(now);
}

Oba tastera koriste INPUT_PULLUP, pa pritisak znači LOW. Novo stanje mora biti stabilno 30 ms prije prihvatanja. Držanje ne ponavlja radnju, a taster držan pri uključivanju mora se prvo otpustiti. Koristi namjerne, obične pritiske: program povremeno prenosi cijelu sliku, pa veoma kratak dodir može proći između dva očitavanja ulaza.

Vremenski račun, prikaz i zvuk

Potenciometar se čita na 100 ms, samo u READY, uz 12-bitni ADC i ADC_11db. durationFromRaw() koristi formulu 10 * (1 + raw * 29UL / 4095): 0 daje 10 s, 2048 daje 150 s, a 4095 daje 300 s. Cjelobrojno dijeljenje određuje korake; srednje očitavanje ne daje 155 ni 160 s. Odnos nije kalibracija ugla potenciometra. Espressif — ADC API

Na početku dionice rada program čuva remainingAtStart i runStartedAt. remainingMs() dobija ostatak oduzimanjem stvarno proteklih milisekundi, sa donjom granicom nula. Pri pauzi prvo obračuna proteklo vrijeme, pa sačuva ostatak. Nastavak pravi novu vremensku početnu tačku uz isti ostatak. Vrijeme pauze ne ulazi u odbrojavanje. Ovakvo oduzimanje ne zavisi od broja prolazaka kroz loop() i podržava prelazak 32-bitnog brojača preko nule za ove kratke intervale. Arduino — Blink Without Delay

drawTimer() računa OLED sekunde kao (remaining + 999) / 1000. Na primjer, 27 500 ms prikazuje kao 28 s; za račun i pauzu i dalje se koristi 27 500 ms. Prenosi samo promijenjen sadržaj; unutar istog stanja između prenosa mora proći najmanje 200 ms. Promjena stanja može zatražiti raniji prikaz. I2C je 100 kHz u Wire podešavanju i u oba takta SSD1306 konstruktora. Priprema sa periphBegin=false čuva već postavljenu magistralu.

Prenos slike je sinhron: okvir od 1024 bajta zahtijeva približno 92 ms samo za podatke i potvrde pri 100 kHz, prije dodatnog režijskog vremena. To je proračun, ne izmjerena vrijednost ovog sklopa. Proteklo vrijeme prenosa uključeno je u račun tajmera, ali ulazi i izlazi obrađuju se tek kada program ponovo dođe do njih. Završni signal može početi nakon logičkog roka, a ivice zvuka mogu biti pomjerene. updateBuzzer() zadaje nominalno 200 ms zvuka i 800 ms tišine. doneAt čuva planirani rok isteka, pa kašnjenje obrade ne produžava desetosekundni prozor; može skratiti njegov čujni početak. Adafruit — SSD1306 implementacija

Praktični eksperiment i očekivanja

Izaberi kratko trajanje i prvo provjeri start, pauzu, nastavak i RESET pojedinačno. Posmatraj READY prije početka da potvrdiš izbor. Zatim izvedi sljedeća očekivanja i uz svako zapiši rezultat.

PostupakOčekivano ponašanje
U READY promijeni položaj potenciometraIzbor se mijenja u koracima od 10 s
Pokreni, pa okreći potenciometarAktivni zadatak zadržava početno trajanje
Pauziraj i sačekaj nekoliko sekundiPreostalo vrijeme miruje
Nastavi nakon pauzeNastavlja sa sačuvanim ostatkom
Pritisni RESET tokom zvukaREADY i ugašen buzzer
Pusti da protekne cijeli završni prozorDONE ostaje, zvuk prestaje

Za provjeru početnog držanja isključi USB, drži SW1, uključi USB i zatim otpusti taster. Samo to ne smije započeti odbrojavanje; slijedi novi pritisak. Za provjeru prioriteta pritisni oba tastera tako da se potvrde zajedno. Ako se zbog fizičke razlike potvrde odvojeno, vidićeš redoslijed dva događaja, a ne istovremeni slučaj.

Najčešći problemi

ProblemProvjera
Ekran ne počinje radSSD1306 tip, 128 × 64, 21/22, adresa i 3,3 V
Taster stalno izgleda pritisnutUnutrašnje veze tastera i spoj GPIO–GND
Kratak dodir se ponekad ne registrujePritisni dovoljno dugo za uzorkovanje i 30 ms stabilnosti
Zvuk izostajeAktivni tip buzzera, Q1 B/C/E, R1/R2, 5 V i polaritet
Cilj ne prati potenciometarPromjene su dozvoljene samo u READY
Ekran na pauzi pokazuje sekund više od očekivanogPrikaz zaokružuje ostatak naviše

Ako se ekran odspoji tokom rada, biblioteka ne garantuje potpunu detekciju tog fizičkog kvara. Početna provjera adrese i povratne vrijednosti pripreme imaju ograničen obuhvat; uspješan poziv nije dokaz da je svaka slika fizički prikazana.

Samostalni izazovi

  1. Tajmer je pokrenut sa 40 s u t = 0. Pauza je obrađena u t = 12 500 ms, a nastavak u t = 20 000 ms. Izračunaj ostatak, prikazane sekunde na pauzi i novi logički rok isteka.
  2. Skrati završni zvučni prozor na tri sekunde, uz nepromijenjen obrazac 200/800 ms. Da li završni ekran treba automatski da nestane?

Razrađena rješenja

U prvom zadatku ostaje 40 000 − 12 500 = 27 500 ms, odnosno prikazanih 28 s. Nova dionica počinje u 20 000 ms i ističe u 20 000 + 27 500 = 47 500 ms. Pauza od 7500 ms nije potrošila aktivno vrijeme. Ovo su vremena obrade događaja; ne treba ih poistovjetiti sa prvim fizičkim dodirom tastera.

U drugom zadatku promijeni SOUND_WINDOW_MS sa 10000 na 3000. Period od 1000 ms i uključeni dio od 200 ms ostaju isti, pa postoje tri nominalne zvučne dionice. DONE i nula ostaju vidljivi do START potvrde ili RESET-a. Završetak zvuka i izlazak iz stanja nisu ista odluka.

Pitanja sa odgovorima

Zašto ne oduzimamo jednu sekundu po osvježavanju? Prenos i druga obrada traju promjenljivo; broj slika nije pouzdan vremenski izvor.

Šta se događa pri pauzi na isteku? Istek ima prednost; ne stvara se PAUSED sa nulom.

Zašto RESET ima prednost? Daje jednoznačan način otkazivanja čak i kada se obje komande potvrde zajedno.

Može li ovaj program garantovati tačne zvučne ivice? Ne. Izlazi se servisiraju u petlji, a sinhroni prenos i potvrda tastera unose kašnjenje.

Primarni izvori

Preuzimanja

Uz primjer sačuvaj tabelu svojih provjera. Računarska provjera logike ne zamjenjuje stvarnu ESP32 kompilaciju i fizičku probu sa navedenim komponentama.