середа, 31 серпня 2016 р.

STM32: Імпорт проекту в CooCoxIDE з CubeMX


Передмова

Розглянемо спосіб імпорту проекту до засобу розробки CooCoxIDE створеного за допомоги CubeMX (генератор коду для ініціалізації). В CubeMX офіційної підтримки CooCox немає, але знаючи декілька маленьких "хитрощів" можна генерувати робочий, повноцінний код для ініціалізації мікроконтролера. Цей спосіб підходить, як для CooCoxIDE 1.7, так і для CooCoxIDE 2.0Beta. Мікроконтролер чи плата розробника може бути будь-якою, принцип зберігається. Я для прикладу візьму плату розробника STM32VLDISCOVERY з мікроконтролером STM32F100RB. У вас може бути інша плата чи мікроконтролер.

Створюємо проект в CooCoxIDE

Спочатку треба створити пустий проект в засобі розробки CooCoxIDE. Якщо спочатку створити проект в CubeMX і зберегти його, то новий проект в CooCoxIDE не можливо буде зберегти в тій же теці. Потрібно буде зберігати окремо, що потягне за собою незручності у додатковому копіюванні файлів проекту. А також неможливо буде оновлювати проект з CubeMX, якщо будуть вноситись якісь зміни. Тому Запускаємо CooCoxIDE 1.7 чи 2.0Beta. Створюємо новий проект. І називаємо його "import example". Як встановити CooCoxIDE і створити новий проект в статті "CooCox IDE встановлення і перший запуск".
Створюємо новий проект
Після натискання кнопки "Next" потрібно обрати свій мікроконтролер. Я обираю STM32F100RB. Далі пропонують обрати з репозиторію потрібні бібліотеки.
Важливо! В репозиторію нічого не обирати. Проект має бути зовсім пустий. Також треба видалити з проекту файл "main.c", а також видалити сам файл "main.c" з диску, який знаходиться в теці проекту CooCoxIDE 1.7. А в CooCoxIDE 2.0Beta можна видалити з диску теку "APP" (в ній знаходиться файл "main.c"), яка знаходиться в теці проекту "import example".
Видалення файлу main з проекту
З CooCoxIDE поки що все і можна його згорнути.

Створюємо проект в CubeMX

Запускаємо CubeMX. Створюємо новий проект "New Project". В вікні, що з'явиться обираємо свій мікроконтролер, вкладка "MCU Selector". Або плату розробника, вкладка "Board Selector". На малюнку позначено червоним "1". CubeMX не підтримує плату розробника STM32VLDiscovery, але мікроконтролер в списку є, так що обираємо "MCU Selector" і прямуємо далі. Там де на малюнку позначено червоним "2" обираємо серію "Series", лінійку "Lines" і так далі. З списку обираємо свій мікроконтролер STM32F100RBTx - позначено червоним "3".
Обираємо свій мікроконтролер

Налаштування проекту

З'явиться вкладка "Pinout" з зображенням обраного мікроконтролеру. В області "Configuration" обираємо "RCC". В списку що розгорнеться обираємо "High Speed Clock (HSE)" - "Crystal/Ceramic Resonator" (на малюнку позначено червоним "1"). Це ми підключимо до мікроконтролеру зовнішній кварц на 8 МГц. Далі обираємо "SYS" і в списку, що розгорнеться, обираємо "Debug - Serial Wire" (на малюнку позначено червоним "2"). Це ми підключили інтерфейс SWD через який заливаємо "прошивки" до мікроконтролеру і працюємо в режимі Debugger. Тепер оголошуємо, що до виводів PC8, PC9 під'єднані голубий і зелений світлодіоди. Спочатку позначаємо що це "GPIO_Output" натиснувши ЛКМ (ліва кнопка миші). А натиснувши ПКМ (права кнопка миші) введемо текстову позначку. Для зручності так і назвемо "LED_BLUE" і "LED_GREEN" (на малюнку позначено червоним "3"). Оголосимо ще кнопку, обравши "PA0" як "GPIO_Input" і назвемо цей вивід як "USER_BUTTON" (на малюнку позначено червоним "4"). Ці два світлодіоди і кнопка входять до складу плати розробника STM32VLDiscovery. Як у вас інша плата розробника, то оголошуйте периферію відповідно до своєї плати. Або якщо ваша плата є в списку CubeMX, то оберіть її у вкладці "Board Selector".
Конфігурація виводів
Переходимо до вкладки "Clock Configuration" (на малюнку позначено червоним "1"). Обираємо "PLL Source MUX" як "HSE" (на малюнку позначено червоним "2"). Далі обираємо "System Clock MUX" як "PLLCLK" (на малюнку червоним "3"). А там де "HCLK (MHz)" обираємо частоту - максимально дозволено 24 МГц (позначка 4). 
Конфігурація тактування
Можна ще для ознайомлення перейти до вкладки "Configuration" і "Power Consumption Calculator", але для наших цілей вже проект налаштований і можна його зберегти та згенерувати код ініціалізації.

