ESP32 (EP.10) ESP32 กับการใช้วัดความถี่และแสดง parameters

ESP32 นั้นสามารถทำงานเป็นตัวนับความถี่และวิเคราะห์รูปคลื่นแสดง parameter ต่าง ๆ ได้อย่างแม่นยำ โดยใน ESP32 มันจะมี hardware อยู่ตัวหนึ่งที่เรียกว่า MCPWM (Motor Control Pulse Width Modulator)  ซึ่ง MCPWM จะเป็น dedicated hardware ที่แยกออกไปทำงานในฟังก์ชันนี้แบบเดี่ยวเลย มันทำงานโดยอาศัย clock ความเร็วสูง 80 MHz คือ APB (Advanced Peripheral Bus)  และ MCPWM มีคุณสมบัติที่แม่นยำมากในการที่จะ capture  โดยทำงานจาก MCPWM Capture Submodule ด้วยการ capture ช่วงขอบขาขึ้นหรือขอบขาลง (Rising/Falling edge) ของคลื่นสี่เหลื่ยมใด ๆ ก็ได้

ภาพ block diagram แบบเต็มของ MCPWM ในการใช้คุมมอเตอร์ BLDC แบบ 3 เฟส ….ซึ่งใน project นี้จะใช้เพียงส่วน MCPWM Capture Submodule 2 ขาเท่านั้น (CAP0 , CAP1)  โดยรับคลื่นสี่เหลี่ยมเข้าทาง GPIO 11 และใช้ code สั่ง Capture Submodule + GPIO Matrix ให้เทียบเวลาของขอบขาขึ้น / ขอบขาลงของคลื่นสี่เหลี่ยมซึ่งการทำเพียงเท่านี้ก็ได้ข้อมูลครบถ้วนสมบูรณ์แล้วที่จะหาค่าของความถี่ , duty cycle , ON/OFF pulse width


ในโครงสร้างของ project นี้จะใช้ web dashboard โดยการทำงานก็จะเชื่อมต่อกันระหว่าง MCPWM Capture Submodule ส่งค่าฐานเวลาที่มัน capture ได้ไปยัง web dashboard และคำนวณออกมาเป็นค่าความถี่ , duty cycle , ON/OFF pulse width  และด้วยความทรงพลังของ HTML5 canvas ก็สามารถเขียน script ให้มันวาดรูปออกมาเป็นรูปคลื่นสี่เหลี่ยม + แสดง parameter อื่น ๆ ได้เลยตามภาพตัวอย่างด้านล่างนี้ครับ

ในคลิปด้านล่างนี้จะเห็นว่าออกแบบให้ dashboard update การแสดงผล 3 ครั้งต่อ 1 วินาที (ใน code ของ ESP32 จะใช้ library ESPAsyncWebServer + WebSockets เพื่อจะได้ update ตัวเลขต่าง ๆ ได้อิสระจาก main loop) …. และเมื่อทดลองเปลี่ยนความถี่ในหน้า web dashboard ก็จะแสดงค่าความถี่ที่เปลี่ยนไปและแสดงรูปคลื่นในรูปแบบของกราฟิก โดยบอก parameter ทั้งหมดมาให้ด้วย ซึ่งก็เป็นเรื่องที่น่าทึ่งว่า HTML สมัยนี้ทำได้ขนาดนี้เลยครับ  แต่ภาพของลูกคลื่นสี่เหลี่ยมที่เห็นนี้ไม่ใช่การวัดโดยตรงเหมือน Oscilloscope นะครับ  แต่เป็นการแสดงค่าเฉย ๆ

อุปกรณ์ที่ใช้มีเพียงเท่านี้

ESP32 (EP.9) ESP32 กับการใช้เป็น native USB device (HID)

ESP32 S3 (รุ่น S3 นะครับ ไม่ใช่ ESP32 ธรรมดา) มีความสามารถพิเศษอย่างหนึ่งก็คือตัวมันจะมี native USB hardware ใน chip  ก็คือเมื่อเสียบเข้า USB port ของ PC/Notebook  ESP32 S3 สามารถทำตัวเป็นอุปกรณ์ input แบบ USB โดยเป็นได้ทั้ง mouse / keyboard และอื่น ๆ ตามแต่เราจะเขียน code ประยุกต์มันให้เป็นอุปกรณ์ USB

ใน project สาธิตของกระทู้นี้ก็จะเสนอแนวทางการประยุกต์ใช้งานให้ทำงานเป็น USB keyboard และ mouse โดยรับ input ใด ๆ เข้ามาทางขา GPIO และส่งสัญญาณไปที่ PC เหมือนกับเรากดคีย์บอร์ดของคอมพิวเตอร์  ใน project นี้ผมจะสาธิตในรูปแบบของเกมสนุกอันนึงใน google chrome ก็คือเกมไดโน

