Individální projekty MPOA

Mikroprocesory s architekturou ARM

Uživatelské nástroje

Nástroje pro tento web


2014:led-tetris

Tetris na RGB LED matici

Zadanie:

Realizujte pomocí FRDM-KL25Z a RG LED matice 8×8 hru Tetris ovládanou akcelerometrem. Při realizaci využijte barevnost matice (např. každá kostka bude mít jinou barvu).


TETRIS

Čo to ten tetris vlastne je: (naozaj sa mi stalo, že to niekto nevedel)


Hardware

Okrem FRDM-KL25Z (ktorej popis sem nebudem dávať, pretože google má doma teraz už skoro každý) a RG LED matice 8×8, ktorá má tak trochu neznáme zapojenie, ale chovanie sa po pripojení k napájaniu dá relatívne jednoducho zistiť (o čo sa postaral Tomáš Jankech), som vytvoril redukciu medzi túto maticu a KL25Z (Redukcia matice 8x8 pre KL25Z).

Čo sa týka redukcie, dôvodov prečo nie je úplne ideálne navrhnutá je niekoľko. Jednak som čakal že bude doska vyrobená s prekovmi, ale kôli rozloženiu pinov na to nebolo moc miesta, za ďalšie som čakal, že pri použití mbed.org bude možné jednoduché maskovanie pinov, preto som sa snažil o zapojenie spodnej časti pinov na jednu farbu a vrchnú časť pinov na druhú, čo sa nakoniec ukázalo ako zbytočné.

Nakoniec som ešte „nabastlil“ dva 68k rezistory (hodnota daná „šuplíkom“), vytvoril tak napäťový delič a pripojil ich k PTB3 čož je A/D vstup. Tým som si môhol ďalej v programe vytvoriť random generátor pre výber segmentov. Napätie Vcc je 3,3V. V tomto projekte sa z redukcie nevyužíva UART a ani tlačítka, nakoľko by boli úplne zbytočné.

Firmware

Firmware je písaný v mbed.org. Celý zdroják je možné nájsť tu Nasledujúce riadky popisujú ako celý projekt funguje.

MAIN

Funkciu celeho systému sa pokúsim vysvetliť pomocou hlavnej funkcie:

int main() {
    SegmentInit();                          //nahranie informacii do struktur segmentov (struct segments)
    OutPortInit();                          //porty vypnute - zhasnuta matica
    disptick.attach(&Display, 0.002);       //nastavenie prerusenia pre vysvecovanie displeja
    while(1) {
            choice = RandSegment();         //nahodny vyber segmentu
            while(collision != 1){          //pokial nenastane kolizia spodnej hrany segmentu
                SegmentMove(&seg[choice]);  //pohybuj segmentov - v pravo, v lavo, dole, rotacia
            }
            LineCheck();                    //skontroluje riadoky ci nie su zaplnene ak hej posunie sa vrchna cast dole
            collision = 0;                  
    }
}

Skôr než budú vysvetlené celkova funkcia je nutné podotknúť, že pohyby segmentov, ich ukladadanie prípadne sledovanie kolízií sa sleduje v matici int dispArray[8][16] na začiatku plnej núl. Riadkov je 8, keďže LED matica je 8×8, no 16 stĺpcov je definovaných z dôvodu, že každý segment si nesie farbu ako vlastnosť. V tomto prípade sú segmenty definované pevne nasledovným spôsobom:

int segment9[2][4] = {{1,1, 1,1},   //oranzova
                     {1,1, 0,0},};

Tento segment sa bude po zobrazení javiť ako oranžové L otočené o 90° do prava. Ak by boli páry v poli definované ako 0,1 išlo by o červený segment, ak 1,0 islo by o zelený segment. Pár 0,0 sa nevysvieti. Všetky segmenty sú definované pevne, ale na základe tohto princípu by sa dalo napríklad jednoducho pomocou náhodnej generácie čísel nulovať každý prvý, druhý alebo žiadny člen poľa a meniť tak farby. Túto možnosť som nevyužil nakoľko je značne jednoduchšie inicializovať všetky polia v štruktúre pred začatím behu samotnej hry a ďalej s nimi pracovať. Základných segmentov je 12, pričom sa jedná o 4 tvary s 3 rôznymi farebnými kombináciami. Tvary sú kocka, L, I a bodka.


SegmentInit();

Stará sa nahranie polí a informácií do poľa štruktúry:

