TinyML trên ESP32: Chạy AI ngay tại thiết bị
Cách mình chạy một model nhận diện người ngay trên ESP32 bằng TensorFlow Lite Micro và ESP-NN, từ giới hạn bộ nhớ đến cách thử ví dụ person_detection.
Chia sẻ

Nếu mới nghe đến TinyML, bạn có thể hình dung đơn giản thế này: một model nhỏ chạy thẳng trên vi điều khiển, tự đưa ra kết quả mà không phải gửi dữ liệu lên server. Với ESP32, đây đã là một bài thử khá gần gũi: Espressif có sẵn component, model mẫu và project để mình build trực tiếp bằng ESP-IDF.
Trong bài này, mình dùng person_detection làm ví dụ. Camera chụp một khung hình, ESP32 trả về hai điểm số person và no_person, và toàn bộ quá trình diễn ra tại thiết bị.

Một demo TinyML đúng nghĩa: camera lấy ảnh, ESP32-S3 tự chạy model và trả kết quả ngay tại bàn — không cần gửi khung hình lên server.
TinyML hữu ích khi nào?
Mình thường nghĩ đến TinyML khi thiết bị chỉ cần nhận ra một tín hiệu hẹp nhưng rõ ràng: có người trong khung hình, nghe thấy một từ khóa, hoặc phát hiện một trạng thái bất thường từ cảm biến.
Cách làm này có ba lợi ích dễ thấy:
- Phản hồi nhanh: không phải chờ một vòng gửi dữ liệu lên server rồi nhận kết quả về.
- Vẫn chạy khi mất mạng: hợp với cảm biến dùng pin hoặc thiết bị ở nơi kết nối chập chờn.
- Giữ dữ liệu riêng tư hơn: ảnh và âm thanh thô không cần rời khỏi thiết bị.
Đổi lại, model phải nhỏ và bài toán phải gọn. person_detection chỉ trả lời “có người hay không”, chứ không nhận diện người đó là ai. TinyML cũng không thay thế cloud AI; nó phù hợp hơn với vai trò của một bộ lọc hoặc trigger luôn sẵn sàng.
Mỗi khung hình đi qua tiền xử lý, model TFLM và ESP-NN rồi mới tạo ra kết quả. Dữ liệu ảnh thô vẫn nằm trên thiết bị.
Phần khó nhất thường là bộ nhớ
ESP32 có SRAM tính bằng hàng trăm kilobyte, không phải gigabyte. Vì vậy, model thường được quantize về int8 để giảm dung lượng flash, RAM trung gian và lượng phép tính khi inference.
TensorFlow Lite Micro (TFLM) dùng một vùng nhớ được cấp trước gọi là tensor arena. Model, tensor đầu vào/đầu ra và các tensor trung gian phải vừa trong vùng này. Arena quá nhỏ thì khởi tạo interpreter thất bại; đặt quá lớn lại lấy mất SRAM của frame buffer camera, Wi-Fi và phần còn lại của firmware.
Model person detection trong ví dụ của Espressif khoảng 250 KB và đã được quantize int8. Con số đó mới là kích thước model, chưa phải là toàn bộ RAM mà ứng dụng cần. Khi ước lượng phần cứng, bạn vẫn phải cộng thêm tensor arena, frame buffer, stack của task và các component khác.
Bộ công cụ: TFLM + ESP-NN
esp-tflite-micro đóng gói TFLM cho ESP-IDF và tích hợp ESP-NN. ESP-NN cung cấp các kernel tối ưu cho convolution, depthwise convolution và những phép toán neural network phổ biến; trên ESP32-S3 và ESP32-P4, thư viện tự chọn phần cài đặt assembly tối ưu.
README hiện tại của project ghi rằng hỗ trợ ESP-IDF từ release/v4.4 trở lên; manifest component hiện yêu cầu ESP-IDF >=5.0. Nếu bắt đầu project mới, mình sẽ chọn một bản ESP-IDF 5.x ổn định thay vì bám vào mức tối thiểu.
Thêm component vào project có sẵn:
idf.py add-dependency "esp-tflite-micro"
Hoặc lấy thẳng ví dụ gần nhất:
idf.py create-project-from-example "esp-tflite-micro:person_detection"
Repo còn có hello_world để kiểm tra toolchain và micro_speech để thử nhận từ khóa từ âm thanh.
Chạy thử person_detection
Ví dụ hỗ trợ các board như ESP32-DevKitC kèm camera, ESP-EYE, ESP32-S3-EYE, ESP32-S3-Korvo-2 và ESP32-S2-Kaluga. Kaluga chỉ đạt khoảng 1–2 FPS theo tài liệu của ví dụ, nên phù hợp để thử ý tưởng hơn là làm mốc hiệu năng.
Nếu chưa có camera, bạn vẫn có thể kiểm tra pipeline bằng 10 ảnh nhúng sẵn. Bật chế độ CLI trong main/esp_main.h:
#define CLI_ONLY_INFERENCE 1
Sau đó build, flash và mở monitor:
idf.py set-target esp32s3
idf.py build
idf.py --port /dev/ttyUSB0 flash monitor
Trên Windows, thay /dev/ttyUSB0 bằng cổng COMx của board. Trong monitor, chạy:
detect_image 0
Bạn có thể đổi số từ 0 đến 9. Log sẽ in ra điểm person và no_person. Mình thích làm bước này trước vì nó xác nhận toolchain, model và inference đã ổn; sau đó, nếu camera không chạy, phạm vi tìm lỗi chỉ còn wiring, pin map và driver.

