Mikrokontrolery - Jak zacząć?

... czyli zbiór praktycznej wiedzy dot. mikrokontrolerów.

niedziela, 20 marca 2011

DIY: Gra "Snake" na matrycy LED 8x8 i ATmega8


Autor: petter0
Redakcja: Dondu

Żyjemy w niesamowitych czasach. Postęp technologiczny, jaki możemy obserwować z dekady na dekadę może przyprawić o ból głowy. Coraz to nowsze urządzenia mają na celu pomóc nam w pracy i ułatwić (skomplikować) wykonywanie codziennych czynności.

Większość czasu wolnego spędzamy wpatrując się w ekrany komputerów, smartfonów czy telewizorów, nierzadko grając w gry. Jakie gry? Również nowoczesne, z coraz dokładniejszym odwzorowaniem rzeczywistości i wciągającą fabułą. Stop!

Czasem warto zatrzymać się na chwilę, oddalić i spojrzeć wstecz. Jakie gry bawiły nas 15 lat temu? Kto z nas nie pamięta gry z którą kojarzy się większość starszych modeli telefonów Nokia? Dzisiaj wspominamy Snake'a:


Arcade - Snake
Sirius Software 1982r.


Snake to gra zręcznościowa w której użytkownik steruje poruszającym się po ekranie wężem. Jego zadaniem jest zjadanie pojawiających się w przypadkowych miejscach jabłek. Każde zjedzone jabłko to dodatkowy punkt, ale również przyrost długości ciała węża o jeden segment.

W ten sposób zwiększa się trudność gry, ponieważ sterując wężem trzeba uważać, żeby zamiast jabłka nie zjeść przypadkiem samego siebie - co oznacza przegraną. Im dłuższy ogon, tym łatwiej o kolizję i zakończenie rozgrywki.

Do dyspozycji mamy zwykle również kilka poziomów szybkości z którą porusza się wąż, co znacznie utrudnia sterowanie, ale jak wiadomo - zawsze wybieramy poziom najwyższy :).

W artykule pokazuję jak zbudowałem moją wersję (którą możesz wykonać na bazie niniejszego artykułu), a na filmie zaprezentowała ją urocza asystentka :-)





Gra 'Snake' na matrycy LED 8x8


Projekt przedstawia prostą realizację gry opartą o mikrokontroler ATmega8, wspierany przez rejestry przesuwne 74HC595. Całość niezbędnej elektroniki wraz z wyświetlaczem mieści się na pojedynczej, dwustronnej płytce drukowanej. Przymocowana z tyłu bateria zasilająca sprawia, że projekt to przenośna oldschoolowa konsola do gry Snake.


Elektronika

Schemat elektryczny układu (widoczny poniżej) został przygotowany w popularnym programie Eagle w wersji 6.1.0.


Schemat elektryczny gry 'Snake'


Sercem układu jest poczciwy (tak samo jak gra) mikrokotroler ATmega8, taktowany wewnętrznym zegarem 8MHz. Otaczają go standardowe kondensatory filtrujące zasilanie oraz rezystor podciągający linię RESET do poziomu VCC.

Obraz wyświetlany jest na dwukolorowej matrycy LED o rozmiarze 8x8. W sumie do dyspozycji mamy 64 diody LED z których każda w teorii może świecić się w kolorze zielonym lub czerwonym. W praktyce jednak mamy do dyspozycji jeszcze jeden kolor - pomarańczowy. Wynika on z połączenia koloru czerwonego z zielonym podczas ich jednoczesnego świecenia.

Wszystkie kolory zostały zaprezentowane na poniższym obrazku:


Możliwe do uzyskania kolory

Do sterowania taką matrycą należy wykorzystać sposób zwany multipleksowaniem. Poniższy wycinek z dokumentacji technicznej dostarczonej przez producenta wskazuje, że mamy do czynienia z matrycą o wspólnej katodzie. Multipleksowanie w takim przypadku polega na naprzemiennym podłączaniu katod wyświetlacza do GND, co zostało zrealizowane przy pomocy układu ULN2803.

Jest to scalak zawierający w sobie 8 par tranzystorów NPN połączonych w układzie Darlingtona. Ogranicza on prąd płynący przez porty wyjściowe mikrokontrolera. Natomiast anody diod LED wyświetlacza połączone są w kolumny. Ich sterowanie odbywa się przy pomocy rejestrów przesuwnych 74HC595. Układy te mają na celu rozszerzenie liczby wyjść ATmegi z którą komunikują się korzystając interfejsu SPI.


Schemat połączeń wewnętrznych matrycy LED


Układ zasilany jest z baterii 9V poprzez stabilizator liniowy LM7805. Służy on do konwersji poziomu napięcia do 5V. Dioda D1 zabezpiecza przed przypadkowym odwrotnym podłączeniem biegunów baterii. Zasilanie włącza się przełącznikiem S11, co sygnalizowane jest poprzez świecenie diody LED1.

Przycisków jest siedem. Cztery z nich służą do sterowania kierunkiem oraz szybkością poruszania się węża, pozostałe dwa to ‘START’ i ‘RESET’. Naciśnięcie dowolnego przycisku powoduje ustawienie stanu niskiego na odpowiadającym mu wejściu procesora oraz wywołanie przerwania zewnętrznego INT0.

Dodatkowo na panelu przednim znajduje się przełącznik S9, którego zadaniem jest włączanie lub wyłączanie sygnałów dźwiękowych wydawanych przez buzzer podczas gry.

Ostatnim elementem, który wymaga opisu jest 6-pinowe gniazdo służące do programowania mikrokontrolera. Jego poszczególne sygnały zostały opisane po stronie bottom co znacznie ułatwia podłączenie programatora.

Do pobrania pliki Eagle: Snake-PCB.rar (kopia)




Program

W załączniku znajduje się program napisany pod mikrokontroler ATmega8 taktowany wewnętrznym zegarem 8MHz. Konfiguracja fusebitów wygląda następująco:
  • LOW: E4
  • HIGH: D9
Program w został napisany w  języku C przy wykorzystaniu środowiska AVR Studio w wersji 4.14. Sam kod jest dość obszerny, ale napisany w sposób bardzo przejrzysty. Uzupełniają go liczne komentarze, które w pełni wystarczają, by zrozumieć działanie programu. Cały projekt podzielony jest na logiczne części osadzone w osobnych plikach.


