Adrian Ariton: Joc Dezamorsare Bomba

Introducere

https://github.com/adrianariton/PMProj

Updated project: https://youtube.com/shorts/TrdBkxlq2JE?feature=share

Old version: https://youtube.com/shorts/uZILq4yrm3E?feature=share

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.

Descriere generală

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.

 Schema Electrica Proiect  Submodul Simon
 Submodul Wire Cutting  Modul Amprenta + Sunet

Hardware Design

Aici puneţi tot ce ţine de hardware design:

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

Schema Logica Hardware

 Schema HW

PCB

 PCB

Legenda:

- U1: BMP190 (Temp + Presiune) I2C

- U3: MPU6500 (Giroscop) I2C

Software Design

Descrierea codului aplicaţiei (firmware):

  • mediu de dezvoltare (if any) (e.g. AVR Studio, CodeVisionAVR)
  • librării şi surse 3rd-party (e.g. Procyon AVRlib)
  • algoritmi şi structuri pe care plănuiţi să le implementaţi
  • (etapa 3) surse şi funcţii implementate

Descriere

Deignuit pe Platformio: VSCode.

Biblioteci:

* driver/dac.h - pt sunet

* TFT_eSPI.h - pt display

* Arduino.h - pt ft putine lucruri (setup)

Code Snippets

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.

Senzorii de giroscop si temperatura sunt implementati cu drivere programate de mine dupa datasheets.

E.g. (girocop):

// Calculate B5 value from the datasheet
int32_t computeB5(int32_t UT) {
  int32_t X1 = (UT - (int32_t)ac6) * ((int32_t)ac5) >> 15;
  int32_t X2 = ((int32_t)mc << 11) / (X1 + (int32_t)md);
  return X1 + X2;
}

float readTemperature() {
  int32_t UT = readRawTemperature();
  int32_t B5 = computeB5(UT);
  
  // Temperature in units of 0.1 deg C
  float temp = ((B5 + 8) >> 4);
  
  // Convert to degrees C
  return temp / 10.0;
}

uint32_t readRawPressure() {
  write8(BMP180_CONTROL, BMP180_READPRESSURECMD + (oversampling << 6));
  
  // Wait for conversion based on oversampling setting
  if (oversampling == BMP180_ULTRALOWPOWER)
    delay(5);
  else if (oversampling == BMP180_STANDARD)
    delay(8);
  else if (oversampling == BMP180_HIGHRES)
    delay(14);
  else
    delay(26);
  
  uint32_t MSB = read8(BMP180_PRESSUREDATA);
  uint32_t LSB = read8(BMP180_PRESSUREDATA + 1);
  uint32_t XLSB = read8(BMP180_PRESSUREDATA + 2);
  
  // Combine readings with proper shifting based on oversampling
  uint32_t raw = ((MSB << 16) + (LSB << 8) + XLSB) >> (8 - oversampling);
  return raw;
}

int32_t readPressure() {
  // Read raw temperature value first
  int32_t UT = readRawTemperature();
  // Then read raw pressure value
  int32_t UP = readRawPressure();
  
  // Temperature compensation
  int32_t B5 = computeB5(UT);
  
  // Do pressure calculations (straight from Adafruit code)
  int32_t B6 = B5 - 4000;
  int32_t X1 = ((int32_t)b2 * ((B6 * B6) >> 12)) >> 11;
  int32_t X2 = ((int32_t)ac2 * B6) >> 11;
  int32_t X3 = X1 + X2;
  int32_t B3 = ((((int32_t)ac1 * 4 + X3) << oversampling) + 2) / 4;
  
  X1 = ((int32_t)ac3 * B6) >> 13;
  X2 = ((int32_t)b1 * ((B6 * B6) >> 12)) >> 16;
  X3 = ((X1 + X2) + 2) >> 2;
  uint32_t B4 = ((uint32_t)ac4 * (uint32_t)(X3 + 32768)) >> 15;
  uint32_t B7 = ((uint32_t)UP - B3) * (uint32_t)(50000UL >> oversampling);
  
  int32_t p;
  if (B7 < 0x80000000) {
    p = (B7 * 2) / B4;
  } else {
    p = (B7 / B4) * 2;
  }
  
  X1 = (p >> 8) * (p >> 8);
  X1 = (X1 * 3038) >> 16;
  X2 = (-7357 * p) >> 16;
  p = p + ((X1 + X2 + (int32_t)3791) >> 4);
  
  return p; // Pressure in Pa
}

// Calculate altitude based on atmospheric pressure
float readAltitude(float seaLevelPressure) {
  float pressure = readPressure();
  float altitude = 44330 * (1.0 - pow(pressure / seaLevelPressure, 0.1903));
  return altitude;
}
Dificultati:

* am setat frecventa la ceasul de transmisie i2c mai jos pt a compensa rezistentele de pullup puse in paralel

Rezultate Obţinute

Care au fost rezultatele obţinute în urma realizării proiectului vostru.

* Jocuri calibrabile si modificabile

* Un workflow nedependent de main loop

Concluzii

Download

O arhivă (sau mai multe dacă este cazul) cu fişierele obţinute în urma realizării proiectului: surse, scheme, etc. Un fişier README, un ChangeLog, un script de compilare şi copiere automată pe uC crează întotdeauna o impresie bună ;-).

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.

https://github.com/adrianariton/PMProj

Jurnal

Puteți avea și o secțiune de jurnal în care să poată urmări asistentul de proiect progresul proiectului.

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)

Bibliografie/Resurse

Listă cu documente, datasheet-uri, resurse Internet folosite, eventual grupate pe Resurse Software şi Resurse Hardware.

Export to PDF

pm/prj2025/cmoarcas/adrian.ariton.txt · Last modified: 2025/05/25 22:21 by adrian.ariton
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0