สวัสดีครับนักศึกษาทุกคน จากบทที่แล้วที่เราอ่านค่า Analog (0-4095) เข้ามาได้แล้ว คำถามคือ… ถ้าเราอยากจะส่งค่า Analog ออกไปบ้างล่ะ? เช่น อยากหรี่ไฟ LED ให้สว่าง 50% หรืออยากขับมอเตอร์พัดลมให้หมุนเบาๆ เราจะทำอย่างไร?

ในเมื่อขา Digital ของ ESP32 มีแค่ “เปิด” (3.3V) กับ “ปิด” (0V) มันจ่ายไฟ 1.5V ออกมาตรงๆ ไม่ได้ … วิศวกรจึงคิดค้นเทคนิคที่เรียกว่า PWM มาหลอกสายตาเราและอุปกรณ์ครับ บทความนี้จะพาทุกคนไปทำความเข้าใจและเขียนโปรแกรมกันครับ


1. หลักการของ PWM (Pulse Width Modulation)

PWM คือเทคนิคการ “สับสวิตช์” เปิด-ปิด ไฟให้เร็วมากๆ จนสายตาเรามองไม่ทันครับ

  • ถ้าเปิดนานกว่าปิด: ไฟเฉลี่ยจะดูสว่างมาก
  • ถ้าปิดนานกว่าเปิด: ไฟเฉลี่ยจะดูสว่างน้อย

เราเรียกสัดส่วนเวลาที่เปิดไฟว่า Duty Cycle (ดิวตี้ ไซเคิล) หน่วยเป็นเปอร์เซ็นต์ (%)

  • Duty Cycle 0%: ปิดตลอดเวลา (ไฟดับสนิท = 0V)
  • Duty Cycle 50%: เปิดครึ่งหนึ่ง ปิดครึ่งหนึ่ง (สว่างปานกลาง ≈ 1.65V)
  • Duty Cycle 100%: เปิดตลอดเวลา (สว่างสุด = 3.3V)

2. LEDC: พระเอกขี่ม้าขาวของ ESP32

ถ้าเป็นบอร์ด Arduino รุ่นเก่า เราจะใช้คำสั่ง analogWrite() ง่ายๆ แต่สำหรับ ESP32 นั้น ภายในชิปมีวงจรพิเศษที่ชื่อว่า LEDC (LED Control) ซึ่งทำงานได้เก่งกว่ามากครับ โดยเราต้องกำหนดค่า 3 อย่างคือ:

  1. Channel (ช่องสัญญาณ): ESP32 มีให้ใช้ 16 ช่อง (0-15) เราเลือกใช้ช่องไหนก็ได้
  2. Frequency (ความถี่): ความเร็วในการสับสวิตช์ ปกติ LED ใช้ 5000 Hz (5000 ครั้งต่อวินาที)
  3. Resolution (ความละเอียด): นิยมใช้ 8-bit (ปรับได้ 0-255 ระดับ) หรือ 10-bit (0-1023 ระดับ)

หมายเหตุ: ใน Arduino ESP32 Core เวอร์ชันใหม่ๆ (v3.0 ขึ้นไป) สามารถใช้ analogWrite() ได้เลย แต่ในการเรียนรู้นี้ ครูขอสอนแบบมาตรฐานเดิม (LEDC) เพื่อให้เข้าใจการตั้งค่าความถี่ซึ่งจำเป็นมากเวลาไปขับมอเตอร์หรือ Servo ครับ


3. ปฏิบัติการ: สร้างไฟหายใจ (Breathing LED)

เราจะเขียนโปรแกรมให้ไฟ LED ค่อยๆ สว่างขึ้นและค่อยๆ มืดลง คล้ายจังหวะการหายใจ

วงจร: ใช้ LED ต่อกับขา GPIO 23 เหมือนเดิม (ต่อผ่าน R 220Ω)

โค้ดโปรแกรม:

C++
const int ledPin = 23;  // ขา LED