Podział programu na pliki


W programie wykorzystana została obsługa komunikacji SPI, przerwania zewnętrznego INT0 oraz dwóch Timerów sprzętowych. W pamięci EEPROM zapisywane są rekordy dla poszczególnych szybkości (poziomów) gry. Pozwala to na zapamiętanie najlepszych uzyskanych wyników nawet po rozłączeniu zasilania.

Do pobrania:





Realizacja

Projekt płytki PCB został wykonany również w programie Eagle. Płytka jest dwustronna i ma rozmiar 9.5 x 5.25 cm. Jej wykonanie jest dość trudne ze względu na prowadzone blisko siebie, wąskie ścieżki oraz drobne napisy umiejscowione wokół kluczowych elementów. Ponadto należy zwrócić uwagę na fakt, że przylutowany wyświetlacz przesłania mikrokontroler, co uniemożliwia ewentualną poprawe jego lutów.

W projekcie wykozystano jeszcze dwie płytki tego samego kształtu co płytka 'bazowa', ale ich zadniem jest tylko i wyłącznie trzymanie baterii zasilającej układ. Całość została skręcona przy pomocy 4 śrub znajdujących się w narożnikach.

Do pobrania pliki Eagle: Snake-PCB.rar (kopia)


Projekt PCB


PCB top

PCB bottom



PCB z elementami
TOP - po lewej, BOTTOM - po prawej


Całość - bok


Całość - przód





Obsługa

Grę włącza się przy pomocy przełącznika ‘ON/OFF’ umieszczonego w prawym górnym rogu, odpowiedzialnego za podłączenie zasilania do układu.

Na początku widoczny jest ekran powitalny w postaci napisu ‘Snake’. W trakcie jego wyświetlania można nacisnąć przycisk ‘START’, aby przejść do ustawień, lub poczekać aż napis zniknie.

W następnej kolejności ukazuje się ekran konfiguracji szybkości poruszania się węża. Dostępnych jest osiem poziomów. Aby przejść do poziomu wyższego lub niższego należy naciskać przyciski ‘PRAWO’ lub ‘LEWO’. Po zdecydowaniu się na którym poziomie ma toczyć się rozgrywka należy nacisnąć przycisk ‘START’.

Natychmiast po tym rozpoczyna się gra. Każde zebrane jabłko powoduje przyrost długości węża o jeden segment oraz inkrementację wyniku końcowego. Jeżeli użytkownik naciśnie przycisk ‘SPEED’ to wąż zacznie poruszać się szybciej.

Gra kończy się w momencie kiedy wąż ‘zje sam siebie’, tzn. jego głowa znajdzie się na pozostałej części ciała. Zaraz po przegranej wyświetlony zostaje wynik w formie: ‘aktualny wynik / rekord ‘. Jeżeli aktualny wynik jest większy od rekordu wyświetlony zostaje napis ‘Record’, a wynik zapisywany jest jako nowy rekord.

Aby rozpocząć kolejną rozgrywkę na tym samym poziomie należy nacisnąć przycisk ‘START’. Natomiast aby rozpocząć grę zupełnie od nowa należy nacisnąć przycisk ‘RESET’. Ponadto w lewym górnym narożniku umieszczony został przycisk ‘MUTE’ służący do wyciszenia buzzera.





Uwagi

Projekt został fizycznie zrealizowany w 2012 roku, ale dopiero teraz został przedstawiony. Od tego czasu zauważyłem w nim parę niedociągnięć, które można byłoby poprawić. Oto one:
  1. Brak jakiegokolwiek zabezpieczenia ścieżek przed śniedzeniem. Jest to szczególnie widoczne podczas porównania pierwszego zdjęcia wykonanego obecnie z jednym ze zdjęć obrazujących proces realizacji projektu z 2012 roku.
  2. Prąd pobierany jest 'skokowo', dlatego przy stabilizatorze powinno zastosować się kondensatory o większej pojemności. Należałoby wtedy również dodać diodę rozładowującą podczas zwarcia na jego wejściu wg schematu zawartego w artykule: zasilanie mikrokontrolera .
  3. Fajnie byłoby dodać inne gry, np. popularne 'samochody'.




Pliki źródłowe

Do pobrania: Snake-Program.rar (kopia)

buzzer.h

#ifndef BUZZER_H
#define BUZZER_H

#define BUZZER (1<<PD7)

void Buzzer_init(void);
void BuzzerPik(uint8_t liczba);

#endif



buzzer.c

#include <avr/io.h>
#include <util/delay.h>

#include "buzzer.h"

void Buzzer_init(void)
{
  DDRD |= BUZZER;
  PORTD &= ~BUZZER;
}

void BuzzerPik(uint8_t liczba)
{
  uint8_t i;
  for(i=0 ; i<liczba ; i++) {
    PORTD |= BUZZER;
    _delay_ms(10);
    PORTD &= ~BUZZER;
    _delay_ms(100);
  }
}


przyciski.h

#ifndef PRZYCISKI_H
#define PRZYCISKI_H

#define PRZYCISK_GORA   (1<<PC0)
#define PRZYCISK_DOL  (1<<PC1)
#define PRZYCISK_LEWO   (1<<PB0)
#define PRZYCISK_PRAWO  (1<<PC2)
#define PRZYCISK_SPEED  (1<<PC3)
#define PRZYCISK_START  (1<<PC4)

extern volatile char kierunek;
extern volatile uint8_t gazu;
extern volatile uint8_t start;

void Przyciski_init(void);

#endif



przyciski.c

#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>

#include "przyciski.h"

void PrzerwZewnetrzne_init(void);

volatile char kierunek = 'x'; // p, l, g, d
volatile uint8_t gazu = 0; // speed
volatile uint8_t start = 0; // start gry