Mình thường kiểm tra model bằng ảnh mẫu trước, sau đó mới nối camera thật. Cách này giúp tách lỗi model khỏi lỗi dây nối và driver.
Benchmark: Vì sao ESP32-S3 đáng chọn hơn?
Espressif công bố thời gian chạy invoke() của cùng ví dụ khi bật ESP-NN và dùng bộ nhớ internal:
| Chip | Không có ESP-NN | Có ESP-NN |
|---|---|---|
| ESP32 | 4084 ms | 380 ms |
| ESP32-C3 | 3355 ms | 426 ms |
| ESP32-S3 | 2300 ms | 54 ms |
| ESP32-P4 | 1395 ms | 73 ms |
Với cùng model, ESP32-S3 chạy nhanh hơn ESP32 thường khoảng bảy lần khi đều bật ESP-NN. Khác biệt lớn không chỉ đến từ xung nhịp; ESP-NN có implementation assembly tối ưu cho S3, còn ESP32 và C3 dùng generic tối ưu.
Nếu camera cần phản hồi vài lần mỗi giây, mình sẽ ưu tiên ESP32-S3. ESP32 thường vẫn đủ cho tác vụ chạy thưa, chẳng hạn vài giây kiểm tra một lần, nhưng nên benchmark trước khi chốt PCB. Số liệu ESP32-P4 cũng cần đọc cùng ghi chú của Espressif rằng phần tối ưu cho chip này vẫn đang được hoàn thiện.

Benchmark nên được đo lại trên đúng phần cứng cuối cùng, khi camera, bộ nhớ và các tác vụ khác cùng hoạt động.
Những chỗ mình sẽ kiểm tra trước khi chốt phần cứng
- Đo tensor arena bằng model thật. Đừng lấy kích thước file
.tflitelàm ngân sách RAM cuối cùng. - Ưu tiên internal RAM cho đường chạy nóng. Nếu tensor hoặc frame buffer phải dùng PSRAM, độ trễ thực tế có thể cao hơn bảng benchmark.
- **Kiểm tra pin map theo đúng board.Camera chạy trên dev kit chưa có nghĩa là sẽ chạy ngay trên PCB tùy chỉnh.
- Thử trong vỏ thật và ánh sáng thật. Góc camera, bóng của thành vỏ và khoảng cách đến chủ thể ảnh hưởng trực tiếp đến kết quả.
- Đo lại khi mọi thứ cùng chạy. Camera, Wi-Fi, màn hình và các task khác có thể thay đổi cả bộ nhớ lẫn độ trễ.
Kết luận
TinyML trên ESP32 không quá xa vời: bạn có thể chạy demo đầu tiên trong một buổi nếu ESP-IDF đã sẵn sàng. Phần cần đầu tư không nằm ở lệnh build, mà ở việc giữ bài toán đủ hẹp, tính đúng ngân sách bộ nhớ và thử trong điều kiện sản phẩm thật.
Nếu bắt đầu từ con số không, mình sẽ chọn ESP32-S3, chạy 10 ảnh mẫu trước, nối camera sau, rồi mới thay model của riêng mình. Trình tự đó tiết kiệm khá nhiều thời gian dò lỗi.
Tài liệu tham khảo
Chia sẻ
Tiếp tục khám phá
Đọc tiếp
Bài viết liên quan
Tối ưu thời lượng pin cho robot ESP32 có màn hình
Phân tích thời lượng pin cho robot ESP32 có màn hình: backlight, Wi-Fi, audio, ngoại vi, sleep mode, wake-up và runtime.
Thiết kế audio hai chiều trên ESP32-S3: Microphone I2S và loa I2S
Giải thích các tín hiệu BCLK, WS, DIN, DOUT, full-duplex, sample rate và buffer khi thiết kế audio hai chiều trên ESP32-S3.
Ghi chú đầu tiên về firmware cho sản phẩm nhúng
Nguyên tắc ban đầu khi viết firmware cho một sản phẩm nhúng nhỏ: phạm vi, ranh giới module, logging, và xử lý lỗi mà không đoán mò.