Генерування коду ініціалізації

Спершу збережемо в теку проекту "import example" створеного в CooCoxIDE файл проекту створений в CubeMX, обравши меню "File -> Save Project". Вказати шлях до вже створеної в CooCoxIDE теки проекту та назвати файл проекту "import example.ioc".
Зберігання проекту
Тепер налаштуємо сам проект для генерування коду. В меню CubeMX обираємо "Project -> Setting". В вікні, що з'явиться, у вкладці "Project" (окреслено червоним. дивись малюнок):

  1. Вказуємо ім'я проекту "Project Name" - Важливо! Ім'я проекту має бути таке саме як і назва проекту, що вже створили в CooCoxIDE - "import example".
  2. Шлях до теки з проектами "Project Location" - має бути шляхом до "workspace" для CoCooxIDE.
  3. Перевіряємо шлях, щоб був "..\workspace\import example", а не "..\workspace\import example\import example" якщо вказати шлях в "Project Location" не до "workspace", а до "workspace\import example".
  4. Та обираємо засіб розробки "Toolchain / IDE" як "TrueSTUDIO".
Налаштування проекту
У вкладці "Code Generator" залишаємо як є. Перевірте щоб було як на малюнку. Тиснемо "OK".
Налаштування проекту
Інші опції "Generated Files" та "HAL Settings" можуть бути обрані або ні. Для цього прикладу це не важливо. Впливають на саму структуру проекту та деякі аспекти ініціалізації мікроконтролера.
Все готово для генерації коду ініціалізації мікроконтролера. Тиснемо меню "Project -> Generate Code". CubeMX згенерує проект і збереже в теку "import example". З'явиться вікно про успішне завершення. Обираємо "Open Folder".
Відкриваємо теку з проектом

Переходимо до CooCoxIDE

Запускаємо CooCoxIDE, якщо був закритий, та розгортаємо, якщо був згорнутий. На тлі CooCoxIDE розгортаємо вікно провідника з проектом. Теки "Drivers", "Inc" і "Src" перетягуємо до теки проекту в CooCoxIDE (як на малюнку):
Підключення файлів до проекту
Файли підключено і проект має складатись з трьох тек "Drivers", "Inc" і "Src". Тепер викличемо конфігурацію проекту, або натиснувши кнопку в місці з позначкою "1", або викликавши контекстне меню проекту як позначено червоним в місці "2".
Конфігурація проекту
З'явиться вкладка "Configuration". У вкладці "Compile" поставити позначку на "Support C++", в розділі "Defined Symbols" додати рядки "USE_HAL_DRIVER" та "STM32F100xB".
Конфігурація компілятора для CoCooxIDE 1.7

Конфігурація компілятора для CooCoxIDE 2.0Beta трішечки відрізняється. Достатньо в розділі "Defined Symbols" додати рядки "USE_HAL_DRIVER", "STM32F100xB". Все інше так само як і для CoCooxIDE 1.7.
Конфігурація компілятора для CoCooxIDE 2.0Beta
Поясню звідки береться рядок "STM32F100xB", що додаємо до "Defined Symbols", так як мікроконтролери різні і потрібно знати точно який рядок додавати. Йдемо до дерева проекту і відкриваємо файл за таким шляхом: "\Drivers\CMSIS\Device\ST\STM32F1xx\Include\stm32f1xx.h" і у файлі шукаємо блок дефайнів "@addtogroup Device_Included". Нас цікавить мікроконтролер STM32F100RB, тому підходить перший рядок:
#if defined(STM32F100xB)
  #include "stm32f100xb.h"
Копіюємо через буфер обміну "STM32F100xB" в "Defined Symbols" тиснемо "Add" і вставляємо з буфера обміну рядок.

