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

STM32: Хто Швидший

В нас вже достатньо знань щоб створити просту гру. В процесі творення ознайомимось як підключати і працювати з цифровим сегментним індикатором. Розглянемо що таке динамічна індикація. Познайомимось з таймерами мікроконтролера. Повторимо, як реагувати на натискання кнопок та керувати світлодіодами.

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

STM32: Хто швидший

 

Передмова

В нас вже достатньо знань щоб створити просту гру. В процесі творення ознайомимось як підключати і працювати з цифровим сегментним індикатором. Розглянемо що таке динамічна індикація. Познайомимось з таймерами мікроконтролера. Повторимо, як реагувати на натискання кнопок та керувати світлодіодами.

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

Компоненти і деталі гри "Хто швидший"
 
  1. Плата розробника STM32VLDISCOVERY - 1 шт
  2. Контактна макетна плата "BreadBoard" 
  3. З'єднувальні дроти
  4. USB шнур - 1 шт.
  5. Зумер "Buzzer" - 1 шт.
  6. Тактильні мікрокнопки  - 2 шт.
  7. Будь які N-P-N транзистори малої потужності - 5 шт.
  8. Семи сегментний індикатор на чотири розряди з загальним катодом E40361 - 1 шт.
  9. Резистор 10 кОм - 2 шт.
  10. Резистор 1 кОм - 6 шт.
  11. Резистор 330 Ом - 7 шт.
Контактна макетна плата. Як видно на світлині, макетна дошка не зовсім фабрична. З-за того, що в платі розробника STM32VLDISCOVERY ніжки 10-15 порту "B" розташовані поперек плати, то прийшлось придбати два окремі модулі контактних макетних плат (breadboard) і наклеїти їх до основи-дошки, за допомоги двошарової клейкої наліпки (скоч). Основа-дошка може бути фанера, гетинакс, текстоліт, або оргскло. Наклеювати треба з встановленою платою розробника в макетні плати, щоб точно витримати відстань між модулями макетних плат. Потім, як виявилось, для зручності монтажу схем, потрібні шини живлення. На світлині це такі вузенькі макетки по краях дошки з червоною і синьою смужками. Але за браком досвіду придбав шини живлення, які по кріпленню не підходили до моїх макетних плат. Тому я просто приліпив їх поруч. Звертайте на це увагу коли будете купувати контактні макетні плати. Ще можна до низу дошки прикрутити гумові ніжки, щоб дошка не ковзала по столу і додати клеми живлення.
Транзистори. Транзистори будь які N-P-N структури малої потужності. В мене валялись без діла багато старих транзисторів КТ315, то я їх і застосував. Можна щось типу 2N2222 чи аналогічні.
Семи-сегментний індикатор. Теж можна будь який такого типу з загальним катодом. 
Зумер (buzzer). Такий собі випромінювач звуку. Є такі що пищать коли на них просто подати живлення, а є такі які треба живити певною частотою - подавати почергово високий рівень і низький. Зазвичай гучніше звучать на конкретній звуковій частоті. Зазначено в документації на зумер. Підійдуть і ті, і ті. Треба дотримуватись полярності при підключенні.

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

Електрична схема гри "Хто швидший"
На схемі не показана плата розробника STM32VLDiscovery, а вказано з якими однойменними контактами плати розробника треба з'єднати елементи схеми.
 
Ось таке приблизне розташування елементів схеми має бути на контактній макетній платі.
Розташування елементів на макетній платі
А так буде виглядати вже зібраний макет відповідно до схеми.
Макет з елементами схеми і монтажем
Всі з'єднання елементів уважно перевірте перед подачею напруги, щоб не вивести з ладу порт USB комп'ютера, або мікроконтролер.

Алгоритм гри