void Przyciski_init(void)
{
  // porty przycisków jako wejścia
  DDRC &= ~(PRZYCISK_GORA | PRZYCISK_DOL | PRZYCISK_PRAWO | PRZYCISK_SPEED |
            PRZYCISK_START);
  DDRB &= ~(PRZYCISK_LEWO);

  // pull up
  PORTC |= PRZYCISK_GORA | PRZYCISK_DOL | PRZYCISK_PRAWO | PRZYCISK_SPEED |
           PRZYCISK_START;
  PORTB |= PRZYCISK_LEWO;

  PrzerwZewnetrzne_init();
}


void PrzerwZewnetrzne_init(void)
{
  DDRD &= ~(1<<PD2); // linia INT0 jako wejście
  PORTD |= (1<<PD2); // pull up

  // przerwanie INT0
  MCUCR |= (1<<ISC01); // zbocze opadające powoduje przerwanie
  GICR |= (1<<INT0); // włączenie przerwania INT0
  sei(); // włączenie globalnego systemu przerwań
}


SIGNAL(SIG_INTERRUPT0)
{
  // zmiana kierunku w zależności od naciśniętego przycisku
  if(!(PINC & PRZYCISK_GORA)) {
    kierunek = 'g';
  }

  if(!(PINC & PRZYCISK_DOL)) {
    kierunek = 'd';
  }

  if(!(PINB & PRZYCISK_LEWO)) {
    kierunek = 'l';
  }

  if(!(PINC & PRZYCISK_PRAWO)) {
    kierunek = 'p';
  }

  // dodanie gazu
  if(!(PINC & PRZYCISK_SPEED)) {
    gazu = 1;
  }

  // wystartowanie gry
  if(!(PINC & PRZYCISK_START)) {
    start = 1;
  }
}



matryca_led.h

#ifndef MATRYCA_LED_H
#define MATRYCA_LED_H

// WIERSZE (aktywne 1)
#define W1 (1<<PD4)
#define W2 (1<<PD3)
#define W3 (1<<PD0)
#define W4 (1<<PD1)
#define W5 (1<<PD6)
#define W6 (1<<PD5)
#define W7 (1<<PB7)
#define W8 (1<<PB6)

// SPI
#define MOSI  (1<<PB3)
#define SCK   (1<<PB5)
#define SSR   (1<<PB2) // CS
#define SSG   (1<<PB1)


extern uint8_t tab[8][2];

void MatrycaLED_init(void);

void ZapalDiode(char kolor, uint8_t w, uint8_t k);
void ZgasDiode(char kolor, uint8_t w, uint8_t k);

void WyczyscEkran(void);

uint8_t WyswietlTekst(uint8_t tekst[], uint8_t dlugosc);

#endif



matryca_led.c

#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>


#include "matryca_led.h"
#include "przyciski.h"
#include "buzzer.h"

void Wiersze_init(void);
void Timer2_init(void);
void Spi_init(void);

void SendSpi(uint8_t wiersz);

// tablica przechowująca wartośći wyświetlane na poszczególnych wierszach
// tab[ nr wiersza ][ 0-red, 1-green ]
uint8_t tab[8][2];

// tablica przechowująca wzory cyfr i liter w standardzie 5x7 px
uint8_t cyfra[22][7] = {
  { 0b01110000, 0b10001000, 0b10011000, 0b10101000, 0b11001000, 0b10001000, 0b01110000 }, // 0
  { 0b00100000, 0b01100000, 0b00100000, 0b00100000, 0b00100000, 0b00100000, 0b01110000 }, // 1
  { 0b01110000, 0b10001000, 0b00001000, 0b00010000, 0b00100000, 0b01000000, 0b11111000 }, // 2
  { 0b11111000, 0b00010000, 0b00100000, 0b00010000, 0b00001000, 0b10001000, 0b01110000 }, // 3
  { 0b00010000, 0b00110000, 0b01010000, 0b10010000, 0b11111000, 0b00010000, 0b00010000 }, // 4
  { 0b11111000, 0b10000000, 0b11110000, 0b00001000, 0b00001000, 0b10001000, 0b01110000 }, // 5
  { 0b00110000, 0b01000000, 0b10000000, 0b11110000, 0b10001000, 0b10001000, 0b01110000 }, // 6
  { 0b11111000, 0b10001000, 0b00010000, 0b00100000, 0b01000000, 0b01000000, 0b01000000 }, // 7
  { 0b01110000, 0b10001000, 0b10001000, 0b01110000, 0b10001000, 0b10001000, 0b01110000 }, // 8
  { 0b01110000, 0b10001000, 0b10001000, 0b01111000, 0b00001000, 0b00010000, 0b01100000 }, // 9
  { 0b00000000, 0b00000000, 0b01110000, 0b00001000, 0b01111000, 0b10001000, 0b01111000 }, // 10 a
  { 0b00000000, 0b00000000, 0b01110000, 0b10000000, 0b10000000, 0b10001000, 0b01110000 }, // 11 c
  { 0b00001000, 0b00001000, 0b01101000, 0b10011000, 0b10001000, 0b10001000, 0b01111000 }, // 12 d
  { 0b00000000, 0b00000000, 0b01110000, 0b10001000, 0b11111000, 0b10000000, 0b01110000 }, // 13 e
  { 0b10000000, 0b10000000, 0b10010000, 0b10100000, 0b11100000, 0b10010000, 0b10001000 }, // 14 k
  { 0b00000000, 0b00000000, 0b10110000, 0b11001000, 0b10001000, 0b10001000, 0b10001000 }, // 15 n
  { 0b00000000, 0b00000000, 0b01110000, 0b10001000, 0b10001000, 0b10001000, 0b01110000 }, // 16 o
  { 0b00000000, 0b00000000, 0b10110000, 0b11001000, 0b10000000, 0b10000000, 0b10000000 }, // 17 r
  { 0b11110000, 0b10001000, 0b10001000, 0b11110000, 0b10100000, 0b10010000, 0b10001000 }, // 18 R
  { 0b01111000, 0b10000000, 0b10000000, 0b01110000, 0b00001000, 0b00001000, 0b11110000 }, // 19 S
  { 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000 }, // 20 spacja
  { 0b00000000, 0b00001000, 0b00010000, 0b00100000, 0b01000000, 0b10000000, 0b00000000 } // 21 /
};


void MatrycaLED_init(void)
{
  Wiersze_init();
  Timer2_init();
  Spi_init();

  sei();
}


