Skip to main content

Basic I/O

· 20 min read
P'Champ

สวัสดีครับ หลังจากที่เราได้ติดตั้ง Software ที่จำเป็นแล้ว และสามารถสั่งการ Board ที่ตนเองได้รับเรียบร้อยแล้วนั้น เนื้อหาหลังจากนี้ไป เราจะได้เริ่มทำความรู้จักอุปกรณ์เริ่มต้นสำหรับการรับข้อมูลจากอุปกรณ์ และส่งออกข้อมูลไปให้อุปกรณ์อย่างง่ายก่อน โดยเริ่มต้นเราจะรับส่งข้อมูลในรูปแบบของดิจิตอล และต่อมาเราจะมาเรียนรู้การรับส่งข้อมูลในข้อมูลแบบอนาล็อคกันครับ

info

เนื่องจากคลิปวิดีโอในครั้งนี้มีเสียงที่เบา น้องๆ อาจใช้ extension ของ google chrome ช่วยเพิ่มเสียงได้ เช่น Volume Master

Intro

โครงสร้าง Project PlatformIO

ก่อนอื่นที่เราจะเริ่มเรียนรู้คำสั่งต่างๆ เราควรที่จะทำความเข้าใจโครงสร้างของโปรเจคเสียก่อน โดยเมื่อเราสร้าง Project ของ PlatformIO ขึ้นมาแล้วเนี่ย ระบบมักจะสร้างไฟล์พื้นฐานประมาณนี้

.
├── include
│   └── README
├── lib
│   └── README
├── platformio.ini
├── src
│   └── main.cpp
└── test
└── README

โดยไฟล์​ที่เราสนใจนั้น มีเพียงสองส่วนคือ

.
├── platformio.ini
├── src
   └── main.cpp

เริ่มจากไฟล์แรก platformio.ini ไฟล์นี้แสดงถึง config environment ต่างๆ เช่น บอร์ดที่ใช้ บอร์ดที่ใช้ ความเร็วในการ upload โค้ดลงบอร์ด ความเร็วของ Serial monitor และไลบราลี่เพิ่มเติมก็จะถูกระบุในไฟล์นี้เช่นเดียวกัน

ต่อมาพระเอกของเราก็คือ src/main.cpp ซึ่งเป็นพื้นที่ที่เราจะพัฒนาในการเขียนโค้ดหลักๆ โดยไฟล์ดังกล่าวมีโครงสร้างดังนี้

src/main.cpp
#include <Arduino.h>
void setup(){

}
void loop(){

}

ลักษณะของโครงสร้างก็เป็นเหมือนภาษา cpp ทั่วไป และมีความคล้ายคลึงกับไฟล์ .ino ของ arduino โดยส่วนหัวก็จะเป็นส่วนของ Header ที่จะ include libraries จากภายนอกเพื่อนำฟังก์ชั่นต่างๆ มาใช้งาน โดยในที่นี้เราได้ include header Arduino.h ซึ่งทำให้เราสามารถใช้คำสั่งพื้นฐานใน Arduino ใน PlatformIO ได้

โดยฟังก์ชั่นหลัก 2 ตัวมีความแตกต่างกันดังนี้

  • void setup() เป็นฟังก์ชั่นเริ่มต้น โดยคำสั่งที่อยู่ภายใต้ฟังก์ชั่นนี้ จะถูกทำเพียงครั้งเดียว
  • void loop() เป็นฟังก์ชั่นที่เริ่มทำงานหลังจาก void setup() โดยคำสั่งที่อยู่ภายใต้ฟังก์ชั่นนี้จะถูกทำไปเรื่อย ไม่มีวันสิ้นสุด (เหมือนถูกครอบด้วย while(1) {})

GPIO

GPIO ย่อมาจาก general purpose input/output เรียกเป็นภาษาไทยง่ายๆว่า พอร์ตเอนกประสงค์ คือเราสามารถควบคุม คอนโทรลให้เป็น ค่าต่างๆได้ และเรายังสามารถกำหนด GPIO เหล่านี้ให้เป็น INPUT หรือ OUTPUT ก็ได้

โดยบอร์ด LOLIN D32 PRO มี pinout ดังนี้

