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

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

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

Заключение

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

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


ссылка на оригинал статьи https://habr.com/ru/company/embox/blog/459730/

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *