Quantcast
Channel: Ayarafun Factory
Viewing all 60 articles
Browse latest View live

Node32Lite : Simple Web Server

$
0
0

บทความนี้ เป็นบทความจุดประกายดีกว่า ไม่อยากจะบอกว่าสอนเขียนโปรแกรมใดๆ เอาว่าเป็นก้าวเดินเล็กๆ สำหรับ คนที่จะหัดเขียนโปรแกรมใช้งานบน Internet และ บนเน็ตเวิร์คไวไฟ (WiFi) กันนะครับ เพราะว่า มันก้าวกระโดดมาจาก Arduino ธรรมดามาพอสมควร ทั้งศัพท์ทางเน็ตเวิร์ค พวกโปรแกรมมิ่งอีก ใครที่มาจับ IoT ช่วงแรก งง แน่นอน

สำหรับบทความนี้ ใช้ได้กับ ทุกบอร์ด ในตระกุล ESP32 นะครับ แต่จะให้ดีใช้ Node32Lite ก้อแล้วกันครับ หลังจากที่ติดตั้ง ESP32 ไปแล้ว หากใครใจร้อนหน่อยลุยเล่นเองตาม Example ได้เลย สำหรับใครที่ไปถูก มาเริ่มตามทางเราก็ได้นะครับ

Simple Web Server

เวปเซิร์ฟเวอร์เป็นเซิร์ฟเวอร์แบบมี Request และ Response โดยหลักการเราเปิด TCP Server ในหมายเลข PORT 80 ก็สามารถทำ Server อย่างง่ายๆได้แล้ว โดยตัว ESP32 จะส่งค่า HTML ที่เป็นหน้าตาของเวปกลับไป และ Server จะรับคำสั่งผ่าน Request มาใช้เปิดปิด LED อีกด้วย

อุปกรณ์ ที่ต้องใช้ กับ วงจรง่ายๆ

ที่จริงแล้วเวปเซิร์ฟเวอร์ ตัวนี้ยังไม่สมบูรณ์ ค่อนข้างลักไก่นะครับ ;-D แต่ตัวอย่างนี้ทำให้เราเข้าใจหลักการของ HTTP Protocol และ การรับส่งข้อมูลของ Browser กับเวปเซิร์ฟเวอร์ ว่าสั่งง่ายๆแบบนี้ล่ะ ซึ่งในลำดับต่อไป จะเอา framework ที่น่าสนใจ ในการทำเวปเซิร์ฟเวอร์มาแนะนำอีกที่ ตอนนี้ หน้าเวปไม่ต้องสวยไปก่อนนะครับ

/*
 WiFi Web Server LED Blink

 A simple web server that lets you blink an LED via the web.
 This sketch will print the IP address of your WiFi Shield (once connected)
 to the Serial monitor. From there, you can open that address in a web browser
 to turn on and off the LED on pin 5.

 If the IP address of your shield is yourAddress:
 http://yourAddress/H turns the LED on
 http://yourAddress/L turns it off

 This example is written for a network using WPA encryption. For
 WEP or WPA, change the Wifi.begin() call accordingly.
 
 Circuit:
 * WiFi shield attached
 * LED attached to pin 5

 created for arduino 25 Nov 2012
 by Tom Igoe

ported for sparkfun esp32 
31.01.2017 by Jan Hendrik Berlin
 
 */

#include <WiFi.h>

const char* ssid     = "ssidHere";
const char* password = "passHere";

#define ledPIN 2

WiFiServer server(80);

void setup()
{
    Serial.begin(115200);
    pinMode(ledPIN, OUTPUT);      // set the LED pin mode

    delay(10);

    // We start by connecting to a WiFi network

    Serial.println();
    Serial.println();
    Serial.print("Connecting to ");
    Serial.println(ssid);

    WiFi.begin(ssid, password);

    while (WiFi.status() != WL_CONNECTED) {
        delay(500);
        Serial.print(".");
    }

    Serial.println("");
    Serial.println("WiFi connected.");
    Serial.println("IP address: ");
    Serial.println(WiFi.localIP());
    
    server.begin();

}

int value = 0;

void loop(){
 WiFiClient client = server.available();   // listen for incoming clients

  if (client) {                             // if you get a client,
    Serial.println("New Client.");           // print a message out the serial port
    String currentLine = "";                // make a String to hold incoming data from the client
    while (client.connected()) {            // loop while the client's connected
      if (client.available()) {             // if there's bytes to read from the client,
        char c = client.read();             // read a byte, then
        Serial.write(c);                    // print it out the serial monitor
        if (c == '\n') {                    // if the byte is a newline character

          // if the current line is blank, you got two newline characters in a row.
          // that's the end of the client HTTP request, so send a response:
          if (currentLine.length() == 0) {
            // HTTP headers always start with a response code (e.g. HTTP/1.1 200 OK)
            // and a content-type so the client knows what's coming, then a blank line:
            client.println("HTTP/1.1 200 OK");
            client.println("Content-type:text/html");
            client.println();

            // the content of the HTTP response follows the header:
            client.print("Click <a href=\"/H\">here</a> to turn the LED on.<br>");
            client.print("Click <a href=\"/L\">here</a> to turn the LED off.<br>");

            // The HTTP response ends with another blank line:
            client.println();
            // break out of the while loop:
            break;
          } else {    // if you got a newline, then clear currentLine:
            currentLine = "";
          }
        } else if (c != '\r') {  // if you got anything else but a carriage return character,
          currentLine += c;      // add it to the end of the currentLine
        }

        // Check to see if the client request was "GET /H" or "GET /L":
        if (currentLine.endsWith("GET /H")) {
          digitalWrite(ledPIN, HIGH);               // GET /H turns the LED on
        }
        if (currentLine.endsWith("GET /L")) {
          digitalWrite(ledPIN, LOW);                // GET /L turns the LED off
        }
      }
    }
    // close the connection:
    client.stop();
    Serial.println("Client Disconnected.");
  }
}

อธิบายโค๊ด

ตั้งค่า ssid กับ password ให้ตรงกับเน็ทเวิร์ก ที่ใช้งานด้วยนะครับ โดยขอย้ำว่า ตัวเล็ก ตัวใหญ่ ช่องว่าง ขีดกลาง ต้องใส่ให้ครบครับ มีผล

const char* ssid     = "ssidHere";
const char* password = "passHere";

ทำการประกาศ WiFiServer server(80); เพื่อใช้คลาส WiFiServer ซึ่งเป็น TCP Server เปิด พอร์ตไว้ที่ 80

WiFiServer server(80);

Setup()

ใน setup() เราจะกำหนดค่าเริ่มต้น เราต้องสั่งให้ esp32 เชื่อมต่อกับ SSID โดยผ่านคำสั่ง WiFi.begin(ssid, password); และ ตรวจสอบ WiFi.status() จนกระทั่งโมดุลมีการเชื่อมต่อสมบูรณ์

WiFi.begin(ssid, password);

while (WiFi.status() != WL_CONNECTED) {
  delay(500);
  Serial.print(".");
}

จากนั้นเริ่มต้นใช้งาน TCP Server ด้วยคำสั่ง Server.begin()

server.begin();

Loop()

ใน loop() เราโปรแกรมสิ่งที่จะเกิดขึ้นเมื่อมี client ใหม่เข้ามาเชื่อมต่อกับเวปเซิร์ฟเวอร์

โดยใน ESP32 จะรอการเชื่อมต่อ ผ่านบรรทัดนี้

WiFiClient client = server.available();   // listen for incoming clients

และเมือมีการ Request จาก Client หรือในที่นี้คือ Browser เราจะบันทึกค่าไว้ในตัวแปร currentLine จนกระทั่ง มี newline เข้ามาจะทำการเริ่มบันทึกใหม่

เมื่อ currentLine หรือ ข้อมูล Request เข้ามา ตัว server ทำการส่ง Response กลับทั้นที่ ซึ่งแสดงในบรรทัด 12 ถึง 22 ซึ่งจะเป็นหน้า Page ที่เราเห็นใน Browser