LOLIN D32 PRO Pinout

โดยพอร์ทที่เราเห็นเป็นตัวเลขสีเทา พอร์ทเหล่านั้นสามารนำมาใช้เป็นพอร์ท INPUT และ OUTPUT ได้หมดเลย

I/O Device

Serial Monitor

Serial Monitor เป็นการส่งข้อมูลผ่านทาง Serial ซึ่ง Serial ของเราในที่นี้ก็คือผ่านทาง USB (Universal Serial Bus) ที่เราเชื่อมต่อให้กับบอร์ดของเราอยู่ แล้วให้คอมพิวเตอร์ของเราอ่านข้อมูลที่ออกมาจาก Serial Bus อีกทีครับ โดยการส่งข้อมูลผ่าน Serial นั้น เราจำเป็นต้องกำหนดอัตราการส่งข้อส่ง/รับ ข้อมูล ให้เท่ากัน จึงจะสามารถตีความข้อมูลที่ส่งมาจากผู้ส่งได้อย่างถูกต้่อง โดยการส่งข้อมูลผ่าน Serial Monitor นั้นเป็นการรับส่งข้อมูลที่พื้นฐานที่สุด เพราะไม่ต้องมานั่งต่ออุปกรณ์เพิ่มเติม

ข้อมูลที่ส่งกันระหว่างอุปกรณ์และคอมพิวเตอร์ก็คือ bit นั่นเอง

ต่อไปนี้จะเป็น ตัวอย่างโค้ดสำหรับการส่งข้อมูลผ่าน Serial Monitor ทุกๆ 1 วินาที

src/main.cpp
#include <Arduino.h>
void setup() {
Serial.begin(115200);
}

void loop() {
Serial.println("Hello World");
delay(1000);
}

อธิบายคำสั่งได้ดังนี้

SyntaxDescription
Serial.begin(115200);กำหนดความเร็วในส่งข้อมูลเป็น 115200 symbol/วินาที
Serial.println("Hello World");ส่งข้อความ "Hello World\n" ผ่าน Serial port
delay(1000);หยุดการทำงานชั่วคราวเป็นเวลา 1000 ms

ทำความรู้จักกับ Breadboard

เป็นบอร์ดที่ใช้ทดลองวงจรอิเล็กทรอนิกส์ ลักษณะเป็นแผ่นพลาสติกหนาสีขาว บนแผ่นมีรูเรียงกันจำนวนมาก ภายในรูมีตัวนำไฟฟ้าซึ่งเชื่อมต่อกัน

ลักษณะการเชื่อมต่อของรู Breadboard ขนาดเล็ก

ลักษณะการเชื่อมต่อของรู Breadboard ขนาดใหญ่

ต่อวงจรเพิ่มเติม

ต่อจากนี้ไป เราจะเริ่มติดต่อสื่อสารกับอุปกรณ์ Input Output เพิ่มเติม โดยให้น้องนำอุปกรณ์ที่ได้ทั้งหมด ตาม List ที่ได้ให้ไป ต่อตาม Pin ที่กำหนดไว้ให้ตามวงจรด้านล่าง

caution

โปรดตรวจสอบความถูกต้องของวงจรก่อนที่จะเสียบ USB ให้กับตัวบอร์ด โดยจุดที่ต้องระวังจะมี 2 ส่วน

  • ไฟ 5V (BAT) เข้ากับตัว LDR โดยระวังอย่าให้ไฟ 5V ต่อเข้ากับ GND โดยไม่ผ่านตัวต้านทาน
  • ปุ่มต้องหันให้ถูกด้าน เพราะอาจทำให้เกิด Short Circuit ได้ โดยสามารถกระโดดไปอ่านเนื้อหา Switch ก่อนได้

Example banner

อุปกรณ์Pin ที่เชื่อมต่อ
LED สีแดง26
LED สีเหลือง25
LED สีเขียว33
LDR34
BUTTON (PULLUP)27

โดยตัวต้านทานที่ใช้กับ LED จะใช้ 330Ω (ส้ม-ส้ม-น้ำตาล) และ LDR ใช้ตัวต้านทาน 10kΩ (น้ำตาล-ดำ-ส้ม)

