Написання програм на C++

Що таке програма в Keira?

Keira написана на C++, і вона містить ряд вбудованих програм. Програма в Keira - це клас, який наслідує клас App та визначає метод App::run(). Цей метод викликається при запуску програми.

Всі програми є вбудованими, тобто вони мають бути частиною Keira. Це означає, що ви не можете додати свою програму до Keira, якщо не зміните код Keira і не перепрошиєте Лілку новим кодом.

Ви можете використовувати будь-які функції з бібліотеки Lilka.

Примітка

Чи можу я написати свою програму на C++ окремо від Keira, якось скомпілювати її і запустити в Keira з SD-карти?

Ні. Динамічне завантаження програм у вбудованих системах - це дуже складний процес. Keira наразі не підтримує цю функцію. Але це може змінитись в майбутньому.

Якщо ви хочете писати програми, не перепрошиваючи Лілку, ми радимо спробувати вам Lua: Написання програм на Lua.

Клас App

Для створення власної програми вам потрібно наслідувати клас App та визначити метод App::run(). Цей метод буде викликатися при запуску програми.

Ось перелік важливих методів та властивостей класу App:

class App

Клас, що представляє додаток для Кіри.

Додатки запускаються за допомогою синглтону AppManager.

Додаток має визначити принаймні метод run(), який буде викликатися в окремій задачі FreeRTOS.

При завершенні додатку, AppManager зупиняє задачу та видаляє об’єкт додатку.

Приклад запуску додатку:

#include <appmanager.h>
#include <myapp.h>

// ...

AppManager::getInstance()->runApp(new MyApp());

Subclassed by AbstractLuaRunnerApp, BallApp, CubeApp, DemoLines, DiskApp, EpilepsyApp, FTPServerApp, GPIOManagerApp, KeyboardApp, LauncherApp, LetrisApp, LilTrackerApp, MJSApp, ModPlayerApp, NesApp, PetPetApp, ScanI2CApp, StatusBarApp, TamagotchiApp, TransformApp, UserSPIApp, WeatherApp, WiFiConfigApp

Public Functions

void queueDraw()

Повідомити ОС, що додаток завершив малювання кадру.

Цей метод слід викликати після малювання кожного кадру.

Технічно, цей метод міняє місцями передній буфер (canvas) та задній буфер (backCanvas).

Якщо цей метод викликається в той час, коли ОС вже малює попередній кадр, то він призведе до нетривалого блокування додатку. Іншими словами, якщо ваш додаток малює кадри швидше, ніж ОС здатна їх відображати, то цей метод буде час від часу заповільнювати ваш додаток. Це не проблема, але варто про це пам’ятати.

Public Members

lilka::Canvas *canvas

Вказівник на передній буфер для малювання.

Додаток повинен використовувати цей буфер для малювання всієї графіки.

Після малювання, буфер потрібно відобразити на екрані за допомогою методу queueDraw().

Protected Functions

void setFlags(AppFlags flags)

Встановити прапорці додатку.

Наприклад, якщо додаток має відображатися на весь екран, то слід викликати setFlags(APP_FLAG_FULLSCREEN).

Параметри:

flags

void setStackSize(uint32_t stackSize)

Встановити розмір стеку задачі додатку.

За замовчуванням, розмір стеку задачі дорівнює 8192 байт. Проте деякі додатки можуть вимагати більший розмір стеку.

Private Functions

virtual void run() = 0

Основний код додатку.

Цей метод викликається в окремій задачі FreeRTOS.

Додаток завершується, коли цей метод завершується або робить return.

inline virtual void onSuspend()

Цей метод викликається операційною системою, коли вона збирається зупинити ваш додаток.

inline virtual void onResume()

Цей метод викликається операційною системою, коли вона збирається відновити роботу вашого додатку.

inline virtual void onStop()

Цей метод викликається операційною системою, коли вона збирається зупинити ваш додаток.

Приклад програми

Давайте створимо просту програму, яка буде малювати круг на екрані, який можна переміщувати за допомогою кнопок.

Для цього створіть два нові файли в директорії firmware/keira/src/apps:

myapp.h
#include <lilka.h>
#include "app.h"

class MyApp : public App {
public:
    MyApp();
private:
    void run() override;
};
myapp.cpp
#include "myapp.h"

MyApp::MyApp() : App("Моя програма") {
}

void MyApp::run() {
    int16_t x = canvas->width() / 2;
    int16_t y = canvas->height() / 2;
    while (true) {
        // читаємо стан кнопок
        lilka::State state = lilka::controller.getState();

        if (state.up.pressed) {
            y--;
        } else if (state.down.pressed) {
            y++;
        }
        if (state.left.pressed) {
            x--;
        } else if (state.right.pressed) {
            x++;
        }
        if (state.a.pressed) {
            // Завершуємо програму
            return;
        }

        // заповнюємо екран чорним кольором
        canvas->fillScreen(canvas->color565(0, 0, 0));
        // малюємо білий круг
        canvas->fillCircle(x, y, 10, canvas->color565(255, 255, 255));

        // повідомляємо Keira, що буфер змінився і його потрібно перемалювати
        queueDraw();
    }
}

