Pular para o conteúdo principal

Oficina de animação: guia de otimização do XIAO ESP32-S3 e LVGL

Bem-vindo à Oficina de Animação! Este tutorial vai guiá-lo pelo processo de pegar animações SVG básicas e otimizá‑las para o ESP32-S3. As otimizações irão conduzi‑lo por cinco fases que mostram melhorias desde uma taxa de quadros travada em 7–9 FPS até uma fluidez de 30 FPS.

Nesta oficina, você implementará um aplicativo que renderiza e anima três assets vetoriais: um Beija-flor, um Guaxinim e uma Baleia. Você aprenderá a lidar com renderização SVG complexa em hardware com recursos limitados, passando por fases de otimização progressiva.

Hummingbird

Beija-flor

Raccoon

Guaxinim

Whale

Baleia

O código de exemplo usa um Seeed Studio XIAO ESP32-S3 Plus e um XIAO Round Display.

🌐 O ecossistema

Este projeto aproveita uma poderosa pilha open source projetada para gráficos embarcados de alto desempenho:

  • ESP32-S3: Um MCU dual-core com instruções vetoriais e 8MB de Octal PSRAM.
  • ESP-IDF: O framework oficial de desenvolvimento para SoCs da Espressif.
  • LVGL: A biblioteca de gráficos embarcados open source mais popular.
  • lvgl_cpp: Um wrapper moderno em C++20 para LVGL que fornece segurança de tipos e abstrações idiomáticas para objetos, animações e displays.

Primeiros passos

Pré-requisitos

Antes de mergulhar no código, certifique‑se de ter um ecossistema de desenvolvimento para trabalhar com ESP32. Este tutorial assume que você tenha as seguintes ferramentas instaladas:

No entanto, não há nada neste projeto que o impeça de usar outras ferramentas como o PlatformIO ou a interface de linha de comando do ESP-IDF.

Etapa 0: A verificação "Hello World" (crucial)

Se você é novo no ecossistema ESP32 com ESP-IDF, não pule esta etapa.

  1. Abra o VS Code e pressione F1.
  2. Digite ESP-IDF: Show Examples Projects.
  3. Selecione get-started -> blink (ou hello_world).
  4. Compile, grave (Flash) e monitore o projeto no seu dispositivo.

Por quê? Isso verifica se sua toolchain, drivers USB e conexão de hardware estão totalmente funcionais antes de introduzirmos a complexidade de uma biblioteca gráfica em C++.

Etapa 1a: Início rápido (clonar a oficina)

Se você prefere não começar do zero, pode clonar o repositório completo da oficina, que já vem pré-configurado com todos os componentes e configurações.

git clone https://github.com/pedapudi/lvgl_workshop.git
cd lvgl_workshop
git submodule update --init --recursive

Se você escolher este caminho, pode pular para a Etapa 3 (Configuração) para verificar suas definições ou ir direto para a compilação.

Etapa 1b: Criar um novo projeto (do zero)

Se você quiser construir o projeto por conta própria:

  1. Abra ESP-IDF: Show Examples Projects novamente.
  2. Escolha get-started -> sample_project.
  3. Clique em "Create project using example blink".

Etapa 2: Instalar lvgl_cpp

Instalaremos a biblioteca como um componente local. No terminal da raiz do seu projeto:

mkdir -p components
cd components
git submodule add https://github.com/pedapudi/lvgl_cpp.git lvgl_cpp
git submodule update --init --recursive

Observação: lvgl_cpp inclui um arquivo idf_component.yml que irá automaticamente buscar a versão correta de lvgl/lvgl (v9.x) no IDF Component Registry durante a primeira compilação. Você não precisa instalar o LVGL manualmente.

Etapa 3: Configuração

Abra o menu de configuração do projeto para habilitar C++20 e definições ideais:

  1. Pressione F1 e execute ESP-IDF: SDK Configuration Editor (Menuconfig).
  2. Navegue até Compiler options -> C++ Language Standard e selecione C++20 (ou GNU++20).
  3. (Opcional, mas recomendado) Defina Compiler optimization level como Optimize for performance (-O2).

🛠️ Como seguir este guia

Esta oficina usa um "limitador de software" para simular limitações de hardware sem forçá‑lo a recompilar o bootloader constantemente. Você pode alternar os níveis de implementação de duas maneiras:

  1. O jeito da oficina: Use idf.py menuconfig -> Animation Workshop para alternar entre as fases 1–5.
  2. O jeito manual: Modifique #define WORKSHOP_PHASE em main/workshop_config.h.

