В нас вже достатньо знань щоб створити просту гру. В процесі творення ознайомимось як підключати і працювати з цифровим сегментним індикатором. Розглянемо що таке динамічна індикація. Познайомимось з таймерами мікроконтролера. Повторимо, як реагувати на натискання кнопок та керувати світлодіодами.
В нас вже достатньо знань щоб створити просту гру. В процесі творення ознайомимось як підключати і працювати з цифровим сегментним індикатором. Розглянемо що таке динамічна індикація. Познайомимось з таймерами мікроконтролера. Повторимо, як реагувати на натискання кнопок та керувати світлодіодами.
Будь які N-P-N транзистори малої потужності - 5 шт.
Семи сегментний індикатор на чотири розряди з загальним катодом E40361 - 1 шт.
Резистор 10 кОм - 2 шт.
Резистор 1 кОм - 6 шт.
Резистор 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//Оголошуємо прототипи функцій що є в нашій програміvoidgameplay();
voiddelay_ms();
voiddelay_us();
voidstart();
voidtablo();
voidwelcome();
voidBEEP(uint16_t tone, uint16_t time);
//Головна програмаintmain(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 і Player2uint8_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 і гравця 2uint_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; //Встановлюємо прапорець
}
elseif ((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; //гасимо світлодіод гравця 2if (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; //Встановлюємо прапорець
}
elseif ((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; //гасимо світлодіод гравця 1if (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); //світиться поточний рахунок в очікуванні натискання кнопки
}
}
//Функція формування затримки в мілісекундахvoiddelay_ms(unsignedint 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 і встановлюємо режим Одного проходу встановленням біту OPMwhile ((TIM7->CR1) & (TIM_CR1_CEN!=0)); //Виконуємо цикл поки рахує таймер до нуля
}
//Функція формування затримки в мікросекундахvoiddelay_us(unsignedint 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 і встановлюємо режим Одного проходу встановленням біту OPMwhile ((TIM7->CR1) & (TIM_CR1_CEN!=0)); //Виконуємо цикл поки рахує таймер до нуля
}
//Функція запрошення до гриvoidwelcome() {
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 () використовується, щоб при різних запусках програма могла використовувати різні послідовності псевдовипадкових чисел
}
//Функция початкової перевірки всіх світлодиодівvoidstart() {
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); //Робимо біп
}
//Функція випадкового засвічування сегментів випадковий часvoidgameplay() {
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;
}
}
//Функція біпvoidBEEP(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);
}
}
//Функция виставляє в порт потрібну цифруvoiddigit_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]; //Запалюємо потрібні
}
//Функція відображення інформації на індикаторvoidtablo(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);//Невеличка затримка. Хай цифра світиться якийсь час
}
Як працює динамічна індикація та які види бувають можна ознайомитись тут.
Принцип роботи і приклад програмної реалізації сегментного індикатора взяв з цієї статті: "Простой счётчик на STM32".
Паузи можна реалізовувати пустим циклом, але тоді напевно ми не можемо знати скільки саме часу триває пауза. За допомоги таймера ми можемо задавати паузи з високою точністю. Реалізацію пауз за допомоги базових таймерів запозичив звідси.
Як це працює
Щоб не нагромаджувати багато тексту, програму максимально доповнив коментарями. З них має бути зрозумілою робота програми. Розглянемо тільки ті моменти які ми не розглядали в попередній статті "Перша програма".
#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:
На малюнку червоним прямокутником позначені базові таймери TIM6 і TIM7, які тактуються/живляться від шини APB1 і вони не мають зв'язку з зовнішнім світом як TIM1 - TIM4 і TIM15 - TIM17. Для відліку часових інтервалів це і не потрібно. Оберемо для цієї задачі TIM7 та увімкнемо його.
Тепер треба налаштувати таймер потрібним чином, дати команду на відлік і слідкувати коли цей відлік досягне кінця. Оформимо паузу в окрему функцію.
//Функція формування затримки в мілісекундахvoiddelay_ms(unsignedint 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 і встановлюємо режим Одного проходу встановленням біту OPMwhile ((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.
«Життя у вулику передбачає не регламентовану монотонність, а МЕТАМОРФОЗУ. Коли комаха досягає межі своїх можливостей, вона чудесним чином перетворюється на абсолютно нову істоту. У цій метаморфозі я...
STM32: Хто Швидший
Світ:
В нас вже достатньо знань щоб створити просту гру. В процесі творення ознайомимось як підключати і працювати з цифровим сегментним індикатором. Розглянемо що таке динамічна індикація. Познайомимось з таймерами мікроконтролера. Повторимо, як реагувати на натискання кнопок та керувати світлодіодами.
Зміст
STM32: Хто швидший
Передмова
Необхідні компоненти і деталі
Електрична схема гри
Алгоритм гри
Програмна реалізація гри
Запускаємо CooCox IDE. Створюємо новий проект і називаємо його "Who Is Faster". Обираємо свій чип STM32F100RB. Обираємо з репозиторію бібліотеку GPIO. Видаляємо шаблон з main.c і копіюємо туди цей текст програми. Або завантажуємо файл.
Як це працює
#include "stdlib.h"
Додали, до вже нам знайомих бібліотек, бібліотеку stdlib.h. В грі "Хто швидший" нам потрібний генератор псевдовипадкових чисел. Для хаотичного миготіння сегментами індикатора в ігровій ситуації, та її випадкова часова тривалість, щоб застати гравців зненацька. Бібліотека stdlib.h має функції rand - яка генерує псевдовипадкове значення. І функцію srand - яка встановлює початкове значення генератора псевдовипадкових чисел. В функції welcome нашої програми, поки очікуємо старт гри, додаємо кожні пів секунди одиницю до змінної start_rand. Щоб при старті гри запустити функцію srand з унікальним числом і ініціювати унікальну псевдовипадкову послідовність чисел функції rand.
Семи-сегментний індикатор, який підключений до порту "B" мікроконтролера, займає з 0 по 10 ніжку порту. За замовчуванням при подачі напруги на мікроконтролер ніжки PA15, PB3 і PB4 зайняті інтерфейсом JTAG. Але ми його не використовуємо, він навіть не запаяний на платі STM32VLDiscovery, а використовуємо для прошивання і налагодження інтерфейс SWD. Тому потрібно звільнити ці виводи під наші потреби. Для цього додали до ініціалізації і налаштування периферії мікроконтролера ці два рядки:
Без цих рядків не будуть працювати сегменти D і E індикатора (дивись схему гри).
Ще нам потрібен один з базових таймерів мікроконтролера. На його основі організуємо паузи в мікросекундах і мілісекундах. Дивимось на малюнок 6, блок-схеми STM32F100RB сторінка 8 документації на плату STM32VLDISCOVERY:
На малюнку червоним прямокутником позначені базові таймери TIM6 і TIM7, які тактуються/живляться від шини APB1 і вони не мають зв'язку з зовнішнім світом як TIM1 - TIM4 і TIM15 - TIM17. Для відліку часових інтервалів це і не потрібно. Оберемо для цієї задачі TIM7 та увімкнемо його.
Тепер треба налаштувати таймер потрібним чином, дати команду на відлік і слідкувати коли цей відлік досягне кінця. Оформимо паузу в окрему функцію.
Де delay - вхідний аргумент функції, приймає тривалість затримки в мілісекундах, F_APB1 - частота тактування шини 24000000Гц, задали на початку програми директивою #define F_APB1 24000000. Все інше зрозуміло з коментарів. Функція затримки в мікросекундах delay_us ідентична, відрізняється тільки значенням подрібнювача. Можна застосувати для роботи з таймерами бібліотеку stm32f10x_tim.h, але в мене так і не вийшло запустити таймер за допомоги бібліотеки. Мабуть не дуже й старався розібратись, бо через регістри все просто і зрозуміло.
Як хтось поділиться робочим варіантом функції delay_ms з використанням бібліотеки TIM в коментарях, то доповню статтю з посиланням на автора.
Далі все за алгоритмом гри.
Ось так гра має працювати:
Зверніть увагу
«Вулик Геллстрома», «Дюна» і 10 принципів Джигаду – політичний проект Френка Герберта