Далі відкриваємо файл за таким шляхом: "\Drivers\CMSIS\Device\ST\STM32F1xx\Source\Templates\gcc\startup_stm32f100xb.s" шукаємо рядок з "_estack" і міняємо на "_eram" (можливо що треба поміняти не в одному рядку, скористайтесь пошуком щоб переконатись що всі "_estack" поміняні на "_eram").
find estack
Переконаємось що все зроблено правильно і компілюється без помилок. Тиснемо "Build" або "F7". Як зроблено все вірно то компіляція пройде без помилок. Можемо написати мікроконтролерний "Hello World" і поблимати світлодіодами що є на платі розробника.

Мікроконтролерний "Hello World"

В CooCoxIDE відкриваємо файл "main.c" в теці "Src". Так як CubeMX зробив всю роботу за нас по ініціалізації мікроконтролера і підключеної до мікроконтролера периферії, нам залишається тільки керувати всім тим. Але, щоб при змінах режимів роботи мікроконтролера, додавання периферії, коли перезаписуємо проект з CubeMX не втратити написаний користувачем програмний код, треба писати свій код в спеціально визначених CubeMX місцях. Наприклад між рядками "/* USER CODE BEGIN 1 */"  та "/* USER CODE END 1 */" та інші подібні де зустрічається ключове слово "USER"  розташовуйте код між "begin" та "end".
По-перше в головній функції "int main(void)" увімкнемо синій світлодіод і зачекаємо пів секунди.
/* USER CODE BEGIN 1 */
 HAL_GPIO_WritePin(LED_BLUE_GPIO_Port,LED_BLUE_Pin,SET);
 HAL_Delay(500);
  /* USER CODE END 1 */
А далі в безкінечному циклі будемо міняти стан світлодіодів синього і зеленого з періодом пів секунди на протилежний.
/* USER CODE BEGIN WHILE */
  while (1)
  {
   HAL_GPIO_TogglePin(LED_BLUE_GPIO_Port,LED_BLUE_Pin | LED_GREEN_Pin);
   HAL_Delay(500);
  /* USER CODE END WHILE */

  /* USER CODE BEGIN 3 */

  }
  /* USER CODE END 3 */
Компілюємо "Build" або "F7". Заливаємо до мікроконтролеру "Flash -> Program Download". Милуємось світлодіодним блиманням.

Відео як імпортувати в CooCoxIDE з CubeMX для 1.7 та 2.0Beta


вівторок, 23 серпня 2016 р.

STM32: Підключення 1602 LCD Keypad Shield до STM32F103 Nucleo в засобі розробки mbed


Компанія STMicroelectronics виробляє плати розробника серії STM32 Nucleo, які підтримуються в онлайн засобі розробки mbed. І мають роз'єм сумісний з популярними і розповсюдженими платами розширення сімейства ARDUINO. Спробуємо на практиці підключити ось таку плату розширення LCD Keypad Shield з рідкокристалічним текстовим дисплеєм 1602 (16 символів, 2 рядки) та клавіатурою з 5 кнопок (вверх, вниз, ліворуч, праворуч, вибір), які підключені через резисторний дільник до одного аналогового входу A0. Кнопка "RST" (скидання) дублює аналогічну кнопку, що на платі розробника. Такі плати розширення LCD Keypad Sield та її клони існують в декількох варіантах. В мене, наприклад, спрощена схема підсвічування індикатора. Підсвічування вмикається від живлення плати. А є плати де підсвічування дисплею виведено на окремий A3 вивід і підсвічуванням дисплею можна керувати програмно. Увімкнути чи вимкнути підсвічування, або навіть регулювати яскравість підсвічування, коли налаштувати вихід A3 як PwmOut.
1602 LCD keypad shield
Підключати будемо до плати розробника STM32F103 Nucleo, але підійде будь яка плата серії Nucleo з роз'ємами arduino connector.
STM32F103 Nucleo
Підключення на рівні заліза дуже просте. Просто з'єднати плату розробника та плату розширення одну до одної "бутербродом" відповідними роз'ємами. Помилитись складно.

А от над програмним підключенням попрацюємо. Заходимо через браузер до "mbed compiler workspace". Ви маєте бути там зареєстровані. Як це зробити у дописі: "Знайомство з STM32F103 Nucleo та засобом розробки mbed".

