Table of Contents

Joc Whac-A-Mole

Autor: Mușat-Mare Cristian-Cosmin

Grupa: 332CD

Îndrumător: Florin Stancu

Introducere

Proiectul presupune crearea unei variante simplificate a jocului clasic Whac-A-Mole. Cârtițele vor fi reprezentate de LED-uri care se vor aprinde la intervale din ce în ce mai mici, pe măsura ce jocul progresează, și vor avea un timeout la expirarea căruia se vor stinge, astfel jucătorul ratând șansa de a-și crește scorul. LED-urile vor avea asociate câte un buton care va fi acționat prin apăsare, ducând astfel la stingerea LED-ului asociat, respectiv omorârea cârtiței. Scorul va fi calculat în funcție de reacția cât mai rapidă a jucătorului (cu cât omoară cârtițele mai repede cu atât scorul va fi mai mare) și va fi afișat pe un ecran LCD.

Arhitecură

Schema bloc

Pentru a acționa LED-urile vor fi folosite timere și întreruperi.

De asemenea, pentru a contoriza scorul, se va măsura tensiunea la buton printr-un pin analogic și cu ajutorul ADC.

Pentru a afișa scorul pe LCD se va folosi protocolul SPI.

Design Hardware

Schema electrică

Componente

Design Software

#include <avr/io.h>
#include <avr/interrupt.h>
#include <stdlib.h>
#include <stdbool.h>
#include <SPI.h>
#include <Adafruit_I2CDevice.h>
#include <Adafruit_ST7735.h>

#define NO_LED 4
#define TFT_CS 10
#define TFT_RST 8
#define TFT_DC 9
#define TIMEOUT 2.490368

Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS, TFT_DC, TFT_RST);

double score = 0;
volatile bool led_on = false;
volatile bool timeout_occurred = false;
volatile int sel;
int look_at_voltage = -1;
volatile int timer2_ovf_count = 0;
volatile int timer0_ovf_count = 0;
volatile int timer2_total_ticks = 0;

void init_display() {
    tft.initR(INITR_BLACKTAB);
    tft.setTextColor(ST7735_WHITE);
    tft.fillScreen(ST7735_BLACK);
    tft.setTextSize(2);
    tft.setRotation(2);
}

ISR(TIMER1_COMPA_vect) {
    if (led_on == false) {
        timer2_total_ticks = 0;
        timer2_ovf_count = 0;
        TCNT2 = 0;
        sel = rand() % NO_LED + 2;
        PORTD |= (1 << sel);
        led_on = true;
        timeout_occurred = false;
        look_at_voltage = -1;
    }
}

ISR(TIMER1_COMPB_vect) {
    tft.fillRect(40, 20, 60, 20, ST7735_BLACK);
}

ISR(TIMER2_OVF_vect) {
    timer2_total_ticks += 256;
    timer2_ovf_count++;
    if (timer2_ovf_count == 76 && led_on == true) {
        led_on = false;
        timeout_occurred = true;
        look_at_voltage = -1;
        PORTD &= ~(1 << sel);
    }
}

ISR(TIMER0_COMPA_vect) {
  timer0_ovf_count++;
  if (timer0_ovf_count == 611) {
    if (OCR1A - 3906 > 15625) {
        OCR1A = OCR1A - 3906;
    }
    else {
        OCR1A = 46875;
    }
    TCNT1 = min(TCNT1, OCR1A - 1);
    timer0_ovf_count = 0;
  }
}

