ขอตั้งกระทู้ Multitasking for ESP32 ครับ

สำหรับกระทู้นี้ ผมขออนุญาต @Remy_Martin นะครับ ผมจะขอศึกษา Multitasking สำหรับ ESP32 หากใครมีข้อมูล อยู่ก็เอามาลงแบ่งปันกันครับ หรือไม่บางท่านที่ สงสัย ทำแล้วยังไม่ได้ ส่วนผมจะศึกษาข้อมูลจากกระทู้นี้ โดย การแสดงความคิดเห็นกัน ผิดถูกยังไง ก็ว่ากันมาเลยครับ โดยผมตั้งคำถามที่ต้องการรู้ดังนี้

  1. ผมต้องการใช้ ทั้ง 2 core (0,1) ได้อย่างมีประสิทธิภาพ
  2. การนำ Library ต่างๆ มาทดลองใช้งานกันดูว่าอันไหน เหมาะกับงานของเรา หรืออันไหนเหมาะสำหรับงานแบบอื่น
  3. ความหมายของ Code ที่ใช้
    ***คือถ้าทำได้จริง แน่นอนครับ ESP32 จะเป็นตัวเลือกที่ดีที่สุด และจะทำให้เห็นความแตกต่างว่ามันต่างจาก ESP8266 อย่างไร เมื่อเราใช้งานจริงๆ ที่เหนือกว่าการเปรียบเทียบจาก Specification

ขอบคุณ @Remy_Martin ที่ให้โครงสร้างหลักมาครับ

/*********
 Rui Santos
 Complete project details at https://randomnerdtutorials.com  
*********/

TaskHandle_t Task1;
TaskHandle_t Task2;

// LED pins
const int led1 = 2;
const int led2 = 4;

void setup() {
 Serial.begin(115200); 
 pinMode(led1, OUTPUT);
 pinMode(led2, OUTPUT);

 //create a task that will be executed in the Task1code() function, with priority 1 and executed on core 0
 xTaskCreatePinnedToCore(
                   Task1code,   /* Task function. */
                   "Task1",     /* name of task. */
                   10000,       /* Stack size of task */
                   NULL,        /* parameter of the task */
                   1,           /* priority of the task */
                   &Task1,      /* Task handle to keep track of created task */
                   0);          /* pin task to core 0 */                  
 delay(500); 

 //create a task that will be executed in the Task2code() function, with priority 1 and executed on core 1
 xTaskCreatePinnedToCore(
                   Task2code,   /* Task function. */
                   "Task2",     /* name of task. */
                   10000,       /* Stack size of task */
                   NULL,        /* parameter of the task */
                   1,           /* priority of the task */
                   &Task2,      /* Task handle to keep track of created task */
                   1);          /* pin task to core 1 */
   delay(500); 
}

//Task1code: blinks an LED every 1000 ms
void Task1code( void * pvParameters ){
 Serial.print("Task1 running on core ");
 Serial.println(xPortGetCoreID());

 for(;;){
   digitalWrite(led1, HIGH);
   delay(1000);
   digitalWrite(led1, LOW);
   delay(1000);
 } 
}

//Task2code: blinks an LED every 700 ms
void Task2code( void * pvParameters ){
 Serial.print("Task2 running on core ");
 Serial.println(xPortGetCoreID());

 for(;;){
   digitalWrite(led2, HIGH);
   delay(700);
   digitalWrite(led2, LOW);
   delay(700);
 }
}

void loop() {
 
}
2 Likes

ผมเริ่มต้นจาก Library กันก่อนครับ ของ TridentTD_EasyFreeRTOS32

TridentTD_EasyFreeRTOS32-master.zip (130.6 KB)

ดูแล้วจะมี 3 ตัวอย่าง แต่ Code โครงสร้างหลัก เค้าไปเขียนกำหนดใน ไฟล์ TridentTD_EasyFreeRTOS32.cpp และ TridentTD_EasyFreeRTOS32.h

1

ทีนี้ถ้าเทียบกับโครงสร้างหลัก ใน library ของ TridentTD_EasyFreeRTOS32 เขียนไว้ที่ตำแหน่งไหน ผมก็จะมาตรวจสอบคำสั่งที่ไฟล์ TridentTD_EasyFreeRTOS32.cpp และ TridentTD_EasyFreeRTOS32.h

RTOS คืออะไร ?