Створюємо новий проект. Тиснемо "New" у верхньому лівому кутку засобу розробки mbed, або комбінацію кнопок"Ctrl+N". У вікні, що з'явиться обираємо "Nucleo-F103RB", або свою платформу, якщо відрізняється. З шаблонів обираємо пусту програму "Empty Program" і даємо назву програмі "1602_LCD_keypad_shield".
Створення нової прогарами
Тиснемо "OK". В просторі "Program Workspace" створиться наш новий пустий проект. 
Тепер наповнимо наш проект змістом. На назві нашої нової програми "1602_LCD_keypad_shield" клікаємо правою кнопкою миші (викликаємо контекстне меню) і обираємо створити новий файл "New File".
Створюємо новий файл
Даємо ім'я новому файлу "main.cpp" і тиснемо "OK".
Даємо назву main.cpp
Далі потрібно додати до проекту бібліотеку mbed. Аналогічно як додавали до проекту новий файл main.cpp, викликаємо контекстне меню проекту і обираємо "Import Library". Далі "From Import Wizard". В рядку пошуку "search" вводимо "mbed" без лапок і тиснемо кнопку "Search". Обираємо подвійним кліком миші бібліотеку "The official mbed C/C++ SDK provides the software platform and libraries to build your applications".
Підключення бібліотеки mbed
Ще потрібно підключити бібліотеку для роботи з нашим "LCD Keypad Shield". Аналогічно, як підключали до проекту бібліотеку "mbed", підключаємо бібліотеку для роботи з нашою платою розширення "LCD Keypad Shield". Для цього викликаємо контекстне меню проекту і обираємо "Import Library". Далі "From Import Wizard". В рядку пошуку "search" вводимо "lcd shield" без лапок і тиснемо кнопку "Search". Із списку обираємо бібліотеку "freetronicsLCDShield" A basic library for the Freetronics arduino LCD shield.

Все готово щоб почати писати програму. В дереві проекту обираємо файл "main.cpp" і першими рядками підключимо потрібні бібліотеки:
1
2
#include "mbed.h"
#include "freetronicsLCDShield.h"
Додамо в програму головну функцію main і безкінечний цикл. Тепер шаблон нашої програми буде виглядати так:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#include "mbed.h"
#include "freetronicsLCDShield.h"

int main()
{
    while(1)
    {
        
    }
}
Щоб дізнатись як працювати з бібліотекою "freetronicsLCDShield", заглянемо до довідки. В дереві проекту розгортаємо "freetronicsLCDShield", натиснувши на "+", далі "Classes" і "freetronicsLCDShield". Відкриється вкладка з довідкою на бібліотеку.
freetronicsLCDShield Class Reference
Спочатку до головної функції за допомоги конструктора створимо об'єкт. Дамо йому назву "lcdKeypad", та вкажемо всі потрібні для роботи піни.
1
freetronicsLCDShield lcdKeypad(D8, D9, D4, D5, D6, D7, D3, A0);
Де D8 - rs, D9 - enable, D4-D7 - data, D3 - back light, A0 - keypad.

І тепер в програмі ми можемо звертатись до членів класу через оператор - "."
Де "lcdKeypad" це назва класу, а члени це функції, такі як - cls, setCursor, home, readButton, тощо. Докладно дивитись в довідку по бібліотеці "freetronicsLCDShield".

Спочатку напишемо програму яка буде показувати на екран отримані значення від натискання той чи іншої кнопки. Максимально доповнив програму коментарями, має бути все зрозумілим.
 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
#include "mbed.h"
#include "freetronicsLCDShield.h"

// Оголошуємо клас freetronicsLCDShield з назвою lcdKeypad,
// та вказуємо всі потрібні піни для роботи
freetronicsLCDShield lcdKeypad(D8, D9, D4, D5, D6, D7, D3, A0); 

