The project is structured around the ATmega328p XMINI microcontroller, which coordinates all hardware and software modules.
Hardware modules:
Software modules:
| Component | Quantity | Details |
|---|---|---|
| ATmega328p XMINI | 1 | Main development board |
| OLED display | 1 | 0.96” SSD1306, I2C, 128x64px |
| LED red | 3 | 5mm, countdown signal |
| LED blue | 2 | 5mm, winner indicator |
| Resistor 220Ω | 5 | Current limiting for LEDs |
| Push button | 2 | 12x12mm tactile with round cap |
| Breadboard | 1 | 830 points |
| Jumper wires | 19 | Male-to-male |
| Pin | Component | Role |
|---|---|---|
| PB0 | LED1 red + 220Ω | Countdown LED 1 |
| PB1 | LED2 red + 220Ω | Countdown LED 2 |
| PB2 | LED3 red + 220Ω | Countdown LED 3 |
| PB3 | LED4 blue + 220Ω | Winner indicator Player 1 |
| PB4 | LED5 blue + 220Ω | Winner indicator Player 2 |
| PB7 | SW0 (on-board) | Start / restart game |
| PD2 | Button Player 1 | External interrupt INT0 |
| PD3 | Button Player 2 | External interrupt INT1 |
| PC4 (SDA) | OLED display | I2C data |
| PC5 (SCL) | OLED display | I2C clock |
| VCC | OLED display | 3.3V - 5V power |
| GND | All components | Common ground |
The game follows a fixed sequence of states:
The firmware was developed using PlatformIO integrated into Visual Studio Code, targeting the ATmega328P with the Arduino framework (used only as a build system — all peripheral control is done via direct AVR register access, without any Arduino library functions).
Upload is done via the xplainedmini protocol using avrdude, over USB.
No external libraries were used. All drivers were implemented from scratch using AVR-libc headers only:
<avr/io.h> — register definitions<avr/interrupt.h> — ISR macro and sei()/cli()<util/delay.h> — _delay_ms() for debouncingThe SSD1306 OLED driver, TWI/I2C driver, USART driver, and timer modules were written entirely by hand, inspired by the laboratory implementations provided during the PM course.
| File | Role |
|---|---|
src/main.cpp | Game logic, state machine, ISR definitions |
src/uptime.cpp | Timer2 CTC — 1ms system tick counter |
src/timers.cpp | Timer1 CTC — reaction time measurement |
src/twi.cpp | I2C/TWI driver using AVR hardware TWI module |
src/ssd1306.cpp | SSD1306 OLED driver with 5×7 bitmap font |
src/usart.cpp | UART driver for serial debug output |
include/uptime.h | uptime_ms() declaration |
include/timers.h | Timer1_init/start/stop/get_ms declarations |
include/twi.h | TWI function declarations and frequency config |
include/ssd1306.h | SSD1306 function declarations |
include/usart.h | USART function declarations |
Game state machine
The game runs as a sequential state machine inside the main loop:
IDLE — display shows “READY”, waiting for SW0 (PB7) pressCOUNTDOWN — LED1, LED2, LED3 light up one by one at 1s intervals using non-blocking timing via uptime_ms(). External interrupts INT0/INT1 are active during this phase to detect false starts.RANDOM DELAY — all 3 LEDs remain on for a random interval (1–3 seconds), generated via rand() seeded with uptime_ms()GO — LEDs turn off, Timer1 starts counting reaction timeRESULT — first player to press wins. If a false start was detected, the offending player is disqualified. Results are shown on OLED and serial.Non-blocking timing
All delays during the countdown and random delay phases use the pattern:
uint32_t t = uptime_ms(); while ((uptime_ms() - t) < 1000 && !false_start_p1 && !false_start_p2);
This allows the main loop to remain responsive to false start interrupts even while waiting.
False start detection
External interrupts INT0 (PD2) and INT1 (PD3) are configured for falling edge
detection. ISRs set volatile flags only if game_started == 0:
ISR(INT0_vect) { if (!game_started) false_start_p1 = 1; } ISR(INT1_vect) { if (!game_started) false_start_p2 = 1; }
Reaction time measurement
Timer1 is initialized in CTC mode at 16MHz with prescaler 8, generating an
interrupt every 1ms (OCR1A = 1999). A volatile counter reaction_ticks
increments in the ISR. Timer1_start() resets and enables it; Timer1_get_ms()
reads the counter atomically using cli()/sei().
I2C communication
The TWI hardware module is configured for 100kHz SCL frequency. The SSD1306 driver sends initialization commands on startup, then writes character bitmaps page by page using twi_start/twi_write/twi_stop sequences.
OLED font rendering
Characters are stored as a 5×7 bitmap font in Flash memory (PROGMEM) for 58 ASCII characters (space through Z). Each character is 5 bytes wide plus 1 byte spacing. ssd1306_print_str() renders a string at a specified page row (0–7).
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.