Skip to content

Commit

Permalink
doc: add info about custom C++ apps
Browse files Browse the repository at this point in the history
  • Loading branch information
and3rson committed Mar 14, 2024
1 parent 57b8a30 commit 7dcdf31
Show file tree
Hide file tree
Showing 5 changed files with 218 additions and 1 deletion.
2 changes: 1 addition & 1 deletion docs/_doxygen/Doxyfile
Original file line number Diff line number Diff line change
Expand Up @@ -943,7 +943,7 @@ WARN_LOGFILE =
# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
# Note: If this tag is empty the current directory is searched.

INPUT = "../../sdk/lib/lilka/src"
INPUT = "../../sdk/lib/lilka/src" "../../firmware/keira"

# This tag can be used to specify the character encoding of the source files
# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
Expand Down
2 changes: 2 additions & 0 deletions docs/library/index.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
.. _lilka-lib:

Бібліотека ``lilka``
====================

Expand Down
191 changes: 191 additions & 0 deletions docs/manual/keira/custom_apps.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
Написання програм на C++
========================

.. contents:: Зміст
:local:

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

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

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

Ви можете використовувати будь-які функції з :ref:`бібліотеки Lilka <lilka-lib>`.

.. note::

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

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

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

Клас :cpp:class:`App`
---------------------

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

.. doxygenclass:: App
:members:
:protected-members:
:private-members:

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

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

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

.. code-block:: cpp
:caption: myapp.h
#include <lilka.h>
#include "app.h"
class MyApp : public App {
public:
MyApp();
private:
void run() override;
};
.. code-block:: cpp
:caption: 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, що буфер змінився і його потрібно перемалювати.

.. note::

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

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

Щоб уникнути конфліктів, Keira використовує `подвійну буферизацію <https://uk.wikipedia.org/wiki/%D0%91%D0%B0%D0%B3%D0%B0%D1%82%D0%BE%D0%BA%D1%80%D0%B0%D1%82%D0%BD%D0%B0_%D0%B1%D1%83%D1%84%D0%B5%D1%80%D0%B8%D0%B7%D0%B0%D1%86%D1%96%D1%8F#%D0%9F%D0%BE%D0%B4%D0%B2%D1%96%D0%B9%D0%BD%D0%B0_%D0%B1%D1%83%D1%84%D0%B5%D1%80%D0%B8%D0%B7%D0%B0%D1%86%D1%96%D1%8F_%D1%83_%D0%BA%D0%BE%D0%BC%D0%BF'%D1%8E%D1%82%D0%B5%D1%80%D0%BD%D1%96%D0%B9_%D0%B3%D1%80%D0%B0%D1%84%D1%96%D1%86%D1%96>`_.
Це означає, що кожна програма має два власні буфери: один ("передній") для малювання, а інший ("задній") - для відображення на екрані.

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

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

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

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

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

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

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

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

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

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

.. code-block:: cpp
:linenos:
:emphasize-lines: 1, 7, 21
:caption: launcher.cpp
#include "myapp.h" // <--- підключаємо вашу програму
// ...
// всередині функції appsMenu:
String titles[] = {
"Моя програма", // <--- назва вашої програми
"Лінії",
"Шайба",
"Перетворення",
"М'ячик",
"Епілепсія",
"Летріс",
"Клавіатура",
"Тест SPI",
"I2C-сканер",
"<< Назад",
};
// vector of functions
APP_CLASS_LIST classes = {
APP_CLASS(MyApp), // <--- клас вашої програми
APP_CLASS(DemoLines),
APP_CLASS(DiskApp),
APP_CLASS(TransformApp),
APP_CLASS(BallApp),
APP_CLASS(EpilepsyApp),
APP_CLASS(LetrisApp),
APP_CLASS(KeyboardApp),
APP_CLASS(UserSPIApp),
APP_CLASS(ScanI2CApp),
};
Після цього перепрошийте Лілку, і ваша програма з'явиться в меню програм.
1 change: 1 addition & 0 deletions docs/manual/keira/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@ Keira OS
features
flashing
sdcard
custom_apps
lua/intro
lua/reference/index
23 changes: 23 additions & 0 deletions firmware/keira/src/app.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,24 @@ typedef enum {
APP_FLAG_FULLSCREEN = 1,
} AppFlags;

/// Клас, що представляє додаток для Кіри.
///
/// Додатки запускаються за допомогою синглтону AppManager.
///
/// Додаток має визначити принаймні метод run(), який буде викликатися в окремій задачі FreeRTOS.
///
/// При завершенні додатку, AppManager зупиняє задачу та видаляє об'єкт додатку.
///
/// Приклад запуску додатку:
///
/// @code
/// #include <appmanager.h>
/// #include <myapp.h>
///
/// // ...
///
/// AppManager::getInstance()->runApp(new MyApp());
/// @endcode
class App {
friend class AppManager;

Expand Down Expand Up @@ -42,6 +60,11 @@ class App {
lilka::Canvas* backCanvas;

private:
/// Основний код програми.
///
/// Цей метод викликається в окремій задачі FreeRTOS.
///
/// Програма завершується, коли цей метод завершується або робить return.
virtual void run() = 0;
virtual void onSuspend() {
}
Expand Down

0 comments on commit 7dcdf31

Please sign in to comment.