เจาะลึกการสร้าง Smart Lock ระดับโปรด้วย ESP32, Bluetooth (BLE) และแอป Android!

DIY Professional Smart Lock ESP32
สร้างระบบสมาร์ทล็อค (Smart Lock) ด้วย ESP32 พร้อมควบคุมผ่าน Bluetooth และแอปพลิเคชัน Android

สวัสดีชาว Maker ทุกคนครับ! ระบบล็อกรหัสผ่านอิเล็กทรอนิกส์ (Electronic Codelock) หรือที่เราเรียกกันว่า Smart Lock เป็นสิ่งที่เข้ามาแทนที่กุญแจแบบเดิมๆ และกลายเป็นมาตรฐานของออฟฟิศ คอนโด และบ้านอัจฉริยะในปัจจุบันไปแล้ว [cite: 1]

ในบทความนี้ เราจะพาคุณขยับสเกลการทำโปรเจกต์ จากของเล่นธรรมดาๆ ให้กลายเป็น "โปรโตไทป์ Smart Lock ระดับโปร" ที่มีความแอดวานซ์ทั้งในฝั่งของฮาร์ดแวร์ ซอฟต์แวร์ และการจัดการพลังงานที่ดีกว่ากลอนประตูทั่วไปในตลาดครับ [cite: 1] โปรเจกต์นี้จะใช้ชิ้นส่วนมาตรฐานที่หาซื้อได้ทั่วไป นำมาประยุกต์เข้ากับกลไกล็อกของจริง เพื่อให้ได้การทดสอบที่สมจริงที่สุด [cite: 1]

ฮาร์ดแวร์ที่ต้องใช้ (Hardware Components) 🛠️

  • บอร์ดพัฒนา ESP32 Development Board [cite: 1]
  • เซนเซอร์วัดกระแสไฟ ACS712 (SparkFun Low Current Sensor Breakout) [cite: 1]
  • ไมโครเซอร์โวมอเตอร์ SG90 [cite: 1]
  • เซนเซอร์แม่เหล็ก Hall Effect Sensor [cite: 1]
  • ไอซีเรกูเลเตอร์ Linear Regulator (7805) [cite: 1]
  • ทรานซิสเตอร์ Power MOSFET N-Channel [cite: 1]
  • แบตเตอรี่ลิเธียมไอออน (Lithium Ion) ชาร์จได้ 2 ก้อน [cite: 1]

💡 Maker's Tip: การเลือกใช้อุปกรณ์ฮาร์ดแวร์ที่แม่นยำอย่างเซนเซอร์วัดกระแสและมอเตอร์ จะช่วยให้ระบบ Smart Lock ของคุณทำงานได้อย่างเสถียร ไม่เกิดอาการมอเตอร์ค้างจนไหม้ครับ!

หากเพื่อนๆ คนไหนกำลังมองหา บอร์ด ESP32, เซนเซอร์ ACS712, เซนเซอร์ Hall Effect หรือเซอร์โวมอเตอร์ เพื่อเอาไปทำโปรเจกต์เจ๋งๆ แบบนี้ แวะมาช้อปปิ้งของแท้พร้อมส่งได้ที่ Globalbyte เลยครับ! ของครบ จบในที่เดียว

แนวคิดและการออกแบบกลไก (Mechanical Design) ⚙️

ในโปรเจกต์นี้ ผู้พัฒนาได้นำ Micro-servo SG90 มาดัดแปลงให้หมุนได้อย่างอิสระต่อเนื่อง (Continuous rotation) เพื่อใช้ขับเคลื่อนตัวล็อก [cite: 1] ข้อดีของวิธีนี้คือมันครอบจักรวาลครับ ถ้าในผลิตภัณฑ์จริงคุณอยากเปลี่ยนไปใช้เซอร์โวเฟืองเหล็กตัวใหญ่ หรือมอเตอร์แบบ Brushless ก็แทบไม่ต้องแก้โค้ดเลย [cite: 1]

