пʼятницю, 16 січня 2015 р.

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 цей текст програми, або завантажуємо файл.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
//Гра "Саймон каже" для плати розробника 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, для відтворення на ньому рівня складності гри. Та додати мелодій для правильної комбінації і програшу. З нотами в мене все дуже погано, а ось як будуть побажання підключити індикатор до гри - пишіть в коментарях.

Немає коментарів:

Дописати коментар