我的ESP32實做書籍:https://youyouyou.pixnet.net/blog/post/121105860
博客來網址:https://www.books.com.tw/products/0010901195


ESP32擁有兩個核心,不過平常都只用到一個核心,這樣是不是很浪費,我們如何善用增加效能呢?或者如何使用平行處理呢?本章分為三個部份解說:
一、    多核心概念
二、    顯示執行核心
三、    雙核心執行

一、多核心概念
我們常聽到說我的手機有4核心,電腦有8核心,所謂的多核心(Multi-core)簡單的說就是CPU能同時處理較多的任務且不會互相干擾,另外一種虛擬的多核心稱為多執行緒(Thread)則是CPU的分時多工,非本篇的討論範圍,由於ESP32有著240/160MHz的雙核心CPU,因此本篇將要說明是能將任務指定給核心執行的「xTaskCreatePinnedToCore」函數。

雙核心執行有什麼好處?例如可以增加效能,或者平行處理,舉例而言,我們有一個工作是要偵測現場溫濕度並上傳資料庫存檔,每20秒感測上傳一次,假定這項規定很嚴格,必須準時上傳不能有延遲,在以往程式的撰寫流程下,感測與網路上傳是寫在同一個程序內,也就是讀取溫濕度→上傳資料庫→等候20秒,但是上傳資料庫須看網路是否擁塞,假設上傳時間花了3秒,再加上等候20秒後,等於是23秒過了,長久下來就會延遲越來越多。

也許你會想說我們可以將等候時間改成17秒,這樣上傳時間3秒加起來剛好20秒,不過你也知道網路不是那麼好預估,有時候鄰居下載迷片,速度就慢了,而半夜時沒人速度又變得飛快,這樣我們總是沒辦法找到一個時間來補回。

而多核心在這裡就非常適用,也就是一個核心負責讀取溫濕度資料,另外一個核心則將資料送到資料庫,平行處理兩者互不相干,就可以固定總時間不變,而這就是雙核心CPU時的好處。
image

二、顯示執行核心
一般我們的程式都僅在ESP32的核心1執行(核心編號1),所以根本沒用到第2個核心(核心編號0),為了證明,我們可以透過xPortGetCoreID()函數來顯示現在使用到哪一個核心,例如我們寫一個簡單的HelloWorld,來測試看看。


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

void loop() {
  Serial.println("HelloWorld!");
  Serial.print("使用核心編號:");
  Serial.println(xPortGetCoreID());//xPortGetCoreID()顯示執行核心編號
delay(1000);
}


image

上圖可以知道,在未特別指定執行核心前,程序都在核心1中執行,我們稱為單線程,而未使用到另外一個核心,我們再拿前一章ThingSpeak的範例來演練看看,順便了解執行的時間變化。


#include <WiFi.h>
#include <HTTPClient.h>
#include <SimpleDHT.h>
//請修改以下參數--------------------------------------------
char ssid[] = "SSID";
char password[] = "SSIDpassword";
//請修改為你自己的API Key,並將https改為http
String url = "http://api.thingspeak.com/update?api_key=換成你的APIKey";
int pinDHT11 = 14;//假設DHT11接在腳位GPIO14,麵包板左側序號8
//---------------------------------------------------------
SimpleDHT11 dht11(pinDHT11);//宣告SimpleDHT11物件

void setup()
{
  Serial.begin(115200);
  Serial.print("開始連線到無線網路SSID:");
  Serial.println(ssid);
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    Serial.print(".");
    delay(1000);
  }
  Serial.println("連線完成");
}

void loop()
{
  Serial.print("使用核心編號:");
  Serial.println(xPortGetCoreID());
  //嘗試讀取溫濕度內容
  byte temperature = 0;
  byte humidity = 0;
  int err = SimpleDHTErrSuccess;
  if ((err = dht11.read(&temperature, &humidity, NULL)) != SimpleDHTErrSuccess) {
    Serial.print("溫度計讀取失敗,錯誤碼="); Serial.println(err); delay(1000);
    return;
  }
  //讀取成功,將溫濕度顯示在序列視窗
  Serial.print("溫度計讀取成功: ");
  Serial.print((int)temperature); Serial.print(" *C, ");
  Serial.print((int)humidity); Serial.println(" H");
  //開始傳送到thingspeak
  Serial.println("啟動網頁連線");
  HTTPClient http;
  //將溫度及濕度以http get參數方式補入網址後方
  url = url + "&field1=" + (int)temperature + "&field2=" + (int)humidity;
  //http client取得網頁內容
  http.begin(url);
  int httpCode = http.GET();
  if (httpCode == HTTP_CODE_OK)      {
    //讀取網頁內容到payload
    String payload = http.getString();
    //將內容顯示出來
    Serial.print("網頁內容=");
    Serial.println(payload);
  } else {
    //讀取失敗
    Serial.println("網路傳送失敗");
  }
  http.end();
  delay(20000);//休息20秒
}