โดยน้องๆ อาจได้ตัวต้านทานที่มีแถบสี 4 สี หรือ 5 สีก็ได้

LED

LED หรือ ไดโอดเปล่งแสง (Light Emitting Diode) คืออุปกรณ์ไฟฟ้าแบบ Passive ที่เมื่อมีกระแสไหลผ่านแล้วจะเปล่งแสงออกมา ด้วยการที่เป็นไดโอด จึงยอมให้กระแสไหลผ่านทางเดียวคือจากขั้ว + ไปยังขั้ว - ถ้าต่อกลับทิศ ไฟจะไม่ติด โดยเราสามารถควบคุม LED ได้โดยการส่งสัญญาณแบบดิจิตอล (HIGH หรือ LOW)

วิธีการดูขั้วของ LED

danger

การต่อ LED ควรต่ออนุกรมกับตัวต้านทานเสมอ เพื่อแบ่งแรงดันไฟฟ้า หากไม่ต่อตัวต้านทาน อาจทำให้ LED ไหม้ได้

โค้ดต่อไปนี้เป็นตัวอย่างแสดงการกระพริบ LED ติด-ดับสลับกัน 0.5 วินาที

src/main.cpp
#include <Arduino.h>
#define GREEN 33
void setup() {
pinMode(GREEN, OUTPUT);
digitalWrite(GREEN,0);
}

void loop() {
digitalWrite(GREEN,1);
delay(500);
digitalWrite(GREEN,0);
delay(500);
}

อธิบายคำสั่งได้ดังนี้

SyntaxDescription
pinMode(pin, mode);ระบุว่า pin ในบอร์ด เป็นโหมด INPUT OUTPUT หรือ INPUT_PULLUP
digitalWrite(pin,0 or 1);สั่ง pin ให้ส่ง logic 0 หรือ 1

Switch

ก่อนที่เราจะมาเรียนรู้การรับสัญญาณของปุ่ม เราควรเรียนรู้ลักษณะของวงจรของปุ่มเสียก่อน

ลักษณะการเชื่อมต่อของขาอุปกรณ์

จะเห็นว่าหากเราหันปุ่มในลักษณะเอาขาแบะไปทางบน-ล่าง ตามรูปนั้น จะเห็นว่า ขาที่ 1-3 และ ขาที่ 2-4 เชื่อมต่อถึงกัน ไม่ว่าจะตอนกดปุ่ม หรือไม่ได้กดปุ่ม ดังนั้น เราจึงใช้แค่ 2 ขา ในการใช้งาน

เช่น ใช้ขา 1 ต่อ GPIO ของบอร์ด และขาที่ 2 เชื่อม ground

หรือจะต่อแบบรูปด้านบนที่ใช้ ขาที่ 1 ต่อที่ของ GPIO27 และ ขาที่ 4 เชื่อมต่อกับ GND ของตัวบอร์ด

caution

โปรดใช้ความระมัดระวังในการต่อ Switch โดยหากใช้ขาที่ Short ถึงกัน ได้แก่ขาที่ 1-3 และ ขาที่ 2-4 หากเชื่อมไฟตรงเข้า GND อาจทำให้อุปกรณ์เสียหายได้

ในการเรียนทั้งหมดของค่าย จะให้ต่อ switch ในรูปแบบ PULL-UP เพื่อลดการใช้ตัวต้านทานที่ไม่จำเป็น โดยน้องสามารถศึกษาเรื่องการต่อ Button ในรูปแบบต่างๆเพิ่มเติมได้ ที่นี่

src/main.cpp
#include <Arduino.h>
#define BUTTON 27
int cnt = 0;
void setup() {
Serial.begin(115200);
Serial.println("");
pinMode(BUTTON, INPUT_PULLUP);
}

void loop() {
if (!digitalRead(BUTTON)){
cnt++;
Serial.println(cnt);
}
}

หมายเหตุ การต่อสวิชต์แบบ PULLUP ค่าขณะที่กดจะเป็น LOW ตอนปล่อยจะเป็น HIGH

ถ้าลองทดลอง ดูค่า cnt จาก Serial Montitor เมื่อทำการกดปุ่ม 1 ครั้ง แต่ทำให้ค่า cnt เพิ่มขึ้นไปเป็นร้อยกว่า