void Wiersze_init(void)
{
  // porty wierszy jako wyjścia, początkowe wygaszenie
  DDRD |= W1 | W2 | W3 | W4 | W5 | W6;
  PORTD &= ~(W1 | W2 | W3 | W4 | W5 | W6);
  DDRB |= W7 | W8;
  PORTB &= ~(W7 | W8);
}


void Timer2_init(void)
{
  TCCR2 |= (1<<WGM21); // tryb CTC
  TCCR2 |= (1<<CS22) | (1<<CS21); // preskaler 256
  // 8000000/256/100 = 39
  OCR2 = 39; // rejestr porównania
  TIMSK |= (1<<OCIE2); // zezwolenie na przerwania Compare Match
}


void Spi_init(void)
{
  // linie portów SPI jako wyjścia
  DDRB |= MOSI | SCK | SSR | SSG;
  //aktywacja SPI, tryb pracy Master, prędkość zegara Fosc/64
  SPCR |= (1<<SPE) | (1<<MSTR) | (1<<SPR1);
}


void SendSpi(uint8_t wiersz)
{
  wiersz = wiersz-1;

  SPDR = tab[wiersz][0];
  while(!(SPSR & (1<<SPIF)));
  PORTB |= SSR;
  _delay_us(1);
  PORTB &= ~SSR;

  SPDR = tab[wiersz][1];
  while(!(SPSR & (1<<SPIF)));
  PORTB |= SSG;
  _delay_us(1);
  PORTB &= ~SSG;
}


ISR(TIMER2_COMP_vect)
{
  static uint8_t licznik = 1; // zmienna do przełączania kolejno wierszy

  switch(licznik) {
  case 1 : {
    PORTB &= ~W8; // wyłączenie wiersza 8
    SendSpi(licznik);
    PORTD |= W1; // włączenie wiersza 1
    break;
  }
  case 2 : {
    PORTD &= ~W1;
    SendSpi(licznik);
    PORTD |= W2;
    break;
  }
  case 3 : {
    PORTD &= ~W2;
    SendSpi(licznik);
    PORTD |= W3;
    break;
  }
  case 4 : {
    PORTD &= ~W3;
    SendSpi(licznik);
    PORTD |= W4;
    break;
  }
  case 5 : {
    PORTD &= ~W4;
    SendSpi(licznik);
    PORTD |= W5;
    break;
  }
  case 6 : {
    PORTD &= ~W5;
    SendSpi(licznik);
    PORTD |= W6;
    break;
  }
  case 7 : {
    PORTD &= ~W6;
    SendSpi(licznik);
    PORTB |= W7;
    break;
  }
  case 8 : {
    PORTB &= ~W7;
    SendSpi(licznik);
    PORTB |= W8;
    break;
  }
  }

  licznik++;
  if(licznik > 8) {
    licznik = 1;
  }
}


void ZapalDiode(char kolor, uint8_t w,
                uint8_t k) // lewy górny narożnik w=1, k=1
{
  w=w-1;

  switch(kolor) {
  case 'r' :
    tab[w][0] |= (1<<(k-1)); // 1 kolumna = najmłodszy bit
    break;

  case 'g' :
    tab[w][1] |= (1<<(8-k)); // 1 kolumna = najstarszy bit
    break;

  case 'o' :
    tab[w][0] |= (1<<(k-1));
    tab[w][1] |= (1<<(8-k));
    break;
  }
}


void ZgasDiode(char kolor, uint8_t w,
               uint8_t k) // lewy górny narożnik w=1, k=1
{
  w=w-1;

  switch(kolor) {
  case 'r' :
    tab[w][0] &= ~(1<<(k-1));
    break;

  case 'g' :
    tab[w][1] &= ~(1<<(8-k));
    break;

  case 'o' :
    tab[w][0] &= ~(1<<(k-1));
    tab[w][1] &= ~(1<<(8-k));
    break;
  }
}


void WyczyscEkran(void)
{
  uint8_t i;
  for(i=0 ; i<8 ; i++) {
    tab[i][0] = 0;
    tab[i][1] = 0;
  }
}


uint8_t WyswietlTekst(uint8_t tekst[], uint8_t dlugosc)
{
  uint8_t i, k;
  uint8_t j;
  start = 0;

  for(k=0 ; k < dlugosc ; k++) {
    for(i=0 ; i<7 ; i++) {
      tab[0][1] = (tab[0][1]<<1) | (cyfra[tekst[k]][0]>>(7-i));
      tab[1][1] = (tab[1][1]<<1) | (cyfra[tekst[k]][1]>>(7-i));
      tab[2][1] = (tab[2][1]<<1) | (cyfra[tekst[k]][2]>>(7-i));
      tab[3][1] = (tab[3][1]<<1) | (cyfra[tekst[k]][3]>>(7-i));
      tab[4][1] = (tab[4][1]<<1) | (cyfra[tekst[k]][4]>>(7-i));
      tab[5][1] = (tab[5][1]<<1) | (cyfra[tekst[k]][5]>>(7-i));
      tab[6][1] = (tab[6][1]<<1) | (cyfra[tekst[k]][6]>>(7-i));

      for(j=0 ; j<80 ; j++) {
        _delay_ms(1);
        if(start) {
          BuzzerPik(2);
          return 1;
        }
      }
    }
  }

  for(i=0 ; i<6 ; i++) {
    tab[0][1] = (tab[0][1]<<1);
    tab[1][1] = (tab[1][1]<<1);
    tab[2][1] = (tab[2][1]<<1);
    tab[3][1] = (tab[3][1]<<1);
    tab[4][1] = (tab[4][1]<<1);
    tab[5][1] = (tab[5][1]<<1);
    tab[6][1] = (tab[6][1]<<1);

    for(j=0 ; j<80 ; j++) {
      _delay_ms(1);
      if(start) {
        BuzzerPik(2);
        return 1;
      }
    }
  }

  BuzzerPik(1);
  WyczyscEkran();
  return 0;
}



snake.h

#ifndef SNAKE_H
#define SNAKE_H

#define WIERSZ 0
#define KOLUMNA 1

extern uint8_t snake[64][2];
extern uint8_t jablko[2];
extern uint8_t game_over;
extern uint16_t speed;
extern char poprzedni_kierunek;

