โปรเจค “TinyML รับคำสั่งเสียง เปิด–ปิดไฟ ด้วย ESP32-S3 + INMP441” เป็นโปรเจคที่สนุกและใช้งานได้จริงมาก เหมาะกับการเรียนรู้ TinyML เบื้องต้นบนไมโครคอนโทรลเลอร์โดยตรง (ไม่ต้องใช้คลาวด์)
TinyML หรือ Tiny Machine Learning ก็คือ ศาสตร์ย่อยแขนงหนึ่งของ Machine Learning (ML) ที่มุ่งเน้นที่จะนำเอาความสามารถของ AI มาใส่ในอุปกรณ์ขนาดเล็ก ที่มีข้อจำกัดทั้งด้านทรัพยากรประมวลผล และพลังงาน อย่างเช่น ไมโครคอนโทรลเลอร์ (MCU) หรือเซนเซอร์อัจฉริยะ ที่ช่วยให้ AI สามารถประมวลผลข้อมูลแบบเรียลไทม์บนอุปกรณ์ที่ฝังตัว (Embedded Systems)
🔧 อุปกรณ์ที่ใช้
- ESP32-S3 (เช่นรุ่น ESP32-S3-DevKitC หรือ ESP32-S3-EYE ก็ได้)
- ไมค์ INMP441 (I2S Microphone)
- LED หรือรีเลย์โมดูล (จำลองหลอดไฟ)
- สายจัมเปอร์ / บอร์ดทดลอง
- คอมพิวเตอร์พร้อม Arduino IDE หรือ PlatformIO
- TensorFlow Lite for Microcontrollers (TFLM)
🧠 แนวคิดของระบบ
- INMP441 รับเสียงพูด (“เปิดไฟ”, “ปิดไฟ”)
- ESP32-S3 ประมวลผลเสียงแบบเรียลไทม์ → แปลงเป็นสเปกโตรแกรม (MFCC)
- โมเดล TinyML จำแนกคำพูดเป็น 3 หมวด:
เปิดไฟ(ON)ปิดไฟ(OFF)เงียบ/อื่นๆ(Noise)
- สั่งเปิดหรือปิดไฟ (LED หรือรีเลย์)
🪜 ขั้นตอนการทำงาน
🥇 ขั้นตอนที่ 1: เก็บข้อมูลเสียง (Dataset)
ดาวน์โหลดและติดตั้ง Audacity
https://www.fosshub.com/Audacity-old.html

ตั้งค่าไฟล์เสียง
Sampling rate: 16 kHz

Mono (1 channel)

เริ่มบันทึกเสียง

พูดเช่น “เปิดไฟ” แล้วกดปุ่ม หยุดบันทึกเสียง

ขยายภาพให้ใหญ่ขึ้น

เลือกช่วงที่ต้องการเก็บไว้ (1 วินาที)
ใช้เครื่องมือ “Selection Tool” (ไอคอนรูปตัว I)
คลิกแล้วลากจากตำแหน่งเวลา 0.0 ถึง 1.0 วินาที
ดูค่าที่มุมขวาล่างของ Audacity → ต้องเป็น 00 h 00 m 01.000 s

ตัดส่วนเกินออก
ไปที่เมนู Edit → Remove Special → Trim Audio

ระบบจะลบทุกอย่างนอกช่วงที่เลือกออก

คลิกเครื่องมือรูป ↔️ (ลูกศรสองหัว) ที่แถบเครื่องมือบนสุด
หรือกดคีย์ลัด F5
คลิกที่คลื่นเสียง แล้ว ลากไปทางซ้ายสุด
(ให้จุดเริ่มต้นของคลื่นเสียงตรงกับเวลา 0 วินาที)


เสร็จแล้วกลับไปใช้เครื่องมือปกติ
ไปที่ File → Export → Export as WAV

Format: .wav
ตั้งชื่อไฟล์ เช่น open_01.wav
ในช่อง Encoding เลือก
WAV (Microsoft) signed 16-bit PCM
กด Save → OK

OK

Duration: 1 วินาทีต่อไฟล์

- ใช้ ไมค์บนคอมพิวเตอร์ อัดเสียงพูด 3 กลุ่ม:
เปิดไฟปิดไฟ- เสียงอื่น ๆ (เช่น เงียบ เสียงพูดอื่น)

อัดหลาย ๆ ตัวอย่าง (อย่างน้อย 20 คลิป)



