Написання програм на C++
Що таке програма в Keira?
Keira написана на C++, і вона містить ряд вбудованих програм. Програма в Keira - це клас, який наслідує клас App та визначає метод App::run(). Цей метод викликається при запуску програми.
Всі вбудовані програми мають бути частиною Keira. Це означає, що для додавання вбудованої C++ програми вам потрібно змінити код Keira і перепрошити Лілку.
Ви можете використовувати будь-які функції з бібліотеки Lilka.
Примітка
Чи можу я написати свою програму на C/C++ окремо від Keira, скомпілювати її і запустити з SD-карти?
Так! Keira підтримує динамічне завантаження програм (.so файлів) з SD-карти за допомогою вбудованого ELF-завантажувача.
Деталі — в розділі Динамічні програми (DynApp / .so).
Якщо ви хочете писати програми скриптовою мовою — спробуйте Lua: Написання програм на Lua.
Клас App
Для створення власної програми вам потрібно наслідувати клас App та визначити метод App::run(). Цей метод буде викликатися при запуску програми.
Ось перелік важливих методів та властивостей класу App:
-
class App : public KeiraThread
Subclassed by AbstractLuaRunnerApp, BallApp, CallBackTestApp, ComboApp, CubeApp, DemoLines, DiskApp, DynApp, EpilepsyApp, FileManagerApp, GPIOManagerApp, KeyboardApp, LauncherApp, LetrisApp, LilCatalogApp, LilTrackerApp, MJSApp, MadPlayerApp, MultiBootApp, NesApp, PetPetApp, ScanI2CApp, SoundConfigApp, StatusBarApp, TamagotchiApp, TransformApp, USBDriveApp, UserSPIApp, WeatherApp, WiFiConfigApp, pastebinApp
Приклад програми
Давайте створимо просту програму, яка буде малювати круг на екрані, який можна переміщувати за допомогою кнопок.
Для цього створіть два нові файли в директорії src/apps:
#include <lilka.h>
#include "keira/app.h"
class MyApp : public App {
public:
MyApp();
private:
void run() override;
};
#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();
}
}
Давайте розберемося з кодом.
Ми створили клас
MyApp, який наслідує класApp.Appмістить в собі віртуальний методrun, який викликається при запуску програми.Також
Appавтоматично створює об’єктcanvas, який представляє собою буфер для малювання. Ви повинні малювати саме на ньому, а не на екрані. Детальніше про це - згодом.Весь код нашої програми знаходиться в методі
run. Він автоматично викликається при запуску програми.Програма виконується в циклі
while (true). Це означає, що вона буде виконуватися постійно, поки ви не викличетеreturn.Ми читаємо стан кнопок за допомогою
lilka::controller.getState(). Це повертає об’єктlilka::State, який містить в собі стан кожної кнопки.Наприклад,
state.up.pressed- цеtrue, якщо кнопкаupнатиснута.Ми щоразу заповнюємо екран чорним кольором, малюємо білий круг, а потім викликаємо
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 та додайте вашу програму в список програм:
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};
Після цього перепрошийте Лілку, і ваша програма з’явиться в меню програм.
Динамічні програми (DynApp / .so)
Keira підтримує завантаження та виконання програм, скомпільованих у формат ELF shared object (.so), безпосередньо з SD-карти.
Це дозволяє писати програми на C, компілювати їх окремо від Keira і запускати без перепрошивки Лілки.
Принцип роботи
Вбудований ELF-завантажувач (lilka::DynLoader) виконує наступні дії:
Завантажує
.soфайл з SD-карти в PSRAMРозбирає секції ELF (
.text,.data,.rodata,.bss)Виконує Xtensa-релокації (
R_XTENSA_RELATIVE,R_XTENSA_GLOB_DAT,R_XTENSA_JMP_SLOT)Знаходить символи через таблицю експортованих функцій Keira
Викликає точку входу
app_main
Написання DynApp
Для написання програми вам потрібно:
Включити заголовок
keira_api.h(знаходиться вexamples/dynapp_demo/)Реалізувати функцію
int app_main(int argc, char *argv[])Використовувати функції
keira_*для роботи з дисплеєм, контролером, звуком та таймерами
#include "keira_api.h"
int app_main(int argc, char *argv[]) {
(void)argc;
(void)argv;
keira_display_fill_screen(KEIRA_COLOR_BLACK);
keira_display_set_cursor(10, 10);
keira_display_set_text_color(KEIRA_COLOR_WHITE);
keira_display_set_text_size(2);
keira_display_println("Hello from .so!");
keira_queue_draw();
/* Чекаємо натискання кнопки B для виходу */
for (;;) {
uint32_t state = keira_controller_get_state();
if (state & KEIRA_JUST_B) break;
keira_delay(16);
}
return 0;
}
Компіляція
Для компіляції потрібен крос-компілятор xtensa-esp32s3-elf-gcc.
Якщо у вас встановлено PlatformIO, він вже є за шляхом
~/.platformio/packages/toolchain-xtensa-esp32s3/bin/.
За допомогою Make:
cd examples/dynapp_demo
make
За допомогою CMake:
cd examples/dynapp_demo
mkdir build && cd build
cmake -DCMAKE_TOOLCHAIN_FILE=../xtensa-esp32s3.cmake ..
make
Після компіляції ви отримаєте файл demo.so.
Запуск
Скопіюйте файл
.soна SD-картуВідкрийте Файловий менеджер у Keira
Виберіть файл — програма запуститься автоматично
Доступний API
Дисплей:
keira_display_fill_screen,keira_display_draw_pixel,keira_display_draw_line,keira_display_draw_rect,keira_display_fill_rect,keira_display_draw_circle,keira_display_fill_circle,keira_display_draw_triangle,keira_display_fill_triangle,keira_display_set_cursor,keira_display_set_text_color,keira_display_set_text_size,keira_display_print,keira_display_println,keira_display_width,keira_display_height,keira_display_color565Буфер:
keira_queue_drawКонтролер:
keira_controller_get_stateЧас:
keira_delay,keira_millisЗуммер:
keira_buzzer_play,keira_buzzer_stop
Повна документація — у файлі examples/dynapp_demo/keira_api.h.
Попередження
Програма виконується в тому ж адресному просторі, що й Keira. Помилки в .so можуть призвести до перезавантаження пристрою.
Стандартна бібліотека C недоступна напряму — базові функції (
memcpy,printf,malloc) надаються через таблицю символів Keira.argv[0]містить шлях до.soфайлу на SD-карті.