Generic selectors
Exact matches only
Search in title
Search in content
Post Type Selectors
/
/
O Poder do Dual-Core: Renderização Fractal com ESP32

O Poder do Dual-Core: Renderização Fractal com ESP32


Arquitetura Dual-Core: Core 0 dedicado à física e Core 1 gerenciando a rede com Mutex protegendo a memória compartilhada.


Comparativo de fluxo de execução: Single-Core com gargalo sequencial vs. Dual-Core com tarefas paralelas e independentes.


http://esp32.local

// Tarefa de física: fixada no Core 1 (APP_CPU), prioridade alta
xTaskCreatePinnedToCore(vTaskCalculoMatematico, "Fisica", 8192, NULL, 2, &taskMathHandle, 1);

// Tarefa de rede: fixada no Core 0 (PRO_CPU), prioridade menor
xTaskCreatePinnedToCore(vTaskTransmissaoRede,   "Rede",   8192, NULL, 1, &taskNetHandle,  0);
// Antes de mexer nas partículas, pede a "chave"
if (xSemaphoreTake(mutexFisica, portMAX_DELAY) == pdTRUE) {
    // ... código que acessa o array particulas[] ...
    
    // Devolve a "chave" ao terminar
    xSemaphoreGive(mutexFisica);
}
// Percorre todos os pares únicos (i, j) — complexidade O(N²)
for (int i = 0; i < ativos; i++) {
    for (int j = i + 1; j < ativos; j++) {
        float dx = particulas[j].x - particulas[i].x;
        float dy = particulas[j].y - particulas[i].y;
        float distSq = dx * dx + dy * dy;
        float raioSoma = particulas[i].raio + particulas[j].raio;

        if (distSq < raioSoma * raioSoma && distSq > 0.0001f) {
            // Separação por overlap e troca de velocidades
            float dist    = sqrtf(distSq);
            float overlap = 0.5f * (raioSoma - dist);
            float nx      = dx / dist;
            float ny      = dy / dist;
            
            particulas[i].x -= nx * overlap;
            particulas[j].x += nx * overlap;
            
            float p = (nx * (particulas[i].vx - particulas[j].vx)) +
                      (ny * (particulas[i].vy - particulas[j].vy));
            
            particulas[i].vx -= p * nx;
            particulas[j].vx += p * nx;
        }
    }
}

/**
 * @brief Benchmark Multithread ESP32: Malha Tecnológica (Constelação) O(N^2).
 * Focado 100% em processamento matemático. Tela Infinita.
 * @note Acesso via: http://esp32.local
 * @author Abraão da Silva
 */

#include <WiFi.h>
#include <WebServer.h>
#include <ESPmDNS.h>
#include <mbedtls/sha1.h>
#include <mbedtls/base64.h>

const char* ssid     = "SEU_WIFI";
const char* password = "SUA_SENHA";

WebServer httpServer(80);
WiFiServer wsServer(81);
WiFiClient wsClient;

// ============================================================================
// CONFIGURAÇÕES DA SIMULAÇÃO
// ============================================================================
const int CANVAS_WIDTH  = 1200;
const int CANVAS_HEIGHT = 800;
const int MAX_PARTICLES = 300;

struct Particula {
  float x, y;
  float vx, vy;
  float raio;
};

Particula particulas[MAX_PARTICLES];
volatile int numParticulasAtivas = 150;

enum ModoExecucao { MODO_SINGLE_CORE, MODO_DUAL_CORE };
volatile ModoExecucao modoAtual = MODO_DUAL_CORE;

SemaphoreHandle_t mutexFisica;
TaskHandle_t taskMathHandle = NULL;
TaskHandle_t taskNetHandle  = NULL;

volatile uint32_t spsAtual    = 0;
volatile uint32_t contadorSps = 0;
uint32_t tempoUltimaMetrica   = 0;