void poczatek(void);
void rysuj_snake(void);
void wyczysc_snake(void);
void UstawPredkosc(void);
void WskaznikPredkosci(uint8_t wskaz);
void Timer1_init(void);

void przesun_snake(uint8_t nowe_pole_wiersz, uint8_t nowe_pole_kolumna);
void kierunek_snake(void);
uint8_t Losuj(void);
void RysujJablko(void);
uint8_t SprJablko(void);
void MrugnijJablko(uint8_t liczba);
uint8_t SnakeOver(void);
void MrugnijOver(uint8_t liczba);
void WyswietlWynik(void);

#endif



snake.c

#include <avr/io.h>
#include <util/delay.h>
#include <stdlib.h>
#include <avr/interrupt.h>
#include <avr/eeprom.h>
#include <avr/pgmspace.h>

#include "snake.h"
#include "matryca_led.h"
#include "przyciski.h"
#include "buzzer.h"

void Losuj_init(void);

uint8_t snake[64][2]; // tablica w której zapisywane są współrzędne segmetów [nr segmentu][w->0, k->1]
uint8_t dlugosc; // długość węża (ilość segmentów wraz z głową), liczona od 1
uint8_t jablko[2]; // pozycja jabłka [0]->wiersz, [1]->kolumna

uint8_t wynik = 0; // wynik (1segment = 1pkt)
uint8_t moc = 4; // poziom szybkość (1..8)

char poprzedni_kierunek; // by snake nie mógł wejść w siebie

uint8_t game_over = 0;
uint16_t tab_speed[8] = { 875, 675, 500, 375, 250, 175, 100, 50 }; // predkość przesuwu w ms
uint16_t speed = 375;


uint8_t EEMEM rekord_poziom1 = 0; // EEPROM
uint8_t EEMEM rekord_poziom2 = 1; // EEPROM
uint8_t EEMEM rekord_poziom3 = 2; // EEPROM
uint8_t EEMEM rekord_poziom4 = 3; // EEPROM
uint8_t EEMEM rekord_poziom5 = 4; // EEPROM
uint8_t EEMEM rekord_poziom6 = 5; // EEPROM
uint8_t EEMEM rekord_poziom7 = 6; // EEPROM
uint8_t EEMEM rekord_poziom8 = 7; // EEPROM


void poczatek(void)
{
  dlugosc = 3; // początkowa długość
  kierunek = 'p'; // początkowy kierunek
  poprzedni_kierunek =
    'p'; // poprzedni kierunek - zabezpieczenie przed skręceniem w sibie
  wynik = 0; // początkowy wynik (1segment = 1pkt)

  // początkowe współrzędne
  snake[0][WIERSZ] = 5;
  snake[0][KOLUMNA] = 3;
  snake[1][WIERSZ] = 5;
  snake[1][KOLUMNA] = 2;
  snake[2][WIERSZ] = 5;
  snake[2][KOLUMNA] = 1;

  ZapalDiode('r', snake[0][WIERSZ], snake[0][KOLUMNA]);
  ZapalDiode('g', snake[1][WIERSZ], snake[1][KOLUMNA]);
  ZapalDiode('g', snake[2][WIERSZ], snake[2][KOLUMNA]);

  // narysowanie pierwszego jabłka
  RysujJablko();

  // wyczyszczenie pozostałych elementów tablicy
  uint8_t i;
  for(i=3 ; i<64 ; i++) {
    snake[i][WIERSZ] = 0;
    snake[i][KOLUMNA] = 1;
  }

  _delay_ms(80);
}

void rysuj_snake(void)
{
  wyczysc_snake();
  kierunek_snake();

  // narysowanie głwoy
  ZapalDiode('r', snake[0][WIERSZ], snake[0][KOLUMNA]);

  // narysowanie reszty ciała
  uint8_t i;
  for(i=1 ; i<64 ; i++) {
    // zabezpieczenie przed rysowaniem pustych komórek tablicy
    if((snake[i][WIERSZ] == 0) || (snake[i][KOLUMNA] == 0)) {
      break;
    }

    // rysowanie poszczególnych segmentów
    ZapalDiode('g', snake[i][WIERSZ], snake[i][KOLUMNA]);
  }

  // sprawdza czy snake zjadł jabłko
  // po najechaniu czerwoną głową na pomarańczowe jabłko to pole to pozostaje czerwone
  // dlatego gaszona jest tylko składowa zielona
  // gaszenie jabłka musi odbywać się w tym miejscu (po narysowaniu węża)
  if(SprJablko()) {
    ZgasDiode('g',jablko[WIERSZ], jablko[KOLUMNA]);
    wynik++;
    MrugnijJablko(1);
    RysujJablko();
  }

  // sprawdza czy snake zjadł sam siebie -> 1 Game Over
  if(SnakeOver()) {
    ZgasDiode('g', snake[0][WIERSZ], snake[0][KOLUMNA]);
    MrugnijOver(5);
    wyczysc_snake();
    game_over = 1;
  }

}


void przesun_snake(uint8_t nowe_pole_wiersz, uint8_t nowe_pole_kolumna)
{
  uint8_t i;

  for(i=(dlugosc-1) ; i>0 ; i--) {
    snake[i][WIERSZ] = snake[i-1][WIERSZ];
    snake[i][KOLUMNA] = snake[i-1][KOLUMNA];
  }
  snake[0][WIERSZ] = nowe_pole_wiersz;
  snake[0][KOLUMNA] = nowe_pole_kolumna;
}


void wyczysc_snake(void)
{
  // gasi głowę
  ZgasDiode('r', snake[0][WIERSZ], snake[0][KOLUMNA]);

  // gasi resztę ciała
  uint8_t i;
  for(i=1 ; i<dlugosc ; i++) {
    ZgasDiode('g', snake[i][WIERSZ], snake[i][KOLUMNA]);
  }
}

