LinkedIn YouTube Facebook
Szukaj

Wstecz
Artykuły

[CZĘŚĆ 2] STM32Butterfly2: Tetris na STM32 – interfejs użytkownika

 

 

 

 

W drugiej części artykułu przedstawiamy budowę interfejsu użytkownika znanej i popularnej od lat gry Tetris, którą autor zaimplementował w mikrokontrolerze STM32F107 z zestawu STM32Butterfly2.

 

 

Interfejs użytkownika składa się z dwóch głównych części:

  • obsługi joysticka,
  • obsługi wyświetlacza graficznego.

Dzięki wcześniej przygotowanej obsłudze przerwań z pinów podłączonych do styków joysticka, bardzo łatwo będzie oprogramować tę część interfejsu.
Na początku zajmijmy się poruszaniem klocka na boki. Będzie to bardzo proste, bo mamy już gotowe funkcje, które to robią. Przed ich wywołaniem musimy tylko sprawdzić czy w zmiennej stan_gry zapisana jest wartość GRA. Zapobiegnie to przesuwaniu klocka podczas pauzy lub po zakończeniu gry. W funkcji przerwania EXTI15_10_IRQHandle w obrębie warunku sprawdzającego czy joystick był naciśnięty w prawo, dodajemy:

  if(stan_gry==GRA)				//Jeżeli gra włączona
  {
    KlocekPrawo();				//przesuń klocek w prawo
  }

Analogiczny wpis dodajemy dla ruchu w lewo i dół, nie zapominając o dodaniu deklaracji (z modyfikatorem extern) zmiennej stan_gry na początku funkcji.
Do funkcji EXTI9_5_IRQHandler dodamy natomiast wywołanie funkcji obracającej klocek, jeżeli joystick zostanie przesunięty w górę. Tu także dodajemy definicję zmiennej stan_gry. Natomiast na początek pliku z funkcjami przerwań kopiujemy deklaracje nazw wartości dla tej zmiennej (NITRO, GRA… itd.).
Do obsługi wyświetlacza można zaadoptować jedna z wielu dostępnych gotowych bibliotek. Ja napisałem swoją od podstaw tak, aby była jak najprostsza. Nie będę tu wnikał zbytnio w szczegóły, zainteresowani sami prześledzą funkcje wyświetlania grafiki. Jeżeli chcesz skorzystać z mojej biblioteki to dodaj pliki NokiaLCD.c i NokiaLCD.h do projektu, a na początku pliku main.c powinno się znaleźć:

#include "NokiaLCD.h"

W ten sposób kompilator uzyska informację o wszystkich funkcjach biblioteki. Przed korzystaniem z wyświetlacza należy jeszcze wywołać funkcje LcdInit. Przygotowuje ona sprzęt do pracy. Najlepiej zrobić to zaraz po konfiguracji peryferiów mikrokontrolera.
Skupmy się teraz na funkcji, która rysuje ekran z planszą gry. W swoim projekcie nazwałem ją RysujEkran. Na początek wywołujemy funkcję LcdClear z naszej biblioteki graficznej, w celu usunięcia poprzedniej zawartość ekranu. W kolejnym kroku rysujemy na ekranie wszystkie stałe elementy tła (rysunek 5).

 

Rys. 5. Wygląd bitmapy tła

Rys. 5. Wygląd bitmapy tła

 

 

W katalogu projektu stworzyłem nowy plik grafika.h i tak skopiowałem tablicę nazywając ją:

  const unsigned char tlo[]={
  0xC4,0x15,0xFD,0x06,0x04,0x04,0x05,0x05,0x04,0x06,0x06,0x04,0x05,0x06,0x04,0x04,
  0x06,0x05,0x04,0x06,0x06,0x05,0x04,0xFD,0x0C,0xF2,0x04,0x35,0x58,0x44,0x84,0x11,
  .
  .  //kolejne 504 bajty grafiki (fragment)
  .
  0x0A,0x0A,0x0A,0x2B,0x0C,0x0F,0x00,0x00
  };

Tak przygotowaną grafikę wyświetla się na ekranie funkcją LcdLoadBMP. Teraz wystarczy narysować samą planszę, spadający klocek, klocek jaki będzie następny oraz wynik gracza. Jednak zanim napiszemy resztę funkcji proponuję zdefiniować współrzędne każdego z tych elementów. Przyda się to nam jak będziemy chcieli w przyszłości zmienić układ graficzny. Moje definicje wyglądają tak:

#define POFFX 3	//wsp. X górnego lewego rogu planszy
#define POFFY 3	//wsp. Y górnego lewego rogu planszy
#define NKOFFX 30	//wsp. X górnego lewego rogu miejsca wyświetlania następnego klocka
#define NKOFFY 31	//wsp. Y górnego lewego rogu miejsca wyświetlania następnego klocka
#define WOFFX 9	//wsp. X wyniku
#define WOFFY 2	//wsp. Y wyniku
#define LOFFX 9	//wsp. X ilości linii
#define LOFFY 5	//wsp. Y ilości linii

Najpierw narysujemy stałe segmenty klocków na planszy. Przy tak niewielkiej rozdzielczości wyświetlacza postanowiłem, że każdy segment klocka będzie składał się z czterech pikseli. Aby narysować całą planszę musimy sprawdzić każdą z komórek tablicy plansza, czyli zastosujemy dwie pętle i warunek. Jeżeli dana komórka będzie miała wartość 1 to należy zapalić odpowiadające jej cztery piksele na ekranie. Ten fragment kodu napisałem w ten sposób:

  for(y=0;y<20;y++)				//odliczanie wierszy pamięci planszy
  for(x=0;x<10;x++)				//odliczanie kolumn
  {
   if(plansza[x][y])				//jeżeli dany punkt na planszy jest zapalony
   {
  	 LcdPixel(OFFX+x*2, POFFY+y*2, PIXEL_ON);	//rysowanie kolejnych 4 pikseli punktu
  	 LcdPixel(OFFX+x*2+1, POFFY+y*2, PIXEL_ON);
  	 LcdPixel(OFFX+x*2, POFFY+y*2+1, PIXEL_ON);
  	 LcdPixel(OFFX+x*2+1, POFFY+y*2+1, PIXEL_ON);
   }
  }

Polski portal branżowy dedykowany zagadnieniom elektroniki. Przeznaczony jest dla inżynierów i konstruktorów, projektantów hardware i programistów oraz dla studentów uczelni technicznych i miłośników elektroniki. Zaglądają tu właściciele startupów, dyrektorzy działów R&D, zarządzający średniego szczebla i prezesi dużych przedsiębiorstw. Oprócz artykułów technicznych, czytelnik znajdzie tu porady i pełne kursy przedmiotowe, informacje o trendach w elektronice, a także oferty pracy. Przeczyta wywiady, przejrzy aktualności z branży w kraju i na świecie oraz zadeklaruje swój udział w wydarzeniach, szkoleniach i konferencjach. Mikrokontroler.pl pełni również rolę patrona medialnego imprez targowych, konkursów, hackathonów i seminariów. Zapraszamy do współpracy!