IoTLabs

Nghiên cứu, Sáng tạo và Thử nghiệm

Series ESP32-S3 Dual-Core – Bài 8: Edge AI – ESP-DSP, ESP-NN & Giới Hạn Thực Tế

Kỳ Vọng vs Thực Tế

Trước khi đi vào kỹ thuật, cần nói thẳng về giới hạn:

ESP32-S3 KHÔNG thể:

  • Chạy mô hình YOLO detect đối tượng real-time ở 30 fps
  • Chạy LLM, GPT, hoặc bất kỳ mô hình ngôn ngữ nào
  • Thay thế GPU hoặc NPU chuyên dụng
  • Làm Computer Vision phức tạp từ camera HD

ESP32-S3 CÓ THỂ:

  • Wake word detection (ví dụ: “Hey Siri” style, nhưng model nhỏ)
  • Phân loại cử chỉ/activity đơn giản từ accelerometer
  • Phát hiện anomaly trong chuỗi cảm biến
  • FFT và signal processing cho audio/vibration
  • Keyword spotting trong audio 16 KHz
  • Image classification với model nhỏ (< 100 KB)

Hiểu đúng giới hạn giúp bạn chọn đúng bài toán cho ESP32-S3.

SIMD: Vector Instructions Trên Xtensa LX7

Xtensa LX7 trong ESP32-S3 có PIE (Processor Instruction Extensions) — tập lệnh SIMD 128-bit. Thay vì xử lý 1 float mỗi lệnh, SIMD xử lý 4 float (hoặc 8 int16) cùng lúc.

Không SIMD: for (i=0; i<N; i++) result[i] = a[i] * b[i];
            → N lần nhân, N chu kỳ

Với SIMD:   Xử lý 4 phần tử/lệnh → N/4 chu kỳ
            → ~4× nhanh hơn cho dot product

Bạn không cần viết assembly để dùng SIMD — Espressif đã wrap vào 2 thư viện:

ESP-DSP: Signal Processing

ESP-DSP là thư viện tối ưu cho Xtensa LX7, dùng SIMD bên dưới:

#include "esp_dsp.h"

// FFT 512 điểm trên 16000 Hz audio
const int N = 512;
__attribute__((aligned(16))) float fft_input[N * 2];  // Complex: real + imag interleaved
__attribute__((aligned(16))) float fft_output[N];

void performFFT(int16_t* audioSamples) {
    // Convert int16 samples → float complex (imag = 0)
    for (int i = 0; i < N; i++) {
        fft_input[i * 2]     = audioSamples[i] / 32768.0f;  // Real part
        fft_input[i * 2 + 1] = 0.0f;                         // Imaginary part
    }

    // FFT forward transform
    dsps_fft2r_fc32(fft_input, N);

    // Bit-reverse
    dsps_bit_rev_fc32(fft_input, N);

    // Convert complex → magnitude
    dsps_cplx2reC_fc32(fft_input, N);

    // Tính magnitude của từng frequency bin
    for (int i = 0; i < N / 2; i++) {
        fft_output[i] = sqrtf(fft_input[i * 2] * fft_input[i * 2] +
                               fft_input[i * 2 + 1] * fft_input[i * 2 + 1]);
    }

    // fft_output[i] = magnitude tại frequency (i * 16000 / N) Hz
    // Ví dụ: fft_output[8] = magnitude tại 250 Hz
}

Dot product — core operation của neural network:

// Dot product tối ưu SIMD: a[] . b[] với N phần tử
float dotProduct(float* a, float* b, int N) {
    float result = 0.0f;
    dsps_dotprod_f32(a, b, &result, N);  // SIMD-optimized
    return result;
}

Benchmark thực tế ESP32-S3 vs ESP32 (LX6) cho FFT 512:

  • ESP32 (LX6): ~4.5 ms
  • ESP32-S3 (LX7, không SIMD): ~2.8 ms
  • ESP32-S3 (LX7 + dsps_fft2r): ~0.6 ms — 7.5× nhanh hơn!

ESP-NN: Neural Network Inference

ESP-NN cung cấp các operator tối ưu SIMD cho neural network inference — kernel của TFLite Micro dùng ESP-NN bên dưới khi chạy trên ESP32-S3.

#include "esp_nn.h"

// Depthwise convolution (quan trọng trong MobileNet, EfficientNet)
esp_nn_depthwise_conv_s8(
    input_data,   // int8 input
    input_dims,   // {batch, height, width, channels}
    filter_data,  // int8 weights
    filter_dims,
    bias_data,    // int32 bias
    output_data,  // int8 output
    output_dims,
    padding,
    stride,
    dilation,
    activation    // RELU, RELU6, NONE
);

Thực tế bạn không gọi ESP-NN trực tiếp — TFLite Micro tự detect và dùng khi chạy trên ESP32-S3.

TFLite Micro: Chạy Model AI

TensorFlow Lite for Microcontrollers là cách phổ biến nhất để chạy model AI trên ESP32-S3:

#include "tensorflow/lite/micro/all_ops_resolver.h"
#include "tensorflow/lite/micro/micro_interpreter.h"
#include "tensorflow/lite/schema/schema_generated.h"

// Model data (array từ file .tflite đã convert)
extern const uint8_t model_data[];
extern const int model_data_len;

// Arena memory cho inference (trong PSRAM nếu model lớn)
const int kArenaSize = 128 * 1024;  // 128 KB
uint8_t* arena = (uint8_t*)ps_malloc(kArenaSize);

tflite::AllOpsResolver resolver;
const tflite::Model* model;
tflite::MicroInterpreter* interpreter;
TfLiteTensor* input;
TfLiteTensor* output;

void setupAI() {
    model = tflite::GetModel(model_data);

    static tflite::MicroInterpreter static_interpreter(
        model, resolver, arena, kArenaSize);
    interpreter = &static_interpreter;

    interpreter->AllocateTensors();

    input  = interpreter->input(0);
    output = interpreter->output(0);

    Serial.printf("Input shape: [%d, %d, %d, %d]\n",
        input->dims->data[0], input->dims->data[1],
        input->dims->data[2], input->dims->data[3]);
}

void runInference(float* inputData, int inputSize) {
    // Copy data vào input tensor
    memcpy(input->data.f, inputData, inputSize * sizeof(float));

    // Chạy inference
    uint32_t t0 = millis();
    interpreter->Invoke();
    uint32_t inferenceMs = millis() - t0;

    // Đọc kết quả
    int numOutputs = output->dims->data[1];
    Serial.printf("Inference: %lu ms, outputs: %d\n", inferenceMs, numOutputs);

    for (int i = 0; i < numOutputs; i++) {
        Serial.printf("  Class %d: %.2f\n", i, output->data.f[i]);
    }
}

Model Size Guide

ModelSizeRAM cầnUse CaseTốc độ
MobileNetV1 0.25 (96×96)~100 KB~200 KBPhân loại ảnh đơn giản~150 ms
Keyword Spotting LSTM~20 KB~50 KBWake word 1–5 từ~30 ms
Gesture from IMU~8 KB~20 KB5–10 cử chỉ~5 ms
Anomaly Detection~4 KB~10 KBVibration, sensor~2 ms
Digit Recognition~16 KB~40 KB28×28 greyscale~20 ms

Quy tắc thực tế: Model phải đủ nhỏ để fit vào PSRAM + chạy trong vài chục ms. Model > 2 MB hoặc cần > 10 ms thường không thực tế cho embedded real-time.

Ví Dụ Thực Tế: Phát Hiện Anomaly Cảm Biến

Autoencoder nhỏ phát hiện anomaly trong chuỗi sensor — không cần label data:

// Input: 10 readings gần nhất từ accelerometer
// Output: reconstruction error — cao = anomaly

float sensorBuffer[10];
int bufferIndex = 0;

void updateSensorBuffer(float newReading) {
    sensorBuffer[bufferIndex] = newReading;
    bufferIndex = (bufferIndex + 1) % 10;

    if (bufferIndex == 0) {  // Buffer đầy → chạy inference
        float error = runAutoencoderInference(sensorBuffer);
        if (error > ANOMALY_THRESHOLD) {
            Serial.printf("ANOMALY detected! Error: %.3f\n", error);
            // Gửi alert, log event, v.v.
        }
    }
}

Tổng Kết

Công CụDùng ChoĐiểm Mạnh
ESP-DSPFFT, filter, dot productSIMD tối ưu, tích hợp ESP-IDF
ESP-NNNeural net operatorsKernel của TFLite, không gọi trực tiếp
TFLite MicroInference model .tflitePhổ biến, nhiều model pretrained
Edge ImpulseModel training + deploymentPlatform tích hợp, xuất code

Bài tiếp theo: Bài 9 — Project Thực Tế: Camera + LCD + Dual-Core Trên ESP32-S3 — Kết hợp camera OV2640, LCD ST7789, Dual-Core FreeRTOS và PSRAM vào một project hoàn chỉnh.