ซึ่งตามความเข้าใจของเราค่าของ cnt ควรเพิ่มแค่ 1 ตามจำนวนครั้งในการกด

ปัญหาดังกล่าวเรียกว่า Bouncing เพราะสวิสต์นั้นภายในเป็นสปริง เมื่อเกิดการกดจะยังเกิดจังหวะที่หน้าสัมผัสของวงจรยังไม่ปิดสนิท ทำให้เกิดจังหวะที่ค่าของสวิสต์สวิงไปสวิงมา ต้องรอจนกว่าวงจรของสวิตส์ปิดสนิท

ดังนั้นเราจึงต้องทำการ Debouncing ปุ่ม โดยทำได้ 2 วิธี

  • แก้ไขโดยใช้ Software โดยใช้การ delay() ในระยะเวลาสั้นๆ เพื่อไปดูสถานะของปุ่มตอนวงจรคงที่แล้ว
  • แก้ไขโดยการต่อตัวเก็บประจุเพิ่มเพื่อสร้าง RC Circuit กรองความถี่ต่ำ (low-pass filter)

สามารถศึกษาเพิ่มเติมได้ ที่นี่

ด้วยเวลาอันจำกัดของเรา เราจะใช้ library ภายนอกเพิ่มเติมที่ชื่อ Bounce2 โดยให้ไปเพิ่มใน PlatformIO ให้เรียบร้อยก่อน

เมื่อทำการเพิ่มไลบราลี่ไปใน Project ของเราแล้วนั้น ให้เปลี่ยนโค้ดการรับสวิสต์เป็นแบบนี้

src/main.cpp
#include <Arduino.h>
#include <Bounce2.h>
#define BUTTON 27
int cnt = 0;
Bounce debouncer = Bounce();
void setup() {
Serial.begin(115200);
Serial.println("BUTTON");
debouncer.attach(BUTTON, INPUT_PULLUP);
debouncer.interval(25);
}

void loop() {
debouncer.update();
if ( debouncer.fell() ) {
cnt++;
Serial.println(cnt);
}
}

จะเห็นว่าค่า cnt จะขึ้นตามจำนวนครั้งในการกดจริงๆ แล้ว

LDR

แอลดีอาร์ (LDR) หรือชื่อเต็ม ๆ คือ Light Dependent Resistor หรือตัวต้านทานที่แปรค่าตามแสง คือ ตัวต้านทานชนิดที่เปลี่ยนสภาพความนำไฟฟ้า (Conductance) ได้เมื่อมีแสงมาตกกระทบ เซนเซอร์ โมดูลนี้ให้สัญญาณเอาต์พุตได้ ทั้งแบบแอนะล็อกที่ช่อง (A0) ซึ่งมีค่าระหว่าง 0 - 4095 และแบบดิจิทัลที่ช่อง (D0) ค่า 0 กับ 1 และจะต้องป้อนใช้ไฟเลี้ยง 3.3-5V ให้กับวงจร

โดยช่อง GPIO ที่จะใช้รับสัญญาณ analog ได้ของบอร์ด LOLIN D32 PRO มีดังนี้

Analog Input Pins6 (VP, VN, 32, 33, 34, 35)
Analog Output Pins2 (25, 26)

เราจะมาลองอ่านค่า LDR โดย LDR จะต้องใช้ร่วมกันกับตัวต้านทาน 10K โอห์ม

src/main.cpp
#include <Arduino.h>
#define LDR 32
void setup() {
Serial.begin(115200);
Serial.println("LDR");
}

void loop() {
Serial.println(analogRead(LDR));
delay(100);
}

เมื่อดู Serial Monitor จะเห็นว่าค่า LDR อยู่ในช่วง 0-4095 (12 bit) โดยหากเราต้องการให้ range ของค่า LDR เป็นค่าอื่นตามต้องการ เราสามารถใช้ฟังก์ชัน map() มาช่วยได้

เปลี่ยนแปลงโค้ดเก่าเล็กน้อย ดังนี้

src/main.cpp
#include <Arduino.h>
#define LDR 32
void setup() {
Serial.begin(115200);
Serial.println("LDR");
}