📊 Visão geral das fases

A oficina progride por quatro estágios de otimização, passando de uma implementação funcional ingênua para uma utilização de PSRAM em nível avançado.

FaseTítuloOtimizações principaisEstratégia de BufferModo de Renderização
Fase 1Linha de baseCPU de 160MHz / SPI de 20MHz1x Frame Completo (Interno)Atualização Completa
Fase 2FundamentosCPU de 240MHz / SPI de 80MHz1x Frame Completo (Interno)Atualização Completa
Fase 3ParalelismoDouble Buffering2x Faixa Parcial (Interno)Atualização Parcial
Fase 4AvançadoOctal PSRAM / SIMD2x Frame Completo (PSRAM)Atualização Parcial
Fase 5NativoDriver Nativo / SWAR SIMD2x Parcial Grande (Interno)Atualização Parcial

Fase 1: A linha de base (implementação ingênua)

Objetivo: Exibir um SVG com configuração mínima.

Nesta fase, focamos na correção funcional. Usamos o mecanismo ThorVG do LVGL para decodificar e renderizar caminhos SVG. No entanto, como ainda não otimizamos o sistema, a CPU é lenta (160MHz) e o modo de renderização está definido como Full Refresh (redesenhando cada pixel a cada frame), causando uma sobrecarga significativa.

⚙️ Configuração do ESP-IDF

Para atingir esta linha de base em um projeto de produção, você configuraria:

  • Frequência da CPU: CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ=160 em sdkconfig.
  • Pilha da tarefa LVGL: Definida como um parâmetro na sua chamada xTaskCreate (tipicamente 8192).
  • Barramento do display: Defina .pclk_hz = 20 * 1000 * 1000 (20MHz) na sua struct esp_lcd_panel_io_spi_config_t.
  • Otimização: CONFIG_COMPILER_OPTIMIZATION_DEFAULT (-Og).

💻 Implementação

Configurar o hardware e a exibição de SVG na Fase 1 exige orquestrar o driver do display e a camada de portabilidade do LVGL.

Inicialização de hardware e porta (app_main):

// Initialize the GC9A01 SPI display
Gc9a01 display_hw(display_cfg);
display_hw.init();

// Initialize the LVGL porting layer with SRAM-only buffers
LvglPort::Config lvgl_config;
lvgl_config.malloc_caps = MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL;
lvgl_config.double_buffered = false;

LvglPort lvgl_port(lvgl_config);
lvgl_port.init(display_hw.get_panel_handle(), display_hw.get_io_handle());

Lógica de exibição do SVG:

// 1. skip SVG header metadata to find the XML start tag
const char* raw_svg_ptr = hummingbird_svg;
while (*raw_svg_ptr && *raw_svg_ptr != '<') raw_svg_ptr++;

// 2. create an image descriptor for ThorVG
static lvgl::ImageDescriptor bird_dsc(75, 75, LV_COLOR_FORMAT_RAW,
(const uint8_t*)raw_svg_ptr,
strlen(raw_svg_ptr) + 1);

// 3. display the SVG on an image object
auto hummingbird = std::make_unique<lvgl::Image>(parent);
hummingbird->set_src(bird_dsc).center();

Resultado: ~9 FPS. O beija-flor estático é renderizado com sucesso e a animação do guaxinim é executada sem travar (embora continue truncada). No entanto, a pilha padrão de 8KB está perigosamente perto de estourar ao fazer o scaling de caminhos complexos.


Fase 2: Fundamentos de hardware

Objetivo: Maximizar as frequências brutas do ESP32-S3.

A renderização de SVG é um "Problema de Matemática". Ao aumentar a frequência da CPU, damos ao mecanismo vetorial mais ciclos para calcular curvas de Bézier.

⚡ A estratégia

  1. Impulso na CPU: Aumentar a frequência de 160MHz para 240MHz.
  2. Overclock do SPI: Aumentar a velocidade da via do display de 20MHz para 80MHz.
  3. Poder do compilador: Habilitar otimizações de desempenho -O3 para acelerar o mecanismo de matemática vetorial.

⚙️ Configuração do ESP-IDF

  • Velocidade da CPU: CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ=240 em sdkconfig.
  • Barramento do display: .pclk_hz = 80 * 1000 * 1000 (80MHz) na sua configuração SPI.