void kierunek_snake(void)
{
  char kierunek_zatrzask;

  // zatrzaśnięcie kierunku, żeby nie dało się go zmienić
  // pomaga w zabezpieczeniu przed skręceniem w siebie
  kierunek_zatrzask = kierunek;

  switch(kierunek_zatrzask) {
  case 'p' : {
    if(poprzedni_kierunek != 'l') {
      if(snake[0][KOLUMNA] != 8) {
        przesun_snake(snake[0][WIERSZ], (snake[0][KOLUMNA]+1));
      } else {
        przesun_snake(snake[0][WIERSZ], 1);
      }

      // zabezpieczenie przed skręceniem w siebie
      poprzedni_kierunek = kierunek_zatrzask;
    } else {
      // jeżeli idziesz w prawo i naciśniesz przycisk w lewo to snake idzie dalej w prawo
      if(snake[0][KOLUMNA] != 1) {
        przesun_snake(snake[0][WIERSZ], (snake[0][KOLUMNA]-1));
      } else {
        przesun_snake(snake[0][WIERSZ], 8);
      }
    }
    break;
  }
  case 'l' : {
    if(poprzedni_kierunek != 'p') {
      if(snake[0][KOLUMNA] != 1) {
        przesun_snake(snake[0][WIERSZ], (snake[0][KOLUMNA]-1));
      } else {
        przesun_snake(snake[0][WIERSZ], 8);
      }

      // zabezpieczenie przed skręceniem w siebie
      poprzedni_kierunek = kierunek_zatrzask;
    } else {
      if(snake[0][KOLUMNA] != 8) {
        przesun_snake(snake[0][WIERSZ], (snake[0][KOLUMNA]+1));
      } else {
        przesun_snake(snake[0][WIERSZ], 1);
      }
    }
    break;
  }
  case 'g' : {
    if(poprzedni_kierunek != 'd') {
      if(snake[0][WIERSZ] != 1) {
        przesun_snake((snake[0][WIERSZ]-1), snake[0][KOLUMNA]);
      } else {
        przesun_snake(8, snake[0][KOLUMNA]);
      }

      // zabezpieczenie przed skręceniem w siebie
      poprzedni_kierunek = kierunek_zatrzask;
    } else {
      if(snake[0][WIERSZ] != 8) {
        przesun_snake((snake[0][WIERSZ]+1), snake[0][KOLUMNA]);
      } else {
        przesun_snake(1, snake[0][KOLUMNA]);
      }
    }
    break;
  }
  case 'd' : {
    if(poprzedni_kierunek != 'g') {
      if(snake[0][WIERSZ] != 8) {
        przesun_snake((snake[0][WIERSZ]+1), snake[0][KOLUMNA]);
      } else {
        przesun_snake(1, snake[0][KOLUMNA]);
      }

      // zabezpieczenie przed skręceniem w siebie
      poprzedni_kierunek = kierunek_zatrzask;
    } else {
      if(snake[0][WIERSZ] != 1) {
        przesun_snake((snake[0][WIERSZ]-1), snake[0][KOLUMNA]);
      } else {
        przesun_snake(8, snake[0][KOLUMNA]);
      }
    }
    break;
  }
  }


}

void Losuj_init(void)
{
  // ustawienie zarodka dla funkcji losującej
  uint16_t *pmem;
  uint16_t seed = 0;
  uint16_t n;
  pmem = (uint16_t *)0;
  for(n=0; n<(RAMEND+1)/2; n++) {
    seed ^= *pmem++;
  }
  srand(seed);
}

uint8_t Losuj(void)
{
  Losuj_init();
  static uint8_t k;
  k = rand() % 8 + 1;

  return k;
}

void RysujJablko(void)
{
  uint8_t i, pomoc=1;

  while(pomoc) {
    jablko[WIERSZ] = Losuj();
    jablko[KOLUMNA] = Losuj();

    pomoc=0;
    for(i=0 ; i < 64 ; i++) {
      // zabezpieczenie przed wylosowaniem jabłka na wężu
      if((jablko[WIERSZ] == snake[i][WIERSZ]) &&
          (jablko[KOLUMNA] == snake[i][KOLUMNA])) {
        pomoc=1;
      }
      if((snake[i][WIERSZ] == 0) && (snake[i][KOLUMNA] == 0)) {
        break;
      }
    }
  }
  ZapalDiode('o', jablko[WIERSZ], jablko[KOLUMNA]);
}


uint8_t SprJablko(void)
{
  if((snake[0][WIERSZ] == jablko[WIERSZ]) &&
      (snake[0][KOLUMNA] == jablko[KOLUMNA])) {
    // przedłużenie snake -> zwiększenie długości + przesunięcie w miejscu
    dlugosc++;
    przesun_snake(snake[0][WIERSZ], snake[0][KOLUMNA]);
    return 1;
  }
  return 0;
}


void MrugnijJablko(uint8_t liczba)
{
  uint8_t a, i;
  for(a=0 ; a < liczba ; a++) {
    for(i=0 ; i <= speed/5 ; i++) {
      _delay_ms(1);
    }
    wyczysc_snake();
    BuzzerPik(1);
    for(i=0 ; i <= speed/10 ; i++) {
      _delay_ms(1);
    }
    // narysowanie głwoy
    ZapalDiode('r', snake[0][WIERSZ], snake[0][KOLUMNA]);

    // narysowanie reszty ciała
    uint8_t i;
    for(i=1 ; i<64 ; i++) {
      // zabezpieczenie przed rysowaniem pustych komórek tablicy
      if((snake[i][WIERSZ] == 0) || (snake[i][KOLUMNA] == 0)) {
        break;
      }

      // rysowanie poszczególnych segmentów
      ZapalDiode('g', snake[i][WIERSZ], snake[i][KOLUMNA]);
    }
    ZgasDiode('g',jablko[WIERSZ], jablko[KOLUMNA]);
  }
}

uint8_t SnakeOver(void)
{
  uint8_t i;

  for(i=3 ; i<64 ; i++) {
    if((snake[i][WIERSZ] == snake[0][WIERSZ]) &&
        (snake[i][KOLUMNA] == snake[0][KOLUMNA])) {
      return 1;
    }
    if((snake[i][WIERSZ] == 0) && (snake[0][KOLUMNA] == 0)) {
      return 0;
    }
  }
  return 0;
}