🥈 ขั้นตอนที่ 2: ฝึกโมเดล (Training)
เขียนโปรแกรม Python ด้วย VS Code
1. ติดตั้ง Python
- ดาวน์โหลดและติดตั้ง Python: หากยังไม่ได้ติดตั้ง ให้ดาวน์โหลด Python จาก เว็บไซต์ทางการ และติดตั้งให้เรียบร้อย.
- ตั้งค่า Path: ตรวจสอบให้แน่ใจว่าได้ตั้งค่า PATH ของ Python ในระบบปฏิบัติการของคุณแล้ว โดยทำตามขั้นตอนการตั้งค่าตัวแปรสภาพแวดล้อม (Environment Variables).
2. ติดตั้งส่วนขยาย (Extension) สำหรับ Python ใน VS Code
- เปิด VS Code แล้วไปที่แท็บ Extensions ที่อยู่ด้านซ้ายมือ
- พิมพ์ “Python” ในช่องค้นหา
- เลือกส่วนขยาย Python ที่มาจาก Microsoft แล้วกดติดตั้ง
3. สร้างและเขียนโค้ด
บันทึกไฟล์: บันทึกไฟล์ด้วยนามสกุล .py
โครงสร้างโฟลเดอร์
project_voice_light/
│
├── dataset/
│ ├── open/ ← เก็บเสียง “เปิดไฟ”
│ ├── close/ ← เก็บเสียง “ปิดไฟ”
│
├── preprocess.py
├── model.py
├── train.py
├── convert_tflite.py
└── test_tflite.py
แต่ละไฟล์ .wav ควรยาวประมาณ 1 วินาที, 16kHz, mono
เช่น dataset/open/on_001.wav, dataset/close/off_001.wav
🔧 1. เตรียมสภาพแวดล้อม (VS Code Terminal)
python3.9 -m venv .venv
source .venv/bin/activate # หรือ .venv\Scripts\activate บน Windows
pip install --upgrade pip
pip install tensorflow==2.10.1 numpy==1.23.5 librosa soundfile matplotlib scikit-learn
🧩 2. preprocess.py
(ใช้ librosa สร้าง MFCC)
import librosa
import numpy as np
import tensorflow as tf
import os
SAMPLE_RATE = 16000
DURATION = 1.0
SAMPLES = int(SAMPLE_RATE * DURATION)
NUM_MFCC = 13
MAX_FRAMES = 49 # ให้ MFCC ทุกไฟล์มีขนาดเท่ากัน (49x13)
def load_wav(path):
wav, sr = librosa.load(path, sr=SAMPLE_RATE)
if len(wav) < SAMPLES:
wav = np.pad(wav, (0, SAMPLES - len(wav)))
else:
wav = wav[:SAMPLES]
return wav
def wav_to_mfcc(wav):
mfcc = librosa.feature.mfcc(y=wav, sr=SAMPLE_RATE, n_mfcc=NUM_MFCC)
mfcc = mfcc.T # (time, mfcc)
if mfcc.shape[0] < MAX_FRAMES:
pad_width = MAX_FRAMES - mfcc.shape[0]
mfcc = np.pad(mfcc, ((0, pad_width), (0,0)), mode='constant')
else:
mfcc = mfcc[:MAX_FRAMES, :]
return mfcc
def load_dataset(data_dir):
labels = sorted(os.listdir(data_dir))
X, y = [], []
for label_idx, label in enumerate(labels):
folder = os.path.join(data_dir, label)
for fname in os.listdir(folder):
if fname.endswith(".wav"):
path = os.path.join(folder, fname)
wav = load_wav(path)
mfcc = wav_to_mfcc(wav)
X.append(mfcc)
y.append(label_idx)
return np.array(X), np.array(y), labels
def make_tf_dataset(X, y, batch=32, training=True):
ds = tf.data.Dataset.from_tensor_slices((X, y))
if training:
ds = ds.shuffle(1000)
ds = ds.batch(batch).prefetch(tf.data.AUTOTUNE)
return ds
🧠 3. model.py
(โมเดล CNN เบา ๆ สำหรับ TinyML)
import tensorflow as tf
def create_model(input_shape=(49, 13), num_classes=2):
inputs = tf.keras.Input(shape=input_shape)
x = tf.expand_dims(inputs, -1)
x = tf.keras.layers.Conv2D(8, (3,3), activation='relu', padding='same')(x)
x = tf.keras.layers.MaxPool2D((2,2))(x)
x = tf.keras.layers.Conv2D(16, (3,3), activation='relu', padding='same')(x)
x = tf.keras.layers.MaxPool2D((2,2))(x)
x = tf.keras.layers.Flatten()(x)
x = tf.keras.layers.Dense(32, activation='relu')(x)
x = tf.keras.layers.Dropout(0.2)(x)
outputs = tf.keras.layers.Dense(num_classes, activation='softmax')(x)
model = tf.keras.Model(inputs, outputs)
model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy'])
return model
🧩 4. train.py
(โหลดข้อมูล, ฝึกโมเดล, เซฟ)
import numpy as np
from sklearn.model_selection import train_test_split
from preprocess import load_dataset, make_tf_dataset
from model import create_model
import tensorflow as tf
# โหลด dataset
X, y, labels = load_dataset("dataset")
X_train, X_val, y_train, y_val = train_test_split(X, y, test_size=0.2, stratify=y)
train_ds = make_tf_dataset(X_train, y_train, batch=32, training=True)
val_ds = make_tf_dataset(X_val, y_val, batch=32, training=False)
# สร้างโมเดล
model = create_model(input_shape=X_train.shape[1:], num_classes=len(labels))
model.summary()
# ฝึกโมเดล
model.fit(train_ds, validation_data=val_ds, epochs=20)
# เซฟโมเดล
model.save("saved_model")
np.save("labels.npy", np.array(labels))
print("✅ Saved model and labels:", labels)
🔁 5. convert_tflite.py
(แปลงเป็น TFLite + quantize)
import tensorflow as tf
import numpy as np
from preprocess import wav_to_mfcc, load_wav
import glob
SAVED = "saved_model"
# Representative dataset สำหรับ quantization
sample_files = glob.glob("dataset/*/*.wav")[:100]
def representative_dataset_gen():
for path in sample_files:
wav = load_wav(path)
mfcc = wav_to_mfcc(wav).astype(np.float32)
mfcc = np.expand_dims(mfcc, axis=0)
yield [mfcc]
converter = tf.lite.TFLiteConverter.from_saved_model(SAVED)
converter.optimizations = [tf.lite.Optimize.DEFAULT]
converter.representative_dataset = representative_dataset_gen
converter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS_INT8]
converter.inference_input_type = tf.int8
converter.inference_output_type = tf.int8
tflite_model = converter.convert()
open("model.tflite", "wb").write(tflite_model)
print("✅ Saved model.tflite (quantized)")
🧪 6. test_tflite.py
(ทดสอบโมเดล TFLite)
import tensorflow as tf
import numpy as np
from preprocess import load_wav, wav_to_mfcc
import sys
interpreter = tf.lite.Interpreter(model_path="model.tflite")
interpreter.allocate_tensors()
input_details = interpreter.get_input_details()
output_details = interpreter.get_output_details()
# โหลด labels
labels = np.load("labels.npy")
# อ่านเสียงทดสอบ
test_file = "dataset/open/open_001.wav" # เปลี่ยนได้
wav = load_wav(test_file)
mfcc = wav_to_mfcc(wav).astype(np.float32)
mfcc = np.expand_dims(mfcc, axis=0)
# Quantize input
scale, zero_point = input_details[0]["quantization"]
mfcc_q = (mfcc / scale + zero_point).astype(np.int8)
interpreter.set_tensor(input_details[0]['index'], mfcc_q)
interpreter.invoke()
# อ่าน output
output = interpreter.get_tensor(output_details[0]['index'])[0]
pred = np.argmax(output)
print("✅ Prediction:", labels[pred])
🎯 ผลลัพธ์สุดท้าย
หลังรันครบ:
มี labels.npy สำหรับ mapping ผลลัพธ์
ได้ไฟล์ model.tflite
ใช้ได้กับ TensorFlow Lite Micro / ESP32-S3

