[TH] Arduino ADC/DAC

บทความนี้แนะนำบอร์ดไมโครคอนโทรลเลอร์ ESP32 และ SAM-D21 มาใช้งานเพื่อเรียนรู้การใช้คำสั่งเกี่ยวกับ ADC (Analog to Digital Converter) และ DAC (Digital to Analog Converter) ด้วยการเชื่อมต่อขา DAC เข้ากับ ADC ดังตัวอย่างภาพที่ 1 (เชื่อม A0 เข้า A1 ของบอร์ด SAM-D21) และ 2 (เชื่อมต่อขา GPIO26 เข้ากับ GPIO36 ของ ESP32) เพื่อส่งข้อมูลที่ไป DAC และให้ ADC อ่านค่ากลับเข้ามา และส่งผลลัพธ์ออกไปที่พอร์ตอนุกรมสำหรับแสดงผลด้วย Serial Plotter ซึ่งตัวอย่างโปรแกรมสั่งส่งข้อมูล 3 แบบ คือ กราฟแบบฟันปลา กราฟแบบสามเหลี่ยม และกราฟรูปคลื่นจากฟังก์ชันไซน์

ภาพที่ 1 บอร์ด SAM-D21 เชื่อมต่อขา A0 เข้ากับ A1
ภาพที่ 2 บอร์ด ESP32 ที่เชื่อมต่อขา 26 เข้ากับ 36

คำสั่ง

คำสั่งสำหรับการใช้งาน ADC และ DAC มีดังต่อไปนี้

การปรับความละเอียดของ ADC

คำสั่งสำหรับปรับความละเอียดของ ADC ของทั้ง ESP32 และ SAM-D21 มีรูปแบบการใช้งานดังนี้ โดย x คือตัวเลขจำนวนบิตในการทำงาน

analogReadResolution( x )

คำสั่งสำหรับปรับความละเอียดของ DAC

คำสั่งปรับความละเอียดของ DAC สำหรับ SAM-D21 ใช้คำสั่งตามรูปแบบต่อไปนี้ โดยกำหนด x เป็นค่าจำนวนเต็มของค่าบิตที่ต้องการใช้งาน ปกติมีค่าเป็น 10

analogWriteResolution(x)

คำสั่งสำหรับอ่านค่า ADC

รูปแบบคำสั่งสำหรับอ่านค่า ADC ของ SAM-D21 และ ESP32 ใช้คำสั่ง analogRead( ) พร้อมระบุหมายเลขขาสำหรับอ่านค่า โดย ESP32 จะสามารถใช้ขา 32,33,34,35,36 และ 39 ส่วน SAM-D21 ใช้ขา A1

ค่าที่อ่านได้ = analogRead( หมายเลขขา )

คำสั่งส่งข้อมูลออก DAC

สำหรับคำสั่งนำออกข้อมูลขนาด 10 บิตของ SAM-D21 นั้นสามารถใช้คำสั่งตามรูปแบบต่อไปนี้โดยกำหนดขาเป็น A0

analogWrite( ขา, ค่านำออก )

ส่วน esp32 ให้เรียกใช้คำสั่ง dacWrite() โดยระบุขาเป็น 25 หรือ 26 ดังนี้

dacWrite( ขา, ค่านำออก )

ตัวอย่างโปรแกรม

ตัวอย่างโปรแกรมวาดกราฟ 3 แบบ คือ กราฟฟันปลา (ตามตัวอย่างภาพที่ 3 และ 4) กราฟสามเหลี่ยม (ภาพที่ 5 และ 6) และกราฟคลื่นไซน์ (ดังภาพที่ 7 และ 8) มีโค้ดการทำงานดังนี้

กราฟฟันปลา

การสร้างกราฟฟันปลาเป็นการส่งค่า 0 ถึงค่าสูงสุดที่เป็นไปได้ของภาค DAC หลังจากนั้นวนรอบส่งใหม่อีกครั้ง โดยโค้ดสำหรับ SAM-D21 เป็นดังนี้ และได้ผลลัพธ์ดังตัวอย่างภาพที่ 3

#include <Arduino.h>

#define pinSpk A0
#define pinMic A1
int adcValue;

void setup() {
  SerialUSB.begin(115200);
  analogReadResolution(12);
  analogWriteResolution(10);

}

void loop() {
  for (int i = 0; i < 1024; i += 4) {
    analogWrite( pinSpk, i);
    adcValue = analogRead(pinMic);
    SerialUSB.println(adcValue);
  }
}
ภาพที่ 3 ตัวอย่างกราฟฟันปลาของบอร์ด SAM-D21

ส่วนโค้ดโปรแกรมสำหรับ esp32 เป็นดังนี้ โดยต้องพึงระวังเรื่องของค่าสูงสุดที่ส่งให้ DAC เนื่องจาก ESP32 มี DAC ที่ละเอียดระดับ 8 บิต สามารถส่งค่าได้ในช่วง 0 ถึง 255

#include <Arduino.h>

#define pinSpk 26
#define pinMic 36
int adcValue;

void setup() {
  Serial.begin(115200);
  analogReadResolution(12);
}

void loop() {
  for (int i = 0; i < 255; i++) {
    dacWrite( pinSpk, i );
    adcValue = analogRead(pinMic);
    Serial.println(adcValue);
  }
}
ภาพที่ 4 ตัวอย่างกราฟฟันปลาจากบอร์ด ESP32