ท่านที่ใช้ Google Chrome พิมพ์แบบนี้ที่ address bar ได้เลย
chrome://dino

เกมไดโนนี้เวลาเล่นจะต้องกดปุ่มลูกศรขึ้น (ARROW_UP) เพื่อให้ไดโนเสาร์กระโดดข้ามต้นกระบองเพชร โดยผมจะใช้ตัว LDR รับค่าของความสว่างในจอภาพ PC เพื่อนำไปเป็นสัญญาณกระตุ้นที่ขา GPIO ให้ ESP32 S3 สังสัญญาณเสมือนกับกดปุ่ม ARROW_UP เพื่อให้ตัวไดโนเสาร์กระโดดหลบต้นกระบองเพชรครับ

อุปกรณ์ที่ใช้มีเพียงเท่านี้


ขอเริ่มที่ตัว sensor ก่อน ใน project นี้จะใช้ sensor แสงพื้นฐานก็คือตัว LDR รับค่าความสว่างบนจอ PC  ก็คือในเกมนี้จะเป็นพื้นหลังสีดำอยู่แล้วและเมื่อมีสิ่งกีดขวางข้างหน้าไดโนเสาร์เลื่อนมา (ต้นกระบองเพชร) มันจะเป็นสีออกเทา-ขาวซึ่งก็หมายความว่าความสว่างจะเปลี่ยน  LDR ก็จะรับค่านี้ได้และไปสั่งงานให้ ESP32 S3 ส่งสัญญาณผ่าน USB ไปที่คอมพิวเตอร์เหมือนกับการปุ่มลูกศรขึ้น (ARROW_UP) เพื่อให้ไดโนเสาร์กระโดดครับ

ในการใช้งาน LDR เชื่อมต่อกับ ESP32 S3 จะต้องสร้างวงจรเองก็คือใช้ LM393 เป็นตัว comparator  ที่ต้องทำแบบนี้เพื่อจะได้มีสัญญาณ trigger ที่คมมากพอ …. จริง ๆ แล้วสามารถใช้ LDR ต่อกับตัวความต้านทานก็ได้ป้อนเข้า GPIO ก็ได้แต่ค่าแรงดันในการเปลี่ยนจาก logic 0 ไปเป็น logic 1 มันจะไม่ค่อยคมซึ่งมีผลต่อ delay time เป็นอย่างมากทำให้ส่งสัญญาณให้ไดโนเสาร์กระโดดพลาดได้ ดังนั้นจึงต้องใช้ comparator มาช่วยครับวงจรตามภาพล่างนี้

ในการติดตั้งตัว LDR ก็จ่อเข้ากับจอภาพง่าย ๆ อย่างนี้เลย

ผลลัพท์ออกมาแบบนี้


นอกจากนี้แล้วด้วยความที่ ESP32 S3 มันเป็นอุปกรณ์ native USB ดังนั้นจึงสั่งให้ประยุกต์ใช้งานให้สามารถ “พิมพ์” ข้อความไปที่โปรแกรมใด ๆ ก็ได้ใน windows อย่างในคลิปบนนี้เลื่อนไปวินาทีที่ 45 ก็คือพิมพ์ข้อความภาษาอังกฤษ  ซึ่งใน code สามารถเขียน delay time ได้ว่าจะให้พิมพ์ช้าหรือเร็วเท่าไหร่  แต่การประยุกต์ใช้งานลักษณะการพิมพ์เองแบบนี้ผมก็ยังนึกไม่ค่อยออกเท่าไหร่ครับ  ดังนั้นการประยุกต์ใช้งานแบบ sensor ร่วมกับเกมไดโนเสาร์กระโดดแบบข้างบนจะมีประโยชน์มากกว่า

ในการที่จะส่งสัญญาณผ่าน USB กับทำได้ทั้ง Keyboard / Mouse  ซึ่งใน library สามารถสั่งงานได้อย่างง่าย ๆ ว่าให้เลื่อน mouse cursor อย่างไร …. จากในคลิปบน (ที่เวลา 1:02) จะเห็นว่าสามารถวาดเป็นวงกลมได้ซึ่งถ้าเราทำด้วยมือไม่มีทางทำได้กลม perfect แบบนี้แต่ใน code ทำได้ครับ

ก็หวังว่าท่านที่สนใจคงจะได้ไอเดียไปประยุกต์ใช้งานเพื่อให้เป็นเสมือนอุปกรณ์ USB สำหรับคอมพิวเตอร์ได้นะครับ