เจ้า RTOS (Real-Time Operating System ) มันเป็น Kernel ที่ช่วยให้บอร์ด ESP32 สามารถเขียนโปรแกรมเพื่อแบ่งเวลาแบ่งทรัพยากรในการประมวลผลงานต่างๆ ในแต่ละ CPU ได้
ที่มา: https://medium.com/@attaphon/ลองเขียนโปรแกรมแบบ-multi-tasking-บน-esp32-กัน-rtos-fd098f798f8f

เพื่อให้โครงสร้างโปรเจ็คมีระเบียบ เราสามารถแยกคลาสต่างๆออกมาเป็นไฟล์ต่างหากได้ โดยโครงสร้างไฟล์ที่ทำหน้าที่เก็บ 1 คลาส ประกอบด้วย 2 ไฟล์ คือ

  • ไฟล์นามสกุล .h ไว้เก็บโครงสร้างของคลาสที่ต้องการสร้างขึ้นมา เป็นการกำหนดโดยภาพรวมว่าภายในคลาสเรานั้นจะประกอบด้วยฟังก์ชัน ตัวแปรอะไรบ้าง มีแต่โครงสร้างอย่างเดียว ทำงานไม่ได้
  • ไฟล์ .cpp ไว้เขียนโค้ดทำงานเพื่อให้คลาสที่ได้มาสามารถใช้งานได้จริง โดยเราต้องยึดตามโครงสร้างที่กำหนดไว้ใน .h
    https://vzrnote.blogspot.com/2019/07/c-class-h-cpp.html

TridentTD_EasyFreeRTOS32.h

#ifndef __TRIDENTTD_EASY_FREERTOS32_H__
#define __TRIDENTTD_EASY_FREERTOS32_H__

#include <Arduino.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <freertos/semphr.h>

#ifndef TridentOS 
#define TridentOS  EasyFreeRTOS32
#endif

#ifndef DELAY
#define DELAY(a) vTaskDelay((a)/portTICK_PERIOD_MS);
#endif

#ifndef VOID
#define VOID
#endif

#ifndef SETUP
#define SETUP()
#endif

#ifndef LOOP
#define LOOP()     for(;;)
#endif

extern portMUX_TYPE mux;

#ifndef NO_INTERRUPTS
#define NO_INTERRUPTS()     taskENTER_CRITICAL_ISR( &mux )
#endif

#ifndef INTERRUPTS
#define INTERRUPTS()        taskEXIT_CRITICAL_ISR( &mux )
#endif

class EasyFreeRTOS32 {
public:
  EasyFreeRTOS32() {};
  void start( TaskFunction_t fn, void * const arg=NULL, const uint32_t StackDepth=2048, uint8_t core_no=1);
  void stop();
  void resume();
    
  EasyFreeRTOS32* ptr = const_cast<EasyFreeRTOS32*>(this);
private:
  TaskHandle_t  _task_handler;
};

#endif //__TRIDENTTD_EASY_FREERTOS32_H__

จาก code สิ่งที่ผมพอจะรู้ และหาข้อมูลมา โดยบางส่วนก็ยังไม่เข้าใจมากนัก คือ

  1. #ifndef TRIDENTTD_EASY_FREERTOS32_H
    #define TRIDENTTD_EASY_FREERTOS32_H

ความหมาย : #ifndef นิยมใช้ก่อนคำสั่ง #define เพื่อที่จะตรวจสอบว่ามีการ define ไว้หรือไม่ ถ้าไม่มีก็ให้ define
define = การกำหนด
ดังนั้น มันก็ควรจะหมายถึง มีการกำหนด TRIDENTTD_EASY_FREERTOS32_H เอาไว้หรือไม่ ถ้ายังไม่ได้กำหนดก็กำหนด TRIDENTTD_EASY_FREERTOS32_H เอาไว้เลย

  1. #include <Arduino.h>
    #include <freertos/FreeRTOS.h>
    #include <freertos/task.h>
    #include <freertos/semphr.h>
    นำไฟล์ header ต่างๆ ที่จำเป็นต้องใช้เข้ามา

  2. #ifndef TridentOS
    #define TridentOS EasyFreeRTOS32
    #endif

ข้อมูล : https://blog.thaieasyelec.com/basic-knowledge-for-c-programming-ch1/
ดังนั้น มันก็ควรจะหมายถึง มีการกำหนด TridentOS เอาไว้หรือไม่ ถ้ายังไม่ได้กำหนดก็กำหนดเป็น TridentOS EasyFreeRTOS32 เอาไว้เลย และจบคำสั่ง

  1. #ifndef DELAY
    #define DELAY(a) vTaskDelay((a)/portTICK_PERIOD_MS);
    #endif