int main()
{
    // оголошуємо змінну типу float для значень з аналогового
    // входу A0, куди підключений keypad
    float val_button;
    // Очищаємо дисплей
    lcdKeypad.cls();
    // В позицію 0,0 пишемо текстовий рядок "STM32: mBed"
    lcdKeypad.printf("STM32: mBed");
    // чекаємо 2-ві секунди
    wait(2);
    
    while(1)
    {
        // Встановлюємо курсор в позицію 1,0 
        lcdKeypad.setCursorPosition(1, 0);
        // Зчитуємо значення на аналоговому вході A0.
        // Так як значення в межах від 0 до 1, то множимо на 1000
        val_button = lcdKeypad.readButton()*1000;
        // Друкуємо на дисплей отримані значення на аналоговому вході A0
        lcdKeypad.printf("Value: %4.0f", val_button);
        // Чекаємо 0.2 секунди щоб отримані значення не так швидко "мерехтіли"
        wait(0.2);
    }
}
Після того як ввели програму, компілюємо її, та переносимо на диск "Nucleo". Програма "заллється" в мікроконтролер і на екрані в першому рядку з'явиться напис "STM32: mBed", а в другому рядку "Value: 1000".
Результат роботи програми
Натискаємо по черзі на кнопки і занотовуємо отримані значення. Мої значення при натисканні на кнопки такі: нічого не натиснуто - 1000, left - 728, down - 468, up - 196, right - 0, select - 1000. Отакої! Виходить, що натискання на кнопку "SELECT" я ніяк не зможу відстежити, мабуть це
якийсь брак конкретно мого екземпляру. Але нічого страшного, достатньо і чотирьох кнопок.

Як виявилось це не брак плати, а "конфлікт" напруг. Напруга, яку подають на резистивний дільник з кнопками, дорівнює 5 вольтам, а максимальна напруга яку міряє наш АЦП - 3.3 вольта. Тому, все що вище 3.3 вольта, не відстежується. Треба плату LCD доопрацювати. Це або живити дільник від 3.3 вольт, або змінити номінали резисторів на дільнику.

Внесемо зміни до програми. Щоб при натисканні на кнопки, на дисплей виводився напис, яку кнопку ми натиснули. 

 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
#include "mbed.h"
#include "freetronicsLCDShield.h"

// Оголошуємо клас freetronicsLCDShield з назвою lcdKeypad,
// та вказуємо всі потрібні піни для роботи
freetronicsLCDShield lcdKeypad(D8, D9, D4, D5, D6, D7, D3, A0); 

int main()
{
    // оголошуємо змінну типу float для значень з аналогового
    // входу A0, куди підключений keypad
    float val_button;
    // Очищаємо дисплей
    lcdKeypad.cls();
    // В позицію 0,0 пишемо текстовий рядок "STM32 : mBed"
    lcdKeypad.printf("STM32 : mBed");
    // чекаємо 2-ві секунди
    wait(2);
    
    while(1)
    {
        // Встановлюємо курсор в позицію 1,0 
        lcdKeypad.setCursorPosition(1, 0);
        // Зчитуємо значення на аналоговому вході A0.
        // Так як значення в межах від 0 до 1, то множимо на 1000
        val_button = lcdKeypad.readButton()*1000;
        // Перевіряємо яку кнопку натиснуто
        if (val_button < 50)
        {
            lcdKeypad.printf("BUTTON: Right  ");
        }
        else if (val_button < 250)
        { 
            lcdKeypad.printf("BUTTON: Up     ");
        } 
        else if (val_button < 500)
        { 
            lcdKeypad.printf("BUTTON: Down   ");
        } 
        else if (val_button < 800)
        { 
            lcdKeypad.printf("BUTTON: Left   "); 
        } 
        else if (val_button > 950)
        { 
            lcdKeypad.printf("BUTTON: Nothing");
        }
        // Чекаємо 0.1 секунду
        wait(0.1);
    }
}
Компілюємо, переносимо до диску "Nucleo". Програма "заллється" до мікроконтролеру і на екрані побачимо таку "картину".
Результат роботи програми
Тепер при натисканні кнопок на дисплей буде виводитись напис: up, down, left, right.

В кого в "LCD Keypad Shield" підсвічування дисплею під'єднане коректно до піну D3, то треба додати до програми рядок "lcdKeypad.setBackLight(true);", щоб увімкнути підсвічування, або "lcdKeypad.setBackLight(false);", щоб вимкнути підсвічування. Наприклад, якщо натиснута кнопка "up" - вмикаємо підсвічування, "down" - вимикаємо.

Відео приклад

Коротке відео, як створити новий пустий проект, під'єднати потрібні бібліотеки. Далі вставляємо програму що вище з цієї статті, компілюємо, "заливаємо" до мікроконтролеру та розглядаємо як працює програма.


