山姆的編程實作分享。。。

Sam Blog, Sam Sharing, Sam Studio

2020年3月31日

2020年3月25日

[DIY] 高麗菜蔬食水餃


高麗菜蔬食水餃 (50顆分量)

食材
高麗菜  400g     豆干丁 150g     蔥 3根    蛋 3顆    冬粉  1把   
乾香菇  20g       鹽  4g  (高麗菜 1%)

餡料調味料
素豪油(醬油膏)  45g    糖  8g   白胡椒粉 2g   香油 20g    鹽  3g

完成!




















餡料


2020年3月23日

[Arduino][ESP8266] 有最佳化你的 ESP8266 ?


[Arduino][ESP8266]
有最佳化你的ESP8266 ?
前言
我寫了以上兩篇文章, 其最終的目的都是在提醒大家,在撰寫ESP8266的程式碼時,請以
Event-Driven 的思維以及callback function 來呼叫 WiFi Library。
但這呼籲也僅是建議,而非絕對,畢竟原廠(Espressif)的 WiFi Library提供了 call directly
以及 Call asynchronous 兩種方式讓大家來使用 WiFi Library,也就是說,你還是可以以直接
呼叫的方式來使用WiFi Library。
但是,就如同我在ESP8266 Event-Driven & Callback Method 這篇文章的文末,自問自答的
那個題目,
Question: 若我不用event-driven 或 callback function 來寫ESP8266的程式碼,不行嗎? 
而我的答案是 “ 可以的”
但同時,我也提出了警告,"若使用call directly”會有你意想不到的後果;所以,這篇就讓我
以實際範例來告訴你這個事實吧 !


範例:WiFi Scan – Call Directly 方式
底下的程式碼是來自ESP8266內建的 WiFi Scan 範例,而非我挖洞,故意設計的喔!
我確實有修改原廠範例的程式碼,但也僅僅加了 “取得系統時間”以及 “LED顯示”,
為了就是顯示call directly 和 call asynchronous 的差異!
首先是 Call directly的方式,相信大部分人都是用這方式在使用 WiFi Scan,因為這段程式碼
是原廠提供的範例啊!

  1. /* 
  2.     This sketch demonstrates how to scan WiFi networks. 
  3.     The API is almost the same as with the WiFi Shield library, 
  4.     the most obvious difference being the different file you need to include: 
  5. */  
  6. #include "ESP8266WiFi.h"  
  7.   
  8. unsigned long time1, time2;  
  9.   
  10. void setup() {  
  11.   
  12.     Serial.begin(115200);  
  13.     pinMode(LED_BUILTIN, OUTPUT);  
  14.     digitalWrite(LED_BUILTIN, HIGH);  
  15.   
  16.     // Set WiFi to station mode and disconnect from an AP if it was previously connected  
  17.     WiFi.mode(WIFI_STA);  
  18.     WiFi.disconnect();  
  19.     delay(100);  
  20. }  
  21.   
  22. void loop() {  
  23.   
  24.     time1 = millis();  
  25.   
  26.     // WiFi.scanNetworks will return the number of networks found  
  27.     int n = WiFi.scanNetworks();  
  28.   
  29.     if (n == 0) {  
  30.         Serial.println("no networks found");  
  31.     } else {  
  32.         Serial.println("Scan by call directly");  
  33.         Serial.printf(" %d network(s) found\n", n);  
  34.   
  35.         for (int i = 0; i < n; ++i) {  
  36.             // Print SSID and RSSI for each network found  
  37.             Serial.printf( "%d: %s, Channel: %d (%ddBm)\n", i + 1,  
  38.                                                             WiFi.SSID(i).c_str(),  
  39.                                                             WiFi.channel(i),  
  40.                                                             WiFi.RSSI(i) );  
  41.         }  
  42.     }  
  43.   
  44.     time2 = millis();  
  45.     Serial.print("Time: ");  
  46.     Serial.println( time2 - time1 );  
  47.   
  48.     digitalWrite(LED_BUILTIN, LOW);  
  49.     delay(100);  
  50.     digitalWrite(LED_BUILTIN, HIGH);  
  51.     delay(100);  
  52.  }  