ดังนั้น มันก็ควรจะหมายถึง มีการกำหนด DELAY เอาไว้หรือไม่ ถ้ายังไม่ได้กำหนดก็กำหนดเป็น DELAY(a) vTaskDelay((a)/portTICK_PERIOD_MS); เอาไว้เลย และจบคำสั่ง

#ifndef VOID
#define VOID
#endif

ดังนั้น มันก็ควรจะหมายถึง มีการกำหนด VOID เอาไว้หรือไม่ ถ้ายังไม่ได้กำหนดก็กำหนดเป็น VOID เอาไว้เลย และจบคำสั่ง

  1. #ifndef SETUP
    #define SETUP()
    #endif

ดังนั้น มันก็ควรจะหมายถึง มีการกำหนด SETUP เอาไว้หรือไม่ ถ้ายังไม่ได้กำหนดก็กำหนดเป็น SETUP เอาไว้เลย และจบคำสั่ง

  1. #ifndef LOOP
    #define LOOP() for(;; )
    #endif

ดังนั้น มันก็ควรจะหมายถึง มีการกำหนด LOOP เอาไว้หรือไม่ ถ้ายังไม่ได้กำหนดก็กำหนดเป็น LOOP() for(;; ) เอาไว้เลย และจบคำสั่ง

extern portMUX_TYPE mux;
คือ เมื่อย้ายไปที่ฟังก์ชั่นการตั้งค่าเริ่มต้นด้วยการเปิดการเชื่อมต่อแบบอนุกรมเพื่อให้สามารถแสดงผลลัพธ์ของโปรแกรมของเราได้

extern คือ External interrupts ( การขัดจังหวะภายนอก , ตัวแปรประเภท portMUX_TYPE)

  1. #ifndef NO_INTERRUPTS
    #define NO_INTERRUPTS() taskENTER_CRITICAL_ISR( &mux )
    #endif

ดังนั้น มันก็ควรจะหมายถึง มีการกำหนด NO_INTERRUPTS เอาไว้หรือไม่ ถ้ายังไม่ได้กำหนดก็กำหนดเป็น NO_INTERRUPTS() taskENTER_CRITICAL_ISR( &mux ) เอาไว้เลย และจบคำสั่ง

  1. #ifndef INTERRUPTS
    #define INTERRUPTS() taskEXIT_CRITICAL_ISR( &mux )
    #endif

ดังนั้น มันก็ควรจะหมายถึง มีการกำหนด INTERRUPTS เอาไว้หรือไม่ ถ้ายังไม่ได้กำหนดก็กำหนดเป็น INTERRUPTS() taskEXIT_CRITICAL_ISR( &mux ) เอาไว้เลย และจบคำสั่ง

  1. class EasyFreeRTOS32 {
    public:
    EasyFreeRTOS32() {};
    void start( TaskFunction_t fn, void * const arg=NULL, const uint32_t StackDepth=2048, uint8_t core_no=1);
    void stop();
    void resume();

EasyFreeRTOS32* ptr = const_cast<EasyFreeRTOS32*>(this);
private:
TaskHandle_t _task_handler;
};

http://marcuscode.com/lang/java/classes-and-objects

ดังนั้น มันก็ควรจะหมายถึง สร้างคลาสที่ชื่อว่า EasyFreeRTOS32 :ซึ่งจะไปตรงกับกับ ข้อที่ 3 แสดงว่าเป็นสร้าง class เพื่อรองรับการ define และในคลาสนี้สมาชิกที่เป็นตัวแปร 1 ตัวคือ EasyFreeRTOS32() {};
และสมาชิกแบบเมธอด 3 ตัว คือ เมธอด คือ start() ,stop() และ void resume()

  1. void start( TaskFunction_t fn, void * const arg=NULL, const uint32_t StackDepth=2048, uint8_t core_no=1);
  2. void stop();
  3. void resume();

เอาแค่พอรู้
https://sites.google.com/site/introductiontoprogrammingc/hnathi-7---taw-pae-rphxy-texr-pointers
EasyFreeRTOS32* ptr = const_cast<EasyFreeRTOS32*>(this);

Pointer คือตัวแปรดัชนีที่ เก็บค่าตำแหน่งแอดเดรสของหน่วยความจำ ซึ่งตัวแปรพอยเตอร์นั้น จะมีเครื่องหมายดอกจันทร์ () นำหน้าเสมอ ดังตัวอย่างต่อไปนี้ แสดง ptr คือชื่อตัวแปร ที่เป็นแบบ pointer โดยเก็บค่า const_cast<EasyFreeRTOS32>(this) เอาไว้ในตำแหน่งแอดเดรสของหน่วยความจำ

