กล่าวได้ว่า ESP32 เป็น chip ที่มีความสามารถมากกว่าไมโครคอนโทรลเลอร์อื่นในตลาดมาก นอกจากจะโดดเด่น การสื่อสารผ่าน wifi แล้ว และ ราคาถูกจนน่าตกใจ ยังมีความสามารถอื่นๆอีก บทความนี้เรารวบรวมความสามารถพื้นฐานของโมดุล ESP32 พร้อมตัวอย่างประกอบครับ

ESP32 Peripherals

โดย ESP32 peripherals จะ ประกอบไปด้วย

  • 18 ช่อง โมดุลแปลง Analog-to-Digital (ADC)
  • 3 ช่อง อินเตอร์เฟส SPI Bus
  • 3 ช่อง อินเตอร์เฟส  UART
  • 2 ช่อง อินเตอร์เฟส  I2C
  • 16 ช่อง เอาต์พุต PWM
  • 2 ช่อง โมดุลแปลง Digital-to-Analog  (DAC)
  • 2 ช่อง อินเตอร์เฟส I2S
  • 10 ช่อง สวิชส์สัมผัส Capacitive
สำหรับความสามารถ ADC และ DAC จะถูกกำหนดไว้ในขาพิเศษ แต่สำหรับ UART, I2C, SPI, PWM และอื่นๆ เราสามารถ กำหนดได้ว่าจะใช้ขาไหนของโมดุล ซึ่งกำหนดได้ด้วยโปรแกรม บอร์ด Node32Lite และ Node32s จะมาพร้อมกับขา Output จำนวนถึง 48 ขา

PINOUT

โดยหัวข้อที่จะมากล่าวมีดังนี้

  • DIGITAL : ข้อจำกัดของ GPIO
  • ANALOG INPUT : การอ่านเซ็นเซอร์แม่เหล็ก (HALL Sensor)
  • ANALOG INPUT : Capacitive touch GPIOs
  • ANALOG INPUT : การใช้เซ็นเซอร์วัดอุณหภูมิ (Temperature Sensor)
  • ANALOG INPUT : โมดุลแปลงค่า Analog to Digital (ADC)
  • ANALOG OUTPUT : โมดุลแปลงค่า Digital to Analog (DAC)
  • DIGITAL INPUT : RTC GPIOs
  • ANALOG OUTPUT : PWM
  • DIGITAL INPUT : Interrupts

ข้อจำกัด

1. ขาที่เป็นได้แค่อินพุตเท่านั้น

ขาที่ 34 ถึง 39 จะเป็น GPIx หรือ เป็นได้แค่ขาอินพุทเท่านั้น, และขาออกนี้ จะไม่มีตัวต้านทานภายใน ที่ทำหน้าที่ Pull-Up หรือ Pull-Down เราต้องหามาใส่เอง และ อีกหน้าที่หนึ่งของขาที่ 36-39 คือ เป็นวงจร ultra low noise pre-amplifier ของ ADC , คือถ้าจะใช้ pre-amplifier ของ ADC นี้ จะต้องต่อตัวเก็บประจุขนาด 270pF เพื่อปรับแต่ง sampling time และ noise ของส่วน pre-ampGPI 34

  • GPI 35
  • GPI 36
  • GPI 37
  • GPI 38
  • GPI 39
Schematic close up of pins 34-39

GPIO 36-39 ถูกต่อด้วย CAP และ ขา 34 และ 35 เป็นได้แค่อินพุทเท่านั้น

2. ขา SPI Flash ขอโมดุล ESP-WROOM-32

ขา GPIO 6 ถึง GPIO 11 เป็นขาที่เอาออกมายังบอร์ด Node32Lite/Node32s ด้วย แต่ขาชุดนี้ ได้ถูกนำไปใช้ เชื่อมต่อกับ SPI FLASH ซึ่งอยู่ในภายใน ESP-WROOM-32 ซึ่งไม่แนะนำให้เอาใช้ มันอาจจะกระทบกระเทือน การใช้งานได้

  • GPIO 6 (SCK/CLK)
  • GPIO 7 (SDO/SD0)
  • GPIO 8 (SDI/SD1)
  • GPIO 9 (SHD/SD2)
  • GPIO 10 (SWP/SD3)
  • GPIO 11 (CSC/CMD)

3. กระแสที่ GPIO ทนได้

  • ตามหัวข้อ “Recommended Operating Conditions” ใน ESP32 datasheet แจ้งไว้ ค่ามากที่สุดที่ทนได้ต่อขา GPIO ประมาณ 40mA

การอ่าน Hall Sensor

