ใส่ใจ 8. การพัฒนาโปรแกรมควบคุมใส่ใจด้วย ESP32 ตอนที่ 1

แบ่ง Task การทำงานออกเป็นส่วนๆ หน้าที่ใครหน้าที่มัน

ESP32 มี MCU 2 Cores และยังมี FreeRTOS มาช่วยงานอีก เราจึงสามารถแบ่งการทำงานแบบ Multitask ได้ โดยทุก TASK จะทำงานได้อย่างอิสระไม่ยุ่งเกี่ยวต่อกัน จุดนี้เป็นจุดแข็งที่โดดเด่นของ ESP32 และเป็นจุดที่ผู้เขียนชอบมากที่สุด การเขียนโปรแกรมบนบอร์ด MCU 1 Core เช่น ESP8266 จะต้องระมัดระวังการเขียนโปรแกรมแบบ Blocking ให้ดี เช่นการใช้คำสั่ง delay() เพราะมันจะทำให้ทั้งบอร์ดหยุดชะงักไปเลย ทำอะไรต่อไม่ได้ จนกว่าจะหลุดจากคำสั่ง delay() ถึงแม้ว่าจะสามารถใช้ฟังก์ชัน Milli() และ Timer เข้าช่วยได้ก็ตามแต่มันก็เป็นการทำงาน MultiTask แบบจำลอง คือการแยกการทำงานออกเป็น Task บน MCU Core เดียวกัน และมันยังทำให้การเขียนโค๊ดค่อนข้างซับซ้อนเลยทีเดียว

ใส่ใจ” จะแยกการทำงานออกเป็น TASK ย่อยๆ แล้วกำหนดให้มันทำหน้าที่ใครหน้าที่มัน ไม่ยุ่งเกี่ยวต่อกัน การทำแบบนี้ทำให้มันมีประสิทธิภาพในการทำงานมาก จะไม่ทำให้เกิดปัญหา wdt-reset เหมือน ESP8266/NodeMCU เวลาที่การเชื่อมต่อ WiFi มีปัญหา

สำหรับผู้ที่ต้องการทบทวนการออกแบบระบบของ “ใส่ใจ” อีกที สามารถเข้าไปอ่านได้ที่นี่ครับ [ ตอนที่ 3. การออกแบบระบบของใส่ใจ ]

มาเช็คอุปกรณ์ที่ใช้สำหรับทำต้นแบบโปรเจค SAMRTFARM ในครั้งนี้กันก่อน

Image for post
Image for post
วงจรต้นแบบโปรเจค SAMRTFARM

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

เขียนโปรแกรมควบคุม “ใส่ใจ”

เนื้อหาต่อไปจะเป็นการอธิบายโค๊ดทำงาน โดยผู้เขียนจะแบ่งแยกโค๊ดออกเป็นส่วนย่อยๆ จำนวน 26 ส่วน แล้วแยกอธิบายแต่ละส่วน จะได้เข้าใจกันได้ง่ายๆ ครับ

Image for post
Image for post

(1) PROJECT INFO

เริ่มจาก่วนของ Project Info ในส่วนนี้ไม่มีผลต่อการทำงานของโปรแกรมแต่อย่างใด เป็นเพียงแค่ข้อความรายละเอียดต่างๆ ทั่วไป เอาไว้เตือนความจำเราและให้ผู้ที่มาพัฒนาโปรแกรมต่อจากเราเข้าใจว่าโปรเจคนี้ใช้อุปกรณ์อะไรบ้าง ใช้ไลบรารี่เวอร์ชันไหน และกำหนดขา Vitual Pin เป็นอย่างไร

// *** (1) PROJECT INFO *** /*
* Demo SMARTFARM PROJECT
* VERSION: 2.2 (18 Feb 2018)
* BY : VISIT WIANGNAK
* EMAIL : VISIT01@GMAIL.COM
*
* MCU : ESP32(ESP-WROOM-32)
*
* Output : Wifi LED(2:HIGH=ON,LOW=OFF)
* : Relay0(LOW=ON,HIGH=OFF)
*
* Demo : BLYNK -> "SMARTFARM"
* : Blynk library V0.5.0
* : DHT11 (Temperature, Humidity)
* : LDR (Light sensor)
* : SOIL Sensor
* : Relay 2 Channel
*
* Widget : Widget_Humidity V1
* : Widget_Temperature V2
* : Widget_SoilRawValue V3
* : Widget_SoilLevel V4
* : Widget_LightRawValue V5
* : Widget_LightLevel V6
* : Widget_WateringButton V7
* : Widget_SendSensorStatus V8
* : Widget_WateringStatus V9
* : Widget_WifiSignal V10
* : Widget_WifiRawValue V11
* : Widget_Terminal V12
*/

ส่วนของ Widget จะเป็นขา Vitual Pin ที่จะถูกนำไปใช้ใน Blynk Mobile APP ผู้เขียนกำหนดไว้ตั้งแต่ V1-V12 มีรายละเอียดดังต่อไปนี้

Image for post
Image for post

V1 Widget_Humidity คือเปอร์ค่าความชื้นที่ได้รับจากเซ็นเซอร์ DHT11 ใช้ Widget Guage ใน Blynk Mobile APP กำหนดค่าต่ำสุดเป็น 0 และสูงสุดคือ 100