// ============================================================================
// MOTOR MATEMÁTICO: física de colisão e movimentação das partículas
// ============================================================================
void atualizarFisica() {
  // Tenta adquirir o mutex antes de mexer nos dados compartilhados
  if (xSemaphoreTake(mutexFisica, portMAX_DELAY) == pdTRUE) {

    int ativos = numParticulasAtivas;

    // Move cada partícula pela sua velocidade
    for (int i = 0; i < ativos; i++) {
      particulas[i].x += particulas[i].vx;
      particulas[i].y += particulas[i].vy;

      // Efeito Tela Infinita: sai por um lado, entra pelo outro
      if (particulas[i].x < 0)              particulas[i].x += CANVAS_WIDTH;
      else if (particulas[i].x > CANVAS_WIDTH)  particulas[i].x -= CANVAS_WIDTH;
      if (particulas[i].y < 0)              particulas[i].y += CANVAS_HEIGHT;
      else if (particulas[i].y > CANVAS_HEIGHT) particulas[i].y -= CANVAS_HEIGHT;

      // Detecção e resolução de colisão O(N²) entre todos os pares
      for (int j = i + 1; j < ativos; j++) {
        float dx     = particulas[j].x - particulas[i].x;
        float dy     = particulas[j].y - particulas[i].y;
        float distSq = dx * dx + dy * dy;
        float raioSoma = particulas[i].raio + particulas[j].raio;

        if (distSq < raioSoma * raioSoma && distSq > 0.0001f) {
          float dist    = sqrtf(distSq);
          float overlap = 0.5f * (raioSoma - dist);
          float nx = dx / dist;
          float ny = dy / dist;

          // Separa as partículas para evitar sobreposição
          particulas[i].x -= nx * overlap;
          particulas[i].y -= ny * overlap;
          particulas[j].x += nx * overlap;
          particulas[j].y += ny * overlap;

          // Troca de momento (colisão elástica)
          float kx = particulas[i].vx - particulas[j].vx;
          float ky = particulas[i].vy - particulas[j].vy;
          float p  = nx * kx + ny * ky;

          particulas[i].vx -= p * nx;
          particulas[i].vy -= p * ny;
          particulas[j].vx += p * nx;
          particulas[j].vy += p * ny;
        }
      }
    }

    // Atualiza o contador de passos por segundo (SPS)
    contadorSps++;
    uint32_t agora = millis();
    if (agora - tempoUltimaMetrica >= 1000) {
      spsAtual            = contadorSps;
      contadorSps         = 0;
      tempoUltimaMetrica  = agora;
    }

    xSemaphoreGive(mutexFisica); // Libera o mutex
  }
}

// ============================================================================
// TAREFA DO CORE 1: Cálculo matemático contínuo
// ============================================================================
void vTaskCalculoMatematico(void* pvParameters) {
  while (true) {
    // No modo Dual-Core, esta tarefa é a responsável pelo cálculo físico no Core 1
    if (modoAtual == MODO_DUAL_CORE) {
      atualizarFisica();
    }
    // Mesmo intervalo de respiro (2ms) em AMBOS os modos — benchmark justo
    vTaskDelay(pdMS_TO_TICKS(2));
  }
}

// ============================================================================
// TAREFA DO CORE 0: Rede — WebSocket, HTTP e envio de telemetria
// ============================================================================
void vTaskTransmissaoRede(void* pvParameters) {
  uint8_t bufferRede[1500];

  while (true) {
    // Aguarda conexão WebSocket se não houver cliente
    if (!wsClient || !wsClient.connected()) {
      WiFiClient novo = wsServer.available();
      if (novo) {
        String req = "";
        while (novo.connected() && novo.available()) {
          req += (char)novo.read();
          if (req.endsWith("\r\n\r\n")) break;
        }
        // Realiza o handshake WebSocket (SHA1 + Base64 da chave)
        int k = req.indexOf("Sec-WebSocket-Key: ");
        if (k != -1) {
          String wsKey = req.substring(k + 19, req.indexOf("\r\n", k));
          wsKey.trim();
          String mk = wsKey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
          unsigned char hash[20];
          mbedtls_sha1((const unsigned char*)mk.c_str(), mk.length(), hash);
          unsigned char b64[64]; size_t outL = 0;
          mbedtls_base64_encode(b64, sizeof(b64), &outL, hash, 20);
          b64[outL] = '\0';
          novo.print("HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: " + String((char*)b64) + "\r\n\r\n");
          wsClient = novo;
        }
      }
      vTaskDelay(pdMS_TO_TICKS(50));
      continue;
    }

    // Lê comandos do navegador (MODO e COUNT)
    if (wsClient.available()) {
      uint8_t h[2]; wsClient.read(h, 2);
      size_t len = h[1] & 0x7F;
      uint8_t mask[4] = {0}; if (h[1] & 0x80) wsClient.read(mask, 4);
      if (len > 0) {
        uint8_t p[32] = {0}; wsClient.read(p, len);
        for (size_t i = 0; i < len; i++) p[i] ^= mask[i % 4];
        String cmd = String((char*)p);
        if (cmd.startsWith("MODO:SINGLE"))      modoAtual = MODO_SINGLE_CORE;
        else if (cmd.startsWith("MODO:DUAL"))   modoAtual = MODO_DUAL_CORE;
        else if (cmd.startsWith("COUNT:"))      numParticulasAtivas = cmd.substring(6).toInt();
      }
    }

    if (modoAtual == MODO_SINGLE_CORE) {
      // Preenche a janela de 33ms com ciclos de física usando o mesmo intervalo do Dual-Core (2ms)
      // O gargalo é natural: o overhead do WebSocket consome parte desse tempo compartilhado
      uint32_t tempoLimite = millis() + 33;
      while ((int32_t)(tempoLimite - millis()) > 2) {
        atualizarFisica();
        vTaskDelay(pdMS_TO_TICKS(2));
      }
    }

    // Lê os dados com o mutex e monta o frame binário para envio
    if (xSemaphoreTake(mutexFisica, pdMS_TO_TICKS(20)) == pdTRUE) {
      int idx = 0;
      // Primeiros 2 bytes: valor do SPS
      bufferRede[idx++] = (spsAtual >> 8) & 0xFF;
      bufferRede[idx++] = spsAtual & 0xFF;

      // 4 bytes por partícula: X (uint16) + Y (uint16)
      int ativos = numParticulasAtivas;
      for (int i = 0; i < ativos; i++) {
        uint16_t px = (uint16_t)particulas[i].x;
        uint16_t py = (uint16_t)particulas[i].y;
        bufferRede[idx++] = (px >> 8) & 0xFF;
        bufferRede[idx++] = px & 0xFF;
        bufferRede[idx++] = (py >> 8) & 0xFF;
        bufferRede[idx++] = py & 0xFF;
      }
      xSemaphoreGive(mutexFisica);

      // Envia o frame WebSocket binário
      uint8_t header[4];
      header[0] = 0x82; // FIN=1, opcode=2 (binário)
      if (idx <= 125) {
        header[1] = idx;
        wsClient.write(header, 2);
      } else {
        header[1] = 126;
        header[2] = (idx >> 8) & 0xFF;
        header[3] = idx & 0xFF;
        wsClient.write(header, 4);
      }
      wsClient.write(bufferRede, idx);
    }

    vTaskDelay(pdMS_TO_TICKS(33)); // ~30 FPS de transmissão
  }
}

