[CZĘŚĆ 1] STM32Butterfly2: Tetris na STM32 – wprowadzenie do mechanizmu gry
W artykule przedstawiamy sposób prostej implementacji na mikrokontrolerze STM32F107 znanej i popularnej od lat gry Tetris. Autor projektu wykorzystał peryferia zestawu STM32Butterfly2 i moduł z tanim monochromatycznym wyświetlaczem LCD z telefonu Nokia 3310.
Sposób połączenia modułu KAmodLCD z zestawem STM32Butterfly2, który zamienia ten zestaw w konsolę do grania, pokazano na rysunku 1. W aplikacji wykorzystano joystick znajdujący się na płytce „motyla”, który dołączono do wyprowadzeń mikrokontrolera w zestawie „na sztywno”. Do zaprogramowania mikrokontrolera potrzebny będzie programator JTAG, można użyć zgodnego z ST-Link/v2 programatora ZL30PRGv2 (fotografia 2). Projekt programistyczny utworzono za pomocą pakietu Atollic TrueSTUDIO Lite.
Rys. 1. Schemat ilustrujący połączenia niezbędne do prawidłowego działania Tetrisa na STM32Butterfly2
Z istniejących plików projektu interesują nas znajdujące się w katalogu src (pliki źródłowe) plik main.c i stm32f10x_it.c. W pierwszym znajduje się główna część programu, w drugim procedury obsługi przerwań.
Fot. 2. Wygląd programatora JTAG/SWD o nazwie ZL30PRGv2
Funkcja main to główna funkcja każdego programu, od niej rozpoczyna się jego wykonywanie. Na jego początku znajdują się procedury konfigurujące peryferia mikrokontrolera. W przykładowym projekcie procedury konfiguracyjne umieszczono w osobnych funkcjach, które kolejno wywołuje na początku funkcji main(), dlatego przed funkcją main należy umieścić deklaracje funkcji:
void RCC_Configuration(void); void GPIO_Configuration(void); void EXTI_Configuration(void); void NVIC_Configuration(void);
Ciała tych funkcji znajdują się na listingu 1.
List. 1. Ciała funkcji inicjujących mikrokontroler
//Funkcje konfiguracyjne //Funkcja konfigurująca porty GPIO void GPIO_Configuration(void) { GPIO_InitTypeDef GPIO_InitStructure; //inicjalizacja struktury konfiguracji GPIO RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE | RCC_APB2Periph_AFIO, ENABLE); //uruchomienie taktowania dla portu GPIOE GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14 | GPIO_Pin_15; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_Init(GPIOE, &GPIO_InitStructure); //ustawienie pinów 14 i 15 portu GPIOE jako wyjść GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10 | GPIO_Pin_11 | GPIO_Pin_12; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING ; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz; GPIO_Init(GPIOE, &GPIO_InitStructure); //ustawienie pinów 8, 9, 10, 11 i 12 portu GPIOE jako wejść } //Funkcja konfigurująca system sygnałów taktowania void RCC_Configuration(void) { ErrorStatus HSEStartUpStatus; RCC_DeInit(); //reset ustawień RCC RCC_HSEConfig(RCC_HSE_ON); //włącz HSE HSEStartUpStatus = RCC_WaitForHSEStartUp(); //oczekiwanie na gotowość HSE if (HSEStartUpStatus == SUCCESS) { FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable); //włączenie buforu dla pamięci Flash FLASH_SetLatency(FLASH_Latency_2); //ustawienie opóźnień dla pamięci Flash RCC_HCLKConfig(RCC_SYSCLK_Div1); //taktowanie rdzenia sygnałem 1:1 (72MHz) RCC_PCLK2Config(RCC_HCLK_Div1); //taktowanie magistrali APB2 sygnałem 1:1 (72MHz) RCC_PCLK1Config(RCC_HCLK_Div2); //taktowanie magistrali APB1 sygnałem 1:2 (36MHz) RCC_PREDIV2Config(RCC_PREDIV2_Div5 ); //ustawienie PLL2 1:5*8 (25MHz/5*8=20Mhz) RCC_PLL2Config(RCC_PLL2Mul_8); RCC_PLL2Cmd(ENABLE); while(RCC_GetFlagStatus(RCC_FLAG_PLL2RDY) == RESET) {} //oczekiwanie na uruchomienie PLL2 RCC_PREDIV1Config(RCC_PREDIV1_Source_PLL2,RCC_PREDIV1_Div5); //ustawienie PLL1 PLL2CLK/5*9 (20MHz/5*9=72MHz) RCC_PLLConfig(RCC_PLLSource_PREDIV1, RCC_PLLMul_9); RCC_PLLCmd (ENABLE); while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET) {} //oczekiwanie na uruchomienie PLL1 RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK); //ustawienie pętli PLL1 jako źródła sygnału zegarowego dla systemu while (RCC_GetSYSCLKSource() != 0x08) {} //oczekiwanie aż pętla PLL1 stanie się źródłem sygnały zegarowego dla sytemu } } //Funkcja konfigurująca GPIO jako źródła przerwań (kontroler EXTI) void EXTI_Configuration(void) { EXTI_InitTypeDef EXTI_InitStructure; //inicjalizacja struktury konfiguracji przerwań EXTI GPIO_EXTILineConfig(GPIO_PortSourceGPIOE, GPIO_PinSource8); //aktywowanie funkcji EXTI pinów joystick'a GPIO_EXTILineConfig(GPIO_PortSourceGPIOE, GPIO_PinSource9); GPIO_EXTILineConfig(GPIO_PortSourceGPIOE, GPIO_PinSource10); GPIO_EXTILineConfig(GPIO_PortSourceGPIOE, GPIO_PinSource11); GPIO_EXTILineConfig(GPIO_PortSourceGPIOE, GPIO_PinSource12); EXTI_InitStructure.EXTI_Line = EXTI_Line8 | EXTI_Line9 | EXTI_Line10 | EXTI_Line11 | EXTI_Line12; EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; //przerwanie przy zboczu opadającym EXTI_InitStructure.EXTI_LineCmd = ENABLE; //uaktywnienie kontrolera EXTI EXTI_Init(&EXTI_InitStructure); } //Funkcja konfigurująca kontroler przerwań NVIC void NVIC_Configuration(void) { NVIC_InitTypeDef NVIC_InitStructure; //inicjalizacja struktury konfiguracji kontrolera przerwań NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1); //wybór grupy priorytetów NVIC_InitStructure.NVIC_IRQChannel = EXTI9_5_IRQn; //konfiguracja przerwania od GPIO piny od 5 do 9 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn; //konfiguracja przerwania od GPIO piny od 10 do 15 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); }