Przenoszenie Qt na STM32

Przenoszenie Qt na STM32Dzień dobry Jesteśmy w projekcie Skrzynka odbiorcza uruchomiłem Qt na STM32F7-Discovery i chciałbym o tym porozmawiać. Wcześniej opowiadaliśmy już, jak udało nam się wystartować OpenCV.

Qt to wieloplatformowy framework, który zawiera nie tylko komponenty graficzne, ale także takie rzeczy jak QtNetwork, zestaw klas do pracy z bazami danych, Qt for Automation (w tym do implementacji IoT) i wiele więcej. Zespół Qt aktywnie korzystał z Qt w systemach wbudowanych, więc biblioteki są dość konfigurowalne. Jednak do niedawna niewiele osób myślało o przeniesieniu Qt na mikrokontrolery, prawdopodobnie dlatego, że takie zadanie wydaje się trudne - Qt jest duże, MCU są małe.

Z drugiej strony, w tej chwili istnieją mikrokontrolery przeznaczone do pracy z multimediami i lepsze od pierwszych Pentiumów. Około rok temu pojawił się blog Qt pisać. Twórcy wykonali port Qt dla systemu operacyjnego RTEMS i uruchomili przykłady z widżetami na kilku płytach obsługujących stm32f7. To nas zainteresowało. Było zauważalne, o czym piszą sami programiści, że Qt na STM32F7-Discovery jest powolny. Zastanawialiśmy się, czy moglibyśmy uruchomić Qt pod Emboxem, a nie tylko narysować widżet, ale uruchomić animację.

Qt 4.8 był już dawno przeniesiony do Emboxa, więc postanowiliśmy go wypróbować. Wybraliśmy aplikację moveblocks - przykład sprężystej animacji.

Qt moveblocks na QEMUPrzenoszenie Qt na STM32

Na początek konfigurujemy Qt, jeśli to możliwe, z minimalnym zestawem komponentów wymaganych do obsługi animacji. W tym celu dostępna jest opcja „-qconfig minimal,small,medium...”. Łączy plik konfiguracyjny z Qt z wieloma makrami - co włączyć, co wyłączyć. Po tej opcji dodajemy do konfiguracji kolejne flagi jeśli chcemy wyłączyć coś innego. Oto przykład naszego konfiguracja.

Aby Qt działał, musisz dodać warstwę zgodności systemu operacyjnego. Jednym ze sposobów jest wdrożenie QPA (abstrakcyjna platforma Qt). Jako podstawę wzięliśmy gotową wtyczkę fb_base zawartą w Qt, na bazie której działa QPA dla Linuksa. Rezultatem jest mała wtyczka o nazwie emboxfb, która udostępnia Qt bufor ramki Emboxa, a następnie rysuje go bez żadnej pomocy z zewnątrz.

Tak wygląda tworzenie wtyczki

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

A tak będzie wyglądać przerysowanie

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

W rezultacie, przy włączonej optymalizacji kompilatora pod kątem rozmiaru pamięci -Os, obraz biblioteki okazał się mieć 3.5 MB, co oczywiście nie mieści się w pamięci głównej STM32F746. Jak już pisaliśmy w naszym innym artykule o OpenCV, tablica ta posiada:

  • 1 MB pamięci ROM
  • 320kB RAM
  • 8MB SDRAMU
  • 16MB QSPI

Ponieważ do OpenCV dodano już obsługę wykonywania kodu z QSPI, postanowiliśmy zacząć od załadowania całego obrazu Embox c Qt do QSPI. I hurra, wszystko zaczęło się niemal natychmiast od QSPI! Jednak podobnie jak w przypadku OpenCV okazało się, że działa on zbyt wolno.

Przenoszenie Qt na STM32

Dlatego postanowiliśmy zrobić to w ten sposób - najpierw kopiujemy obraz do QSPI, następnie ładujemy go do SDRAM-u i stamtąd uruchamiamy. Z SDRAM-u stało się trochę szybciej, ale wciąż daleko od QEMU.

Przenoszenie Qt na STM32

Następnie pojawił się pomysł dołączenia zmiennoprzecinkowego - w końcu Qt wykonuje pewne obliczenia współrzędnych kwadratów w animacji. Próbowaliśmy, ale tutaj nie uzyskaliśmy widocznego przyspieszenia, chociaż w Artykuł Twórcy Qt twierdzili, że FPU zapewnia znaczny wzrost szybkości „animacji przeciągania” na ekranie dotykowym. W blokach ruchu może być znacznie mniej obliczeń zmiennoprzecinkowych, a to zależy od konkretnego przykładu.

Najbardziej efektywnym pomysłem było przeniesienie bufora ramki z SDRAM-u do pamięci wewnętrznej. Aby to zrobić, ustawiliśmy wymiary ekranu nie na 480x272, ale 272x272. Obniżyliśmy także głębię kolorów z A8R8G8B8 do R5G6B5, zmniejszając w ten sposób rozmiar jednego piksela z 4 do 2 bajtów. Wynikowy rozmiar bufora ramki wynosi 272 * 272 * 2 = 147968 bajtów. Dało to znaczne przyspieszenie, a co być może najbardziej zauważalne, animacja stała się niemal płynna.

Najnowsza optymalizacja polegała na uruchomieniu kodu Embox z RAM i kodu Qt z SDRAM-u. Aby to zrobić, najpierw, jak zwykle, statycznie łączymy Embox z Qt, ale segmenty tekstu, rodata, data i bss biblioteki umieszczamy w QSPI, aby następnie skopiować je do SDRAM-u.

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))

Wykonując kod Embox z ROM-u, również otrzymaliśmy zauważalne przyspieszenie. W rezultacie animacja okazała się całkiem płynna:


Na sam koniec, przygotowując artykuł i próbując różnych konfiguracji Emboxa, okazało się, że moveblocks Qt świetnie sprawdzają się z QSPI z buforem ramki w SDRAM-ie, a wąskim gardłem była właśnie wielkość bufora ramki! Najwyraźniej, aby przezwyciężyć początkowy „pokaz slajdów”, wystarczyło 2-krotne przyspieszenie ze względu na banalne zmniejszenie rozmiaru bufora ramki. Ale nie udało się osiągnąć takiego wyniku, przenosząc jedynie kod Emboxa do różnych szybkich pamięci (przyspieszenie nie było 2, ale około 1.5 razy).

Jak spróbować samemu

Jeśli masz STM32F7-Discovery, możesz samodzielnie uruchomić Qt w Emboxie. Jak to się robi, możesz przeczytać na naszym вики.

wniosek

W rezultacie udało nam się uruchomić Qt! Naszym zdaniem złożoność zadania jest nieco przesadzona. Naturalnie trzeba wziąć pod uwagę specyfikę mikrokontrolerów i ogólnie zrozumieć architekturę systemów komputerowych. Wyniki optymalizacji wskazują na dobrze znany fakt, że wąskim gardłem w systemie komputerowym nie jest procesor, ale pamięć.

W tym roku będziemy brać udział w festiwalu TechTrain. Tam opowiemy Ci bardziej szczegółowo i pokażemy Qt, OpenCV na mikrokontrolerach i inne nasze osiągnięcia.

Źródło: www.habr.com

Dodaj komentarz