จากภาพที่ 4 จะพบว่า กราฟจาก ADC ของ ESP32 นั้นออกมาไม่เป็นเส้นตรงเมื่อเทียบกับภาพที่ 3 และจากประสบการณ์ในการใช้งานของพวกเราพบว่าภาค ADC ของ ESP32 มีความไวต่อสัญญาณรบกวนค่อนข้างมาก ดังนั้น ต้องหาวิธีการชดเชยค่าที่ผิดพลาด และการป้องกันสัญญาณรบกวน

กราฟสามเหลี่ยม

กราฟสามเหลี่ยมมีหลักการคล้ายกับฟันปลาด้วยการเพิ่มการวนรอบเพื่อส่งค่ามากสุดกลับมาเป็น 0 ดังโค้ดของ SAM-D21 ดังต่อไปนี้ และตัวอย่างผลลัพธ์เป็นดังภาพที่ 5

#include <Arduino.h>

#define pinSpk A0
#define pinMic A1
int adcValue;

void setup() {
  SerialUSB.begin(115200);
  analogReadResolution(12);
  analogWriteResolution(10);

}

void loop() {
  for (int i = 0; i < 1024; i += 8) {
    analogWrite( pinSpk, i);
    adcValue = analogRead(pinMic);
    SerialUSB.println(adcValue);
  }
  for (int i = 1023; i >= 0; i -= 8) {
    analogWrite( pinSpk, i);
    adcValue = analogRead(pinMic);
    SerialUSB.println(adcValue);
  }
}
ภาพที่ 5 ตัวอย่างกราฟสามเหลี่ยมจาก SAM-D21

การเขียนโค้ดสำหรับ esp32 เป็นดังนี้ และตัวอย่างที่ได้เป็นดังภาพที่ 6

#include <Arduino.h>

#define pinSpk 26
#define pinMic 36
int adcValue;

void setup() {
  Serial.begin(115200);
  analogReadResolution(12);
}

void loop() {
  for (int i = 0; i < 255; i++) {
    dacWrite( pinSpk, i );
    adcValue = analogRead(pinMic);
    Serial.println(adcValue);
  }
  for (int i = 254; i >= 0; i--) {
    dacWrite( pinSpk, i );
    adcValue = analogRead(pinMic);
    Serial.println(adcValue);
  }
}
ภาพที่ 6 ตัวอย่างกราฟสามเหลี่ยมจาก ESP32

กราฟคลื่นไซน์

การสร้างกราฟคลื่นรูปไซน์ใช้การกำหนดให้มีการวนรอบเพื่อเพิ่มค่ามุมองศาจาก 0 ไปถึง 359 โดยในแต่ละรอบทำสิ่งต่อไปนี้

  1. แปลงค่าองศาเป็นเรเดียน
  2. คำนวณหาไซน์จากมุมเรเดียน
  3. แปลงค่าทศนิยมให้เป็นจำนวนเต็มในช่วงค่าที่ DAC รองรับ
  4. อ่านค่าจาก ADC
  5. ส่งข้อมูลออกทางพอร์ตอนุกรมให้โปรแกรม Serial Plotter

โค้ดสำหรับ SAM-D21 เป็นดังนี้ และตัวอย่างผลลัพธ์ดังภาพที่ 7

#include <Arduino.h>

#define pinSpk A0
#define pinMic A1
int adcValue;

void setup() {
  SerialUSB.begin(115200);
  analogReadResolution(12);
  analogWriteResolution(10);

}

void loop() {
  int degree = 0;
  float radian = 0.0;
  float sineValue = 0.0;
  int dValue = 0;

  for (degree = 0; degree < 360; degree++) {

    radian = (float)degree * (2.0 * 3.1415926) / 360.0;
    sineValue = sin(radian);
    dValue = (int)((1.0+sineValue)*510.0);
    analogWrite( pinSpk, dValue);
    adcValue = analogRead(pinMic);
    SerialUSB.println(adcValue);
  }
}
ภาพที่ 7 ตัวอย่างผลลัพธ์กราฟรูปไซน์จาก SAM-D21

สำหรับ ESP32 มีโค้ดโปรแกรมเป็นดังนี้ และตัวอย่างผลลัพธ์เป็นดังภาพที่ 8

#include <Arduino.h>

#define pinSpk 26
#define pinMic 36
int adcValue;

void setup() {
  Serial.begin(115200);
  analogReadResolution(12);
}

void loop() {
  int degree = 0;
  float radian = 0.0;
  float sineValue = 0.0;
  int dValue = 0;

  for (degree = 0; degree < 360; degree++) {
    radian = (float)degree * (2.0 * 3.1415926) / 360.0;
    sineValue = sin(radian);
    dValue = (int)((1.0 + sineValue) * 125.0);
    dacWrite( pinSpk, dValue );
    adcValue = analogRead(pinMic);
    Serial.println(adcValue);
  }
}
ภาพที่ 8 ตัวอย่างผลลัพธ์กราฟรูปไซน์จาก ESP32

สรุป

จากบทความนี้จะพบว่า ADC ของ esp32 มีความผดิพลาดบ้างซึ่งต้องอาศัยการชดเชยค่าเพื่อให้ค่าออกมามีความแม่นยำขึ้น และคำสั่ง DAC ของ esp32 ต้องใช้ dacWrite() แทนคำสั่ง analogWrite() ดังนั้น การเลือกใช้ขึ้นอยู่กับปัจจัยหลายด้าน เช่น ราคา ความจำเป็นของการใช้ ADC ที่มีความแม่นยำสูง ปริมาณหน่วยความจำแรม/รอม เป็นต้น สุดท้าย ขอให้สนุกกับการเขียนโปรแกรมครับ

(C) 2020-2021, โดย อ.ดนัย เจษฎาฐิติกุล/อ.จารุต บุศราทิจ
ปรับปรุงเมื่อ 2021-11-19