Алгоритм, або правила гри: Подаємо живлення. Гра очікує старту. Тиснемо кнопку старт. На цифровому індикаторі випадковий час відтворюється ігрова ситуація (хаотично світяться сегменти індикатора). Як тільки ігрова ситуація припиняється, показуємо рахунок гравців, і тоді гравці тиснуть, кожен на свою кнопку. Хто швидше натиснув, тому зараховується бал. Гра йде до 5 балів.
Алгоритм гри "Хто швидший"
Алгоритм гри якихось додаткових пояснень не потребує. Все зрозуміло з блок-схеми алгоритму.

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

Запускаємо CooCox IDE. Створюємо новий проект і називаємо його "Who Is Faster". Обираємо свій чип STM32F100RB. Обираємо з репозиторію бібліотеку GPIO. Видаляємо шаблон з main.c і копіюємо туди цей текст програми. Або завантажуємо файл

//Гра "Хто швидший" автор Гончаренко А.В.
//Вкладаємо до проекту потрібні файли
#include "stm32f10x.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_rcc.h"
#include "stdlib.h"
#include "stdbool.h"
//Макроси
#define IND_PORT GPIOB //Порт до якого під'єднаний індікатор E40361
#define LED_PORT GPIOC //Порт до якого під'єднані світлодіоди
#define BUTTON_PORT GPIOA //Порт до якого під'єднані кнопки
#define BUZZER_PORT GPIOC //Порт до якого під'єднаний зумер
//Загальні ніжки індикатора - розряди індикатора
#define D0 GPIO_Pin_7
#define D1 GPIO_Pin_8
#define D2 GPIO_Pin_9
#define D3 GPIO_Pin_10
//До якої ноги який сегмент під'єднаний
#define SEG_A GPIO_Pin_0
#define SEG_B GPIO_Pin_1
#define SEG_C GPIO_Pin_2
#define SEG_D GPIO_Pin_3
#define SEG_E GPIO_Pin_4
#define SEG_F GPIO_Pin_5
#define SEG_G GPIO_Pin_6
//Збираємо цифри з сегментів
#define DIG0 (SEG_A | SEG_B | SEG_C | SEG_D | SEG_E | SEG_F)
#define DIG1 ( SEG_B | SEG_C )
#define DIG2 ( SEG_A | SEG_B | SEG_G | SEG_E | SEG_D )
#define DIG3 ( SEG_A | SEG_B | SEG_G | SEG_C | SEG_D )
#define DIG4 ( SEG_F | SEG_G | SEG_B | SEG_C)
#define DIG5 ( SEG_A | SEG_F | SEG_G | SEG_C | SEG_D )
#define DIG6 ( SEG_A | SEG_C | SEG_D | SEG_E | SEG_F | SEG_G )
#define DIG7 ( SEG_A | SEG_B | SEG_C )
#define DIG8 ( SEG_A | SEG_B | SEG_C | SEG_D | SEG_E | SEG_F | SEG_G)
#define DIG9 ( SEG_A | SEG_B | SEG_C | SEG_D | SEG_F | SEG_G)
#define DIGP (SEG_F | SEG_E | SEG_A | SEG_B | SEG_G)
#define ALL_PINS (DIG8 | D0 | D1 | D2 | D3 )
//Назначаємо на яких ногах, які кнопки
#define BUTTON_0 GPIO_Pin_0
#define BUTTON_1 GPIO_Pin_6
#define BUTTON_2 GPIO_Pin_7
#define Match_Drawn (BUTTON_1 | BUTTON_2) //Натисното дві кнопки одночасно
//Назначаємо на яких ногах, які свілодіоди
#define LED_BLUE GPIO_Pin_8
#define LED_GREEN GPIO_Pin_9
#define LED_ALL (LED_BLUE | LED_GREEN)
//До якої ноги під'єднаний зумер
#define  BUZZER GPIO_Pin_2
//Для зручності надаємо порядковим номерам масиву зрозумілі позначення
#define TIRE 10
#define NOP 12
#define P 11
//Частота таймера
#define	F_APB1 24000000
//Оголошуємо прототипи функцій що є в нашій програмі
void gameplay();
void delay_ms();
void delay_us();
void start();
void tablo();
void welcome();
void BEEP(uint16_t tone, uint16_t time);
//Головна програма
int main(void){
	uint8_t counter1=0; //Лічильник натискань першого гравця
	uint8_t counter2=0; //Лічильник натискань другого гравця
	uint8_t score=5; //до якого рахунку граємо
	bool button_flag = false; //прапорець натискання кнопки. false - ще нічого не натиснули, true - якусь кнопку вже натиснуто
	GPIO_InitTypeDef GPIO_InitStruct; //Оголошуємо структуру яка містить налаштування порту
	//вимикаємо JTAG (він займає ноги PB3,PB4 - вони потрібні нам)
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
	GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);
	//налаштовуємо на вихід всі ноги підключенні до індикатору
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	GPIO_InitStruct.GPIO_Pin = ALL_PINS; //Вказуємо які ноги потрібно налаштувати
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP; //Налаштовуємо як вихід push-pull
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz; //частота 2МГц
	GPIO_Init(IND_PORT, &GPIO_InitStruct); //викликаємо функцію налаштування порту
	//налаштовуємо ноги (PA0, PA7, PA9) з кнопками на вхід
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_InitStruct.GPIO_Pin = (BUTTON_0 | BUTTON_1 | BUTTON_2);
	GPIO_Init(BUTTON_PORT, &GPIO_InitStruct);//викликаємо функцію налаштування порту
	//налаштовуємо ноги з світлодіодами
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
	GPIO_InitStruct.GPIO_Pin = (LED_ALL | BUZZER);
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
	GPIO_Init(LED_PORT, &GPIO_InitStruct);//викликаємо функцію налаштування порту
	//Вмикаємо тактування базового таймера 7
	RCC->APB1ENR |= RCC_APB1ENR_TIM7EN;
	start(); //Запускаємо функцію start - почергове засвічування всіх вогників
	welcome(); //Запускаємо функцію welcome - запрошення до гри
	gameplay(); //Запускаємо функцію gameplay - ігрова ситуація

	while(1)
	{
    	if ((BUTTON_PORT->IDR & Match_Drawn)==Match_Drawn) { //Натиснули одночасно кнопки?
    		if (!button_flag) {	//Як прапорець скинуто то...
    			IND_PORT -> BRR = LED_ALL; //Гасимо всі вогники на індикаторі
    			counter1++;	//Додаємо першому гравцю бал
    			counter2++;	//Додаємо другому гравцю бал
    			BEEP(600,300); //Робимо біп
    			if ((counter1 == counter2)&(counter1 == score))  {	//Якщо перший і другий гравець має однаково балів і досягнули кінця рахунку...
    				while(1){ //Входимо до безкінечного циклу де поблимаємо написом P1P2 - що значить нічья Player1 і Player2
    					uint8_t j;
    				    for (j = 0; j < 200; ++j) {
    				       tablo(NOP, NOP, NOP, NOP);
    				    }
    				    for (j = 0; j < 200; ++j) {
    				    	tablo(2, P, 1, P);
    				    }
    				}
				}

    			if (counter1 == score) { //Як гравець 1 досягнув переможних балів...
					goto player1;	//Перейдемо до мітки player1
				}
    			if (counter2 == score) {	//Як гравець 2 досягнув переможних балів...
					goto player2;	//Перейдемо до мітки player2
				}
    			LED_PORT -> BSRR = LED_ALL; //світимо світлодіоди обох гравців
    			uint8_t i;
    			for (i = 0; i < 5; ++i) {	//Поблимаємо рахунком гравця 1 і гравця 2
    				uint_fast8_t j;
    				for (j = 0; j < 200; ++j) {
    			       tablo(NOP, NOP, NOP, NOP);
    			    }
    			    for (j = 0; j < 200; ++j) {
    			       tablo(counter1, TIRE, TIRE, counter2);
    			    }
    			}
    			    			IND_PORT -> BRR = ALL_PINS; //Гасимо всі вогники на індикаторі
    			    			gameplay();	 //Запускаємо функцію gameplay - ігрова ситуація
    		}
    		button_flag = true; //Встановлюємо прапорець
    }
    	else if ((BUTTON_PORT -> IDR & BUTTON_1)==BUTTON_1) { //Якщо кнопку 1 натиснули?
    		if (!button_flag) { //і прапорець скинуто
    			counter1++; //Додаємо гравцю 1 один бал
    			BEEP(500,100); //Робимо біп
    			LED_PORT -> BSRR = LED_GREEN; //світимо світлодіод гравця 1
    			LED_PORT -> BRR = LED_BLUE; //гасимо світлодіод гравця 2
    			if (counter1 == score) {	//Перевіряємо чи не досягнули переможних балів?
    				player1:
    				while(1){	//Якщо так, то до безкінечного циклу де блимаємо рахунком переможця
					uint8_t j;
    				   for (j = 0; j < 200; ++j) {
    					   tablo(NOP, NOP, NOP, NOP);
    				   }
    				   for (j = 0; j < 200; ++j) {
    				   	   tablo(counter1, TIRE, 1, P);
    				   }
					}
				}
    			uint8_t i;
    			for (i = 0; i < 5; ++i) { //Якщо ні, то блимаємо рахунком гравця 1 п'ять разів
    				uint8_t j;
    				   for (j = 0; j < 200; ++j) {
    					   tablo(counter2, TIRE, NOP, NOP);
    				   }
    				   for (j = 0; j < 200; ++j) {
    				   	   tablo(counter2, TIRE, TIRE, counter1);
    				   }
				}
    			IND_PORT -> BRR = ALL_PINS; //Гасимо всі вогники на індикаторі
    			gameplay();	//Запускаємо функцію gameplay - ігрова ситуація
    		}
    		button_flag = true;	//Встановлюємо прапорець
    	}
    	else if ((BUTTON_PORT -> IDR & BUTTON_2)==BUTTON_2) { //Якщо кнопку 2 натиснули?
    	   if (!button_flag) {	//і прапорець скинуто
    		   counter2++;	//Додаємо гравцю 2 один бал
    		   BEEP(500,100);	//Робимо біп
    		   LED_PORT -> BSRR = LED_BLUE; //світимо світлодіод гравця 2
    		   LED_PORT -> BRR = LED_GREEN; //гасимо світлодіод гравця 1
    		   if (counter2 == score) {	//Перевіряємо чи не досягнули переможних балів?
    			   player2:
    			   while(1){	//Якщо так, то до безкінечного циклу де блимаємо рахунком переможця
    				   uint8_t j;
    				   for (j = 0; j < 200; ++j) {
    				   	   tablo(NOP, NOP, NOP, NOP);
    				   }
    				   for (j = 0; j < 200; ++j) {
    				   	   tablo(counter2, TIRE, 2, P);
    				   }
    			   }
    		   }
    		   uint_fast8_t i;
    		   for (i = 0; i < 5; ++i) {	//Якщо ні, то блимаємо рахунком гравця 2 п'ять разів
    			   uint_fast8_t j;
    			   for (j = 0; j < 200; ++j) {
    				   tablo(NOP, NOP, TIRE, counter1);
				}
    			   for (j = 0; j < 200; ++j) {
    				   tablo(counter2, TIRE, TIRE, counter1);
				}
    		   }
    		   IND_PORT -> BRR = ALL_PINS;	//Гасимо всі вогники на індикаторі
    		   gameplay();	//Запускаємо функцію gameplay - ігрова ситуація
    	   }
    	   button_flag = true;	//Встановлюємо прапорець
    	}
    	else {	//інакше як нічого не натиснуто...
    	   button_flag = false; //скидаємо прапорець
    	}
    	tablo(counter2, TIRE, TIRE, counter1); //світиться поточний рахунок в очікуванні натискання кнопки
    }
}

