#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include <stdlib.h>
#include <string.h>
#define GRID_WIDTH 16
#define GRID_HEIGHT 2
#define MAX_SNAKE_LENGTH 32
// LCD
#define LCD_RS_PORT PORTB
#define LCD_RS_PIN 4
#define LCD_EN_PORT PORTB
#define LCD_EN_PIN 3
#define LCD_D4_PORT PORTD
#define LCD_D4_PIN 5
#define LCD_D5_PORT PORTD
#define LCD_D5_PIN 4
#define LCD_D6_PORT PORTD
#define LCD_D6_PIN 3
#define LCD_D7_PORT PORTD
#define LCD_D7_PIN 2
typedef struct {
unsigned char x;
unsigned char y;
} Position;
typedef struct {
Position body[MAX_SNAKE_LENGTH];
unsigned char length;
unsigned char direction;
unsigned char nextDirection;
} Snake;
volatile unsigned long tick_ms = 0;
volatile unsigned char button_left = 0;
volatile unsigned char button_right = 0;
volatile unsigned char button_reset = 0;
// LCD INIT STATE
volatile unsigned char lcd_init_state = 0;
volatile unsigned long lcd_init_timer = 0;
Snake snake;
Position food_pos;
int score = 0;
unsigned int game_speed = 300;
unsigned long last_move_time = 0;
unsigned char game_running = 0;
unsigned int adc_value = 0;
// TIMER
void timer_init() {
TCCR1A = 0;
TCCR1B = (1 << WGM12) | (1 << CS11);
OCR1A = 1999; // 1ms
TIMSK1 = (1 << OCIE1A);
}
ISR(TIMER1_COMPA_vect) {
tick_ms++;
}
// UART
void uart_init(unsigned int baud) {
unsigned int ubrr = (F_CPU / (16UL * baud)) - 1;
UBRR0H = (unsigned char)(ubrr >> 8);
UBRR0L = (unsigned char)ubrr;
UCSR0B = (1 << RXEN0) | (1 << TXEN0);
UCSR0C = (1 << UCSZ01) | (1 << UCSZ00);
}
void uart_send_char(char c) {
while (!(UCSR0A & (1 << UDRE0)));
UDR0 = c;
}
void uart_send_string(const char *str) {
while (*str) {
uart_send_char(*str++);
}
}
unsigned char uart_available() {
return (UCSR0A & (1 << RXC0));
}
char uart_read_nonblocking() {
if (uart_available()) {
return UDR0;
}
return 0;
}
// LCD
void lcd_pulse_enable() {
LCD_EN_PORT |= (1 << LCD_EN_PIN);
_delay_us(1);
LCD_EN_PORT &= ~(1 << LCD_EN_PIN);
_delay_us(100);
}
void lcd_send_nibble(unsigned char nibble) {
if (nibble & 0x01) LCD_D4_PORT |= (1 << LCD_D4_PIN);
else LCD_D4_PORT &= ~(1 << LCD_D4_PIN);
if (nibble & 0x02) LCD_D5_PORT |= (1 << LCD_D5_PIN);
else LCD_D5_PORT &= ~(1 << LCD_D5_PIN);
if (nibble & 0x04) LCD_D6_PORT |= (1 << LCD_D6_PIN);
else LCD_D6_PORT &= ~(1 << LCD_D6_PIN);
if (nibble & 0x08) LCD_D7_PORT |= (1 << LCD_D7_PIN);
else LCD_D7_PORT &= ~(1 << LCD_D7_PIN);
lcd_pulse_enable();
}
void lcd_send_byte(unsigned char byte, unsigned char is_data) {
if (is_data) LCD_RS_PORT |= (1 << LCD_RS_PIN);
else LCD_RS_PORT &= ~(1 << LCD_RS_PIN);
lcd_send_nibble((byte >> 4) & 0x0F);
lcd_send_nibble(byte & 0x0F);
}
void lcd_init() {
DDRB |= (1 << LCD_RS_PIN) | (1 << LCD_EN_PIN);
DDRD |= (1 << LCD_D4_PIN) | (1 << LCD_D5_PIN) | (1 << LCD_D6_PIN) | (1 << LCD_D7_PIN);
// Inicia máquina de estados de inicialização
lcd_init_state = 1;
lcd_init_timer = tick_ms;
}
// Máquina de estados para inicializar LCD
void lcd_init_state_machine() {
unsigned long elapsed = tick_ms - lcd_init_timer;
switch (lcd_init_state) {
case 1: // Esperar 20ms inicial
if (elapsed >= 20) {
lcd_send_nibble(0x03);
lcd_init_timer = tick_ms;
lcd_init_state = 2;
}
break;
case 2: // Esperar 5ms após primeiro nibble
if (elapsed >= 5) {
lcd_send_nibble(0x03);
lcd_init_timer = tick_ms;
lcd_init_state = 3;
}
break;
case 3: // Esperar 1ms
if (elapsed >= 1) {
lcd_send_nibble(0x03);
lcd_init_timer = tick_ms;
lcd_init_state = 4;
}
break;
case 4: // Esperar 1ms
if (elapsed >= 1) {
lcd_send_nibble(0x02);
lcd_init_timer = tick_ms;
lcd_init_state = 5;
}
break;
case 5: // Enviar comandos de configuração
if (elapsed >= 1) {
lcd_send_byte(0x28, 0);
lcd_init_timer = tick_ms;
lcd_init_state = 6;
}
break;
case 6: // Esperar 2ms
if (elapsed >= 2) {
lcd_send_byte(0x08, 0);
lcd_init_timer = tick_ms;
lcd_init_state = 7;
}
break;
case 7: // Esperar 2ms
if (elapsed >= 2) {
lcd_send_byte(0x01, 0);
lcd_init_timer = tick_ms;
lcd_init_state = 8;
}
break;
case 8: // Esperar 2ms
if (elapsed >= 2) {
lcd_send_byte(0x06, 0);
lcd_init_timer = tick_ms;
lcd_init_state = 9;
}
break;
case 9: // Esperar 2ms
if (elapsed >= 2) {
lcd_send_byte(0x0C, 0);
lcd_init_state = 0; // Inicialização completa
}
break;
}
}
void lcd_clear() {
lcd_send_byte(0x01, 0);
}
void lcd_set_cursor(unsigned char col, unsigned char row) {
unsigned char addr = col + (row * 0x40);
lcd_send_byte(0x80 | addr, 0);
}
void lcd_print_char(char c) {
lcd_send_byte(c, 1);
}
void lcd_print_string(const char *str) {
while (*str) {
lcd_print_char(*str++);
}
}
// ADC
void adc_init() {
DDRC &= ~(1 << PC0);
ADMUX = (1 << REFS0);
ADCSRA =
(1 << ADEN) |
(1 << ADPS2) |
(1 << ADPS1) |
(1 << ADPS0);
adc_read();
}
unsigned int adc_read() {
ADCSRA |= (1 << ADSC);
while (ADCSRA & (1 << ADSC));
return ADC;
}
// BOTÕES
void buttons_init() {
DDRB &= ~((1 <<PB0) | (1 << PB1)); // D8, D9
PORTB |= (1 << PB0) | (1 << PB1);
DDRD &= ~(1 << PD6); // D6
PORTD |= (1 << PD6);
PCMSK0 |= (1 << PCINT0) | (1 << PCINT1);
PCICR |= (1 << PCIE0);
}
ISR(PCINT0_vect) {
static unsigned long last_reset = 0;
unsigned long now = tick_ms;
// D9 reset - debounce 150ms
if (!(PINB & (1 << PB1))) {
if (now - last_reset > 150) {
button_reset = 1;
last_reset = now;
}
}
}
void check_button_right() {
static unsigned long last_right = 0;
unsigned long now = tick_ms;
if (!(PIND & (1 << PD6))) { // D6 direita - debounce 100ms
if (now - last_right > 100) {
button_right = 1;
last_right = now;
}
}
}
void check_button_left() {
static unsigned long last_left = 0;
unsigned long now = tick_ms;
if (!(PIND & (1 << PB0))) { // PB0 esquerda - debounce 100ms
if (now - last_left > 100) {
button_left = 1;
last_left = now;
}
}
}
// LOGICA DO JOGO
void reset_game() {
score = 0;
game_speed = 300;
snake.body[0].x = 7;
snake.body[0].y = 0;
snake.body[1].x = 6;
snake.body[1].y = 0;
snake.body[2].x = 5;
snake.body[2].y = 0;
snake.length = 3;
snake.direction = 0;
snake.nextDirection = 0;
food_pos.x = 10 + (rand() % 6);
food_pos.y = rand() % 2;
lcd_clear();
last_move_time = tick_ms;
}
unsigned char is_valid_direction(unsigned char new_dir) {
if (snake.direction == 0 && new_dir == 1) return 0;
if (snake.direction == 1 && new_dir == 0) return 0;
if (snake.direction == 2 && new_dir == 3) return 0;
if (snake.direction == 3 && new_dir == 2) return 0;
return 1;
}
void move_snake() {
for (int i = snake.length - 1; i > 0; i--) {
snake.body[i] = snake.body[i - 1];
}
if (is_valid_direction(snake.nextDirection)) {
snake.direction = snake.nextDirection;
}
switch (snake.direction) {
case 0: snake.body[0].x++; break;
case 1: snake.body[0].x--; break;
case 2: snake.body[0].y--; break;
case 3: snake.body[0].y++; break;
}
// Logica para X
if (snake.body[0].x >= 128) {
snake.body[0].x = GRID_WIDTH - 1; // Foi pra esquerda do 0, teleporta pra direita
} else if (snake.body[0].x >= GRID_WIDTH) {
snake.body[0].x = 0; // Passou da direita, teleporta pro 0
}
// Logica para Y
if (snake.body[0].y >= 128) {
snake.body[0].y = GRID_HEIGHT - 1; // Foi pra cima do 0, teleporta pra baixo
} else if (snake.body[0].y >= GRID_HEIGHT) {
snake.body[0].y = 0; // Passou de baixo, teleporta pra cima
}
}
unsigned char check_collision() {
for (int i = 1; i < snake.length; i++) {
if (snake.body[0].x == snake.body[i].x &&
snake.body[0].y == snake.body[i].y) {
return 1;
}
}
return 0;
}
void eat_food() {
score += 10;
if (snake.length < MAX_SNAKE_LENGTH) {
snake.body[snake.length] = snake.body[snake.length - 1];
snake.length++;
}
food_pos.x = 1 + (rand() % 14);
food_pos.y = rand() % 2;
if (game_speed > 150) game_speed -= 10;
uart_send_string("[FOOD] Score: ");
uart_send_char('0' + (score / 10));
uart_send_char('0' + (score % 10));
uart_send_char('\n');
}
void update_display() {
char grid[GRID_HEIGHT][GRID_WIDTH];
for (int y = 0; y < GRID_HEIGHT; y++) {
for (int x = 0; x < GRID_WIDTH; x++) {
grid[y][x] = ' ';
}
}
grid[snake.body[0].y][snake.body[0].x] = '#';
for (int i = 1; i < snake.length; i++) {
grid[snake.body[i].y][snake.body[i].x] = '*';
}
grid[food_pos.y][food_pos.x] = 'o';
for (int y = 0; y < GRID_HEIGHT; y++) {
lcd_set_cursor(0, y);
for (int x = 0; x < GRID_WIDTH; x++) {
lcd_print_char(grid[y][x]);
}
}
}
void show_welcome() {
lcd_clear();
lcd_set_cursor(0, 0);
lcd_print_string(" SNAKE GAME ");
lcd_set_cursor(0, 1);
lcd_print_string("Press 's' Start");
}
void show_game_over() {
lcd_clear();
lcd_set_cursor(0, 0);
lcd_print_string("GAME OVER! ");
lcd_set_cursor(0, 1);
lcd_print_string("Score:");
lcd_print_char('0' + (score / 10));
lcd_print_char('0' + (score % 10));
uart_send_string("[OVER] Score: ");
uart_send_char('0' + (score / 10));
uart_send_char('0' + (score % 10));
uart_send_string(" Snake: ");
uart_send_char('0' + snake.length);
uart_send_char('\n');
}
// MAIN
int main(void) {
uart_init(9600);
uart_send_string("\n=== SNAKE GAME ===\n");
uart_send_string("s = Start\n");
uart_send_string("q = Quit\n");
uart_send_string("p = Pause\n");
uart_send_string("Botoes: Reset Esq Dir \n\n");
// Timer DEVE ser inicializado ANTES da LCD
timer_init();
sei(); // Habilitar interrupções para timer funcionar
// Agora inicializa LCD com timer rodando
lcd_init();
// Aguarda a máquina de estados terminar a inicialização do LCD
while (lcd_init_state != 0) {
lcd_init_state_machine();
}
adc_init();
buttons_init();
show_welcome();
for (;;) {
// UART
char cmd = uart_read_nonblocking();
if (cmd != 0) {
if (cmd == 's' || cmd == 'S') {
if (!game_running) {
reset_game();
game_running = 1;
uart_send_string("[START] Game on!\n");
}
}
else if (cmd == 'q' || cmd == 'Q') {
if (game_running) {
game_running = 0;
show_game_over();
uart_send_string("[QUIT] Type 's' to restart\n");
}
}
else if (cmd == 'p' || cmd == 'P') {
game_running = !game_running;
if (game_running) {
uart_send_string("[PLAY] Resumed\n");
last_move_time = tick_ms;
} else {
uart_send_string("[PAUSE] Paused\n");
}
}
}
// BOTÕES
if (button_reset) {
button_reset = 0;
game_running = 0;
show_welcome();
uart_send_string("[RESET] Back to menu\n");
}
check_button_left();
if (button_left) {
button_left = 0;
if (game_running) {
switch (snake.nextDirection) {
case 0: snake.nextDirection = 2; break;
case 2: snake.nextDirection = 1; break;
case 1: snake.nextDirection = 3; break;
case 3: snake.nextDirection = 0; break;
}
}
}
check_button_right();
if (button_right) {
button_right = 0;
if (game_running) {
switch (snake.nextDirection) {
case 0: snake.nextDirection = 3; break;
case 3: snake.nextDirection = 1; break;
case 1: snake.nextDirection = 2; break;
case 2: snake.nextDirection = 0; break;
}
}
}
// JOGO
if (game_running) {
adc_value = adc_read();
game_speed = 100 + ((adc_value * 400) >> 10); // 100-500ms mais �bvio
if (tick_ms - last_move_time >= game_speed) {
last_move_time = tick_ms;
move_snake();
if (check_collision()) {
game_running = 0;
show_game_over();
}
if (snake.body[0].x == food_pos.x && snake.body[0].y == food_pos.y) {
eat_food();
}
update_display();
}
}
}
return 0;
}