STEM kviz: tri odgovora, RGB povratna informacija

Napravi OLED kviz sa pet STEM pitanja, tasterima A/B/C i RGB prikazom tačnog ili netačnog odgovora. Organizuj pitanja u niz i obradi držanje tastera, višestruki pritisak i novi krug.

Integrisani projekti i korisničke komandeSrednji nivo120 min

Ciljevi učenja

  • Povezati tri tastera i RGB diodu sa zasebnim otpornikom za svaki kanal.
  • Prilagoditi šemu i izlazni polaritet zajedničkoj katodi ili anodi na 3,3 V.
  • Organizovati pitanje, tri odgovora i tačan indeks u nizu zapisa.
  • Zahtijevati otpuštanje tastera i ignorisati uočeni višestruki pritisak.
  • Prikazati rezultat i pokrenuti novi krug bez ponovnog pokretanja ploče.

Prije početka

Kursevi 06, 11 i 18: RGB kanali i polaritet, OLED preko I2C, uklanjanje odskakivanja i stanja interaktivne igre.

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 samostalni STEM kviz sa OLED ekranom, tri tastera i RGB LED diodom. Učesnik bira odgovor A, B ili C. Plavo svjetlo označava čekanje, zeleno tačan, a crveno netačan odgovor. Nakon pet pitanja ekran prikazuje rezultat. Novi krug pokreće se tasterom A, bez ponovnog uključivanja ESP32.

Sadržaj pitanja odvojen je od pravila igre. Zbog toga isti program kasnije može poslužiti za kratku provjeru znanja iz drugih oblasti. Posebno ćemo provjeriti šta se dešava kada se taster drži, kada kontakti odskaču i kada program uoči dva pritisnuta tastera.

Šta ćeš naučiti

  • Organizovati pitanja, ponuđene odgovore i indeks tačnog odgovora u nizu zapisa.
  • Povezati tri tastera i tri odvojene RGB grane.
  • Koristiti stanja za prikaz pitanja, povratne informacije i rezultata.
  • Zahtijevati otpuštanje tastera prije prihvatanja sljedećeg odgovora.
  • Objasniti ponašanje pri višestrukom pritisku i promjeni RGB varijante.

Predznanje

Završi kurseve 06, 11 i 18: RGB kanali, OLED preko I2C magistrale i igra sa obradom događaja. Treba razumjeti INPUT_PULLUP, odskakivanje kontakata i poređenje vremenskih intervala pomoću millis(). Koristimo klasični ESP32/WROOM32; brojevi GPIO nisu univerzalni raspored priključaka svih ESP32 ploča.

Potrebna oprema

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.
Trenutni taster3Koristiti par priključaka koji se spaja pritiskom; ista strana četvoronožnog tastera može već biti spojena.
RGB LED sa četiri izvoda1R, G, B i zajednički izvod COM. Podržane su odvojene šeme za zajedničku katodu ili anodu; redoslijed nogica nije pretpostavljen.
Otpornik 220 Ω3Po jedan otpornik za svaku LED granu; 220 Ω iz kompleta.
Spojni kablovi18M-M ili F-M prema priključcima ploče.

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

Potrebna je RGB dioda sa četiri izvoda, bez adresabilnog upravljačkog čipa, i tri zasebna otpornika od 220 Ω. Podrazumijevana varijanta ima zajedničku katodu. U paketu je i posebna šema za zajedničku anodu; izbor mora odgovarati stvarnoj diodi. Raspored izvoda R, G, B i COM utvrdi iz dokumentacije konkretnog dijela.

OLED referenca je SSD1306, 128 × 64, I2C, napajan sa 3,3 V. Fotografija sama ne potvrđuje kontroler, adresu ili kompatibilnost modula. Označi tastere A, B i C prije rada kako bi položaj na stolu odgovarao ponuđenim odgovorima.

Pravila kviza i električni nivoi

Svaki taster spaja svoj ulaz sa GND. Unutrašnji pull-up daje HIGH kada je taster otpušten, a pritisak daje LOW. Program prihvata novi nivo poslije 30 ms stabilnosti. Držanje tastera nije više odgovora.