ESP32 (EP.8) ESP32 ควบคุมอุปกรณ์ไฟฟ้าในห้อง

วันนี้จะขอเสนอเรื่องการใช้งานประยุกต์ใช้ ESP32 กับรีโมทคอนโทรลในการควบคุมอุปกรณ์ไฟฟ้าในห้อง

การใช้งาน ESP32 ในการควบคุมอุปกรณ์ไฟฟ้าต่าง ๆ ในห้องจะมีความสะดวกมาก  เนื่องจาก ESP32 มีขา GPIO จำนวนมาก (สูงสุดถึงประมาณ 15 ขาในการใช้งานแบบ digital ON/OFF)  และรีโมท 1 อันก็จะมีทั้งแป้นตัวเลขและปุ่มอื่นจำนวนมากเช่นกัน  นั่นก็หมายความว่าเราสามารถควบคุมอุปกรณ์ได้นับสิบอย่างที่มีในห้องโดยที่เรานั่งอยู่กับโต๊ะหรือนอนอยู่บนเตียงก็ได้ครับ

project นี้ค่อนข้างจะง่ายมาก …. ขั้นตอนจะมี 2 อย่างเท่านั้น

ขั้นแรกก็คือเราจะต้องหา code ที่ตัวรีโมทคอนโทรลส่งสัญญาณ Infrared ออกมาก่อน  ซึ่งสัญญาณ infrared ที่รีโมทส่งออกมาจะเป็น pulse train แบบภาพนี้

ซึ่งก็แน่นอนว่าทุกปุ่มของรีโมทคอนโทรลจะมี code ต่างกัน  ลักษณะของ code นี้เมื่อรับโดย ESP32 มันจะแปลง pulse train ที่ซับซ้อนออกมาเป็นชุด data ในรูปแบบ HEX (Hexadecimal numbers) จะต้องทำงานร่วมกับ library ครับ …. ซึ่ง library ใหม่สุดที่แนะนำก็คือ “IRremote” library (พัฒนาโดย shirriff / z3t0 / ArminJo)  ตัวล่าสุดนี้ทำงานได้ดีและให้ HEX data สั้นที่สุด  ดูได้จากคลิปด้านล่างนี้เลยครับ

จากคลิปนี้จะเห็นว่าเมื่อเรากดปุ่มตัวเลข 1 2 3 และอื่น ๆ มันจะให้ HEX data ที่สั้นมาก  เราก็เอา HEX data นี้ไปใส่ใน code ของ ESP32 เพื่อที่จะสั่งงาน GPIO ให้ ON / OFF ตามปุ่มของรีโมทที่เรากดได้

Code ทั้งหมด

#include <Arduino.h>
#include <IRremote.hpp>

// Hardware Pin Definitions
const int IR_RECEIVE_PIN = 15;
const int PIN_BTN1 = 21;
const int PIN_BTN2 = 38;
const int PIN_BTN3 = 39;
const int PIN_BTN4 = 47;
const int PIN_BUZZER = 4;

// Captured IR Raw Data Constants
const uint32_t IR_BTN_1 = 0x7308CF;
const uint32_t IR_BTN_2 = 0xB304CF;
const uint32_t IR_BTN_3 = 0x330CCF;
const uint32_t IR_BTN_4 = 0xD302CF;

// Variables to track output states
bool statePin21 = LOW;
bool statePin38 = LOW;
bool statePin39 = LOW;
bool statePin47 = LOW;

// Non-blocking Buzzer Timing Variables
bool buzzerActive = false;
unsigned long buzzerStartTime = 0;
const unsigned long BUZZER_DURATION = 200; // 0.5 second beep duration

void setup() {
  Serial.begin(115200);

  // Initialize GPIO outputs
  pinMode(PIN_BTN1, OUTPUT);
  pinMode(PIN_BTN2, OUTPUT);
  pinMode(PIN_BTN3, OUTPUT);
  pinMode(PIN_BTN4, OUTPUT);
  pinMode(PIN_BUZZER, OUTPUT);

  digitalWrite(PIN_BTN1, LOW);
  digitalWrite(PIN_BTN2, LOW);
  digitalWrite(PIN_BTN3, LOW);
  digitalWrite(PIN_BTN4, LOW);
  digitalWrite(PIN_BUZZER, LOW);

  // Initialize IR Receiver
  IrReceiver.begin(IR_RECEIVE_PIN, ENABLE_LED_FEEDBACK);
  Serial.println("ESP32-S3 IR Control System Ready.");
}