private เข้าถึงได้เฉพาะภายใน Class นี้เท่านั้น ก็คือ ให้ TaskHandle_t _task_handler เข้าถึงได้เฉพาะภายใน Class นี้เท่านั้น

  1. #endif //TRIDENTTD_EASY_FREERTOS32_H จบคำสั่ง TRIDENTTD_EASY_FREERTOS32_H

จากการศึกษาดูก็พอเข้าใจในระดับหนึ่ง (เอาพอรู้ครับ)

TridentTD_EasyFreeRTOS32.cpp

#include "TridentTD_EasyFreeRTOS32.h"

#ifndef TASK_RESUME
#define TASK_RESUME(c)  vTaskResume(c)
#endif
#ifndef TASK_STOP
#define TASK_STOP(c)    vTaskSuspend(c)
#endif

void EasyFreeRTOS32::stop()   { TASK_STOP(_task_handler);   }
void EasyFreeRTOS32::resume() { TASK_RESUME(_task_handler); }

void EasyFreeRTOS32::start( TaskFunction_t fn, void * const arg, const uint32_t StackDepth, uint8_t core_no) {
  String task_name = String("FreeRTOS_Task_")+ String(random(10000));
  //Serial.println(task_name);
  if(core_no>1) core_no = 1;
  xTaskCreatePinnedToCore( fn, task_name.c_str(),StackDepth, arg, 5, &ptr->_task_handler, core_no); 
  //xTaskCreate( fn, task_name.c_str(),StackDepth, arg, 5, &ptr->_task_handler); 
}

portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;
  1. #include “TridentTD_EasyFreeRTOS32.h”

นำไฟล์ header ชื่อ TridentTD_EasyFreeRTOS32.h เข้ามา

  1. #ifndef TASK_RESUME
    #define TASK_RESUME© vTaskResume©
    #endif
    #ifndef TASK_STOP
    #define TASK_STOP© vTaskSuspend©
    #endif

ดังนั้น มันก็ควรจะหมายถึง
2.1 มีการกำหนด TASK_RESUME เอาไว้หรือไม่ ถ้ายังไม่ได้กำหนดก็กำหนดเป็น TASK_RESUME© และ vTaskResume© เอาไว้เลย และจบคำสั่ง
2.2 มีการกำหนด TASK_STOP เอาไว้หรือไม่ ถ้ายังไม่ได้กำหนดก็กำหนดเป็น TASK_STOP© และ vTaskSuspend© เอาไว้เลย และจบคำสั่ง

  1. void EasyFreeRTOS32::stop() { TASK_STOP(_task_handler); }
    void EasyFreeRTOS32::resume() { TASK_RESUME(_task_handler); }

