Зображення користувача Андрій Гончаренко.
Андрій Гончаренко
  • Відвідувань: 0
  • Переглядів: 0

STM32: Саймон каже

Якось випадково, коли шукав щось по мікроконтролерам, натрапив на просту і цікаву гру "Simon Says" на платформі ARDUINO. Дуже сподобалась гра. Мінімум деталей, а ігровий процес затягує. Почав шукати в мережі, може є така гра на платформі STM32? Не знайшов абсолютно нічого. Ну що ж, значить робота по портуванню цієї цікавої гри за мною. За основу портування взяв програмний код звідси. З ARDUINO ніколи не мав справу, то ж прийшлось трішки розібратись що до чого, але на загал витратив на порт гри не багато часу. Гра проста. Головне алгоритм! Все інше деталі.

Попередні статті:

STM32: Саймон каже

Передмова

Якось випадково, коли шукав щось по мікроконтролерам, натрапив на просту і цікаву гру "Simon Says" на платформі ARDUINO. Дуже сподобалась гра. Мінімум деталей, а ігровий процес затягує. Почав шукати в мережі, може є така гра на платформі STM32? Не знайшов абсолютно нічого. Ну що ж, значить робота по портуванню цієї цікавої гри за мною. За основу портування взяв програмний код звідси. З ARDUINO ніколи не мав справу, то ж прийшлось трішки розібратись що до чого, але на загал витратив на порт гри не багато часу. Гра проста. Головне алгоритм, все інше деталі.

Необхідні компоненти і деталі

 
  1. Плата розробника STM32VLDISCOVERY - 1 шт
  2. Контактна макетна плата "BreadBoard" 
  3. З'єднувальні дроти
  4. USB шнур - 1 шт.
  5. Зумер "Buzzer" - 1 шт.
  6. Тактильні мікрокнопки - 4 шт.
  7. Будь який N-P-N транзистор малої потужності - 1 шт.
  8. Світлодіоди, бажано різного кольору - 4 шт.
  9. Резистор 10 кОм - 4 шт.
  10. Резистор 330 Ом - 4 шт.

Електрична схема гри

Електрична схема гри "Саймон каже"

На схемі немає зображення плати розробника STM32VLDiscovery (мікроконтролеру), а вказані назви виводів мікроконтролера з якими потрібно з'єднати елементи схеми.

Зібрана гра на макетній платі:

Алгоритм гри

При подачі живлення, на макет гри, почергово миготять світлодіоди - запрошення до гри. Натиснувши кнопку старт, кнопка USER на самій платі  STM32VLDISCOVERY, гра почергово миготить два рази ігровими світлодіодами зі звуком - сигнал до старту. А потім починається гра. Випадково запалюються світлодіоди зі звуком, з кожним раундом послідовність вогників збільшується. Гра очікує відтворення тієї ж послідовності кнопками, які гравець тисне. Як правильно - рівень збільшується, як ні - гра спочатку.

Програмна реалізація гри

Створюємо новий проект в CooCox IDE. Називаємо його "simon says". Обираємо чип STM32F100RB. Позначаємо в репозиторії бібліотеку GPIO. Переходимо до "main.c". Видаляємо обов'язковий шаблон. Копіюємо до main.c цей текст програми, або завантажуємо файл.
//Гра "Саймон каже" для плати розробника STM32VLDiscovery на чіпові STM32F100RB (порт з платформи ARDUINO)
//Додаємо потрібні файли до проекту
#include "stm32f10x.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_rcc.h"
#include "stdbool.h"
#include "stdlib.h"
//Макроси #define щоб нам було зручно
#define BUZZER_PORT GPIOB //Порт до якого під'єднаний зумер
#define BUZZER GPIO_Pin_9 //До якої ноги під'єднаний зумер

#define BUTTON_PORT GPIOA //Порт до якого підключені кнопки
#define BUTTON_START GPIO_Pin_0 //Кнопка USER на платі STM32VLDiscovery
#define BUTTON_1 GPIO_Pin_1	//Кнопка 1
#define BUTTON_2 GPIO_Pin_2 //Кнопка 2
#define BUTTON_3 GPIO_Pin_3 //Кнопка 3
#define BUTTON_4 GPIO_Pin_4 //Кнопка 4