นอกจากนี้ยังมีการทำ "ตลับลูกปืน (Bearing)" แบบพิเศษขึ้นมา เพื่อให้มอเตอร์สามารถเลื่อนกุญแจไปซ้าย-ขวาได้จำลองสภาพแวดล้อมที่สมจริงที่สุด [cite: 1] ฮาร์ดแวร์ทั้งหมดนี้ หากเปลี่ยนไปใช้อุปกรณ์แบบ SMD จะสามารถย่อส่วนลงแผ่น PCB เล็กๆ ได้สบายเลยครับ [cite: 1]

เจาะลึกซอฟต์แวร์และการตั้งค่าตัวแปร (Software Variables) 💻

โค้ดในโปรเจกต์นี้ถูกออกแบบมาอย่างชาญฉลาด ให้สามารถปรับแต่งการทำงานของกลอนได้ตั้งแต่ต้นไฟล์ โดยไม่ต้องไปไล่หาในโค้ดบรรทัดลึกๆ ครับ [cite: 1] มาดูตัวแปรสำคัญกัน:

  • OFFSET: ค่าที่ให้เซอร์โวหยุดหมุนเมื่อไม่มีคำสั่ง (ขึ้นอยู่กับตัวต้านทานในเซอร์โวที่ถูกดัดแปลง) [cite: 1]
  • S_U และ S_L: ความเร็วในการหมุนตอนปลดล็อก (Unlocking) และตอนล็อก (Locking) [cite: 1]
  • IGNORE_TIME: เวลาที่จะสั่งให้ปิดเซนเซอร์ Hall ไว้ชั่วคราวตอนเริ่มสั่งงานมอเตอร์ (ถ้าตั้งเป็น 0 มอเตอร์จะไม่ขยับเลย) [cite: 1]
  • SOFT_START_TIME: เวลาหน่วงเพื่อให้มอเตอร์เริ่มขยับ ช่วยป้องกันไม่ให้เซนเซอร์วัดกระแสไฟอ่านค่าไฟกระชาก (Current peaks) ตอนมอเตอร์ออกตัว ไม่งั้นระบบจะนึกว่ามอเตอร์ติดขัด [cite: 1]
  • BRAKE_PULSE: หน่วยไมโครวินาที ใช้ปรับจูนละเอียดให้แม่เหล็กหยุดตรงตำแหน่งเซนเซอร์ HOME พอดีเป๊ะ! [cite: 1]
  • CURRENT_LIMIT: หากกระแสไฟพุ่งเกินค่ามิลลิแอมป์นี้ ระบบจะถือว่า "มอเตอร์ขัดข้อง (Stalled)" และสั่งตัดไฟทันที เพื่อป้องกันมอเตอร์ไหม้ [cite: 1]
  • STALL_SAMPLES: ต้องอ่านค่ากระแสไฟเกินกี่ครั้งติดกัน ถึงจะสั่งตัดไฟ (กันสัญญาณหลอก) [cite: 1]
  • vZero: ใช้เพื่อสอบเทียบ (Calibration) แรงดันแบตเตอรี่ให้แสดงผลถูกต้อง [cite: 1]

การควบคุมด้วยแอป Android ผ่าน BLE 📱

ด้านการควบคุมและกรอกรหัสผ่าน ผู้พัฒนาได้เขียนแอปพลิเคชัน Android ขึ้นมาเองครับ แม้จะเป็นการเขียนแอปครั้งแรกแต่มันก็ตอบโจทย์การทำงานเต็มรูปแบบ: [cite: 1]

  • เมื่อเปิดแอป จะมีหน้าจอให้กรอกรหัส PIN Code เป็นด่านแรกเพื่อความปลอดภัย [cite: 1]
  • เมื่อใส่รหัสถูก แอปจะเชื่อมต่อกับ Smart Lock ผ่าน BLE (Bluetooth Low Energy) [cite: 1]
  • Main Menu: แสดงสถานะว่าล็อกอยู่ (Locked) หรือปลดล็อก (Unlocked) พร้อมโชว์ระดับแบตเตอรี่ด้วยแถบสีต่างๆ ตรงกลางมีปุ่มกลมใหญ่ๆ ไว้สั่งเปิด-ปิดประตู ใช้งานง่ายสุดๆ [cite: 1]
  • Settings: หน้าตั้งค่าสามารถเลือกได้ว่า จะให้กุญแจบิด 1 รอบ หรือ 2 รอบ [cite: 1] แถมยังมีปุ่ม L และ R สำหรับบังคับมอเตอร์แบบแมนนวล เผื่อในกรณีกุญแจติดขัด ปุ่มนี้จะจ่ายไฟสูงสุดและข้ามเซนเซอร์กระแสไฟไปเลยครับ [cite: 1]