//Функція формування затримки в мілісекундах
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 welcome() {
	uint16_t start_rand;	//Оголошуємо змінну для вихідного числа послідовності, що генерується функцією rand ()
	//Гасимо всі вогники світлодіоди та сегменти індикатора
	LED_PORT ->BRR = LED_ALL;
	IND_PORT ->BRR = ALL_PINS;
	LED_PORT ->BSRR = (LED_BLUE); //Засвічуємо потрібні вогники
	while ((BUTTON_PORT -> IDR & BUTTON_0)!=BUTTON_0){ //Поки не натиснули кнопку 0 (Start) - виконуємо цикл
		delay_ms(500); //Затримка на 500 мілісекунд (пів секунди)
		LED_PORT->ODR^=LED_ALL; //Інвертуємо вогники гравців
		start_rand++; //Додаємо одиницю до змінної start_rand
	}
	//Якщо кнопку start натиснуто
	LED_PORT ->BRR = LED_ALL; //вимикаємо всі світлодіоди
	IND_PORT ->BRR = ALL_PINS; //вимикаємо індикатор
	BEEP(400,200); //Робимо біп
	srand(start_rand); //функція srand () використовується, щоб при різних запусках програма могла використовувати різні послідовності псевдовипадкових чисел
}

//Функция початкової перевірки всіх світлодиодів
void start() {
	uint_fast8_t segment[]={SEG_A,SEG_B,SEG_C,SEG_D,SEG_E,SEG_F,SEG_G}; //Оголошуємо масив з усіма сегментами індикатора
	uint_fast8_t digit[]={D0,D1,D2,D3}; //Оголошуємо масив з усіма розрядами індикатора
	uint_fast8_t splash[]={LED_BLUE,LED_GREEN}; //Оголошуємо масив з усіма світлодіодами
	uint8_t i; //змінна для циклу
	LED_PORT ->BRR = LED_ALL;	//вимикаємо всі світлодіоди
	IND_PORT ->BRR = ALL_PINS;	//вимикаємо індикатор
		for (i=0; i<=(sizeof(splash)/sizeof(int))-1; i++) { //цикл який послідовно запалює і гасить світлодіоди
			LED_PORT ->BSRR = splash[i];
			delay_ms(50);
			LED_PORT ->BRR = splash[i];
			delay_ms(50);
		}
		uint8_t d;	//змінна для циклу
		uint8_t s;	//змінна для циклу
		for (s=0; s<=(sizeof(segment)/sizeof(int))-1; s++){	//цикл який послідовно запалює і гасить сегменти індикатора по всіх розрядах
			for (d=0; d<=(sizeof(digit)/sizeof(int))-1; d++){
				IND_PORT ->BSRR = (digit[d] | segment[s]);
				delay_ms(50);
				IND_PORT ->BRR = (digit[d] | segment[s]);
			}
		}
		BEEP(300,150);	//Робимо біп
}