Давайте розберемося з кодом.

  1. Ми створили клас MyApp, який наслідує клас App.

    App містить в собі віртуальний метод run, який викликається при запуску програми.

    Також App автоматично створює об’єкт canvas, який представляє собою буфер для малювання. Ви повинні малювати саме на ньому, а не на екрані. Детальніше про це - згодом.

  2. Весь код нашої програми знаходиться в методі run. Він автоматично викликається при запуску програми.

    Програма виконується в циклі while (true). Це означає, що вона буде виконуватися постійно, поки ви не викличете return.

  3. Ми читаємо стан кнопок за допомогою lilka::controller.getState(). Це повертає об’єкт lilka::State, який містить в собі стан кожної кнопки.

    Наприклад, state.up.pressed - це true, якщо кнопка up натиснута.

  4. Ми щоразу заповнюємо екран чорним кольором, малюємо білий круг, а потім викликаємо queueDraw().

    Цей метод повідомляє Keira, що буфер змінився і його потрібно перемалювати.

    Примітка

    Чому ми не малюємо безпосередньо на екрані, і чому щоразу заповнюємо його чорним кольором? І що таке* queueDraw()?

    Це все пов’язано з тим, що Keira - це мультизадачна операційна система, і різні програми можуть намагатись одночасно малювати щось на екрані.

    Щоб уникнути конфліктів, Keira використовує подвійну буферизацію. Це означає, що кожна програма має два власні буфери: один («передній») для малювання, а інший («задній») - для відображення на екрані.

    • canvas - це передній буфер. Саме на ньому ваша програма малює все, що ви хочете побачити на екрані.

    • backCanvas - це задній буфер. Вам не потрібно ним керувати.

    Коли ви викликаєте метод queueDraw(), Keira міняє місцями передній і задній буфери і через деякий час починає малювати задній буфер на екрані в фоновому режимі. Таким чином ваша програма ніколи не малює безпосередньо на екрані: це робить Keira, а конкретніше - клас AppManager.

    canvas завжди вказує на передній буфер, тому ви повинні малювати саме на ньому. Але оскільки ці буфери постійно міняються місцями, ваша програма не повинна робити жодних припущень про те, що було намальовано в попередній ітерації.

    Тому після кожного виклику queueDraw() кожна програма повинна знову малювати все, що ви хочете побачити на екрані, оскільки canvas буде містити «сміття», а не те, що ви малювали в попередній ітерації, і завжди відставатиме на одну ітерацію від того, що відображається на екрані.

    Це дає можливість не лише здійснювати конкурентне малювання на екрані з декількох програм, але й використовувати для цього обидва ядра процесора: одне ядро виконує вашу програму, а інше - перемальовує екран. Це збільшує FPS (кількість кадрів в секунду) і дозволяє досягнути максимальної утилізації процесора.

    Майте на увазі, що виклик queueDraw() може заблокувати вашу програму на деякий час. Це ставатиметься в ситуаціях, коли Кіра ще не завершила малювати на екрані попередній буфер, а ви вже викликаєте queueDraw() знову. Це - не проблема, але варто про це пам’ятати.

    В середньому, малювання займає близько 1/30 секунди. Це означає, що ви можете викликати queueDraw() близько 30 разів в секунду без блокування вашої програми.

Реєстрація програми в меню програм

Основна програма, що запускається при завантаженні Кіри, називається Launcher. Вона відповідає за відображення меню програм, налаштувань, інформації, а такоє запуск програм.

Щоб програма з’явилася в меню програм, вам потрібно зареєструвати її в одному з меню Launcher. Найпростіший спосіб - це додати вашу програму в меню додатків. Для цього знайдіть наступний код у файлі launcher.cpp та додайте вашу програму в список програм:

launcher.cpp
 1#include "myapp.h"  // <--- підключаємо вашу програму
 2
 3// ...
 4
 5ITEM_LIST app_items = {
 6    ITEM_SUBMENU(
 7        "Демо",
 8        {
 9            ITEM_APP("Лінії", DemoLines),
10            ITEM_APP("Диск", DiskApp),
11            ITEM_APP("Перетворення", TransformApp),
12            ITEM_APP("М'ячик", BallApp),
13            ITEM_APP("Куб", CubeApp),
14            ITEM_APP("Епілепсія", EpilepsyApp),
15        }
16    ),
17    ITEM_SUBMENU(
18        "Тести",
19        {
20            ITEM_APP("Клавіатура", KeyboardApp),
21            ITEM_APP("Тест SPI", UserSPIApp),
22            ITEM_APP("I2C-сканер", ScanI2CApp),
23        },
24    ),
25    ITEM_APP("Летріс", LetrisApp),
26    ITEM_APP("Тамагочі", TamagotchiApp),
27    ITEM_APP("Моя програма", MyApp),  // <--- додаємо вашу програму
28};

Після цього перепрошийте Лілку, і ваша програма з’явиться в меню програм.