середа, 17 серпня 2016 р.

STM32: Знайомство з STM32F103Nucleo та засобом розробки mbed

Знайомство з STM32F103 Nucleo та засобом розробки mbed

Огляд плати розробника STM32F103 Nucleo

Загальний вигляд плати розробника STM32F103 Nucleo
Плата STM32F103 Nucleo має борту піддослідний чіп STM32F103RB. Це мікроконтролер з ядром Cortex-M3, 128Kbytes FLASH пам'яті, 72 MHz максимальна тактова частота процесора, максимальна кількість портів вводу/виводу 51, чотири - 16 бітних таймери, годинник реального часу RTC, інтерфейси: UART, SPI, I2C, USB, CAN, DMA, та ЦАП з АЦП. Докладна документація на сайті виробника за цим посиланням.

Периферія, що на борту плати розробника серії Nucleo доволі скромна і має тільки користувацькі світлодіод і кнопку. Має два типи роз'ємів для спілкування з зовнішнім світом. Це Arduino connector та ST Morpho connector. Розташування елементів і роз'ємів на платі зображено на малюнку.
Розташування елементів на платі
Документація по цій платі доступна на сайті розробника за цим посиланням. Як і інші плати розробника, містить програматор ST-link v2, яким також можна прошивати інші мікроконтролери STM. У разі застосування плати в кінцевий продукт, програматор ST-link, можна легко від'єднати від основної плати. І отримати в своє розпорядження окремий програматор.
ST-Link V2
На загал, доволі стандартно. Зосередимось на особливостях.

Головна особливість плат серії Nucleo це можливість підключення великої кількості "Arduino Shield", плат розширення периферії системи ARDUINO, за рахунок сумісності роз'ємів з Arduino connector. А також компанія ST пропонує плати розширення периферії своєї розробки. Це дозволяє швидко розширяти можливості плати і будувати доволі складні програмно-апаратні системи.
Arduino connector
Роз'єми ST Morpho дозволяють скористатись повним набором периферії та можливостей мікроконтролера STM32F103RB.
ST Morpho
На платі STM32F103 Nucleo не впаяний зовнішній кварцовий резонатор і два конденсатора для тактування мікроконтролера, хоча місце для нього на платі зарезервовано і при необхідності можна самому допаяти резонатор і два конденсатори. Можна і без нього, мікроконтролер має внутрішній генератор тактових імпульсів. Читав що плата постачається також і без зовнішнього годинникового кварцу для RTC, але особисто моя плата, такий кварц має.

Огляд засобу розробки mbed

Серія плат Nucleo входить до складу засобу розробки і компілятора mbed. Це така собі соціальна мережа розробників. Тут є все необхідне для швидкого старту. Засоби написання коду, компілятор, приклади програм, бібліотеки, форум розробників, тощо. Дуже зручно, що можна мати доступ до своїх розробок з будь якого місця, комп'ютера і ОС, з одного боку. І дуже не зручно з іншого боку, бо те все працює тільки при наявності доступу до інтернет.

Перш за все потрібно зареєструватись на цьому ресурсі. Заходимо за посиланням та реєструємось натиснувши кнопку "Signup". Далі тиснемо на "no, i haven't created an accaunt before" і дотримуючись інструкцій заповнюємо необхідні поля. Після успішної реєстрації отримаєте повідомлення "Welcome to mbed! To register a board, please plug the board in and click on MBED.HTM". Тепер можна додати свою плату натиснувши "Platform" та обрати STM32F103 Nucleo, або ту плату, що є у вас в наявності і підтримується mbed. Далі з'явиться робочий простір "Workspace Management". Як приклад, моя сторінка на mbed.
Workspace management
Все майже готово для початку. Залишилось завантажити і встановити "свіжі" драйвера для ST-LINK на свій ПК за цим посиланням. Та оновити "прошивку" самого програматора ST-LINK. Як це зробити, можна дізнатись за посиланням.

Мікроконтролерний "Hello World"

Випробуємо декілька простих програм такого собі мікроконтролерного "Hello World", це блимання світлодіодом і реагування на кнопку.