Kod zajedničke katode COM ide na GND, pa HIGH uključuje izabranu boju. Kod zajedničke anode COM ide na 3V3, a izabrana boja uključuje se nivoom LOW. Postavka COMMON_ANODE obrće izlaze, dok značenje boja ostaje isto. COM se ni u jednoj varijanti ne povezuje na 5 V. PWM nije potreban jer prikazujemo potpuno uključene pojedinačne boje.

Prije svakog pitanja program traži da sva tri tastera budu otpuštena i potvrđena stabilnim visokim nivoom. Ako uoči višestruki pritisak, ne bira proizvoljno odgovor A: vraća se na zahtjev za otpuštanjem. Pritisak koji se desi tek nakon već prihvaćenog odgovora ne može retroaktivno poništiti prethodnu odluku.

Šema i redoslijed povezivanja

RGB — zajednička katoda

Osnovna varijanta

COM ide na GND; COMMON_ANODE false. Svaki R/G/B kanal ima svoj otpornik 220 Ω. Raspored nogica utvrdi na stvarnoj komponenti.

Potrebna postavka programa: #define COMMON_ANODE false

RGB — zajednička katoda — električne veze navedene u tabeli ispod
RGB — zajednička katodaUvećajSVGPNG
Veze · RGB — zajednička katoda
OdDoOpis veze
ESP32.GPIO25R1.1Digitalno upravljanje: crveni kanal.
R1.2RGB1.RR1, 220 Ω, zasebni serijski otpornik kanala.
ESP32.GPIO26R2.1Digitalno upravljanje: zeleni kanal.
R2.2RGB1.GR2, 220 Ω, zasebni serijski otpornik kanala.
ESP32.GPIO33R3.1Digitalno upravljanje: plavi kanal.
R3.2RGB1.BR3, 220 Ω, zasebni serijski otpornik kanala.
RGB1.COMESP32.GNDZajednička katoda na GND; ostavi COMMON_ANODE false.
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.GPIO27SW1.CONTACT_ATaster A; ulaz INPUT_PULLUP.
SW1.CONTACT_BESP32.GNDPar kontakata koji se spaja samo pritiskom.
ESP32.GPIO32SW2.CONTACT_ATaster B; ulaz INPUT_PULLUP.
SW2.CONTACT_BESP32.GNDPar kontakata koji se spaja samo pritiskom.
ESP32.GPIO14SW3.CONTACT_ATaster C; ulaz INPUT_PULLUP.
SW3.CONTACT_BESP32.GNDPar kontakata koji se spaja samo pritiskom.

RGB — zajednička anoda

Alternativna varijanta

COM ide na 3V3; COMMON_ANODE true. Svaki R/G/B kanal ima svoj otpornik 220 Ω. Raspored nogica utvrdi na stvarnoj komponenti.

Potrebna postavka programa: #define COMMON_ANODE true

RGB — zajednička anoda — električne veze navedene u tabeli ispod
RGB — zajednička anodaUvećajSVGPNG
Veze · RGB — zajednička anoda
OdDoOpis veze
ESP32.GPIO25R1.1Digitalno upravljanje: crveni kanal.
R1.2RGB1.RR1, 220 Ω, zasebni serijski otpornik kanala.
ESP32.GPIO26R2.1Digitalno upravljanje: zeleni kanal.
R2.2RGB1.GR2, 220 Ω, zasebni serijski otpornik kanala.
ESP32.GPIO33R3.1Digitalno upravljanje: plavi kanal.
R3.2RGB1.BR3, 220 Ω, zasebni serijski otpornik kanala.
RGB1.COMESP32.3V3Zajednička anoda na 3V3; postavi COMMON_ANODE true.
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.GPIO27SW1.CONTACT_ATaster A; ulaz INPUT_PULLUP.
SW1.CONTACT_BESP32.GNDPar kontakata koji se spaja samo pritiskom.
ESP32.GPIO32SW2.CONTACT_ATaster B; ulaz INPUT_PULLUP.
SW2.CONTACT_BESP32.GNDPar kontakata koji se spaja samo pritiskom.
ESP32.GPIO14SW3.CONTACT_ATaster C; ulaz INPUT_PULLUP.
SW3.CONTACT_BESP32.GNDPar kontakata koji se spaja samo pritiskom.
  1. Isključi USB i potvrdi tip RGB diode, njene izvode i oznake OLED modula.
  2. Poveži OLED GND na ESP32 GND, VCC na 3V3, SDA na GPIO21 i SCL na GPIO22.
  3. Poveži GPIO25 preko R1 od 220 Ω na RGB izvod R; GPIO26 preko R2 od 220 Ω na G; GPIO33 preko R3 od 220 Ω na B.
  4. Za zajedničku katodu poveži COM na GND i ostavi #define COMMON_ANODE false. Za zajedničku anodu primijeni drugu šemu, COM poveži na 3V3 i postavi #define COMMON_ANODE true.
  5. Spoji SW1, taster A, između GPIO27 i GND; SW2, taster B, između GPIO32 i GND; SW3, taster C, između GPIO14 i GND.
  6. Kod četvoronožnih tastera izaberi kontakte koji se spajaju tek pritiskom. Provjeri kontinuitet i redove eksperimentalne ploče; dva kontakta iste strane mogu već biti spojena.
  7. Pregledaj sve tri RGB grane, zajednički GND i izabranu varijantu. Tek tada uključi USB.