V2 Widget_Temperature คือเปอร์ค่าอุณหภูมิที่ได้รับจากเซ็นเซอร์ DHT11 มีหน่วยเป็นหน่วยเป็นองศาเซลเซียส ใช้ Widget Guage ใน Blynk Mobile APP กำหนดค่าต่ำสุดเป็น 0 และสูงสุดคือ 50

V3 Widget_SoilRawValue คือค่าความชื้นของดินที่ได้รับจากเซ็นเซอร์ ตรงๆ(RAW Value) มีหน่วยเป็นค่า 12 บิต (ค่าที่ได้รับจะอยู่ในระหว่าง 0–4095) ใช้ Widget LABEL ใน Blynk Mobile APP

V4 Widget_SoilLevel คือค่าความชื้นของดินที่ได้รับจากเซ็นเซอร์ที่ได้รับการแปลงหน่วยแล้วในรูปแบบเปอร์เซ็นต์ ใช้ Widget ROW LEVEL ใน Blynk Mobile APP กำหนดค่าต่ำสุดเป็น 0 และสูงสุดคือ 100

V5 Widget_LightRawValue คือค่าความเข้มของแสงที่ได้รับจากเซ็นเซอร์ตรงๆ(RAW Value) มีหน่วยเป็นค่า 12 บิต (ค่าที่ได้รับจะอยู่ในระหว่าง 0–4095) ใช้ Widget LABEL ใน Blynk Mobile APP

V6 Widget_LightLevel คือค่าความเข้มของแสงที่ได้รับจากเซ็นเซอร์ที่ได้รับการแปลงหน่วยแล้วในรูปแบบเปอร์เซ็นต์ ใช้ Widget ROW LEVEL ใน Blynk Mobile APP กำหนดค่าต่ำสุดเป็น 0 และสูงสุดคือ 100

V7 Widget_WateringButton คือปุ่ม Button ที่สั่งให้ Relay ทำงานและสั่งให้ปั๊มน้ำเริ่มสูบน้ำรดต้นไม้ตามเวลาที่กำหนด

V8 Widget_SendSensorStatus คือสถานะการส่งข้อมูลเซ็นเซอร์ หลอดไฟ LED จะติดกระพริบ ถ้ามีการส่งข้อมูลไปยัง Blynk Server

V9 Widget_WateringStatus คือสถานะการทำงานของ Relay และปั๊มน้ำ หลอดไฟ LED จะติดสว่างถ้ามีการรดน้ำต้นไม้

V10 Widget_WifiSignal คือค่าสัญญาณของ WiFi ที่ได้รับการแปลงหน่วยแล้วในรูปแบบเปอร์เซ็นต์ ใช้ Widget ROW LEVEL ใน Blynk Mobile APP กำหนดค่าต่ำสุดเป็น 0 และสูงสุดคือ 100

V11 Widget_WifiRawValue คือค่าสัญญาณของ WIFI (RSSI) ในรูปแบบขอ RAW Value มีหน่วยเป็น dBm (ค่าที่ได้รับจะอยู่ในระหว่าง -40dBm ถึง -90dBm) ใช้ Widget LABEL ใน Blynk Mobile APP

V12 Widget_Terminal คือ Terminal แสดงข้อความประวัติพร้อมวันที่และเวลาการทำงานของ Relay และปั๊มน้ำ เมื่อมีการรดน้ำต้นไม้เสร็จสมบูรณ์

(2) SYSTEM CONFIGURATION

ในส่วนนี้จะเป็นการตั้งค่าคอนฟิกต่างๆ ของระบบ เช่นการเชื่อมต่อ WiFi การเข้าถึง BLYNK SERVER / LINE API และ FIREBASE โดยใช้รหัส TOKEN ในการยืนยันตัวตน พิสูจน์ตัวตนและกำหนดสิทธิ์การใช้งาน นอกจากนั้นเรายังสามารถกำหนดค่าเงื่อนไขต่างๆ สำหรับการรดน้ำตนไม้ได้อีกด้วย

// *** (2) SYSTEM CONFIGURATION ***//WIFI CONFIG
char ssid[] = "myWiFi_SSID";
char pass[] = "wifipassword";
//BLYNK TOKEN
char auth[] = "589a20ebd2b043f5b3e57e4378ahadg6";
char server[] = "xxx.xxx.xxx.xxx";
unsigned int port = 8442;
//LINE TOKEN
#define TokenLine "Ynzzovc8dway85pRSJKNASdkTylkPnvBPs9MUpaBWRA"
//FIREBASE CONFIG
#define FIREBASE_HOST "smartfarmingxx.firebaseio.com"
#define FIREBASE_AUTH "u7j7XhFN57gnDpmIfUrE5asdfa344RVKm3bnNHAQc"
/* ---------------------------------------------------------------*/// *** WATERING CONFIG ***//ค่าเปอร์เซ็นต์ความชื้นในดินที่ต้องการให้เร่ิมรดน้ำต้นไม้
#define PercentWatering = 40
//ตั้งระยะเวลาการรดน้ำต้นไม้แต่ละครั้ง
//15000 = 15 วินาที
#define TimeWatering = 15000
//ตั้งระยะห่างของเวลาที่ต้องการเช็คความชื้นในดิน
//60000 = 6 นาที
#define TimeSoilCheck = 360000
/* ---------------------------------------------------------------*/// *** TIMER CONFIG ***//ตั้งเวลาส่งข้อมูลให้ BLYNK SERVER ทุกๆ 2 นาที
//120 Secounds = 120000 Milliseconds
#define sendSensorTime = 120000
//ตั้งเวลาส่งข้อมูลให้ BLYNK SERVER ทุกๆ 3 นาที
//180 Secound = 180000 Milliseconds
#define sendSensor2Time = 180000
//ตั้งเวลาให้เช็คว่า Blynk เชื่อมต่ออยู่หรือไม่ทุกๆ 10 วินาที
#define checkConnectionTime = 10000
//ส่งข้อมูลเข้า firebase ทุกๆ 5 นาที
#define sendFirebaseTime = 300000

