Добридень! Ми у проекті
Qt - це кросплатформовий фреймворк, який включає не тільки графічні компоненти, але й такі речі як QtNetwork, набір класів для роботи з базами даних, Qt for Automation (у тому числі, для реалізації IoT) і багато іншого. Розробники команди Qt заздалегідь передбачили використання Qt у вбудованих системах, тому бібліотеки досить добре конфігуруються. Проте донедавна мало хто замислювався про портування Qt на мікроконтролери, ймовірно тому, що таке завдання виглядає складним — Qt велике, MCU маленькі.
З іншого боку, зараз існують мікроконтролери, призначені для роботи з мультимедіа і перевершують перші Pentium-и. Близько року тому у блозі Qt з'явився
У Embox вже давно портовано Qt 4.8, тому вирішили спробувати на ньому. Вибрали програму moveblocks — приклад пружної анімації.
Qt moveblocks на QEMU
Для початку конфігуруємо Qt наскільки можна з мінімальним набором компонент, необхідним підтримки анімації. І тому існує опція “-qconfig minimal,small,medium …”. Вона підключає конфігураційний файл зі складу Qt з безліччю макросів - що включити/що вимкнути. Після цієї опції додаємо до конфігурації інші прапори, якщо хочемо ще щось вимкнути додатково. Ось приклад нашої
Щоб Qt запрацювало, потрібно додати шар сумісності з ОС. Один із способів – реалізувати QPA (Qt Platform Abstraction). За основу взяли вже готовий плагін fb_base у складі Qt, на базі якого працює QPA для Лінукс. У результаті вийшов невеликий плагін emboxfb, який надає Qt фреймбуфер Embox'a, а далі малює туди вже без сторонньої допомоги.
Ось так виглядає створення плагіна
QEmboxFbIntegration::QEmboxFbIntegration()
: fontDb(new QGenericUnixFontDatabase())
{
struct fb_var_screeninfo vinfo;
struct fb_fix_screeninfo finfo;
const char *fbPath = "/dev/fb0";
fbFd = open(fbPath, O_RDWR);
if (fbPath < 0) {
qFatal("QEmboxFbIntegration: Error open framebuffer %s", fbPath);
}
if (ioctl(fbFd, FBIOGET_FSCREENINFO, &finfo) == -1) {
qFatal("QEmboxFbIntegration: Error ioctl framebuffer %s", fbPath);
}
if (ioctl(fbFd, FBIOGET_VSCREENINFO, &vinfo) == -1) {
qFatal("QEmboxFbIntegration: Error ioctl framebuffer %s", fbPath);
}
fbWidth = vinfo.xres;
fbHeight = vinfo.yres;
fbBytesPerLine = finfo.line_length;
fbSize = fbBytesPerLine * fbHeight;
fbFormat = vinfo.fmt;
fbData = (uint8_t *)mmap(0, fbSize, PROT_READ | PROT_WRITE,
MAP_SHARED, fbFd, 0);
if (fbData == MAP_FAILED) {
qFatal("QEmboxFbIntegration: Error mmap framebuffer %s", fbPath);
}
if (!fbData || !fbSize) {
qFatal("QEmboxFbIntegration: Wrong framebuffer: base = %p,"
"size=%d", fbData, fbSize);
}
mPrimaryScreen = new QEmboxFbScreen(fbData, fbWidth,
fbHeight, fbBytesPerLine,
emboxFbFormatToQImageFormat(fbFormat));
mPrimaryScreen->setPhysicalSize(QSize(fbWidth, fbHeight));
mScreens.append(mPrimaryScreen);
this->printFbInfo();
}
А ось так буде виглядати перемальовка
QRegion QEmboxFbScreen::doRedraw()
{
QVector<QRect> rects;
QRegion touched = QFbScreen::doRedraw();
DPRINTF("QEmboxFbScreen::doRedrawn");
if (!compositePainter) {
compositePainter = new QPainter(mFbScreenImage);
}
rects = touched.rects();
for (int i = 0; i < rects.size(); i++) {
compositePainter->drawImage(rects[i], *mScreenImage, rects[i]);
}
return touched;
}
У результаті з увімкненою оптимізацією компілятора за розміром пам'яті -Os образ бібліотеки вийшов 3.5 Мб, що звичайно не влазить в основну пам'ять STM32F746. Як ми вже писали в іншій статті про OpenCV, на цій платі є:
- 1 Мб ROM
- 320 Кб RAM
- 8 Мб SDRAM
- 16 Мб QSPI
Так як для OpenCV вже було додано підтримку виконання коду з QSPI, ми вирішили почати з того, що завантажили образ Embox c Qt в QSPI повністю. І ура, все майже одразу ж запустилося з QSPI! Але як і у випадку з OpenCV виявилося, що працює надто повільно.
Тому вирішили робити так — спочатку копіюємо образ QSPI, потім завантажуємо його в SDRAM і виконуємося звідти. З SDRAM стало трохи швидшим, але все одно далеко від QEMU.
Далі була ідея включити плаваючу точку - адже Qt робить деякі обчислення координат квадратів в анімації. Спробували, але тут не отримали видимого прискорення, хоч у
Найефективнішим виявилася ідея перенести фреймбуфер з SDRAM у внутрішню пам'ять. Для цього ми зробили розміри екрана не 480х272, а 272х272. Ще зменшили глибину кольору з A8R8G8B8 до R5G6B5, таким чином скоротивши розмір одного пікселя з 4 до 2 байт. Отримали розмір фреймбуфера 272*272*2 = 147968 байт. Це дало значне прискорення, мабуть найпомітніше, анімація стала майже плавною.
Останньою оптимізацією стало виконання коду Embox із RAM, а Qt із SDRAM. Для цього ми спочатку зазвичай лінкуємо статично Embox разом з Qt, але сегменти text, rodata, data і bss бібліотеки розміщуємо в QSPI, щоб потім скопіювати в SDRAM.
section (qt_text, SDRAM, QSPI)
phdr (qt_text, PT_LOAD, FLAGS(5))
section (qt_rodata, SDRAM, QSPI)
phdr (qt_rodata, PT_LOAD, FLAGS(5))
section (qt_data, SDRAM, QSPI)
phdr (qt_data, PT_LOAD, FLAGS(6))
section (qt_bss, SDRAM, QSPI)
phdr (qt_bss, PT_LOAD, FLAGS(6))
За рахунок виконання коду Embox із ROM теж отримали відчутне прискорення. У результаті анімація вийшла досить плавною:
Вже в самому кінці, готуючи статтю та пробуючи різні конфігурації Embox'a, з'ясувалося, що Qt moveblocks чудово працює і з QSPI з фреймбуфером у SDRAM, а вузьким місцем був розмір фреймбуфера! Очевидно, щоб подолати початкове "слайдшоу" вистачало прискорення вдвічі за рахунок банального зменшення розміру фреймбуфера. А досягти такого результату переносом тільки коду Embox в різні швидкі пам'яті не вдалося (прискорення виходило не в 2, а приблизно в 2 рази).
Як спробувати самому
Якщо у вас є STM32F7-Discovery, ви можете запустити Qt під Embox самі. Прочитати як це робиться можна на нашому
Висновок
У результаті нам удалося запустити Qt! Складність завдання, з погляду, дещо перебільшена. Звичайно потрібно враховувати специфіку мікроконтролерів і взагалі розуміти архітектуру обчислювальних систем. Результати оптимізації вказують на відомий факт, що найвужче місце у обчислювальній системі, це не процесор, а пам'ять.
Цього року ми братимемо участь на фестивалі
Джерело: habr.com