ทดสอบการกินไฟ และการจัดการพลังงาน (Power Consumption) 🔋

สิ่งสำคัญที่สุดของ Smart Lock คือ "มันต้องประหยัดแบตเตอรี่" [cite: 1] จากการนำแอมป์มิเตอร์มาวัดตอนเครื่องสแตนด์บาย (Idle) พบว่ากินกระแสไฟอยู่ที่ประมาณ 30 mA [cite: 1] ซึ่งสำหรับกลอนประตูถือว่าเยอะครับ!

โค้ดนี้ใช้โหมด Light Sleep แทน Deep Sleep เพราะมันช่วยให้เชื่อมต่อ BLE ได้แทบจะทันที (ถ้าใช้ Deep Sleep จะใช้เวลาตื่นขึ้นมาเชื่อมต่อบลูทูธนานเป็นสิบวินาที ซึ่งไม่ทันใจคนรอเปิดประตูแน่ๆ) [cite: 1]

ตัวการที่สูบแบตที่สุดตอนนี้คือ Linear Regulator (7805) ที่ทำหน้าที่ลดไฟจาก 8V ให้เหลือ 3.3V สำหรับ ESP32 [cite: 1] ถ้าเราเปลี่ยนไปใช้ Switching Mode Regulator การกินกระแสจะลดลงเหลือแค่ 3 ถึง 5 mA เท่านั้น ซึ่งเป็นระดับที่ต่ำที่สุดเท่าที่ฮาร์ดแวร์ชุดนี้จะทำได้ครับ! [cite: 1]

💡 ข้อเท็จจริงเกี่ยวกับแบตเตอรี่:

แม้จะปรับแต่งสุดๆ แล้ว แบตเตอรี่ของโปรเจกต์นี้ก็อาจอยู่ได้แค่ประมาณ 1 สัปดาห์ครับ [cite: 1] ความลับที่ทำให้กลอนประตูแบรนด์ดังๆ อยู่ได้นาน 3-6 เดือน คือพวกเขาใช้ชิปเฉพาะทางระดับอุตสาหกรรม (อย่างเช่นของ Nordic Semiconductors) ที่กินไฟน้อยกว่า ESP32 หลายพันเท่าเวลามันสแตนด์บาย [cite: 1] แต่นี่คือโปรโตไทป์ที่สมบูรณ์แบบสำหรับการเรียนรู้ตรรกะการทำงาน (Programming logic) ก่อนจะก้าวไปสู่ชิประดับอุตสาหกรรมครับ [cite: 1]

วิดีโอสาธิตการทำงาน (Demo Video) 🎬

Commercial Codelock
ตัวอย่างระบบกลอนประตูดิจิทัล (Codelock) เชิงพาณิชย์
Finished Prototype
ชุดฮาร์ดแวร์ต้นแบบ (Prototype) ที่ถูกประกอบลงบนแผ่นพลาสติก
ACS712 Current Sensor
โมดูลเซนเซอร์วัดกระแสไฟ ACS712 สำหรับตรวจจับมอเตอร์ขัดข้อง
Hall Effect and Magnet
เซนเซอร์แม่เหล็ก Hall Effect ใช้กำหนดจุดหยุดของกุญแจ
Arduino Code Variables
การตั้งค่าตัวแปรในโค้ดที่สามารถปรับเปลี่ยนได้ง่าย
Enter PIN App Screen
หน้าจอใส่รหัสผ่าน (PIN) เพื่อเข้าสู่แอปพลิเคชัน
Main App Screen
หน้าจอหลักสำหรับสั่งปลดล็อก/ล็อก พร้อมแสดงสถานะแบตเตอรี่
Settings App Screen
หน้าจอ Settings สำหรับปรับรอบหมุนและสั่งงานแมนนวล
Power Consumption Measurement
การวัดอัตราการกินกระแสไฟ (Power Consumption) ในโหมดสแตนด์บาย
Linear Regulator
ตัวจ่ายไฟ Linear Regulator ซึ่งเป็นตัวกินพลังงานหลักในโปรโตไทป์นี้
Circuit Schematic
ผังวงจร (Schematics) ของระบบ Smart Lock

