This shows you the differences between two versions of the page.
|
pm:prj2025:cmoarcas:adrian.ariton [2025/05/13 11:32] adrian.ariton [Jurnal] |
pm:prj2025:cmoarcas:adrian.ariton [2025/05/25 22:21] (current) adrian.ariton |
||
|---|---|---|---|
| Line 2: | Line 2: | ||
| ===== Introducere ===== | ===== Introducere ===== | ||
| + | <html><iframe width="560" height="315" src="https://www.youtube.com/embed/TrdBkxlq2JE?si=ej5ocPEVzuya_28Z" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe></html> | ||
| + | |||
| + | 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". | Proiectul **"Bomb Defusal Challenge"** este un joc fizic interactiv inspirat de "Keep Talking and Nobody Explodes". | ||
| Line 94: | Line 103: | ||
| - | ===== Schema Logica Hardware ===== | + | ==== Schema Logica Hardware ==== |
| {{pm:prj2025:cmoarcas:adi_schema.png?500 | Schema HW}} | {{pm:prj2025:cmoarcas:adi_schema.png?500 | Schema HW}} | ||
| - | ===== PCB ===== | + | ==== PCB ==== |
| {{pm:prj2025:cmoarcas:pcbadi.png?500 | PCB}} | {{pm:prj2025:cmoarcas:pcbadi.png?500 | PCB}} | ||
| Line 105: | Line 114: | ||
| - U1: BMP190 (Temp + Presiune) I2C | - U1: BMP190 (Temp + Presiune) I2C | ||
| + | |||
| - U3: MPU6500 (Giroscop) I2C | - U3: MPU6500 (Giroscop) I2C | ||
| Line 117: | Line 127: | ||
| * (etapa 3) surse şi funcţii implementate | * (etapa 3) surse şi funcţii implementate | ||
| </note> | </note> | ||
| + | |||
| + | === 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 ===== | ===== Rezultate Obţinute ===== | ||
| Line 123: | Line 383: | ||
| Care au fost rezultatele obţinute în urma realizării proiectului vostru. | Care au fost rezultatele obţinute în urma realizării proiectului vostru. | ||
| </note> | </note> | ||
| + | |||
| + | * Jocuri calibrabile si modificabile | ||
| + | |||
| + | * Un workflow nedependent de main loop | ||
| ===== Concluzii ===== | ===== Concluzii ===== | ||
| Line 134: | Line 398: | ||
| </note> | </note> | ||
| + | |||
| + | https://github.com/adrianariton/PMProj | ||
| ===== Jurnal ===== | ===== Jurnal ===== | ||