//Функція випадкового засвічування сегментів випадковий час
void gameplay() {
	uint_fast8_t segment[]={SEG_A,SEG_B,SEG_C,SEG_D,SEG_E,SEG_F,SEG_G};
	uint_fast8_t digit[]={D0,D1,D2,D3};
	uint16_t i;
	uint16_t play = rand()%500+10;
	for (i = 0; i < play; ++i) {
		IND_PORT ->BSRR = (digit[rand()%(sizeof(digit)/sizeof(int))] | segment[rand()%(sizeof(segment)/sizeof(int))]);
		delay_ms(50);
		IND_PORT ->BRR = ALL_PINS;
		}
}
//Функція біп
void BEEP(uint16_t tone, uint16_t time){ //Функція приймає значення тону звука і тривалість звуку
	int j;
		for (j = 0; j < time; ++j) {
			BUZZER_PORT ->BSRR = BUZZER;
			delay_us(tone);
			BUZZER_PORT ->BRR = BUZZER;
			delay_us(tone);
		}
}
//Функция виставляє в порт потрібну цифру
void digit_to_port (uint8_t digit) {
	uint8_t digitsp[]={DIG0,DIG1,DIG2,DIG3,DIG4,DIG5,DIG6,DIG7,DIG8,DIG9,SEG_G,DIGP,~DIG8}; //оголошуємо масив з можливими варіантами символами на індикатор
	IND_PORT -> ODR &= ~DIG8; //Вимикаємо всі сегменти
	IND_PORT -> ODR |= digitsp[digit]; //Запалюємо потрібні
}