โค้ดฉบับสมบูรณ์ (Complete ESP32 Code) 🧑💻

คุณสามารถคัดลอกโค้ด C++ นี้ไปใช้ใน Arduino IDE เพื่อแฟลชลงบอร์ด ESP32 ได้เลยครับ [cite: 1]

C++ (Arduino IDE)
// by mircemk March, 2026

#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>
#include <ESP32Servo.h>
#include "esp_pm.h"      
#include "esp_wifi.h"    

// --- КОНФИГУРАЦИЈА ПИНОВИ (PIN CONFIGURATION) ---
const int PIN_SERVO   = 18; 
const int PIN_HALL    = 19; 
const int PIN_CURRENT = 4;   
const int PIN_BATTERY = 35;   
const int PIN_MOSFET  = 2; // Control power for ACS712 and Servo

// --- ТАЈМЕРИ (TIMERS) ---
unsigned long lastWatchdogTick = 0;
const unsigned long WATCHDOG_TIMEOUT = 300; 
unsigned long lastBatteryReport = 0;
const unsigned long BATTERY_REPORT_INTERVAL = 5000; 

// --- ПАРАМЕТРИ (PARAMETERS) ---
RTC_DATA_ATTR int turnsNeeded = 1; 
int OFFSET = 5;     
const int S_U = 4;      
const int S_L = -4;     
const unsigned long IGNORE_TIME      = 600;   
const unsigned long SOFT_START_TIME  = 400;   
const unsigned long BRAKE_PULSE      = 25000; 
const int CURRENT_LIMIT              = 920;  
const int STALL_SAMPLES              = 3;    
float vZero                          = 2.5;

#define SERVICE_UUID           "12345678-1234-1234-1234-1234567890ab"
#define COMMAND_CHAR_UUID      "abcd1234-5678-1234-5678-1234567890ab"
#define STATUS_CHAR_UUID       "dcba4321-8765-4321-8765-1234567890ab"

Servo myServo;
BLECharacteristic *pStatusCharacteristic;
bool deviceConnected = false;
bool lastConnectionState = false;
volatile bool magnetHit = false;
bool hallEnabled = false;       
bool isManualMode = false;      
unsigned long moveStartTime = 0;
int lastDir = 0; 
int stallCounter = 0; 
int magnetCount = 0; 
String lockStatus = "LOCKED";
String lastKnownValidStatus = "LOCKED";

// --- ПОМОШНИ ФУНКЦИИ (HELPER FUNCTIONS) ---

void IRAM_ATTR onHall() { 
  if (hallEnabled && !isManualMode) magnetHit = true; 
}

void updateStatus(String newStatus) {
  lockStatus = newStatus;
  if (newStatus == "LOCKED" || newStatus == "UNLOCKED") lastKnownValidStatus = newStatus;
  pStatusCharacteristic->setValue(lockStatus.c_str());
  pStatusCharacteristic->notify(); 
}

void stopAction(String finalStatus) {
  int tempDir = lastDir;
  lastDir = 0; moveStartTime = 0; hallEnabled = false; stallCounter = 0; magnetCount = 0;
  
  if (tempDir == 1) myServo.write(S_L + 90 + OFFSET);
  else if (tempDir == -1) myServo.write(S_U + 90 + OFFSET);
  
  if (tempDir != 0) ets_delay_us(BRAKE_PULSE); 
  myServo.write(90 + OFFSET);

  // Power OFF peripheral (Energy saving)
  digitalWrite(PIN_MOSFET, LOW); 
  
  updateStatus(finalStatus);
}