#define LED_PORT GPIOC //Порт до якого під'єднані світлодіоди
#define LED_4 GPIO_Pin_6 //Світлодіод 4
#define LED_3 GPIO_Pin_7 //Світлодіод 3
#define LED_BLUE GPIO_Pin_8 //Світлодіод голубий на платі STM32VLDiscovery
#define LED_GREEN GPIO_Pin_9 //Світлодіод зелений на платі STM32VLDiscovery
#define LED_2 GPIO_Pin_10 //Світлодіод 2
#define LED_1 GPIO_Pin_11 //Світлодіод 1
#define LED_ALL (LED_BLUE | LED_GREEN | LED_1 | LED_2 | LED_3 | LED_4) //Світлодіоди всі разом

//Позначимо тональність звуку
#define TON1 600
#define TON2 500
#define TON3 400
#define TON4 300

#define	F_APB1 24000000 //Частота таймера в герцах
#define MAX_LEVEL 100	//Максимальний рівень гри
//Оголошення глобальних змінних
int sequence[MAX_LEVEL]; //масив з послідовністю номерів світлодіодів для ігрової ситуації
int your_sequence[MAX_LEVEL]; //Масив з послідовністю натиснутих клавіш
int level = 1;	//Початковий рівень складності
int velocity = 1000; //швидкість
//Оголошення всих прототипів функцій самі функції розташовані за основною функцією main
void GPIO_Init_Game(void);	//Функція увімкнення і налаштування периферії яку задіяли
void delay_ms(unsigned int delay);	//Функція паузи в мілісекундах
void delay_us(unsigned int delay);	//Функція паузи в мікросекундах
void BEEP(uint16_t tone, uint16_t time);	//Функція відтворення звуку "біп"
void start();	//Функція запрошення до гри. Блимаємо світлодіодами очікуємо на старт. Генеруємо унікальне число для srand
void generate_sequence(void);	//Генеруємо псевдовипадкову послідовність і заповнюємо масив sequence номерами світлодіодів 0-3
void show_sequence(void);	//Показуємо ігрову ситуацію. Почергово засвічуємо світлодіоди
void get_sequence(void);	//Приймаємо послідовність натискання кнопок і перевірка на правильність
void right_sequence(void);	//Як послідовність вірна, то блимаємо всіма світлодіодами і робимо біп
void wrong_sequence(void);	//Як послідовність не вірна, то декілька раз блимаємо світлодіодами з сиреною вертаємо рівень на 1 і швидкість на 1000

//Головна функція
int main(void)
{
	GPIO_Init_Game(); //ініціалізація всієї периферії

while(1)
	{
		if (level==1) {
			generate_sequence(); //генеруємо послідовність і заповнюємо масив номерами світлодіодів 0-3
		}
			show_sequence();	//відтворюємо послідовність вогниками
			get_sequence();	//приймаємо послідовність від кнопок
	}
}
//Показуємо ігрову ситуацію. Почергово засвічуємо світлодіоди
void show_sequence(){
	uint_fast8_t led_lights[]={LED_1,LED_2,LED_3,LED_4}; //Оголошуємо масив з усіма ігровими світлодіодами
	uint16_t beep_tone[]={TON1,TON2,TON3,TON4};	//Оголошуємо масив з переліком тону звуку відповідно до кожного світлодіода
	GPIO_ResetBits(LED_PORT,LED_ALL);	//Скидаємо всі вогники
	int i;	//Оголошуємо змінну для циклу
	for (i=0; i<level; i++){	//Поки не досягнули рівня гри показуємо послідовність вогників
		GPIO_SetBits(LED_PORT,led_lights[sequence[i]]); //Берем з масиву номер вогника і засвічуємо його
		BEEP(beep_tone[sequence[i]],100);	//Створюємо звук який відповідає за конкретний вогник
		delay_ms(velocity);	//Час який буде світити вогник. Чим більший рівень тим менше часу світитиме
		GPIO_ResetBits(LED_PORT,led_lights[sequence[i]]);	//Гасимо вогник
		delay_ms(200);	//Невеличка затримка
	}
}