ESP32 ยังได้บรรจุตัววัดสัญญาณแม่เหล็กไฟฟ้าด้วย Hall Sensor อันนี้ทางผมคิดว่า เขาคงอยากใส่ตัววัดสัญญาณรบกวน ถ้าที่ไหนที่มี แม่เหล็กไฟฟ้า มากๆ จะผลกับ RF ด้วย ซึงยังไม่เห็นตัวอย่างว่าชาวเน็ทเขาเอาไปทำอะไรกันบ้าง แต่สำหรับวิธีใช้ ดังข้างล่างเลยครับ

//Simple sketch to access the internal hall effect detector on the esp32.
//values can be quite low. 
//Brian Degger / @sctv  

int val = 0;
void setup() {
  Serial.begin(9600);
    }

void loop() {
  // put your main code here, to run repeatedly:
  val = hallRead();
  // print the results to the serial monitor:
  //Serial.print("sensor = ");
  Serial.println(val);//to graph 
}

โค๊ด

โปรแกรมนี้ทำงานแบบง่ายๆ คือ พิมพ์ค่า hall-sensor ออกทาง serial monitor โดยใช้คำสั่ง val = hallRead();

ผลลัพท์

ให้เปิด serial plotter นะครับ

จะเห็นกราฟแสดงผล ตอบสนองตามขั่วแม่เหล็กไฟฟ้า ทั้งเหนือ และ ใต้

Hall Sensor

โมดุลแปลงค่า Analog to Digital (ADC)

ADC ย่อมาจาก Analog to Digital Converter ใช้ในการอ่านค่าแรงดันไฟฟ้า สำหรับ ESP32 สามารถใช้ความละเอียด 12 บิต และใส่แรงดันสูงสุดอยู่ที่ 3.3 V โดยจะขาอินพุตอนาลอก ได้ 18 ช่อง โดย GPIO ที่สามารถใช้งานได้ เป็นดังต่อไปนี้ อีกสองช่องที่หายไป GPIO37 กับ GPIO38 ที่สงวนไว้

สำหรับวิธีการอ่านค่าอนาลอกใช้คำสั่ง int analogRead(PIN)โดย PIN หมายถึงเลขอ้างอิง สามารถดูจาก Pinout คือสามารถอ้างอิงตามขา GPIO หรืออ้างอิงจากหมายเลข ADC ก็ได้ เช่น ถ้าใช้ช่อง ADC0 ในให้ใส่ใช้ analogRead(A0) หรือ ใส่ตามหมายเลข GPIO analogRead(36); ก็ได้เช่นกัน สามารถดูรายละเอียดทั้งหมด ดังนี้

  • ADC0 => GPIO 36 => ADC1_CH0
  • ADC1 => GPIO 37 => ADC1_CH1
  • ADC2 => GPIO 38 => ADC1_CH2
  • ADC3 => GPIO 39 =>ADC1_CH3
  • ADC4 => GPIO 32 => ADC1_CH4
  • ADC5 => GPIO 33 => ADC1_CH5
  • ADC6 => GPIO 34 => ADC1_CH6
  • ADC7 => GPIO 35 => ADC1_CH7
  • ADC10 => GPIO 4  => ADC2_CH0
  • ADC11 => GPIO 0  => ADC2_CH1
  • ADC12 => GPIO 2  => ADC2_CH2
  • ADC13 => GPIO 15  => ADC2_CH3
  • ADC14 => GPIO 13  => ADC2_CH4
  • ADC15 => GPIO 12  => ADC2_CH5
  • ADC16 => GPIO 14  => ADC2_CH6
  • ADC17 => GPIO 27  => ADC2_CH7
  • ADC18 => GPIO 25  => ADC2_CH8
  • ADC19 => GPIO 26  => ADC2_CH9

ช่องสัญญาณอนาลอก มีความละเอียด 12 bit คือค่าแสดงค่าในช่วง 0-4095 โดยค่าแรงดันที่อ่านได้สูงสุดจะจะมาจากแรงดันอ้างอิงที่จ่ายให้โมดุล สมมุติว่าบอร์ดเราต่อแรงดันอ้างอิงเท่ากับ 3.3V ถ้าอ่านค่าได้ 4095 แสดงว่าค่าที่ใส่เข้าไปเท่ากับ 3.3V แต่จากการทดสอบช่องอนาลอกของจะไม่เป็น Linear จะอ่านค่าได้ 4095 ตั้งแต่ยังไม่ถึง 3.3 อาจจะ drop ลงมา 0-0.1V  ถ้าใครใช้แล้วเจอพฤติกรรมแบบนี้ ไม่ต้องสงสัยว่าเสียนะครับ น่าจะเป็นทุกตัว โดยความสัมพันธ์ จะแสดงดังกราฟนี้