Šema prikazuje električne veze, a ne obavezan fizički redoslijed nožica. Jedan otpornik u zajedničkom RGB izvodu ne zamjenjuje tri predviđena otpornika. GPIO34 iz jednog pitanja nije dio ovog sklopa; pitanje se odnosi na ranije korišćeni analogni ulaz.

Priprema Arduino okruženja

U Arduino IDE-u 2.x izaberi esp32 by Espressif Systems 3.3.12, ESP32 Dev Module i odgovarajući port. Instaliraj Adafruit SSD1306 2.5.17, Adafruit GFX Library 1.12.6 i Adafruit BusIO 1.17.4. Wire dolazi uz ESP32 paket.

Otvori stem_quiz/stem_quiz.ino. Provjeri RGB postavku i OLED adresu: podrazumijevana je 0x3C, a dokumentovanu varijantu 0x3D postavi samo ako odgovara modulu. Program provjerava odziv prije inicijalizacije ekrana. I2C koristi GPIO21/22 i 100 kHz; Serial Monitor podesi na 115200 baud.

Pokreni Verify, zatim Upload. Tekst na OLED-u je ASCII engleski u obje jezičke verzije kursa, jer koristimo ugrađeni font. „Release all buttons” znači „Otpusti sve tastere”, a „Score” označava rezultat. Ako početna provjera OLED-a ne uspije, RGB ostaje ugašen i kviz ne prihvata nevidljive odgovore. Odziv na I2C adresi sam ne potvrđuje da modul zaista ima SSD1306 kontroler.

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 Arduino program

stem_quiz.ino Arduino / C++
Preuzmi .ino

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

#ifndef COMMON_ANODE
#define COMMON_ANODE false  // true ONLY with the common-anode wiring variant.
#endif
constexpr uint8_t BUTTON_A_PIN = 27, BUTTON_B_PIN = 32, BUTTON_C_PIN = 14;
constexpr uint8_t RED_PIN = 25, GREEN_PIN = 26, BLUE_PIN = 33;
constexpr uint8_t SDA_PIN = 21, SCL_PIN = 22;
constexpr uint8_t OLED_ADDRESS = 0x3C;
constexpr uint32_t DEBOUNCE_MS = 30;
constexpr uint32_t FEEDBACK_MS = 1200;
// 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 buttonA(BUTTON_A_PIN), buttonB(BUTTON_B_PIN), buttonC(BUTTON_C_PIN);
struct Question {
  const char* prompt;
  const char* options[3];
  uint8_t correctIndex;
};
const Question Questions[] = {
  {"GPIO logic level?", {"3.3 V", "5 V", "12 V"}, 0},
  {"Button to GND is?", {"HIGH", "LOW", "PWM"}, 1},
  {"ADC input here?", {"GPIO34", "3V3 pin", "GND pin"}, 0},
  {"OLED data bus?", {"UART", "SPI", "I2C"}, 2},
  {"PIR detects?", {"Air temp", "Motion", "Humidity"}, 1}
};
constexpr size_t QUESTION_COUNT = sizeof(Questions) / sizeof(Questions[0]);
enum QuizState { WAIT_RELEASE, QUESTION, FEEDBACK, FINISHED };
QuizState state = WAIT_RELEASE;
size_t questionIndex = 0, score = 0;
uint32_t stateSince = 0;
bool answerCorrect = false, restartReady = false, screenDirty = true;
void enterState(QuizState next, uint32_t now);