//Приймаємо послідовність натискання кнопок і перевірка на правильність
void get_sequence(){
	bool flag; //Цей прапор вказує, якщо кнопку натиснули
	int i;	//Оголошуємо змінну для циклу
	for (i = 0; i < level; i++) {	//Поки не досягнули рівня гри приймаємо послідовність натискань на кнопки
		flag=false;	//Скидаємо прапорець натискання кнопки
		while(flag==false){	//Поки прапорець скинуто, йде опитування кнопок
			if (GPIO_ReadInputDataBit(BUTTON_PORT,BUTTON_1)==SET) {	//Якщо натиснули першу кнопку то...
				GPIO_SetBits(LED_PORT,LED_1);	//Світимо перший вогник
				BEEP(TON1,100);	//Створюємо звук відповідний до вогника
				your_sequence[i]=0;	//Записуємо до масиву номер кнопки-1
				flag=true;	//Встановлюємо прапорець - кнопку вже натиснуто
				delay_ms(200);	//Невеличка затримка
				if (your_sequence[i] != sequence[i]){	//Перевіряємо чи співпадає натиснута кнопка з заданою послідовністю
					wrong_sequence();	//Як ні то виходимо з функції
					return;
				}
				GPIO_ResetBits(LED_PORT,LED_1); //Як так, то продовжуємо функцію поки цикл
			}
			//Перевірка натискання другої кнопки, повністю аналогічна першої
			if (GPIO_ReadInputDataBit(BUTTON_PORT,BUTTON_2)==SET){
				GPIO_SetBits(LED_PORT,LED_2);
				BEEP(TON2,100);
				your_sequence[i]=1;
				flag=true;
				delay_ms(200);
				if (your_sequence[i] != sequence[i]){
					wrong_sequence();
					return;
				}
				GPIO_ResetBits(LED_PORT,LED_2);
			}
			//Перевірка натискання третьої кнопки, повністю аналогічна першій
			if (GPIO_ReadInputDataBit(BUTTON_PORT,BUTTON_3)==SET){
				GPIO_SetBits(LED_PORT,LED_3);
				BEEP(TON3,100);
				your_sequence[i]=2;
				flag=true;
				delay_ms(200);
				if (your_sequence[i] != sequence[i]){
					wrong_sequence();
					return;
				}
				GPIO_ResetBits(LED_PORT,LED_3);
			}
			//Перевірка натискання четвертої кнопки, повністю аналогічна першої
			if (GPIO_ReadInputDataBit(BUTTON_PORT,BUTTON_4)==SET){
				GPIO_SetBits(LED_PORT,LED_4);
				BEEP(TON4,100);
				your_sequence[i]=3;
				flag=true;
				delay_ms(200);
				if (your_sequence[i] != sequence[i]){
					wrong_sequence();
					return;
				}
				GPIO_ResetBits(LED_PORT,LED_4);
			}
		}
	}
	right_sequence();	//Як послідовність вірна, викличемо функцію right_sequence
}
//Генеруємо псевдовипадкову послідовність і заповнюємо масив sequence номерами світлодіодів 0-3
void generate_sequence(){
	start(); //запрошення до гри. генерується число для srand()
	int i;	//Оголошуємо змінну для циклу
	for (i = 0; i < MAX_LEVEL; i++) {	//Заповнюємо масив від 0 до MAX_LEVEL-1
		sequence[i]=rand()%4;	//числами від 0 до 3
	}
}
//Як послідовність не вірна, то декілька раз блимаємо світлодіодами з сиреною, вертаємо рівень на 1 і швидкість на 1000
void wrong_sequence(){
	int i;	//Оголошуємо змінну для циклу
	for (i = 0; i < 3; i++) {	//Блимаємо три рази
		GPIO_SetBits(LED_PORT,LED_ALL);	//Запалюємо всі вогники що є
		BEEP(800,100);	//Звук
		delay_ms(50);	//Невеличка затримка
		GPIO_ResetBits(LED_PORT,LED_ALL);	//Гасимо всі вогники що є
		BEEP(1000,200);	//Звук
		delay_ms(50);	//Невеличка затримка
	}
	level=1;	//Після циклу присвоюємо рівень 1
	velocity=1000;	//І швидкість 1000
}
//Як послідовність вірна, то "радісно" блимаємо всіма світлодіодами і робимо короткий біп
void right_sequence(){
	GPIO_ResetBits(LED_PORT,LED_ALL);
	delay_ms(250);
	BEEP(100,200);
	GPIO_SetBits(LED_PORT,LED_ALL);
	delay_ms(500);
	GPIO_ResetBits(LED_PORT,LED_ALL);
	delay_ms(500);
	if (level<MAX_LEVEL) {	//Поки не досягнули максимального рівня
		level++;	//збільшуємо рівень гри на одиницю
	}
	velocity -=50;	//Швидкість збільшуємо
}