// ============================================================================
// SERVIDOR HTTP: entrega o dashboard HTML/CSS/JS ao navegador
// ============================================================================
void handleRoot() {
  // O HTML completo está embutido como string literal raw no firmware
  String html = R"rawliteral(
<!DOCTYPE html>
<html lang="pt-BR">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Benchmark ESP32</title>
  <style>
    body { font-family: 'Segoe UI', sans-serif; margin: 0; padding: 0; background: #020617; color: #e2e8f0; overflow: hidden; }
    canvas { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; z-index: -1; }
    .ui-container { position: absolute; top: 20px; left: 50%; transform: translateX(-50%); width: 90%; max-width: 800px; background: rgba(15,23,42,0.7); backdrop-filter: blur(8px); border: 1px solid rgba(51,65,85,0.5); border-radius: 12px; padding: 20px; box-shadow: 0 10px 30px rgba(0,0,0,0.5); z-index: 10; }
    .hud { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; flex-wrap: wrap; gap: 10px; }
    .metrics { font-size: 1.2rem; background: rgba(30,41,59,0.8); padding: 10px 20px; border-radius: 8px; border: 1px solid #334155; }
    .metrics strong { color: #38bdf8; font-size: 1.6rem; }
    .controls { display: flex; gap: 15px; align-items: center; flex-wrap: wrap; }
    button { background: rgba(30,41,59,0.8); color: white; border: 1px solid #334155; padding: 10px 20px; border-radius: 6px; cursor: pointer; transition: 0.2s; font-weight: 600; }
    button:hover { background: #334155; }
    button.active { background: #38bdf8; border-color: #0284c7; color: #0f172a; }
    .slider-container { display: flex; align-items: center; gap: 10px; margin-left: auto; background: rgba(30,41,59,0.8); padding: 8px 15px; border-radius: 6px; border: 1px solid #334155; }
  </style>
</head>
<body>
  <canvas id="cvs" width="1200" height="800"></canvas>
  <div class="ui-container">
    <div class="hud">
      <div>
        <h2 style="margin:0; color:#fff;">Estresse FPU O(N²)</h2>
        <span style="color:#94a3b8;">Teste direto de colisão e malha dinâmica</span>
      </div>
      <div class="metrics">Cálculos da CPU: <strong id="valSps">0</strong> pass/s</div>
    </div>
    <div class="controls">
      <button id="b1" onclick="setMode('SINGLE')">1 Núcleo (Single)</button>
      <button id="b2" class="active" onclick="setMode('DUAL')">2 Núcleos (Dual)</button>
      <div class="slider-container">
        Densidade: <strong id="valCount" style="color:#38bdf8; min-width:30px;">150</strong>
        <input type="range" min="10" max="300" value="150" oninput="changeCount(this.value)">
      </div>
    </div>
  </div>
  <script>
    const canvas = document.getElementById('cvs');
    const ctx = canvas.getContext('2d');
    let ws;

    function initWS() {
      ws = new WebSocket('ws://' + window.location.hostname + ':81/');
      ws.binaryType = 'arraybuffer';
      ws.onmessage = (e) => {
        if (!(e.data instanceof ArrayBuffer)) return;
        let buf = new Uint8Array(e.data);
        if (buf.length < 2) return;

        // Atualiza o contador de pass/s
        document.getElementById('valSps').innerText = (buf[0] << 8) | buf[1];

        // Limpa o canvas e redesenha
        ctx.clearRect(0, 0, 1200, 800);
        let particulas = [];
        let num = (buf.length - 2) / 4;

        // Extrai posições de cada partícula
        for (let i = 0; i < num; i++) {
          let o = 2 + i * 4;
          particulas.push({ x: (buf[o] << 8) | buf[o+1], y: (buf[o+2] << 8) | buf[o+3] });
        }

        // Desenha linhas de conexão entre partículas próximas (malha/constelação)
        ctx.lineWidth = 1;
        for (let i = 0; i < num; i++) {
          for (let j = i + 1; j < num; j++) {
            let dx = particulas[i].x - particulas[j].x;
            let dy = particulas[i].y - particulas[j].y;
            let distSq = dx*dx + dy*dy;
            if (distSq < 7000) {
              let alpha = 1.0 - (Math.sqrt(distSq) / 83.6);
              ctx.strokeStyle = `rgba(56, 189, 248, ${alpha})`;
              ctx.beginPath(); ctx.moveTo(particulas[i].x, particulas[i].y);
              ctx.lineTo(particulas[j].x, particulas[j].y); ctx.stroke();
            }
          }
        }

        // Desenha os nós (pontos brilhantes)
        ctx.fillStyle = '#bae6fd';
        for (let i = 0; i < num; i++) {
          ctx.beginPath(); ctx.arc(particulas[i].x, particulas[i].y, 3.5, 0, Math.PI * 2); ctx.fill();
        }
      };
      ws.onclose = () => setTimeout(initWS, 1000);
    }

    window.setMode = (m) => {
      document.getElementById('b1').className = m === 'SINGLE' ? 'active' : '';
      document.getElementById('b2').className = m === 'DUAL'   ? 'active' : '';
      if (ws) ws.send('MODO:' + m);
    };
    window.changeCount = (v) => { document.getElementById('valCount').innerText = v; if (ws) ws.send('COUNT:' + v); };
    window.onload = initWS;
  </script>
</body>
</html>
  )rawliteral";
  httpServer.send(200, "text/html", html);
}

// ============================================================================
// SETUP: inicializa hardware, rede, mDNS e cria as tarefas FreeRTOS
// ============================================================================
void setup() {
  Serial.begin(115200);

  // Inicializa as partículas com posições e velocidades aleatórias
  for (int i = 0; i < MAX_PARTICLES; i++) {
    particulas[i].x  = random(0, CANVAS_WIDTH);
    particulas[i].y  = random(0, CANVAS_HEIGHT);
    particulas[i].vx = (random(-40, 40)) / 10.0f;
    particulas[i].vy = (random(-40, 40)) / 10.0f;
    particulas[i].raio = 4.0f;
  }

  // Cria o mutex antes de qualquer tarefa que possa acessar os dados
  mutexFisica = xSemaphoreCreateMutex();

  // Conecta ao Wi-Fi
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) delay(500);
  Serial.print("Conectado! IP: "); Serial.println(WiFi.localIP());

  // Registra o endereço mDNS amigável
  if (MDNS.begin("esp32")) Serial.println("MDNS: http://esp32.local");

  // Inicia o servidor HTTP e o servidor WebSocket
  httpServer.on("/", handleRoot);
  httpServer.begin();
  wsServer.begin();

  // Cria as tarefas e as fixa nos núcleos específicos
  xTaskCreatePinnedToCore(vTaskCalculoMatematico, "Fisica", 8192, NULL, 2, &taskMathHandle, 1);
  xTaskCreatePinnedToCore(vTaskTransmissaoRede,   "Rede",   8192, NULL, 1, &taskNetHandle,  0);
}

void loop() {
  httpServer.handleClient(); // Atende requisições HTTP
  vTaskDelay(pdMS_TO_TICKS(10));
}


Sobre o Autor


Abraão da Silva

Estudante de Engenharia da Computação, especializado em curiosidades aparentemente aleatórias e desenvolvimento de software. Se eu não estiver pedalando agora, estou estudando ou tentando aproveitar a energia dos raios.

Neste projeto, vamos acordar os dois núcleos e colocá-los a trabalhar em paralelo para resolver um dos problemas mais pesados da computação: a simulação de física de colisões com complexidade O(N²).

Precisa dos componentes para este projeto?

Encontre tudo na Loja Eletrogate com frete grátis para compras acima de R$ 200