This is an old revision of the document!
Proiectul “Bomb Defusal Challenge” este un joc fizic interactiv inspirat de “Keep Talking and Nobody Explodes”.
Jucătorii trebuie să dezamorseze o bombă virtuală prin rezolvarea mai multor mini-jocuri (module) înainte ca timerul să ajungă la zero.
Ceea ce face acest proiect special este integrarea hardware-ului real - senzori, LED-uri, butoane și afișaje - pentru a crea o experiență tactilă autentică.
Scopul proiectului este de a crea o experiență de joc cooperativă, care necesită comunicare clară, logică și abilitatea de a lucra sub presiune. Ideea de bază este de a transforma un joc digital popular într-o experiență fizică interactivă folosind componente electronice accesibile.
Acest proiect este util ca exercițiu practic de electronică și programare, demonstrând cum pot fi integrate multiple tehnologii într-un singur sistem coerent. Pentru jucători, oferă o experiență de joc unică și antrenantă care combină elemente digitale cu cele fizice.
Componente principale:
Unitatea centrală de control (ESP32)
- Gestionează logica jocului, timerele și comunicarea între module - Afișează informații pe ecranul TTGO T-Display - Generează sunetele pentru alarmă și feedback
Modulul Giroscop/Accelerometru (MPU-6500) compatibil I2C
- Detectează orientarea și mișcarea “bombei” - Permite activarea “gimmick-ului” care necesită întoarcerea bombei când alarma sună
Modulul Simon Says Go
- Semaforul cu 3 LED-uri (roșu, galben, verde) - LED-ul “Simon” care indică când trebuie apăsat un buton - 3 butoane tactile pentru interacțiunea jucătorului
Modulul Wire Cutting
- 3 conexiuni de fire detașabile - Sistem de detecție pentru identificarea firului “tăiat” - Logică pentru determinarea firului corect bazată pe alte condiții
Modulul Bomb Disarm Code (Beta)
- Senzor de presiune BMP180 compatibil I2C utilizat ca “fingerprint scanner” - Senzor de temperatură DS18B20 pentru măsurarea căldurii degetului - Senzor de sunet pentru detectarea parolei vocale șoptite/fluierate - LED-uri pentru feedback vizual
Sistem de alarmă și feedback
- Modul amplificator audio LM386 pentru alarmă și efecte sonore - LEDuri pentru indicarea statusului (timpul rămas, greșeli)
Toate aceste module comunică cu unitatea centrală ESP32, care coordonează logica jocului și afișează informații relevante pe ecran.
| Nume | Descriere |
|---|---|
| Placă TTGO T-Display ESP32 | Controler pentru module și unitatea centrală |
| Modul MPU-6500 (accelerometru + giroscop) | Detectarea orientării bombei |
| Modul senzor sunet LM393 | Detectarea parolei vocale |
| Senzor BMP180 | Măsurarea presiunii pentru “fingerprint scanner” |
| Senzor DS18B20 | Măsurarea temperaturii degetului |
| Modul amplificator audio LM386 | Sistem de alarmă și efecte sonore |
| Conectori XH2.54 | Conexiuni pentru module |
| Module cu 3 LED-uri (R, Y, G) | Semafor pentru Simon Says și indicatori |
| Switch-uri toggle | Controale și interacțiuni |
| Senzor distanță HC-SR04P | - |
| LED-uri verzi 5mm | Indicatori de stare |
| LED-uri galbene 5mm | Indicatori de stare |
| LED-uri roșii 3mm | Indicatori de eroare |
| Butoane rotunde | Interfața pentru interacțiuni |
| Condensatoare + Rezistoare (diverse) | Filtrare și stabilizare circuite |
| Mini breadboarduri | Montarea circuitelor |
| Fire de conexiune | Conectarea componentelor |
Deignuit pe Platformio: VSCode.
Biblioteci:
* driver/dac.h - pt sunet
* TFT_eSPI.h - pt display
* Arduino.h - pt ft putine lucruri (setup)
Timer loop:
Timer loopul se triggeruieste la fiecare tick, si mentine starea jocului prin urmatorul loop:
void IRAM_ATTR onTimer() {
portENTER_CRITICAL_ISR(&timerMux);
myMillis++;
if (myMillis % 200 == 0) {
if (is_active(GAME_SIMON_SAYS)) {
if (simon_can_do_next_round){
simon_last_round_begin_milis = myMillis;
simon_can_do_next_round = false;
simon_round_ongoing = true;
}
int deltaMillisTilStart = (myMillis - simon_last_round_begin_milis);
if ((myMillis - simon_last_round_begin_milis) <= simon_round_intro_time_delay_milis) {
// round pending
if (deltaMillisTilStart % 1000 == 0)
simonOnRoundPrepareTick((simon_round_intro_time_delay_milis - deltaMillisTilStart) / 1000);
} else {
if (!simon_semaphore_started){
simonOnRoundStart();
simon_semaphore_started = true;
}
int time_since_round_semaphore_start = deltaMillisTilStart - simon_round_intro_time_delay_milis;
int tick_cnt = time_since_round_semaphore_start / simon_time_delay_between_colors;
if (time_since_round_semaphore_start % simon_time_delay_between_colors == 0) {
if (tick_cnt <= simon_size_of_colors)
simonOnRoundStartTick(tick_cnt);
else {
if (!simon_can_begin_pressing){
simonOnGameEnd();
}
simon_can_begin_pressing = true;
}
}
}
} else if (is_active(GAME_WIRE_CUTTING)) {
if (!wire_cut_game_started){
wireCutOnStart();
wire_cut_game_started = true;
wire_cut_game_start_time = myMillis;
}
if (wire_cut_game_started) {
int deltaMilisSinceWireCutStart = (myMillis - wire_cut_game_start_time);
if (deltaMilisSinceWireCutStart % 1000 == 0) {
int ticks = deltaMilisSinceWireCutStart / 1000;
if (ticks < wire_cut_game_duration_seconds)
wireCutOnGameTick(wire_cut_game_duration_seconds - ticks);
else if (!wire_cut_game_ended) {
wire_cut_game_ended = true;
wire_cut_game_won = false;
}
}
}
} else if (is_active(GAME_PASS_UNLOCK)) {
if (!pass_game_started) {
pass_game_started = true;
pass_game_start_time = myMillis;
passUnlockOnStart();
}
if (pass_game_started) {
int dm = (myMillis - pass_game_start_time);
if (dm % 1000 == 0) {
int ticks = dm / 1000;
if (ticks < pass_game_time_limit_seconds)
passUnlockOnGameTick(pass_game_time_limit_seconds - ticks);
else if (!pass_game_ended) {
pass_game_ended = true;
pass_game_won = false;
}
}
}
}
}
portEXIT_CRITICAL_ISR(&timerMux);
}
* Fiecare callback este implemetat:
- Simon Games callbacks defns & vars:
int simon_size_of_colors = 3; char simonBufferSemaphoreSize = 0; char simonBufferCompareIdx = 0; void simonOnRoundPrepareTick(int tick_count); void simonOnRoundStart(); void simonOnRoundStartTick(int tick_count); void simonOnGameEnd();
- Wire Cut callbacks defns & vars:
volatile int wire_cut_game_duration_seconds = 20; volatile int wire_cut_game_target_wire = -1; void wireCutOnStart(); void wireCutOnGameTick(int ticks_left); void wireCutOnEnd(bool won);
- Finger scan callbacks defns & vars:
int pass_env_temperatures_read_interv_millis = 1000; // pt calibrarea temperaturii de mediu const int pass_env_temperatures_read_limit = 10; const float pass_temp_threshold_celsius = 0.5; volatile int pass_game_time_limit_seconds = 10; void passUnlockOnStart(); void passUnlockOnGameTick(int ticks_left); void passUnlockOnEnd(bool won);
! Singura folosire a delay-ului implementat nu de mine (custom) e in functia de playNote care ajuta la audio.
const int dacPin = 25; // DAC1 - GPIO25 const int frequency = 1000; // tone frequency Hz const int duration = 100; // duration in ms
void playTone(int freq, int dur_ticks=1) {
int samplesPerCycle = 100; // number of samples per waveform cycle
int sampleDelayUs = 1000000 / (freq * samplesPerCycle);
int amplitude = 64; // 8-bit DAC mid-range (0-255)
int dur_ms = dur_ticks * duration;
unsigned long endTime = millis() + dur_ms;
while (millis() < endTime) {
for (int i = 0; i < samplesPerCycle; i++) {
// Generate a sine wave sample (scaled 0-255)
float sineVal = sin(2 * PI * i / samplesPerCycle);
uint8_t dacVal = (uint8_t)(amplitude + amplitude * sineVal);
dac_output_voltage(DAC_CHANNEL_1, dacVal);
delayMicroseconds(sampleDelayUs);
}
}
dac_output_voltage(DAC_CHANNEL_1, 128); // center output (silence)
}
care nu e inclus in cele 3 laboratoare.
I2C ul se face pe pinii 26 si 27, pt ca pinul 25 e folosit pt DAC. Acesta e conectat atat la senzorl giroscopic cat si la cel de temperatura.
Fişierele se încarcă pe wiki folosind facilitatea Add Images or other files. Namespace-ul în care se încarcă fişierele este de tipul :pm:prj20??:c? sau :pm:prj20??:c?:nume_student (dacă este cazul). Exemplu: Dumitru Alin, 331CC → :pm:prj2009:cc:dumitru_alin.
Laburi folosite:
- GPIO
- Timere (flow-ul jocului)
- Intreruperi (butoane/fire)
- I2C (senzorii de temp si de giroscop comunica in tandem pe magistrala SDA SCL conectate la Pinii 25 si 26)