void loop() {
  // 1. Process Incoming IR Commands
  if (IrReceiver.decode()) {
    // Ignore repeat hold signals to prevent accidental fast toggling
    if (!(IrReceiver.decodedIRData.flags & IRDATA_FLAGS_IS_REPEAT)) {
      uint32_t rawData = IrReceiver.decodedIRData.decodedRawData;

      // Trigger Buzzer for 0.5 sec upon receiving any IR command
      digitalWrite(PIN_BUZZER, HIGH);
      buzzerActive = true;
      buzzerStartTime = millis();

      switch (rawData) {
        case IR_BTN_1:
          statePin21 = !statePin21;
          digitalWrite(PIN_BTN1, statePin21);
          Serial.print("Button 1 pressed -> GPIO 21 state: ");
          Serial.println(statePin21 ? "ON" : "OFF");
          break;

        case IR_BTN_2:
          statePin38 = !statePin38;
          digitalWrite(PIN_BTN2, statePin38);
          Serial.print("Button 2 pressed -> GPIO 38 state: ");
          Serial.println(statePin38 ? "ON" : "OFF");
          break;

        case IR_BTN_3:
          statePin39 = !statePin39;
          digitalWrite(PIN_BTN3, statePin39);
          Serial.print("Button 3 pressed -> GPIO 39 state: ");
          Serial.println(statePin39 ? "ON" : "OFF");
          break;

        case IR_BTN_4:
          statePin47 = !statePin47;
          digitalWrite(PIN_BTN4, statePin47);
          Serial.print("Button 4 pressed -> GPIO 47 state: ");
          Serial.println(statePin47 ? "ON" : "OFF");
          break;

        default:
          // Unmapped IR codes
          break;
      }
    }

    IrReceiver.resume(); // Enable receiving the next signal
  }

  // 2. Non-blocking Buzzer Timer (turns OFF active buzzer after 500ms)
  if (buzzerActive && (millis() - buzzerStartTime >= BUZZER_DURATION)) {
    digitalWrite(PIN_BUZZER, LOW);
    buzzerActive = false;
  }
}

อุปกรณ์ที่ใช้มีเพียงเท่านี้  

LED 4 ดวงในภาพต่ออยู่กับ GPIO ของ ESP32 …. ซึ่งในการประยุกต์ใช้งานจริงเราจะต้องต่อวงจรจาก GPIO ต่าง ๆ เพื่อไปควบคุมอุปกรณ์ซึ่งใช้ไฟ 220 โวลต์  วิธีการต่อที่ง่ายที่สุดก็คือใช้ครับรายละเอียดตามภาพด้านล่าง

ในการหา HEX data ของแต่ละปุ่มในการกดรีโมท  อุปกรณ์ที่ใช้มีเพียงเท่านี้

Code ทั้งหมด

#include <Arduino.h>
#include <IRremote.hpp>

// Define the GPIO pin where your IR receiver module (e.g., VS1838B) OUT pin is connected
const int IR_RECEIVE_PIN = 15; 

void setup() {
  // Initialize Serial communication at 115200 baud
  Serial.begin(115200);
  while (!Serial) delay(10); // Wait for Serial to open

  Serial.println("\n--- ESP32 IR Code Scanner ---");
  Serial.println("Point your remote at the receiver and press any button...\n");

  // Start the IR receiver
  // ENABLE_LED_FEEDBACK blinks the ESP32's built-in LED whenever an IR signal is detected
  IrReceiver.begin(IR_RECEIVE_PIN, ENABLE_LED_FEEDBACK);
}

void loop() {
  // Check if an IR signal has been received and decoded
  if (IrReceiver.decode()) {
    
    // Ignore repeat signals (when you hold down a button)
    if (!(IrReceiver.decodedIRData.flags & IRDATA_FLAGS_IS_REPEAT)) {
      
      Serial.print("Protocol: ");
      Serial.print(getProtocolString(IrReceiver.decodedIRData.protocol));
      
      // Print the 8-bit Command in HEX format (Most common for button mapping)
      Serial.print(" | Command (HEX): 0x");
      if (IrReceiver.decodedIRData.command < 0x10) Serial.print("0"); // Leading zero formatting
      Serial.print(IrReceiver.decodedIRData.command, HEX);

      // Print the full Raw Data/Address frame in HEX format for reference
      Serial.print(" | Raw Data: 0x");
      Serial.println(IrReceiver.decodedIRData.decodedRawData, HEX);
    } 
    else {
      Serial.println("[Button Held Down - Repeat Signal]");
    }

    // Prepare the receiver to accept the next IR signal
    IrReceiver.resume(); 
  }
}

ให้ท่านเปลี่ยน HEX data ตามของรีโมทท่าน