void setRgb(bool red, bool green, bool blue) {
  digitalWrite(RED_PIN, (red != COMMON_ANODE) ? HIGH : LOW);
  digitalWrite(GREEN_PIN, (green != COMMON_ANODE) ? HIGH : LOW);
  digitalWrite(BLUE_PIN, (blue != COMMON_ANODE) ? HIGH : LOW);
}

void enterState(QuizState next, uint32_t now) {
  state = next;
  stateSince = now;
  screenDirty = true;
  if (state == WAIT_RELEASE || state == QUESTION) setRgb(false, false, true);
  else if (state == FEEDBACK) setRgb(!answerCorrect, answerCorrect, false);
  else setRgb(false, false, false);
}

bool allReleased(uint32_t now) {
  return buttonA.isReleased(now) && buttonB.isReleased(now) && buttonC.isReleased(now);
}

void drawQuiz() {
  if (!screenDirty) return;
  display.clearDisplay();
  if (state == FINISHED) {
    display.setCursor(0, 0); display.print("QUIZ FINISHED");
    display.setCursor(0, 16); display.print("Score: "); display.print(score);
    display.print(" / "); display.print(QUESTION_COUNT);
    display.setCursor(0, 38); display.print(restartReady ? "A: restart" : "Release all buttons");
  } else if (state == WAIT_RELEASE) {
    display.setCursor(0, 0); display.print("Question "); display.print(questionIndex + 1);
    display.print(" / "); display.print(QUESTION_COUNT);
    display.setCursor(0, 22); display.print("Release all buttons");
    display.setCursor(0, 38); display.print("Choose one: A, B or C");
  } else if (state == FEEDBACK) {
    display.setCursor(0, 0); display.print(answerCorrect ? "CORRECT" : "NOT CORRECT");
    display.setCursor(0, 18); display.print("Answer: ");
    display.print(static_cast<char>('A' + Questions[questionIndex].correctIndex));
    display.setCursor(0, 34); display.print("Score: "); display.print(score);
    display.print(" / "); display.print(QUESTION_COUNT);
  } else {
    const Question& question = Questions[questionIndex];
    display.setCursor(0, 0); display.print("Q"); display.print(questionIndex + 1);
    display.print("/"); display.print(QUESTION_COUNT); display.print("  Score "); display.print(score);
    display.setCursor(0, 12); display.print(question.prompt);
    for (uint8_t i = 0; i < 3; ++i) {
      display.setCursor(0, 24 + i * 10);
      display.print(static_cast<char>('A' + i)); display.print(": "); display.print(question.options[i]);
    }
    display.setCursor(0, 56); display.print("Press one button");
  }
  display.display();  // Redraw only on a state/value change; short taps may be missed.
  screenDirty = false;
}

void setup() {
  Serial.begin(115200);
  pinMode(RED_PIN, OUTPUT); pinMode(GREEN_PIN, OUTPUT); pinMode(BLUE_PIN, OUTPUT);
  setRgb(false, false, false);
  const uint32_t now = millis();
  buttonA.begin(now); buttonB.begin(now); buttonC.begin(now);
  ready = initializeDisplay();
  if (!ready) return;
  enterState(WAIT_RELEASE, millis());
  drawQuiz();
}