// การตั้งค่า PWM
const int freq = 5000;      // ความถี่ 5000 Hz
const int ledChannel = 0;   // ใช้ Channel 0
const int resolution = 8;   // ความละเอียด 8-bit (ค่า 0-255)

void setup() {
  // 1. กำหนดค่าให้ Channel ของ PWM
  ledcSetup(ledChannel, freq, resolution);
  
  // 2. ผูกขา GPIO เข้ากับ Channel นั้น
  ledcAttachPin(ledPin, ledChannel);
}

void loop() {
  // วนลูปเพิ่มความสว่าง (Fade In)
  // i คือค่าความสว่าง (Duty Cycle) ตั้งแต่ 0 ถึง 255
  for (int i = 0; i <= 255; i++) {
    ledcWrite(ledChannel, i); // สั่งงาน PWM
    delay(10); // รอ 10ms เพื่อให้เห็นการเปลี่ยนแปลงทัน
  }

  // วนลูปดลดความสว่าง (Fade Out)
  for (int i = 255; i >= 0; i--) {
    ledcWrite(ledChannel, i);
    delay(10);
  }
}

4. ภารกิจท้าทาย (Challenge): วอลุ่มหรี่ไฟ

เพื่อเป็นการรวบยอดความรู้ บทที่ 6 (Analog Input) และ บทที่ 7 (PWM) เข้าด้วยกัน ให้นักศึกษาลองเขียนโปรแกรม:

  1. อ่านค่าจากตัวต้านทานปรับค่าได้ (Potentiometer) -> ค่าที่ได้คือ 0 – 4095
  2. แปลงค่านั้นให้เป็นค่า PWM (0 – 255) -> ใบ้ให้ว่าใช้ฟังก์ชัน map()
  3. ส่งค่าไปควบคุมความสว่างของ LED
  4. ผลลัพธ์: หมุนวอลุ่มแล้วไฟต้องหรี่-สว่างตามมือหมุน!

เฉลยแนวทาง (Snippet):

C++
int adcVal = analogRead(potPin); // อ่านค่า 0-4095
// แปลงย่านข้อมูล: map(ค่าดิบ, ต่ำสุดเดิม, สูงสุดเดิม, ต่ำสุดใหม่, สูงสุดใหม่)
int pwmVal = map(adcVal, 0, 4095, 0, 255); 
ledcWrite(ledChannel, pwmVal); // ส่งออกไป

สรุปท้ายบท

การเข้าใจ PWM คือพื้นฐานสำคัญมาก เพราะในโลกของ IoT เราไม่ได้แค่สั่งเปิด-ปิด แต่เราต้อง “ควบคุมปริมาณ” ได้ด้วย ไม่ว่าจะเป็นความเร็วโดรน, แขนหุ่นยนต์ หรือความสว่างของไฟ Smart Home

ภารกิจต่อไป: ตอนนี้เรามีพื้นฐาน Hardware และ Coding (Input/Output/Analog/PWM) ครบถ้วนแล้วครับ พร้อมหรือยังที่จะก้าวเข้าสู่หัวใจของวิชานี้? ในบทความหน้า เราจะมาเริ่มต้น “การเชื่อมต่อไร้สาย” โดยเราจะมาดูวิธีทำให้ ESP32 เชื่อมต่อ WiFi บ้าน และดูว่าเราจะเช็ค IP Address ได้อย่างไรครับ


หมายเหตุ:

  • โค้ดที่ให้ไปเป็นแบบ Core v2 (ledcSetup) ซึ่งเป็นมาตรฐานที่ใช้กันแพร่หลายที่สุดในหนังสือเรียนปัจจุบันครับ หากนักศึกษาคนไหนลงโปรแกรมใหม่ล่าสุดแล้ว Code Error ให้เปลี่ยนไปใช้ ledcAttach(ledPin, freq, resolution) แทนครับ
  • Challenge เรื่อง map() เป็นจุดที่เด็ก ปวส. ควรทำได้ครับ เป็นคณิตศาสตร์ประยุกต์ง่ายๆ