void MrugnijOver(uint8_t liczba)
{
  uint8_t a;
  for(a=0 ; a < liczba ; a++) {
    BuzzerPik(1);
    wyczysc_snake();
    _delay_ms(100);
    // narysowanie głwoy
    ZapalDiode('r', snake[0][WIERSZ], snake[0][KOLUMNA]);

    // narysowanie reszty ciała
    uint8_t i;
    for(i=1 ; i<64 ; i++) {
      // zabezpieczenie przed rysowaniem pustych komórek tablicy
      if((snake[i][WIERSZ] == 0) || (snake[i][KOLUMNA] == 0)) {
        break;
      }

      // rysowanie poszczególnych segmentów
      ZapalDiode('g', snake[i][WIERSZ], snake[i][KOLUMNA]);
    }
    ZgasDiode('g', snake[0][WIERSZ], snake[0][KOLUMNA]);
  }
  ZgasDiode('o', jablko[WIERSZ], jablko[KOLUMNA]);
}


void UstawPredkosc(void)
{
  Timer1_init();

  WskaznikPredkosci(moc);

  start = 0;
  while(!start) {
    if(kierunek == 'p') {
      BuzzerPik(1);
      _delay_ms(100);
      kierunek = 'x';
      moc++;
      if(moc>8) {
        moc = 8;
      }
      WskaznikPredkosci(moc);
      speed = tab_speed[moc-1];
    }

    if(kierunek == 'l') {
      BuzzerPik(1);
      _delay_ms(100);
      kierunek = 'x';
      moc--;
      if(moc<1) {
        moc = 1;
      }
      WskaznikPredkosci(moc);
      speed = tab_speed[moc-1];
    }
  }
  BuzzerPik(2);
  start = 0;
  TIMSK &= ~(1<<OCIE1A); // brak zezwolenia na przerwanie Compare Math
  WyczyscEkran();
}

void WskaznikPredkosci(uint8_t wskaz) // wskaz = 1..8
{
  uint8_t i;

  for(i = 1 ; i <= wskaz ; i++) {
    ZapalDiode('r', 5, i);
    ZapalDiode('r', 6, i);

    ZgasDiode('g', 5, i);
    ZgasDiode('g', 6, i);
  }

  for(i = (wskaz+1) ; i <= 8 ; i++) {
    ZapalDiode('g', 5, i);
    ZapalDiode('g', 6, i);

    ZgasDiode('r', 5, i);
    ZgasDiode('r', 6, i);
  }
}


void WyswietlWynik(void)
{
  // dla początkowej inicjalizacji pamięci EEPROM
  // po wgraniu programu należy w pierwszej kolejności zagrać na poziomie 5
  // żeby pamięć nie zerowała się
  uint8_t a = 0;
  if((eeprom_read_byte(&rekord_poziom5) < 0) ||
      (eeprom_read_byte(&rekord_poziom5) > 64)) {
    eeprom_write_byte(&rekord_poziom1, a);
    eeprom_write_byte(&rekord_poziom2, a);
    eeprom_write_byte(&rekord_poziom3, a);
    eeprom_write_byte(&rekord_poziom4, a);
    eeprom_write_byte(&rekord_poziom5, a);
    eeprom_write_byte(&rekord_poziom6, a);
    eeprom_write_byte(&rekord_poziom7, a);
    eeprom_write_byte(&rekord_poziom8, a);
  }

  uint8_t i, rekord=0, rek=0;
  uint8_t Rezultat[12] = { 0,0,0,0,0,0,0,0,0,0,0,0 };

  switch(moc) {
  case 1 :
    rekord = eeprom_read_byte(&rekord_poziom1);
    if(wynik > rekord) {
      rek = 1;
      eeprom_write_byte(&rekord_poziom1, wynik);
    }
    break;
  case 2 :
    rekord = eeprom_read_byte(&rekord_poziom2);
    if(wynik > rekord) {
      rek = 1;
      eeprom_write_byte(&rekord_poziom2, wynik);
    }
    break;
  case 3 :
    rekord = eeprom_read_byte(&rekord_poziom3);
    if(wynik > rekord) {
      rek = 1;
      eeprom_write_byte(&rekord_poziom3, wynik);
    }
    break;
  case 4 :
    rekord = eeprom_read_byte(&rekord_poziom4);
    if(wynik > rekord) {
      rek = 1;
      eeprom_write_byte(&rekord_poziom4, wynik);
    }
    break;
  case 5 :
    rekord = eeprom_read_byte(&rekord_poziom5);
    if(wynik > rekord) {
      rek = 1;
      eeprom_write_byte(&rekord_poziom5, wynik);
    }
    break;
  case 6 :
    rekord = eeprom_read_byte(&rekord_poziom6);
    if(wynik > rekord) {
      rek = 1;
      eeprom_write_byte(&rekord_poziom6, wynik);
    }
    break;
  case 7 :
    rekord = eeprom_read_byte(&rekord_poziom7);
    if(wynik > rekord) {
      rek = 1;
      eeprom_write_byte(&rekord_poziom7, wynik);
    }
    break;
  case 8 :
    rekord = eeprom_read_byte(&rekord_poziom8);
    if(wynik > rekord) {
      rek = 1;
      eeprom_write_byte(&rekord_poziom8, wynik);
    }
    break;
  }

  if(rek == 1) {
    rek = 0;
    Rezultat[0] = 18; // R
    Rezultat[1] = 13; // e
    Rezultat[2] = 11; // c
    Rezultat[3] = 16; // o
    Rezultat[4] = 17; // r
    Rezultat[5] = 12; // d
    Rezultat[6] = 20; // spacja
    Rezultat[7] = wynik/10;
    Rezultat[8] = wynik%10;
    Rezultat[9] = 21; // /
    Rezultat[10] = rekord/10;
    Rezultat[11] = rekord%10;
    BuzzerPik(2);
    while(!start) {
      for(i = 1 ; i <= moc ; i++) {
        ZapalDiode('r', 8, i);
      }

      for(i = (moc+1) ; i <= 8 ; i++) {
        ZapalDiode('g', 8, i);
      }

      start = WyswietlTekst(Rezultat,12);
      WyczyscEkran();
    }
  } else {
    Rezultat[0] = wynik/10;
    Rezultat[1] = wynik%10;
    Rezultat[2] = 21; // /
    Rezultat[3] = rekord/10;
    Rezultat[4] = rekord%10;
    while(!start) {
      for(i = 1 ; i <= moc ; i++) {
        ZapalDiode('r', 8, i);
      }

      for(i = (moc+1) ; i <= 8 ; i++) {
        ZapalDiode('g', 8, i);
      }

      WyswietlTekst(Rezultat,5);
      WyczyscEkran();
    }
  }
  start=0;
}