🔧 ขั้นตอนต่อจากนี้ (หลังจาก test_tflite.py ทำงานได้)
✅ ขั้นที่ 1 — ตรวจสอบว่าไฟล์โมเดลพร้อมใช้งาน
ตรวจว่ามีไฟล์เหล่านี้ในโฟลเดอร์โปรเจกต์:
model.tflite
labels.txt (เช่น บรรทัดแรก on, บรรทัดสอง off)
model.tflite
labels.txt (เช่น บรรทัดแรก on, บรรทัดสอง off)
ถ้ายังไม่มี labels.txt ให้สร้างเองได้เลย เช่น:
on
off
✅ ขั้นที่ 2 — แปลงโมเดลสำหรับ ESP32-S3 (Micro Speech Model)
สร้าง ไฟล์: convert_float16.py สำหรับทำ Float16 Quantization
# convert_float16.py
import tensorflow as tf
# 🔹 1. โหลดโมเดลที่ฝึกเสร็จแล้ว (เลือกแหล่งที่ลุงบันทึกไว้)
# ถ้าลุงบันทึกเป็น SavedModel ให้ใช้โค้ดนี้:
model = tf.keras.models.load_model("saved_model")
# หรือถ้าลุงมีเฉพาะไฟล์ .h5 ให้ใช้บรรทัดนี้แทน:
# model = tf.keras.models.load_model("model.h5")
# 🔹 2. สร้าง TFLiteConverter
converter = tf.lite.TFLiteConverter.from_keras_model(model)
# 🔹 3. ตั้งค่าให้บีบด้วย Float16
converter.optimizations = [tf.lite.Optimize.DEFAULT]
converter.target_spec.supported_types = [tf.float16]
# 🔹 4. แปลงโมเดล
tflite_model = converter.convert()
# 🔹 5. บันทึกไฟล์ออกมาเป็น .tflite
with open("model_float16.tflite", "wb") as f:
f.write(tflite_model)
print("✅ แปลงโมเดลสำเร็จ: model_float16.tflite พร้อมใช้งานบน ESP32-S3")