Observação: 80MHz é o limite absoluto para o barramento SPI do S3. Valores maiores não são suportados pelo hardware, e velocidades menores causam "rasgos" visíveis, já que o barramento não consegue acompanhar as atualizações de frame.

  • Nível de otimização: Defina CONFIG_COMPILER_OPTIMIZATION_PERF=y na sua configuração para habilitar -O3.
  • Correção de bytes: No seu flush_cb, implemente um loop para trocar os bytes de Little-Endian (CPU) para Big-Endian (LCD):
void LvglPort::flush_cb(lv_display_t* disp, const lv_area_t* area, uint8_t* px_map) {
auto* port = (LvglPort*)lv_display_get_user_data(disp);
uint16_t* buf = (uint16_t*)px_map;
uint32_t len = (area->x2 - area->x1 + 1) * (area->y2 - area->y1 + 1);

for (uint32_t i = 0; i < len; i++) {
uint16_t color = buf[i];
buf[i] = (color << 8) | (color >> 8);
}

esp_lcd_panel_draw_bitmap(port->panel_handle_, area->x1, area->y1,
area->x2 + 1, area->y2 + 1, buf);
}

Resultado: ~15 FPS. O movimento é visivelmente mais suave em comparação com a Fase 1.


Fase 3: Lógica paralela e double buffering

Objetivo: Eliminar o screen tearing e desacoplar a CPU do display.

⚡ A estratégia

  • Double buffering: Alocamos dois buffers separados. Enquanto o hardware DMA (Direct Memory Access) está enviando o Buffer A para a tela, a CPU pode imediatamente começar a desenhar no Buffer B.
  • Aumento de stack: Aumentamos a stack da task do LVGL para 64KB. Gráficos vetoriais usam recursão; uma stack de 8KB vai estourar e travar ao escalar recursos complexos.

⚙️ Configuração ESP-IDF

  • DMA habilitado: Garanta que MALLOC_CAP_DMA seja usado durante a alocação do buffer.
  • Buffers LVGL: Ao chamar lv_display_set_buffers, forneça dois ponteiros em vez de um.
  • Modo de buffer: Defina LV_DISPLAY_RENDER_MODE_PARTIAL.

💻 Implementação

Mover para a Fase 3 exige habilitar o double buffering e aumentar a stack da task para evitar stack overflows durante a escala de SVGs complexos.

Alocação de double buffer:

// Allocate two strike buffers in internal memory
lvgl_config.double_buffered = true;
lvgl_config.render_mode = LV_DISPLAY_RENDER_MODE_PARTIAL;

// Use small strip buffers (usually 1/10th or 1/20th of the screen)
// to fit both buffers in fast internal SRAM.
lvgl_config.full_frame = false;

Aumento de stack seguro para recursão:

// Increasing from 8KB (Standard) to 64KB (Vector-Safe)
lvgl_config.task_stack_size = 65536;

Resultado: ~9 FPS (regressão!).

🧠 Análise profunda: Penalidade de tiling

Por que adicionar paralelismo às vezes torna a animação mais lenta?

  1. Fase 2 (Frame Completo): O motor ThorVG calcula o caminho vetorial uma vez por frame para toda a imagem 240x240.
  2. Fase 3 (Faixas Parciais): Como estamos fazendo double buffering em SRAM limitada, só conseguimos acomodar buffers pequenos (por exemplo, 20 linhas). Isso força o ThorVG a executar seu loop de cálculo 12 vezes (uma para cada faixa) para gerar a imagem completa.

Esse overhead de recalcular a geometria vetorial 12 vezes supera em muito o benefício da transferência DMA paralela, resultando em FPS geral menor (~9 FPS vs ~15 FPS). Esse é o clássico trade-off "Computação vs. Largura de Banda". Resolvemos isso na Fase 4.


Fase 4: Otimização avançada (o segredo dos 26 FPS)

Objetivo: Eliminar o "Tiling Overhead" usando Large Octal PSRAM.

⚡ A estratégia

  1. Buffers de frame completo: Movemos os buffers para a octal PSRAM de 8MB e os aumentamos para um Frame completo (240x240 pixels).
  • O ganho: O ThorVG renderiza o Guaxinim em um único passe. Mesmo que a PSRAM seja um pouco mais lenta que a SRAM, evitar a recalculação 12x é uma grande vitória.
  1. Intrinsics Xtensa: Substituímos o loop de troca manual por __builtin_bswap16, uma instrução de hardware que troca bits em um único ciclo.