void Timer1_init(void)
{
  TCCR1B |= (1<<WGM12); // tryb CTC
  TCCR1B |= (1<<CS12) | (1<<CS20); // preskaler 1024
  // 8000000/1024/200 = 39 przerwanie co 5ms(200Hz)
  OCR1A = 39; // rejestr porównawczy
  TIMSK |= (1<<OCIE1A); //zezwolenie na przerwanie Compare Math
}


ISR(TIMER1_COMPA_vect)
{
  static uint16_t zliczaj = 0;
  static uint8_t numer = 1;

  zliczaj = zliczaj + 5;

  if(zliczaj >= speed) {
    zliczaj = 0;
    switch(numer) {
    case 7 :
      ZgasDiode('r', 2, 2);
      ZgasDiode('g', 2, 1);
      ZgasDiode('g', 2, 8);
      ZapalDiode('r', 2, 3);
      ZapalDiode('g', 2, 2);
      ZapalDiode('g', 2, 1);
      break;
    case 8 :
      ZgasDiode('r', 2, 3);
      ZgasDiode('g', 2, 2);
      ZgasDiode('g', 2, 1);
      ZapalDiode('r', 2, 4);
      ZapalDiode('g', 2, 3);
      ZapalDiode('g', 2, 2);
      break;
    case 1 :
      ZgasDiode('r', 2, 4);
      ZgasDiode('g', 2, 3);
      ZgasDiode('g', 2, 2);
      ZapalDiode('r', 2, 5);
      ZapalDiode('g', 2, 4);
      ZapalDiode('g', 2, 3);
      break;
    case 2 :
      ZgasDiode('r', 2, 5);
      ZgasDiode('g', 2, 4);
      ZgasDiode('g', 2, 3);
      ZapalDiode('r', 2, 6);
      ZapalDiode('g', 2, 5);
      ZapalDiode('g', 2, 4);
      break;
    case 3 :
      ZgasDiode('r', 2, 6);
      ZgasDiode('g', 2, 5);
      ZgasDiode('g', 2, 4);
      ZapalDiode('r', 2, 7);
      ZapalDiode('g', 2, 6);
      ZapalDiode('g', 2, 5);
      break;
    case 4 :
      ZgasDiode('r', 2, 7);
      ZgasDiode('g', 2, 6);
      ZgasDiode('g', 2, 5);
      ZapalDiode('r', 2, 8);
      ZapalDiode('g', 2, 7);
      ZapalDiode('g', 2, 6);
      break;
    case 5 :
      ZgasDiode('r', 2, 8);
      ZgasDiode('g', 2, 7);
      ZgasDiode('g', 2, 6);
      ZapalDiode('r', 2, 1);
      ZapalDiode('g', 2, 8);
      ZapalDiode('g', 2, 7);
      break;
    case 6 :
      ZgasDiode('r', 2, 1);
      ZgasDiode('g', 2, 8);
      ZgasDiode('g', 2, 7);
      ZapalDiode('r', 2, 2);
      ZapalDiode('g', 2, 1);
      ZapalDiode('g', 2, 8);
      break;
    }
    numer++;
    if(numer>8) {
      numer=1;
    }
  }
}



main.c

// *** SNAKE ***
// Piotr Nawrocki

#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>

#include "matryca_led.h"
#include "przyciski.h"
#include "snake.h"
#include "buzzer.h"

int main(void)
{
  uint16_t i, speed_pomoc;
  uint8_t Snake[] = { 19, 15, 10, 14, 13 };

  MatrycaLED_init();
  Przyciski_init();
  Buzzer_init();

  BuzzerPik(1);
  start = WyswietlTekst(Snake, 5);
  WyczyscEkran();

  UstawPredkosc();

  while(1) {
    poczatek();
    while(!game_over) {
      rysuj_snake();
      if(gazu) {
        speed_pomoc = speed/3;
        if((PINC & PRZYCISK_SPEED)) {
          gazu = 0;
        }
      } else {
        speed_pomoc = speed;
      }

      for(i=0 ; i <= speed_pomoc ; i++) {
        _delay_ms(1);
      }
    }
    BuzzerPik(1);
    WyswietlWynik();
    game_over = 0;
  }
}

AVR Memory Usage
----------------
Device: atmega8

Program:    5160 bytes (63.0% Full)
(.text + .data + .bootloader)

Data:        341 bytes (33.3% Full)
(.data + .bss + .noinit)

EEPROM:        8 bytes (1.6% Full)
(.eeprom)


Oceń artykuł.
Wasze opinie są dla nas ważne, gdyż pozwalają dopracować poszczególne artykuły.
Pozdrawiamy, Autorzy
Ten artykuł oceniam na:

5 komentarzy:

  1. Na schemacie nie jest podane co to za układ IC3.

    OdpowiedzUsuń
    Odpowiedzi
    1. W załączonych plikach Eagle można uzyskać informację, że jest to ULN2803.

      Usuń
  2. Ile program zajmuje pamięci w mikrokontrolerze?

    OdpowiedzUsuń
    Odpowiedzi
    1. Dodałem na końcu tabelkę z info kompilacji.

      Usuń

Działy
Działy dodatkowe
Inne
O blogu




Dzisiaj
--> za darmo!!! <--
1. USBasp
2. microBOARD M8


Napisz artykuł
--> i wygraj nagrodę. <--


Co nowego na blogu?
Śledź naszego Facebook-a



Co nowego na blogu?
Śledź nas na Google+

/* 20140911 Wyłączona prawa kolumna */
  • 00

    dni

  • 00

    godzin

  • :
  • 00

    minut

  • :
  • 00

    sekund

Nie czekaj do ostatniego dnia!
Jakość opisu projektu także jest istotna (pkt 9.2 regulaminu).

Sponsorzy:

Zapamiętaj ten artykuł w moim prywatnym spisie treści.