ที่มา และ การทดสอบ View source

ตัวอย่างการใช้งาน

void setup() {
  Serial.begin(9600);
}
void loop() {
  int sensorValue = analogRead(A0);
  Serial.println(sensorValue);
  delay(100);
}

โค๊ด

ใน  loop() เราอ่านค่า ADC0 ด้วย analogRead(A0) แล้วพิมพ์ลง Serial Terminal หรือ ใช้จะลองใช้ Serial Plotter ก็ได้นะครับ ให้เอา delay(100)ออก

Capacitive touch GPIOs

ใน ESP32 ได้ใส่เซ็นเซอร์สัมผัสเอาไว้ โดยเซ็นเซอร์นี้อ่านค่าเป็นค่าแบบอนาลอก เหมือนมีการสัมผัส ที่ขา หรือ ที่ PAD ESP32 จะมีเซ็นเซอร์สัมผัสทั้งหมด 10 ช่อง และ ขา touch สามารถใช้ wake-up จาก deep sleep

สำหรับวิธีการอ่านค่าจากเซ็นเซอร์สัมผัส touch ใช้คำสั่ง int touchRead(PIN)โดย PIN หมายถึงเลขอ้างอิง สามารถดูจาก Pinout คือสามารถอ้างอิงตามขา GPIO หรืออ้างอิงจากหมายเลข Touch ก็ได้ เช่น ถ้าใช้ช่อง TOUCH0 ในให้ใส่ใช้ touchRead(T0) หรือ ใส่ตามหมายเลข GPIO touchRead(4); ก็ได้เช่นกัน สามารถดูรายละเอียดทั้งหมด ดังนี้

  • T0 (GPIO 4)
  • T1 (GPIO 0)
  • T2 (GPIO 2)
  • T3 (GPIO 15)
  • T4 (GPIO 13)
  • T5 (GPIO 12)
  • T6 (GPIO 14)
  • T7 (GPIO 27)
  • T8 (GPIO 33)
  • T9 (GPIO 32)

EX1 – ตัวอย่างการใช้งาน

// ESP32 Touch Test
// Just test touch pin - Touch0 is T0 which is on GPIO 4.

void setup()
{
  Serial.begin(9600);
  delay(1000); // give me time to bring up serial monitor
}

void loop()
{
  Serial.println(touchRead(T0));  // get value using T0
}

ผลลัพท์

ผลลัพท์จะเป็นดังวีดีโอนี้ครับ เหมือนมีการสัมผัส ค่าประจุ จะลดลงอย่างรวดเร็ว

Touch Sensor

EX2 – Touch แบบ Interrupt

เราสามารถสร้าง Interrupt ที่เกิดจากการสัมผัสได้ โดยใช้คำสั่ง touchAttachInterrupt( touch_pin , function() , threshold value) จะเป็นการประกาศให้ Event Interrupt ที่เกิดขึ้นผูกกับ function() ได้ มาดูตัวอย่างโค๊ด

const int ledPin =  2;      // the number of the LED pin
const int threshold =  40;      // the number of the LED pin
int ledState = LOW;             // ledState used to set the LED
void setup() {
  // put your setup code here, to run once:
  //pinMode(ledPin, OUTPUT);
  Serial.begin(115200);
  touchAttachInterrupt(T0 , click , threshold );
}
void loop() {    
  Serial.println( touchRead(T0) );
  delay(200);
}
void click () {
  Serial.println("click!");
}

โค๊ด

ตัวอย่างโปรแกรมนี้ จะประกาศใช้  touchAttachInterrupt(T0 , click , threshold ); จะเป็นการผูกฟังก์ชั่น click กับ Touch Event ถ้ามีการสัมผัสที่ T0 หรือ GPIO4 จะเรียกฟังก์ชั่น click() มาทำงาน จะเห็นว่าเราสามารถทำปุ่มเองด้วย โดยสร้าง Pad ทองแดง และ ข้อดีอีกอย่างของ Capacitive touch มันทะลุพลาสติกบางๆได้ ถ้าจะทำ case กันน้ำ 100% ใช้วิธีนี้ ทำได้ไม่ยากเลย

การอ่าน Temperature Sensor ภายใน

ไอซี ESP32 ได้บรรจุเซ็นเซอร์วัดอุณหภูมิมาด้วย ส่วนวิธีการดังตัวอย่าง

#ifdef __cplusplus
extern "C" {
#endif
uint8_t temprature_sens_read();
#ifdef __cplusplus
}
#endif
uint8_t temprature_sens_read();

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