⚙️ Configuração ESP-IDF

  • Inicialização da PSRAM: CONFIG_SPIRAM=y, CONFIG_SPIRAM_MODE_OCT=y, CONFIG_SPIRAM_SPEED_80M=y.
  • Capacidades de memória: Use MALLOC_CAP_DMA | MALLOC_CAP_SPIRAM para alocar o buffer de 115KB.
  • Poder do compilador: Habilite CONFIG_COMPILER_OPTIMIZATION_PERF=y e CONFIG_COMPILER_OPTIMIZATION_LTO=y.

Resultado: ~25 FPS. Animação SVG suave e de alta fidelidade.

🧠 Análise profunda: Intrinsics explícitos de hardware

A parte mais cara do pipeline de renderização é o Flush Callback. Para cada frame, precisamos trocar os bytes de cada pixel (correção de endianness) e inverter as cores (exigência do painel LCD).

Em vez de um loop de matemática C padrão, a Fase 4 usa:

buf[i] = __builtin_bswap16(buf[i]);
  • __builtin_bswap16: Este é um intrinsic do compilador que informa à CPU para usar uma instrução de hardware dedicada (BE) para trocar bytes em um único ciclo de clock.
  • ⚠️ NÃO é armadilha (~): Você pode ver exemplos usando ~__builtin_bswap16(). Isso é usado para painéis LCD "Active-Low" que exigem inversão bit a bit para exibir as cores corretamente. Se suas cores parecerem um "negativo de foto", remova o operador ~.

Nota: Durante a implementação da Fase 1, usar a inversão ~ em painéis GC9A01 padrão geralmente resulta em cores invertidas. Sempre verifique o nível lógico do seu painel antes de aplicar negação bit a bit no loop de flush.


Fase 5: Arquitetura Nativa (o padrão de 30+ FPS)

Objetivo: Bufferização SRAM "Partial Grande" e bit-swapping SWAR de 32 bits.

Embora a Fase 4 tenha alcançado desempenho à força bruta usando buffers massivos em PSRAM, ela introduziu latência devido aos wait-states da memória externa. A Fase 5 atinge o marco de 30+ FPS ao migrar para uma arquitetura de nível "Mobile": usando buffers de SRAM interna em alta velocidade, mas grandes o suficiente para minimizar o overhead de tiling.

⚡ A estratégia: Arquitetura "Mobile-Grade"

  1. Bufferização "Partial Grande": Alocamos buffers de 1/2 Tela (240x120) na SRAM interna. Isso alcança o melhor dos dois mundos: maior largura de banda que a PSRAM e apenas um multiplicador de tiling 2x (comparado a 12x na Fase 3).
  2. Processamento SWAR de 32 bits: Implementamos SIMD-within-a-Register (SWAR) na lógica de flush do driver. Isso nos permite trocar e inverter dois pixels (32 bits) no mesmo tempo que antes levava para processar um pixel (16 bits) usando intrinsics padrão.
  3. Desfixação de core: Ao remover o pinning de tasks (tskNO_AFFINITY), permitimos que o escalonador do FreeRTOS sature os dois núcleos da S3—um núcleo pode lidar com a rasterização pesada do ThorVG enquanto o outro atende às interrupções de SPI DMA de alta frequência.

🚀 Aceleração: patch SIMD

Além das otimizações em C++, contamos com um acelerador de hardware crítico: o lvgl_s3_simd_patch.

  • Problema: O LVGL padrão usa loops genéricos em C para blending de pixels. No ESP32, isso é lento porque processa um pixel por vez (ineficiente) ou usa otimizações genéricas do compilador que ignoram instruções específicas da plataforma.
  • Solução: Injetamos rotinas em Assembly ESP32-S3 escritas à mão (macros como lv_color_blend_to_rgb565_esp) que aproveitam os recursos SIMD (Single Instruction, Multiple Data) do Xtensa LX7.

Configuração: Isso é controlado via sdkconfig:

CONFIG_LV_USE_DRAW_SW_ASM=255
nota

Global vs. Fase 5: Essa configuração é globalmente ativa para todas as fases do workshop (1-5). Porém, você provavelmente não notará seu impacto até a Fase 5.