程式碼重點說明:
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 的方式”連續” 閃爍。
因為在loop()迴圈裡,Scan network 大約花了2秒,致使LED 無法以100ms 的方式”連續” 閃
爍。
這樣一切合理 ? 
先不說call directly 方式哪裡有問題,我們先來看看 Call asynchronous方式的程式碼
及執行結果


  1. /* 
  2.     This sketch demonstrates how to scan WiFi networks. 
  3.     The API is almost the same as with the WiFi Shield library, 
  4.     the most obvious difference being the different file you need to include: 
  5. */  
  6. #include "ESP8266WiFi.h"  
  7.   
  8. unsigned long time1, time2;  
  9. bool scanDone;  
  10.   
  11. void scan_OnComplete(int n) {  
  12.   
  13.     scanDone = true;  
  14.   
  15.     if (n == 0) {  
  16.         Serial.println("no networks found");  
  17.     } else {  
  18.         Serial.println("Scan Async");  
  19.         Serial.printf(" %d network(s) found\n", n);  
  20.         for (int i = 0; i < n; ++i )  
  21.         {  
  22.             Serial.printf( "%d: %s, Channel: %d (%ddBm)\n", i + 1,  
  23.                                                       WiFi.SSID(i).c_str(),  
  24.                                                       WiFi.channel(i),  
  25.                                                       WiFi.RSSI(i) );  
  26.         }  
  27.     }  
  28. }  
  29.   
  30.   
  31. void setup() {  
  32.   
  33.     Serial.begin(115200);  
  34.     pinMode(LED_BUILTIN, OUTPUT);  
  35.     digitalWrite(LED_BUILTIN, HIGH);  
  36.   
  37.     // Set WiFi to station mode and disconnect from an AP if it was previously connected  
  38.     WiFi.mode(WIFI_STA);  
  39.     WiFi.disconnect();  
  40.     delay(100);  
  41.   
  42.     scanDone = false;  
  43.     WiFi.scanNetworksAsync(&scan_OnComplete);  
  44. }  
  45.   
  46. void loop() {  
  47.   
  48.     time1 = millis();  
  49.   
  50.      if ( scanDone )  
  51.      {  
  52.          scanDone = false;  
  53.          WiFi.scanNetworksAsync(scan_OnComplete);  
  54.      }  
  55.   
  56.      time2 = millis();  
  57.      Serial.print("Time: ");  
  58.      Serial.println( time2 - time1 );  
  59.   
  60.      digitalWrite(LED_BUILTIN, LOW);  
  61.      delay(100);  
  62.      digitalWrite(LED_BUILTIN, HIGH);  
  63.      delay(100);  
  64. }  
程式碼和call directly的差異說明 (二個差異點):
第一個差異點 :
Line 43 :以 call asynchronous 方式呼叫WiFi.scanNetworksAync,並註冊scan_OnComplete() callback
function
第二個差異點 :
Line 11 ~ 28 : scan_onComplete(),”列印scan network 結果”,從 loop()移到此處

下圖是 Call asynchronous serial port 的列印輸出
從 serial port 的輸出,可看到 Scan network Asynchronous花費了0ms,看到這輸出結果,
你應該猜測到,LED的燈號閃爍應該是以100ms 的方式”連續” 閃爍吧。


Why ?
為什麼 Call Asynchronous 方式, LED 可以連續閃爍,而 Call directly 卻要被 scan network
中斷,致使LED無法連續閃爍呢 ? 不是同一顆 CPU ?
首先,大家要有一個觀念, 晶片和外界溝通有很大部分時間是花費在等待,而且好的
(或說貴的)晶片通常會有獨立控制單元,也就不太需佔用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 是不會執行你的程式碼的。


下一篇,就讓我以來告訴你幾種trace “WiFi Library” 的方便方法吧 !
敬請期待!!!


延伸閱讀