void EasyFreeRTOS32::start( TaskFunction_t fn, void * const arg, const uint32_t StackDepth, uint8_t core_no) {
String task_name = String(“FreeRTOS_Task_”)+ String(random(10000));
//Serial.println(task_name);
if(core_no>1) core_no = 1;
xTaskCreatePinnedToCore( fn, task_name.c_str(),StackDepth, arg, 5, &ptr->_task_handler, core_no);
//xTaskCreate( fn, task_name.c_str(),StackDepth, arg, 5, &ptr->_task_handler);

น่าจะเป็นชุดคำสั่งหลักในการสร้าง task ครับ โดยค่าตัวแปรต่างๆ ที่ดึงมาจากไฟล์ .h

class EasyFreeRTOS32 {
public:
EasyFreeRTOS32() {};
void start( TaskFunction_t fn, void * const arg=NULL, const uint32_t StackDepth=2048, uint8_t core_no=1);
void stop();
void resume();

EasyFreeRTOS32* ptr = const_cast<EasyFreeRTOS32*>(this);
private:
TaskHandle_t _task_handler;
};

ตรง ส่วนนี้แหละครับ
void start( TaskFunction_t fn, void * const arg=NULL, const uint32_t StackDepth=2048, uint8_t core_no=1);
ที่ผมจะดูว่า เค้ากำหนด หมายเลข core อะไรในการใช้ ปรากฎว่าเค้าใช้ Core no.1 ในการทำงานอย่างเดียว “core_no=1” ไม่มีการใช้ core 0 เข้ามาร่วมด้วย ดังนั้นถ้าผม วิเคราะห์ไม่ผิด ก็เป็นการใช้ Core หมายเลข 1 อย่างเดียว และ StackDepth=2048 เป็นค่าเดียวเท่านั้น คนไม่รู้ก็ปรับแต่งไม่ได้ เหมือนกับว่า ใช้ core เดียว แต่แบ่งการงานออกเป็นหลายงาน ซึ่งก็ไม่ต่างอะไรกับการแบ่งงานใน Esp8266 (***ถ้าวิเคราะห์ผิดพลาดก็แย้งมาเลยครับ ผมยินดีรับฟังทุกๆท่านเลย)

ก็จบส่วน libraly นี้ก่อนนะครับ

2 Likes

อันนี้เป็น เคสคัวอย่างเวลาเจอ watchdog ครับ

ขอบคุณครับ เดี๋ยวยังไงจะลองปรับเปลี่ยนดูครับ

code ผมลองเป็นตัวอย่างการแบ่ง core โดย Core0 6 task Core1 6 task ครับ โดยผนวกชุด Wifi manager เข้าไปด้วย โดยสร้าง task ไว้เฉยๆ ยังไม่ได้ใส่คำสั่งลงไป ซึ่งชุดนี้จะเหมือนกับชุดโครงสร้างหลักในโพสต์ แรกสุด

#include <FS.h>                   //this needs to be first, or it all crashes and burns...
#include <SPIFFS.h>//เพิ่ม

#include <WiFi.h>          //https://github.com/esp8266/Arduino
#include <WiFiClient.h>


//needed for library
#include <DNSServer.h>
#include <WebServer.h>
#include <WiFiManager.h>          //https://github.com/tzapu/WiFiManager
#include <ArduinoJson.h>       //Ver 5.13.4   //https://github.com/bblanchon/ArduinoJson


//----------------------------------  กำหนดหมายเลขของขาของ Node MCU ESP32  --------------------------------------------//

const int Ledblynk = 2;                     // ใช้ไฟ LED สีฟ้า ของบอร์ด MCU ESP32 ให้มีสัญญาณไฟกระพริบ ตาม Code ที่เขียน
const int AP_Config = 23;                // ใช้เป็นปุ่มกด เพื่อเข้า AP Config ได้ตามความต้องการของผู้ใช้
//------------------------------------------------------------------------------------------------------------------------//



bool shouldSaveConfig = false;

//callback notifying us of the need to save config
void saveConfigCallback () {
  Serial.println("Should save config");
  shouldSaveConfig = true;
}
//------------------------------------------------------------------------------------------------------------------------//


//-------------สำหรับ Server local ที่แจกให้ เพิ่ม **** แค่ 2 บรรทัดนี้--------------   (ถ้าเป็น Blynk Server ปกติไม่ต้องใส่)  ----------//
//char server[] = "oasiskit.com";
//int port = 8080;
//------------------------------------------------------------------------------------------------------------------------//

TaskHandle_t Task1;
TaskHandle_t Task2;
TaskHandle_t Task3;
TaskHandle_t Task4;
TaskHandle_t Task5;
TaskHandle_t Task6;
TaskHandle_t Task7;
TaskHandle_t Task8;
TaskHandle_t Task9;
TaskHandle_t Task10;
TaskHandle_t Task11;
TaskHandle_t Task12;




//------------------------------------------------------------------------------------------------------------------------//
//*********************************************       void setup        **************************************************//
//------------------------------------------------------------------------------------------------------------------------//

void setup() {

  //-------IO NODE MCU Esp32-------//
  pinMode(Ledblynk, OUTPUT);      //กำหนดโหมดใช้งานให้กับขา Ledblynk เป็นขา สัญญาณไฟ ในสภาวะต่างๆ
  pinMode(AP_Config, INPUT_PULLUP);//กำหนดโหมดใช้งานให้กับขา AP_Config เป็นขา กดปุ่ม ค้าง เพื่อตั้งค่า AP config


  // ให้ LED ทั้งหมดดับก่อน
  digitalWrite(Ledblynk, LOW);//ให้หลอด LED สีฟ้าดับก่อน

  //-------------------------------//


  Serial.begin(115200);
  //-------------------------------//





  //*************************    การ อ่าน  เขียนค่า WiFi + Password ]ลงใน Node MCU ESP32   ************//

  //read configuration from FS json
  Serial.println("mounting FS...");//แสดงข้อความใน Serial Monitor

  if (SPIFFS.begin(true)) {
    Serial.println("mounted file system");
    if (SPIFFS.exists("/config.json")) {
      //file exists, reading and loading
      Serial.println("reading config file");
      File configFile = SPIFFS.open("/config.json", "r");
      if (configFile) {
        Serial.println("opened config file");
        size_t size = configFile.size();
        // Allocate a buffer to store contents of the file.
        std::unique_ptr<char[]> buf(new char[size]);

        configFile.readBytes(buf.get(), size);
        DynamicJsonBuffer jsonBuffer;
        JsonObject& json = jsonBuffer.parseObject(buf.get());
        json.printTo(Serial);
        if (json.success()) {
          Serial.println("\nparsed json");

        } else {
          Serial.println("failed to load json config");//แสดงข้อความใน Serial Monitor
        }
      }
    }
  } else {
    Serial.println("failed to mount FS");//แสดงข้อความใน Serial Monitor
  }
  //end read


  //*************************   จบการ อ่าน  เขียนค่า WiFi + Password ]ลงใน Node MCU ESP32   **********//









  //**************************        AP AUTO CONNECT   ********************************************//

  //WiFiManager
  //Local intialization. Once its business is done, there is no need to keep it around
  WiFiManager wifiManager;

  //set config save notify callback
  wifiManager.setSaveConfigCallback(saveConfigCallback);


  for (int i = 5; i > -1; i--) {  // นับเวลาถอยหลัง 5 วินาทีก่อนกดปุ่ม AP Config
    digitalWrite(Ledblynk, HIGH);
    delay(500);
    digitalWrite(Ledblynk, LOW);
    delay(500);
    Serial.print (String(i) + " ");//แสดงข้อความใน Serial Monitor
  }


  if (digitalRead(AP_Config) == LOW) {
    digitalWrite(Ledblynk, HIGH);
    Serial.println("Button Pressed");//แสดงข้อความใน Serial Monitor



    // wifiManager.resetSettings();//ให้ล้างค่า SSID และ Password ที่เคยบันทึกไว้
    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); //load the flash-saved configs
    esp_wifi_init(&cfg); //initiate and allocate wifi resources (does not matter if connection fails)
    delay(2000); //wait a bit
    if (esp_wifi_restore() != ESP_OK)

    {
      Serial.println("WiFi is not initialized by esp_wifi_init ");
    } else {
      Serial.println("WiFi Configurations Cleared!");
    }

  }

  wifiManager.setTimeout(60);
  //ใช้ได้ 2 กรณี
  //1. เมื่อกดปุ่มเพื่อ Config ค่า AP แล้ว จะขึ้นชื่อ AP ที่เราตั้งขึ้น
  //   ช่วงนี้ให้เราทำการตั้งค่า SSID+Password หรืออื่นๆทั้งหมด ภายใน 60 วินาที ก่อน AP จะหมดเวลา
  //   ไม่เช่นนั้น เมื่อครบเวลา 60 วินาที MCU จะ Reset เริ่มต้นใหม่ ให้เราตั้งค่าอีกครั้งภายใน 60 วินาที
  //2. ช่วงไฟดับ Modem router + MCU จะดับทั้งคู่ และเมื่อมีไฟมา ทั้งคู่ก็เริ่มทำงานเช่นกัน
  //   โดยปกติ Modem router จะ Boot ช้ากว่า  MCU ทำให้ MCU กลับไปเป็น AP รอให้เราตั้งค่าใหม่
  //   ดังนั้น AP จะรอเวลาให้เราตั้งค่า 60 วินาที ถ้าไม่มีการตั้งค่าใดๆ เมื่อครบ 60 วินาที MCU จะ Reset อีกครั้ง
  //   ถ้า Modem router  Boot และใช้งานได้ภายใน 60 วินาที และหลังจากที่ MCU Resset และเริ่มทำงานใหม่
  //   ก็จะสามารถเชื่อมต่อกับ  Modem router ที่ Boot และใช้งานได้แล้ว  ได้  ระบบจะทำงานปกติ

  if (!wifiManager.autoConnect("PUYIOT ESP32 AP CONFIG")) {
    Serial.println("failed to connect and hit timeout");//แสดงข้อความใน Serial Monitor
    delay(3000);
    //reset and try again, or maybe put it to deep sleep
    ESP.restart();//แก้ เดิม ESP.reset(); ใน Esp8266
    delay(5000);

  }

  Serial.println("Connected.......OK!)");//แสดงข้อความใน Serial Monitor
  //strcpy(blynk_token, custom_blynk_token.getValue());


  //save the custom parameters to FS
  if (shouldSaveConfig) {
    Serial.println("saving config");
    DynamicJsonBuffer jsonBuffer;
    JsonObject& json = jsonBuffer.createObject();

    File configFile = SPIFFS.open("/config.json", "w");

    if (!configFile) {
      Serial.println("failed to open config file for writing");//แสดงข้อความใน Serial Monitor

    }
    json.printTo(Serial);
    json.printTo(configFile);
    configFile.close();
    //end save
  }

  //**************************    จบ    AP AUTO CONNECT   *****************************************//




  Serial.println("local ip"); //แสดงข้อความใน Serial Monitor
  delay(100);
  Serial.println(WiFi.localIP());//แสดงข้อความใน Serial Monitor
  Serial.println("gateway");
  delay(100);
  Serial.println(WiFi.gatewayIP());
  Serial.println("subnetMask");
  delay(100);
  Serial.println(WiFi.subnetMask());
  Serial.println("SSID");
  delay(100);
  Serial.println(WiFi.SSID());
  Serial.println("Password");
  delay(100);
  Serial.println(WiFi.psk());



  //create a task that will be executed in the Task1code() function, with priority 1 and executed on core 0
  xTaskCreatePinnedToCore(
    Task1code,   /* Task function. */
    "Task1",     /* name of task. */
    10000,       /* Stack size of task */
    NULL,        /* parameter of the task */
    1,           /* priority of the task */
    &Task1,      /* Task handle to keep track of created task */
    0);          /* pin task to core 0 */
  delay(500);

  //create a task that will be executed in the Task2code() function, with priority 1 and executed on core 0
  xTaskCreatePinnedToCore(
    Task2code,   /* Task function. */
    "Task2",     /* name of task. */
    10000,       /* Stack size of task */
    NULL,        /* parameter of the task */
    1,           /* priority of the task */
    &Task2,      /* Task handle to keep track of created task */
    0);          /* pin task to core 0 */
  delay(500);


  //create a task that will be executed in the Task3code() function, with priority 1 and executed on core 0
  xTaskCreatePinnedToCore(
    Task3code,   /* Task function. */
    "Task3",     /* name of task. */
    10000,       /* Stack size of task */
    NULL,        /* parameter of the task */
    1,           /* priority of the task */
    &Task3,      /* Task handle to keep track of created task */
    0);          /* pin task to core 0 */
  delay(500);


  //create a task that will be executed in the Task4code() function, with priority 1 and executed on core 0
  xTaskCreatePinnedToCore(
    Task4code,   /* Task function. */
    "Task4",     /* name of task. */
    10000,       /* Stack size of task */
    NULL,        /* parameter of the task */
    1,           /* priority of the task */
    &Task4,      /* Task handle to keep track of created task */
    0);          /* pin task to core 0 */
  delay(500);

  //create a task that will be executed in the Task5code() function, with priority 1 and executed on core 0
  xTaskCreatePinnedToCore(
    Task5code,   /* Task function. */
    "Task5",     /* name of task. */
    10000,       /* Stack size of task */
    NULL,        /* parameter of the task */
    1,           /* priority of the task */
    &Task5,      /* Task handle to keep track of created task */
    0);          /* pin task to core 0 */
  delay(500);

  //create a task that will be executed in the Task6code() function, with priority 1 and executed on core 0
  xTaskCreatePinnedToCore(
    Task6code,   /* Task function. */
    "Task6",     /* name of task. */
    10000,       /* Stack size of task */
    NULL,        /* parameter of the task */
    1,           /* priority of the task */
    &Task6,      /* Task handle to keep track of created task */
    0);          /* pin task to core 0 */
  delay(500);


  //create a task that will be executed in the Task7code() function, with priority 1 and executed on core 1
  xTaskCreatePinnedToCore(
    Task7code,   /* Task function. */
    "Task7",     /* name of task. */
    10000,       /* Stack size of task */
    NULL,        /* parameter of the task */
    1,           /* priority of the task */
    &Task7,      /* Task handle to keep track of created task */
    1);          /* pin task to core 1 */
  delay(500);



  //create a task that will be executed in the Task8code() function, with priority 1 and executed on core 1
  xTaskCreatePinnedToCore(
    Task8code,   /* Task function. */
    "Task8",     /* name of task. */
    10000,       /* Stack size of task */
    NULL,        /* parameter of the task */
    1,           /* priority of the task */
    &Task8,      /* Task handle to keep track of created task */
    1);          /* pin task to core 1 */
  delay(500);



  //create a task that will be executed in the Task9code() function, with priority 1 and executed on core 1
  xTaskCreatePinnedToCore(
    Task9code,   /* Task function. */
    "Task9",     /* name of task. */
    10000,       /* Stack size of task */
    NULL,        /* parameter of the task */
    1,           /* priority of the task */
    &Task9,      /* Task handle to keep track of created task */
    1);          /* pin task to core 1 */
  delay(500);



  //create a task that will be executed in the Task10code() function, with priority 1 and executed on core 1
  xTaskCreatePinnedToCore(
    Task10code,   /* Task function. */
    "Task10",     /* name of task. */
    10000,       /* Stack size of task */
    NULL,        /* parameter of the task */
    1,           /* priority of the task */
    &Task10,      /* Task handle to keep track of created task */
    1);          /* pin task to core 1 */
  delay(500);



  //create a task that will be executed in the Task11code() function, with priority 1 and executed on core 1
  xTaskCreatePinnedToCore(
    Task11code,   /* Task function. */
    "Task11",     /* name of task. */
    10000,       /* Stack size of task */
    NULL,        /* parameter of the task */
    1,           /* priority of the task */
    &Task11,      /* Task handle to keep track of created task */
    1);          /* pin task to core 1 */
  delay(500);



  //create a task that will be executed in the Task12code() function, with priority 1 and executed on core 1
  xTaskCreatePinnedToCore(
    Task12code,   /* Task function. */
    "Task12",     /* name of task. */
    10000,       /* Stack size of task */
    NULL,        /* parameter of the task */
    1,           /* priority of the task */
    &Task12,      /* Task handle to keep track of created task */
    1);          /* pin task to core 1 */
  delay(500);















}