void loop() {
  Serial.print("Temperature: ");
  
  // Convert raw temperature in F to Celsius degrees
  Serial.print((temprature_sens_read() - 32) / 1.8);
  Serial.println(" C");
  delay(5000);
}

โค๊ด

คำสั่งอ่านอุณหภูมิ CPU Core อยู่ใน ESP-IDF อันนี้เป็นตัวอย่าง เอา function จาก ESP-IDF มาใช้งานครับ

PWM

PWM (Pulse Width Modulation) เป็นการส่งความถี่สูง เพื่อทำให้สัญญาณในขาออกมาเป็นอนาล็อก ช่วงเวลาที่เปิด และ ช่วงเวลาที่ปิด รวมกันจะเรียกว่า ความถี่ โดยช่วงเวลาที่เปิด จะเรียกว่า ดิวตี้ไซเคิล (Duty Cycle) ซึ่งมักเรียกเป็นเปอร์เซ็นต์เสมอ

ใน ESP32 จะใช้ Timer ในการสร้างสัญญาณ PWM ซึ่งจะ Timer ถึง 16 ตัว ทำให้ ESP32 สามารถกำหนดความถี่ได้อิสระ ทำให้การใช้คำสั่ง analogWrite() จะใช้ไม่ได้ใน esp32 ไม่มีนะครับเนื่องจากมันไม่มี hardware ของ PWM

โดย PWM แต่ล่ะช่อง สามารถตั้งให้ความถี่ไม่เท่ากันก็ได้ เพราะว่า PWM ของ ESP32 สร้างจาก Timer ซึ่งมาตั้ง 16 ช่อง โดยทุกขาของ ESP32 จะสามารถใช้เป็นขา PWM ได้ (ยกเว้น ขา 34-39 ไม่สามารถสร้าง PWM) โดยความละเอียดได้ตั้งแต่ 8 ถึง 16 บิต

โดย Arduino – ESP32 สร้างไลบารี่ชื่อ LEDC ไว้สำหรับ สร้างความถี่ใน timer แล้ว ผูกกับขา OUTPUT ที่ต้องการให้เป็น PWM

ตัวอย่างการใช้งาน PWM สามารถดูได้จากโค้ดด้านล่างนี้

// fade LED PIN (replace with LED_BUILTIN constant for built-in LED)
#define LED_PIN            2

// use first channel of 16 channels (started from zero)
#define LEDC_CHANNEL_0     0

// use 13 bit precission for LEDC timer
#define LEDC_TIMER_BIT  8

// use 5000 Hz as a LEDC base frequency
#define LEDC_BASE_FREQ     5000

int brightness = 0;    // how bright the LED is
int fadeAmount = 2;    // how many points to fade the LED by

void setup() {
  Serial.begin(115200);
  // Setup timer and attach timer to a led pin
  ledcSetup(LEDC_CHANNEL_0, LEDC_BASE_FREQ, LEDC_TIMER_BIT);
  ledcAttachPin(LED_PIN, LEDC_CHANNEL_0);
}

void loop() {
  // set the brightness on LEDC channel 0
  ledcWrite(LEDC_CHANNEL_0, brightness);

  // change the brightness for next time through the loop:
  brightness = brightness + fadeAmount;

  // reverse the direction of the fading at the ends of the fade:
  if (brightness <= 0 || brightness >= 255) {    
    fadeAmount = -fadeAmount;    
  } 
  if (brightness < 0) brightness = 0;
  if (brightness > 255) brightness = 255;
  
  // wait for 30 milliseconds to see the dimming effect
  Serial.println(brightness);
  delay(30);
}

โค๊ด

Setup()

ใน setup()จะเป็นการ setup timer ตั้งความถี่ และ ตั้งความละเอียด โดยใช้ void ledcSetup(byte channel, double freq, byte resolution_bits) ค่าความละเอียดจะตั้งเป็นจำนวนบิต อย่างเช่น ถ้า กำหนดไว้ที่ 8 บิต ค่าสูงสูดจะเท่ากับ สูตร 2resolution_bits – 1 ฉะนั้น 8 บิต จะสามารถกำหนดได้สูงสุด 28 – 1 = 255 และ อีกคำสั่งที่ใช้ผูก timer เข้ากับ LED PIN ใช้คำสั่ง void ledcAttachPin(int pin, byte channel)

Loop()

ใน loop() จะเป็นการใช้ตัวอย่าง fade ของ arduino คำนวนค่า brightness ให้เพิ่มขึ้นตามเวลา  แล้วเอาค่า brightness ไปขับหลอด LED ผ่านคำสั่ง void ledcWrite(byte channel, int duty);