Por quê? Na Fase 4 (PSRAM), a CPU fica constantemente parada aguardando a chegada dos dados a partir do chip de RAM externo. A rotina em assembly é rápida, mas não pode rodar se não tiver dados! Na Fase 5 (SRAM Interna), a largura de banda de memória finalmente é rápida o suficiente para alimentar o pipeline SIMD, permitindo que as otimizações em assembly brilhem.

Como funciona:

  1. Injeção de header: O componente lvgl_s3_simd_patch se força no caminho de includes do LVGL.
  2. Overlay de macro: Ele redefine macros padrão (por exemplo, LV_DRAW_SW_COLOR_BLEND_TO_RGB565) para apontar para nossas funções shim personalizadas.
  3. Mágica do linker: Ele usa flags -u symbol para forçar o linker a incluir os objetos em assembly, garantindo que não sejam otimizados fora.

💻 Loop SWAR de 32 bits

O segredo da Fase 5 é a otimização SIMD-Within-A-Register (SWAR). O processamento padrão de pixels lida com um pixel de 16 bits por vez. Ao fazer cast do buffer para uint32_t*, podemos processar dois pixels por ciclo de CPU.

Intuição: Precisamos trocar bytes (RGB565 -> BGR565) para dois pixels empacotados em um inteiro de 32 bits 0xAABBCCDD.

  • Alvo: 0xBBAADDCC
  • Máscara 1 (0xFF00FF00): Isola AA e CC.
  • Máscara 2 (0x00FF00FF): Isola BB e DD.
  • Shift e OR: Move-os para as posições corretas simultaneamente.

Implementação (esp32_spi.cpp):

// 1. Cast 16-bit buffer to 32-bit pointer for 2x throughput
uint32_t* buf32 = reinterpret_cast<uint32_t*>(buf);
size_t len32 = len / 2; // Process 2 pixels at once

// 2. Define the SWAR lambda
auto swap_two_pixels = [](uint32_t v) {
// 0xAABBCCDD -> 0xBBAADDCC
return ((v & 0xFF00FF00) >> 8) |
((v & 0x00FF00FF) << 8);
};

// 3. Unrolled loop (8x) to maximize pipeline usage
// This processes 16 pixels per loop iteration!
size_t i = 0;
for (; i < len32 - 8; i += 8) {
buf32[i + 0] = swap_two_pixels(buf32[i + 0]);
buf32[i + 1] = swap_two_pixels(buf32[i + 1]);
buf32[i + 2] = swap_two_pixels(buf32[i + 2]);
buf32[i + 3] = swap_two_pixels(buf32[i + 3]);
buf32[i + 4] = swap_two_pixels(buf32[i + 4]);
buf32[i + 5] = swap_two_pixels(buf32[i + 5]);
buf32[i + 6] = swap_two_pixels(buf32[i + 6]);
buf32[i + 7] = swap_two_pixels(buf32[i + 7]);
}

// 4. Handle remaining pixels
for (; i < len32; i++) {
buf32[i] = swap_two_pixels(buf32[i]);
}

🧠 Análise profunda: Por que usar um buffer partial grande é mais rápido?

Parece contraintuitivo que Buffers Parciais (Fase 5) sejam mais rápidos que Frame Completo (Fase 4).

  • Fase 4 (PSRAM):
  • Prós: 1x passe de renderização. 0x overhead de tiling.
  • Contras: Escritas em PSRAM são ~3-4x mais lentas que em SRAM. A CPU passa um tempo significativo esperando pelo barramento SPI.
  • Fase 5 (SRAM Interna):
  • Prós: Escritas em SRAM são quase instantâneas (comparativamente).
  • Contras: 2x passes de renderização (metade superior, metade inferior).
  • Veredito: Para o ESP32-S3 @ 240MHz, o custo de um passe extra de renderização SVG é menor que o custo de escrever 115KB de dados para a PSRAM externa. Esta é uma constatação arquitetural crítica: largura de banda de memória muitas vezes importa mais do que ciclos de computação.

Resultado: ~30 FPS. Animação fluida que atinge os limites do barramento SPI do XIAO Round Display.


🎨 O motor de animação

Nossa UI não exibe apenas imagens estáticas; ela reproduz movimento de alta fidelidade a partir de especificações SVG usando lvgl_cpp. Embora o motor de renderização SVG do LVGL, o ThorVG, não suporte tags de animação em SVGs, este workshop ainda assim utiliza lvgl::Animation para animar os SVGs. Isso é uma utilidade útil para animação e, para o propósito deste workshop, gera carga intensiva de CPU para demonstrar o desempenho das fases do workshop.