void SegmentInit(void){
    //...
    seg[6].line = 1;    
    seg[6].colum = 4;
    seg[6].nextRot = 12;
    SegmentCopy(&seg[6], segment6);  //oranzova ciara 
    seg[12].line = 2;    
    seg[12].colum = 2;
    seg[12].nextRot = 6;
    SegmentCopy(&seg[12], segment61);  //oranzova ciara rot
    //...
}

Jedná sa len o názornú ukážku funkcie, takže nie je uvedená celá. Ako je vidno, štruktúra obsahuje počet riadkov, stĺpcov segmentu, číslo poľa v prípade rotovania segmentu a samotný segment, ktorý sa kopíruje do poľa definovaného v štruktúre pomocou funkcie SegmentCopy, pričom ta obsahuje len 2 for cykly.

OutPortInit();

Jedná sa o jednoduchú funkciu, ktorá privedie na porty ovládajúce farby úroveň 1, aby bola LED matica v počiatočnom stave zhasnutá.

disptick.attach(&Display, 0.002);

Jedná sa o časové prerušenie, volané každé 2ms. Je ňou volaná funkcia Display, ktorá pri volaní vysvieti jeden riadok matice zároveň sa inkrementuje riadok, ktorý sa bude vysvecovať pri ďalšom prerušení. Je to veľmi jednoduchá funkcia, takže pravdepodobne nie je nutné ju vpisovať do tohoto dokumentu.


choice = RandSegment();

Z funkcie sa do globálnej premennej int choice; vráti náhodná hodnota. Tá je získaná jednak vyššie popisovaným deličom a ďalej zvyškom funkcie:

short RandSegment(void){                    
    static short num = 0;
    num = randAdc.read_u16()%12;    //precitanie A/D s %12 - zvysok od 0 do 11 <-> zakladne segmenty od 0 do 11
    return(num);                            
}

kde randAdc.read_u16() číta 16 bitovú hodnotu z A/D prevodníka. Modulo 12 zaistí, že vrátená hodnota bude v rozsahu od 0 do 11. Tento rozsah je daný počtom základných segmentov.


SegmentMove(&seg[choice]);

Táto funkcia sa stará v podstate o celú hrateľnosť. Beží v cykle pokial premenna collision nebude rovná 1. Tento stav nastane pokiaľ segment dopadne na spodnú časť poľa alebo na vysvietenú časť. Nakoľko je funkcia pomerne dlhá bude jednoduchšie vysvetliť ako funguje. Funkcia pracuje s premennými x a y, ktoré predstavujú súradnice lavého horného segmentu, pričom y predstavuje riadok, x predstavuje stĺpec.

Každý nový segment sa vždy objaví na súradnici y=0, x=6.

Od tejto súradnice sa v dvoch for cykloch (riadok, stĺpec) skontroluje, či sa neprekryje nový segment s vysvietenou časťou (čiže predchádzajúce segmenty, prípadne ich zvyšky). Ak dojde ku kolízií pole int dispArray[8][16] sa vynuluje a začína nová hra. Ak nenastane kolízia, segment sa na určitu dobu zapíše to tohoto poľa a tým sa vysvieti.

V tomto momente sa začína inkrementovať premenná counter. Vždy keď je counter násobkom 4000, testuje sa návratová hodnota z funkcie pre obsluhu akcelerometra, pričom plne dostačujúce sú hodnoty z osí x a y. Podľa návratovej hodnoty z funkcie obsluhujúcej akcelerometer sa rozhodne:

  • či sa má segment pohnúť v pravo - realizované náklonom dosky do prava
  • či sa má segment pohnúť v ľavo - realizované náklonom dosky do ľava
  • či má segment prudko klesať na dol - náklon dosky k sebe
  • či má segment rotovať - náklon od seba

Ak vyhovie niektorá z podmnienok, vymaže sa segment z poľa, odtestuje sa či segment po zmene súradníc, prípadne po orotovaní nezmizne z poľa, pripadne sa neprekryje s vysvietenou časťou. Ak je všetko v poriadku zmenia sa súradnice/rotuje sa a segment sa znova zapiše do poľa. To neplatí ale pre klesanie na dol. Rotácia berie ako východzí bod súradnice x, y. Od tohto bodu sa do matice vpíše nový rotovaný segment, ak sa splnia podmienky a nedôjde ku kolízií/vybehnutí z poľa.