void loop() {
  if (!ready) return;
  const uint32_t now = millis();
  buttonA.update(now); buttonB.update(now); buttonC.update(now);
  if (state == WAIT_RELEASE) {
    if (allReleased(now)) enterState(QUESTION, now);
  } else if (state == QUESTION) {
    const uint8_t rawPressed = static_cast<uint8_t>(!buttonA.rawHigh) +
                               static_cast<uint8_t>(!buttonB.rawHigh) +
                               static_cast<uint8_t>(!buttonC.rawHigh);
    if (rawPressed > 1) {
      // An observed chord is rejected, without any A/B/C priority.
      enterState(WAIT_RELEASE, now);
    } else {
      int8_t answer = -1;
      if (buttonA.pressed && buttonB.rawHigh && buttonC.rawHigh) answer = 0;
      else if (buttonB.pressed && buttonA.rawHigh && buttonC.rawHigh) answer = 1;
      else if (buttonC.pressed && buttonA.rawHigh && buttonB.rawHigh) answer = 2;
      if (answer >= 0) {
        answerCorrect = static_cast<uint8_t>(answer) == Questions[questionIndex].correctIndex;
        if (answerCorrect) ++score;
        enterState(FEEDBACK, now);
      }
    }
  } else if (state == FEEDBACK) {
    if (static_cast<uint32_t>(now - stateSince) >= FEEDBACK_MS) {
      if (questionIndex + 1 < QUESTION_COUNT) {
        ++questionIndex;
        enterState(WAIT_RELEASE, now);
      } else {
        restartReady = false;
        enterState(FINISHED, now);
      }
    }
  } else {
    // B/C do not restart, and any observed chord must release completely.
    if ((!buttonB.rawHigh || !buttonC.rawHigh) && restartReady) {
      restartReady = false;
      screenDirty = true;
    }
    if (!restartReady && allReleased(now)) {
      restartReady = true;
      screenDirty = true;
    }
    if (restartReady && buttonA.pressed && buttonB.rawHigh && buttonC.rawHigh) {
      score = questionIndex = 0;
      restartReady = false;
      enterState(WAIT_RELEASE, now);
    }
  }
  drawQuiz();
}

Preuzeti .ino i prikazani program treba da ostanu ista verzija. Najprije isprobaj pet pripremljenih pitanja, pa tek onda mijenjaj njihov sadržaj.

Kako program radi i šta pitanja provjeravaju

Niz Questions[] čuva tekst pitanja, tri opcije i correctIndex: A je indeks 0, B indeks 1, a C indeks 2. QUESTION_COUNT dobija se kao sizeof(Questions) / sizeof(Questions[0]), pa dodavanje zapisa ne zahtijeva ručnu izmjenu broja pet na više mjesta.

Pitanje na OLED-uA / B / CTačan odgovor i objašnjenje
GPIO logic level?3.3 V / 5 V / 12 VA. GPIO ovog ESP32 sklopa koristi logiku 3,3 V. USB napajanje od 5 V nije dozvoljeni logički nivo ulaza.
Button to GND is?HIGH / LOW / PWMB. Pritisnut taster povezuje ulaz na GND. Pull-up određuje otpušteno stanje.
ADC input here?GPIO34 / 3V3 pin / GND pinA. GPIO34 je analogni ulaz korišćen u ranijim vježbama; 3V3 i GND su naponske veze.
OLED data bus?UART / SPI / I2CC. Naš referentni OLED razmjenjuje podatke preko SDA/SCL. Drugi OLED moduli mogu koristiti drugačiji interfejs.
PIR detects?Air temp / Motion / HumidityB. U ovim vježbama PIR prijavljuje kretanje kroz promjene infracrvenog zračenja, bez mjerenja temperature vazduha ili vlažnosti.

WAIT_RELEASE uspostavlja uslov za novi odgovor. QUESTION dozvoljava jedan novi pritisak samo ako su drugi sirovi ulazi visoki. FEEDBACK prikazuje zelenu ili crvenu boju 1200 ms; rezultat se mijenja samo jednom pri prihvatanju tačnog odgovora. Slijedi naredno pitanje ili FINISHED. Završni ekran zadržava rezultat dok se ne prihvati novi pritisak A poslije otpuštanja svih tastera; B i C ne pokreću novi krug.

Tajmer povratne informacije koristi razliku vremena bez delay(). Završetak intervala ne pretvara držani taster u odgovor na sljedeće pitanje. drawQuiz() osvježava ekran samo kada se sadržaj promijeni, ali display.display() sinhrono prenosi sliku. Sam prenos 1024 bajta na 100 kHz traje računski oko 92 ms, uz dodatno vrijeme protokola. Vrlo kratak dodir tokom prenosa može biti propušten: koristi obične, namjerne pritiske. Interval se računa iz proteklog vremena, ne iz broja prikazanih slika.

