Portando Qt para STM32

Portando Qt para STM32Boa tarde Estamos no projeto Caixa de entrada lançou o Qt no STM32F7-Discovery e gostaria de falar sobre isso. Anteriormente, já contamos como conseguimos lançar OpenCV.

Qt é uma estrutura multiplataforma que inclui não apenas componentes gráficos, mas também coisas como QtNetwork, um conjunto de classes para trabalhar com bancos de dados, Qt for Automation (inclusive para implementação de IoT) e muito mais. A equipe do Qt tem sido proativa no uso do Qt em sistemas embarcados, portanto as bibliotecas são bastante configuráveis. No entanto, até recentemente, poucas pessoas pensavam em portar o Qt para microcontroladores, provavelmente porque tal tarefa parece difícil - o Qt é grande, os MCUs são pequenos.

Por outro lado, atualmente existem microcontroladores projetados para trabalhar com multimídia e superiores aos primeiros Pentiums. Cerca de um ano atrás, o blog Qt apareceu postar. Os desenvolvedores fizeram um port do Qt para o sistema operacional RTEMS e lançaram exemplos com widgets em diversas placas rodando stm32f7. Isso nos interessou. Foi perceptível, e os próprios desenvolvedores escrevem sobre isso, que o Qt é lento no STM32F7-Discovery. Estávamos nos perguntando se poderíamos executar o Qt no Embox, e não apenas desenhar um widget, mas executar uma animação.

O Qt 4.8 foi portado para o Embox há muito tempo, então decidimos experimentá-lo. Escolhemos o aplicativo moveblocks - um exemplo de animação elástica.

Moveblocks Qt no QEMUPortando Qt para STM32

Para começar, configuramos o Qt, se possível, com o conjunto mínimo de componentes necessários para suportar animação. Para isso existe a opção “-qconfig mínimo, pequeno, médio...”. Ele conecta um arquivo de configuração do Qt com muitas macros - o que habilitar/o que desabilitar. Após esta opção, adicionamos outros flags à configuração caso queiramos desabilitar alguma outra coisa. Aqui está um exemplo do nosso configuração.

Para que o Qt funcione, você precisa adicionar uma camada de compatibilidade do sistema operacional. Uma maneira é implementar QPA (Qt Platform Abstraction). Tomamos como base o plugin fb_base pronto incluído no Qt, com base no qual funciona o QPA para Linux. O resultado é um pequeno plugin chamado emboxfb, que fornece ao Qt o framebuffer do Embox e então desenha lá sem qualquer ajuda externa.

É assim que se parece a criação de um 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();
}

E é assim que ficará o redesenho

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

Como resultado, com a otimização do compilador para tamanho de memória -Os habilitada, a imagem da biblioteca acabou sendo de 3.5 MB, o que obviamente não cabe na memória principal do STM32F746. Como já escrevemos em nosso outro artigo sobre OpenCV, esta placa possui:

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

Como o suporte para execução de código do QSPI já foi adicionado ao OpenCV, decidimos começar carregando toda a imagem Embox c Qt no QSPI. E viva, tudo começou quase imediatamente no QSPI! Mas, como no caso do OpenCV, descobriu-se que ele funciona muito devagar.

Portando Qt para STM32

Portanto, decidimos fazer desta forma - primeiro copiamos a imagem para QSPI, depois carregamos no SDRAM e executamos a partir daí. Do SDRAM ficou um pouco mais rápido, mas ainda longe do QEMU.

Portando Qt para STM32

A seguir, surgiu a ideia de incluir um ponto flutuante - afinal, o Qt faz alguns cálculos das coordenadas dos quadrados na animação. Tentamos, mas aqui não obtivemos nenhuma aceleração visível, embora em статье Os desenvolvedores do Qt alegaram que o FPU oferece um aumento significativo na velocidade para “arrastar animação” na tela sensível ao toque. Pode haver significativamente menos cálculos de ponto flutuante em moveblocks, e isso depende do exemplo específico.

A ideia mais eficaz foi mover o framebuffer da SDRAM para a memória interna. Para fazer isso, fizemos com que as dimensões da tela não fossem 480x272, mas 272x272. Também reduzimos a profundidade de cor de A8R8G8B8 para R5G6B5, reduzindo assim o tamanho de um pixel de 4 para 2 bytes. O tamanho do framebuffer resultante é 272 * 272 * 2 = 147968 bytes. Isto deu uma aceleração significativa, talvez o mais perceptível, a animação tornou-se quase suave.

A otimização mais recente foi executar o código Embox da RAM e o código Qt da SDRAM. Para fazer isso, primeiro, como de costume, vinculamos estaticamente o Embox ao Qt, mas colocamos os segmentos de texto, rodata, dados e bss da biblioteca no QSPI para depois copiá-los para 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))

Ao executar o código Embox da ROM, também obtivemos uma aceleração notável. Como resultado, a animação ficou bastante suave:


No final, enquanto preparava o artigo e tentava diferentes configurações do Embox, descobriu-se que os moveblocks do Qt funcionam muito bem no QSPI com um framebuffer em SDRAM, e o gargalo era exatamente o tamanho do framebuffer! Aparentemente, para superar o “slideshow” inicial, bastou uma aceleração de 2 vezes devido a uma redução banal no tamanho do framebuffer. Mas não foi possível alcançar tal resultado transferindo apenas o código Embox para várias memórias rápidas (a aceleração não foi 2, mas cerca de 1.5 vezes).

Como experimentar você mesmo

Se você tiver um STM32F7-Discovery, poderá executar o Qt no Embox você mesmo. Você pode ler como isso é feito em nosso вики.

Conclusão

Como resultado, conseguimos lançar o Qt! A complexidade da tarefa, em nossa opinião, é um tanto exagerada. Naturalmente, você precisa levar em consideração as especificidades dos microcontroladores e compreender de maneira geral a arquitetura dos sistemas de computador. Os resultados da otimização apontam para o fato bem conhecido de que o gargalo em um sistema de computação não é o processador, mas sim a memória.

Este ano participaremos do festival TechTrain. Lá contaremos com mais detalhes e mostraremos Qt, OpenCV em microcontroladores e nossas outras conquistas.

Fonte: habr.com

Adicionar um comentário