[Arduino][ESP8266] 有最佳化你的 ESP8266 ?
[Arduino][ESP8266]
有最佳化你的ESP8266 ?
前言
我寫了以上兩篇文章, 其最終的目的都是在提醒大家,在撰寫ESP8266的程式碼時,請以
Event-Driven 的思維以及callback function 來呼叫 WiFi Library。
Event-Driven 的思維以及callback function 來呼叫 WiFi Library。
但這呼籲也僅是建議,而非絕對,畢竟原廠(Espressif)的 WiFi Library提供了 call directly
以及 Call asynchronous 兩種方式讓大家來使用 WiFi Library,也就是說,你還是可以以直接
呼叫的方式來使用WiFi Library。
以及 Call asynchronous 兩種方式讓大家來使用 WiFi Library,也就是說,你還是可以以直接
呼叫的方式來使用WiFi Library。
Question: 若我不用event-driven 或 callback function 來寫ESP8266的程式碼,不行嗎?
而我的答案是 “ 可以的”。
而我的答案是 “ 可以的”。
但同時,我也提出了警告,"若使用call directly”會有你意想不到的後果;所以,這篇就讓我
以實際範例來告訴你這個事實吧 !
以實際範例來告訴你這個事實吧 !
範例:WiFi Scan – Call Directly 方式
底下的程式碼是來自ESP8266內建的 WiFi Scan 範例,而非我挖洞,故意設計的喔!
我確實有修改原廠範例的程式碼,但也僅僅加了 “取得系統時間”以及 “LED顯示”,
為了就是顯示call directly 和 call asynchronous 的差異!
我確實有修改原廠範例的程式碼,但也僅僅加了 “取得系統時間”以及 “LED顯示”,
為了就是顯示call directly 和 call asynchronous 的差異!
首先是 Call directly的方式,相信大部分人都是用這方式在使用 WiFi Scan,因為這段程式碼
是原廠提供的範例啊!
是原廠提供的範例啊!
- /*
- This sketch demonstrates how to scan WiFi networks.
- The API is almost the same as with the WiFi Shield library,
- the most obvious difference being the different file you need to include:
- */
- #include "ESP8266WiFi.h"
- unsigned long time1, time2;
- void setup() {
- Serial.begin(115200);
- pinMode(LED_BUILTIN, OUTPUT);
- digitalWrite(LED_BUILTIN, HIGH);
- // Set WiFi to station mode and disconnect from an AP if it was previously connected
- WiFi.mode(WIFI_STA);
- WiFi.disconnect();
- delay(100);
- }
- void loop() {
- time1 = millis();
- // WiFi.scanNetworks will return the number of networks found
- int n = WiFi.scanNetworks();
- if (n == 0) {
- Serial.println("no networks found");
- } else {
- Serial.println("Scan by call directly");
- Serial.printf(" %d network(s) found\n", n);
- for (int i = 0; i < n; ++i) {
- // Print SSID and RSSI for each network found
- Serial.printf( "%d: %s, Channel: %d (%ddBm)\n", i + 1,
- WiFi.SSID(i).c_str(),
- WiFi.channel(i),
- WiFi.RSSI(i) );
- }
- }
- time2 = millis();
- Serial.print("Time: ");
- Serial.println( time2 - time1 );
- digitalWrite(LED_BUILTIN, LOW);
- delay(100);
- digitalWrite(LED_BUILTIN, HIGH);
- delay(100);
- }
程式碼重點說明:
Line 27 : 以 call directly 方式呼叫WiFi.scanNetworks。
Line 29 ~ 42 : 在loop()函數內列印scan network 結果。
Line 24, 44 ~46 : 列印出 呼叫WiFi.scanNetworks函數前後的時間差,也就是WiFi.scanNetworks這函數的
執行時間。
執行時間。
Line 48 ~51 : LED做 100ms 的閃爍。
下圖是 Call directly serial port 的列印輸出
從 serial port 的輸出,可看到 Scan network 花費了約2196ms,若你理解這程式碼或實際執行
這程式碼,你應該可以知道或觀看到,LED的燈號閃爍則是無法以100ms 的方式”連續” 閃爍。
這程式碼,你應該可以知道或觀看到,LED的燈號閃爍則是無法以100ms 的方式”連續” 閃爍。
因為在loop()迴圈裡,Scan network 大約花了2秒,致使LED 無法以100ms 的方式”連續” 閃
爍。
爍。
這樣一切合理 ?
先不說call directly 方式哪裡有問題,我們先來看看 Call asynchronous方式的程式碼
及執行結果
及執行結果
- /*
- This sketch demonstrates how to scan WiFi networks.
- The API is almost the same as with the WiFi Shield library,
- the most obvious difference being the different file you need to include:
- */
- #include "ESP8266WiFi.h"
- unsigned long time1, time2;
- bool scanDone;
- void scan_OnComplete(int n) {
- scanDone = true;
- if (n == 0) {
- Serial.println("no networks found");
- } else {
- Serial.println("Scan Async");
- Serial.printf(" %d network(s) found\n", n);
- for (int i = 0; i < n; ++i )
- {
- Serial.printf( "%d: %s, Channel: %d (%ddBm)\n", i + 1,
- WiFi.SSID(i).c_str(),
- WiFi.channel(i),
- WiFi.RSSI(i) );
- }
- }
- }
- void setup() {
- Serial.begin(115200);
- pinMode(LED_BUILTIN, OUTPUT);
- digitalWrite(LED_BUILTIN, HIGH);
- // Set WiFi to station mode and disconnect from an AP if it was previously connected
- WiFi.mode(WIFI_STA);
- WiFi.disconnect();
- delay(100);
- scanDone = false;
- WiFi.scanNetworksAsync(&scan_OnComplete);
- }
- void loop() {
- time1 = millis();
- if ( scanDone )
- {
- scanDone = false;
- WiFi.scanNetworksAsync(scan_OnComplete);
- }
- time2 = millis();
- Serial.print("Time: ");
- Serial.println( time2 - time1 );
- digitalWrite(LED_BUILTIN, LOW);
- delay(100);
- digitalWrite(LED_BUILTIN, HIGH);
- delay(100);
- }
程式碼和call directly的差異說明 (二個差異點):
第一個差異點 :
Line 43 :以 call asynchronous 方式呼叫WiFi.scanNetworksAync,並註冊scan_OnComplete() callback
function
第一個差異點 :
Line 43 :以 call asynchronous 方式呼叫WiFi.scanNetworksAync,並註冊scan_OnComplete() callback
function
第二個差異點 :
Line 11 ~ 28 : scan_onComplete(),”列印scan network 結果”,從 loop()移到此處
Line 11 ~ 28 : scan_onComplete(),”列印scan network 結果”,從 loop()移到此處
下圖是 Call asynchronous serial port 的列印輸出
從 serial port 的輸出,可看到 Scan network Asynchronous花費了0ms,看到這輸出結果,
你應該猜測到,LED的燈號閃爍應該是以100ms 的方式”連續” 閃爍吧。
你應該猜測到,LED的燈號閃爍應該是以100ms 的方式”連續” 閃爍吧。
Why ?
為什麼 Call Asynchronous 方式, LED 可以連續閃爍,而 Call directly 卻要被 scan network
中斷,致使LED無法連續閃爍呢 ? 不是同一顆 CPU ?
中斷,致使LED無法連續閃爍呢 ? 不是同一顆 CPU ?
首先,大家要有一個觀念, 晶片和外界溝通有很大部分時間是花費在等待,而且好的
(或說貴的)晶片通常會有獨立控制單元,也就不太需佔用CPU的資源,所以類似scan network
這種request 是可以快速把CPU的執行權交回到User -Task (也就是Loop() )。
(或說貴的)晶片通常會有獨立控制單元,也就不太需佔用CPU的資源,所以類似scan network
這種request 是可以快速把CPU的執行權交回到User -Task (也就是Loop() )。
第二點,ESP8266只是單核,也算不上有RTOS (雖然存在2個以上的Task),所以call directly
的方式,勢必一定要等到執行有結果;而CPU 這時在睡?
不,他是以最高速運算頻率在等待網路掃描結果喔,也就是說CPU一直在做虛功,
而且還不執行你User-Task內的程式碼!
第三,可以trace 一下 WiFi Library 的程式碼,因為Library 程式碼就已經告訴你,
call directly 的ScanNetwork() 會suspend User-Task,CPU執行權會交給WiFi-MAC,
必須要等到Scan Complete,這段時間 (以scan為例,就是2秒) CPU 是不會執行你的程式碼的。
call directly 的ScanNetwork() 會suspend User-Task,CPU執行權會交給WiFi-MAC,
必須要等到Scan Complete,這段時間 (以scan為例,就是2秒) CPU 是不會執行你的程式碼的。
下一篇,就讓我以來告訴你幾種trace “WiFi Library” 的方便方法吧 !
敬請期待!!!
延伸閱讀