จะได้ไฟล์ใหม่ชื่อ
model_float16.tflite
ทดสอบความถูกต้องก่อนใช้กับ ESP32-S3: สร้างไฟล์ test.py
import tensorflow as tf
interpreter = tf.lite.Interpreter(model_path="model_float16.tflite")
interpreter.allocate_tensors()
print("✅ model_float16.tflite โหลดได้สำเร็จ")
print("Input:", interpreter.get_input_details())
print("Output:", interpreter.get_output_details())

🧩 ส่วนที่ 1 : แปลง model_float16.tflite → model.h (C array)
ESP32 ไม่สามารถอ่านไฟล์ .tflite โดยตรงได้
เราจึงต้องแปลงให้เป็น array ในรูปแบบ C ก่อน
เช่นนี้จะถูกบรรจุในโค้ด Arduino ได้เลย
🔹 วิธีแปลง (บน macOS Terminal)
อยู่ในโฟลเดอร์เดียวกับ model_float16.tflite แล้วพิมพ์:
xxd -i model_float16.tflite > model.h
ผลลัพธ์จะได้ไฟล์ชื่อ model.h
ภายในมีข้อมูลประมาณนี้:

✅ เก็บไฟล์ model.h ไว้ในโฟลเดอร์เดียวกับสเก็ตช์ Arduino (เช่น voice_control/)
💡 ส่วนที่ 2 : โค้ด Arduino สำหรับ ESP32-S3 + INMP441
โค้ดนี้จะทำหน้าที่:
- ใช้ไมค์ INMP441 (ผ่าน I2S)
- อ่านเสียง 1 วินาที
- แปลงเป็น MFCC (เบื้องต้น)
- ส่งเข้าโมเดล TensorFlow Lite
- เปิด/ปิด LED ตามคำสั่งเสียง “เปิดไฟ” หรือ “ปิดไฟ”
📄 ไฟล์: voice_control.ino
💡 ตัวอย่างโค้ด
โค้ดนี้ใช้ไลบรารี TensorFlowLite_ESP32
รันโมเดลเสียง (เปิดไฟ / ปิดไฟ) ที่บีบอัดแล้วในรูป.tflite
ไฟ LED อยู่ที่ GPIO2
#include <Arduino.h>
#include <TensorFlowLite_ESP32.h> // ✅ ใช้เวอร์ชันนี้แทน include ย่อย
#include "model.h"
#define I2S_WS 15 // LRCL
#define I2S_SD 13 // DOUT
#define I2S_SCK 14 // BCLK
#define LED_PIN 2
// ขนาด buffer สำหรับเก็บเสียง
constexpr int SAMPLE_RATE = 16000;
constexpr int SAMPLE_BUFFER_SIZE = 16000; // 1 วินาที
int16_t audio_buffer[SAMPLE_BUFFER_SIZE];
// ขนาดหน่วยความจำสำหรับ TensorFlow Lite
constexpr int kTensorArenaSize = 60 * 1024;
static uint8_t tensor_arena[kTensorArenaSize];
// ตัวแปร TensorFlow Lite
const tflite::Model* model;
tflite::MicroInterpreter* interpreter;
TfLiteTensor* input;
TfLiteTensor* output;
// ฟังก์ชันติดตั้ง I2S เพื่อรับเสียงจากไมค์ INMP441
void i2s_install() {
const i2s_config_t i2s_config = {
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX),
.sample_rate = SAMPLE_RATE,
.bits_per_sample = I2S_BITS_PER_SAMPLE_32BIT,
.channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,
.communication_format = I2S_COMM_FORMAT_I2S,
.intr_alloc_flags = 0,
.dma_buf_count = 4,
.dma_buf_len = 512,
.use_apll = false,
.tx_desc_auto_clear = false,
.fixed_mclk = 0
};
const i2s_pin_config_t pin_config = {
.bck_io_num = I2S_SCK,
.ws_io_num = I2S_WS,
.data_out_num = I2S_PIN_NO_CHANGE,
.data_in_num = I2S_SD
};
i2s_driver_install(I2S_NUM_0, &i2s_config, 0, NULL);
i2s_set_pin(I2S_NUM_0, &pin_config);
i2s_zero_dma_buffer(I2S_NUM_0);
}
void setup() {
Serial.begin(115200);
pinMode(LED_PIN, OUTPUT);
i2s_install();
Serial.println("🎤 เริ่มระบบ TinyML Voice Control (ESP32-S3)");
// โหลดโมเดล
model = tflite::GetModel(model_float16_tflite);
if (model->version() != TFLITE_SCHEMA_VERSION) {
Serial.println("❌ เวอร์ชันโมเดลไม่ตรงกับ TensorFlow Lite Runtime");
while (1);
}
static tflite::MicroMutableOpResolver<10> resolver;
resolver.AddFullyConnected();
resolver.AddReshape();
resolver.AddSoftmax();
resolver.AddConv2D();
resolver.AddDepthwiseConv2D();
static tflite::MicroInterpreter static_interpreter(
model, resolver, tensor_arena, kTensorArenaSize);
interpreter = &static_interpreter;
if (interpreter->AllocateTensors() != kTfLiteOk) {
Serial.println("❌ จัดสรรหน่วยความจำ Tensor ล้มเหลว");
while (1);
}
input = interpreter->input(0);
output = interpreter->output(0);
Serial.println("✅ พร้อมรับเสียงแล้ว...");
}
void loop() {
size_t bytes_read;
i2s_read(I2S_NUM_0, (void*)audio_buffer,
SAMPLE_BUFFER_SIZE * sizeof(int16_t),
&bytes_read, portMAX_DELAY);
// แปลงค่าเสียงเป็น float (-1.0 ถึง 1.0)
for (int i = 0; i < SAMPLE_BUFFER_SIZE && i < input->bytes / sizeof(float); i++) {
input->data.f[i] = (float)audio_buffer[i] / 32768.0f;
}
// รัน inference
if (interpreter->Invoke() != kTfLiteOk) {
Serial.println("❌ Inference ล้มเหลว");
return;
}
float open_score = output->data.f[0];
float close_score = output->data.f[1];
if (open_score > close_score) {
digitalWrite(LED_PIN, HIGH);
Serial.printf("💡 เปิดไฟ (%.2f)\n", open_score);
} else {
digitalWrite(LED_PIN, LOW);
Serial.printf("💡 ปิดไฟ (%.2f)\n", close_score);
}
delay(1000);
}
ติดตั้งผ่าน Arduino Library Manager
- เปิด Arduino IDE
- ไปที่เมนู
Tools → Manage Libraries… - ในช่องค้นหา พิมพ์ว่า
TensorFlow Lite - ให้ติดตั้งอันนี้: 📦 TensorFlowLite_ESP32
🔧 การต่อสาย INMP441 กับ ESP32-S3

| INMP441 | ESP32-S3 Pin | หมายเหตุ |
|---|---|---|
| VCC | 3.3V | |
| GND | GND | |
| WS (LRCL) | GPIO15 | I2S_WS |
| SCK (BCLK) | GPIO14 | I2S_SCK |
| SD | GPIO13 | I2S_SD |
| L/R | GND | ใช้ช่อง Left เท่านั้น |
🧠 สรุปเส้นทางทั้งหมดของโปรเจกต์
| ขั้นตอน | งานที่ทำ | เครื่องมือ |
|---|---|---|
| 1 | ฝึกโมเดลคำว่า “เปิดไฟ / ปิดไฟ” | Python + TensorFlow |
| 2 | ทดสอบโมเดลใน PC (test_tflite.py) | ✅ ผ่านแล้ว |
| 3 | แปลงเป็น model_float16.tflite | convert_float16.py |
| 4 | แปลงเป็น model.h | xxd -i |
| 5 | อัปโหลดโค้ด voice_control.ino | Arduino IDE |
| 6 | ต่อ INMP441 และทดสอบพูด | ESP32-S3 ทำงานจริง! 💡 |