為了計算網路傳送的延遲時間,我們可以開啟顯示時間戳記Show timestamp來觀察每次的傳輸時間,我們比較上圖2與3之間的時間差異發現,經過五次的傳送,時間已經慢了約4秒鐘,同時我們觀察執行核心也都是在編號1上執行。

image

三、雙核心執行
了解概念後,我們先來實做ESP32多核心的寫法,步驟是這樣的
1.    宣告任務變數:TaskHandle_t Task1;
2.    設定並執行任務://設定任務
       xTaskCreatePinnedToCore(
         Task1code, //本任務實際對應的Function
         "Task1",      //任務名稱(自行設定)
         10000,        //所需堆疊空間(常用10000)
         NULL,         //輸入值
         0,               //優先序:0代表最低,數字越高越優先
         &Task1,     //對應的任務handle變數
         0);              //指定執行核心編號(0、1或tskNO_AFFINITY:系統指定)
因此我們將原本ThinkSpeak的流程改為多核心架構,核心1執行主流程loop()監控DHT11的溫溼度,將數值存放在公用變數區,並設定上傳旗標=True,而核心0執行Task1,主要工作是將資料上傳,當發現上傳旗標=True後就執行上傳,並在完成上傳後修改上傳旗標=False,等待下次任務啟動。

image

此時我們以上圖方式加入多核心架構後,程式如下


#include <WiFi.h>
#include <HTTPClient.h>
#include <SimpleDHT.h>
//請修改以下參數--------------------------------------------
char ssid[] = "SSID";
char password[] = "SSIDpassword";
//請修改為你自己的API Key,並將https改為http
String url = "http://api.thingspeak.com/update?api_key=換成你的APIKey";
int pinDHT11 = 14;//假設DHT11接在腳位GPIO14,麵包板左側序號8
//---------------------------------------------------------
SimpleDHT11 dht11(pinDHT11);//宣告SimpleDHT11物件

//公用變數區
byte temperature = 0;
byte humidity = 0;
bool SendFlag = false;
//宣告任務Task1
TaskHandle_t Task1;

//任務1副程式Task1_senddata
void Task1_senddata(void * pvParameters ) {
  //無窮迴圈
  for (;;) {
    //偵測上傳旗標是否為true
    if (SendFlag) {
      Serial.print("Task1:啟動網頁連線,at core:");
      Serial.println(xPortGetCoreID());
      HTTPClient http;
      //將溫度及濕度以http get參數方式補入網址後方
      String url1 = url + "&field1=" + (int)temperature + "&field2=" + (int)humidity;
      //http client取得網頁內容
      http.begin(url1);
      int httpCode = http.GET();
      if (httpCode == HTTP_CODE_OK) {
        //讀取網頁內容到payload
        String payload = http.getString();
        //將內容顯示出來
        Serial.print("網頁內容=");
        Serial.println(payload);
      } else {
        //傳送失敗
        Serial.println("網路傳送失敗");
      }
      //修改完畢,修改傳送旗標=false
      SendFlag = false;
      http.end();
    } else {
      //Task1休息,delay(1)不可省略
      delay(1);
    }
  }
}

void setup()
{
  Serial.begin(115200);
  Serial.print("開始連線到無線網路SSID:");
  Serial.println(ssid);
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    Serial.print(".");
    delay(1000);
  }
  Serial.println("連線完成");
  //在核心0啟動任務1
  xTaskCreatePinnedToCore(
    Task1_senddata, /*任務實際對應的Function*/
    "Task1",        /*任務名稱*/
    10000,          /*堆疊空間*/
    NULL,           /*無輸入值*/
    0,                 /*優先序0*/
    &Task1,       /*對應的任務變數位址*/
    0);                /*指定在核心0執行 */
}

void loop()
{
  Serial.print("loop主流程:溫濕度讀取,at core:");
  Serial.println(xPortGetCoreID());
  //嘗試讀取溫濕度內容
  int err = SimpleDHTErrSuccess;
  if ((err = dht11.read(&temperature, &humidity, NULL)) != SimpleDHTErrSuccess) {
    Serial.print("溫度計讀取失敗,錯誤碼="); Serial.println(err); delay(1000);
    return;
  }
  //讀取成功,將溫濕度顯示在序列視窗
  Serial.print("溫度計讀取成功: ");
  Serial.print((int)temperature); Serial.print(" *C, ");
  Serial.print((int)humidity); Serial.println(" H");
  //修改上傳旗標=true
  SendFlag = true;
  delay(20000);//休息20秒
}


image
經由將程式修改為多核心執行後,可以發現,儘管網路會有延遲,但是每次開始傳送的時間都不會相差太多,多次執行後,仍能保持在0.1秒內的誤差。

image

讀者了解多核心的優點後,可以思考要如何應用在不同的議題上喔。

arrow
arrow
    創作者介紹
    創作者 夜市 小霸王 的頭像
    夜市 小霸王

    夜市小霸王

    夜市 小霸王 發表在 痞客邦 留言(0) 人氣()