ผลลัพท์

อันนี้สามารถเปิด serial plotter เพื่อดูค่า กับ สังเกตุที่ความสว่างของ LED ได้

PWM

RTC GPIOs

ความพิเศษอีกอย่างคือ ตัวของ ESP32 เข้า mode deep sleep จะทำงานในโหมดพลังงานต่ำ (Ultra Low Power – ULP) จะมี CPU อีกตัวทำงาน และ สามารถใช้ GPIO เพื่อตื่นเข้ามาทำงานได้ โดย GPIO ที่สามารถต่อ external wakeup ได้ ได้แก่

  • RTC_GPIO0 (GPIO36)
  • RTC_GPIO3 (GPIO39)
  • RTC_GPIO4 (GPIO34)
  • RTC_GPIO5 (GPIO35)
  • RTC_GPIO6 (GPIO25)
  • RTC_GPIO7 (GPIO26)
  • RTC_GPIO8 (GPIO33)
  • RTC_GPIO9 (GPIO32)
  • RTC_GPIO10 (GPIO4)
  • RTC_GPIO11 (GPIO0)
  • RTC_GPIO12 (GPIO2)
  • RTC_GPIO13 (GPIO15)
  • RTC_GPIO14 (GPIO13)
  • RTC_GPIO15 (GPIO12)
  • RTC_GPIO16 (GPIO14)
  • RTC_GPIO17 (GPIO27)

ซึ่งทางผมจะมาเพิ่มเติม เรื่องในนี้ ต่อบทความ deep sleep ซึ่งจะกล่าวในวันหลังนะครับ

โมดุลแปลงค่า Digital to Analog (DAC)

โมดุลนี้ความสามารถคือแปลงค่า digital signal ให้เป็นค่าโวลต์ โดยใน ESP จะมี 2 โมดุล ความละเอียด 8 bits ถูกกำหนดไว้ที่

  • DAC1 (GPIO25)
  • DAC2 (GPIO26)

ซึ่งทางผม คงเอา DAC ไปยกในตัวอย่างที่เกี่ยวกับ ทำ player จะกล่าวในวันหลัง

Interrupts

สำหรับ ESP32 เราสามารถใช้ทุกขาเป็นขารับสัญญาณ Interrupts ได้

#include <Arduino.h>

struct Button {
    const uint8_t PIN;
    uint32_t numberKeyPresses;
    bool pressed;
};

Button button1 = {23, 0, false};
Button button2 = {18, 0, false};

void IRAM_ATTR isr(void* arg) {
    Button* s = static_cast<Button*>(arg);
    s->numberKeyPresses += 1;
    s->pressed = true;
}

void IRAM_ATTR isr() {
    button2.numberKeyPresses += 1;
    button2.pressed = true;
}

void setup() {
    Serial.begin(115200);
    pinMode(button1.PIN, INPUT_PULLUP);
    attachInterruptArg(button1.PIN, isr, &button1, FALLING);
    pinMode(button2.PIN, INPUT_PULLUP);
    attachInterrupt(button2.PIN, isr, FALLING);
}

void loop() {
    if (button1.pressed) {
        Serial.printf("Button 1 has been pressed %u times\n", button1.numberKeyPresses);
        button1.pressed = false;
    }
    if (button2.pressed) {
        Serial.printf("Button 2 has been pressed %u times\n", button2.numberKeyPresses);
        button2.pressed = false;
    }
    static uint32_t lastMillis = 0;
    if (millis() - lastMillis > 10000) {
      lastMillis = millis();
      detachInterrupt(button1.PIN);
    }
}

โค๊ด

สุดท้าย

เอาล่ะครับ ตอนนี้ทุกท่านคงได้ทดลอง feature ใหม่ๆ ของ esp32 ที่เด่นๆ ไปแล้วนะครับ ผมว่าการทดลองเล่นจะทำให้เกิดไอเดีย สักวันพอเราเจอปัญหาอะไรบ้างอย่าง เราอาจจะหยิบ เอาไอเดีย ที่เคยผ่านหู ผ่านตา เอาไปลองบ้าง

chang

ชื่อ “ช้าง” ส่วนมากเขาจะเรียกว่า “พี่ช้าง” แล้ว มีความสนใจทางเทคโนโลยีทางคอมพิวเตอร์ อิเล็กทรอนิกส์ และ หุ่นยนต์ เป็นทั้งนักคิด นักประดิษฐ์ ชอบทดลองเล่น จนเดี่ยวนี้รู้สึกว่าจะเล่นมากกว่ามืออาชีพไปสักแล้ว

Leave a Reply