//------------------------------------------------------------------------------------------------------------------------//
//*********************************************   จบ  void setup        **************************************************//
//------------------------------------------------------------------------------------------------------------------------//



//Task1code: blinks an LED every 1000 ms
void Task1code( void * pvParameters ) {
  Serial.print("Task1 running on core ");
  Serial.println(xPortGetCoreID());

  for (;;) {

    delay(1000);
  }
}

//Task2code: blinks an LED every 700 ms
void Task2code( void * pvParameters ) {
  Serial.print("Task2 running on core ");
  Serial.println(xPortGetCoreID());

  for (;;) {

    delay(700);
  }
}



//Task3code: blinks an LED every 2000 ms
void Task3code( void * pvParameters ) {
  Serial.print("Task3 running on core ");
  Serial.println(xPortGetCoreID());

  for (;;) {

    delay(2000);
  }
}


//Task4code: blinks an LED every 2000 ms
void Task4code( void * pvParameters ) {
  Serial.print("Task4 running on core ");
  Serial.println(xPortGetCoreID());

  for (;;) {

    delay(2000);
  }
}


//Task5code: blinks an LED every 2000 ms
void Task5code( void * pvParameters ) {
  Serial.print("Task5 running on core ");
  Serial.println(xPortGetCoreID());

  for (;;) {

    delay(2000);
  }
}