(3) INCLUDE LIBRARY

โปรเจค “ใส่ใจ” จำเป็นต้องเรียกใช้ไลบรารี่หลายตัวมาช่วยงาน เช่น ไลบรารี่เกี่ยวกับ WiFi เพื่อจัดการเชื่อมต่อเครือข่ายไร้สาย มีการเรียกใช้ไลบรารี่ BLYNK (BlynkSimpleEsp32.h) เพื่อเข้าถึง BLYNK SERVER ที่ทำให้เราสามารถควบคุมอุปกรณ์ผ่านทาง Mobile App ได้ นอกจากนั้นยังมีการเรียกใช้ไลบรารี่ DHT11 (SimpleDHT.h) สำหรับวัดค่าอุณหภูมิและความชื้นสัมพัทธ์ ส่วนการดึงเวลาผ่านทางอินเตอร์เนตด้วย NTP จะใช้ไลบรารี่ (time.h) สุดท้ายการส่งข้อมูลไปยังฐานข้อมูล Google Firebase จะใช้ไลบรารี่ (IOXhop_FirebaseESP32.h)

// *** (3) LIBRARY INCLUDE ***//ไลบรารี่เกี่ยวกับ WIFI
#include <WiFi.h>
#include <WiFiClient.h>
#include <WiFiClientSecure.h>
//ไลบรารี่ของ BLYNK
#include <BlynkSimpleEsp32.h>
//ไลบรารี่ของเซ็นเซอร์ DHT11
#include <SimpleDHT.h>
//ไลบรารี่ Firebase สำหรับ ESP32
#include <IOXhop_FirebaseESP32.h>
//ไลบรารี่สำหรับดึงเวลาจากอินเตอร์เน็ต NTP
#include <time.h>

(4) GPIO PIN SETUP

โปรเจค “ใส่ใจ” จะใช้ขา GPIO ที่เป็นแบบ Digital Input จำนวน 1 ขาได้แก่ เซ็นเซอร์วัดอุณหภูมิและความชื้นสัมพัทธ์ (DHT11) เซ็นเซอร์

ขา GPIO ที่เป็นแบบ Analog Input จำนวน 3 ขาได้แก่ เซ็นเซอร์วัดความชื้นในดิน (SoilSensorPin 25) เซ็นเซอร์วัดความเข้มของแสง (LdrSensorPin 35) และขา GPIO ที่เป็นแบบ Input_pullup จำนวน 1 ขาคือสวิตซ์ปุ่มกด (SwitchPin 25)

ส่วนขา GPIO ที่เป็นแบบ Digital Output มีหลอดไฟ LED แบบต่อภายนอกจำนวน 2 ขา (pin 14, 26) หลอดไฟ LED แบบ Builin ในบอร์ด 1 ขา (pin2) ขาที่ใช้ควบคุม Relay เพื่อปิด เปิดปั๊มน้ำ 1 ขา (pin 5) และขาเอาไว้สำหรับการอ้างอิงการรดน้ำต้นไม้ 1 ขา (pin 27)

// *** (4) GPIO PIN SETUP ***//กำหนดให้เซ็นเซอร์ DHT11 ใช้ขา GPIO ที่ 32
#define DHTPin 32
//กำหนดให้เซ็นเซอร์วัดความชื้นในดิน ใช้ขา GPIO ที่ 34
#define SoilSensorPin 34
//กำหนดให้เซ็นเซอร์วัดความเข้มของแสง ใช้ขา GPIO ที่ 35
#define LdrSensorPin 35
//กำหนดให้สวิตซ์ปุ่มกด ใช้ขา GPIO ที่ 25
#define SwitchPin 25
//กำหนดให้หลอดไฟ LED แสดงสถานะการส่งข้อมูล blynk server ใช้ขา GPIO ที่ 26
#define LEDSensorPin 26
//กำหนดให้ขาอ้างอิงการสั่งรดน้ำต้นไม้ ใช้ขา GPIO ที่ 27
#define WateringPin 27
//กำหนดให้หลอดไฟ LED บนบอร์ดแสดงสถานะ WIFI ใช้ขา GPIO ที่ 2
#define LEDWiFiPin 2
//กำหนดขาอ้างอิงการรดน้ำต้นไม้ ใช้ขา GPIO ที่ 14
#define LEDWateringPin 14
//กำหนดหให้ขาควบคุม Relay ใช้ขา GPIO ที่ 5
#define RelayPin 5

