/** * \file main.c * \brief Simple gaming board * * \subsection Copyright * Copyright (c) 2013-2022 Michael Buesch * * \subsection License * Licensed under the terms of the GNU General Public License version 2. */ #include "main.h" #include "ks0108.h" #include "output_extender.h" #include "util.h" #include "shr3.h" #include "buzzer.h" #if GAMEID == GAMEID_TETRIS # include "tetris.h" #endif #include #include #include /** Systemtimer tick-Merker. */ static uint8_t timer_tick; /** Tasterereignisse. */ static struct button_event_queue button_events; /** Tasten-Autofire. */ static uint8_t button_autofire_count[NR_BTN]; /** Schnittstelle zur Spiellogik. */ static struct game_interface *game_interface; /** Seed fuer Zufallszahlengenerator. */ static uint32_t EEMEM ee_rng_seed = RNG_SEED; /** \brief Taster Hardware auslesen. * * \return Gibt die Tasterbits zurueck. * Gesetztes Bit -> Taster gedrueckt. * Geloeschtes Bit -> Taster nicht gedrueckt. */ static uint8_t buttons_state_get(void) { return PINC ^ 0xFF; } /** \brief Taster initialisieren. */ static void buttons_init(void) { DDRC &= ~((1 << DDC0) | (1 << DDC1) | (1 << DDC2) | (1 << DDC3) | (1 << DDC4) | (1 << DDC5)); PORTC |= (1 << PC0) | (1 << PC1) | (1 << PC2) | (1 << PC3) | (1 << PC4) | (1 << PC5); } /** \brief Tasterereignisse bearbeiten. */ static void handle_button_events(void) { struct button_event event; uint8_t sreg; sreg = irq_disable_save(); while (button_events.nr_events) { /* Ereignis aus dem Ringpuffer entnehmen. */ event = button_events.events[button_events.read_index]; button_events.read_index++; if (button_events.read_index >= ARRAY_SIZE(button_events.events)) button_events.read_index = 0; button_events.nr_events--; irq_restore(sreg); /* Tastenereignis an das Spiel uebergeben. */ game_interface->handle_button(&event); sreg = irq_disable_save(); } irq_restore(sreg); } /** \brief Tasterereignis in Ringpuffer ablegen. * * \param button Die logische Taster-ID-Nummer. * * \param action Die logische Taster-Action-ID-Nummer. * * \param allocation Anzahl der Bufferplaetze, die im Ringpuffer frei sein muessen. */ static void button_ringbuf_put(enum button button, enum button_action action, uint8_t allocation) { struct button_event *ev; uint8_t sreg; sreg = irq_disable_save(); if (allocation <= ARRAY_SIZE(button_events.events) && button_events.nr_events <= ARRAY_SIZE(button_events.events) - allocation) { /* Ereignis in Ringpuffer schreiben. */ ev = &button_events.events[button_events.write_index]; button_events.write_index++; if (button_events.write_index >= ARRAY_SIZE(button_events.events)) button_events.write_index = 0; button_events.nr_events++; ev->button = button; ev->action = action; } irq_restore(sreg); } /** \brief Tasterereignis pruefen und in Ringpuffer ablegen. * * \param state Der Hardwarestatus des Tasters. * * \param pos_edge Der Hardwarestatus des Tasters: Steigende Flanke. * * \param neg_edge Der Hardwarestatus des Tasters: Fallende Flanke. * * \param mask Die Bitmaske des Tasters. * * \param button Die logische Taster-ID-Nummer. */ static void check_button_event(uint8_t state, uint8_t pos_edge, uint8_t neg_edge, uint8_t mask, enum button button) { uint8_t autofire_delay; autofire_delay = game_interface->get_autofire_delay(button); if (autofire_delay > 0) { /* Autofire-Modus. */ if (state & mask) { if (button_autofire_count[button] == 0) { button_ringbuf_put(button, BTNACT_PRESSED, 2); button_ringbuf_put(button, BTNACT_RELEASED, 1); button_autofire_count[button] = autofire_delay - 1; } else { button_autofire_count[button]--; } } else { button_autofire_count[button] = 0; } } else { /* Flankenmodus. */ if (pos_edge & mask) button_ringbuf_put(button, BTNACT_PRESSED, 1); if (neg_edge & mask) button_ringbuf_put(button, BTNACT_RELEASED, 1); button_autofire_count[button] = 0; } } /** \brief Tasterhardware auslesen. */ static void buttons_hardware_read(void) { uint8_t state, pos_edge, neg_edge; static uint8_t prev_state; /* Tasterhardware auslesen. */ state = buttons_state_get(); /* Ungenutzte Bits loeschen */ state &= ((1 << BTNBIT_UP) | (1 << BTNBIT_LEFT) | (1 << BTNBIT_RIGHT) | (1 << BTNBIT_DOWN) | (1 << BTNBIT_A) | (1 << BTNBIT_B)); /* Flankenerkennung. */ pos_edge = state & ~prev_state; neg_edge = ~state & prev_state; prev_state = state; /* "UP" Taster auswerten. */ check_button_event(state, pos_edge, neg_edge, (1 << BTNBIT_UP), BTN_UP); /* "LEFT" Taster auswerten. */ check_button_event(state, pos_edge, neg_edge, (1 << BTNBIT_LEFT), BTN_LEFT); /* "RIGHT" Taster auswerten. */ check_button_event(state, pos_edge, neg_edge, (1 << BTNBIT_RIGHT), BTN_RIGHT); /* "DOWN" Taster auswerten. */ check_button_event(state, pos_edge, neg_edge, (1 << BTNBIT_DOWN), BTN_DOWN); /* "A" Taster auswerten. */ check_button_event(state, pos_edge, neg_edge, (1 << BTNBIT_A), BTN_A); /* "B" Taster auswerten. */ check_button_event(state, pos_edge, neg_edge, (1 << BTNBIT_B), BTN_B); } /** \brief Systemtimer - 100Hz. */ ISR(TIMER2_COMP_vect) { mb(); /* Systemtick inkrementieren. */ timer_tick++; /* Taster auslesen und verarbeiten. */ buttons_hardware_read(); /* Periodische Summerfunktion aufrufen. */ buzzer_100hz_tick(); mb(); } /** \brief Systemtimer initialisieren. */ static void systemtimer_init(void) { /* Timer 2 auf 100 Hz Interrupt initialisieren. */ OCR2 = 156; TCNT2 = 0; TCCR2 = (1 << WGM21) | (1 << CS20) | (1 << CS21) | (1 << CS22); TIMSK |= (1 << OCIE2); } /** \brief Zufallszahlengenerator initialisieren. */ static void rng_init(void) { uint32_t seed; seed = eeprom_read_dword(&ee_rng_seed); eeprom_update_dword(&ee_rng_seed, seed + 1u); shr3_init(seed); } /** \brief Hauptprogramm. */ int main(void) { uint8_t nr_ticks; irq_disable(); /* System initialisieren. */ rng_init(); extout_init(); ks0108_init(); buttons_init(); systemtimer_init(); buzzer_init(); /* Spiel initialisieren. */ #if GAMEID == GAMEID_TETRIS game_interface = &tetris_game_interface; #else # error "Unknown game type." #endif game_interface->init(); irq_enable(); while (1) { /* Tasten verarbeiten und ggf. an Spiel uebergeben. */ handle_button_events(); /* Tick-Merker auslesen */ irq_disable(); nr_ticks = timer_tick; timer_tick = 0; irq_enable(); /* Ticks an Spiel uebergeben. */ while (nr_ticks--) game_interface->do_100hz_tick(); /* Periodische Aufgaben durchfuehren. */ game_interface->periodic_work(); } }