โปรเจค “TinyML รับคำสั่งเสียง เปิด–ปิดไฟ ด้วย ESP32-S3 + INMP441” เป็นโปรเจคที่สนุกและใช้งานได้จริงมาก เหมาะกับการเรียนรู้ TinyML เบื้องต้นบนไมโครคอนโทรลเลอร์โดยตรง (ไม่ต้องใช้คลาวด์)

TinyML หรือ Tiny Machine Learning ก็คือ ศาสตร์ย่อยแขนงหนึ่งของ Machine Learning (ML) ที่มุ่งเน้นที่จะนำเอาความสามารถของ AI มาใส่ในอุปกรณ์ขนาดเล็ก ที่มีข้อจำกัดทั้งด้านทรัพยากรประมวลผล และพลังงาน อย่างเช่น ไมโครคอนโทรลเลอร์ (MCU) หรือเซนเซอร์อัจฉริยะ ที่ช่วยให้ AI สามารถประมวลผลข้อมูลแบบเรียลไทม์บนอุปกรณ์ที่ฝังตัว (Embedded Systems)

🔧 อุปกรณ์ที่ใช้

  1. ESP32-S3 (เช่นรุ่น ESP32-S3-DevKitC หรือ ESP32-S3-EYE ก็ได้)
  2. ไมค์ INMP441 (I2S Microphone)
  3. LED หรือรีเลย์โมดูล (จำลองหลอดไฟ)
  4. สายจัมเปอร์ / บอร์ดทดลอง
  5. คอมพิวเตอร์พร้อม Arduino IDE หรือ PlatformIO
  6. TensorFlow Lite for Microcontrollers (TFLM)

🧠 แนวคิดของระบบ

  1. INMP441 รับเสียงพูด (“เปิดไฟ”, “ปิดไฟ”)
  2. ESP32-S3 ประมวลผลเสียงแบบเรียลไทม์ → แปลงเป็นสเปกโตรแกรม (MFCC)
  3. โมเดล TinyML จำแนกคำพูดเป็น 3 หมวด:
    • เปิดไฟ (ON)
    • ปิดไฟ (OFF)
    • เงียบ/อื่นๆ (Noise)
  4. สั่งเปิดหรือปิดไฟ (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 วินาทีต่อไฟล์

  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

  1. เปิด Arduino IDE
  2. ไปที่เมนู
    Tools → Manage Libraries…
  3. ในช่องค้นหา พิมพ์ว่า TensorFlow Lite
  4. ให้ติดตั้งอันนี้: 📦 TensorFlowLite_ESP32


🔧 การต่อสาย INMP441 กับ ESP32-S3

INMP441ESP32-S3 Pinหมายเหตุ
VCC3.3V
GNDGND
WS (LRCL)GPIO15I2S_WS
SCK (BCLK)GPIO14I2S_SCK
SDGPIO13I2S_SD
L/RGNDใช้ช่อง Left เท่านั้น

🧠 สรุปเส้นทางทั้งหมดของโปรเจกต์

ขั้นตอนงานที่ทำเครื่องมือ
1ฝึกโมเดลคำว่า “เปิดไฟ / ปิดไฟ”Python + TensorFlow
2ทดสอบโมเดลใน PC (test_tflite.py)✅ ผ่านแล้ว
3แปลงเป็น model_float16.tfliteconvert_float16.py
4แปลงเป็น model.hxxd -i
5อัปโหลดโค้ด voice_control.inoArduino IDE
6ต่อ INMP441 และทดสอบพูดESP32-S3 ทำงานจริง! 💡

Leave a Reply

Your email address will not be published. Required fields are marked *