สามารถดูรายละเอียดขาใช้งานต่างๆ ได้ตามรูปด้านล่าง

Image for post
Image for post
รูปแสดงขาใช้งาน GPIO ของบอร์ด ESP32

(5) BLYNK PARAMETER SETUP

ในส่วนนี้จะเป็นการตั้งค่าพารามิเตอร์ต่างๆ การแลกเปลี่ยนข้อมูลระหว่าง ESP32 ผ่านทางไลบรารี่ BLYNK และ BLYNK SERVER ซึ่งจะทำให้เราสามารถรับค่าจากเซ็นเซอร์ต่างๆ มาแสดงผลค่าข้อมูล แสดงเป็น Guage มิเตอร์ พล๊อตกราฟ และควบคุม ESP32 ผ่านทาง BLYNK Mobile APP ได้

// *** (5) BLYNK PARAMETER SETUP ***//ตั้งค่าตัวแปรระบบของ BLYNK
#define BLYNK_TIMEOUT_MS 750
#define BLYNK_HEARTBEAT 17
#define BLYNK_PRINT Serial
//การกำหนดขา Vitual Pin ที่สร้างขึ้นให้กับ Widget ต่างๆ ใน Blynk Mobile APP
#define Widget_Humidity V1
#define Widget_Temperature V2
#define Widget_SoilRawValue V3
#define Widget_SoilLevel V4
#define Widget_LightRawValue V5
#define Widget_LightLevel V6
#define Widget_WateringButton V7
#define Widget_SendSensorStatus V8
#define Widget_WateringStatus V9
#define Widget_WifiSignal V10
#define Widget_WifiRawValue V11
#define Widget_Terminal V12
WidgetLED LEDSensor(Widget_SendSensorStatus);
WidgetLED LEDWatering(Widget_WateringStatus);
WidgetTerminal terminal(Widget_Terminal);

(6) NTP SERVER SETUP

เวลาเป็นสิ่งที่สำคัญมากดังน้ัน โปรเจค “ใส่ใจ” จำเป็นต้องดึงเวลาจากทางอินเตอร์เน็ตผ่านโปรโตคอล NTP มาแสดงคู่กับ Log ที่เกิดขึ้น ทำให้เราทราบถึงเวลาที่เกิดเหตุการณ์ต่าง ๆ ได้ และมีการนำเวลาส่งไปเก็บไว้ที่ฐานข้อมูลออนไลน์ Firebase พร้อมกับข้อมูลจากเซ็นเซอร์ต่างๆ อีกด้วย การเก็บข้อมูลในลักษณะนี้จะทำให้เราสามารถนำข้อมูลไปพล๊อตแสดงกราฟต่อไปได้

NTP server เราสามารถกำหนดได้มากกว่า 1 server ได้ ถ้าท่านต้องการดู log ของ “ใส่ใจ” ก็สามารถเข้าไปดูได้ที่ Serial Monitor

// *** (6) NTP SERVER SETUP ***char ntp_server1[20] = "time.navy.mi.th";
char ntp_server2[20] = "clock.nectec.or.th";
char ntp_server3[20] = "th.pool.ntp.org";

(7) GENAREL SETUP

ในส่วนนี้จะเป็นการตั้งค่าให้กับตัวแปรใช้งานทั่วไป

// *** (7) GENAREL SETUP ***//ระบุรุ่นเซ็นเซอร์วัดความชื้นสัมพัทธ์และอุณมหภูมิเป็นรุ่น DHT11
SimpleDHT11 dht11;
//เรียกใช้การตั้งเวลาของ Blynk
BlynkTimer timer;
//กำหนดตัวแปรเริ่มต้น
int WifiSignal;
bool isFirstConnect = true;
byte oldtemperature;
byte oldhumidity;
int CurrentSoilValue;
int MapReadSoilValue;
int CurrentWiFiSignal;
int MapReadLightValue;
int CurrentLightValue;
int dhtdata[2];

(8) BLYNK CONNECTED FUNCTION

หลังจากที่บอร์ด ESP3 สามารถเชื่อมต่อกับ WiFi และเข้าถึง BLYNK SERVER ได้แล้ว หลอดไฟ LED สีน้ำเงินบนบอร์ดจะติด เพื่อแสดงสถานะว่าการเชื่อมต่อเสร็จสมบูรณ์

ถ้าเป็นการเชื่อมต่อกับ Blynk Server ครั้งแรก ก็ให้ทำการร้องขอสถานะและค่าต่างๆ ของแต่ละ Widget จาก BLYNK SERVER ล่าสุด จากนั้นให้ทำการซิงค์สถานะให้กับบอร์ดและทำการอัพเดทขา Virtual Pin