2020年3月22日

[Arduino][ESP8266] ESP8266 Event-Driven & Callback Method


[Arduino][ESP8266]
ESP8266 Event-Driven & Callback Method
前言
在前篇” ESP8266 開發指南 – 軟體架構篇” 的文章末段,我針對ESP8266提出了
3點結論及4點撰寫程式碼( Coding ) 的建議,其目的就是告訴大家請用
“ callback function” 以及 “ event-driven”來撰寫ESP8266的程式碼。
所以,這篇文章就讓我來說明 “ Event-Driven Method”。

ESP8266 的Event-Driven & Callback Method
Event-driven 有各種作法,常見的有:
  • Flag 機制 : 就是透過設定Flag 來傳達 Event,這種做法一般多用在super-loop 的程式架構。
  • Event -Task : 這做法是架構在RTOS ,也就是透過Event的傳遞,來做到工作分派及Task的切換。
  • Event-Callback :  這做法是透過註冊 Callback function 來傳遞 Event,但這做法必須結合 (借助) 第一或第二做法。

ESP8266 的核心雖然可以處理3個Task,但WiFi-MAC, Timer Task以及User_Task
至少使用掉2個,所以,使用者能使用event-driven的方法就剩第一及第三的做法。
以下是我建議使用event-driven的流程:
Step1: 註冊 Callback function。
Step2: 透過Callback function 傳遞來的Event,然後設定Flag,或直接在Callback 處理(註1)。
Step3: 在User_Task (也就loop()迴圈) 判斷Flag狀態,適時機處理Event。
註1: 程式碼短小為佳,避免影響Task scheduling。

整個運作流程就如下圖所示:
範例:WiFi Event
瞭解了 ESP8266 event-driven 的做法後,這章節就讓我以實際的程式碼來做說明。
底下的程式碼是來自ESP8266內建的 WiFiEvent 範例,但是為了好說明,我修改了
部分的程式碼。
  1. /* 
  2.     This sketch shows how to use WiFi event handlers. 
  3.  
  4.     In this example, ESP8266 works in AP mode. 
  5.     Three event handlers are demonstrated: 
  6.     - station connects to the ESP8266 AP 
  7.     - station disconnects from the ESP8266 AP 
  8.     - ESP8266 AP receives a probe request from a station 
  9.  
  10.     Written by Markus Sattler, 2015-12-29. 
  11.     Updated for new event handlers by Ivan Grokhotkov, 2017-02-02. 
  12.     This example is released into public domain, 
  13.     or, at your option, CC0 licensed. 
  14. */  
  15.   
  16. #include   
  17. #include   
  18.   
  19. const char* ssid     = "esp8266";  
  20. const char* password = "12345678";  
  21.   
  22. WiFiEventHandler stationConnectedHandler;  
  23. WiFiEventHandler stationDisconnectedHandler;  
  24.   
  25. bool blinkFlag;  
  26.   
  27. void setup() {  
  28.   
  29.     Serial.begin(115200);  
  30.     pinMode(LED_BUILTIN, OUTPUT);  
  31.     digitalWrite(LED_BUILTIN, HIGH);  
  32.     blinkFlag = false;  
  33.   
  34.     // Don't save WiFi configuration in flash - optional  
  35.     WiFi.persistent(false);  
  36.   
  37.     // Set up an access point  
  38.     WiFi.mode(WIFI_AP);  
  39.     WiFi.softAP(ssid, password);  
  40.   
  41.     // Register event handlers.  
  42.     // Callback functions will be called as long as these handler objects exist.  
  43.     // Call "onStationConnected" each time a station connects  
  44.     stationConnectedHandler = WiFi.onSoftAPModeStationConnected(&onStationConnected);  
  45.   
  46.     // Call "onStationDisconnected" each time a station disconnects  
  47.     stationDisconnectedHandler = WiFi.onSoftAPModeStationDisconnected(&onStationDisconnected);  
  48. }  
  49.   
  50. void onStationConnected(const WiFiEventSoftAPModeStationConnected& evt) {  
  51.     Serial.print("Station connected: ");  
  52.     Serial.println(macToString(evt.mac));  
  53.     // let LED blink  
  54.     blinkFlag = true;  
  55. }  
  56.   
  57. void onStationDisconnected(const WiFiEventSoftAPModeStationDisconnected& evt) {  
  58.     Serial.print("Station disconnected: ");  
  59.     Serial.println(macToString(evt.mac));  
  60.     blinkFlag = true;  
  61. }  
  62.   
  63. void loop() {  
  64.     if (blinkFlag) {  
  65.         // let LED blink twice  
  66.         blinkFlag = false;  
  67.         digitalWrite(LED_BUILTIN, LOW);  
  68.         delay(200);  
  69.         digitalWrite(LED_BUILTIN, HIGH);  
  70.         delay(200);  
  71.         digitalWrite(LED_BUILTIN, LOW);  
  72.         delay(200);  
  73.         digitalWrite(LED_BUILTIN, HIGH);  
  74.     }  
  75. }  
  76.   
  77. String macToString(const unsigned char* mac) {  
  78.     char buf[20];  
  79.     snprintf(buf, sizeof(buf), "%02x:%02x:%02x:%02x:%02x:%02x",  
  80.              mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);  
  81.     return String(buf);  
  82. }  
