【MakerPRO】ESP32專欄 善用ESP32雙核心,平行處理提昇效能

ESP32是一款具有WiFi及低功率藍牙BLE的晶片,相容於Arduino架構,可使用Arduino IDE來開發,不僅程式與Arduino UNO幾乎相同,感測器也完全相容,因此非常適合用來取代現有國、高中UNO的教材。

作者 尤濬哲(夜市小霸王),本文經MakerPRO同意轉載,原文連結
ESP32腳位定義圖

ESP32雖然擁有兩個核心,不過以平常Arduino的程式寫法都只會用到一個核心,這樣是不是很浪費?我們如何善用增加效能呢?或者如何使用平行處理呢?本篇文章將介紹ESP32的雙核心架構,並分為三個部份解說。

1. 多核心概念

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

ESP32功能方塊圖中可看出它為雙核心架構(Source)

 

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

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

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

2. 顯示執行核心

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

[pastacode lang=”python” manual=”void%20setup()%20%7B%0ASerial.begin(115200)%3B%0A%7D%0A%20%0Avoid%20loop()%20%7B%0ASerial.println(%22HelloWorld!%22)%3B%0ASerial.print(%22%E4%BD%BF%E7%94%A8%E6%A0%B8%E5%BF%83%E7%B7%A8%E8%99%9F%EF%BC%9A%22)%3B%0ASerial.println(xPortGetCoreID())%3B%2F%2FxPortGetCoreID()%E9%A1%AF%E7%A4%BA%E5%9F%B7%E8%A1%8C%E6%A0%B8%E5%BF%83%E7%B7%A8%E8%99%9F%0Adelay(1000)%3B%0A%7D” message=”” highlight=”” provider=”manual”/]

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