//Функция запрошення до гри і генерація вихідного числа для для функції srand()
void start() {
	uint16_t start_rand=0;	//Оголошуємо змінну для вихідного числа послідовності, що генерується функцією rand ()
	uint_fast8_t splash[]={LED_BLUE,LED_GREEN,LED_1,LED_2,LED_3,LED_4}; //Оголошуємо масив з усіма світлодіодами
	uint8_t i=0; //Оголошуємо тимчасову змінну
	while (GPIO_ReadInputDataBit(BUTTON_PORT,BUTTON_START)==Bit_RESET){ //Поки не натиснуто кнопки Старт виконуємо цикл
		LED_PORT ->BSRR = splash[i];	//По черзі запалюємо вогники
		delay_ms(70);	//Хай вогник посвітить якийсь час
		LED_PORT ->BRR = splash[i];	//По черзі гасимо вогники
		start_rand++;	//Генеруємо унікальне число для генерації унікальної псевдовипадкової послідовності
		if ((i<(sizeof(splash)/sizeof(int))-1)) {	//Поки не досягнули кінця масиву
			i++;	//додаємо 1 до i
		} else {	//інакше
			i=0;	// i обнуляємо
		}

	}
	//Як кнопку старт натиснули
	srand(start_rand); //ініціалізуємо унікальним числом функцію rand
	//Далі послідовність запалювання вогників і звук для початку гри
	GPIO_SetBits(LED_PORT,LED_1);
	BEEP(TON1,100);
	GPIO_ResetBits(LED_PORT,LED_1);
	GPIO_SetBits(LED_PORT,LED_2);
	BEEP(TON2,100);
	GPIO_ResetBits(LED_PORT,LED_2);
	GPIO_SetBits(LED_PORT,LED_3);
	BEEP(TON3,100);
	GPIO_ResetBits(LED_PORT,LED_3);
	GPIO_SetBits(LED_PORT,LED_4);
	BEEP(TON4,100);
	GPIO_ResetBits(LED_PORT,LED_4);
	delay_ms(500);
	GPIO_SetBits(LED_PORT,LED_4);
	BEEP(TON4,100);
	GPIO_ResetBits(LED_PORT,LED_4);
	GPIO_SetBits(LED_PORT,LED_3);
	BEEP(TON3,100);
	GPIO_ResetBits(LED_PORT,LED_3);
	GPIO_SetBits(LED_PORT,LED_2);
	BEEP(TON2,100);
	GPIO_ResetBits(LED_PORT,LED_2);
	GPIO_SetBits(LED_PORT,LED_1);
	BEEP(TON1,100);
	GPIO_ResetBits(LED_PORT,LED_1);
	delay_ms(1000);
}

//Функція формування затримки в мілісекундах
void delay_ms(unsigned int delay)
{
    TIM7->PSC = F_APB1/1000+1;	//Встановлюємо подрібнювач
    TIM7->ARR = delay;	//встановлюємо значення переповнювання таймеру, а також і значення при якому генеруеться подія оновлення
    TIM7->EGR |= TIM_EGR_UG;	//Генерируемо Подію оновлення для запису даних в регістри PSC і ARR
    TIM7->CR1 |= TIM_CR1_CEN|TIM_CR1_OPM;	//Запускаемо таймер записом биту CEN і встановлюємо режим Одного проходу встановленням біту OPM
	while ((TIM7->CR1) & (TIM_CR1_CEN!=0)); //Виконуємо цикл поки рахує таймер до нуля
}