Заходимо через браузер до "mbed compiler workspace". Створюємо нову програму. Тиснемо меню "New".
Створюємо нову програму
З'явиться вікно де потрібно обрати свою платформу, шаблон і ім'я програми. Наша платформа "Platform: NUCLEO-F103RB", шаблон обираємо "Template: Blinky LED Hello World", ім'я програми "Program Name: Nucleo_blink_led" генерується, в цьому випадку, автоматично, але можна поміняти при бажанні.
Обираємо шаблон з списку
Тиснемо "OK" і отримуємо робочий простір з деревом проекту програми (1), списком файлів (2) і детально про програму (3).
Проект програми blink led
В дереві проекту (1) тиснемо на файлі "main.cpp" і ознайомлюємось з шаблоном програми.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#include "mbed.h"

DigitalOut myled(LED1);

int main() {
    while(1) {
        myled = 1; // LED is ON
        wait(0.2); // 200 ms
        myled = 0; // LED is OFF
        wait(1.0); // 1 sec
    }
}
В першому рядку підключаємо бібліотеку "mbed". В третьому рядку оголошуємо цифровим виходом LED1 та присвоюємо йому ім'я myled. Далі в основній функції main безкінечний цикл в якому вмикаємо світлодіод на 200 мілісекунд, а потім вимикаємо його на одну секунду і повторюємо знову і знову. Все дуже просто, наочно і зрозуміло.

Тепер потрібно скомпілювати програму. Для цього в меню тиснемо "Compile", або "Ctrl+D" і ваш браузер завантажує готовий для заливки бінарний файл "Nucleo_blink_led_NUCLEO_F103RB.bin" в теку завантажень. Під'єднуємо до ПК через USB свою плату Nucleo і з'явиться електронний диск (флешка) з ім'ям "NUCLEO". Тепер достатньо завантажений бінарний файл з програмою перемістити або скопіювати на цей диск і програма прошиється до мікроконтролеру автоматично. Як все виконали правильно, то на вашій платі Nucleo має блимати зелений світлодіод.

Реагуємо на кнопку

Випробуємо інший приклад програми. Створюємо нову програму - тиснемо в меню "New". З пропонованих шаблонів обираємо "Read the board user button state". І тиснемо "OK". В дереві проекту нашої нової програми обираємо файл "main.cpp" і ознайомлюємось з текстом програми.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#include "mbed.h"
 
DigitalIn mybutton(USER_BUTTON);
DigitalOut myled(LED1);
 
int main() {
  while(1) {
    if (mybutton == 0) { // Button is pressed
      myled = !myled; // Toggle the LED state
      wait(0.2); // 200 ms
    }
  }
}
Перший рядок підключаємо бібліотеку "mbed" в третьому рядку оголошуємо USER_BUTTON як цифровий вхід і даємо йому ім'я mybutton. Аналогічно, як в попередньому прикладі, оголошуємо світлодіод myled, як цифровий вихід. А далі, в тілі основній функції main, у безкінечному циклі перевіряємо чи натиснута кнопка - це рядок 8. Якщо так, то змінюємо стан світлодіода на протилежний. Затримка на 200 мілісекунд в 10 рядку і на початок безкінечного циклу.
Компілюємо програму, переносимо або копіюємо завантажений бінарний файл до диску Nucleo і перевіряємо роботу програми на нашій платі STM32F103RB Nucleo натискаючи на блакитну кнопку та спостерігаємо як міняється стан зеленого світлодіода.

Бібліотека mbed

В дереві проекту, Nucleo_blink_led та Nucleo_read_button, крім файлу main.cpp присутня бібліотека mbed, яку ми підключаємо першим рядком програми #include "mbed.h". Насправді, якщо відкрити бібліотеку натиснувши "mbed", натиснувши в дереві проекту побачимо список з документації на класи, групи і структури:
Документація на бібліотеку mbed
Тепер подвійний клік в списку на classes і розгорнеться список з документації на класи. Наприклад розглянемо клас DigitalOut. Подвійний клік на DigitalOut в списку, відкриється опис цього класу:
Опис класу digitalout
Як видно з малюнку, опис складається з всіх доступних функцій і параметрів для цього класу, та з невеличкого прикладу програми. 

Тепер ви вже можете самостійно спробувати та ознайомитись з іншими прикладами програм, що надаються mbed compiler. Та розглянути документацію інших класів, структур та груп.

Відео приклад "Blinky LED" та "Read Button"

Коротеньке наочне відео як завантажити приклади, компілювати їх, та "прошити" до мікроконтролера. 

середа, 10 серпня 2016 р.

