Portarea Qt la STM32

Portarea Qt la STM32Bună ziua Suntem în proiect Embox a lansat Qt pe STM32F7-Discovery și ar dori să vorbim despre asta. Mai devreme, am povestit deja cum am reușit să lansăm OpenCV.

Qt este un cadru multiplatformă care include nu numai componente grafice, ci și lucruri precum QtNetwork, un set de clase pentru lucrul cu baze de date, Qt for Automation (inclusiv pentru implementarea IoT) și multe altele. Echipa Qt a fost proactivă cu privire la utilizarea Qt în sistemele încorporate, astfel încât bibliotecile sunt destul de configurabile. Cu toate acestea, până de curând, puțini oameni s-au gândit la portarea Qt la microcontrolere, probabil pentru că o astfel de sarcină pare dificilă - Qt este mare, MCU-urile sunt mici.

Pe de altă parte, în acest moment există microcontrolere concepute să funcționeze cu multimedia și superioare primelor Pentium. Acum aproximativ un an a apărut blogul Qt rapid. Dezvoltatorii au creat un port pentru Qt pentru sistemul de operare RTEMS și au lansat exemple cu widget-uri pe mai multe plăci care rulează stm32f7. Asta ne-a interesat. S-a observat, și dezvoltatorii înșiși scriu despre asta, că Qt este lent pe STM32F7-Discovery. Ne întrebam dacă am putea rula Qt sub Embox și nu doar să desenăm un widget, ci să rulăm o animație.

Qt 4.8 a fost portat pe Embox de mult timp, așa că am decis să-l încercăm. Am ales aplicația moveblocks - un exemplu de animație elastică.

Qt moveblocks pe QEMUPortarea Qt la STM32

Pentru început, configuram Qt, dacă este posibil, cu setul minim de componente necesare pentru a susține animația. Pentru aceasta există o opțiune „-qconfig minimal,small,medium...”. Conectează un fișier de configurare de la Qt cu multe macrocomenzi - ce să activezi / ce să dezactivezi. După această opțiune, adăugăm alte steaguri în configurație dacă dorim să dezactivăm altceva. Iată un exemplu al nostru configurație.

Pentru ca Qt să funcționeze, trebuie să adăugați un strat de compatibilitate cu sistemul de operare. O modalitate este de a implementa QPA (Qt Platform Abstraction). Am luat ca bază pluginul gata fb_base inclus în Qt, pe baza căruia funcționează QPA pentru Linux. Rezultatul este un mic plugin numit emboxfb, care furnizează Qt framebuffer-ul Embox și apoi desenează acolo fără niciun ajutor extern.

Așa arată crearea unui plugin

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();
}

Și așa va arăta redesenarea

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;
}

Ca urmare, cu optimizarea compilatorului pentru dimensiunea memoriei -Os activată, imaginea bibliotecii s-a dovedit a fi de 3.5 MB, care desigur nu se încadrează în memoria principală a STM32F746. După cum am scris deja în celălalt articol despre OpenCV, această placă are:

  • 1 MB ROM
  • 320 KB RAM
  • 8 MB SDRAM
  • 16 MB QSPI

Deoarece suportul pentru executarea codului de la QSPI a fost deja adăugat la OpenCV, am decis să începem prin a încărca întreaga imagine Embox c Qt în QSPI. Și hai, totul a început aproape imediat de la QSPI! Dar, ca și în cazul OpenCV, s-a dovedit că funcționează prea lent.

Portarea Qt la STM32

Prin urmare, am decis să o facem astfel - mai întâi copiem imaginea în QSPI, apoi o încărcăm în SDRAM și o executăm de acolo. Din SDRAM a devenit puțin mai rapid, dar încă departe de QEMU.

Portarea Qt la STM32

În continuare, a existat o idee de a include o virgulă mobilă - la urma urmei, Qt face niște calcule ale coordonatelor pătratelor din animație. Am încercat, dar aici nu am obținut nicio accelerație vizibilă, deși în articol Dezvoltatorii Qt au susținut că FPU oferă o creștere semnificativă a vitezei pentru „tragerea animației” pe ecranul tactil. Pot exista mai puține calcule cu virgulă mobilă în blocurile de mișcare, iar acest lucru depinde de exemplul specific.

Cea mai eficientă idee a fost să muți framebuffer-ul din SDRAM în memoria internă. Pentru a face acest lucru, am făcut dimensiunile ecranului nu 480x272, ci 272x272. De asemenea, am redus adâncimea culorii de la A8R8G8B8 la R5G6B5, reducând astfel dimensiunea unui pixel de la 4 la 2 octeți. Dimensiunea framebuffer-ului rezultată este 272 * 272 * 2 = 147968 octeți. Acest lucru a dat o accelerare semnificativă, poate cel mai vizibil, animația a devenit aproape lină.

Cea mai recentă optimizare a fost să ruleze codul Embbox din RAM și codul Qt din SDRAM. Pentru a face acest lucru, mai întâi, ca de obicei, legăm static Embox împreună cu Qt, dar plasăm segmentele de text, rodata, date și bss ale bibliotecii în QSPI pentru a le copia apoi în 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))

Prin executarea codului Embbox din ROM, am primit și o accelerare notabilă. Ca rezultat, animația s-a dovedit destul de lină:


La final, în timp ce pregăteam articolul și încercam diferite configurații Embox, s-a dovedit că Qt moveblocks funcționează excelent din QSPI cu un framebuffer în SDRAM, iar blocajul era exact de dimensiunea framebuffer-ului! Aparent, pentru a depăși „slideshow-ul” inițial, o accelerare de 2 ori a fost suficientă datorită unei reduceri banale a dimensiunii framebuffer-ului. Dar nu a fost posibil să se obțină un astfel de rezultat transferând doar codul Embox în diferite memorii rapide (accelerarea nu a fost de 2, ci de aproximativ 1.5 ori).

Cum să-l încerci singur

Dacă aveți un STM32F7-Discovery, puteți rula singur Qt sub Embbox. Puteți citi cum se face acest lucru pe site-ul nostru вики.

Concluzie

Drept urmare, am reușit să lansăm Qt! Complexitatea sarcinii, în opinia noastră, este oarecum exagerată. Desigur, trebuie să țineți cont de specificul microcontrolerelor și să înțelegeți în general arhitectura sistemelor informatice. Rezultatele optimizării indică faptul binecunoscut că blocajul dintr-un sistem de calcul nu este procesorul, ci memoria.

Anul acesta vom participa la festival TechTrain. Acolo vă vom spune mai detaliat și vă vom arăta Qt, OpenCV pe microcontrolere și celelalte realizări ale noastre.

Sursa: www.habr.com

Adauga un comentariu