// *** (8) BLYNK CONNECTED FUNCTION ***BLYNK_CONNECTED() {//ให้หลอดไฟ LED บนบอร์ดสีน้ำเงินติดเพื่อเป็นการแสดงสถานะว่าการเชื่อมต่อเสร็จสมบูรณ์
digitalWrite(LEDBUILIN, HIGH);
//ถ้าเป็นการเชื่อมต่อ Blynk Server ครั้งแรกหลังบอร์ดรีบูต
if(isFirstConnect) {
//ให้ซิงค์ข้อมูลทั้งหมดล่าสุดจาก Blynk Server
Blynk.syncAll();
//ให้ซิงค์สถานะของขา Virtual V7
Blynk.syncVirtual(Widget_WateringButton);
isFirstConnect = false; }
}

(9) BLYNK BUTTON FUNCTION

ฟังก์ชันนี้จะถูกเรียกใช้ถ้ามีการกดปุ่ม V7 ใน Mobile Blynk APP โดยหลังจากมีการกดปุ่ม BLYNK SERVER จะส่งค่า 0 หรือ 1 มาให้กับ ESP32 ผ่านทาง Widget_WateringButton ถ้าค่าที่ได้จาก Widget_WateringButton เท่ากับ 1 จะหมายถึงให้เริ่มรดน้ำต้นไม้แบบกำหนดเอง (Manual) แต่ถ้าในเวลาน้ันมีการรดน้ำต้นไม้อยู่แล้วก็จะไม่สนใจ และไม่โปรเซสดำเนินการอะไรต่อ

การสั่งให้เร่ิมการรดน้ำต้นไม้จะทำโดยการเปลี่ยนสถานะของ GPIO ขาที่ 27 (WateringPin) ซึ่งเป็นขาอ้างอิงการสั่งรดน้ำต้นไม้ ให้เป็น LOW

// *** (9) BLYNK BUTTON FUNCTION ***BLYNK_WRITE(Widget_WateringButton) { //ค่าที่ได้จาก Widget_WateringButton (V7) จะเป็น 0 และ 1 ถ้ามีการปดปุ่ม
if (param.asInt() == 1) {
//ป้องกันไม่ให้กดซ้ำ
if (digitalRead(WateringPin) != LOW) {
//เรียกใช้ฟังก์ชันรดน้ำ
digitalWrite(WateringPin, LOW);
} //if
} //if
}

(10) FUNCTION SETUP()

ทุกโปรแกรมที่พัฒนาด้วย Arduino IDE จะต้องมีฟังก์ชัน SETUP() เป็นค่าเร่ิมต้นเสมอ โดยฟังก์ชัน SETUP() จะทำงานเพียงแค่ครั้งเดียวในระหว่างที่บอร์ด ESP32 บูต “ใส่ใจ” จะตั้งค่าเริ่มต้นต่างๆ ของระบบไว้ที่นี่ ดังนี้

มัลติทาสก์คืออะไร ?

โปรเจค “ใส่ใจ” ได้นำการทำงานแบบมัลติทาสก์เข้ามาใช้งาน ซึ่งมันก็คือการทำงานแบบหลายกระบวนการในเวลาเดียวกัน โดยการแบ่งแยกคอร์ของ MCU ควบคุมการทำงานของเทรด (Thead) ที่แบ่งงานออกเป็นย่อยๆ โดยใช้หน่วยประมวลผลเพียงแค่ตัวเดียว และมันยังสามารถกำหนดระดับความสำคัญ (Priority) ได้ ความสามารถนี้ ESP32 จะใช้ระบบปฏบัติการ FreeRTOS ซึ่งเป็นระบบปฏิบัติการสำหรับอุปกรณ์สมองกลแบบโอเพ่นซอร์ส มันมีขนาดเล็กมากๆ ใช้ภาษาซีเพียง 3–4 ไฟล์เท่านั้นเองครับ แต่มีประสิทธิภาพการทำงานมาก นิยมใช้กับไมโครคอนโทรเลอร์

“FreeRTOS จะเรียกเทรด (Thead) ว่าทาสก์ (Task)”

ทาสก์มันก็คือโปรแกรมย่อยขนาดเล็กนั่นเอง มันจะทำงานไม่มีที่สิ้นสุด ทาสก์ไม่เหมือนกับฟังก์ชันนะครับ มันไม่สามารถคืนค่าหรือ Return ค่าตัวแปรที่สร้างในทาสก์ได้ อีกอย่างเราจะออกจากทาสก์ไม่ได้นะครับ แต่ละตัวทำงานอย่างอิสระและมีสแต็กของใครของมัน และตัวทาสก์เองจะมีสองสถานะคือ ทำงาน (running) กับไม่ทำงาน (not running) ส่วนการสร้าง Task ใน ESP32 เราสามารถใช้ฟังก์ชัน xTaskCreate() สร้างขึ้นมา ยกตัวอย่างดังต่อไปนี้

xTaskCreate(&Task1, "myTask1",  2000, NULL, 10, NULL);

จะเป็นการสร้างทาสก์ขึ้นมาแล้วไปเรียกใช้ฟังก์ชัน “Task1” เข้ามาทำงาน ทาสก์นี้กำหนดชื่อว่า “myTask1” กำหนดขนาดของสแต็กจำนวน 2000 มีระดับความสำคัญเท่ากับ 10 (ค่า 0 คือค่าความสำคัญน้อยที่สุด)

จะกำหนดขนาดสแต็กอย่างไร ?