//Функція формування затримки в мікросекундах
void delay_us(unsigned int delay)
{
    TIM7->PSC = F_APB1/1000000+1;	///Встановлюємо подрібнювач
    TIM7->ARR = delay;	//встановлюємо значення переповнювання таймеру, а також і значення при якому генеруеться подія оновлення
    TIM7->EGR |= TIM_EGR_UG;	//Генерируемо Подію оновлення для запису даних в регістри PSC і ARR
    TIM7->CR1 |= TIM_CR1_CEN|TIM_CR1_OPM;	//Запускаемо таймер записом биту CEN і встановлюємо режим Одного проходу встановленням біту OPM
    while ((TIM7->CR1) & (TIM_CR1_CEN!=0));	//Виконуємо цикл поки рахує таймер до нуля
}


//Функція біп
void BEEP(uint16_t tone, uint16_t time){ //Функція приймає значення тону звука і тривалість звуку
	uint16_t j;
	for (j = 0; j < time; ++j) {
		BUZZER_PORT ->BSRR = BUZZER;
		delay_us(tone);
		BUZZER_PORT ->BRR = BUZZER;
		delay_us(tone);
		}
}
//Функція увімкнення і налаштування периферії яку задіяли
void GPIO_Init_Game(void)
{
  GPIO_InitTypeDef GPIO_InitStruct; //Оголошуємо назву об'єкта структури

  /*Вмикаємо потрібну периферію і тактуємо її */
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOC|RCC_APB2Periph_GPIOB, ENABLE);

  /*Налаштування порту де кнопки GPIO pin : PA */
  GPIO_InitStruct.GPIO_Pin = BUTTON_START|BUTTON_1|BUTTON_2|BUTTON_3|BUTTON_4;
  GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
  GPIO_Init(BUTTON_PORT, &GPIO_InitStruct);

  /*Налаштування порту де свілодіоди GPIO pin : PC */
  GPIO_InitStruct.GPIO_Pin = LED_ALL;
  GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
  GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
  GPIO_Init(LED_PORT, &GPIO_InitStruct);

  /*Налаштування порту де зумер GPIO pin : PB */
  GPIO_InitStruct.GPIO_Pin = BUZZER;
  GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
  GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
  GPIO_Init(BUZZER_PORT, &GPIO_InitStruct);

  //Вмикаємо тактування базового таймера 7
  RCC->APB1ENR |= RCC_APB1ENR_TIM7EN;

  GPIO_ResetBits(LED_PORT,LED_ALL); //гасимо всі вогники світлодіодів
}

Як це працює

Гра дуже проста і в тексті програми по максимуму додав коментарі. Має бути все зрозумілим. В порівнянні з попередньою грою "Хто швидший" нічого нового немає. Як бачимо з програми, тут застосовані ті ж самі функціїdelay_ms(us) і beep. Використовуємо ті самі бібліотеки. А ініціалізацію і налаштування периферії оформили окремою функцією. 
В якості вдосконалення гри можна додати семи-сегментний індикатор на два розряди, або індикатор на рідких кристалах LCD, для відтворення на ньому рівня складності гри. Та додати мелодій для правильної комбінації і програшу. З нотами в мене все дуже погано, а ось як будуть побажання підключити індикатор до гри - пишіть в коментарях.
Якщо ви помітили помилку, то виділіть фрагмент тексту не більше 20 символів і натисніть Ctrl+Enter
Підписуюсь на новини

Зверніть увагу

Френк Герберт: Ну як вам друге дно Вулика Геллстрома?

«Вулик Геллстрома», «Дюна» і 10 принципів Джигаду – політичний проект Френка Герберта

«Життя у вулику передбачає не регламентовану монотонність, а МЕТАМОРФОЗУ. Коли комаха досягає межі своїх можливостей, вона чудесним чином перетворюється на абсолютно нову істоту. У цій метаморфозі я...

Останні записи