Pri klesaní nadol sa do counter nahrá hodnota 400001 (hodnota daná pozorovaním - subjektívne dobrá hrateľnosť pri optimálnej obtiažnosti). To z dôvodu, že ak counter == 400001, inkrementuje sa súradnica y, skontroluje sa, či nedojde ku kolízií s vysvietenou časťou alebo segment nevypadne z poľa. Taktiež dochádza k vymazaniu a zápisu segmentu do poľa. V tejto časti sa teda rozhoduje o tom či:

  • segment sa posunie o riadok nižšie a funkcia je v cykle znova volaná
  • segment nemôže posunúť nižšie - segment sa na tejto pozicií zapíše do poľa, do collision sa zapíše 1 a tak sa vystúpi z cyklu while(collision != 1){…

LineCheck();

Funkcia skontroluje, či po dopade segmentu nie je zaplnený riadok. Ak je riadok zaplnený vymaže ho a časť nad týmto riadkom presunie smerom na dol.

void LineCheck(void){
    static int ch=0;
    static int lnCh, colCh;
 
    for(lnCh = 0; lnCh < 8; lnCh++){
        ch = 0;
        for(colCh=0; colCh < 15; colCh+=2){
            if((dispArray[lnCh][colCh]==1) || (dispArray[lnCh][colCh+1]==1)){       //ak je bod vysvieteny
                ch++;}                                                              //pripocita k premennej
            else {
                colCh = 15;       //ak bod v riadku nie je vysvieteny nema cenu ho cely kontrolovat a skoci sa 
                                  //tak na dalsi riadok
                ch = 0;
                }    
        }
        if((ch == 8)){                               //ak je cely riadok vysvieteny
            for(colCh=0; colCh < 16; colCh++){       //nuluj riadok
                dispArray[lnCh][colCh] = 0;
            }
            MoveDown(dispArray, lnCh);  //posunie zvysok pola dole a kontroluje sa zvysok a teda proces sa
                                        // opakuje kym sa neskontroluje cele pole
        }
    }    
}

Zoznam všetkých funkcií

podrobnejšie sú tieto funkcie, tak ako celý firmware popísanétu

// prototypy funkcii
short RandSegment(void);                                            //funkcia nahodne vybera novy segment pomocou citania A/D vstupu
void SegmentInit(void);                                             //prida jednotlivym segmentom informacie o ich vlastnostiach - pocet riadkov, stlpcov, rotovany segment, priradi pole s vlastnostou farby
void SegmentCopy(struct segments *psgm, int segmentIn[2][4]);       //v SegmentInit skopiruje vytvorene pole segmentu do pola v strukture (struct segments)
void MoveDown(int array[8][16], int line);                          //posun pola smerom dole po odstranení zaplneneho riadku
void LineCheck(void);                                               //funkcia kontroluje zaplneny riadok
void SegmentMove(struct segments *psgm);                            //funkcia sa stara o pohyby segmentu
void WriteClrSeg(int wrtEnab, struct segments *psgm);               //zmazanie alebo zapis segmentu pri pohybe maticou
int DecLvl(void);                                                   //obsluha akcelerometra  (preco prave Dec som zabudol)
void LineAdd(int line);                                             //adresacia riadkov
void Display(void);                                                 //rozsvecovanie farieb v riadkoch podla hlavnej matice
void OutPortInit(void);                                             //nastavenie portov do pociatocneho stavu - vypnutie/zhasnutie

Video


Záver

Zadaním bolo realizovať hru Tetris pomocou FRDM-KL25Z a RG LED matice 8×8 s využitím farebnosti matice. Ovládanie bolo realizované pomocou akcelerometra.

Zadanie sa podarilo splniť v plnom rozsahu. Program bol odladený od drobných chýb takže hra je plne hrateľná, odskúšaná a relatívne náročná na ovládanie, čo jej prídáva na atraktivite. Drobnosti ako skóre, zvyšovanie rýchlosti po dosiahnutí určitej úrovne a podobne neboli pridané. Jednak je matica relatívne malá, takže už po pár segmentoch môže byť pole plne obsadené a ovládanie je už beztak dostatočne náročné, takže nízke skóre skôr odradí. Zato je hra vybavená random generátorom, takže je o to viac zaujímavejšia.

Malou nevýhodou je, že ak je pri pohybe s doskou jemne buchnuté, môže akcelerometer zaznamenať vyššiu hodnotu v niektorej osi (viz video 0:20), najpravdepodobnejšie v tej, kde je najväčší náklon. Riešenie je jednoduché: stačí mať hru vždy vo vzduchu.

2014/led-tetris.txt · Poslední úprava: 2015/01/18 22:07 autor: Matej Hojdík