//Функція відображення інформації на індикатор
void tablo(int_fast8_t SEG_1, int_fast8_t SEG_2, int_fast8_t SEG_3, int_fast8_t SEG_4){

			IND_PORT -> BRR = (D0|D1|D2|D3);//Вимикаємо всі розряди
			IND_PORT-> BSRR = D0;//Вмикаємо нульовий розряд індикатора
			digit_to_port(SEG_1);//Виводимо цифру у нульовий розряд
			delay_us(500);//Невеличка затримка. Хай цифра світиться якийсь час

			IND_PORT -> BRR = (D0|D1|D2|D3);//Вимикаємо всі розряди
			IND_PORT-> BSRR = D1;//Вмикаємо перший розряд індикатора
			digit_to_port(SEG_2);//Виводимо цифру у перший розряд
			delay_us(500);//Невеличка затримка. Хай цифра світиться якийсь час

			IND_PORT -> BRR = (D0|D1|D2|D3);//Вимикаємо всі розряди
			IND_PORT-> BSRR = D2;//Вмикаємо другий розряд індикатора
			digit_to_port(SEG_3);//Виводимо цифру у другий розряд
			delay_us(500);//Невеличка затримка. Хай цифра світиться якийсь час

			IND_PORT -> BRR = (D0|D1|D2|D3);//Вимикаємо всі розряди
			IND_PORT-> BSRR = D3;//Вмикаємо третій розряд індикатора
			digit_to_port(SEG_4);//Виводимо цифру у третій розряд
			delay_us(500);//Невеличка затримка. Хай цифра світиться якийсь час

}
  1. Як працює динамічна індикація та які види бувають можна ознайомитись тут.
  2. Принцип роботи  і приклад програмної реалізації сегментного індикатора взяв з цієї статті: "Простой счётчик на STM32".
  3. Паузи можна реалізовувати пустим циклом, але тоді напевно ми не можемо знати скільки саме часу триває пауза. За допомоги таймера ми можемо задавати паузи з високою точністю. Реалізацію пауз за допомоги базових таймерів запозичив звідси.
 