void my_setup() {
    DDRD |= (1 << PD3);
    DDRD |= (1 << PD2);
    DDRD |= (1 << PD4);
    DDRD |= (1 << PD5);

    TCCR1A = 0; 
    TCCR1B = 0;
    TCNT1 = 0;
    OCR1A = 46875;
    OCR1B = 15625;
    TCCR1B |= (1 << WGM12);
    TCCR1B |= (1 << CS12) | (1 << CS10);
    TIMSK1 |= (1 << OCIE1A) | (1 << OCIE1B);

    TCCR2A = 0;
    TCCR2B = 0;
    TCNT2 = 0;
    TCCR2B |= (1 << CS22) | (1 << CS21) | (1 << CS20);
    TIMSK2 |= (1 << TOIE2);

    TCCR0A = 0;
    TCCR0B = 0;
    TCNT0 = 0;
    OCR0A = 255;
    TCCR0A |= (1 << WGM01);
    TCCR0B |= (1 << CS02) | (1 << CS00);
    TIMSK0 |= (1 << OCIE0A);
}

void adc_init(int port) {
    ADMUX |= (1 << REFS0);
    ADCSRA |= (1 << ADEN);
    ADCSRA |= (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0);
    ADMUX &= ~(1 << MUX0);  
    ADMUX &= ~(1 << MUX1);  
    ADMUX &= ~(1 << MUX2);  
    ADMUX &= ~(1 << MUX3);
    if (port == 0) {
    }
    else if (port == 1) {
        ADMUX |= (1 << MUX0);
    }
    else if (port == 2) {
        ADMUX |= (1 << MUX1);
    }
    else if (port == 3) {
        ADMUX |= (1 << MUX1) | (1 << MUX0);
    }
    else if (port == 4) {
        ADMUX |= (1 << MUX2);
    }
    else if (port == 5) {
        ADMUX |= (1 << MUX2) | (1 << MUX0);
    }
}

int adc_read() {
    ADCSRA |= (1 << ADSC);
    while ((ADCSRA & (1 << ADSC)));
    int res = ADC;
    return res;
}

void my_loop() {
    for (int analog_pin = 0; analog_pin < NO_LED; analog_pin++) {
        adc_init(analog_pin);
        int analog_in = adc_read();
        int v_in = analog_in * (5.0 / 1024.0);
        if (v_in > 0) {
            look_at_voltage = analog_pin;
        }

        int voltage_to_check = -1;
        if (look_at_voltage == analog_pin) {
            voltage_to_check = v_in;
        } 
        if (voltage_to_check == 0 && led_on == true && !timeout_occurred) {
            timer2_total_ticks += TCNT2;
            TCNT1 = 0;
            PORTD &= ~(1 << sel);
            led_on = false;
            look_at_voltage = -1;
            double react_time = 0.000064 * timer2_total_ticks;
            double percent = react_time / TIMEOUT;
            score += (1 - percent);
            tft.setCursor(40, 20);
            tft.print("+");
            tft.print(1 - percent);
            tft.setCursor(40, 40);
            tft.print("SCORE");
            tft.fillRect(40, 60, 88, 128, ST7735_BLACK);
            tft.setCursor(40, 60);
            tft.print(score);
        }
    }
}

int main(void) {
    init();
    init_display();
    sei();
    my_setup();
    while (1) {
        my_loop();
    }
    return 0;
}

Pentru a interfața cu LCD-ul am folosit o bibliotecă built-in Adafruit_ST7735.h.
Timer 0 are rolul de a controla durata dintre două aprinderi succesive ale LED-urilor, inițial la 3 secunde, dar la fiecare 10 secunde, cu 0.25 secunde mai puțin până ce se ajunge la o secundă, când se resetează la durata inițială de 3 secunde.
Timer 2 are rolul de a stinge LED-ul aprins după 1.5 secunde în cazul în care butonul asociat lui nu a fost apăsat.
Timer 1 are rolul de a aprinde LED-ul pe canalul A și de a afișa scorul obținut la ultima apăsare pe canalul B.
Scorul obținut la ultima apăsare se calculează în funcție de numărul total de ticks ale Timer 2 la momentul apăsării butonului, iar scorul total este afișat în permanență și actualizat atunci când este cazul.
Pentru a verifica că un LED a fost stins datorită apăsării butonului sau am folosit ADC pentru a măsura tensiunea la buton.