[pastacode lang=”python” manual=”%23include%0A%23include%0A%23include%0A%2F%2F%E8%AB%8B%E4%BF%AE%E6%94%B9%E4%BB%A5%E4%B8%8B%E5%8F%83%E6%95%B8——————————————–%0Achar%20ssid%5B%5D%20%3D%20%22SSID%22%3B%0Achar%20password%5B%5D%20%3D%20%22SSIDpassword%22%3B%0A%2F%2F%E8%AB%8B%E4%BF%AE%E6%94%B9%E7%82%BA%E4%BD%A0%E8%87%AA%E5%B7%B1%E7%9A%84API%20Key%EF%BC%8C%E4%B8%A6%E5%B0%87https%E6%94%B9%E7%82%BAhttp%0AString%20url%20%3D%20%22http%3A%2F%2Fapi.thingspeak.com%2Fupdate%3Fapi_key%3D%E6%8F%9B%E6%88%90%E4%BD%A0%E7%9A%84APIKey%22%3B%0Aint%20pinDHT11%20%3D%2014%3B%2F%2F%E5%81%87%E8%A8%ADDHT11%E6%8E%A5%E5%9C%A8%E8%85%B3%E4%BD%8DGPIO14%EF%BC%8C%E9%BA%B5%E5%8C%85%E6%9D%BF%E5%B7%A6%E5%81%B4%E5%BA%8F%E8%99%9F8%0A%2F%2F———————————————————%0ASimpleDHT11%20dht11(pinDHT11)%3B%2F%2F%E5%AE%A3%E5%91%8ASimpleDHT11%E7%89%A9%E4%BB%B6%0A%20%0Avoid%20setup()%0A%7B%0ASerial.begin(115200)%3B%0ASerial.print(%22%E9%96%8B%E5%A7%8B%E9%80%A3%E7%B7%9A%E5%88%B0%E7%84%A1%E7%B7%9A%E7%B6%B2%E8%B7%AFSSID%3A%22)%3B%0ASerial.println(ssid)%3B%0AWiFi.mode(WIFI_STA)%3B%0AWiFi.begin(ssid%2C%20password)%3B%0Awhile%20(WiFi.status()%20!%3D%20WL_CONNECTED)%20%7B%0ASerial.print(%22.%22)%3B%0Adelay(1000)%3B%0A%7D%0ASerial.println(%22%E9%80%A3%E7%B7%9A%E5%AE%8C%E6%88%90%22)%3B%0A%7D%0A%20%0Avoid%20loop()%0A%7B%0ASerial.print(%22%E4%BD%BF%E7%94%A8%E6%A0%B8%E5%BF%83%E7%B7%A8%E8%99%9F%EF%BC%9A%22)%3B%0ASerial.println(xPortGetCoreID())%3B%0A%2F%2F%E5%98%97%E8%A9%A6%E8%AE%80%E5%8F%96%E6%BA%AB%E6%BF%95%E5%BA%A6%E5%85%A7%E5%AE%B9%0Abyte%20temperature%20%3D%200%3B%0Abyte%20humidity%20%3D%200%3B%0Aint%20err%20%3D%20SimpleDHTErrSuccess%3B%0Aif%20((err%20%3D%20dht11.read(%26temperature%2C%20%26humidity%2C%20NULL))%20!%3D%20SimpleDHTErrSuccess)%20%7B%0ASerial.print(%22%E6%BA%AB%E5%BA%A6%E8%A8%88%E8%AE%80%E5%8F%96%E5%A4%B1%E6%95%97%EF%BC%8C%E9%8C%AF%E8%AA%A4%E7%A2%BC%3D%22)%3B%20Serial.println(err)%3B%20delay(1000)%3B%0Areturn%3B%0A%7D%0A%2F%2F%E8%AE%80%E5%8F%96%E6%88%90%E5%8A%9F%EF%BC%8C%E5%B0%87%E6%BA%AB%E6%BF%95%E5%BA%A6%E9%A1%AF%E7%A4%BA%E5%9C%A8%E5%BA%8F%E5%88%97%E8%A6%96%E7%AA%97%0ASerial.print(%22%E6%BA%AB%E5%BA%A6%E8%A8%88%E8%AE%80%E5%8F%96%E6%88%90%E5%8A%9F%3A%20%22)%3B%0ASerial.print((int)temperature)%3B%20Serial.print(%22%20*C%2C%20%22)%3B%0ASerial.print((int)humidity)%3B%20Serial.println(%22%20H%22)%3B%0A%2F%2F%E9%96%8B%E5%A7%8B%E5%82%B3%E9%80%81%E5%88%B0thingspeak%0ASerial.println(%22%E5%95%9F%E5%8B%95%E7%B6%B2%E9%A0%81%E9%80%A3%E7%B7%9A%22)%3B%0AHTTPClient%20http%3B%0A%2F%2F%E5%B0%87%E6%BA%AB%E5%BA%A6%E5%8F%8A%E6%BF%95%E5%BA%A6%E4%BB%A5http%20get%E5%8F%83%E6%95%B8%E6%96%B9%E5%BC%8F%E8%A3%9C%E5%85%A5%E7%B6%B2%E5%9D%80%E5%BE%8C%E6%96%B9%0AString%20url1%20%3D%20url%20%2B%20%22%26field1%3D%22%20%2B%20(int)temperature%20%2B%20%22%26field2%3D%22%20%2B%20(int)humidity%3B%0A%2F%2Fhttp%20client%E5%8F%96%E5%BE%97%E7%B6%B2%E9%A0%81%E5%85%A7%E5%AE%B9%0Ahttp.begin(url1)%3B%0Aint%20httpCode%20%3D%20http.GET()%3B%0Aif%20(httpCode%20%3D%3D%20HTTP_CODE_OK)%20%7B%0A%2F%2F%E8%AE%80%E5%8F%96%E7%B6%B2%E9%A0%81%E5%85%A7%E5%AE%B9%E5%88%B0payload%0AString%20payload%20%3D%20http.getString()%3B%0A%2F%2F%E5%B0%87%E5%85%A7%E5%AE%B9%E9%A1%AF%E7%A4%BA%E5%87%BA%E4%BE%86%0ASerial.print(%22%E7%B6%B2%E9%A0%81%E5%85%A7%E5%AE%B9%3D%22)%3B%0ASerial.println(payload)%3B%0A%7D%20else%20%7B%0A%2F%2F%E8%AE%80%E5%8F%96%E5%A4%B1%E6%95%97%0ASerial.println(%22%E7%B6%B2%E8%B7%AF%E5%82%B3%E9%80%81%E5%A4%B1%E6%95%97%22)%3B%0A%7D%0Ahttp.end()%3B%0Adelay(20000)%3B%2F%2F%E4%BC%91%E6%81%AF20%E7%A7%92%0A%7D” message=”” highlight=”” provider=”manual”/]

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