Як це працює

Щоб не нагромаджувати багато тексту, програму максимально доповнив коментарями. З них має бути зрозумілою робота програми. Розглянемо тільки ті моменти які ми не розглядали в попередній статті "Перша програма".
#include "stdlib.h"

Додали, до вже нам знайомих бібліотек, бібліотеку stdlib.h. В грі "Хто швидший" нам потрібний генератор псевдовипадкових чисел. Для хаотичного миготіння сегментами індикатора в ігровій ситуації, та її випадкова часова тривалість, щоб застати гравців зненацька. Бібліотека stdlib.h має функції rand - яка генерує псевдовипадкове значення. І функцію srand - яка встановлює початкове значення генератора псевдовипадкових чисел. В функції welcome нашої програми, поки очікуємо старт гри, додаємо кожні пів секунди одиницю до змінної start_rand. Щоб при старті гри запустити функцію srand з унікальним числом і ініціювати унікальну псевдовипадкову послідовність чисел функції rand.

Семи-сегментний індикатор, який підключений до порту "B" мікроконтролера, займає з 0 по 10 ніжку порту. За замовчуванням при подачі напруги на мікроконтролер ніжки PA15, PB3 і PB4 зайняті інтерфейсом JTAG. Але ми його не використовуємо, він навіть не запаяний на платі STM32VLDiscovery, а використовуємо для прошивання і налагодження інтерфейс SWD. Тому потрібно звільнити ці виводи під наші потреби. Для цього додали до ініціалізації і налаштування периферії мікроконтролера ці два рядки: 

 //вимикаємо JTAG (він займає ноги PB3,PB4 - вони потрібні нам)
 RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
 GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);