แต่ละทาสก์จะมีสแต็กเป็นของตัวเอง ซึ่ง kernel จะสร้างมันขึ้นมาตอนที่เราสร้างครั้งแรก ส่วนค่าของ usStackDepth จะเป็นตัวบอกขนาดของสแต็ก ถ้าเป็นสถาปัตยกรรมแบบ 32 บิต จะมีขนาด stack width เท่ากับ 4-byte

ยกตัวอย่าง Task1 ของ “ใส่ใจ” ได้กำหนด usStackDepth เท่ากับ 2000 แสดงว่า เราได้ทาสก์ที่มีสแต็กขนาด 1000 x 4 bytes หรือ 4000 bytes มาใช้งาน เท่าที่ผู้เขียนได้ลองทดสอบการสร้างทาสก์ใน ESP32 พบว่า ถ้ากำหนดขนาด usStackDepth น้อยกว่า 1000 สำหรับโปรแกรมทั่วไป ก็จะมีโอกาสเกิด Stack Over Flow ได้ ซึ่งจะทำให้บอร์ด ESP32 ทำการรีบูตตลอดเวลา ต้องระมัดระวังจุดนี้ให้มากด้วยครับ

ใสใจ” จะกำหนด usStackDepth อยู่ที่ 1000 ถึง 3000 ขึ้นอยู่กับจำนวน words ที่ใช้งาน ดูรายละเอียดเพิ่มเติมเกี่ยวกับการสร้างทาสก์ได้ที่ [ xTaskCreate ]

ยังมีรายละเอียดอีกมากมายจริงๆ สำหรับ FreeRTOS เอาไว้บทความต่อไปจะเจาะลงลึกอีกที สำหรับใครสนใจศึกษาต่อดูได้ที่นี่ครับ [ FreeRTOS Manual ] และดูการใช้พารามิเตอร์ต่างๆ ได้ที่นี่ [ How to use FreeRTOS ]

// *** (10) FUNCTION SETUP() ***void setup() {//เรียกใช้งาน Serial Monitor
Serial.begin(115200);
/* -------------------------------------------------------------- *///กำหนดโหมดใช้งานให้กำขา GPIOpinMode(LEDWiFiPin, OUTPUT);
pinMode(LEDSensorPin, OUTPUT);
pinMode(LEDWateringPin, OUTPUT);
pinMode(WateringPin, OUTPUT);
pinMode(RelayPin, OUTPUT);
pinMode(SwitchPin, INPUT_PULLUP);
/* -------------------------------------------------------------- *///ตั้งสถานะเริ่มต้นให้กับขา GPIO เพื่อป้องกันการทำงานเองตอนที่บอร์ด ESP32 รีบูตdigitalWrite(LEDWiFiPin, LOW);
digitalWrite(LEDSensorPin, LOW);
digitalWrite(LEDWateringPin, LOW);
digitalWrite(LEDWateringPin, HIGH);
digitalWrite(RelayPin, HIGH);
/* -------------------------------------------------------------- */// สร้างมัลติทาสก์ขึ้นมาใช้งานอย่างอิสระจำนวน 5 ทาสก์//TASK1: ลูปเช็คค่าความชื้นดิน ถ้าต่ำกว่าที่กำหนดให้เร่ิมการรดน้ำต้นไม้
xTaskCreate(&Task1, "Task1", 2000, NULL, 9, NULL);
//TASK2: ลูปอ่านค่าการกดปุ่มสวิตซ์ ถ้ามีการกดปุ่มสวิตซ์ให้เริ่มการรดน้ำต้นไม้
xTaskCreate(&Task2, "Task2", 1000, NULL, 9, NULL);
//TASK3: ลูปให้หลอดไฟ LED กระพริบ ถ้ามีการรดน้ำต้นไม้
xTaskCreate(&Task3, "Task3", 3000, NULL, 8, NULL);
//TASK4: ลูปเช็คการเชื่อมต่อ WiFi และให้เชื่อมต่อใหม่เองแบบอัตโนมัติ
xTaskCreate(&Task4, "Task4", 3000, NULL, 10, NULL);
//TASK5: รดน้ำต้นไม้ตามเวลาที่กำหนด
xTaskCreate(&Task5, "Task5", 3000, NULL, 9, NULL);
/* -------------------------------------------------------------- *///ทำการเชื่อมต่อ WiFiBlynk.connectWiFi(ssid, pass);/* -------------------------------------------------------------- *///ตั้งเวลาส่งข้อมูลให้ BLYNK SERVER
timer.setInterval(sendSensorTime, sendSensor);
//ตั้งเวลาส่งข้อมูลให้ BLYNK SERVER
timer.setInterval(sendSensor2Time, sendSensor2);
//ตั้งเวลาให้เช็คว่า Blynk เชื่อมต่ออยู่หรือไม่
timer.setInterval(checkConnectionTime, CheckConnection);
//ตั้งเวลาส่งข้อมูลเข้า Google Firebase
timer.setInterval(sendFirebaseTime, sendFirebase);
/* -------------------------------------------------------------- *///ทำการเชื่อมต่อ Google FirebaseFirebase.begin(FIREBASE_HOST, FIREBASE_AUTH);/* -------------------------------------------------------------- *///แสดงข้อความใน Serial Monitor
Serial.println();
Serial.println("Welcom to smartfram IoT");
Serial.print("WiFi Connected: ");
Serial.println(WiFi.localIP());
Serial.print("WiFi signal RSSI: ");
Serial.print(WiFi.RSSI());
Serial.println("dBm");
/* -------------------------------------------------------------- *///ดึงเวลาจากอินเตอร์เน็ตจาก NTP Time Server
configTime(7 * 3600, 0, ntp_server1, ntp_server2, ntp_server3);
//แสดงข้อความใน Serial Monitor
Serial.println("Waiting for time.");
while (!time(nullptr)) {Serial.print(".");
vTaskDelay(100 / portTICK_PERIOD_MS);
}//แสดงข้อความใน Serial Monitor
Serial.println();
Serial.println("Current time: " + NowString());
/* -------------------------------------------------------------- *///ทำการเชื่อมต่อ BLYNK SERVER
Blynk.config(auth, server, port);
Blynk.connect();
/* -------------------------------------------------------------- */}

