Добрый день! Мы в проекте 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 по возможности с минимальным набором компонент, требуемым для поддержки анимации. Для этого существует опция “-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::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;
}
В итоге с включенной оптимизацией компилятора по размеру памяти -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 на микроконтроллерах и прочие наши достижения.
Автор: alexkalmuk