Без цих рядків не будуть працювати сегменти D і E індикатора (дивись схему гри).

Ще нам потрібен один з базових таймерів мікроконтролера. На його основі організуємо паузи в мікросекундах і мілісекундах. Дивимось на малюнок 6, блок-схеми STM32F100RB сторінка 8 документації на плату STM32VLDISCOVERY:

Блок схема STM32F100RB (малюнок 6, сторінка 8 STM32VLDISCOVERY)

На малюнку червоним прямокутником позначені базові таймери TIM6 і TIM7, які тактуються/живляться від шини APB1 і вони не мають зв'язку з зовнішнім світом як TIM1 - TIM4 і TIM15 - TIM17. Для відліку часових інтервалів це і не потрібно. Оберемо для цієї задачі TIM7 та увімкнемо його. 

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

Тепер треба налаштувати таймер потрібним чином, дати команду на відлік і слідкувати коли цей відлік досягне кінця. Оформимо паузу в окрему функцію. 

//Функція формування затримки в мілісекундах
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)); //Виконуємо цикл поки рахує таймер до нуля
}

Де delay - вхідний аргумент функції, приймає тривалість затримки в мілісекундах, F_APB1 - частота тактування шини 24000000Гц, задали на початку програми директивою #define F_APB1 24000000. Все інше зрозуміло з коментарів. Функція затримки в мікросекундах delay_us ідентична, відрізняється тільки значенням подрібнювача. Можна застосувати для роботи з таймерами бібліотеку stm32f10x_tim.h, але в мене так і не вийшло запустити таймер за допомоги бібліотеки. Мабуть не дуже й старався розібратись, бо через регістри все просто і зрозуміло.
Як хтось поділиться робочим варіантом функції delay_ms з використанням бібліотеки TIM в коментарях, то доповню статтю з посиланням на автора.

Все про базові таймери і їх регістри можна переглянути в довіднику на наш чип, сторінка 440. Розширений довідник на наш чип можна знайти по пошуку, або завантажити за ланкою.
Для звуку оформили окрему функцію BEEP, яка приймає тон звуку і тривалість звуку в мікросекундах. Звук створюється просто. Як на цифровий вихід швидко (в межах звукової частоти) подавати почергово 0 і 1 то на виході утвориться генерація сигналу. Який ми посилили транзисторним ключем, що живиться від 5В, а цифровий вихід логічної одиниці мікроконтролера має 3.3В. Навантаженням транзисторного ключа є наш "Зуммер" (Buzzer). Дивись схему гри. Спробуйте різні значеннями тону і тривалості звуку.

Далі все за алгоритмом гри.

В програмі зустрічається оператор безумовного переходу goto. Є така "традиція" в програмерських колах, що використовувати цей оператор нізащо не можна, дурний тон і таке інше. Я не сноб, по потребі використовую. Не зловживаю. В випадку нашої програми це нормальний хід. Докладніше, про використовувати оператор goto чи ні, в хорошій статті з цього приводу: "Запретный плод GOTO сладок (версия для микроконтроллеров)!"

Ось так гра має працювати:

 

 

Гру ще можна вдосконалити. Ввести зняття балів за фальш-старт. Додати мелодію для старту і перемоги. 
Як буде потреба щось пояснити детальніше повідомте мене в коментарях до статті.
В наступній статті порт гри "Саймон каже" з платформи ARDUINO на STM32.
Якщо ви помітили помилку, то виділіть фрагмент тексту не більше 20 символів і натисніть Ctrl+Enter
Теги: 
STM32
Підписуюсь на новини

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

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

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

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

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