STM32VLDISCOVERY та RTC. Помилка на платі.

Запускав на платі розробника STM32VLDiscovery, що на мікроконтролері STM32F100RB, годинник реального часу (RTC). Годинник працював відмінно. Але коли захотів перевірити як зберігається час від резервного живлення (окремої батарейки яка під'єднується до окремої "ноги" мікроконтролера), а не вийшло. Бо на платі STM32VLDiscovery не правильно зробили розведення доріжок. По схемі в документації все вірно.

Якщо роз'єднати перемичку SB1, а на EXT VBAT подати живлення від окремої батарейки у 3В, то при пропаданні основного живлення, час буде зберігатись, а точніше - буде продовжувати "йти". Але на самій платі розведення доріжок  не відповідає схемі. Ось реально що є. 

При знятій перемичці SB1 в повітрі висить VBAT з С16, а 1 пін чипу, як був з'єднаний з основним живленням, так і залишився. І при пропаданні основного живлення RTC буде обнулятись.

Майте на увазі. Потрібно самому робити зміни в схемі. Наприклад, відпаяти 1 пін мікроконтролеру від схеми і вивести окремо vbat.

середа, 3 серпня 2016 р.

Про важливість оновлення ST-LINK Utility та ST-LINK Upgrade

Придбав собі окремий програматор ST-LINK V2 (купити можна як на ebay так і на aliexpress, або деінде).
Програматор ST-Link v2

Бо, наприклад, такі плати не мають на борту свого програматора і тому приходиться підключатись до програматора у складі якоїсь плати розробника STM32 Discovery, STM32 Nucleo, тощо, які мають на своєму борту програматор ST-Link V2. А це доволі не зручно.
STM32F103C8T6 Minimum System Development Board
З підключенням не виникло проблем. Чотирьох-дротовим кабелем, що постачався в комплекті з програматором, з'єднати між собою програматор і плату однойменні виводи. Це 3.3V, GND, CLK і DIO (живлення +3.3В, земля, синхронізація і данні вхід/вихід).
З заливкою програм з засобу розробки CooCoxIDE 1.7 теж проблем не виникло. Але вже з CooCoxIDE 2.0 Beta залити програму до мікроконтролеру не вдалось. Виникала постійна помилка (з'єднайте кабель, підключіть програматор).
Як згодом виявилось, "прошивку" самого програматора, теж треба оновлювати. Бо не відомо скільки той програматор у продавця пролежав на складі і наскільки софт програматора вже застарів.
Завантажив з сайту st.com утиліту STM32 ST-LINK utility (ви маєте бути зареєстровані на сайті, щоб завантажувати). Встановив собі до ПК. Встановлення стандартне, без особливостей. Ця утиліта доволі універсальна і корисна. Одна з функцій, що нас зараз цікавить, це оновлення софту програматора.
Запустіть програму STM32 ST-LINK utility. Відкриється таке вікно.
Далі тиснемо меню ST-LINK -- FirmWare Updater і запуститься програма ST-LinkUpgrade яка має такий вигляд.
Далі тиснемо кнопку "Device Connect". Програматор має бути вже під'єднаний до ПК. З'явиться напис з поточною версією що у вашому програматорі "Firmware Version: V2.J27.S6 STM32+STM8 Debugger" та наявна остання версія на сайті ST - "Upgrade the firmware: V2.J27.S6". Це тільки приклад для мого конкретного програматора і на цей момент. В вас можуть бути інші версії "прошивок". Моя "прошивка" вже оновлена до останньої і прошивати не потрібно. А як у вас "старіша" версія, то тисніть кнопку YES для оновлення "прошивки". 
Після оновлення програмного забезпечення, програматор став без проблем "шити" мікроконтролер і з CooCoxIDE 2.0 Beta.
Є ще на сайті ST окрема утиліта ST-LINK/V2 firmware upgrade. Але спробувавши її, виявилось що утиліта пропонує встановити старішу "прошивку" чим вже є в програматорі.
Таке враження, що ці утиліти мають різні репозиторії. Так що майте на увазі. І перевіряйте де в наявності свіжіша "прошивка".

пʼятниця, 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, для відтворення на ньому рівня складності гри. Та додати мелодій для правильної комбінації і програшу. З нотами в мене все дуже погано, а ось як будуть побажання підключити індикатор до гри - пишіть в коментарях.