Porterar Qt till STM32

Porterar Qt till STM32God eftermiddag Vi är med i projektet embox lanserade Qt på STM32F7-Discovery och skulle vilja prata om det. Tidigare berättade vi redan hur vi lyckades lansera OpenCV.

Qt är ett plattformsoberoende ramverk som inte bara innehåller grafiska komponenter, utan även sådant som QtNetwork, en uppsättning klasser för att arbeta med databaser, Qt för automation (inklusive för IoT-implementering) och mycket mer. Qt-teamet har varit proaktivt när det gäller att använda Qt i inbyggda system, så biblioteken är ganska konfigurerbara. Men tills nyligen var det få som tänkte på att porta Qt till mikrokontroller, förmodligen för att en sådan uppgift verkar svår - Qt är stort, MCU:er är små.

Å andra sidan finns det för närvarande mikrokontroller designade för att fungera med multimedia och överlägsna de första Pentiums. För ungefär ett år sedan dök Qt-bloggen upp post. Utvecklarna gjorde en port av Qt för RTEMS OS, och lanserade exempel med widgets på flera kort som kör stm32f7. Detta intresserade oss. Det märktes, och utvecklarna själva skriver om det, att Qt är långsam på STM32F7-Discovery. Vi undrade om vi kunde köra Qt under Embox, och inte bara rita en widget, utan köra en animation.

Qt 4.8 har porterats till Embox under en lång tid, så vi bestämde oss för att testa den på den. Vi valde applikationen moveblocks - ett exempel på fjädrande animering.

Qt moveblocks på QEMUPorterar Qt till STM32

Till att börja med konfigurerar vi Qt, om möjligt, med den minsta uppsättning komponenter som krävs för att stödja animering. För detta finns ett alternativ "-qconfig minimal,small, medium...". Den kopplar ihop en konfigurationsfil från Qt med många makron - vad ska man aktivera / vad man ska inaktivera. Efter det här alternativet lägger vi till andra flaggor i konfigurationen om vi vill inaktivera något annat. Här är ett exempel på vår konfiguration.

För att Qt ska fungera måste du lägga till ett OS-kompatibilitetslager. Ett sätt är att implementera QPA (Qt Platform Abstraction). Vi tog som utgångspunkt den färdiga fb_base-plugin som ingår i Qt, utifrån vilken QPA för Linux fungerar. Resultatet är en liten plugin som heter emboxfb, som förser Qt med Embox framebuffer, och sedan drar den dit utan någon hjälp utifrån.

Så här ser det ut att skapa ett 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();
}

Och så här kommer omteckningen att se ut

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

Som ett resultat, med kompilatoroptimeringen för minnesstorlek -Os aktiverad, visade sig biblioteksbilden vara 3.5 MB, vilket naturligtvis inte passar in i huvudminnet i STM32F746. Som vi redan skrev i vår andra artikel om OpenCV har den här styrelsen:

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

Eftersom stöd för exekvering av kod från QSPI redan har lagts till i OpenCV, bestämde vi oss för att börja med att ladda hela Embox c Qt-bilden i QSPI. Och hurra, allt började nästan direkt från QSPI! Men som i fallet med OpenCV visade det sig att det fungerar för långsamt.

Porterar Qt till STM32

Därför bestämde vi oss för att göra det på det här sättet - först kopierar vi bilden till QSPI, laddar sedan in den i SDRAM och kör därifrån. Från SDRAM blev det lite snabbare, men ändå långt ifrån QEMU.

Porterar Qt till STM32

Därefter var det en idé att inkludera en flyttal - Qt gör trots allt några beräkningar av koordinaterna för kvadrater i animering. Vi försökte, men här fick vi ingen synlig acceleration, fast in artikeln Qt-utvecklare hävdade att FPU ger en betydande ökning av hastigheten för att "dra animation" på pekskärmen. Det kan finnas betydligt färre flyttalsberäkningar i moveblocks, och detta beror på det specifika exemplet.

Den mest effektiva idén var att flytta framebuffern från SDRAM till internminnet. För att göra detta gjorde vi skärmmåtten inte 480x272, utan 272x272. Vi sänkte också färgdjupet från A8R8G8B8 till R5G6B5, vilket minskade storleken på en pixel från 4 till 2 byte. Den resulterande rambuffertstorleken är 272 * 272 * 2 = 147968 byte. Detta gav en betydande acceleration, kanske mest märkbart, animeringen blev nästan jämn.

Den senaste optimeringen var att köra Embox-kod från RAM och Qt-kod från SDRAM. För att göra detta länkar vi först, som vanligt, embox statiskt ihop med Qt, men vi placerar text-, rodata-, data- och bss-segmenten av biblioteket i QSPI för att sedan kopiera det till 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))

Genom att köra Embox-koden från ROM fick vi också en märkbar acceleration. Som ett resultat blev animationen ganska smidig:


I slutet, när man förberedde artikeln och provade olika Embox-konfigurationer, visade det sig att Qt moveblocks fungerar utmärkt från QSPI med en framebuffer i SDRAM, och flaskhalsen var precis i storleken på framebuffern! Tydligen, för att övervinna det initiala "bildspelet", räckte en 2-faldig acceleration på grund av en banal minskning av storleken på rambufferten. Men det var inte möjligt att uppnå ett sådant resultat genom att bara överföra Embox-koden till olika snabba minnen (hastigheten var inte 2, utan cirka 1.5 gånger).

Hur man provar själv

Om du har en STM32F7-Discovery kan du själv köra Qt under Embox. Du kan läsa hur detta går till på vår wiki.

Slutsats

Som ett resultat lyckades vi lansera Qt! Uppgiftens komplexitet är enligt vår mening något överdriven. Naturligtvis måste du ta hänsyn till detaljerna hos mikrokontroller och allmänt förstå arkitekturen för datorsystem. Optimeringsresultaten pekar på det välkända faktumet att flaskhalsen i ett datorsystem inte är processorn utan minnet.

I år kommer vi att delta i festivalen TechTrain. Där kommer vi att berätta mer i detalj och visa Qt, OpenCV på mikrokontroller och våra andra prestationer.

Källa: will.com

Lägg en kommentar