(11) FUNCTION LOOP()

มีให้เหลือเฉพาะรัน Blynk ทำงาน และรัน Timer เพื่อตั้งเวลาเท่านั้นครับ งานที่เหลือไม่ได้หายไปไหนแต่จะทำงานแบบมัลติทาสก์ อาจจะดูผิดหูผิดตาสำหรับคนเคยเขียนโปรแกรมที่ใช้ MCU แบบคอร์เดียว เช่น UNO / NodeMCU ที่ทุกอย่างจะเขียนลงในฟังก์ชันลูป

// *** (11) FUNCTION LOOP() ***void loop() {Blynk.run();
timer.run();
}

(12) FUNCTION SENDSENSOR()

ฟังก์ชันนี้เอาไว้ส่งข้อมูลเซ็นเซอร์ไปยัง BLYNK SERVER โดยหลอดไฟ LED ที่ต่อกับบอร์ด ESP32 และ Widget LED ใน BLYNK MOBILE APP จะติดสว่างในระหว่างการส่งข้อมูล พร้อมทั้งแสดงข้อความการทำงานใน Serail Monitor ด้วย

การทำงานจะเร่ิมจาก เรียกใช้ฟังก์ชัน GetDHT11() อ่านข้อมูลเซ็นเซอร์ DHT11 เพื่อหาค่าอุณหภูมิและความชื้นสัมพัทธ์ แล้วอ่านข้อมูลแบบอนาล๊อกจากเซ็นเซอร์วัดความชื้นในดิน โดยผลลัพธ์ที่ได้จะเป็นค่า 12 บิต มีค่าตั้งแต่ 0–4095 จากน้ันจะเรียกใช้ฟังก์ชัน SoilPercentValue() เพื่อหาค่าความชื้นในดินในรูปแบบของเปอร์เซ็นต์

// *** (12) FUNCTION SENDSENSOR() ***void sendSensor() {//ให้หลอดไฟ LED ติด
digitalWrite(LEDWiFiPin, HIGH);
//ให้หลอดไฟ LED บน BLYNK MOBILE APP ติด
LEDSensor.on();
//แสดงข้อความใน Serial Monitor
Serial.print(NowString());
Serial.println(", Send data to blynk server");
/* -------------------------------------------------------------- *///อ่านข้อมูลเซ็นเซอร์ DHT11
GetDHT11(dhtdata);
Blynk.virtualWrite(Widget_Humidity, dhtdata[0]);
Blynk.virtualWrite(Widget_Temperature, dhtdata[1]);
Blynk.virtualWrite(Widget_SoilRawValue, analogRead(SoilSensorPin));
Blynk.virtualWrite(Widget_SoilLevel, SoilPercentValue());
/* -------------------------------------------------------------- *///แสดงข้อความใน Serial Monitor
Serial.print(NowString());
Serial.println(", Send DHT11 / Soil sensors to blynk server.");
/* -------------------------------------------------------------- *///ให้หลอด LED ที่แสดงสถานะดับ
digitalWrite(LEDWiFiPin, LOW);
//ให้หลอดไฟ LED บน BLYNK MOBILE APP ดับ
LEDSensor.off();
}

(13) FUNCTION SENDSENSOR2()

ฟังก์ชันนี้เอาไว้ส่งข้อมูลเซ็นเซอร์ชุดที่ 2 ไปยัง BLYNK SERVER สาเหตุที่แบ่งออกเป็น 2 ชุดเพราะว่าถ้าเราส่งข้อมูลที่มีขนาดใหญ่ๆ ไปในครั้งเดียวกัน อาจทำให้ข้อมูลที่ส่งไม่ผ่าน เสียหายระหว่างทางได้ เท่าที่ผู้เขียนเคยเจอคือ BLYNK SERVER จะปฏิเสธ package ที่มีขนาดใหญ่ และมีข้อความแจ้งเตือนกลับมาว่า “Package to Big” จากนั้นบอร์ด ESP32 จะไม่สามารถเชื่อมต่อกับ BLYNK SERVER ได้อีก ต้องทำการรีเซ็ท บอร์ดใหม่อย่างเดียวเลยครับ