//Task6code: blinks an LED every 2000 ms
void Task6code( void * pvParameters ) {
  Serial.print("Task6 running on core ");
  Serial.println(xPortGetCoreID());

  for (;;) {

    delay(2000);
  }
}


//Task7code: blinks an LED every 2000 ms
void Task7code( void * pvParameters ) {
  Serial.print("Task7 running on core ");
  Serial.println(xPortGetCoreID());

  for (;;) {

    delay(2000);
  }
}


//Task8code: blinks an LED every 2000 ms
void Task8code( void * pvParameters ) {
  Serial.print("Task8 running on core ");
  Serial.println(xPortGetCoreID());

  for (;;) {

    delay(2000);
  }
}



//Task9code: blinks an LED every 2000 ms
void Task9code( void * pvParameters ) {
  Serial.print("Task9 running on core ");
  Serial.println(xPortGetCoreID());

  for (;;) {

    delay(2000);
  }
}



//Task10code: blinks an LED every 2000 ms
void Task10code( void * pvParameters ) {
  Serial.print("Task10 running on core ");
  Serial.println(xPortGetCoreID());

  for (;;) {

    delay(2000);
  }
}


//Task11code: blinks an LED every 2000 ms
void Task11code( void * pvParameters ) {
  Serial.print("Task11 running on core ");
  Serial.println(xPortGetCoreID());

  for (;;) {

    delay(2000);
  }
}



//Task12code: blinks an LED every 2000 ms
void Task12code( void * pvParameters ) {
  Serial.print("Task12 running on core ");
  Serial.println(xPortGetCoreID());

  for (;;) {

    delay(2000);
  }
}









//------------------------------------------------------------------------------------------------------------------------//
//*********************************************       void Loop        ***************************************************//
//------------------------------------------------------------------------------------------------------------------------//


void loop() {




}

//------------------------------------------------------------------------------------------------------------------------//
//*********************************************      จบ void Loop       **************************************************//
//------------------------------------------------------------------------------------------------------------------------//
2 Likes