if (client) {                             // if you get a client,
   Serial.println("New Client.");           // print a message out the serial port
   String currentLine = "";                // make a String to hold incoming data from the client
   while (client.connected()) {            // loop while the client's connected
     if (client.available()) {             // if there's bytes to read from the client,
       char c = client.read();             // read a byte, then
       Serial.write(c);                    // print it out the serial monitor
       if (c == '\n') {                    // if the byte is a newline character

         // if the current line is blank, you got two newline characters in a row.
         // that's the end of the client HTTP request, so send a response:
         if (currentLine.length() == 0) {
           // HTTP headers always start with a response code (e.g. HTTP/1.1 200 OK)
           // and a content-type so the client knows what's coming, then a blank line:
           client.println("HTTP/1.1 200 OK");
           client.println("Content-type:text/html");
           client.println();

           // the content of the HTTP response follows the header:
           client.print("Click <a href=\"/H\">here</a> to turn the LED on.<br>");
           client.print("Click <a href=\"/L\">here</a> to turn the LED off.<br>");

           // The HTTP response ends with another blank line:
           client.println();
           // break out of the while loop:
           break;
         } else {    // if you got a newline, then clear currentLine:
           currentLine = "";
         }
       } else if (c != '\r') {  // if you got anything else but a carriage return character,
         currentLine += c;      // add it to the end of the currentLine
       }

     }
   }

โดยถ้า currentLine มีคำสั่ง “GET /H” จะทำการเปิด LED และ ถ้า “GET /L” จะไปสั่งปิด LED ซึ่งคำสั่งนี้ มันได้มาจาก การคลิกหน้า page ที่เราสร้างขึ้นมาล่ะครับ

// Check to see if the client request was "GET /H" or "GET /L":
if (currentLine.endsWith("GET /H")) {
   digitalWrite(ledPIN, HIGH);               // GET /H turns the LED on
}
if (currentLine.endsWith("GET /L")) {
   digitalWrite(ledPIN, LOW);                // GET /L turns the LED off
}

ถึงตรงนี้ จะเห็นว่า หลักการเบื้องต้น มันง่ายมาก ใช่ไหมครับ

ผลลัพท์

ไฟกระพริบผ่าน Web

Static/FIX IP Address

ทุกครั้งที่มีการ Reboot ระบบเน็กเวิร์ก หมายเลข IP ESP32  อาจจะไม่ใช่หมายเลขเดิมครับ มันจะรันใหม่ทุกครั้งตาม DHCP  ซึ่งผมจะแถมให้อีกนิด ถ้าเราจะ Fix IP หรือจะกำหนดไอพี จะทำอย่างไง

อันนี้ให้เอาตัวอย่างอันที่ 2 มาเพิ่มโค๊ดที่ Header ดังข้างล่าง

โดยเราจะกำหนดให้ ESP32 มี IP 192.168.1.115 เชื่อมต่อกับ Gateway IP 192.168.1.1

IPAddress local_IP(192, 168, 1, 115);
IPAddress gateway(192, 168, 1, 1);
IPAddress subnet(255, 255, 255, 0);
IPAddress primaryDNS(8, 8, 8, 8); //optional
IPAddress secondaryDNS(8, 8, 4, 4); //optional

หมายเลขดังกล่าวสามารถเปลี่ยนได้ และ อาจจะไม่เวิร์กกับ network ที่ท่านทดลองนะครับ อย่างไงลองศึกษาอีกนิด

Setup()

เราจะกำหนด fix ip ด้วยคำสั่ง WiFi.config() 

// Configures static IP address
if (!WiFi.config(local_IP, gateway, subnet, primaryDNS, secondaryDNS)) {
  Serial.println("STA Failed to configure");
}

จากนั้นทุกอย่างจะเหมือนเดิม แค่ตอนนี้ จะ reset Network กี่รอบ ก็ยังได้ IP มาด้วย

/*
 WiFi Web Server LED Blink

 A simple web server that lets you blink an LED via the web.
 This sketch will print the IP address of your WiFi Shield (once connected)
 to the Serial monitor. From there, you can open that address in a web browser
 to turn on and off the LED on pin 5.

 If the IP address of your shield is yourAddress:
 http://yourAddress/H turns the LED on
 http://yourAddress/L turns it off

 This example is written for a network using WPA encryption. For
 WEP or WPA, change the Wifi.begin() call accordingly.
 
 Circuit:
 * WiFi shield attached
 * LED attached to pin 5

 created for arduino 25 Nov 2012
 by Tom Igoe

ported for sparkfun esp32 
31.01.2017 by Jan Hendrik Berlin
 
 */

#include <WiFi.h>

const char* ssid     = "duck Family2.4GHz";
const char* password = "212224236";

IPAddress local_IP(192, 168, 1, 115);
IPAddress gateway(192, 168, 1, 1);
IPAddress subnet(255, 255, 0, 0);
IPAddress primaryDNS(8, 8, 8, 8); //optional
IPAddress secondaryDNS(8, 8, 4, 4); //optional

#define ledPIN 2

WiFiServer server(80);

void setup()
{
    Serial.begin(115200);
    
    if (!WiFi.config(local_IP, gateway, subnet, primaryDNS, secondaryDNS)) {
      Serial.println("STA Failed to configure");
    }

    pinMode(ledPIN, OUTPUT);      // set the LED pin mode

    delay(10);

    // We start by connecting to a WiFi network

    Serial.println();
    Serial.println();
    Serial.print("Connecting to ");
    Serial.println(ssid);

    WiFi.begin(ssid, password);

    while (WiFi.status() != WL_CONNECTED) {
        delay(500);
        Serial.print(".");
    }

    Serial.println("");
    Serial.println("WiFi connected.");
    Serial.println("IP address: ");
    Serial.println(WiFi.localIP());
    
    server.begin();

}

int value = 0;

void loop(){
 WiFiClient client = server.available();   // listen for incoming clients

  if (client) {                             // if you get a client,
    Serial.println("New Client.");           // print a message out the serial port
    String currentLine = "";                // make a String to hold incoming data from the client
    while (client.connected()) {            // loop while the client's connected
      if (client.available()) {             // if there's bytes to read from the client,
        char c = client.read();             // read a byte, then
        Serial.write(c);                    // print it out the serial monitor
        if (c == '\n') {                    // if the byte is a newline character

          // if the current line is blank, you got two newline characters in a row.
          // that's the end of the client HTTP request, so send a response:
          if (currentLine.length() == 0) {
            // HTTP headers always start with a response code (e.g. HTTP/1.1 200 OK)
            // and a content-type so the client knows what's coming, then a blank line:
            client.println("HTTP/1.1 200 OK");
            client.println("Content-type:text/html");
            client.println();

            // the content of the HTTP response follows the header:
            client.print("Click <a href=\"/H\">here</a> to turn the LED on.<br>");
            client.print("Click <a href=\"/L\">here</a> to turn the LED off.<br>");

            // The HTTP response ends with another blank line:
            client.println();
            // break out of the while loop:
            break;
          } else {    // if you got a newline, then clear currentLine:
            currentLine = "";
          }
        } else if (c != '\r') {  // if you got anything else but a carriage return character,
          currentLine += c;      // add it to the end of the currentLine
        }

        // Check to see if the client request was "GET /H" or "GET /L":
        if (currentLine.endsWith("GET /H")) {
          digitalWrite(ledPIN, HIGH);               // GET /H turns the LED on
        }
        if (currentLine.endsWith("GET /L")) {
          digitalWrite(ledPIN, LOW);                // GET /L turns the LED off
        }
      }
    }
    // close the connection:
    client.stop();
    Serial.println("Client Disconnected.");
  }
}

ผลลัพท์

เมื่อเรา Reset ESP32 จะเห็นว่าใน Serial Monitor พิมพ์ ip ว่าเป็น 192.168.1.115 แล้วถ้าพิมพ์เข้าใน browser จะเข้าไปหน้าเวปได้

ผลลัพท์

สุดท้าย

จบแล้วสำหรับบทความแนวฝึกสอนนะครับ Goal จริงๆ จะเขียนในละเอียดๆ ทำไปเรื่อยๆ เผื่อทำหนังสือบ้าง ที่จริงทางผมชอบทำโปรเจคมากกว่า แต่ตอนนี้ ดูในเวป document ดีๆหายากจังครับ ถ้าใครสนใจจะช่วย หรือ จะมาแจมแจ้งได้เลยนะครับ ผมไม่ห่วงเลยแม้แต่น้อย และ ถ้ามีติดปัญหาอะไร หรือ มีข้อสงสัย อีเมล์ แจ้งเข้ามาได้


Node32Lite : การใช้งานอินพุตเอาต์พุตพื้นฐานของ ESP32

$
0
0

กล่าวได้ว่า 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 ที่เด่นๆ ไปแล้วนะครับ ผมว่าการทดลองเล่นจะทำให้เกิดไอเดีย สักวันพอเราเจอปัญหาอะไรบ้างอย่าง เราอาจจะหยิบ เอาไอเดีย ที่เคยผ่านหู ผ่านตา เอาไปลองบ้าง

อุปกรณ์วัดคุณภาพอากาศ ฝุ่น PM2.5 แบบเองก้อได้

$
0
0

สถานการณ์ของฝุ่น PM2.5 ตอนนี้ในกรุงเทพเข้าขั้นวิกฤติ ซึ่งจะเห็นได้ว่าอุปกรณ์ที่เกี่ยวกับกันฝุ่นขายหมดกลิ้ง ทั้งหน้ากาก N95 ทั้งเครื่องกรองอากาศ อันไหนใครว่าดี ขายหมด Out of order อย่างรวดเร็ว เลยมันเป็นบทความพิเศษของวันนี้ครับ คือทางผมอยากได้เครื่องวัด PM2.5  ตอนนี้มีลูกเล็ก เห็นท้องฟ้าใสๆ ไม่แน่ใจว่า ฝุ่นจะเป็นอย่างไง ซึ่งตอนนี้ ก็ สั่งซื้อเครื่องวัดไปแล้วก่อนตรุษจีน แต่ของยังมาไม่ถึง ผมเลยต้องแสดงฝีมือทำเองใช้ชั่วคราวก่อน

ช่วงนี้ แม้แต่มิเตอร์วัดฝุ่น ยังขึ้นราคา เคยเห็นพันกว่า ตอนนี้กระโดดไป สามพันแล้ว แต่ผมโชคดี ที่เคยมีไว้หมดแล้วครับ โดยอุปกรณ์นี้จะใช้ Dust Sensor ของ Plantower รุ่น PMSA003 เป็นแบบ Laser Particle Sensor วัดการกระเจิงความแสงที่ผ่านฝุ่น โดยเซ็นเซอร์ตัวนี้ผมได้มานานแล้ว จาก aliexpress ซื้อตอนนี้คงหาไม่ได้แน่ๆ มีตัวไหนก้อใช้ตัวนั้นล่ะครับ โดยผมให้มันแสดงค่า PM1.0 PM2.5 PM10 และ ค่า AQI(PM2.5) ดัชนี AQI ผมใช้เกณท์ไทยมาแสดงผลนะครับ และ มันยังเปลี่ยนสีตามเลขดัชนีอีกด้วยครับ เลยทำให้ดูง่าย ขึ้นสีแดงเมื่อไร กลับบ้านเลยดีกว่า

  

ทำความเข้าใจ PM2.5

  1. ฝุ่นละออง PM 2.5 (Particle Matter Smaller Than 2.5 Micron) คือ ฝุ่นละอองที่มีขนาดเล็กมาก มองไม่เห็น คือเล็กกว่า 2.5 ไมครอน (ไมโครเมตร) หรือ เล็กกว่า 3% ของเส้นผ่านศูนย์กลางเส้นผมเสียอีก ในเมืองใหญ่นั้น สาเหตุหลักๆ คือ จากการเผาไหม้ของเครื่องยนต์ และจากการก่อสร้าง แต่ ฝุ่นยิ่งเล็กแทนที่มันจะตกลงสู่พื้น มันกลับยิ่งแขวนลอยอยู่ในอากาศนานยิ่งขึ้น
  2. ฝุ่น PM 2.5 มีอันตรายต่อสุขภาพอย่างชัดเจน เพราะการที่มันเล็กมาก ทำให้มันสามารถผ่านทางเดินหายใจสู่ปอดและสร้างปัญหากับหลอดเลือดได้ง่ายขึ้นเยอะ (พวกที่มีขนาดใหญ่ มักจะโดนดักเอาไว้ตั้งแต่ด้วยขนจมูก และด้วยเมือกและขนโบกตามช่องทางเดินหายใจ) และจะไปเพิ่มความเสี่ยงต่อการเกิดโรคหัวใจและโรคทางเดินหายใจ เพราะมันสามารถทะลุทะลวงผ่านปอดเข้าสู่เส้นเลือดฝอยที่หล่อเลี้ยงอวัยวะทุกส่วนของร่างกาย รวมทั้งสมองและหัวใจ ทำให้เกิดโรคมะเร็ง โรคหัวใจ โรคทางสมอง ฯลฯ แม้กระทั่งทารกในครรภ์ที่แม่สัมผัสกับอนุภาค PM2.5 ก็จะทำให้เด็กเมื่อโตขึ้นมีโอกาสเสี่ยงต่อโรคต่าง ๆ ดังกล่าว และยังมีผลต่อระดับสติปัญญาของเด็กอีกตลอดชีวิต
  3. ฝุ่นละออง PM10 เป็นอนุภาคที่มีขนาดใหญ่กว่า มองเห็นด้วยตาเปล่าได้ ถือเป็นอีกมลพิษ

M5Stack PM2.5 Meter

สำหรับโปรเจคนี้ ผมเอา M5 Stack มาใช้นะครับ คิดว่า งานมันน่าจะเหมาะกับอะไรที่มีจอสี อยู่แล้ว หยิบใช้เลย ส่วน Sensor วัดฝุ่น ยี่ห้อ Plantower เป็นแบบ Laser Particle Sensor

  1. M5Stack Basic  จาก Gravitech หรือ inex หรือจะสั่งจาก aliexpress ก้อได้ ฮ่าๆ ถ้ารอได้ ข้อด๊ มันมีจอ มีแบตพร้อม มาพร้อมกับเคสสวยๆ ราคาประหยัด

2. PM2.5 Sensor ผมใช้ Plantower  Model: PMSA003 แต่ในไทย ใช้ PMS7003 ของ Gravitech แทนก็ได้นะครับ สิ่งที่ต่างกัน ขนาด กับ ช่องเข้า กับ ช่องลมออก

ในโอกาศหน้าจะทำในรูปแบบที่ราคาถูกลงมานะครับ หลายคนอาจจะสงสัยว่า Sensor ตัวนี้มันน่าจะเชื่อถือได้ไหม ซึ่งจากข้อมูลเพิ่มเติมจากเอกสารของ การศึกษาเซ็นเซอร์หลักการทางแสงราคาถูกสำหรับงานตรวจวัดฝุ่นละอองในอากาศ พบว่า Sensor ของ Plantower มีความ reliability เชื่อถือได้ คงเส้นคงวา แต่จะเอาให้ถูกแป๊ะเลย ต้องเอาไปสอบเทียบครับ แต่ถึงมันไม่ถูกต้อง 100% เราเอามาใช้ดูแนวโน้มล่ะครับ

วิธีการอ่านค่าจาก Plantower นะครับ

โมดุล Plantower  แค่จ่ายไฟให้โมดุล ค่าที่อ่านได้จะออกมาทาง Uart โดยความยาว Package ยาว 32 Byte ประกอบไปด้วยปริมาณฝุ่น PM1.0 , PM2.5 , PM10

/*
PMS1003, PMS5003, PMS7003:
  32 byte long messages via UART 9600 8N1 (3.3V TTL).
DATA(MSB,LSB): Message header (4 bytes), 2 pairs of bytes (MSB,LSB)
  -1(  1,  2): Begin message       (hex:424D, ASCII 'BM')
   0(  3,  4): Message body length (hex:001C, decimal 28)
DATA(MSB,LSB): Message body (28 bytes), 14 pairs of bytes (MSB,LSB)
   1(  5,  6): PM 1.0 [ug/m3] (TSI standard)
   2(  7,  8): PM 2.5 [ug/m3] (TSI standard)
   3(  9, 10): PM 10. [ug/m3] (TSI standard)
   4( 11, 12): PM 1.0 [ug/m3] (std. atmosphere)
   5( 13, 14): PM 2.5 [ug/m3] (std. atmosphere)
   6( 15, 16): PM 10. [ug/m3] (std. atmosphere)
   7( 17, 18): num. particles with diameter > 0.3 um in 100 cm3 of air
   8( 19, 19): num. particles with diameter > 0.5 um in 100 cm3 of air
   9( 21, 22): num. particles with diameter > 1.0 um in 100 cm3 of air
  10( 23, 24): num. particles with diameter > 2.5 um in 100 cm3 of air
  11( 25, 26): num. particles with diameter > 5.0 um in 100 cm3 of air
  12( 27, 28): num. particles with diameter > 10. um in 100 cm3 of air
  13( 29, 30): Reserved
  14( 31, 32): cksum=byte01+..+byte30
*/

โดยอุปกรณ์นี้ เรียกค่า std. atmosphere เป็น Byte ที่ 11-16 มาใช้นะครับ

Hardware

โมดุลที่ผมได้ว่าจะมาสายต่อดังภาพครับ ให้หัน Connector ไปตามภาพนะครับ แล้วใช้

  • สายที่ 1 –  VCC  -> (+5V)
  • สายที่ 2 – GND ->  (GND)
  • สายที่ 3 – RxD -> GPIO16
  • สายที่ 4 – TxD -> GPIO17 (ไม่จำเป็น)

Index AQI

โดยค่า  ดัชนีคุณภาพอากาศ ผมเอามาจาก Air4Thai ซึ่งเขาจะระบุว่า การวัด ดัชนีคุณภาพอากาศ ซึ่งค่าวัดมลพิษทั้งหมด 6 ชนิด วิธีการเทียบดูว่าถ้าใดมีผลทำให้ AQI มากสุด ให้แสดงค่านั้นๆ แปลว่า มิเตอร์นี้ไม่ได้ครอบคลุมทุกมลพิษนะครับ วัดแค่ PM2.5 โดยมลพิษที่เอามาประเมิน มีด้วยกัน 6 ชนิด

  • ฝุ่นละอองขนาดไม่เกิน 2.5 ไมครอน (PM2.5) เป็นฝุ่นที่มีเส้นผ่านศูนย์กลางไม่เกิน 2.5 ไมครอน เกิดจากการเผาไหม้ทั้งจากยานพาหนะ การเผาวัสดุการเกษตร ไฟป่า และกระบวนการอุตสาหกรรม สามารถเข้าไปถึงถุงลมในปอดได้ เป็นผลทําให้เกิดโรคในระบบทางเดินหายใจ และโรคปอดต่างๆ หากได้รับในปริมาณมากหรือเป็นเวลานานจะสะสมในเนื้อเยื่อปอด ทําให้การทํางานของปอดเสื่อมประสิทธิภาพลง ทําให้หลอดลมอักเสบ มีอาการหอบหืด
  • ฝุ่นละอองขนาดไม่เกิน 10 ไมครอน (PM10) เป็นฝุ่นที่มีขนาดเส้นผ่านศูนย์กลางไม่เกิน 10 ไมครอน เกิดจากการเผาไหม้เชื้อเพลิง การเผาในที่โล่ง กระบวนการอุตสาหกรรม การบด การโม่ หรือการทําให้เป็นผงจากการก่อสร้าง ส่งผลกระทบต่อสุขภาพเนื่องจากเมื่อหายใจเข้าไปสามารถเข้าไปสะสมในระบบทางเดินหายใจ
  • ก๊าซโอโซน (O3) เป็นก๊าซที่ไม่มีสีหรือมีสีฟ้าอ่อน มีกลิ่นฉุน ละลายน้ำได้เล็กน้อย เกิดขึ้นได้ทั้งในระดับบรรยากาศชั้นที่สูงจากผิวโลก และระดับชั้นบรรยากาศผิวโลกที่ใกล้พื้นดิน ก๊าซโอโซนที่เป็นสารมลพิษทางอากาศคือก๊าซโอโซนในชั้นบรรยากาศผิวโลก เกิดจากปฏิกิริยาระหว่างก๊าซออกไซด์ของไนโตรเจน และสารประกอบอินทรีย์ระเหยง่าย โดยมีแสงแดดเป็นตัวเร่งปฏิกิริยา มีผลกระทบต่อสุขภาพ โดยก่อให้เกิดการระคายเคืองตาและระคายเคืองต่อระบบทางเดินหายใจและเยื่อบุต่างๆ ความสามารถในการทำงานของปอดลดลง เหนื่อยเร็ว โดยเฉพาะในเด็ก คนชรา และคนที่เป็นโรคปอดเรื้อรัง
  • ก๊าซคาร์บอนมอนอกไซด์ (CO) เป็นก๊าซที่ไม่มีสี กลิ่น และรส เกิดจากการเผาไหม้ที่ไม่สมบูรณ์ของเชื้อเพลิงที่มีคาร์บอนเป็นองค์ประกอบ ก๊าซนี้สามารถสะสมอยู่ในร่างกายได้โดยจะไปรวมตัวกับฮีโมโกลบินในเม็ดเลือดแดงได้ดีกว่าออกซิเจนประมาณ 200-250 เท่า เมื่อหายใจเข้าไปทำให้ก๊าซชนิดนี้จะไปแย่งจับกับฮีโมโกลบินในเลือด เกิดเป็นคาร์บอกซีฮีโมโกลบิน (CoHb) ทำให้การลำเลียงออกซิเจนไปสู่เซลล์ต่างๆ ของร่างกายลดน้อยลง ส่งผลให้ร่างกายเกิดอาการอ่อนเพลีย และหัวใจทำงานหนักขึ้น
  • ก๊าซไนโตรเจนไดออกไซด์ (NO2) เป็นก๊าซที่ไม่มีสีและกลิ่น ละลายน้ำได้เล็กน้อย มีอยู่ทั่วไปในธรรมชาติ หรือเกิดจากการกระทำของมนุษย์ เช่น การเผาไหม้เชื้อเพลิงต่างๆ อุตสาหกรรมบางชนิด เป็นต้น ก๊าซนี้มีผลต่อระบบการมองเห็นและผู้ที่มีอาการหอบหืดหรือ โรคเกี่ยวกับทางเดินหายใจ
  • ก๊าซซัลเฟอร์ไดออกไซด์ (SO2) เป็นก๊าซที่ไม่มีสี หรืออาจมีสีเหลืองอ่อนๆ มีรสและกลิ่นที่ระดับความเข้มข้นสูง เกิดจากธรรมชาติและการเผาไหม้เชื้อเพลิงที่มีกำมะถัน (ซัลเฟอร์) เป็นส่วนประกอบ สามารถละลายน้ำได้ดี สามารถรวมตัวกับสารมลพิษอื่นแล้วก่อตัวเป็นอนุภาคฝุ่นขนาดเล็กได้ ก๊าซนี้มีผลกระทบโดยตรงต่อสุขภาพ ทำให้เกิดการระคายเคืองต่อเยื่อบุตา ผิวหนัง และระบบทางเดินหายใจ หากได้รับเป็นเวลานาน ๆ จะทำให้เป็นโรคหลอดลมอักเสบเรื้อรังได้้

เพื่อความง่าย ให้เข้าถึงง่าย เขาจึงแบ่งสีหรือระดับความปลอดภัยไว้ ทั้งหมด 5 ระดับนะครับ 5 สีด้วย ดังภาพ

โดย PM2.5 และ PM10 ความเข้มข้นของสารมลพิษ ที่เทียบเท่ากับ ค่าดัชนีคุณภาพอากาศ

AQI
PM2.5
(มคก./ลบ.ม.)
PM10
(มคก./ลบ.ม.)
เฉลี่ย 24 ชั่วโมงต่อเนื่อง
ดีมาก (0 – 25) 0 – 25 0 – 50
ดี (26 – 50) 26 – 37 51 – 80
ปานกลาง (51 – 100) 38 – 50 81 – 120
เริ่มมีผลกระทบกับสุขภาพ (101 – 200) 51 – 90 121 – 180
มีผลกระทบกับสุขภาพ (มากกว่า 200) 91 ขึ้นไป 181 ขึ้นไป

โดยสุดท้าย ผมเอาค่าที่อ่านได้จาก Dust Sensor มาเทียบกับหาค่า AQI แล้วแสดงผลครับ โดยตอนนี้ทำแค่ AQI ของ PM2.5 ก่อนนะครับ ผมยึดเกณท์ PM2.5 อยู่ไม่เกิน 0-50 ug/m3 ยังถือว่าปลอดภัย หรือ ฟ้า ถึง เหลือง (เอาจิงๆ ผมว่ามันเกิน 30 ไม่ค่อยโอเคแล้วล่ะครับ)

Source Code

โปรแกรมของทางผม ไม่ได้ใช้ Lib เพิ่มเติมนะครับ เก็บข้อมูลจาก Uart2 แล้ว เอามาคำนาณ แสดงผลเลย ข้อดี ผมว่าถ้ามี sensor ตัวอื่นที่เป็น serial น่าจะเอาประยุกต์ ใช้งานได้เลยไม่ต้องรอ คนทำ Lib นะครับ

#include <M5Stack.h>
#include <HardwareSerial.h>

//const uint8_t PMS_RX=16, PMS_TX=17;
HardwareSerial pmsSerial(2); // UART2 on GPIO16(RX),GPIO17(TX)

// Stock font and GFXFF reference handle
#define GFXFF 1
#define FF18 &FreeSans12pt7b

#define CF_OL24 &Orbitron_Light_24
#define CF_OL32 &Orbitron_Light_32
#define CF_RT24 &Roboto_Thin_24
#define CF_S24  &Satisfy_24
#define CF_Y32  &Yellowtail_32

int PM25AQI;

void setup() {
  M5.begin();
   
  // our debugging output
  Serial.begin(115200);
  
  // sensor baud rate is 9600
  pmsSerial.begin(9600);
}
 
struct pms7003data {
  uint16_t framelen;
  uint16_t pm10_standard, pm25_standard, pm100_standard;
  uint16_t pm10_env, pm25_env, pm100_env;
  uint16_t particles_03um, particles_05um, particles_10um, particles_25um, particles_50um, particles_100um;
  uint16_t unused;
  uint16_t checksum;
};
 
struct pms7003data data;
    
void loop() {

if (readPMSdata(&pmsSerial)) {
    M5.Lcd.setFreeFont(FF18);                 // Select the font
    M5.Lcd.setTextSize(0.5);
    
    if (data.pm25_env <= 25) {
      M5.Lcd.fillScreen(TFT_BLUE);           
      M5.Lcd.setTextColor(TFT_WHITE, TFT_BLUE);    
      M5.Lcd.drawString("GOOD", 160, 160, GFXFF);// Print the string name of the font
      //Good
      PM25AQI = data.pm25_env;
    } else if  ( (data.pm25_env >= 26) &&  (data.pm25_env <= 37) ) {
      M5.Lcd.fillScreen(TFT_GREEN);
      M5.Lcd.setTextColor(TFT_WHITE, TFT_GREEN);
      M5.Lcd.drawString("Moderate", 160, 160, GFXFF);
      //Moderate
      PM25AQI = map(data.pm25_env,26,37,26,50);
    } else if  ( (data.pm25_env >= 38) &&  (data.pm25_env <= 50) ) {
      M5.Lcd.fillScreen(TFT_GREENYELLOW);
      M5.Lcd.setTextColor(TFT_WHITE, TFT_GREENYELLOW);
      M5.Lcd.drawString("unhealthy", 160, 160, GFXFF);t
      //unhealthy for kid
      PM25AQI = map(data.pm25_env,38,50,51,100);
    } else if  ( (data.pm25_env >= 51) &&  (data.pm25_env <= 90) ) {
       M5.Lcd.fillScreen(TFT_ORANGE);           
      M5.Lcd.setTextColor(TFT_WHITE, TFT_ORANGE);
      M5.Lcd.drawString("very unhealthy", 160, 160, GFXFF);  
      //very unhealthy
      PM25AQI = map(data.pm25_env,51,90,101,200);
    } else if (data.pm25_env >= 91) {
      M5.Lcd.fillScreen(TFT_RED);
      M5.Lcd.setTextColor(TFT_WHITE, TFT_RED);
      //Hazardous
      PM25AQI= 201;
      M5.Lcd.drawString("Hazardous", 160, 160, GFXFF);
    } 
  
  //  M5.Lcd.setTextSize(1);
    M5.Lcd.drawString("PM2.5(AQI)", 0, 20, GFXFF);
    
    M5.Lcd.setTextDatum(ML_DATUM);
    M5.Lcd.drawString("PM1:", 0, 195, GFXFF);/
    M5.Lcd.drawNumber( data.pm10_env, 80, 195);
    M5.Lcd.drawString("PM2.5:", 160, 195, GFXFF);
    M5.Lcd.drawNumber( data.pm25_env, 240, 195);
    M5.Lcd.drawString("PM10:", 0, 220, GFXFF);
    M5.Lcd.drawNumber( data.pm100_env, 80, 220);
    
    //M5.Lcd.setTextPadding(80);
    M5.Lcd.setTextDatum(MC_DATUM);
    M5.Lcd.setFreeFont(CF_OL32);
    M5.Lcd.setTextSize(2);
    if (PM25AQI < 200) {
      M5.Lcd.drawNumber( PM25AQI, 160, 100);    
    } else {      
      M5.Lcd.drawString("Over 200", 160, 100, GFXFF);// Print the string name of the font
    }
    
    // Reset text padding to zero (default)
    M5.Lcd.setTextPadding(0);

    printTest();  //debug
 }

  
}

void printTest() {
  
    // reading data was successful!
    Serial.println();
    Serial.println("---------------------------------------");
    Serial.println("Concentration Units (standard)");
    Serial.print("PM 1.0: "); Serial.print(data.pm10_standard);
    Serial.print("\t\tPM 2.5: "); Serial.print(data.pm25_standard);
    Serial.print("\t\tPM 10: "); Serial.println(data.pm100_standard);
    Serial.println("---------------------------------------");
    Serial.println("Concentration Units (environmental)");
    Serial.print("PM 1.0: "); Serial.print(data.pm10_env);
    Serial.print("\t\tPM 2.5: "); Serial.print(data.pm25_env);
    Serial.print("\t\tPM 10: "); Serial.println(data.pm100_env);
    Serial.println("---------------------------------------");
    Serial.print("Particles > 0.3um / 0.1L air:"); Serial.println(data.particles_03um);
    Serial.print("Particles > 0.5um / 0.1L air:"); Serial.println(data.particles_05um);
    Serial.print("Particles > 1.0um / 0.1L air:"); Serial.println(data.particles_10um);
    Serial.print("Particles > 2.5um / 0.1L air:"); Serial.println(data.particles_25um);
    Serial.print("Particles > 5.0um / 0.1L air:"); Serial.println(data.particles_50um);
    Serial.print("Particles > 10.0 um / 0.1L air:"); Serial.println(data.particles_100um);
    Serial.println("---------------------------------------");
  
}
boolean readPMSdata(Stream *s) {
  if (! s->available()) {
    return false;
  }
  
  // Read a byte at a time until we get to the special '0x42' start-byte
  if (s->peek() != 0x42) {
    s->read();
    return false;
  }
 
  // Now read all 32 bytes
  if (s->available() < 32) {
    return false;
  }
    
  uint8_t buffer[32];    
  uint16_t sum = 0;
  s->readBytes(buffer, 32);
 
  // get checksum ready
  for (uint8_t i=0; i<30; i++) {
    sum += buffer[i];
  }
 
  /* debugging
  for (uint8_t i=2; i<32; i++) {
    Serial.print("0x"); Serial.print(buffer[i], HEX); Serial.print(", ");
  }
  Serial.println();
  */
  
  // The data comes in endian'd, this solves it so it works on all platforms
  uint16_t buffer_u16[15];
  for (uint8_t i=0; i<15; i++) {
    buffer_u16[i] = buffer[2 + i*2 + 1];
    buffer_u16[i] += (buffer[2 + i*2] << 8);
  }
 
  // put it into a nice struct 🙂
  memcpy((void *)&data, (void *)buffer_u16, 30);
 
  if (sum != data.checksum) {
    Serial.println("Checksum failure");
    return false;
  }
  // success!
  return true;
}

เอาล่ะครับ ตอนนี้ เราก้อได้ อุปกรณ์คุณภาพอากาศ PM2.5 แล้ว ตะลุยวัดฝุ่นกันได้เลยครับ

เพิ่มเติมอีกนิดพฤติกรรมของฝุ่น

อนุภาค PM2.5 มันเบาและลอยอยู่สูง แต่คนที่อยู่ภายในบ้าน ใช่กว่าจะปลอดภัยนะครับ ถ้าสภาพข้างนอกดูมีมืดๆ เหมือนฝนตก รีบปิดประตูทุกบานได้เลยครับ อันนี้ประสบการณ์จากที่ผมเดินวัดฝุ่นมา ฝุ่น PM2.5 มันมองไม่เห็น มันไหลเข้ามากับอากาศปกติได้ มันจะทำให้ระคายทางเดินหายใจได้ ถ้าใครรู้สีก หายใจลำบาก เริ่มไอ รีบหาหน้ากาก หรือ เครื่องฟอกอากาศมาด่วนเลย และ ต่อให้มีแอร์ แต่แอร์ไม่ได้มีกรอง PM2.5 ก้อใช่ว่าจะปลอดภัยนะครับเพราะว่า อนุภาค PM2.5 มันลอยอยู่ในอากาศในห้องล่ะครับ ฉะนั้นถ้ารู้สึกไม่ดี รีบปรับปรุงแก้ไขกันด่วน

1.กรองอากาศภายในรถนะครับ 

เครื่องกรองแอร์ของรถทุกรุ่น จะเป็น HEPA อยู่แล้ว เราไม่จำเป็นต้องซื้อเพิ่ม นอกจาก รถเก่ามาก กับ แอร์ทำงานได้ไม่ดี ถึงจะมี HEPA แต่ต้องมั่นเปลี่ยนบ่อยๆนะครับ อันนี้คือที่ผมว่าภายในรถ ปกติจะเกิน 50-80 ug/m3 แต่เขามาในรถจะเหลือ 20 ug/m3 ได้

ถ้าที่วัดได้ภายในรถยนต์ ที่เห็นไส้กรองดำๆ นั้นล่ะครับ วัดได้ 21 ครับ

2.ห้องนอน

ถ้ายังไม่มีเครื่องกรองแนะนำให้ใช้ filtrete 3M ที่เป็นแผ่นใยไฟฟ้าสถิต มาติดที่ filter เพิ่มนะครับ ลดได้เยอะเหมือนกัน

เอา filter มาห่อ ใส่กลับเข้าไป

ตอนนี้ปิดได้ 1 อาทิตย์แล้ว คาดว่าคงเปลี่ยนบ่อยนะครับ และ ก้อเครื่องฟอก ลดฝุ่นได้เร็วกว่า

อ้างอิง

ปรับปรุง อุปกรณ์วัดคุณภาพอากาศ ฝุ่น PM2.5

$
0
0

จากช่วงต้นปี เกิดวิกฤตฝุ่นพิษ PM2.5 ทำให้ทางผมได้ทดลองใช้ เซ็นเซอร์วัดฝุ่น และ M5Stack ทำต้นแบบเครื่องวัดฝุ่นขึ้นมาง่ายๆ ตอนนี้ถึงเวลาทำให้สมบูรณ์แล้ว

 

  • รวมโมดุล จับเอา Sensor และ Battery ทั้งหมดใส่เข้าไปในเคสเดียวกัน ด้วย 3D Printer ขึ้นรูปมา ดังภาพ โมดิฟาย M5Stack โมดุลเดิมนิดหน่อยครับ

  • แก้ไข UI ใหม่ครับ ให้เป็นระเบียบมากขึ้น ช่วงแรกยังไม่คุ้นกับ M5Stack เลยทำให้แสดงผลได้ก่อน ในตอนนี้ทางผมปรับปรุง UI ใหม่ และแก้ไขเรื่องกระพริบ และ ผมคิดว่า Code ใหม่น่าจะช่วยให้คนที่สนใจ ต่อยอดงานอื่นๆ ได้ง่ายขึ้นครับ
#include <M5Stack.h>
#include <HardwareSerial.h>

//const uint8_t PMS_RX=16, PMS_TX=17;
HardwareSerial pmsSerial(2); // UART2 on GPIO16(RX),GPIO17(TX)

#define GFXFF 1
#define FF9 &FreeSans9pt7b

#define CF_OL24 &Orbitron_Light_24
#define CF_OL32 &Orbitron_Light_32
#define CF_RT24 &Roboto_Thin_24
#define CF_S24  &Satisfy_24
#define CF_Y32  &Yellowtail_32

struct pms7003data {
    uint16_t framelen;
    uint16_t pm10_standard, pm25_standard, pm100_standard;
    uint16_t pm10_env, pm25_env, pm100_env;
    uint16_t particles_03um, particles_05um, particles_10um, particles_25um, particles_50um, particles_100um;
    uint16_t unused;
    uint16_t checksum;
};

struct displayCode {
    int bg_color;
    int text_color;
    int AQI;
    String Code;
};

struct pms7003data data;

struct displayCode displayCode_t;

void setup() {
    M5.begin();
    
    // our debugging output
    Serial.begin(115200);
    
    // sensor baud rate is 9600
    pmsSerial.begin(9600);
    
    M5.Lcd.setFreeFont(CF_RT24);
    M5.Lcd.setTextDatum(MC_DATUM);
    M5.Lcd.fillScreen(TFT_BLACK);
    M5.Lcd.setTextColor(TFT_WHITE);
    M5.Lcd.drawString("LOADING DATA", M5.Lcd.width()/2, M5.Lcd.height()/2, GFXFF);
}
 
int old=-1;
    
void loop() {

  if (readPMSdata(&pmsSerial)) {
      displayCode_t = PM25AQI(data.pm25_env);
      
     // update bg color
     if (old != displayCode_t.bg_color) {
          M5.Lcd.setFreeFont(CF_RT24);                 // Select the font    
          M5.Lcd.setTextSize(0.5);
          M5.Lcd.fillScreen(displayCode_t.bg_color);           
          M5.Lcd.setTextColor(displayCode_t.text_color, displayCode_t.bg_color);
          M5.Lcd.setTextDatum(ML_DATUM); 
          M5.Lcd.drawString("PM2.5(AQI)", 5, 15, GFXFF);     
          M5.Lcd.setTextDatum(MC_DATUM);
          M5.Lcd.drawString(displayCode_t.Code, M5.Lcd.width()/2, M5.Lcd.height()/2+40, GFXFF);// Print the string name of the font
        
          M5.Lcd.setFreeFont(FF9);
          M5.Lcd.setTextPadding(0);
          M5.Lcd.drawString("PM1.0", 40, 195, GFXFF);
          M5.Lcd.drawString("PM2.5", M5.Lcd.width()/2, 195, GFXFF);
          M5.Lcd.drawString("PM10", 280, 195, GFXFF);
     }
     
      M5.Lcd.setFreeFont(FF9); 
      M5.Lcd.setTextPadding(40);
      M5.Lcd.setTextSize(1);
      M5.Lcd.drawNumber( data.pm10_env, 40, 220);
      M5.Lcd.drawNumber( data.pm25_env, M5.Lcd.width()/2, 220);
      M5.Lcd.drawNumber( data.pm100_env, 280, 220);
  
      M5.Lcd.setFreeFont(CF_OL32);
      M5.Lcd.setTextDatum(MC_DATUM);
      M5.Lcd.setTextSize(2);
      M5.Lcd.setTextPadding(120);  
      M5.Lcd.drawNumber( displayCode_t.AQI, M5.Lcd.width()/2, M5.Lcd.height()/2-20);
      
      printTest();  //debug
      old = displayCode_t.bg_color;
   }

}

displayCode PM25AQI(int reading) {
    struct displayCode display_t;
    display_t.text_color = TFT_WHITE;
    if (reading <= 25) {
        display_t.bg_color = TFT_BLUE;      
        display_t.Code = "GOOD";
        display_t.AQI = reading;
    } else if ( (reading >= 26) &&  (reading <= 37) ) {
        display_t.bg_color = TFT_GREEN;
        display_t.Code = "Moderate";      
        display_t.AQI = map(reading,26,37,26,50);
    } else if  ( (reading >= 38) &&  (reading <= 50) ) {
        display_t.bg_color = TFT_GREENYELLOW;        
        display_t.Code = "unhealthy"; //unhealthy for kid
        display_t.AQI = map(reading,38,50,51,100);
    } else if  ( (reading >= 51) &&  (reading <= 90) ) {
        display_t.bg_color = TFT_ORANGE;          
        display_t.Code = "very unhealthy";  //very unhealthy
        display_t.AQI = map( reading,51,90,101,200 );
    } else if (data.pm25_env >= 91) {
        display_t.bg_color = TFT_RED;
        display_t.AQI = map( reading,91,200,201,510 );               
        display_t.Code = "Hazardous";  //Hazardous
    } 
    return display_t;
}

void printTest() {
  
    // reading data was successful!
    Serial.println();
    Serial.println("---------------------------------------");
    Serial.println("Concentration Units (standard)");
    Serial.print("PM 1.0: "); Serial.print(data.pm10_standard);
    Serial.print("\t\tPM 2.5: "); Serial.print(data.pm25_standard);
    Serial.print("\t\tPM 10: "); Serial.println(data.pm100_standard);
    Serial.println("---------------------------------------");
    Serial.println("Concentration Units (environmental)");
    Serial.print("PM 1.0: "); Serial.print(data.pm10_env);
    Serial.print("\t\tPM 2.5: "); Serial.print(data.pm25_env);
    Serial.print("\t\tPM 10: "); Serial.println(data.pm100_env);
    Serial.println("---------------------------------------");
    Serial.print("Particles > 0.3um / 0.1L air:"); Serial.println(data.particles_03um);
    Serial.print("Particles > 0.5um / 0.1L air:"); Serial.println(data.particles_05um);
    Serial.print("Particles > 1.0um / 0.1L air:"); Serial.println(data.particles_10um);
    Serial.print("Particles > 2.5um / 0.1L air:"); Serial.println(data.particles_25um);
    Serial.print("Particles > 5.0um / 0.1L air:"); Serial.println(data.particles_50um);
    Serial.print("Particles > 10.0 um / 0.1L air:"); Serial.println(data.particles_100um);
    Serial.println("---------------------------------------");
  
}
boolean readPMSdata(Stream *s) {
  if (! s->available()) {
    return false;
  }
  
  // Read a byte at a time until we get to the special '0x42' start-byte
  if (s->peek() != 0x42) {
    s->read();
    return false;
  }
 
  // Now read all 32 bytes
  if (s->available() < 32) {
    return false;
  }
    
  uint8_t buffer[32];    
  uint16_t sum = 0;
  s->readBytes(buffer, 32);
 
  // get checksum ready
  for (uint8_t i=0; i<30; i++) {
    sum += buffer[i];
  }
 
  /* debugging
  for (uint8_t i=2; i<32; i++) {
    Serial.print("0x"); Serial.print(buffer[i], HEX); Serial.print(", ");
  }
  Serial.println();
  */
  
  // The data comes in endian'd, this solves it so it works on all platforms
  uint16_t buffer_u16[15];
  for (uint8_t i=0; i<15; i++) {
    buffer_u16[i] = buffer[2 + i*2 + 1];
    buffer_u16[i] += (buffer[2 + i*2] << 8);
  }
 
  // put it into a nice struct 🙂
  memcpy((void *)&data, (void *)buffer_u16, 30);
 
  if (sum != data.checksum) {
    Serial.println("Checksum failure");
    return false;
  }
  // success!
  return true;
}

เพิ่มเติมตอนนี้ มีน้องใหม่ สำหรับ Node32Lite แสดงผลได้เหมือนกัน แค่ต้องเหนื่อยทำเองครับ เดี่ยวจะแชร์ กันในวันหลัง

LINE CHATBOT With IoT Device

$
0
0

หลักสูตรฝึกอบรม Internet of Things สำหรับ ผู้เริ่มต้นเน้น ภาคปฏิบัติและทฤษฏี เหมาะให้ผู้เข้าอบรมใช้งาน และ สร้างโปรเจค IoT ได้ต่อยอดได้ โดยเราได้เลือกโปรเจคเครื่องวัดฝุ่น เพื่อ ให้ทดลองโปรแกรม ทำ Hardware แสดงผล และ Platform IoT บน LINE Chatbot

LINE เป็น Platform พูดคุย ที่ทุกคน มีในโทรศัพท์มือถือ ทำให้ สะดวกและเข้าถึงผู้ใช้ เราสามารถใช้ LINE ใช้ในการแจ้งเตือน มอนิเตอร์ รวมถึง การควบคุมอุปกรณ์ผ่าน CHATBOT ผ่าน API ของ LINE ที่เป็นมาตราฐานและทันสมัย

รายละเอียด
วันที่ วันเสาร์ที่ 29 กุมภาพันธ์ 63
สถานที่อบรม: SynHUB ,Pantip Plaza ประตูน้ำ
จำนวนผู้เข้าอบรม: 12 ท่าน
ค่าลงทะเบียน: ราคา 4,500 บาท (ยังไม่รวม VAT 7%)  รวม break อาหารว่าง และ อุปกรณ์ M5Stack และ Hat PMSA003
ค่าลงทะเบียน: ราคา 2,500 บาท (ยังไม่รวม VAT 7%)  รวม break อาหารว่าง

ลงทะเบียนได้ที่นี้ >>> https://forms.gle/NKAThkqEcsuoii6JA

Course Syllabus

A: แนะนำ เกี่ยวกับ Internet of Thing

1: GETTING STARTED WITH THE M5STACK

  • รู้จักบอร์ด M5STACK
  • การติดตั้ง Arduino IDE
  • HELLO WORLD WITH M5STACK

2: FIRST GRAPHIC PROJECTS WITH THE M5STACK

  • การสร้างรูป LINE ,BOX
  • ระบบสีบน RGB565
  • การโหลดภาพ PNG,JPG
  • การแสดงผลค่า และ การใช้ Font

3: Sensing Real World:

  • อ่านค่า อุณหภูมิ และ ความชิ้น
  • อ่านค่า PM2.5

4: รู้จัก LINE CHATBOT

  • รู้จัก Dialogflow (ลองสร้างบทสนทนาแรกกัน)
  • เชื่อมต่อ BOT กับ LINE Account
  • LIFF
  • LINE Things คืออะไร

5:  ประยุกต์การใช้งาน IoT กับ ESP32 ( ESP32 เบื้องต้น)

  • สร้างอุปกรณ์เชื่อมต่อกับ LINE ด้วย ESP32
  • ประยุกต์ LINE อ่านค่า PM2.5 ผ่านทาง CHATBOT

 

ตัวอย่าง M5Stack

สิ่งประดิษฐ์ของชาวเมกเกอร์ ในวิกฤตไวรัส Covid-19

$
0
0

วิกฤตไวรัส Covid-19 นี้ กระทบกันทั่วโลกมากครับ เริ่มจากจีน และ ลามไปทั่วโลก ซึ่งทำให้เราได้เห็นไอเดียของสิ่งประดิษฐ์ ที่จะมาแก้ไขปัญหานี้ครับ

1. เครื่องกดน้ำ แบบไร้กด (Non-contact Switch for Public Water Dispenser – Arduino)

การสัมผัสของจากในที่สาธารณะ ในตอนนี้จะไม่ปลอดภัย เราจะแปลงเครื่องเดิมๆ อย่างไง ในอัตโมมัติ เมกเกอร์คนนี้เลยทำเครื่องกดน้ำให้อัตโนมัติ โดยใช้อุปกรณ์ใกล้ตัว Ardiono IR และ Servo ดังภาพ

ที่มา non-contact-switch-for-public-water-dispenser-arduino

2. หน้ากากตรวจหา Covid-19 (Coronavirus Detector)

เขาทำหน้ากาก ที่ติด Digital Infrared Temperature Sensor (MLX90615) แล้วใช้ตรวจคนเข้า LAB ถ้าใครมีอุณหภูมิเกิน 38 องศาองศาเซลเซียส OLED จะขึ้นข้อความเตือน และ RGB LED จะเปลี่ยนสีแดง

อืมมมมม ใช้ Thermal Gun ก้อเหมือนกัน เปล่าว่ะ!!!!

ที่มา Coronavirus Detector 

3. หน้ากาก Cyberpunk Mask

ส่วนอันนี้ แอดมินเห็นแก่ความน่ารักของน้องเขาเลยติดมาให้เพื่อนดูด้วย หน้ากากตัวนี้จะมีตัววัดฝุ่น PM2.5 ซึ่งเมือใดที่มีค่าอันตรายมากเกินไป จะปิดช่องอากาศเข้า ป้องกันฝุ่นเข้าเต็มที่ และ LED จะเปลี่ยนเป็นสีแดงครับ

 

ที่มา Cyberpunk Mask

4. Fire Detecting Drone

เอาล่ะครับ แถมด้วยโปรเจคสุดท้ายเลยครับ โดรนบินตรวจไฟป่า อันนี้เหมาะกับไทยมาก ช่วงนี้ยังมีการลักลอมเผาป่ากันอยู่ แต่จับไม่ได้ ผมคิดว่าสิ่งนี้น่าจะช่วยได้นะครับ หลักการเขาใช้ Drone บิน รุ่นกับ Thermal Cam ตรวจดูจุดที่มีความร้อน แล้วแจ้งเตือนภาคพื้นดิน

ที่มา Fire Detecting Drone

เอาล่ะครับ ถ้าแอดมินเจออะไรที่น่าสนใจ จะมา Update กันใหม่นะครับ งานก็ต้องเร่ง สิ่งประดิษฐ์ก้อต้องทำ Covid-19 ก็ต้องระวังอีก โอ้ยยยย ไม่อยากจะบ่น ไปล่ะครับ

เล่นสนุกกับ Capacitive TOUCH บน Node32S (esp32)

$
0
0

หลังจากหายไปหลายวัน ตอนนี้ ESP32 มีการ UPDATE อีกแล้วครับ ตอนนี้น่าสนใจหน่อยครับ คือมันมีความสามารถทำให้ขาที่ต่อออกมา เป็น Sensor แบบสัมผัสหรือ Capacitive touch ได้ครับ ซึ่งอันนี้ ก้อเพื่งจากทดสอบสดๆ ร้อนๆเลยครับ อีกสักพักคงเป็นตัวอย่างใน Arduino ESP32 แต่ทางเราเอามาเปิดเผยก่อนนะครับ

Touch Sensor คือ sensor สัมผัส สัมผัสโดยตรง หรือ จะเป็นแบบไม่สัมผัมแต่เข้าใกล้ๆ ซึ่งข้างหลังนี้เป็นนิยามจาก datasheet โดยหลักการทำงาน มันจะใช้การวัดการเปลี่ยนของความจุไฟฟ้า หรือ Capacitive ซึ่งบางที่เราได้ยินเขาเรียกว่า cap sense

สำหรับบอร์ดที่ทางเราใช้เป็น Node32s ครับ ที่ภูมิใจ ไทยทำ และ ตอนนี้มีจำหน่ายแล้ว ที่ Gravitech Thai

สำหรับบอร์ดของ Node32s ขาที่เอามาทำเป็น Touch Sensor ได้ มี 10 ขา ดังภาพข้างล่าง

เราสามารถ ทำให้การ Touch เป็น Interrupt Event ได้ คือ ถ้ามีการสัมผัสให้กระโดดไปทำงานต่อคำสั่งเลย อันนี้ทางผมลองแบบง่ายๆ ต่อสายออกมาจาก PIN T0 หรือ ขา GPIO4 ครับ ซึ่งเราสามารถลากไปต่อกับ โลหะทีเป็นพื้นผิวใหญ่ จะเพิ่มพื้นที่สัมผัสได้ครับ และข้อดีอีกอย่างของ Capacitive touch มันทะลุ วัตถุได้ คือเราเอา แถบโลหะซ่อนไว้ใต้กระจก ยังทำงานได้ ถ้าจะทำ case กันน้ำ 100% ใช้วิธีนี้ ทำได้ไม่ยากเลย

อันนี้เป็น โค๊ด ตัวอย่าง

https://gist.github.com/prasertsakd/77b37f12f587d88ddf522fb34ecd0276

เมกเกอร์แฟร์ ปี 2 ทำของมาอวด

$
0
0

ขอเชิญ เพื่อนๆ พี่ๆ น้องๆ  มาร่วมงาน และ พูดคุยกันในงานใหญ่ประจำปี Bangkok Maker Faires ปี 2 ในวันที่ 21-22 ม.ค. 2017 งานนี้ Workshop มากมี ผลงานเด่นดีมากหลายครับ งานนี้โชว์กันตั้งแต่เที่ยงยันดึก ห้ามพลาดเด็ดขาดครับ

รายละเอียดงาน และ Highlight ในงาน(บางส่วน) และ อีกหลายส่วน ที่ยังไม่ยอมเปิดเผย 😀


สถานที่จัดหน้าห้าง The Street รัชดา แล้วพบกัน



ติดตั้ง ESP-IDF กับ node32s (esp32)

$
0
0

ESP-IDF คืออะไร

เป็นชุดคอมไพล์เลอร์ ESP32 ที่พัฒนาโดยบริษัท Espressif ผู้ที่ออกแบบ SOC ตัวนี้ขึ้นมา โดยในตอนแรกๆ เลยทาง Espressif เปิดตัว ESP-IDF ออกมาก่อน ซึ่งใน Arduino และ Platform IO เป็นตัวที่ตามมาที่หลังนะครับ และ ใช้ ESP-IDF นี้ล่ะที่เป็นตัวคอมไพล์โค๊ดเช่นกัน แต่เพิ่มเติมการเขียนโปรแกรมแบบ Arduino เข้าไป ซึ่งหมายความว่า ต้องรอคน เขียน Lib ขึ้นมาก่อน อาจจะใช้งานได้ทั้งหมด

ความน่าสนใจของ ESP-IDF คือ

  • ความสามารถ Code Optimism ได้ดีกว่า โดยใช้ sdkconfig
  • สามารถใช้งานส่วนใหม่ๆ ได้ก่อนใคร

วิธีติดตั้ง ESP-IDF บน Windows 10

ทาง Espressif ได้จัดเตรียม Toolchain และ MSYS32 สำหรับ ESP-IDF บน Windows ไว้ชื่อ msys32 ซึ่งรวมเครื่องมือสำหรับคอมไพล์ไว้แล้ว สำหรับคนที่กลัวว่าจะ ทำเองไม่ work แนะนำติดตั้ง msys32 ดีกว่า ศึกษาเพิ่มเติมได้จาก https://github.com/espressif/esp-idf/blob/master/docs/windows-setup.rst

ขั้นตอน 1. การติดตั้ง

ดาวน์โหลด ESP-IDF และ Toolchain ได้ที่

https://dl.espressif.com/dl/esp32_win32_msys2_environment_and_toolchain-20160816.zip

แตกไฟล์ออกมา แล้ววางไปที่โฟลเดอร์ C:\msys32

รูปโฟลเดอร์ C:\msys32

  • home – โฟลเดอร์เริ่มต้นของยูสเซอร์ home/admin
  • msys2_shell.cmd – โปรแกรม msys32

ขั้นตอนที่ 2 ติดตั้ง esp-idf

ใน เครื่องมือนี้ จะได้ติดตั้ง msys32 เป็น bash command ที่รันบน windows ไว้ จึงทำให้เราใช้คำสั่งต่างๆ แบบระบบ Linux บน Windows ได้ครับ

ไปที่โฟลเดอร์ C:\msys32 คลิกขวาที่ msys2_shell.cmd แล้วคลิกซ้าย Run as administrator

รูป คลิกขวาที่ msys2_shell.cmd แล้วคลิกซ้าย Run as administrator

จากนั้นย้ายไปยัง ไดเรกทรอรี ที่ต้องการ Clone SDK โดยการพิมพ์คำสั่ง cd “C:/pathe/to/dir/” หรือพิมพ์ cd

จากนั้นพิมพ์คำสั่ง

git clone –recursive https://github.com/espressif/esp-idf.git

ดังภาพ

ขั้นตอนที่ 3 เริ่มต้นสร้าง Project

วิธีที่ง่ายที่สุดในการเริ่มต้นสร้างโปรเจค ให้ดาวน์โหลดจาก Github โดยวิธี เหมือนกันในขั้นตอนที่ 2 นะครับ แต่เรา clone ตัวอย่างโปรเจคมาแทน

พิมพ์คำสั่งว่า

git clone https://github.com/espressif/esp-idf-template.git

ดังภาพ

จากนั้น พิมพ์คำสั่ง “ls -l”

จะเห็นเรามี โฟลเดอร์ใหม่ดังภาพ

ข้อควรระวัง: esp-idf ไม่สนับสนุนให้มา ช่องว่าง (space) ใน PATH esp-idf

ขั้นตอนที่ 4 การตั้งค่าโปรเจค

เปิด MSYS2 Terminal โดยการรัน C:\msys32\msys2_shell.cmd จะเป็นการรัน Bash Shell ขึ้นมาบนวินโดว์

พิมพ์คำสั่ง เพื่อทำการตั้งค่า PATH ของ ESP-IDF ที่เราได้ติดตั้งไว้

export IDF_PATH=”C:/path/to/esp-idf”

ดังภาพ

ถ้าคุณไม่ต้องการพิมพ์คำสั่ง export ทุกครั้ง สามารถเอาคำสั่ง script export ไปใส่ในไดเรกทรอรี่ /etc/profile.d หรือ ใส่ใน .bashrc ก้อได้เช่นกัน

จากนั้นย้ายไปยังไดเรกทรอรีของโปรเจค พิมพ์คำสั่งว่า

Make menuconfig

จะขึ้นดังภาพ

หลังจากพิมพ์คำสั่ง menuconfig จะขึ้นหน้าต่างดังภาพ

  • ให้เลือก Serial flasher config ตั้งค่า Serial Port ให้ถูกต้อง แล้ว กด Save จะได้ SDKConfig

  • make app เพื่อคอมไพล์ แนะนำว่า เครื่องควรแรงๆนะครับ จะได้ไม่ต้องรอนานน ฮ่าๆ

ถ้า สำเร็จ ไม่เจอ error ใดๆ จะมาถึง บรรทัดสุดท้ายว่า เอาไป flash ได้ แต่เราสามารถ พิมพ์คำสั่ง make flash  สำหรับ Flash ลง Board

เอาล่ะครับ ใคร flash ได้ไม่มีปัญหา ลองเอาตัวอย่างอื่นๆ ของ esp32 ใน github มาลองครับ

https://github.com/espressif/esp-idf/tree/master/examples

มันยังมีตัวอย่างการใช้งานของ Bluetooth และ ทำให้ ESP32 เป็น เครื่องเล่นเกมส์อีก ที่ได้ลองประสิทธิ์ภาพ ที่เหนือล้ำกว่าบน Arduino มากนัก ซึ่งเดี่ยวจะมาแนะนำในตอนหน้าครับ

ติดตั้ง Node32s กับ PlatformIO

$
0
0

Platform IO เป็นแพ็คเกจเสริมการทำงานของ Editor ที่โด่งดังนั่นชื่อ Atom ทำให้สามารถเขียนโค้ดโปรแกรมลงบอร์ดทดลองได้หลากหลาย (Embedded Board) ทั้ง Arduino ,ESP8266, Rapberry PI จากที่เคยลองใช้ตอนเขียนโปรแกรมบน ESP8266 ประทับใจเรื่องความเร็วการคอมไพล์ และ การ flash ที่รวดเร็วกว่า Arduino มาก แต่ก้อเหมาะกับมือโปรแกรมเมอร์นะครับ และ ตอนนี้ Support ESP32 และ Node32s อีกด้วย จะรออะไรล่ะมาลองกัน

วิธีติดตั้ง Platform IO

  • เปิดบราวเซอร์ เข้าไปเวปที่ http://platformio.org คลิก IDE
  • คลิก [Download for Windows] เพื่อดาวน์โหลดไฟล์ platformio-atom-windows.exe

รูปที่ 2 คลิก IDE > Download for Windows

  • ติดตั้งโปรแกรม โดยการดับเบิ้ลคลิก ที่ไฟล์ platformio-atom-windows.exe  รอจนกระทั่งได้หน้าจอ PlatformIO IDE: Installing…
    แล้วรอจน Atom เรียกแพ็คเกจ PlatformIO มาติดตั้งให้โดยอัตโนมัติ ขั้นตอนนี้ใช้เวลานานพอสมควร

รูปที่ 3 รอจนโปรแกรมติดตั้งเสร็จ

  • เมื่อโปรแกรมติดตั้งเสร็จแล้ว จะขึ้นหน้าจอ Atom PlatformIO IDE has been successfully installed! คลิก Reload Now เพื่อเริ่มค่าใหม่ ดังรูป 4

รูปที่ 4 คลิก Reload Now

  • เปิดโปรแกรมอีกครั้ง หน้าจอ PlatformIO Home – Atom เห็นข้อความ Welcome to PlatformIO แสดงว่าคุณได้ติดตั้งโปรแกรมเสร็จเรียบร้อยแล้ว

รูปที่ 5 หน้าจอ PlatformIO Home – Atom

ติดตั้ง LLVM

Platformio ใช้ LLVM เป็น Clang เพื่อทำให้การคอมไพล์ได้รวดเร็วมากขึ้น

  • ให้คุณเปิดบราวเซอร์ไปที่ http://llvm.org คลิก LLVM 3.9.0
  • จากนั้นคลิก Clang for Windows (64-bit) เพื่อดาวน์โหลดไฟล์ LLVM-3.9.0-win64.exe

รูปที่ 6 ภาพแสดงหน้าเวป ให้คลิก LLVM 3.9.0

รูปที่ 7 คลิก Clang for Windows (64-bit)

  • ดับเบิ้ลคลิกไฟล์ LLVM-3.9.0-win64.exe ที่คุณเพิ่งดาวน์โหลดมา จะได้หน้าจอ LLVM Setup ข้อความต้อนรับสู่การติดตั้ง ให้คลิก Next >
  • หน้าจอ LLVM Setup ข้อกำหนดและเงื่อนไขการใช้งาน LLVM เมื่อคุณอ่านเข้าใจแล้ว คลิก I Agree

รูปที่ 8 คลิก Next >

รูปที่ 9 คลิก I Agree

  • หน้าจอ LLVM Setup ตัวเลือกการติดตั้งให้คลิกวงกลมหน้า Add LLVM to the system PATH for all users เพื่อเพิ่มพาธของ LLVM ให้ทุกยูสเซอร์
    • Do not add LLVM to the system PATH – ไม่ต้องเพิ่มพาธของ LLVM
    • Add LLVM to the system PATH for all users – เพิ่มพาธให้ทุกยูสเซอร์
    • Add LLVM to the system PATH for current user – เพิ่มพาธให้เฉพาะยูสเซอร์นี้
    • Create LLVM Desktop Icon – สร้างไอคอน LLVM บนเดสก์ทอป

รูปที่ 10 คลิก Add LLVM > Next >

  • แล้วคลิก Next >
  • หน้าจอ LLVM Setup เลือกโฟลเดอร์ที่ต้องการเก็บ LLVM (หากต้องการเปลี่ยนคลิก Browse…) แล้วคลิก Next >

รูปที่ 11 คลิก Next >

  • หน้าจอ LLVM Setup เพื่อเลือกสร้างเมนู LLVM ในเมนูสตาร์ท ในที่นี้เลือกเป็น LLVM แล้วคลิก Install ดังรูป 12 แล้วรอจนโปรแกรมติดตั้งเสร็จ ดังรูป 13

รูปที่ 12 คลิก Install

รูปที่ 13 รอสักครู่

  • หน้าจอ cmd ให้กดคีย์ Enter (หรือกดคีย์ใดคีย์หนึ่งบนแป้นพิมพ์ก็ได้)
  • หน้าจอ LLVM Setup ติดตั้งเสร็จเรียบร้อยแล้ว ให้คุณ Finish

รูปที่ 14 กดคีย์ Enter

รูปที่ 15 คลิก Finish

ข้อแนะนำ เมื่อติดตั้งทั้ง 2 โปรแกรมอันได้แก่ Atom PlatformIO, LLVM และตั้งค่าต่างๆ เสร็จเรียบร้อยแล้ว คุณควรจะ Restart คอมพิวเตอร์ เพื่อให้คอมพิวเตอร์เริ่มค่าใหม่ได้อย่างถูกต้อง

 

Hello world

มาลองสร้างโปรเจคบน PlatformIO กันบ้างครับ ขั้นตอนง่ายๆนะครับ แค่ คลิก New Project เราเริ่มต้นกับ โค้ดโปรแกรมสั้นๆ เรียกขวัญและกำลังใจกันก่อนครับ แค่ 6 บรรทัด เพื่อแสดงข้อความที่อยู่ในคำสั่ง printf ข้อความในที่นี้คือ พิมพ์ Hello world ออกทางหน้าจอ Serial Monitor

<1> ที่หน้าจอ PlatformIO Home ให้คุณคลิก New Project เพื่อสร้างโครงการใหม่

(โน๊ต: คุณสามารถคลิกเมนู PlatformIO > Home Screen เพื่อมาหน้าจอ PlatformIO Home)

รูปที่ 16 คลิก New Project

<2.> ในช่อง Select board: เลือกบอร์ดเป็น Espressif ESP32 Dev Module หรือ ตอนนี้ เลือกบอร์ด Ayarafun Node32s ได้เลยครับ

<3> ในช่อง Choose the directory: เลือกโฟลเดอร์เป็น C:\Users\admin\Documents\atom\helloworld

<4> เมื่อคุณเลือกบอร์ด และโฟลเดอร์ที่เก็บโค้ด ที่ต้องการเรียบร้อยแล้ว ให้คลิก Process

รูปที่ 17 คลิก Process

หรือจะ คลิก Node32s ได้นะครับ

<5> จะได้โฟลเดอร์ helloworld ขึ้นทางด้านซ้ายมือ จากนั้นคลิกขวาที่ src แล้วคลิกซ้ายที่ New File

<6> จะมีช่อง Enter the path for the new file. ให้พิมพ์ชื่อไฟล์ src\hello_world.c แล้วกดคีย์ Enter

รูปที่ 18 คลิกขวา src > คลิกซ้าย New File

รูปที่ 19 พิมพ์ชื่อไฟล์ แล้วกดคีย์ Enter

<7> จะได้หน้าจอ hello_world.c ให้พิมพ์โค้ดข้างล่างนี้ลงไป

https://gist.github.com/prasertsakd/10f349972a26b584764685972dc45e9f

<8> เมื่อคุณพิมพ์เสร็จเรียบร้อยแล้ว ให้คลิกปุ่ม Serial Monitor

<9> ในช่อง Port ให้เลือกพอร์ตที่บอร์ดของคุณเชื่อมต่อกับคอมพิวเตอร์ ในที่นี้คือ พอร์ต COM5

<10> ในช่อง Baudrate ให้เลือกที่ 115200

<11> คลิกปุ่ม Start

(โน๊ต: ปกติโปรแกรม PlatformIO จะเลือกพอร์ตกับ baudrate ให้คุณแล้วโดยอัตโนมัติ)

รูปที่ 20 พิมพ์โค้ด

รูปที่ 21 เลือกพอร์ตและ baudrate

<12> คลิกปุ่ม Upload

<13> คลิกปุ่ม Save and build เพื่อบันทึกโค้ด และอัปโหลดโค้ดลงบอร์ด Node32s

รูปที่ 22 คลิก Upload > Save and build

<14 > กรุณารอจนกว่าโปรแกรมอัปโหลดเสร็จเรียบร้อย แล้วโปรแกรมจะแสดงข้อความที่อยู่ในคำสั่ง printf ออกมาทางหน้าจอ Serial Monitor ในที่นี้แสดงข้อความ Hello world ออกทางหน้าจอ Serial Monitor ดังรูป 23

รูปที่ 23 แสดงข้อความ Hello world ออกทางหน้าจอ

การเขียนโปรแกรมบน PlatformIO แบบ Arduino

โค้ดโปรแกรมเขียนเหมือน Arduino IDE ทุกประการ แต่ต้องเพิ่ม #include “Arduino.h” ในบรรทัดแรก

  • คลิกปุ่ม Initialze or Update PlatformIO Project เพื่อสร้างโครงการใหม่ จากนั้นเลือกบอร์ดและโฟลเดอร์ที่ต้องการ

รูปที่ 24 สร้าง project ใหม่

รูปที่ 25 เลือกบอร์ดและโฟลเดอร์

  • สร้างไฟล์ใหม่ชื่อ blink_wiring.cpp แล้วพิมพ์โค้ดข้างล่างนี้ (นามสกุลของไฟล์ต้องเป็น .cpp นะครับ) แล้วอัปโหลดเข้า Node32s จะเห็นว่าไฟบนบอร์ดจะกระพริบ จากนั้นลองเปลี่ยนตัวเลข delay เพื่อดูความเปลี่ยนแปลง

https://gist.github.com/prasertsakd/6a5a85af83b387ca01247368a693f32c

รูปที่ 26 โค้ดโปรแกรมลง Node32s

การเขียนโปรแกรม แบบ ESP-IDF

ข้อดีอย่างหนึ่งในการเขียนโปรแกรมบน Platform IO คือ เราสามารถทดสอบโปรเจคได้ทั้งแบบ Arduino และ ESP-IDF โดยใน ESP-IDF จะมีตัวอย่างการใช้งาน Hardware ที่เยอะ คุณสามารถเปิดไฟล์ตัวอย่างขึ้นเพื่อมาศึกษาได้นะครับ แต่โค้ดตัวอย่างนี้ยังเป็น beta อยู่ อาจต้องมีการปรับแก้โค้ดบางส่วน เพื่อให้โค้ดโปรแกรมสามารถทำงานได้ ในหัวข้อนี้จะนำไฟล์ตัวอย่าง hello world มาโค้ดโปรแกรมลง Node32s

  • เปิดบราวเซอร์ไปที่ https://github.com/espressif/esp-idf/tree/master/examples คุณจะเห็นตัวอย่างโค้ด esp-idf ในที่นี้มี 12 ตัวอย่าง แต่เพื่อไม่ให้หนังสือเล่มนี้มีขนาดหนามาก ผู้เขียนจะโค้ดโปรแกรม 01_hello_world เพียงโปรแกรมเดียวเท่านั้น

รูปที่ 27 ไฟล์ตัวอย่างทั้งหมด

  • 01_hello_world แล้วคลิก main แล้วคลิก hello_world_main.c คุณจะเห็นโค้ดโปรแกรม ให้คลิกปุ่ม Raw เพื่อแสดงโค้ด จากนั้นให้คัดลอกโค้ดทั้งหมด

รูปที่ 28 คัดลอกไฟล์ตัวอย่าง hello_world_main.c

  • ให้สร้างโปรเจ็คใหม่ สร้างไฟล์ชื่อ hello_world_main.c แล้ววางโค้ดที่คุณคัดลอกไว้ลงในไฟล์นี้  เนื่องจากยังเป็นโค้ดตัวอย่างทดลอง ให้คุณคอมเม้นต์ // บรรทัดที่ 24 เป็น //esp_restart(); ดังรูป 29 จากนั้นอัปโหลดลง Node32s จะเห็นมีข้อความ Restarting in 9 – 0 second… แล้ว cpu ก็หยุดทำงาน

รูปที่ 29 ผลลัพธ์ที่ได้จากการโค้ดโปรแกรม hello_world_main.c

ฝากไว้เป็นอีกตัวเลือกนะครับ ที่ผมประทับใจกับ Platform IO นะครับ คือการทำงานที่รวดเร็วกว่า และ UI ดูทันสมัย ดูเป็น มืออาชีพ ขึ้นอีกที่นิดหนึ่ง เดี่ยวเจอกัน ในรอบหน้านะครับ จะหาอะไรสนุกๆมา update

Node32s และ NodeWifi สำหรับ Fritzing

$
0
0

เอาใจคนใช้ Fritzing กันหน่อยนะครับ ตอนนี้ทางผมได้ทำ Node32s และ NodeWifi ในเวอร์ชั่น Fritzing ให้แล้วครับ เอาไปใช้ทำภาพประกอบ ทำโปรเจค Workshop กันได้เลยครับ

ตามไปโหลดได้ที่  Github

รู้จักกับ NodeWIFI

$
0
0

Internet of Things เป็น แนวคิดที่ว่าต่อไปเราจะมีเครือข่ายของทุกสรรพสิ่ง พวกมันจะสื่อสารผ่านอินเตอร์เน็ทได้ ไม่ว่าจะเป็นเครื่องใช้ไฟฟ้าในบ้าน ทีวี ตู้เย็น หลอดไฟ ปลั้กไฟ จะถูกเชื่อมโยงเข้าด้วยกัน และ เราจะสามารถจะควบคุม จากที่ไหน ก็ได้ ทุกที่ทุกเวลา ซึ่งจะทำให้ชีวิตสะดวก สบายมากขึ้น และ ใช้ทรัพยกรได้อย่างมีประสิทธิ์

ไอเดียน่าจะเป็นจริงได้ อุปกรณ์และเซ็นเซอร์จำนวนมาก ต้องถูกเชื่อมต่อเข้ากับเน็ท ซึ่งหมายถึง อุปกรณ์ที่จะเชื่อมต่อต้องมีราคาที่ถูกมาก และ เครือข่ายอินเตอร์เน็ท กระจายอยู่ทุกมุมโลกอีกด้วย ที่ผ่านมา เราได้รู้จักบอร์ดที่มีการเชื่อมกับ wifi ได้หลายตัวกันไปแล้ว ไม่ว่าจะเป็น esp8266 ,NodeMCU และ ล่าสุดที่เป็นโปรดักส์ของทางเรา เป็น Node32s ในตอนนี้ทางเราภูมิใจเสนอ น้องใหม่ตัวเล็ก อย่าง NodeWIFI

รู้จักกับ Node-WIFI

Node-WIFI เป็นบอร์ดไมโครคอนโทรลเลอร์ตระกุล อย่าง ESP8285 ของบริษัท Espressif มาออกแบบเป็นบอร์ดพัฒนา โดย ESP8285 คือไอซี ESP8266 ที่เพิ่ม flash memory เข้าไปใน chip เลย ยังคงความสามารถเดียวกับ ESP8266 แต่ ขนาดวงจรเล็กกว่า และ ยังใช้ Arduino เขียนโปรแกรมได้ด้วย ซึ่งตัวอย่างของ ESP8266 กับการประยุกต์งาน IoT เยอะมาก ทั้งในและนอกประเทศ

ภาพรวมของ NodeWIFI(ESP8285)

  • ใช้ ESP8285 จาก Espressif ซึ่งเป็น WiFi
  • Breadboard Friendly มีขนาดกว้าง 0.9″ วางบน breadboard จะเหลือข้างล่ะ 1 ช่อง
  • ใช้ USB2Serial ตระกุล FTDI ชิปเพื่อการโหลดโปรแกรมแบบอัตโนมัติ ความเร็วสูงสุดถึง 921000
  • JST 2mm Connector สำหรับเสียบแบตเตอรี่
  • มีวงจรชาร์จ Lithium Ion และ Lithium Polymer (1 cell) พร้อมทั้งไฟแสดงสถานะ
  • มีวงจร PTC Fuse ตัดกระแสไฟเกินที่ 500mA
  • 3.3V 600mA On-board Voltage Regulator
  • Push ฺButton Switch ที่ขา IO0 และ EN (Reset)
  • เหมาะสำหรับงาน พัฒนาต้นแบบ อุปกรณ์รูปแบบ Portable และ Wearable

สำหรับ ESP-8285 ทำอะไรได้บ้าง

  • หน่วยประมวลผล Tensilica LX6
  • ความเร็ว สูงสุด 240MHz
  • แรมภายใน 52kB
  • WiFi transceiver B/G/N
  • ช่วงไฟ 2.2 ถึง  3.6V กินกระแสประมาณ 200mA
  • กินกระแส 2.5 µA (ในสถานะ deep sleep)
  • 15 GPIO
  • 1 ช่อง Analog-to-Digital converter (ADC)

สิ่งที่ต่างแตกจาก NodeWIFI(ESP8285) กับ ESP8266 ทั่วไป

  • ESP8285 เป็น SOC ที่ buildin FLASH 1MB เข้าไปด้วย ทำให้ไม่ต้องต่อ FLASH ภายนอก
  • NodeWIFI มี GPIO มากกว่า บอร์ด ESP8266 จำนวน 5 ขา เนื่องจากได้ I/O บางส่วนที่ไม่ต้องต่อ FLASH
  • ตัวโมดุลจะมีขา ส่วนมากที่เข้ากับ Node32s ซึ่งหมายความว่า ถ้าคุณเอาบอร์ดนี้ไปทำโปรเจค อยากเปลี่ยนไปอีกบอร์ด ก็ทำได้เลย
  • โมดุลนี้ รวมวงจร charge แบตตอรี่ เข้าไปแล้ว ทำใช้งาน portable ได้สะดวกมากๆ

ภาพแสดง PIN ที่ NodeWIFI และ Node32S เรียงเหมือนกัน

บอร์ด NodeWIFI จึงได้ความสามารถของ ESP8266 นั้นล่ะครับ แต่เราเพิ่มเติมเรื่องการใช้งานแบบไร้สายเป็นหลัก เราจึงได้ออกแบบอุปกรณ์ภายใน บอร์ดให้ใช้ไฟจากแบตเตอรี่ได้ และ มีวงจรชาร์จไฟแบตเตอรี Lipo เข้าไปอีกด้วย และ รวมกับ Community ของ ESP8266 เดิมที่ทำมาหลายปี ทำให้ เราใช้บอร์ด NodeWIF ทำโปรเจค IOT ได้ ไม่จำกัดอีกต่อไป

 

Powering the NodeWIFI

แหล่งพลังงานของ NodeWIFI ได้มาจากสองแหล่ง คือ USB และ แบตเตอรี่ (LiPo) ถ้าจ่ายทั้งสองแหล่ง บอร์ดจะใช้ไฟจาก USB และ ชาร์ทไฟ LiPo ไปด้วยในตัว ด้วยกระแสประมาณ 400mA ซึ่งเป็นแรงดันที่ไม่ทำให้ช่องจ่ายไฟ USB อันตราย (เราสามารถออกแบบชาร์จกระแสได้สูงกว่านี้ แต่ USB ของคอมท่านอาจจะจ่ายไม่ไหวนะครับ)

เราสามารถสลับไฟจาก USB กับ Batt ได้ทันที่ เราถอดสาย USB บอร์ดจะสลับไปใช้ไฟแบต ทันที่ แล้วต่อแบตอีกครั้งจะกลับไปใช้ไฟ USB

NodeWIFI  ทำงานช่วงไฟเลี้ยง 2.2 ถึง 3.6V. โดยปกติจะจ่ายไฟ 3.3V, และ I/O ของ ESP8285ไม่สามารถใช้ไฟ 5V ได้ (5V tolerant) เพราะฉะนั้นเพื่อความปลอดภัยควรมีไอซีเป็น Buffer หรือหา Shift Level ใส่ไว้ด้วย

ไฟเลี้ยงจาก 3.3V Regulator ของ Node-WIFI สามารถจ่ายกระแสได้ 600mA ตามสเป็คของ ESP ใช้กระแสสูงสุดถึง 250mA แต่เราวัดได้ 150mA  ซึ่งมากพอสำหรับงานอุปกรณ์จำพวก Mobile เพราะว่าใช้กระแสเยอะ แบตเตอรี่จะหมดเร็ว แต่ในกรณีที่มีความต้องการกระแสที่มากกว่านี้ แนะนำให้ ไฟ 3.3 V จากภายนอก ต่อเข้าขา “3V3”

สำหรับช่อง VIN สามารถ ขาสำหรับไฟเข้าได้ นอกจากทาง USB แต่รับไฟได้ไม่เกิน 6V นะครับ และช่อง VBATT สามารถนำไปต่อแบตเตอรี่ได้ หรือ นำไปจ่ายอุปกรณ์อื่น ที่ใช่ไฟแบต โดยตรงได้ สำหรับช่อง 3V3 สามารถรับไฟจากแหล่งข้างนอกโดยตรงได้ แต่ต้องอยู่ในช่วง 2.2V ถึง 3.6V  ช่องนี้ต่อตรงกับอุปกรณ์ภายในบอร์ด

Node32Lite : Simple Web Server

$
0
0

บทความนี้ เป็นบทความจุดประกายดีกว่า ไม่อยากจะบอกว่าสอนเขียนโปรแกรมใดๆ เอาว่าเป็นก้าวเดินเล็กๆ สำหรับ คนที่จะหัดเขียนโปรแกรมใช้งานบน Internet และ บนเน็ตเวิร์คไวไฟ (WiFi) กันนะครับ เพราะว่า มันก้าวกระโดดมาจาก Arduino ธรรมดามาพอสมควร ทั้งศัพท์ทางเน็ตเวิร์ค พวกโปรแกรมมิ่งอีก ใครที่มาจับ IoT ช่วงแรก งง แน่นอน

สำหรับบทความนี้ ใช้ได้กับ ทุกบอร์ด ในตระกุล ESP32 นะครับ แต่จะให้ดีใช้ Node32Lite ก้อแล้วกันครับ หลังจากที่ติดตั้ง ESP32 ไปแล้ว หากใครใจร้อนหน่อยลุยเล่นเองตาม Example ได้เลย สำหรับใครที่ไปถูก มาเริ่มตามทางเราก็ได้นะครับ

Simple Web Server

เวปเซิร์ฟเวอร์เป็นเซิร์ฟเวอร์แบบมี Request และ Response โดยหลักการเราเปิด TCP Server ในหมายเลข PORT 80 ก็สามารถทำ Server อย่างง่ายๆได้แล้ว โดยตัว ESP32 จะส่งค่า HTML ที่เป็นหน้าตาของเวปกลับไป และ Server จะรับคำสั่งผ่าน Request มาใช้เปิดปิด LED อีกด้วย

อุปกรณ์ ที่ต้องใช้ กับ วงจรง่ายๆ

ที่จริงแล้วเวปเซิร์ฟเวอร์ ตัวนี้ยังไม่สมบูรณ์ ค่อนข้างลักไก่นะครับ ;-D แต่ตัวอย่างนี้ทำให้เราเข้าใจหลักการของ HTTP Protocol และ การรับส่งข้อมูลของ Browser กับเวปเซิร์ฟเวอร์ ว่าสั่งง่ายๆแบบนี้ล่ะ ซึ่งในลำดับต่อไป จะเอา framework ที่น่าสนใจ ในการทำเวปเซิร์ฟเวอร์มาแนะนำอีกที่ ตอนนี้ หน้าเวปไม่ต้องสวยไปก่อนนะครับ

/*
 WiFi Web Server LED Blink

 A simple web server that lets you blink an LED via the web.
 This sketch will print the IP address of your WiFi Shield (once connected)
 to the Serial monitor. From there, you can open that address in a web browser
 to turn on and off the LED on pin 5.

 If the IP address of your shield is yourAddress:
 http://yourAddress/H turns the LED on
 http://yourAddress/L turns it off

 This example is written for a network using WPA encryption. For
 WEP or WPA, change the Wifi.begin() call accordingly.
 
 Circuit:
 * WiFi shield attached
 * LED attached to pin 5

 created for arduino 25 Nov 2012
 by Tom Igoe

ported for sparkfun esp32 
31.01.2017 by Jan Hendrik Berlin
 
 */

#include <WiFi.h>

const char* ssid     = "ssidHere";
const char* password = "passHere";

#define ledPIN 2

WiFiServer server(80);

void setup()
{
    Serial.begin(115200);
    pinMode(ledPIN, OUTPUT);      // set the LED pin mode

    delay(10);

    // We start by connecting to a WiFi network

    Serial.println();
    Serial.println();
    Serial.print("Connecting to ");
    Serial.println(ssid);

    WiFi.begin(ssid, password);

    while (WiFi.status() != WL_CONNECTED) {
        delay(500);
        Serial.print(".");
    }

    Serial.println("");
    Serial.println("WiFi connected.");
    Serial.println("IP address: ");
    Serial.println(WiFi.localIP());
    
    server.begin();

}

int value = 0;

void loop(){
 WiFiClient client = server.available();   // listen for incoming clients

  if (client) {                             // if you get a client,
    Serial.println("New Client.");           // print a message out the serial port
    String currentLine = "";                // make a String to hold incoming data from the client
    while (client.connected()) {            // loop while the client's connected
      if (client.available()) {             // if there's bytes to read from the client,
        char c = client.read();             // read a byte, then
        Serial.write(c);                    // print it out the serial monitor
        if (c == '\n') {                    // if the byte is a newline character

          // if the current line is blank, you got two newline characters in a row.
          // that's the end of the client HTTP request, so send a response:
          if (currentLine.length() == 0) {
            // HTTP headers always start with a response code (e.g. HTTP/1.1 200 OK)
            // and a content-type so the client knows what's coming, then a blank line:
            client.println("HTTP/1.1 200 OK");
            client.println("Content-type:text/html");
            client.println();

            // the content of the HTTP response follows the header:
            client.print("Click <a href=\"/H\">here</a> to turn the LED on.<br>");
            client.print("Click <a href=\"/L\">here</a> to turn the LED off.<br>");

            // The HTTP response ends with another blank line:
            client.println();
            // break out of the while loop:
            break;
          } else {    // if you got a newline, then clear currentLine:
            currentLine = "";
          }
        } else if (c != '\r') {  // if you got anything else but a carriage return character,
          currentLine += c;      // add it to the end of the currentLine
        }

        // Check to see if the client request was "GET /H" or "GET /L":
        if (currentLine.endsWith("GET /H")) {
          digitalWrite(ledPIN, HIGH);               // GET /H turns the LED on
        }
        if (currentLine.endsWith("GET /L")) {
          digitalWrite(ledPIN, LOW);                // GET /L turns the LED off
        }
      }
    }
    // close the connection:
    client.stop();
    Serial.println("Client Disconnected.");
  }
}

อธิบายโค๊ด

ตั้งค่า ssid กับ password ให้ตรงกับเน็ทเวิร์ก ที่ใช้งานด้วยนะครับ โดยขอย้ำว่า ตัวเล็ก ตัวใหญ่ ช่องว่าง ขีดกลาง ต้องใส่ให้ครบครับ มีผล

const char* ssid     = "ssidHere";
const char* password = "passHere";

ทำการประกาศ WiFiServer server(80); เพื่อใช้คลาส WiFiServer ซึ่งเป็น TCP Server เปิด พอร์ตไว้ที่ 80

WiFiServer server(80);

Setup()

ใน setup() เราจะกำหนดค่าเริ่มต้น เราต้องสั่งให้ esp32 เชื่อมต่อกับ SSID โดยผ่านคำสั่ง WiFi.begin(ssid, password); และ ตรวจสอบ WiFi.status() จนกระทั่งโมดุลมีการเชื่อมต่อสมบูรณ์

WiFi.begin(ssid, password);

while (WiFi.status() != WL_CONNECTED) {
  delay(500);
  Serial.print(".");
}

จากนั้นเริ่มต้นใช้งาน TCP Server ด้วยคำสั่ง Server.begin()

server.begin();

Loop()

ใน loop() เราโปรแกรมสิ่งที่จะเกิดขึ้นเมื่อมี client ใหม่เข้ามาเชื่อมต่อกับเวปเซิร์ฟเวอร์

โดยใน ESP32 จะรอการเชื่อมต่อ ผ่านบรรทัดนี้

WiFiClient client = server.available();   // listen for incoming clients

และเมือมีการ Request จาก Client หรือในที่นี้คือ Browser เราจะบันทึกค่าไว้ในตัวแปร currentLine จนกระทั่ง มี newline เข้ามาจะทำการเริ่มบันทึกใหม่

เมื่อ currentLine หรือ ข้อมูล Request เข้ามา ตัว server ทำการส่ง Response กลับทั้นที่ ซึ่งแสดงในบรรทัด 12 ถึง 22 ซึ่งจะเป็นหน้า Page ที่เราเห็นใน Browser

if (client) {                             // if you get a client,
   Serial.println("New Client.");           // print a message out the serial port
   String currentLine = "";                // make a String to hold incoming data from the client
   while (client.connected()) {            // loop while the client's connected
     if (client.available()) {             // if there's bytes to read from the client,
       char c = client.read();             // read a byte, then
       Serial.write(c);                    // print it out the serial monitor
       if (c == '\n') {                    // if the byte is a newline character

         // if the current line is blank, you got two newline characters in a row.
         // that's the end of the client HTTP request, so send a response:
         if (currentLine.length() == 0) {
           // HTTP headers always start with a response code (e.g. HTTP/1.1 200 OK)
           // and a content-type so the client knows what's coming, then a blank line:
           client.println("HTTP/1.1 200 OK");
           client.println("Content-type:text/html");
           client.println();

           // the content of the HTTP response follows the header:
           client.print("Click <a href=\"/H\">here</a> to turn the LED on.<br>");
           client.print("Click <a href=\"/L\">here</a> to turn the LED off.<br>");

           // The HTTP response ends with another blank line:
           client.println();
           // break out of the while loop:
           break;
         } else {    // if you got a newline, then clear currentLine:
           currentLine = "";
         }
       } else if (c != '\r') {  // if you got anything else but a carriage return character,
         currentLine += c;      // add it to the end of the currentLine
       }

     }
   }

โดยถ้า currentLine มีคำสั่ง “GET /H” จะทำการเปิด LED และ ถ้า “GET /L” จะไปสั่งปิด LED ซึ่งคำสั่งนี้ มันได้มาจาก การคลิกหน้า page ที่เราสร้างขึ้นมาล่ะครับ

// Check to see if the client request was "GET /H" or "GET /L":
if (currentLine.endsWith("GET /H")) {
   digitalWrite(ledPIN, HIGH);               // GET /H turns the LED on
}
if (currentLine.endsWith("GET /L")) {
   digitalWrite(ledPIN, LOW);                // GET /L turns the LED off
}

ถึงตรงนี้ จะเห็นว่า หลักการเบื้องต้น มันง่ายมาก ใช่ไหมครับ

ผลลัพท์

ไฟกระพริบผ่าน Web

Static/FIX IP Address

ทุกครั้งที่มีการ Reboot ระบบเน็กเวิร์ก หมายเลข IP ESP32  อาจจะไม่ใช่หมายเลขเดิมครับ มันจะรันใหม่ทุกครั้งตาม DHCP  ซึ่งผมจะแถมให้อีกนิด ถ้าเราจะ Fix IP หรือจะกำหนดไอพี จะทำอย่างไง

อันนี้ให้เอาตัวอย่างอันที่ 2 มาเพิ่มโค๊ดที่ Header ดังข้างล่าง

โดยเราจะกำหนดให้ ESP32 มี IP 192.168.1.115 เชื่อมต่อกับ Gateway IP 192.168.1.1

IPAddress local_IP(192, 168, 1, 115);
IPAddress gateway(192, 168, 1, 1);
IPAddress subnet(255, 255, 255, 0);
IPAddress primaryDNS(8, 8, 8, 8); //optional
IPAddress secondaryDNS(8, 8, 4, 4); //optional

หมายเลขดังกล่าวสามารถเปลี่ยนได้ และ อาจจะไม่เวิร์กกับ network ที่ท่านทดลองนะครับ อย่างไงลองศึกษาอีกนิด

Setup()

เราจะกำหนด fix ip ด้วยคำสั่ง WiFi.config() 

// Configures static IP address
if (!WiFi.config(local_IP, gateway, subnet, primaryDNS, secondaryDNS)) {
  Serial.println("STA Failed to configure");
}

จากนั้นทุกอย่างจะเหมือนเดิม แค่ตอนนี้ จะ reset Network กี่รอบ ก็ยังได้ IP มาด้วย

/*
 WiFi Web Server LED Blink

 A simple web server that lets you blink an LED via the web.
 This sketch will print the IP address of your WiFi Shield (once connected)
 to the Serial monitor. From there, you can open that address in a web browser
 to turn on and off the LED on pin 5.

 If the IP address of your shield is yourAddress:
 http://yourAddress/H turns the LED on
 http://yourAddress/L turns it off

 This example is written for a network using WPA encryption. For
 WEP or WPA, change the Wifi.begin() call accordingly.
 
 Circuit:
 * WiFi shield attached
 * LED attached to pin 5

 created for arduino 25 Nov 2012
 by Tom Igoe

ported for sparkfun esp32 
31.01.2017 by Jan Hendrik Berlin
 
 */

#include <WiFi.h>

const char* ssid     = "duck Family2.4GHz";
const char* password = "212224236";

IPAddress local_IP(192, 168, 1, 115);
IPAddress gateway(192, 168, 1, 1);
IPAddress subnet(255, 255, 0, 0);
IPAddress primaryDNS(8, 8, 8, 8); //optional
IPAddress secondaryDNS(8, 8, 4, 4); //optional

#define ledPIN 2

WiFiServer server(80);

void setup()
{
    Serial.begin(115200);
    
    if (!WiFi.config(local_IP, gateway, subnet, primaryDNS, secondaryDNS)) {
      Serial.println("STA Failed to configure");
    }

    pinMode(ledPIN, OUTPUT);      // set the LED pin mode

    delay(10);

    // We start by connecting to a WiFi network

    Serial.println();
    Serial.println();
    Serial.print("Connecting to ");
    Serial.println(ssid);

    WiFi.begin(ssid, password);

    while (WiFi.status() != WL_CONNECTED) {
        delay(500);
        Serial.print(".");
    }

    Serial.println("");
    Serial.println("WiFi connected.");
    Serial.println("IP address: ");
    Serial.println(WiFi.localIP());
    
    server.begin();

}

int value = 0;

void loop(){
 WiFiClient client = server.available();   // listen for incoming clients

  if (client) {                             // if you get a client,
    Serial.println("New Client.");           // print a message out the serial port
    String currentLine = "";                // make a String to hold incoming data from the client
    while (client.connected()) {            // loop while the client's connected
      if (client.available()) {             // if there's bytes to read from the client,
        char c = client.read();             // read a byte, then
        Serial.write(c);                    // print it out the serial monitor
        if (c == '\n') {                    // if the byte is a newline character

          // if the current line is blank, you got two newline characters in a row.
          // that's the end of the client HTTP request, so send a response:
          if (currentLine.length() == 0) {
            // HTTP headers always start with a response code (e.g. HTTP/1.1 200 OK)
            // and a content-type so the client knows what's coming, then a blank line:
            client.println("HTTP/1.1 200 OK");
            client.println("Content-type:text/html");
            client.println();

            // the content of the HTTP response follows the header:
            client.print("Click <a href=\"/H\">here</a> to turn the LED on.<br>");
            client.print("Click <a href=\"/L\">here</a> to turn the LED off.<br>");

            // The HTTP response ends with another blank line:
            client.println();
            // break out of the while loop:
            break;
          } else {    // if you got a newline, then clear currentLine:
            currentLine = "";
          }
        } else if (c != '\r') {  // if you got anything else but a carriage return character,
          currentLine += c;      // add it to the end of the currentLine
        }

        // Check to see if the client request was "GET /H" or "GET /L":
        if (currentLine.endsWith("GET /H")) {
          digitalWrite(ledPIN, HIGH);               // GET /H turns the LED on
        }
        if (currentLine.endsWith("GET /L")) {
          digitalWrite(ledPIN, LOW);                // GET /L turns the LED off
        }
      }
    }
    // close the connection:
    client.stop();
    Serial.println("Client Disconnected.");
  }
}

ผลลัพท์

เมื่อเรา Reset ESP32 จะเห็นว่าใน Serial Monitor พิมพ์ ip ว่าเป็น 192.168.1.115 แล้วถ้าพิมพ์เข้าใน browser จะเข้าไปหน้าเวปได้

ผลลัพท์

สุดท้าย

จบแล้วสำหรับบทความแนวฝึกสอนนะครับ Goal จริงๆ จะเขียนในละเอียดๆ ทำไปเรื่อยๆ เผื่อทำหนังสือบ้าง ที่จริงทางผมชอบทำโปรเจคมากกว่า แต่ตอนนี้ ดูในเวป document ดีๆหายากจังครับ ถ้าใครสนใจจะช่วย หรือ จะมาแจมแจ้งได้เลยนะครับ ผมไม่ห่วงเลยแม้แต่น้อย และ ถ้ามีติดปัญหาอะไร หรือ มีข้อสงสัย อีเมล์ แจ้งเข้ามาได้

Node32Lite : การใช้งานอินพุตเอาต์พุตพื้นฐานของ ESP32

$
0
0

กล่าวได้ว่า 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 ที่เด่นๆ ไปแล้วนะครับ ผมว่าการทดลองเล่นจะทำให้เกิดไอเดีย สักวันพอเราเจอปัญหาอะไรบ้างอย่าง เราอาจจะหยิบ เอาไอเดีย ที่เคยผ่านหู ผ่านตา เอาไปลองบ้าง

อุปกรณ์วัดคุณภาพอากาศ ฝุ่น PM2.5 แบบทำเองก้อได้

$
0
0

สถานการณ์ของฝุ่น PM2.5 ตอนนี้ในกรุงเทพเข้าขั้นวิกฤติ ซึ่งจะเห็นได้ว่าอุปกรณ์ที่เกี่ยวกับกันฝุ่นขายหมดกลิ้ง ทั้งหน้ากาก N95 ทั้งเครื่องกรองอากาศ อันไหนใครว่าดี ขายหมด Out of order อย่างรวดเร็ว เลยมันเป็นบทความพิเศษของวันนี้ครับ คือทางผมอยากได้เครื่องวัด PM2.5  ตอนนี้มีลูกเล็ก เห็นท้องฟ้าใสๆ ไม่แน่ใจว่า ฝุ่นจะเป็นอย่างไง ซึ่งตอนนี้ ก็ สั่งซื้อเครื่องวัดไปแล้วก่อนตรุษจีน แต่ของยังมาไม่ถึง ผมเลยต้องแสดงฝีมือทำเองใช้ชั่วคราวก่อน

ช่วงนี้ แม้แต่มิเตอร์วัดฝุ่น ยังขึ้นราคา เคยเห็นพันกว่า ตอนนี้กระโดดไป สามพันแล้ว แต่ผมโชคดี ที่เคยมีไว้หมดแล้วครับ โดยอุปกรณ์นี้จะใช้ Dust Sensor ของ Plantower รุ่น PMSA003 เป็นแบบ Laser Particle Sensor วัดการกระเจิงความแสงที่ผ่านฝุ่น โดยเซ็นเซอร์ตัวนี้ผมได้มานานแล้ว จาก aliexpress ซื้อตอนนี้คงหาไม่ได้แน่ๆ มีตัวไหนก้อใช้ตัวนั้นล่ะครับ โดยผมให้มันแสดงค่า PM1.0 PM2.5 PM10 และ มันยังเปลี่ยนสีตามเลขดัชนีอีกด้วยครับ เลยทำให้ดูง่าย ขึ้นสีแดงเมื่อไร กลับบ้านเลยดีกว่า

  

ทำความเข้าใจ PM2.5

  1. ฝุ่นละออง PM 2.5 (Particle Matter Smaller Than 2.5 Micron) คือ ฝุ่นละอองที่มีขนาดเล็กมาก มองไม่เห็น คือเล็กกว่า 2.5 ไมครอน (ไมโครเมตร) หรือ เล็กกว่า 3% ของเส้นผ่านศูนย์กลางเส้นผมเสียอีก ในเมืองใหญ่นั้น สาเหตุหลักๆ คือ จากการเผาไหม้ของเครื่องยนต์ และจากการก่อสร้าง แต่ ฝุ่นยิ่งเล็กแทนที่มันจะตกลงสู่พื้น มันกลับยิ่งแขวนลอยอยู่ในอากาศนานยิ่งขึ้น
  2. ฝุ่น PM 2.5 มีอันตรายต่อสุขภาพอย่างชัดเจน เพราะการที่มันเล็กมาก ทำให้มันสามารถผ่านทางเดินหายใจสู่ปอดและสร้างปัญหากับหลอดเลือดได้ง่ายขึ้นเยอะ (พวกที่มีขนาดใหญ่ มักจะโดนดักเอาไว้ตั้งแต่ด้วยขนจมูก และด้วยเมือกและขนโบกตามช่องทางเดินหายใจ) และจะไปเพิ่มความเสี่ยงต่อการเกิดโรคหัวใจและโรคทางเดินหายใจ เพราะมันสามารถทะลุทะลวงผ่านปอดเข้าสู่เส้นเลือดฝอยที่หล่อเลี้ยงอวัยวะทุกส่วนของร่างกาย รวมทั้งสมองและหัวใจ ทำให้เกิดโรคมะเร็ง โรคหัวใจ โรคทางสมอง ฯลฯ แม้กระทั่งทารกในครรภ์ที่แม่สัมผัสกับอนุภาค PM2.5 ก็จะทำให้เด็กเมื่อโตขึ้นมีโอกาสเสี่ยงต่อโรคต่าง ๆ ดังกล่าว และยังมีผลต่อระดับสติปัญญาของเด็กอีกตลอดชีวิต
  3. ฝุ่นละออง PM10 เป็นอนุภาคที่มีขนาดใหญ่กว่า มองเห็นด้วยตาเปล่าได้ ถือเป็นอีกมลพิษ

M5Stack PM2.5 Meter

สำหรับโปรเจคนี้ ผมเอา M5 Stack มาใช้นะครับ คิดว่า งานมันน่าจะเหมาะกับอะไรที่มีจอสี อยู่แล้ว หยิบใช้เลย ส่วน Sensor วัดฝุ่น ยี่ห้อ Plantower เป็นแบบ Laser Particle Sensor

  1. M5Stack Basic  จาก Gravitech หรือ inex หรือจะสั่งจาก aliexpress ก้อได้ ฮ่าๆ ถ้ารอได้ ข้อด๊ มันมีจอ มีแบตพร้อม มาพร้อมกับเคสสวยๆ ราคาประหยัด

2. PM2.5 Sensor ผมใช้ Plantower  Model: PMSA003 แต่ในไทย ใช้ PMS7003 ของ Gravitech แทนก็ได้นะครับ สิ่งที่ต่างกัน ขนาด กับ ช่องเข้า กับ ช่องลมออก

ในโอกาศหน้าจะทำในรูปแบบที่ราคาถูกลงมานะครับ หลายคนอาจจะสงสัยว่า Sensor ตัวนี้มันน่าจะเชื่อถือได้ไหม ซึ่งจากข้อมูลเพิ่มเติมจากเอกสารของ การศึกษาเซ็นเซอร์หลักการทางแสงราคาถูกสำหรับงานตรวจวัดฝุ่นละอองในอากาศ พบว่า Sensor ของ Plantower มีความ reliability เชื่อถือได้ คงเส้นคงวา แต่จะเอาให้ถูกแป๊ะเลย ต้องเอาไปสอบเทียบครับ แต่ถึงมันไม่ถูกต้อง 100% เราเอามาใช้ดูแนวโน้มล่ะครับ

วิธีการอ่านค่าจาก Plantower นะครับ

โมดุล Plantower  แค่จ่ายไฟให้โมดุล ค่าที่อ่านได้จะออกมาทาง Uart โดยความยาว Package ยาว 32 Byte ประกอบไปด้วยปริมาณฝุ่น PM1.0 , PM2.5 , PM10

/*
PMS1003, PMS5003, PMS7003:
  32 byte long messages via UART 9600 8N1 (3.3V TTL).
DATA(MSB,LSB): Message header (4 bytes), 2 pairs of bytes (MSB,LSB)
  -1(  1,  2): Begin message       (hex:424D, ASCII 'BM')
   0(  3,  4): Message body length (hex:001C, decimal 28)
DATA(MSB,LSB): Message body (28 bytes), 14 pairs of bytes (MSB,LSB)
   1(  5,  6): PM 1.0 [ug/m3] (TSI standard)
   2(  7,  8): PM 2.5 [ug/m3] (TSI standard)
   3(  9, 10): PM 10. [ug/m3] (TSI standard)
   4( 11, 12): PM 1.0 [ug/m3] (std. atmosphere)
   5( 13, 14): PM 2.5 [ug/m3] (std. atmosphere)
   6( 15, 16): PM 10. [ug/m3] (std. atmosphere)
   7( 17, 18): num. particles with diameter > 0.3 um in 100 cm3 of air
   8( 19, 19): num. particles with diameter > 0.5 um in 100 cm3 of air
   9( 21, 22): num. particles with diameter > 1.0 um in 100 cm3 of air
  10( 23, 24): num. particles with diameter > 2.5 um in 100 cm3 of air
  11( 25, 26): num. particles with diameter > 5.0 um in 100 cm3 of air
  12( 27, 28): num. particles with diameter > 10. um in 100 cm3 of air
  13( 29, 30): Reserved
  14( 31, 32): cksum=byte01+..+byte30
*/

โดยอุปกรณ์นี้ เรียกค่า std. atmosphere เป็น Byte ที่ 11-16 มาใช้นะครับ

Hardware

โมดุลที่ผมได้ว่าจะมาสายต่อดังภาพครับ ให้หัน Connector ไปตามภาพนะครับ แล้วใช้

  • สายที่ 1 –  VCC  -> (+5V)
  • สายที่ 2 – GND ->  (GND)
  • สายที่ 3 – RxD -> GPIO16
  • สายที่ 4 – TxD -> GPIO17 (ไม่จำเป็น)

Index AQI

โดยสี ผมจะเอามาจากค่า ดัชนีคุณภาพอากาศ ผมเอามาจาก Air4Thai ซึ่งเขาจะระบุว่า การวัด ดัชนีคุณภาพอากาศ ซึ่งค่าวัดมลพิษทั้งหมด 6 ชนิด วิธีการเทียบดูว่าถ้าใดมีผลทำให้ AQI มากสุด ให้แสดงค่านั้นๆ แปลว่า มิเตอร์นี้ไม่ได้ครอบคลุมทุกมลพิษนะครับ วัดแค่ PM2.5 โดยมลพิษที่เอามาประเมิน มีด้วยกัน 6 ชนิด

  • ฝุ่นละอองขนาดไม่เกิน 2.5 ไมครอน (PM2.5) เป็นฝุ่นที่มีเส้นผ่านศูนย์กลางไม่เกิน 2.5 ไมครอน เกิดจากการเผาไหม้ทั้งจากยานพาหนะ การเผาวัสดุการเกษตร ไฟป่า และกระบวนการอุตสาหกรรม สามารถเข้าไปถึงถุงลมในปอดได้ เป็นผลทําให้เกิดโรคในระบบทางเดินหายใจ และโรคปอดต่างๆ หากได้รับในปริมาณมากหรือเป็นเวลานานจะสะสมในเนื้อเยื่อปอด ทําให้การทํางานของปอดเสื่อมประสิทธิภาพลง ทําให้หลอดลมอักเสบ มีอาการหอบหืด
  • ฝุ่นละอองขนาดไม่เกิน 10 ไมครอน (PM10) เป็นฝุ่นที่มีขนาดเส้นผ่านศูนย์กลางไม่เกิน 10 ไมครอน เกิดจากการเผาไหม้เชื้อเพลิง การเผาในที่โล่ง กระบวนการอุตสาหกรรม การบด การโม่ หรือการทําให้เป็นผงจากการก่อสร้าง ส่งผลกระทบต่อสุขภาพเนื่องจากเมื่อหายใจเข้าไปสามารถเข้าไปสะสมในระบบทางเดินหายใจ
  • ก๊าซโอโซน (O3) เป็นก๊าซที่ไม่มีสีหรือมีสีฟ้าอ่อน มีกลิ่นฉุน ละลายน้ำได้เล็กน้อย เกิดขึ้นได้ทั้งในระดับบรรยากาศชั้นที่สูงจากผิวโลก และระดับชั้นบรรยากาศผิวโลกที่ใกล้พื้นดิน ก๊าซโอโซนที่เป็นสารมลพิษทางอากาศคือก๊าซโอโซนในชั้นบรรยากาศผิวโลก เกิดจากปฏิกิริยาระหว่างก๊าซออกไซด์ของไนโตรเจน และสารประกอบอินทรีย์ระเหยง่าย โดยมีแสงแดดเป็นตัวเร่งปฏิกิริยา มีผลกระทบต่อสุขภาพ โดยก่อให้เกิดการระคายเคืองตาและระคายเคืองต่อระบบทางเดินหายใจและเยื่อบุต่างๆ ความสามารถในการทำงานของปอดลดลง เหนื่อยเร็ว โดยเฉพาะในเด็ก คนชรา และคนที่เป็นโรคปอดเรื้อรัง
  • ก๊าซคาร์บอนมอนอกไซด์ (CO) เป็นก๊าซที่ไม่มีสี กลิ่น และรส เกิดจากการเผาไหม้ที่ไม่สมบูรณ์ของเชื้อเพลิงที่มีคาร์บอนเป็นองค์ประกอบ ก๊าซนี้สามารถสะสมอยู่ในร่างกายได้โดยจะไปรวมตัวกับฮีโมโกลบินในเม็ดเลือดแดงได้ดีกว่าออกซิเจนประมาณ 200-250 เท่า เมื่อหายใจเข้าไปทำให้ก๊าซชนิดนี้จะไปแย่งจับกับฮีโมโกลบินในเลือด เกิดเป็นคาร์บอกซีฮีโมโกลบิน (CoHb) ทำให้การลำเลียงออกซิเจนไปสู่เซลล์ต่างๆ ของร่างกายลดน้อยลง ส่งผลให้ร่างกายเกิดอาการอ่อนเพลีย และหัวใจทำงานหนักขึ้น
  • ก๊าซไนโตรเจนไดออกไซด์ (NO2) เป็นก๊าซที่ไม่มีสีและกลิ่น ละลายน้ำได้เล็กน้อย มีอยู่ทั่วไปในธรรมชาติ หรือเกิดจากการกระทำของมนุษย์ เช่น การเผาไหม้เชื้อเพลิงต่างๆ อุตสาหกรรมบางชนิด เป็นต้น ก๊าซนี้มีผลต่อระบบการมองเห็นและผู้ที่มีอาการหอบหืดหรือ โรคเกี่ยวกับทางเดินหายใจ
  • ก๊าซซัลเฟอร์ไดออกไซด์ (SO2) เป็นก๊าซที่ไม่มีสี หรืออาจมีสีเหลืองอ่อนๆ มีรสและกลิ่นที่ระดับความเข้มข้นสูง เกิดจากธรรมชาติและการเผาไหม้เชื้อเพลิงที่มีกำมะถัน (ซัลเฟอร์) เป็นส่วนประกอบ สามารถละลายน้ำได้ดี สามารถรวมตัวกับสารมลพิษอื่นแล้วก่อตัวเป็นอนุภาคฝุ่นขนาดเล็กได้ ก๊าซนี้มีผลกระทบโดยตรงต่อสุขภาพ ทำให้เกิดการระคายเคืองต่อเยื่อบุตา ผิวหนัง และระบบทางเดินหายใจ หากได้รับเป็นเวลานาน ๆ จะทำให้เป็นโรคหลอดลมอักเสบเรื้อรังได้้

เพื่อความง่าย ให้เข้าถึงง่าย เขาจึงแบ่งสีหรือระดับความปลอดภัยไว้ ทั้งหมด 5 ระดับนะครับ 5 สีด้วย ดังภาพ

โดย PM2.5 และ PM10 ความเข้มข้นของสารมลพิษ ที่เทียบเท่ากับ ค่าดัชนีคุณภาพอากาศ

AQI
PM2.5
(มคก./ลบ.ม.)
PM10
(มคก./ลบ.ม.)
เฉลี่ย 24 ชั่วโมงต่อเนื่อง
ดีมาก (0 – 25) 0 – 25 0 – 50
ดี (26 – 50) 26 – 37 51 – 80
ปานกลาง (51 – 100) 38 – 50 81 – 120
เริ่มมีผลกระทบกับสุขภาพ (101 – 200) 51 – 90 121 – 180
มีผลกระทบกับสุขภาพ (มากกว่า 200) 91 ขึ้นไป 181 ขึ้นไป

โดยสุดท้าย ผมเอาค่าที่อ่านได้จาก Dust Sensor มาเทียบกับหาค่า AQI แล้วแสดงผลครับ โดยตอนนี้ทำแค่ AQI ของ PM2.5 ก่อนนะครับ ผมยึดเกณท์ PM2.5 อยู่ไม่เกิน 0-50 ug/m3 ถ้า ยังถือว่าปลอดภัย ซึ่งถ้าสีฟ้า หรือ เขียว ดูเป็นแนวโน้มแล้วกันครับ

Source Code

โปรแกรมของทางผม ไม่ได้ใช้ Lib เพิ่มเติมนะครับ เก็บข้อมูลจาก Uart2 แล้ว เอามาคำนาณ แสดงผลเลย ข้อดี ผมว่าถ้ามี sensor ตัวอื่นที่เป็น serial น่าจะเอาประยุกต์ ใช้งานได้เลยไม่ต้องรอ คนทำ Lib นะครับ

#include <M5Stack.h>
#include <HardwareSerial.h>

//const uint8_t PMS_RX=16, PMS_TX=17;
HardwareSerial pmsSerial(2); // UART2 on GPIO16(RX),GPIO17(TX)

// Stock font and GFXFF reference handle
#define GFXFF 1
#define FF18 &FreeSans12pt7b

#define CF_OL24 &Orbitron_Light_24
#define CF_OL32 &Orbitron_Light_32
#define CF_RT24 &Roboto_Thin_24
#define CF_S24  &Satisfy_24
#define CF_Y32  &Yellowtail_32

int PM25AQI;

void setup() {
  M5.begin();
   
  // our debugging output
  Serial.begin(115200);
  
  // sensor baud rate is 9600
  pmsSerial.begin(9600);
}
 
struct pms7003data {
  uint16_t framelen;
  uint16_t pm10_standard, pm25_standard, pm100_standard;
  uint16_t pm10_env, pm25_env, pm100_env;
  uint16_t particles_03um, particles_05um, particles_10um, particles_25um, particles_50um, particles_100um;
  uint16_t unused;
  uint16_t checksum;
};
 
struct pms7003data data;
    
void loop() {

if (readPMSdata(&pmsSerial)) {
    M5.Lcd.setFreeFont(FF18);                 // Select the font
    M5.Lcd.setTextSize(0.5);
    
    if (data.pm25_env <= 25) {
      M5.Lcd.fillScreen(TFT_BLUE);           
      M5.Lcd.setTextColor(TFT_WHITE, TFT_BLUE);    
      M5.Lcd.drawString("GOOD", 160, 160, GFXFF);// Print the string name of the font
      //Good
      PM25AQI = data.pm25_env;
    } else if  ( (data.pm25_env >= 26) &&  (data.pm25_env <= 37) ) {
      M5.Lcd.fillScreen(TFT_GREEN);
      M5.Lcd.setTextColor(TFT_WHITE, TFT_GREEN);
      M5.Lcd.drawString("Moderate", 160, 160, GFXFF);
      //Moderate
      PM25AQI = map(data.pm25_env,26,37,26,50);
    } else if  ( (data.pm25_env >= 38) &&  (data.pm25_env <= 50) ) {
      M5.Lcd.fillScreen(TFT_GREENYELLOW);
      M5.Lcd.setTextColor(TFT_WHITE, TFT_GREENYELLOW);
      M5.Lcd.drawString("unhealthy", 160, 160, GFXFF);t
      //unhealthy for kid
      PM25AQI = map(data.pm25_env,38,50,51,100);
    } else if  ( (data.pm25_env >= 51) &&  (data.pm25_env <= 90) ) {
       M5.Lcd.fillScreen(TFT_ORANGE);           
      M5.Lcd.setTextColor(TFT_WHITE, TFT_ORANGE);
      M5.Lcd.drawString("very unhealthy", 160, 160, GFXFF);  
      //very unhealthy
      PM25AQI = map(data.pm25_env,51,90,101,200);
    } else if (data.pm25_env >= 91) {
      M5.Lcd.fillScreen(TFT_RED);
      M5.Lcd.setTextColor(TFT_WHITE, TFT_RED);
      //Hazardous
      PM25AQI= 201;
      M5.Lcd.drawString("Hazardous", 160, 160, GFXFF);
    } 
  
  //  M5.Lcd.setTextSize(1);
    M5.Lcd.drawString("PM2.5(AQI)", 0, 20, GFXFF);
    
    M5.Lcd.setTextDatum(ML_DATUM);
    M5.Lcd.drawString("PM1:", 0, 195, GFXFF);/
    M5.Lcd.drawNumber( data.pm10_env, 80, 195);
    M5.Lcd.drawString("PM2.5:", 160, 195, GFXFF);
    M5.Lcd.drawNumber( data.pm25_env, 240, 195);
    M5.Lcd.drawString("PM10:", 0, 220, GFXFF);
    M5.Lcd.drawNumber( data.pm100_env, 80, 220);
    
    //M5.Lcd.setTextPadding(80);
    M5.Lcd.setTextDatum(MC_DATUM);
    M5.Lcd.setFreeFont(CF_OL32);
    M5.Lcd.setTextSize(2);
    if (PM25AQI < 200) {
      M5.Lcd.drawNumber( PM25AQI, 160, 100);    
    } else {      
      M5.Lcd.drawString("Over 200", 160, 100, GFXFF);// Print the string name of the font
    }
    
    // Reset text padding to zero (default)
    M5.Lcd.setTextPadding(0);

    printTest();  //debug
 }

  
}

void printTest() {
  
    // reading data was successful!
    Serial.println();
    Serial.println("---------------------------------------");
    Serial.println("Concentration Units (standard)");
    Serial.print("PM 1.0: "); Serial.print(data.pm10_standard);
    Serial.print("\t\tPM 2.5: "); Serial.print(data.pm25_standard);
    Serial.print("\t\tPM 10: "); Serial.println(data.pm100_standard);
    Serial.println("---------------------------------------");
    Serial.println("Concentration Units (environmental)");
    Serial.print("PM 1.0: "); Serial.print(data.pm10_env);
    Serial.print("\t\tPM 2.5: "); Serial.print(data.pm25_env);
    Serial.print("\t\tPM 10: "); Serial.println(data.pm100_env);
    Serial.println("---------------------------------------");
    Serial.print("Particles > 0.3um / 0.1L air:"); Serial.println(data.particles_03um);
    Serial.print("Particles > 0.5um / 0.1L air:"); Serial.println(data.particles_05um);
    Serial.print("Particles > 1.0um / 0.1L air:"); Serial.println(data.particles_10um);
    Serial.print("Particles > 2.5um / 0.1L air:"); Serial.println(data.particles_25um);
    Serial.print("Particles > 5.0um / 0.1L air:"); Serial.println(data.particles_50um);
    Serial.print("Particles > 10.0 um / 0.1L air:"); Serial.println(data.particles_100um);
    Serial.println("---------------------------------------");
  
}
boolean readPMSdata(Stream *s) {
  if (! s->available()) {
    return false;
  }
  
  // Read a byte at a time until we get to the special '0x42' start-byte
  if (s->peek() != 0x42) {
    s->read();
    return false;
  }
 
  // Now read all 32 bytes
  if (s->available() < 32) {
    return false;
  }
    
  uint8_t buffer[32];    
  uint16_t sum = 0;
  s->readBytes(buffer, 32);
 
  // get checksum ready
  for (uint8_t i=0; i<30; i++) {
    sum += buffer[i];
  }
 
  /* debugging
  for (uint8_t i=2; i<32; i++) {
    Serial.print("0x"); Serial.print(buffer[i], HEX); Serial.print(", ");
  }
  Serial.println();
  */
  
  // The data comes in endian'd, this solves it so it works on all platforms
  uint16_t buffer_u16[15];
  for (uint8_t i=0; i<15; i++) {
    buffer_u16[i] = buffer[2 + i*2 + 1];
    buffer_u16[i] += (buffer[2 + i*2] << 8);
  }
 
  // put it into a nice struct 🙂
  memcpy((void *)&data, (void *)buffer_u16, 30);
 
  if (sum != data.checksum) {
    Serial.println("Checksum failure");
    return false;
  }
  // success!
  return true;
}

เอาล่ะครับ ตอนนี้ เราก้อได้ อุปกรณ์คุณภาพอากาศ PM2.5 แล้ว ตะลุยวัดฝุ่นกันได้เลยครับ

เพิ่มเติมอีกนิดพฤติกรรมของฝุ่น

อนุภาค PM2.5 มันเบาและลอยอยู่สูง แต่คนที่อยู่ภายในบ้าน ใช่กว่าจะปลอดภัยนะครับ ถ้าสภาพข้างนอกดูมีมืดๆ เหมือนฝนตก รีบปิดประตูทุกบานได้เลยครับ อันนี้ประสบการณ์จากที่ผมเดินวัดฝุ่นมา ฝุ่น PM2.5 มันมองไม่เห็น มันไหลเข้ามากับอากาศปกติได้ มันจะทำให้ระคายทางเดินหายใจได้ ถ้าใครรู้สีก หายใจลำบาก เริ่มไอ รีบหาหน้ากาก หรือ เครื่องฟอกอากาศมาด่วนเลย และ ต่อให้มีแอร์ แต่แอร์ไม่ได้มีกรอง PM2.5 ก้อใช่ว่าจะปลอดภัยนะครับเพราะว่า อนุภาค PM2.5 มันลอยอยู่ในอากาศในห้องล่ะครับ ฉะนั้นถ้ารู้สึกไม่ดี รีบปรับปรุงแก้ไขกันด่วน

1.กรองอากาศภายในรถนะครับ 

เครื่องกรองแอร์ของรถทุกรุ่น จะเป็น HEPA อยู่แล้ว เราไม่จำเป็นต้องซื้อเพิ่ม นอกจาก รถเก่ามาก กับ แอร์ทำงานได้ไม่ดี ถึงจะมี HEPA แต่ต้องมั่นเปลี่ยนบ่อยๆนะครับ อันนี้คือที่ผมว่าภายในรถ ปกติจะเกิน 50-80 ug/m3 แต่เขามาในรถจะเหลือ 20 ug/m3 ได้

ถ้าที่วัดได้ภายในรถยนต์ ที่เห็นไส้กรองดำๆ นั้นล่ะครับ วัดได้ 21 ครับ

2.ห้องนอน

ถ้ายังไม่มีเครื่องกรองแนะนำให้ใช้ filtrete 3M ที่เป็นแผ่นใยไฟฟ้าสถิต มาติดที่ filter เพิ่มนะครับ ลดได้เยอะเหมือนกัน

เอา filter มาห่อ ใส่กลับเข้าไป

ตอนนี้ปิดได้ 1 อาทิตย์แล้ว คาดว่าคงเปลี่ยนบ่อยนะครับ และ ก้อเครื่องฟอก ลดฝุ่นได้เร็วกว่า

อ้างอิง


ปรับปรุง อุปกรณ์วัดคุณภาพอากาศ ฝุ่น PM2.5

$
0
0

จากช่วงต้นปี เกิดวิกฤตฝุ่นพิษ PM2.5 ทำให้ทางผมได้ทดลองใช้ เซ็นเซอร์วัดฝุ่น และ M5Stack ทำต้นแบบเครื่องวัดฝุ่นขึ้นมาง่ายๆ ตอนนี้ถึงเวลาทำให้สมบูรณ์แล้ว

 

  • รวมโมดุล จับเอา Sensor และ Battery ทั้งหมดใส่เข้าไปในเคสเดียวกัน ด้วย 3D Printer ขึ้นรูปมา ดังภาพ โมดิฟาย M5Stack โมดุลเดิมนิดหน่อยครับ

  • แก้ไข UI ใหม่ครับ ให้เป็นระเบียบมากขึ้น ช่วงแรกยังไม่คุ้นกับ M5Stack เลยทำให้แสดงผลได้ก่อน ในตอนนี้ทางผมปรับปรุง UI ใหม่ และแก้ไขเรื่องกระพริบ และ ผมคิดว่า Code ใหม่น่าจะช่วยให้คนที่สนใจ ต่อยอดงานอื่นๆ ได้ง่ายขึ้นครับ
#include <M5Stack.h>
#include <HardwareSerial.h>

//const uint8_t PMS_RX=16, PMS_TX=17;
HardwareSerial pmsSerial(2); // UART2 on GPIO16(RX),GPIO17(TX)

#define GFXFF 1
#define FF9 &FreeSans9pt7b

#define CF_OL24 &Orbitron_Light_24
#define CF_OL32 &Orbitron_Light_32
#define CF_RT24 &Roboto_Thin_24
#define CF_S24  &Satisfy_24
#define CF_Y32  &Yellowtail_32

struct pms7003data {
    uint16_t framelen;
    uint16_t pm10_standard, pm25_standard, pm100_standard;
    uint16_t pm10_env, pm25_env, pm100_env;
    uint16_t particles_03um, particles_05um, particles_10um, particles_25um, particles_50um, particles_100um;
    uint16_t unused;
    uint16_t checksum;
};

struct displayCode {
    int bg_color;
    int text_color;
    int AQI;
    String Code;
};

struct pms7003data data;

struct displayCode displayCode_t;

void setup() {
    M5.begin();
    
    // our debugging output
    Serial.begin(115200);
    
    // sensor baud rate is 9600
    pmsSerial.begin(9600);
    
    M5.Lcd.setFreeFont(CF_RT24);
    M5.Lcd.setTextDatum(MC_DATUM);
    M5.Lcd.fillScreen(TFT_BLACK);
    M5.Lcd.setTextColor(TFT_WHITE);
    M5.Lcd.drawString("LOADING DATA", M5.Lcd.width()/2, M5.Lcd.height()/2, GFXFF);
}
 
int old=-1;
    
void loop() {

  if (readPMSdata(&pmsSerial)) {
      displayCode_t = PM25AQI(data.pm25_env);
      
     // update bg color
     if (old != displayCode_t.bg_color) {
          M5.Lcd.setFreeFont(CF_RT24);                 // Select the font    
          M5.Lcd.setTextSize(0.5);
          M5.Lcd.fillScreen(displayCode_t.bg_color);           
          M5.Lcd.setTextColor(displayCode_t.text_color, displayCode_t.bg_color);
          M5.Lcd.setTextDatum(ML_DATUM); 
          M5.Lcd.drawString("PM2.5(AQI)", 5, 15, GFXFF);     
          M5.Lcd.setTextDatum(MC_DATUM);
          M5.Lcd.drawString(displayCode_t.Code, M5.Lcd.width()/2, M5.Lcd.height()/2+40, GFXFF);// Print the string name of the font
        
          M5.Lcd.setFreeFont(FF9);
          M5.Lcd.setTextPadding(0);
          M5.Lcd.drawString("PM1.0", 40, 195, GFXFF);
          M5.Lcd.drawString("PM2.5", M5.Lcd.width()/2, 195, GFXFF);
          M5.Lcd.drawString("PM10", 280, 195, GFXFF);
     }
     
      M5.Lcd.setFreeFont(FF9); 
      M5.Lcd.setTextPadding(40);
      M5.Lcd.setTextSize(1);
      M5.Lcd.drawNumber( data.pm10_env, 40, 220);
      M5.Lcd.drawNumber( data.pm25_env, M5.Lcd.width()/2, 220);
      M5.Lcd.drawNumber( data.pm100_env, 280, 220);
  
      M5.Lcd.setFreeFont(CF_OL32);
      M5.Lcd.setTextDatum(MC_DATUM);
      M5.Lcd.setTextSize(2);
      M5.Lcd.setTextPadding(120);  
      M5.Lcd.drawNumber( displayCode_t.AQI, M5.Lcd.width()/2, M5.Lcd.height()/2-20);
      
      printTest();  //debug
      old = displayCode_t.bg_color;
   }

}

displayCode PM25AQI(int reading) {
    struct displayCode display_t;
    display_t.text_color = TFT_WHITE;
    if (reading <= 25) {
        display_t.bg_color = TFT_BLUE;      
        display_t.Code = "GOOD";
        display_t.AQI = reading;
    } else if ( (reading >= 26) &&  (reading <= 37) ) {
        display_t.bg_color = TFT_GREEN;
        display_t.Code = "Moderate";      
        display_t.AQI = map(reading,26,37,26,50);
    } else if  ( (reading >= 38) &&  (reading <= 50) ) {
        display_t.bg_color = TFT_GREENYELLOW;        
        display_t.Code = "unhealthy"; //unhealthy for kid
        display_t.AQI = map(reading,38,50,51,100);
    } else if  ( (reading >= 51) &&  (reading <= 90) ) {
        display_t.bg_color = TFT_ORANGE;          
        display_t.Code = "very unhealthy";  //very unhealthy
        display_t.AQI = map( reading,51,90,101,200 );
    } else if (data.pm25_env >= 91) {
        display_t.bg_color = TFT_RED;
        display_t.AQI = map( reading,91,200,201,510 );               
        display_t.Code = "Hazardous";  //Hazardous
    } 
    return display_t;
}

void printTest() {
  
    // reading data was successful!
    Serial.println();
    Serial.println("---------------------------------------");
    Serial.println("Concentration Units (standard)");
    Serial.print("PM 1.0: "); Serial.print(data.pm10_standard);
    Serial.print("\t\tPM 2.5: "); Serial.print(data.pm25_standard);
    Serial.print("\t\tPM 10: "); Serial.println(data.pm100_standard);
    Serial.println("---------------------------------------");
    Serial.println("Concentration Units (environmental)");
    Serial.print("PM 1.0: "); Serial.print(data.pm10_env);
    Serial.print("\t\tPM 2.5: "); Serial.print(data.pm25_env);
    Serial.print("\t\tPM 10: "); Serial.println(data.pm100_env);
    Serial.println("---------------------------------------");
    Serial.print("Particles > 0.3um / 0.1L air:"); Serial.println(data.particles_03um);
    Serial.print("Particles > 0.5um / 0.1L air:"); Serial.println(data.particles_05um);
    Serial.print("Particles > 1.0um / 0.1L air:"); Serial.println(data.particles_10um);
    Serial.print("Particles > 2.5um / 0.1L air:"); Serial.println(data.particles_25um);
    Serial.print("Particles > 5.0um / 0.1L air:"); Serial.println(data.particles_50um);
    Serial.print("Particles > 10.0 um / 0.1L air:"); Serial.println(data.particles_100um);
    Serial.println("---------------------------------------");
  
}
boolean readPMSdata(Stream *s) {
  if (! s->available()) {
    return false;
  }
  
  // Read a byte at a time until we get to the special '0x42' start-byte
  if (s->peek() != 0x42) {
    s->read();
    return false;
  }
 
  // Now read all 32 bytes
  if (s->available() < 32) {
    return false;
  }
    
  uint8_t buffer[32];    
  uint16_t sum = 0;
  s->readBytes(buffer, 32);
 
  // get checksum ready
  for (uint8_t i=0; i<30; i++) {
    sum += buffer[i];
  }
 
  /* debugging
  for (uint8_t i=2; i<32; i++) {
    Serial.print("0x"); Serial.print(buffer[i], HEX); Serial.print(", ");
  }
  Serial.println();
  */
  
  // The data comes in endian'd, this solves it so it works on all platforms
  uint16_t buffer_u16[15];
  for (uint8_t i=0; i<15; i++) {
    buffer_u16[i] = buffer[2 + i*2 + 1];
    buffer_u16[i] += (buffer[2 + i*2] << 8);
  }
 
  // put it into a nice struct 🙂
  memcpy((void *)&data, (void *)buffer_u16, 30);
 
  if (sum != data.checksum) {
    Serial.println("Checksum failure");
    return false;
  }
  // success!
  return true;
}

เพิ่มเติมตอนนี้ มีน้องใหม่ สำหรับ Node32Lite แสดงผลได้เหมือนกัน แค่ต้องเหนื่อยทำเองครับ เดี่ยวจะแชร์ กันในวันหลัง

LINE CHATBOT With IoT Device

$
0
0

หลักสูตรฝึกอบรม Internet of Things สำหรับ ผู้เริ่มต้นเน้น ภาคปฏิบัติและทฤษฏี เหมาะให้ผู้เข้าอบรมใช้งาน และ สร้างโปรเจค IoT ได้ต่อยอดได้ โดยเราได้เลือกโปรเจคเครื่องวัดฝุ่น เพื่อ ให้ทดลองโปรแกรม ทำ Hardware แสดงผล และ Platform IoT บน LINE Chatbot

LINE เป็น Platform พูดคุย ที่ทุกคน มีในโทรศัพท์มือถือ ทำให้ สะดวกและเข้าถึงผู้ใช้ เราสามารถใช้ LINE ใช้ในการแจ้งเตือน มอนิเตอร์ รวมถึง การควบคุมอุปกรณ์ผ่าน CHATBOT ผ่าน API ของ LINE ที่เป็นมาตราฐานและทันสมัย

รายละเอียด
วันที่ วันเสาร์ที่ 29 กุมภาพันธ์ 63
สถานที่อบรม: SynHUB ,Pantip Plaza ประตูน้ำ
จำนวนผู้เข้าอบรม: 12 ท่าน
ค่าลงทะเบียน: ราคา 4,500 บาท (ยังไม่รวม VAT 7%)  รวม break อาหารว่าง และ อุปกรณ์ M5Stack และ Hat PMSA003
ค่าลงทะเบียน: ราคา 2,500 บาท (ยังไม่รวม VAT 7%)  รวม break อาหารว่าง

ลงทะเบียนได้ที่นี้ >>> https://forms.gle/NKAThkqEcsuoii6JA

Course Syllabus

A: แนะนำ เกี่ยวกับ Internet of Thing

1: GETTING STARTED WITH THE M5STACK

  • รู้จักบอร์ด M5STACK
  • การติดตั้ง Arduino IDE
  • HELLO WORLD WITH M5STACK

2: FIRST GRAPHIC PROJECTS WITH THE M5STACK

  • การสร้างรูป LINE ,BOX
  • ระบบสีบน RGB565
  • การโหลดภาพ PNG,JPG
  • การแสดงผลค่า และ การใช้ Font

3: Sensing Real World:

  • อ่านค่า อุณหภูมิ และ ความชิ้น
  • อ่านค่า PM2.5

4: รู้จัก LINE CHATBOT

  • รู้จัก Dialogflow (ลองสร้างบทสนทนาแรกกัน)
  • เชื่อมต่อ BOT กับ LINE Account
  • LIFF
  • LINE Things คืออะไร

5:  ประยุกต์การใช้งาน IoT กับ ESP32 ( ESP32 เบื้องต้น)

  • สร้างอุปกรณ์เชื่อมต่อกับ LINE ด้วย ESP32
  • ประยุกต์ LINE อ่านค่า PM2.5 ผ่านทาง CHATBOT

 

ตัวอย่าง M5Stack

สิ่งประดิษฐ์ของชาวเมกเกอร์ ในวิกฤตไวรัส Covid-19

$
0
0

วิกฤตไวรัส Covid-19 นี้ กระทบกันทั่วโลกมากครับ เริ่มจากจีน และ ลามไปทั่วโลก ซึ่งทำให้เราได้เห็นไอเดียของสิ่งประดิษฐ์ ที่จะมาแก้ไขปัญหานี้ครับ

1. เครื่องกดน้ำ แบบไร้กด (Non-contact Switch for Public Water Dispenser – Arduino)

การสัมผัสของจากในที่สาธารณะ ในตอนนี้จะไม่ปลอดภัย เราจะแปลงเครื่องเดิมๆ อย่างไง ในอัตโมมัติ เมกเกอร์คนนี้เลยทำเครื่องกดน้ำให้อัตโนมัติ โดยใช้อุปกรณ์ใกล้ตัว Ardiono IR และ Servo ดังภาพ

ที่มา non-contact-switch-for-public-water-dispenser-arduino

2. หน้ากากตรวจหา Covid-19 (Coronavirus Detector)

เขาทำหน้ากาก ที่ติด Digital Infrared Temperature Sensor (MLX90615) แล้วใช้ตรวจคนเข้า LAB ถ้าใครมีอุณหภูมิเกิน 38 องศาองศาเซลเซียส OLED จะขึ้นข้อความเตือน และ RGB LED จะเปลี่ยนสีแดง

อืมมมมม ใช้ Thermal Gun ก้อเหมือนกัน เปล่าว่ะ!!!!

ที่มา Coronavirus Detector 

3. หน้ากาก Cyberpunk Mask

ส่วนอันนี้ แอดมินเห็นแก่ความน่ารักของน้องเขาเลยติดมาให้เพื่อนดูด้วย หน้ากากตัวนี้จะมีตัววัดฝุ่น PM2.5 ซึ่งเมือใดที่มีค่าอันตรายมากเกินไป จะปิดช่องอากาศเข้า ป้องกันฝุ่นเข้าเต็มที่ และ LED จะเปลี่ยนเป็นสีแดงครับ

 

ที่มา Cyberpunk Mask

4. Fire Detecting Drone

เอาล่ะครับ แถมด้วยโปรเจคสุดท้ายเลยครับ โดรนบินตรวจไฟป่า อันนี้เหมาะกับไทยมาก ช่วงนี้ยังมีการลักลอมเผาป่ากันอยู่ แต่จับไม่ได้ ผมคิดว่าสิ่งนี้น่าจะช่วยได้นะครับ หลักการเขาใช้ Drone บิน รุ่นกับ Thermal Cam ตรวจดูจุดที่มีความร้อน แล้วแจ้งเตือนภาคพื้นดิน

ที่มา Fire Detecting Drone

เอาล่ะครับ ถ้าแอดมินเจออะไรที่น่าสนใจ จะมา Update กันใหม่นะครับ งานก็ต้องเร่ง สิ่งประดิษฐ์ก้อต้องทำ Covid-19 ก็ต้องระวังอีก โอ้ยยยย ไม่อยากจะบ่น ไปล่ะครับ

วิธีการเพิ่มเลขหน้าในไฟล์ PDF ด้วย JavaScript

$
0
0

สวัสดีทุกท่าน หลังจากที่ไม่ได้ update เวปมานาน ตอนนี้มาขยับเมาส์เพื่อปั่นบทความบันเทิงกันบ้าง ตอนนี้ งานหลักๆของทางผม จะเป็นการสร้างเอกสาร ประกอบใบเสนอราคา ซึ่งมันจะมีหมวดหนึ่ง ที่ใช้คน คนหนึ่งเพื่อ ติดเลขหน้า ไปกับ ไฟล์ pdf ที่ระบุสเป๊คของสินค้าเอาไว้ อ้างอิง ซึ่งเป็นงานที่ใช้เวลาสูงมาก จริงๆ หลายชั่วโมงกว่าจะทำเสร็จ และ ปัญหาก็คือ ถ้ามีการปรับเปลี่ยน เอาไฟล์ pdf ออก ต้องมาทำเรียงกันใหม่ด้วย สำหรับ จิตวิญญาณการเป็นโปรแกรมเมอร์ ของผมแล้ว ขอเสนอวิธีการที่ดีกว่า และ สนุกกว่า

การจัดการกับไฟล์ PDF เป็นเรื่องที่พบได้บ่อยในหลายๆ งาน ไม่ว่าจะเป็นการแปลงไฟล์ การเพิ่มข้อมูล หรือแม้กระทั่งการเพิ่มเลขหน้าให้กับไฟล์ PDF ซึ่งเอาเข้าจริง ผมว่า น่าจะมีโปรแกรมทำได้ล่ะ แต่ สำหรับผมแล้ว จะโหลดมาทำไมล่ะ เดี่ยวนี้ เขียนเองง่ายจะตาย ฉะนั้นในบทความนี้ เราจะสอนคุณในการใช้ JavaScript ร่วมกับ pdf-lib เพื่อเพิ่มเลขหน้าในไฟล์ PDF และ สร้างไฟล์ exe เอาไปรันกันเลย

สำหรับบทความนี้ เราจะใช้ NodeJs และ NPM ในการทำโปรเจค เพราะว่า เป็น tool ที่มีในเครื่องอยู่แล้ว อย่างไง อย่างลืมติดตั้งกันด้วยล่ะครับ

ขั้นตอนที่ 1: ตั้งค่าโปรเจกต์

1. สร้างไดเรกทอรีใหม่สำหรับโปรเจกต์

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

mkdir pdf-page-numbering
cd pdf-page-numbering

2. รันคำสั่ง npm init

สร้างไฟล์ package.json โดยใช้คำสั่ง npm init (คุณสามารถใช้ -y เพื่อยอมรับค่าเริ่มต้นทั้งหมด):

npm init -y

3. ติดตั้ง dependencies ที่จำเป็น

ติดตั้ง pdf-lib และ pkg ซึ่งเป็นเครื่องมือสำหรับสร้างไฟล์ปฏิบัติการ:

npm install pdf-lib @pdf-lib/fontkit
npm install -g pkg

4. ดาวน์โหลดฟอนต์ไทย

ดาวน์โหลดฟอนต์ ttf ที่คุณต้องการใช้ เช่น THSarabunNew.ttf และวางในโฟลเดอร์โปรเจกต์ของคุณ โดยโปรเจคนี้จะฝั่งไฟล์ ที่คุณชอบเข้าไปใน ไฟล์ EXE เลย

โดยโครงสร้างของโปรเจคของเราจะเป็นแบบนี้

pdf-page-numbering/
├── node_modules/
├── THSarabunNew.ttf
├── index.js
├── package.json

ขั้นตอนที่ 2: เขียนโค้ด JavaScript

สร้างไฟล์ index.js ในไดเรกทอรีโปรเจกต์ของคุณและใส่โค้ดต่อไปนี้:

const { PDFDocument, rgb } = require('pdf-lib');
const fs = require('fs');
const path = require('path');
const fontkit = require('@pdf-lib/fontkit');

async function addPageNumbers(inputPdfPath, outputPdfPath, fontPath) {
    const existingPdfBytes = fs.readFileSync(inputPdfPath);
    const pdfDoc = await PDFDocument.load(existingPdfBytes);
    pdfDoc.registerFontkit(fontkit);

    const fontBytes = fs.readFileSync(fontPath);
    const customFont = await pdfDoc.embedFont(fontBytes);

    const pages = pdfDoc.getPages();
    const { width, height } = pages[0].getSize();

    for (let i = 0; i < pages.length; i++) {
        const page = pages[i];
        page.drawText(`หน้า ${i + 1}`, {
            x: width - 50,
            y: 10,
            size: 12,
            font: customFont,
            color: rgb(0, 0, 0),
        });
    }

    const pdfBytes = await pdfDoc.save();
    fs.writeFileSync(outputPdfPath, pdfBytes);
}

const inputPdfPath = process.argv[2];
const outputPdfPath = process.argv[3];
const fontPath = path.join(__dirname, 'THSarabunNew.ttf');

if (!inputPdfPath || !outputPdfPath) {
    console.log('plaese input a paramater inpPdf outpPdf');
    process.exit(1);
}

addPageNumbers(inputPdfPath, outputPdfPath, fontPath);

ขั้นตอนที่ 3: แก้ไข package.json

เปิดไฟล์ package.json และเพิ่มฟิลด์ bin เพื่อกำหนด entry point ของโปรเจกต์:

{
  "name": "pdf-page-numbering",
  "version": "1.0.0",
  "description": "Add page numbers to PDF files",
  "main": "index.js",
  "scripts": {
    "start": "node index.js"
  },
  "dependencies": {
    "pdf-lib": "^1.17.1",
    "@pdf-lib/fontkit": "^1.0.0"
  },
  "bin": {
    "pdf-page-numbering": "./index.js"
  },
  "pkg": {
    "assets": [
      "THSarabunNew.ttf",
      "node_modules/@pdf-lib/fontkit/**/*"
    ]
  }
}

ขั้นตอนที่ 4: สร้างไฟล์ EXE

การใช้ไฟล์ EXE สำหรับ nodejs เราจะใช้ pkg

รันคำสั่ง pkg เพื่อสร้างไฟล์ EXE:

pkg . --targets node16-win-x64,node16-macos-x64,node16-linux-x64

คำสั่งนี้จะสร้างไฟล์ปฏิบัติการสำหรับ Windows, MacOS, และ Linux ในไดเรกทอรีเดียวกับสคริปต์

สำหรับคนที่ใช้กับ Windows อย่างเดียว จะตัด node16-macos-x64,node16-linux-x64 ออกก็ได้

ขั้นตอนที่ 5: ทดสอบ

ตอนนี้คุณสามารถรันไฟล์ปฏิบัติการได้โดยระบุเส้นทางไฟล์ PDF อินพุตและเอาต์พุตจากบรรทัดคำสั่ง:

./pdf-page-numbering input.pdf output.pdf  # สำหรับ Unix-based systems
pdf-page-numbering.exe input.pdf output.pdf  # สำหรับ Windows

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

สรุป

เราสามารถใช้ Nodejs ,Javascript และ pdf-lib เพื่อจัดการกับไฟล์ PDF ได้ เป็นวิธีที่สะดวกและยืดหยุ่น  เพราะว่า หลังจากนี้ เราอาจจะเพิ่มหมวด หรือ ข้อความอื่นๆได้ นอกจากนี้ เรายังนำเสนอวิธีการเอา ใช้ Nodejs เพื่อ สร้างไฟล์ EXE ที่คุณเอารันตรงไหนก็ได้ ส่งให้เพื่อนก็ได้ ยังช่วยให้การใช้งานง่ายขึ้นสำหรับผู้ใช้งานที่ไม่ต้องการติดตั้ง Node.js และ dependencies ต่างๆ หวังว่าบทความนี้จะเป็นประโยชน์และช่วยให้คุณสามารถเพิ่มเลขหน้าในไฟล์ PDF ได้อย่างง่ายดาย แล้ว พบการใหม่ Comment แล้วมาเล่าให้ฟังกันบ้าง เจอปัญหาอะไร กับการจัดการเอกสารกันบ้าง ขอตัวไปคิดใบเสนอราคาต่อล่ะครับ บาย

The post วิธีการเพิ่มเลขหน้าในไฟล์ PDF ด้วย JavaScript first appeared on Ayarafun Factory.

การรวมไฟล์ PDF ด้วย Node.js

$
0
0

บทความภาคต่อจาก ชุดเดิมที่ เติมเลขหน้าได้แล้ว เราจึงคิดว่า เราควรจบทุกอย่างด้วยคำสั่งชุดเดียว เลยออกแบบให้มีโปรแกรมรวมไฟล์ที่มาจาก command line ได้ด้วย คือการใช้งาน รวมไฟล์หลายๆ ไฟล์ แล้ว ก็มา เพิ่มเลขหน้า จะได้จบงาน

สำหรับ บทความนี้ การรวมไฟล์ PDF หลายไฟล์เป็นไฟล์เดียว เราสามารถใช้ Node.js และไลบรารี pdf-lib ในการจัดการและรวมไฟล์เหล่านั้นเข้าด้วยกันได้ง่าย ๆ บทความนี้จะแนะนำขั้นตอนการทำงานแบบ step-by-step รวมถึงการแปลงโปรแกรมเป็นไฟล์ .exe สำหรับใช้งานบน Windows

ขั้นตอนที่ 1: การติดตั้งไลบรารีที่จำเป็น

ก่อนอื่นให้ทำการติดตั้งไลบรารีที่จำเป็น ได้แก่ pdf-lib และ commander ซึ่งใช้ในการจัดการไฟล์ PDF และการรับค่า arguments จาก command line

npm install pdf-lib commander

ขั้นตอนที่ 2: สร้างสคริปต์สำหรับรวมไฟล์ PDF

สร้างไฟล์ merge-pdfs.js แล้วเพิ่มโค้ดต่อไปนี้ลงไป:

const fs = require('fs');
const path = require('path');
const { PDFDocument } = require('pdf-lib');
const { Command } = require('commander');

async function mergePdfs(inputDir, outputPdfPath) {
    const pdfDoc = await PDFDocument.create();
    const files = fs.readdirSync(inputDir).filter(file => file.endsWith('.pdf'));

    for (const file of files) {
        const filePath = path.join(inputDir, file);
        const pdfBytes = fs.readFileSync(filePath);
        const pdfToMerge = await PDFDocument.load(pdfBytes);
        const [copiedPages] = await pdfDoc.copyPages(pdfToMerge, pdfToMerge.getPageIndices());
        copiedPages.forEach(page => {
            pdfDoc.addPage(page);
        });
    }

    const mergedPdfBytes = await pdfDoc.save();
    fs.writeFileSync(outputPdfPath, mergedPdfBytes);
}

const program = new Command();
program
    .requiredOption('-i, --input-dir ', 'input directory containing PDF files')
    .requiredOption('-o, --output ', 'output PDF file path')
    .parse(process.argv);

const options = program.opts();
const inputDir = options.inputDir;
const outputPdfPath = options.output;

mergePdfs(inputDir, outputPdfPath)
    .then(() => console.log('PDF files merged successfully'))
    .catch(err => console.error('Error:', err));

โค้ดนี้จะทำการรวมไฟล์ PDF ทั้งหมดที่อยู่ในไดเรกทอรีที่กำหนดและบันทึกเป็นไฟล์ PDF ไฟล์เดียวที่ระบุ

ขั้นตอนที่ 3: การใช้งานสคริปต์

หลังจากสร้างไฟล์สคริปต์แล้ว คุณสามารถรันสคริปต์ได้โดยใช้คำสั่งดังนี้:

node merge-pdfs.js --input-dir <ไดเรกทอรีที่มีไฟล์ PDF> --output <ไฟล์ PDF ที่ต้องการบันทึก>
node merge-pdfs.js --input-dir ./pdf-files --output merged.pdf

ขั้นตอนที่ 4: การสร้างไฟล์ [.exe]

หากต้องการแปลงโปรแกรม Node.js เป็นไฟล์ [.exe] สำหรับใช้งานบน Windows สามารถใช้ไลบรารี [pkg] ได้

[1] ติดตั้ง [pkg]
npm install -g pkg
[2] แก้ไข [package.json] เพื่อให้ [pkg] สามารถหาโมดูลที่จำเป็นได้
{
    "name": "merge-pdfs",
    "version": "1.0.0",
    "main": "merge-pdfs.js",
    "scripts": {
        "start": "node merge-pdfs.js"
    },
    "bin": "merge-pdfs.js",
    "pkg": {
        "scripts": [
            "merge-pdfs.js"
        ]
    },
    "dependencies": {
        "pdf-lib": "^1.17.1",
        "commander": "^8.0.0"
    }
}
[3] สร้างไฟล์ [.exe] ด้วยคำสั่ง [pkg]
pkg . --targets node14-win-x64 --output merge-pdfs.exe
[4] ใช้ไฟล์ .exe ที่สร้างขึ้น
merge-pdfs.exe --input-dir <ไดเรกทอรีที่มีไฟล์ PDF> --output <ไฟล์ PDF ที่ต้องการบันทึก>

บทสรุป

บทความนี้แสดงขั้นตอนการรวมไฟล์ PDF หลายไฟล์เป็นไฟล์เดียวโดยใช้ Node.js และไลบรารี [pdf-lib] พร้อมกับการรับค่า arguments จาก command line ด้วย commander และการแปลงโปรแกรมเป็นไฟล์ .exe สำหรับใช้งานบน Windows หวังว่าบทความนี้จะเป็นประโยชน์สำหรับผู้ที่ต้องการจัดการไฟล์ PDF ด้วย Node.js

The post การรวมไฟล์ PDF ด้วย Node.js first appeared on Ayarafun Factory.

Viewing all 60 articles
Browse latest View live