середу, 14 січня 2015 р.

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 і копіюємо туди цей текст програми. Або завантажуємо файл.
  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
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
//Гра "Хто швидший" автор Гончаренко А.В.
//Вкладаємо до проекту потрібні файли
#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.

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

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