float readBatteryVoltage() {
  long sum = 0;
  const int samples = 40;
  for (int i = 0; i < samples; i++) {
    sum += analogRead(PIN_BATTERY);
    delayMicroseconds(400);
  }
  float adcRaw = (float)sum / (float)samples;
  float vAdc = (adcRaw / 4095.0f) * 3.3f;
  float vBat = vAdc * 3.2f * 1.1f;
  return vBat;
}

void reportBatteryVoltage() {
  float vb = readBatteryVoltage();
  if (deviceConnected) {
    String msg = "BAT:" + String(vb, 2) + "V";
    pStatusCharacteristic->setValue(msg.c_str());
    pStatusCharacteristic->notify();
  }
}

float readCurrent() {
  long sum = 0;
  for (int i = 0; i < 15; i++) sum += analogRead(PIN_CURRENT);
  float voltage = ((float)sum / 15.0f * 3.3f) / 4095.0f;
  float current = (voltage - vZero) / 0.185f;
  return abs(current * 1000.0f);
}

void handleCommand(char cmd) {
  // Before any action, power ON motor and sensor
  if (cmd == 'U' || cmd == 'L' || cmd == '[' || cmd == ']' || cmd == 'M') {
    digitalWrite(PIN_MOSFET, HIGH);
    delay(20); // Pause for voltage stabilization
  }

  if (isManualMode) {
    if (cmd == '[' || cmd == ']') {
      lastWatchdogTick = millis();
      if (cmd == '[') { lastDir = 1; myServo.write(S_U + 90 + OFFSET); }
      else { lastDir = -1; myServo.write(S_L + 90 + OFFSET); }
      return;
    }
    if (cmd == '1') { turnsNeeded = 1; updateStatus("SET_1_TURN"); }
    else if (cmd == '2') { turnsNeeded = 2; updateStatus("SET_2_TURNS"); }
    else if (cmd == 'S') { stopAction("MAN_STOP"); }
    if (cmd == '1' || cmd == '2' || cmd == 'S') { delay(500); updateStatus(lastKnownValidStatus); }
  }

  if (cmd == 'M') { 
    isManualMode = !isManualMode;
    stopAction(isManualMode ? "MANUAL_ON" : "MANUAL_OFF");
    if (!isManualMode) { delay(500); updateStatus(lastKnownValidStatus); }
  }
  else if (!isManualMode) {
    if (cmd == 'U' || cmd == 'L') {
      magnetHit = false; hallEnabled = false; stallCounter = 0; magnetCount = 0;
      moveStartTime = millis(); 
      if (cmd == 'U') { lastDir = 1; myServo.write(S_U + 90 + OFFSET); updateStatus("UNLOCKING"); }
      else { lastDir = -1; myServo.write(S_L + 90 + OFFSET); updateStatus("LOCKING"); }
    }
    else if (cmd == 'S') { stopAction(lockStatus); }
  }
}

class MyServerCallbacks: public BLEServerCallbacks {
  void onConnect(BLEServer* pServer) { deviceConnected = true; }
  void onDisconnect(BLEServer* pServer) { deviceConnected = false; BLEDevice::startAdvertising(); }
};

class CommandCallbacks: public BLECharacteristicCallbacks {
  void onWrite(BLECharacteristic *pCharacteristic) {
    std::string rxValue = pCharacteristic->getValue();
    if (rxValue.length() > 0) handleCommand(rxValue[0]);
  }
};