A ponte SVG-para-LVGL

Animações SVG frequentemente usam curvas "Cubic Bezier" (definidas como keySplines) para movimento fluido. O LVGL tem um motor Bezier embutido, mas tradicionalmente é usado para easing interno.

Exponibilizamos essa lógica nativamente em lvgl_cpp para que você possa usar pontos de controle SVG padrão diretamente no seu código C++:

// From main/ui/workshop_ui.cpp
// ANIMATION: FLAPPING (SVG keySpline="0.25 0.1 0.25 1.0")
a1.set_var(bird_dsc)
.set_values(0, 7)
.set_duration(150)
// ...
// Explicitly using the SVG cubic bezier curve
.set_path_cb(lvgl::Animation::Path::CubicBezier(256, 102, 256, 1024));

Nota: Os pontos de controle (0.25, 0.1, etc.) são mapeados para o intervalo inteiro 0..1024 usado pelo mecanismo de matemática de ponto fixo do LVGL.

🧪 Mergulho profundo na configuração: sdkconfig.defaults

Para animações de alto desempenho, o seu sdkconfig é tão importante quanto o seu código. Aqui está a análise das configurações usadas neste workshop:

ConfigValorFinalidade
CONFIG_ESP_CONSOLE_USB_SERIAL_JTAGyCrucial: Transfere o log para o hardware dedicado do S3. Isso libera os GPIO 43/44 no XIAO S3 Plus, que de outra forma são conectados diretamente ao console UART0 e entram em conflito com os pinos de display/toque no Seeed XIAO Round Display.
CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240yDefine o clock da CPU para o máximo. Renderização vetorial é matemática pura; cada MHz conta.
CONFIG_COMPILER_OPTIMIZATION_PERFyHabilita otimizações -O3. Isso diz ao compilador para priorizar a velocidade de execução em vez do tamanho do binário (essencial para os loops complexos do ThorVG).
CONFIG_COMPILER_OPTIMIZATION_LTOyHabilita link time optimization. Isso permite que o compilador otimize entre arquivos fonte, potencialmente embutindo seu flush_cb diretamente no loop de renderização do mecanismo.
CONFIG_SPIRAMyHabilita a PSRAM externa de 8MB. Sem isso, você fica limitado a ~320KB de RAM interna, tornando o buffer de quadro completo impossível.
CONFIG_SPIRAM_MODE_OCTyConfigura a PSRAM em modo octal (8 linhas de dados). Isso fornece a enorme largura de banda necessária para a CPU ler/gravar quadros de 240x240 pixels sem travamentos.
CONFIG_LV_USE_THORVGyHabilita o mecanismo vetorial C++ de alto desempenho usado pelo LVGL para renderização SVG.
CONFIG_LV_CACHE_DEF_SIZE2097152Aloca um cache de imagem de 2MB na PSRAM. Isso basicamente "lembra" quadros renderizados, transformando matemática vetorial cara em simples cópias de memória para quadros estáticos ou repetidos.

📊 Resumo final de desempenho

FaseOtimizaçãoFPS AlvoFoco principal
Fase 1Baseline~9 FPSSPI 20MHz / Atualização Completa
Fase 2Fundação~15 FPSCPU 240MHz / SPI 80MHz
Fase 3Paralelismo~9 FPS (Regressão!)Buffer Duplo / DMA
Fase 4Ajuste Avançado~25 FPSPSRAM / Buffers de Quadro Completo
Fase 5Nativo~30 FPSSRAM interna / SWAR 32-bit / SIMD

Nota final

Em um app de produção, você poderia mirar nos parâmetros da Fase 5 desde o primeiro dia. A "jornada" é para ensino, mas o destino é sempre a arquitetura nativa no ESP32-S3. Os arquivos fonte contêm comentários pedagógicos verbosos para explicar por que certos padrões de C++ foram escolhidos.

Suporte Técnico & Discussão de Produto

Obrigado por escolher nossos produtos! Estamos aqui para oferecer diferentes tipos de suporte para garantir que sua experiência com nossos produtos seja a mais tranquila possível. Oferecemos vários canais de comunicação para atender a diferentes preferências e necessidades.

Loading Comments...