Портирование Qt на STM32

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!

Добрый день! Мы в проекте 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 по возможности с минимальным набором компонент, требуемым для поддержки анимации. Для этого существует опция “-qconfig minimal,small,medium ...”. Она подключает конфигурационный файл из состава Qt c множеством макросов — что включить / что отключить. После этой опции добавляем в конфигурацию другие флаги, если хотим еще что-то отключить дополнительно. Вот пример нашей конфигурации.

Для того, чтобы 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::doRedraw\n");

    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 делает некоторые вычисления координат квадратов в анимации. Попробовали, но здесь не получили видимого ускорения, хотя в статье разработчики Qt утверждали, что FPU дает значительный прирост в скорости для “dragging animation” на touchscreen’e. Возможно, в moveblocks существенно меньше вычислений с плавающей точкой, и это зависит от конкретного примера.

Самым же эффективным оказалась идея перенести фреймбуфер из SDRAM во внутреннюю память. Для этого мы сделали размеры экрана не 480x272, а 272x272. Еще понизили глубину цвета с 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, а узким местом был именно размер фреймбуфера! По-видимому, чтобы преодолеть начальное “слайдшоу” хватало ускорения в 2 раза за счет банального уменьшения размера фреймбуфера. А добиться такого результата переносом только лишь кода Embox в различные быстрые памяти не удалось (ускорение получалось не в 2, а примерно в 1.5 раза).

Как попробовать самому


Если у Вас имеется STM32F7-Discovery, Вы можете запустить Qt под Embox сами. Прочитать как это делается можно на нашем вики.

Заключение


В итоге нам удалось запустить Qt! Сложность задачи, на наш взгляд, несколько преувеличена. Естественно нужно учитывать специфику микроконтроллеров и вообще понимать архитектуру вычислительных систем. Результаты оптимизации указывают на известный факт, что самое узкое место в вычислительной системе, это не процессор, а память.

В этом году мы будем участвовать на фестивале TechTrain. Там мы подробней расскажем и покажем Qt, OpenCV на микроконтроллерах и прочие наши достижения.
Источник: https://habr.com/ru/company/embox/blog/459730/


Интересные статьи

Интересные статьи

Как известно CMSIS предоставляет доступ к регистрам микроконтроллера. Это конечно хорошо, но не очень удобно. В данной статье речь пойдет о настройке GPIO. Порты ввода-вы...
SWAP (своп) — это механизм виртуальной памяти, при котором часть данных из оперативной памяти (ОЗУ) перемещается на хранение на HDD (жёсткий диск), SSD (твёрдотельный накоп...
Помните как некто cnlohr запустил передачу ТВ сигнала на ESP8266? Недавно мне попалось к просмотру это видео, стало интересно как это возможно и выяснил что автор видео разогнал ча...
Нельзя доверять коду, который вы не написали полностью сами. — Кен ТомпсонПожалуй, моя самая любимая цитата. Именно она и стала причиной по которой я решил нырнуть в самую глубь кроличьей норы. С...
Однажды, в понедельник, мне пришла в голову мысль — "а покопаюсь ка я в новом ядре" (новым относительно, но об этом позже). Мысль не появилась на ровном месте, а предпосылками для нее стали: ...