程式碼說明:
Line 16 : 為了使用ESP8266  WiFi Classes ,必須 include WiFi Library – ESP8266WiFi.h
Line 19, 20 : 在這範例裡,ESP8266會被設定成 AP mode (Access-Point),
這兩行是設定這AP 的名稱 (ssid) 及他的連接密碼 (password)
Line 22 : 宣告一個 WiFi class 的 Event Handler 名為”stationConnectdHandler”
Line 44 : 則是註冊 stationConnectdHandler 為onSoftAPModeStationConnected() 
函數的event handler,他的callback function 為 onStationConnected()
Line 22 及 Line 44 就是我在前面提到的 Step1: 註冊 Callback function
Line 23, 47 : 相同於 Line 22, 44,差異只是在一個是 connected 的 event handler / 
callback function, 另一個是disconnected 的 event handler / callback function
Line 25 : 宣告一個LED blink 的旗標,作為 LED 是否要閃爍的依據
Line 29 : 設定UART baudrate 為115200bps
Line 30~32 : 設定LED及旗標的初始狀態
Line 38, 39:設定ESP8266為 AP mode以及他的AP名稱 (ssid)和密碼 (password)
Line 50~55 : Connected 的callback function,也就是當有裝置連結上這AP時,
WiFi-MAC就會呼叫onStationConnected()這個函數,在這函數中只是簡單的列印
出連接上的裝置 mac address 以及設定blinkFlag 為 true
Line 50 ~ Line 55 就是我在前面提到的 Step2 : 透過Callback function 傳遞來的
Event,然後設定Flag
Line 63~75 :  loop() 函數,也就是User-Task的主程式,程式中就是簡單的判斷blinkFlag ,
若為true ,LED閃兩次,然後清掉旗標。
Line 63 ~ Line 75 就是我在前面提到的 Step3 : 在User_Task判斷Flag狀態,
適時機處理Event

Why use Event-Driven
瞭解了 ESP8266 event-driven 的做法,也看完了實際範例的說明,
想必大家會使用了吧!? 若有疑問或哪裡要加強說明,請留言給我。

另外,這時候你或許會問我底下這問題,
”若我不用event-driven 或 callback function 來寫ESP8266的程式碼,不行嗎? “
我的答案是 “ 可以的”, 但我也必須告訴你一個事實,就是你的ESP8266再呼叫WiFi Classes時 , CPU會一直在做虛功,這會讓你覺得ESP8266跑不動你的程式碼, 或甚至你會覺得你要換顆更強的CPU 了,例如雙核的ESP32 !

下一篇,就讓我以實際範例來告訴你這個事實吧 !
敬請期待!!!
延伸閱讀

熱門文章