Eksperiment i očekivani rezultati

PostupakOčekivano ponašanje
Otpuštaj tastere, zatim biraj A, B, A, C, B.Pet tačnih odgovora, zeleno poslije svakog, rezultat 5/5.
Na prvom pitanju izaberi C.Crvena boja 1200 ms; rezultat ostaje nula.
Drži A preko završetka povratne informacije.Sljedeće pitanje čeka otpuštanje; rezultat se ne povećava sam.
Pritisni dva tastera tako da program vidi oba pritisnuta.Nema odgovora ni kaznenog boda; otpusti sve prije novog izbora.
Resetuj dok držiš B.Kviz traži otpuštanje prije prvog odgovora.
Na kraju otpusti sve, pa pritisni A.Počinje novi krug sa rezultatom nula.

Upiši svoja opažanja, posebno redoslijed otpuštanja i pritisaka. Očekivanja u tabeli nisu unaprijed izvršena mjerenja niti rezultat fizičkog ispitivanja tvog sklopa.

Najčešći problemi

SimptomProvjera i postupak
Ekran je prazan, RGB ugašen.Provjeri VCC/GND, SDA/SCL, adresu i SSD1306 kontroler; pogledaj poruku u Serial Monitoru.
Tačan odgovor svijetli crveno ili plavo.Provjeri identifikaciju izvoda R/G/B i tri odgovarajuće grane.
Boje ostaju uključene kada bi morale biti ugašene.Uskladi COM spoj i COMMON_ANODE; ne mijenjaj samo program.
Kviz stalno traži otpuštanje.Provjeri taster koji ostaje na GND, pogrešan par kontakata ili kratki spoj.
Sljedeće pitanje ne reaguje na držani taster.Predviđeno ponašanje: otpusti sva tri, sačekaj potvrdu i napravi novi pritisak.

Samostalni izazovi

Dodaj šesto pitanje: „Common cathode to?” sa opcijama „GND”, „5 V”, „GPIO only”. Provjeri da ekran staje u raspoloživi prostor i da završni rezultat pokazuje ukupno šest pitanja. Zatim promijeni trajanje povratne informacije sa 1200 na 2000 ms, bez dodavanja delay().

Razrađeno rješenje izazova

U Questions[] poslije postojećeg posljednjeg zapisa dodaj zarez i ovaj zapis:

{"Common cathode to?", {"GND", "5 V", "GPIO only"}, 0}

Slijed tačnih odgovora postaje A, B, A, C, B, A, a završni rezultat 6/6. Ne unosi broj 1 za A: indeksi počinju od nule. Zadrži automatsko računanje broja elemenata niza.

Promijeni samo FEEDBACK_MS na 2000. Uslov i dalje poredi proteklo vrijeme sa intervalom. Produženje prikaza ne mijenja pravilo jednog boda niti zahtjev za otpuštanjem. Isprobaj i namjerno pogrešan šesti odgovor: ukupni broj mora biti šest, a broj bodova pet ako su ostali tačni.

Provjera znanja sa odgovorima

Zašto posmatramo i sirove nivoe drugih tastera? Drugi taster može već biti pritisnut, ali još nije prošlo njegovih 30 ms potvrde. Taj uočeni višestruki izbor ne treba prihvatiti kao jednoznačan odgovor.

Zašto je potrebna prepreka otpuštanja poslije svakog pitanja? Jedna fizička radnja pripada jednom pitanju. Držanje ili pritisak tokom povratne informacije ne smije se prenijeti na sljedeće.

Da li reset čuva rezultat? Ne. Rezultat je u radnoj memoriji; novi početak vraća ga na nulu. Da li netačan odgovor oduzima bod? Ne, samo ne dodaje novi bod.

Primarni izvori

Preuzimanja

Izaberi šemu za stvarni tip RGB diode. Stvarna ESP32 kompilacija, izvršavanje stvarnih biblioteka i proba na komponentama zasebni su koraci provjere; ovaj tekst ih ne predstavlja kao već izvršene.