void setup() {
  // 1. ENERGY OPTIMIZATION (Auto Light Sleep between BLE events)
  esp_wifi_stop(); 
  esp_pm_config_esp32_t pm_config;
  pm_config.max_freq_mhz = 80;    
  pm_config.min_freq_mhz = 10;    
  pm_config.light_sleep_enable = true; 
  esp_pm_configure(&pm_config);

  Serial.begin(115200);

  // 2. MOSFET SETUP (Main switch)
  pinMode(PIN_MOSFET, OUTPUT);
  digitalWrite(PIN_MOSFET, LOW); // Start with peripherals OFF

  // 3. HARDWARE
  ESP32PWM::allocateTimer(0);
  myServo.setPeriodHertz(50);
  myServo.attach(PIN_SERVO, 500, 2400);
  pinMode(PIN_HALL, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(PIN_HALL), onHall, FALLING);
  pinMode(PIN_BATTERY, INPUT);
  analogReadResolution(12);

  // 4. BLE SETUP
  esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT);
  BLEDevice::init("BLE_LOCK_TEST");
  BLEServer *pServer = BLEDevice::createServer();
  pServer->setCallbacks(new MyServerCallbacks());
  BLEService *pService = pServer->createService(SERVICE_UUID);
  
  BLECharacteristic *pCmdChar = pService->createCharacteristic(COMMAND_CHAR_UUID, BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_READ);
  pCmdChar->setCallbacks(new CommandCallbacks());
  
  pStatusCharacteristic = pService->createCharacteristic(STATUS_CHAR_UUID, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY);
  pStatusCharacteristic->addDescriptor(new BLE2902());
  pStatusCharacteristic->setValue(lockStatus.c_str());
  
  pService->start();
  BLEDevice::getAdvertising()->start();
  
  myServo.write(90 + OFFSET);
  Serial.println("System v1.7 Ready - Peripherals OFF");
}

void loop() {
  if (Serial.available() > 0) handleCommand(Serial.read());

  if (deviceConnected && !lastConnectionState) {
    updateStatus(lockStatus);
  }
  lastConnectionState = deviceConnected;

  if (millis() - lastBatteryReport >= BATTERY_REPORT_INTERVAL) {
    lastBatteryReport = millis();
    reportBatteryVoltage();
  }

  if (isManualMode && lastDir != 0) {
    if (millis() - lastWatchdogTick > WATCHDOG_TIMEOUT) stopAction(lastKnownValidStatus);
  }

  if (lastDir != 0 && !isManualMode) {
    if (millis() - moveStartTime > SOFT_START_TIME) {
      float current = readCurrent();
      if (current > CURRENT_LIMIT) {
        stallCounter++;
        if (stallCounter >= STALL_SAMPLES) stopAction("Z");
      } else { if (stallCounter > 0) stallCounter--; }
    }
    if (magnetHit) {
      magnetCount++;
      if (magnetCount >= turnsNeeded) stopAction(lastDir == 1 ? "UNLOCKED" : "LOCKED");
      else { magnetHit = false; hallEnabled = false; moveStartTime = millis(); }
    }
    if (!hallEnabled && moveStartTime != 0 && (millis() - moveStartTime > IGNORE_TIME)) hallEnabled = true;
  }

  delay(10); 
}

อ้างอิงและเรียบเรียงข้อมูลจาก: Globalbyteshop Blog

แหล่งที่มาบทความต้นฉบับ: Hackster.io - Professional grade Smart Lock with ESP32, BLE and Android [cite: 1]

ดาวน์โหลดโค้ดโปรเจกต์ (.ino): Download Code [cite: 1]

ดาวน์โหลดแอป Android (.apk): Download APK [cite: 1]

*คำเตือน: เนื้อหานี้เป็นการสรุปและเรียบเรียงจากบทความเทคโนโลยีต้นฉบับภาษาอังกฤษ ข้อมูลฉบับภาษาไทยอาจมีความคลาดเคลื่อนบางประการจากการตีความหรือย่อเนื้อหา การดัดแปลงมอเตอร์และการต่อวงจรกับแบตเตอรี่ลิเธียมไอออนมีความเสี่ยงต่อการลัดวงจร ผู้สนใจควรศึกษาเรื่องความปลอดภัยทางไฟฟ้า สามารถตรวจสอบรายละเอียดเชิงเทคนิคและโค้ดต้นฉบับแบบเต็มได้ที่ เว็บไซต์ต้นฉบับ ก่อนลงมือทำโปรเจกต์

 

แท็ก


Blog posts

เข้าสู่ระบบ

ลืมรหัสผ่านใช่ไหม?

ยังไม่มีบัญชีใช่ไหม?
สร้างบัญชี