void loop() {
Serial.println(map(analogRead(LDR),0,4095,0,255));
delay(100);
}

โดยถ้าอยากสลับให้สว่างมาก ค่า x มากได้โดยการสลับค่า 0 กับ 255 ได้

และค่า 0 กับ 4095 ก็ควรเป็นค่าต่ำสุด สูงสุดที่สามารถอ่านได้ ณ ขณะที่ทำอยู่ เพราะเมื่อทำต่างสถานที่ ค่าของ LDR ก็จะเปลี่ยนแปลงตามสภาพของแสง ณ บริเวณนั้นๆ

PWM

PWM หมายถึง Pulse Width Modulation เป็นเทคนิคที่ใช้ในการควบคุมวงจร และ เขียนค่าแบบอะนาล๊อก (Analog) ด้วยพอร์ตดิจิตัล (Digital)

โดยปกติแล้ว พอร์ตดิจิตัล จะสามารถมีได้แค่ 2 สถานะ คือ HIGH (5 โวล์ท) กับ LOW (0 โวล์ท) เท่านั้น จึงทำให้สร้างค่าสัญญาณลอจิคได้เพียง เปิดหรือปิด (1 หรือ 0 , มีไฟหรือไม่มีไฟ) แค่นั้น ซึ่งการใช้เทคนิค PWM นั้น จะเป็นการทำให้พอร์ตดิจิตัล สามารถเขียนค่าได้มากกว่า HIGH หรือ LOW โดย ทำให้สามารถเขียนค่าเป็นแบบอะนาล๊อกได้ (อาจเป็น 0-255 หรือ 0-1023) โดยวิธีการนั้น จะใช้การปรับสถานะของสัญญาณลอจิค HIGH / LOW สลับกันไปมาด้วยคาบเวลาหนึ่งๆ โดยค่าที่ได้นั้นจะขึ้นอยู่กับ สัดส่วนเวลาของสัญญาณในช่วงเวลาที่มีสถานะเป็น HIGH กับช่วงเวลาที่เป็น LOW โดย ช่วงเวลาทั้งหมดที่สัญญาณมีสถานะเป็น HIGH นั้นเราจะเรียกว่าเป็น

"ความกว้าง Pulse (Pulse Width)"

ตัวอย่างการส่งสัญญาณ PWM เพื่อส่งค่า analog ขนาด 8 bit

โดยเราจะนำสัญญาณ PWM มาลองใช้ในการควบคุมความสว่างของหลอด LED

src/main.cpp
#include <Arduino.h>
#define GREEN 33
void setup()
{
ledcSetup(0, 5000, 8);
ledcAttachPin(GREEN, 0);
}
void loop()
{
for (int i = 0; i < 255; i++) {
ledcWrite(0, i);
delay(5);
}
for (int i = 255; i > 0; i--) {
ledcWrite(0, i);
delay(5);
}
delay(1000);
}

โค้ดดังกล่าวทำให้หลอด LED สีเขียวหรี่-ติด แบบไฟหิ่งห้อย

รูปแบบคำอธิบาย
ledcSetup(channel,freq,resolution);ฟังก์ชั่นกำหนดค่าใน Timer
-channel หมายเลขช่องของ Timer ใช้งานได้ 16 ช่องค่า 0-15
-freq ค่าความถี่ที่ใช้สร้างสัญญาณ PWM
-resolution ค่าความละเอียดของ Duty cycle 1-16 bit
เช่นถ้าใช้ 8 bit ค่า Duty cycle ที่กำหนดจะมีค่า 0-255 หมายถึง 0-100%
ledcAttachPin(GPIO, channel);ฟังก์ชั่นกำหนดขาพอร์ตที่ใช้งานกับช่องของ Timer
-GPIO หมายเลขขาพอร์ตที่ใช้งาน
-channel หมายเลขช่องของ Timer ที่เลือกใช้งานกับขาพอร์ตที่ระบุ
ledcWrite(channel, dutycycle);ฟังก์ชั่นสั่งการให้ Timer สร้างสัญญาณ PWM
-channel หมายเลขช่องของ Timer ที่ต้องการสั่งการ
-dutycycle ค่า Duty cycle ที่ต้องการให้ Timer สร้างขึ้น