Портування Qt на STM32

Портування Qt на STM32Добридень! Ми у проекті Embox запустили Qt на STM32F7-Discovery та хотіли б про це розповісти. Раніше ми вже розповідали як нам вдалося запустити OpenCV.

Qt - це кросплатформовий фреймворк, який включає не тільки графічні компоненти, але й такі речі як QtNetwork, набір класів для роботи з базами даних, Qt for Automation (у тому числі, для реалізації IoT) і багато іншого. Розробники команди Qt заздалегідь передбачили використання Qt у вбудованих системах, тому бібліотеки досить добре конфігуруються. Проте донедавна мало хто замислювався про портування Qt на мікроконтролери, ймовірно тому, що таке завдання виглядає складним — Qt велике, MCU маленькі.

З іншого боку, зараз існують мікроконтролери, призначені для роботи з мультимедіа і перевершують перші Pentium-и. Близько року тому у блозі Qt з'явився пост. Розробники зробили порт Qt під ОС RTEMS і запустили приклади з віджетами на декількох платах під управлінням stm32f7. Нас це зацікавило. Було помітно, і розробники про це пишуть, що Qt гальмує на STM32F7-Discovery. Нам стало цікаво, чи зможемо ми запустити Qt під Embox, причому не просто намалювати віджет, а запустити анімацію.

У Embox вже давно портовано Qt 4.8, тому вирішили спробувати на ньому. Вибрали програму moveblocks — приклад пружної анімації.

Qt moveblocks на QEMUПортування Qt на STM32

Для початку конфігуруємо 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 виявилося, що працює надто повільно.

Портування Qt на STM32

Тому вирішили робити так — спочатку копіюємо образ QSPI, потім завантажуємо його в SDRAM і виконуємося звідти. З SDRAM стало трохи швидшим, але все одно далеко від QEMU.

Портування Qt на STM32

Далі була ідея включити плаваючу точку - адже Qt робить деякі обчислення координат квадратів в анімації. Спробували, але тут не отримали видимого прискорення, хоч у статті Розробники Qt стверджували, що FPU дає значний приріст у швидкості для “dragging animation” на touchscreen'e. Можливо, у рухомихlocks значно менше обчислень з плаваючою точкою, і це залежить від конкретного прикладу.

Найефективнішим виявилася ідея перенести фреймбуфер з 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! Складність завдання, з погляду, дещо перебільшена. Звичайно потрібно враховувати специфіку мікроконтролерів і взагалі розуміти архітектуру обчислювальних систем. Результати оптимізації вказують на відомий факт, що найвужче місце у обчислювальній системі, це не процесор, а пам'ять.

Цього року ми братимемо участь на фестивалі TechTrain. Там ми детальніше розповімо та покажемо Qt, OpenCV на мікроконтролерах та інші наші досягнення.

Джерело: habr.com

Додати коментар або відгук