การทำงานจะเร่ิมจากสั่งให้หลอดไฟ LED ที่ต่อกับบอร์ด ESP32 และ Widget LED ใน BLYNK MOBILE APP ติดสว่างในระหว่างการส่งข้อมูล พร้อมทั้งแสดงข้อความการทำงานใน Serail Monitor จากนั้นจะทำการอ่านข้อมูลแบบอนาล๊อกจากเซ็นเซอร์วัดความเข้มของแสง โดยผลลัพธ์ที่ได้จะเป็นค่า 12 บิต มีค่าตั้งแต่ 0–4095 จากน้ันจะเรียกใช้ฟังก์ชัน LdrPercentValue() เพื่อหาค่าความเข้มของแสงในรูปแบบของเปอร์เซ็นต์

แล้วหาค่า RSSI สัญญาณของ WiFi โดยใช้ฟังก์ชัน WiFi.RSSI() ผลลัพธ์ที่ได้จะเป็นหน่วย dBm มีค่าตั้งแต่ -40 ถึง -90 แล้วเรียกใช้ฟังก์ชัน WifiPercentSignal() เพื่อหาค่าสัญญาณของ WiFiในรูปแบบของเปอร์เซ็นต์

// *** (13) FUNCTION SENDSENSOR2() ***void sendSensor2() {//ให้หลอดไฟ LED ติด
digitalWrite(LEDWiFiPin, HIGH);
//ให้หลอดไฟ LED บน BLYNK MOBILE APP ติด
LEDSensor.on();
//แสดงข้อความใน Serial Monitor
Serial.print(NowString());
Serial.println(", Send data to blynk server");
/* -------------------------------------------------------------- *///แสดงข้อความใน Serial Monitor
Serial.print(NowString());
Serial.print(", Wifi Signal: ");
Serial.print(WifiPercentSignal());
Serial.println("%");
/* -------------------------------------------------------------- *///อัพเดทข้อมูลไปยัง Blynk serverBlynk.virtualWrite(Widget_LightRawValue, analogRead(LdrSensorPin));
Blynk.virtualWrite(Widget_LightLevel, LdrPercentValue());
Blynk.virtualWrite(Widget_WifiSignal, WifiPercentSignal());
Blynk.virtualWrite(Widget_WifiRawValue, WiFi.RSSI());
/* -------------------------------------------------------------- *///แสดงข้อความใน Serial Monitor
Serial.print(NowString());
Serial.println(", Send LDR sensors / WIFI to blynk server.");
/* -------------------------------------------------------------- *///ให้หลอด LED ที่แสดงสถานะดับ
digitalWrite(LEDWiFiPin, LOW);
//ให้หลอด LED บน app Blynk ดับ
LEDSensor.off();
}

(14) FUNCTION SENDFIREBASE()

เริ่มจากสร้างตัวแปรเพื่อเก็บชุดข้อมูลจากเซ็นต์เซอร์ต่างๆ แบบ JSON ชื่อ root แล้วส่งไปเก็บใน Google Fiebase จากนั้นแสดง Log ใน Serial Monitor

// *** (14) FUNCTION SENDFIREBASE() ***void sendFirebase() {//อัพเดทข้อมูลไปยัง BLYNK SERVER
GetDHT11(dhtdata);
//อัพเดทข้อมูลให้ Firebase ในรูปแบบ JSON
StaticJsonBuffer<200> jsonBuffer;
JsonObject& root = jsonBuffer.createObject();
root["temperature"] = dhtdata[1];
root["humidity"] = dhtdata[0];
root["time"] = NowString();
root["ldr"] = LdrPercentValue();
root["soil"] = SoilPercentValue();
root["wifi"] = WifiPercentSignal();
// ส่งข้อมูลใหม่เป็นเก็บใน Firebase /SmartFarmString name = Firebase.push("SmartFarm", root);// ถ้ามี error
if(Firebase.failed()) {
//แสดงข้อความบนคอนโซล
Serial.print(NowString());
Serial.print(", Firebase pushing /SmartFarm failed:");
Serial.println(Firebase.error());
return;
}//แสดงข้อความบนคอนโซล
Serial.print(NowString());
Serial.println(", Firebase connected.");
Serial.print(NowString());
Serial.print(", Firebase pushed: /SmartFarm/");
Serial.println(name);
}
Image for post
Image for post
แสดงการเก็บข้อมูลใน Google Firebase

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

พบกับใหม่ตอนต่อไป “ตอนที่ 9 การพัฒนาโปรแกรมควบคุม ใส่ใจ ด้วย ESP32 ตอนที่ 2

Image for post
Image for post

ผู้เขียนมีความตั้งใจที่จะถ่ายทอดประสบการณ์ การพัฒนาระบบรดน้ำต้นไม้อัจฉริยะ “ใส่ใจ” ด้วย ESP32 เพื่อให้ผู้ที่สนใจ นำไปพัฒนาต่อยอดสร้างนวกรรมใหม่ๆ ขอเป็นส่วนหนึ่งเล็กๆ น้อยๆ ช่วยผลักดันประเทศของเราให้เข้าสู่ยุคดิจิตอล Thailand 4.0

ติดตามข่าวสารเกี่ยวกับ IoT ได้ที่ facebook IoT Thailand

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store