3. 雙核心執行

了解概念後,我們先來實做ESP32多核心的寫法,步驟是這樣的。

1. 宣告任務變數:TaskHandle_t Task1;//宣告Task1任務
2. 設定並執行任務://設定任務
xTaskCreatePinnedToCore(
Task1code, //本任務實際對應的Function
“Task1”, //任務名稱(自行設定)
10000, //所需堆疊空間(常用10000)
NULL, //輸入值
0, //優先序:0代表最優先執行,1次之,以此類推
&Task1, //對應的任務handle變數
0) //指定執行核心編號(0、1或tskNO_AFFINITY:系統指定)

為了測試,我們將原本ThinkSpeak的流程改為多核心架構,核心1執行主流程loop()監控DHT11的溫溼度,將數值存放在公用變數區,並設定上傳旗標=True,核心0執行Task1,主要工作是將資料上傳,當發現上傳旗標=True後就執行上傳,並在完成上傳後修改上傳旗標=False,等待下次任務啟動。

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

[pastacode lang=”python” manual=”%23include%0A%23include%0A%23include%0A%2F%2F%E8%AB%8B%E4%BF%AE%E6%94%B9%E4%BB%A5%E4%B8%8B%E5%8F%83%E6%95%B8——————————————–%0Achar%20ssid%5B%5D%20%3D%20%22SSID%22%3B%0Achar%20password%5B%5D%20%3D%20%22SSIDpassword%22%3B%0A%2F%2F%E8%AB%8B%E4%BF%AE%E6%94%B9%E7%82%BA%E4%BD%A0%E8%87%AA%E5%B7%B1%E7%9A%84API%20Key%EF%BC%8C%E4%B8%A6%E5%B0%87https%E6%94%B9%E7%82%BAhttp%0AString%20url%20%3D%20%22http%3A%2F%2Fapi.thingspeak.com%2Fupdate%3Fapi_key%3D%E6%8F%9B%E6%88%90%E4%BD%A0%E7%9A%84APIKey%22%3B%0Aint%20pinDHT11%20%3D%2014%3B%2F%2F%E5%81%87%E8%A8%ADDHT11%E6%8E%A5%E5%9C%A8%E8%85%B3%E4%BD%8DGPIO14%EF%BC%8C%E9%BA%B5%E5%8C%85%E6%9D%BF%E5%B7%A6%E5%81%B4%E5%BA%8F%E8%99%9F8%0A%2F%2F———————————————————%0ASimpleDHT11%20dht11(pinDHT11)%3B%2F%2F%E5%AE%A3%E5%91%8ASimpleDHT11%E7%89%A9%E4%BB%B6%0A%20%0A%2F%2F%E5%85%AC%E7%94%A8%E8%AE%8A%E6%95%B8%E5%8D%80%0Abyte%20temperature%20%3D%200%3B%0Abyte%20humidity%20%3D%200%3B%0Abool%20SendFlag%20%3D%20false%3B%0A%2F%2F%E5%AE%A3%E5%91%8A%E4%BB%BB%E5%8B%99Task1%0ATaskHandle_t%20Task1%3B%0A%20%0A%2F%2F%E4%BB%BB%E5%8B%991%E5%89%AF%E7%A8%8B%E5%BC%8FTask1_senddata%0Avoid%20Task1_senddata(void%20*%20pvParameters%20)%20%7B%0A%2F%2F%E7%84%A1%E7%AA%AE%E8%BF%B4%E5%9C%88%0Afor%20(%3B%3B)%20%7B%0A%2F%2F%E5%81%B5%E6%B8%AC%E4%B8%8A%E5%82%B3%E6%97%97%E6%A8%99%E6%98%AF%E5%90%A6%E7%82%BAtrue%0Aif%20(SendFlag)%20%7B%0ASerial.print(%22Task1%EF%BC%9A%E5%95%9F%E5%8B%95%E7%B6%B2%E9%A0%81%E9%80%A3%E7%B7%9A%EF%BC%8Cat%20core%3A%22)%3B%0ASerial.println(xPortGetCoreID())%3B%0AHTTPClient%20http%3B%0A%2F%2F%E5%B0%87%E6%BA%AB%E5%BA%A6%E5%8F%8A%E6%BF%95%E5%BA%A6%E4%BB%A5http%20get%E5%8F%83%E6%95%B8%E6%96%B9%E5%BC%8F%E8%A3%9C%E5%85%A5%E7%B6%B2%E5%9D%80%E5%BE%8C%E6%96%B9%0AString%20url1%20%3D%20url%20%2B%20%22%26field1%3D%22%20%2B%20(int)temperature%20%2B%20%22%26field2%3D%22%20%2B%20(int)humidity%3B%0A%2F%2Fhttp%20client%E5%8F%96%E5%BE%97%E7%B6%B2%E9%A0%81%E5%85%A7%E5%AE%B9%0Ahttp.begin(url1)%3B%0Aint%20httpCode%20%3D%20http.GET()%3B%0Aif%20(httpCode%20%3D%3D%20HTTP_CODE_OK)%20%7B%0A%2F%2F%E8%AE%80%E5%8F%96%E7%B6%B2%E9%A0%81%E5%85%A7%E5%AE%B9%E5%88%B0payload%0AString%20payload%20%3D%20http.getString()%3B%0A%2F%2F%E5%B0%87%E5%85%A7%E5%AE%B9%E9%A1%AF%E7%A4%BA%E5%87%BA%E4%BE%86%0ASerial.print(%22%E7%B6%B2%E9%A0%81%E5%85%A7%E5%AE%B9%3D%22)%3B%0ASerial.println(payload)%3B%0A%7D%20else%20%7B%0A%2F%2F%E5%82%B3%E9%80%81%E5%A4%B1%E6%95%97%0ASerial.println(%22%E7%B6%B2%E8%B7%AF%E5%82%B3%E9%80%81%E5%A4%B1%E6%95%97%22)%3B%0A%7D%0A%2F%2F%E4%BF%AE%E6%94%B9%E5%AE%8C%E7%95%A2%EF%BC%8C%E4%BF%AE%E6%94%B9%E5%82%B3%E9%80%81%E6%97%97%E6%A8%99%3Dfalse%0ASendFlag%20%3D%20false%3B%0Ahttp.end()%3B%0A%7D%20else%20%7B%0A%2F%2FTask1%E4%BC%91%E6%81%AF%EF%BC%8Cdelay(1)%E4%B8%8D%E5%8F%AF%E7%9C%81%E7%95%A5%0Adelay(1)%3B%0A%7D%0A%7D%0A%7D%0A%20%0Avoid%20setup()%0A%7B%0ASerial.begin(115200)%3B%0ASerial.print(%22%E9%96%8B%E5%A7%8B%E9%80%A3%E7%B7%9A%E5%88%B0%E7%84%A1%E7%B7%9A%E7%B6%B2%E8%B7%AFSSID%3A%22)%3B%0ASerial.println(ssid)%3B%0AWiFi.mode(WIFI_STA)%3B%0AWiFi.begin(ssid%2C%20password)%3B%0Awhile%20(WiFi.status()%20!%3D%20WL_CONNECTED)%20%7B%0ASerial.print(%22.%22)%3B%0Adelay(1000)%3B%0A%7D%0ASerial.println(%22%E9%80%A3%E7%B7%9A%E5%AE%8C%E6%88%90%22)%3B%0A%2F%2F%E5%9C%A8%E6%A0%B8%E5%BF%830%E5%95%9F%E5%8B%95%E4%BB%BB%E5%8B%991%0AxTaskCreatePinnedToCore(%0ATask1_senddata%2C%20%2F*%E4%BB%BB%E5%8B%99%E5%AF%A6%E9%9A%9B%E5%B0%8D%E6%87%89%E7%9A%84Function*%2F%0A%22Task1%22%2C%20%2F*%E4%BB%BB%E5%8B%99%E5%90%8D%E7%A8%B1*%2F%0A10000%2C%20%2F*%E5%A0%86%E7%96%8A%E7%A9%BA%E9%96%93*%2F%0ANULL%2C%20%2F*%E7%84%A1%E8%BC%B8%E5%85%A5%E5%80%BC*%2F%0A0%2C%20%2F*%E5%84%AA%E5%85%88%E5%BA%8F0*%2F%0A%26Task1%2C%20%2F*%E5%B0%8D%E6%87%89%E7%9A%84%E4%BB%BB%E5%8B%99%E8%AE%8A%E6%95%B8%E4%BD%8D%E5%9D%80*%2F%0A0)%3B%20%2F*%E6%8C%87%E5%AE%9A%E5%9C%A8%E6%A0%B8%E5%BF%830%E5%9F%B7%E8%A1%8C%20*%2F%0A%7D%0A%20%0Avoid%20loop()%0A%7B%0ASerial.print(%22loop%E4%B8%BB%E6%B5%81%E7%A8%8B%EF%BC%9A%E6%BA%AB%E6%BF%95%E5%BA%A6%E8%AE%80%E5%8F%96%EF%BC%8Cat%20core%3A%22)%3B%0ASerial.println(xPortGetCoreID())%3B%0A%2F%2F%E5%98%97%E8%A9%A6%E8%AE%80%E5%8F%96%E6%BA%AB%E6%BF%95%E5%BA%A6%E5%85%A7%E5%AE%B9%0Aint%20err%20%3D%20SimpleDHTErrSuccess%3B%0Aif%20((err%20%3D%20dht11.read(%26temperature%2C%20%26humidity%2C%20NULL))%20!%3D%20SimpleDHTErrSuccess)%20%7B%0ASerial.print(%22%E6%BA%AB%E5%BA%A6%E8%A8%88%E8%AE%80%E5%8F%96%E5%A4%B1%E6%95%97%EF%BC%8C%E9%8C%AF%E8%AA%A4%E7%A2%BC%3D%22)%3B%20Serial.println(err)%3B%20delay(1000)%3B%0Areturn%3B%0A%7D%0A%2F%2F%E8%AE%80%E5%8F%96%E6%88%90%E5%8A%9F%EF%BC%8C%E5%B0%87%E6%BA%AB%E6%BF%95%E5%BA%A6%E9%A1%AF%E7%A4%BA%E5%9C%A8%E5%BA%8F%E5%88%97%E8%A6%96%E7%AA%97%0ASerial.print(%22%E6%BA%AB%E5%BA%A6%E8%A8%88%E8%AE%80%E5%8F%96%E6%88%90%E5%8A%9F%3A%20%22)%3B%0ASerial.print((int)temperature)%3B%20Serial.print(%22%20*C%2C%20%22)%3B%0ASerial.print((int)humidity)%3B%20Serial.println(%22%20H%22)%3B%0A%2F%2F%E4%BF%AE%E6%94%B9%E4%B8%8A%E5%82%B3%E6%97%97%E6%A8%99%3Dtrue%0ASendFlag%20%3D%20true%3B%0Adelay(20000)%3B%2F%2F%E4%BC%91%E6%81%AF20%E7%A7%92%0A%7D” message=”” highlight=”” provider=”manual”/]

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

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

作者為本刊共筆作者,其專欄文章同步發表於作者部落格

*本文經MakerPRO同意轉載,原文連結,特此感謝

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *