2019年5月10日 星期五

ESP8285 兩軸伺服馬達雲台運動控制學習套件{1 of 4}手機控制的兩軸伺服馬達雲台

網頁最後修改時間:2019/05/10

這篇是 ESP8285 兩軸伺服馬達雲台運動控制學習套件中的第一個部分:用手機控制雲台的水平(左右)與垂直(上下)轉動。

套件所包含的學習部分可由下面這張圖看出。

其中,本篇網頁主要說明的是圖中 雲台手機控制 (右下,綠色線和框)的部分:
搭配自製的手機 APP 與 ESP8255 通訊,傳送與接收兩軸伺服馬達的設定,並且以手機螢幕中的搖桿圖示來控制雲台的兩軸動作。
詳細的操作和說明,請看網頁中的影片介紹。

*********************************************************************************
學習套件可至下面網址購買:
*********************************************************************************

【學習套件的基本電路】

不管使用哪一個程式,學習套件只用到下面這個佈線電路圖;即便沒有用到 MPU6050 的程式,測試的時候也不需要拆,直接就能使用

不過這裡有一個很重要!必須要注意的地方,要特別提出來:電源!電源!電源!

實際測試用的麵包板電源模組,它的外接電源輸入是 5V/2A 的手機充電器。

如果電源(有試過 5V/1A)功率不夠的話,接好電路通電的結果,就會看到伺服馬達不受使喚;此時,就是告訴你(妳),請更換一顆功率較大的就沒問題了!

學習套件參考接線圖
實際測試用的佈線電路,如下圖所示。

學習套件參考接線實際佈線圖例
要能直接燒錄的話,請參考下面的 *燒錄接線: 再多接上去即可。

/*--*//**---/*///**---*-*////***--*/*///***----*///--*/*///**--*/*//**--**/*//
* 燒錄接線:

ESP8285 燒錄的接線與 ESP8266 沒有什麼不同,只要將 <GP0><GND> 然後再將模組通電就會自動進入到燒錄模式。

下面提供兩種燒錄接線圖。第二種方式是比較推薦的方法,不需重覆插拔線,馬上燒錄、馬上測試!

** USB 轉 TTL 模組 / 線:

USB 轉 UART 模組燒錄接線方式
** ESP8266 入門學習套件裡的通訊與燒錄底板:

不要去質疑下面的電路為什麼 <TXD><TX0>,就是要這樣接!

ESP-01/01S 通訊/燒錄底板燒錄接線方式
【韌體燒錄說明】

/*--*//**---/*///**---*-*////***--*/*///***----*///--*/*///**--*/*//**--**/*//
* 韌體燒錄選項:

Arduino IDE 和 Flash Download Tool 韌體燒錄的選項,如下圖所示。

紅色框框的部分是不能做更改的部分,這是屬於 ESP8285 專用的設定。沒出現在 Arduino IDE 的選項中,表示這是內定的預設值,不應該被修改。

Upload Speed 燒錄速度設定要看使用的 UART 模組而定,115200 是大多都適用的速度選項,若使用的是入門套件中的燒錄底板,則速度可拉至 921600(或更高?)。

Flash Size 這基本的大小(1M)是不能變的,但是 SPIFFS 的大小卻是可以。盡量不要選 no SPIFFS,因為有些函式庫會用到此部分。對於這個選項,我都是選擇 1M(512k SPIFFS) 比較多。

其他沒說到的選項就用預設值就可以。如果要改,請先弄清楚該選項再去做,否則只會讓自己徒增麻煩而已!

ESP8285, ESP-01M 燒錄選項設定
如果使用的是入門套件的燒錄底板,這些選項要怎麼選都在資料夾裡面,請自行翻閱!

/*--*//**---/*///**---*-*////***--*/*///***----*///--*/*///**--*/*//**--**/*//
* 完全清除韌體與紀錄 :

ESP8285(或某些函式庫)在執行期間會用到 SPIFFS 來儲存資料,因此韌體上傳時若是沒有將其清除的話,它們就會繼續保留在 SPIFFS 中讓程式取用。

不清楚什麼是 SPIFFS 的話,這裡有篇說明 ESP8266 Arduino IDE 開發問與答 Q&A ( 2 )

雖然大部分的時候,沒清除 SPIFFS 再進行燒錄都讓人覺得方便(當然有時候在燒錄的同時,或是燒錄前將其清除是有其必要性的,例如無線網路連線資料),端看應用與自己的選擇。

Erase Flash 提供三個清除選擇在 Arduino IDE 的選單:Only SketchSketch + WiFi Settings All Flash Contents,除了第一個選項只清除韌體之外,其他兩個選項都可以選,第三個則是清除 SPIFFS 最徹底的一個。

另外一種清除的方法是使用 esptool.py 直接下指令(系統內要有 python 2.7 和 esptool
$> pip install esptool
$> esptool.py -p com8 -b 74880 erase_flash
其中,com8 是 USB 轉 UART 模組的 COM Port 號碼;74880 是通訊速度; erase_flase 表示清除 flash。

【程式架構說明】

下面的示意圖,除了說明處理的流程,也將手機端與 ESP8285 之間的通訊方式與傳輸的資料格式都列在上面,只要與程式相對照,不難了解其中的流程與原理。

程式架構示意圖,點擊看超大圖
一但無線網路設定成功後,ESP8285 會自動連線到指定的網路中,並且回傳 ESP8285 所取得的 IP 位址在設定頁面中。

將手機設定到與 ESP8285 同一個網路,打開 APP 進入 WiFi 連線設定 頁面,將該 IP 地址輸入到欄位中,即可開始操作其他畫面中的東西。

IP 地址輸入後就會自動被記錄,下一次重新啟動 APP 就不需要再重新輸入!

程式架構示意圖裡面沒有特別畫出無線網路設定的部分,這部分請看影片中的操作說明。

APP 與 ESP8285 之間的通訊採非連續性的通訊,也就是說:只有需要的時候才會與 ESP8285 通訊,其餘時候都是斷開的。

雲台初始化、雲台資料上傳和下載,都是使用 HTTP POST 將所需要的資料封裝進 JSON 字串裡發送和被接收,之後 APP 和 ESP8285 解析 JSON 字串後,再取出資料進行相對應的動作。
  • action:"initial"
    進行雲台初始化。
  • action:"upload"
    將雲台設定值寫入到 EEPROM 中,雲台初始化就會使用這個值,否則就採用預設值。
  • action:"download"
    EEPROM 儲存的雲台資料若有效則使用這些數據做為回傳給 APP,否則採用預設值回傳。
/*-/--*-*/*/*/*/***//-*-*-**-*/*-*-/*/*/*-*-/-////--/**/**--**/--///--//**----**//--**//**----***//*-**//*
第一次啟動 APP有兩個動作一定要先做(先進入到 雲台組態設定 頁面中,設定好雲台各軸的各項參數值,再繼續下面的動作(基本上使用 APP 預設值上傳就可以,不用特別修改!)):

  1. 按下 "上傳設定值" 按鈕,上傳資料到 ESP8285 並儲存至 EEPROM;
    沒有執行這動作,ESP8285 就不知道有 EEPROM 的資料可以用,所執行的動作都會採程式預設值;但只要執行過一次之後,ESP8285 就會自己去檢查 EEPROM 裡面的資料,程式執行就會按照設定值動作。
  2. 按下 "儲存設定值" 按鈕,儲存設定值到手機;
    儲存之後,每次按下 "恢復初始值" 按鈕,就會以此儲存的設定值恢復頁面欄位值。
/*-/--*-*/*/*/*/***//-*-*-**-*/*-*-/*/*/*-*-/-////--/**/**--**/--///--//**----**//--**//**----***//*-**//*

進入 雲台操作 頁面後,APP 會自動建立與 ESP8285 的 WebSocket 連接,其連線的狀態會顯示在頁面的上方;只有連線成功,搖桿才會出現。

一但離開該頁面,就會馬上關閉 WebSocket 連線。

所以只有在需要連線的時候才建立連線,任何的傳送都帶有回覆,沒有回覆就代表連線不存在,需要檢查雙方的連線或是 IP 有沒設錯。

/*--*//**---/*///**---*-*////***--*/*///***----*///--*/*///**--*/*//**--**/*//
* Android APP GUI:

APP 1.84MB 左右,安裝之後打開可看到有四個頁面,如下所示。

Android 手機 APP 操作頁面,點擊看原圖
  • WiFi 連線設定
    ESP8285 在網路中的 IP 地址。
  • 雲台組態設定
    雲台各軸的脈波寬度範圍以及初始化的位置設定。設定錯誤的脈波寬度範圍,會導致伺服馬達動作不正確,詳情可看此篇網頁的說明。
  • 雲台操作
    左邊搖桿控制水平(PAN)旋轉;右邊搖桿控制上下(TILT)旋轉,搖桿往上頭部往下,搖桿往下頭部往上。
  • 雲台初始化
    根據 ESP8285 EEPROM 裡的設定或是預設值,進行雲台初始化的動作。
/*--*//**---/*///**---*-*////***--*/*///***----*///--*/*///**--*/*//**--**/*//
* 手動測試:

那麼怎麼在沒 APP 的情況之下做測試呢 ?

HTTP POST 可使用 Postman;WebSocket 則上 chrome 線上應用程式商店安裝 Simple WebSocket Client

** HTTP POST 通訊測試:

雲台初始化的測試,切換到頁面 Body 選擇  x-www0form-urlencoded、輸入 KEY 為 action 和設定 VALUE 為 initial,按下 "Send" 後觀察雲台的動作;此時應該要回到設定的初始化位置。

雲台初始化格式
同樣的動作,只不過這次設定 VALUE 為 download。按下 "Send" 後等一下,下面就會出接收到的 JSON 字串,裡面包含雲台各軸的各項資料。

從 ESP8285 下載雲台資料
若是要上傳,需要的資料要填寫的就比較多,action 值必須設為 upload,其他各項說明如下:
  • panmin, panmax, panpos
    PAN servo 最小與最大的脈衝寬度值(µs),以及初始化的位置(degree)。
  • tiltmin, tiltmax, tiltpos
    TILT servo 最小與最大的脈衝寬度值(µs),以及初始化的位置(degree)
填寫完畢後,按下 "Send" 發送。如果此時 ESP8285 有接 UART 模組的話,就會看到下面的訊息出現
PAN-SERVO, INITIAL DATA
  MIN: 700  MAX: 2400  POS: 90  CKSUM: 0xd6
TILT-SERVO, INITIAL DATA
  MIN: 700  MAX: 2400  POS: 90  CKSUM: 0xd6
Got platform data from APP
若是 APP,就會出現提示的視窗,表示資料已成功上傳;這些都可以在影片中看到。
從遠端上傳雲台資料到 ESP8285
** Web Socket 通訊測試:

Web Socket 的通訊主要是,要傳送 APP 左右兩支搖桿的移動位置和方向。ESP8285 接收到之後,進行簡單的運算,就能給出兩軸需要的轉動角度。

Server Location 輸入 ws://192.168.11.12:81,表示 ESP8285 的 IP 是 192.168.11.12,Web Socket port number 是 81(這個值設定在 ESP8285 的程式裡)。

Request 輸入的是各軸的角度以及方位,這跟 APP 搖桿的移動方向以及位移值有關,ESP8285 的程式是根據這個來撰寫的。所以,不需要糾結為什麼是這樣寫,完全可以創造自己想要的呈現方式。

格式是這樣定義的(有些部分是保留以後擴充用的,在解析時不會用到):
p(l|r)(a|s)(degree) 
t(u|d)(a|s)(degree)
其中,
  • p, t:代表控制的是雲台水平或是上下轉動的伺服馬達;
  • l, r:代表搖桿的水平移動是左轉還是右轉,現在是保留,沒用到;
  • u, d:代表搖桿的上下移動是往上還是往下,現在是保留,沒用到;
  • a, s:代表是角度還是速度模式,s 現在是保留,沒用到;
  • degree:代表角度值。原本應該是 0 .. 180 度,但是因為 ESP8285 轉換失敗所回傳的是 0,所以從 APP 發送出去的值都是取值之後加一再送出的。ESP8285 接收到之後會先做轉換,確認轉換成功之後就會減一再輸出給伺服馬達,所以下面會看到發送值與回傳值會相差一,就是這個原因。
成功發送並被接收之後,ESP8285 會以 JSON 格式的字串回傳給 APP 現在雲台兩軸的轉動角度。

遠端 Web Socket 與 ESP8285 的雲台動作測試
【ESP8285 Arduino 程式碼】

下面說明程式碼的部分。要知道雲端資料夾裡的程式碼幾乎沒有包含註解,所以要了解程式碼要看這裡的說明。

/*-/--*-*/*/*/*/***//-*-*-**-*/*-*-/*/*/*-*-/-////--/**/**--**/--///--//**----**//--**//**----***//*-**//*
有購買商品的使用者,網頁中所需相關資料已放置於雲端硬碟,請自行與網頁中的內容相互對照。
其他的使用者,程式碼可以給,但是不提供標頭檔定義以及所使用的函式庫名稱;從網頁內文以及影片中可以找到提示。
/*-/--*-*/*/*/*/***//-*-*-**-*/*-*-/*/*/*-*-/-////--/**/**--**/--///--//**----**//--**//**----***//*-**//*

再把程式結構圖拉過來看一下。

程式架構示意圖,點擊看超大圖
韌體程式主要處理的是:HTTP POST 的客戶端請求(Client Request)、WebSocket 的事件、EEPROM 的讀取/寫入和雲台的控制,還有一個沒畫在圖中的無線網路組態的部分(這部分的程式碼可參考網頁 "如何使用 ESP8266 與手機 APP 控制 WS2812B(LED 閃爍效果、流星燈色環挑色)"),下面就上面所講的部份來做說明。

/*--*//**---/*///**---*-*////***--*/*///***----*///--*/*///**--*/*//**--**/*//
* 全局變數:
  • line  3...4:softAP 的名稱與密碼;
  • line  6:建立 Web Server,port number 80;
  • line  7:建立 Web Socket,port number 81;
  • line 10...11:ESP8285 可選擇 <IO4><IO12><IO14> <IO15> 這四支接腳來控制伺服馬達;此處選用的是水平 <IO12>、上下 <IO14>
  • line 12:列舉一些常數,PAN = 0, TILT = 1, SERVOITEMS = 2,方便陣列使用常數而不是數字,容易識別;
  • line 13:建立一個可控制兩個伺服馬達的陣列,大小為 2;
  • line 15...20:儲存伺服馬達脈衝寬度範圍和初始化位置的結構,EEPROM 儲存的就是這個部分;checksum 是用來驗證該筆數據是否有效的根據。
  • line 22...25:用於儲存伺服馬達初始化數據以及位置的結構,並同時建立和初始化一個儲存雲台兩軸預設值的結構陣列。
ESPWebServoControl.ino, 1 of  6
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//* 全局變數 *
// ESP8266 AP 名稱與連線密碼
const char* ESP_AP_NAME       = "espPanTilt";
const char* ESP_AP_PASS       = "123456789";
// ESP8266 Web Server
ESP8266WebServer WEBSERVER(80);
// ESP8266 Web Socket Server
WebSocketsServer WEBSOCKET(81);
// Two-Axis Servo Gimbal
const int PIN_PAN_SERVO    = 12;    // Horizontal rotation
const int PIN_TILT_SERVO   = 14;    // Vertical rotation
enum {PAN, TILT, SERVOITEMS};
Servo platform[SERVOITEMS];

struct init_t {
   uint16_t min_pulse_width;        // pulse width in microseconds
   uint16_t max_pulse_width;
   uint8_t  pos;                    // initial position, 0...180 degree
   uint8_t  checksum;
};

struct servo_t {
   init_t init;
   uint8_t currpos;
} servo[SERVOITEMS] = { { {700, 2400, 90, 0xD6}, 90 }, { {700, 2400, 90, 0xD6}, 90 } };

/*--*//**---/*///**---*-*////***--*/*///***----*///--*/*///**--*/*//**--**/*//
* 雲台初始化:

雲台兩軸的初始化函式。從這個函式也可以看出,為什麼全局變數那麼宣告?以及使用的方式。

這裡要特別說明的是 line 9...10attach 時必須同時指定脈衝寬度的大小範圍,否則伺服馬達的轉動只會是預設的 90 度。再者,脈衝寬度的範圍以現在的設定就是 180 度的轉動量,雖然說可以改,但是錯誤的修改會導致伺服馬達損壞或動作不正常,所以除非知道自己在做什麼,否則不建議去做變更!
ESPWebServoControl.ino, 2 of  6
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
/**-------------------------*
 *  伺 服 馬 達 輔 助 函 式   *
 * ------------------------*/
void platformInit() {
   // detach servo
   platform[PAN].detach();
   platform[TILT].detach();
   // attach servo
   platform[PAN].attach(  PIN_PAN_SERVO,  servo[PAN].init.min_pulse_width,  servo[PAN].init.max_pulse_width );
   platform[TILT].attach( PIN_TILT_SERVO, servo[TILT].init.min_pulse_width, servo[TILT].init.max_pulse_width );
   // 回預設的初始點
   platform[PAN].write(  servo[PAN].init.pos );
   platform[TILT].write( servo[TILT].init.pos );
   // 紀錄現在伺服馬達的位置
   servo[PAN].currpos   = servo[PAN].init.pos;
   servo[TILT].currpos  = servo[TILT].init.pos;
}

/*--*//**---/*///**---*-*////***--*/*///***----*///--*/*///**--*/*//**--**/*//
* EEPROM 輔助函式:

下面幾個函式,是用來讀取與寫入資料到 EEPROM、計算校驗和以及輸出雲台資料的,其中:
  • line  4...8:校驗和計算。針對 init_t 結構裡面前三項進行 8 位元計算,然後全部加總在一起,返回最後計算結果。實例可以看全局變數小節 line 25 的初始化宣告,它的校驗和就是經過計算所得出的結果。
  • line 10...19:取回兩個伺服馬達的初始值設定,並且驗證校驗和是否正確;只有正確的校驗和才能確保讀取的資料是正確且有效的!
  • line 21...34:EEPROM 的寫入分兩個步驟,首先將要寫入的資料 put() 到緩衝記憶體(line 22...27),然後再 commit() 將記憶體裡的資料寫入到 EEPROM(line 29);前提是欲寫入的資料的校驗和是正確的(line 15...16),否則就會馬上中斷寫入的動作。
  • line 36...50:輸出雲台初始值設定與現在的轉動角度。
這幾個函式實際的使用方法請看 setup() 裡的程式碼。
ESPWebServoControl.ino, 3 of  6
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
/**---------------------*
 *  EEPROM 輔 助 函 式   *
 * --------------------*/
uint8_t calculateChecksum( uint8_t *cb , uint8_t size ) {
   uint8_t sum = 0x55;
   while( --size ) sum += *cb++;
   return sum;
}

bool readGlobalSet() {
   // Get EEPROM data into our local copy
   size_t sz = sizeof( init_t );
   for( int i = 0; i < SERVOITEMS; i++ ) {
      EEPROM.get( 0 + ( i * sz ), servo[i].init );
      if( calculateChecksum( (uint8_t*)&servo[i].init, sz ) != servo[i].init.checksum )
         return false;
   }
   return true;
}

bool writeGlobalSet() {
   size_t sz = sizeof( init_t );
   // Set the EEPROM data ready for writing
   for( int i = 0; i < SERVOITEMS; i++ ) {
      servo[i].init.checksum = calculateChecksum( (uint8_t*)&servo[i].init, sz );
      EEPROM.put( 0 + ( i * sz ), servo[i].init );
   }

   // Write the data to EEPROM
   bool ok = EEPROM.commit();
   if( !ok ) return false;
   Serial.println( (ok) ? "Commit OK" : "Commit failed" );
   return true;
}

void printGlobalSet() {
   Serial.println();
   Serial.println( F("PAN-SERVO, INITIAL DATA") );
   Serial.printf(  "  MIN: %d",  servo[PAN].init.min_pulse_width);
   Serial.printf(  "  MAX: %d",  servo[PAN].init.max_pulse_width);
   Serial.printf(  "  POS: %d",  servo[PAN].init.pos);
   Serial.printf("  CKSUM: 0x%x",  servo[PAN].init.checksum);
   Serial.println();
   Serial.println( F("TILT-SERVO, INITIAL DATA") );
   Serial.printf(  "  MIN: %d",  servo[TILT].init.min_pulse_width);
   Serial.printf(  "  MAX: %d",  servo[TILT].init.max_pulse_width);
   Serial.printf(  "  POS: %d",  servo[TILT].init.pos);
   Serial.printf("  CKSUM: 0x%x",  servo[TILT].init.checksum);
   Serial.println();
}

/*--*//**---/*///**---*-*////***--*/*///***----*///--*/*///**--*/*//**--**/*//
* setup():

setup() 裡面包含的不只是初始化:整個程式的處理中心也在這裡,Web Server 客戶端請求、Web Socket 的事件處理分配也在這裡,所以程式碼有點多。為了方便說明,將此處的程式碼分成四段,分段說明。

下面是關於無線網路組態的設定說明:
  • line  4:初始化 UART,通訊速率為 115200 bps;
  • line  9...17:無線網路的連線或配置;
    • line 10:設定最小的訊號品質,預設 < 8% 的訊號品質不輸出;
    • line 11:連線逾時的時間設定(秒);
    • line 12:自動連線。若失敗首先啟用 SoftAP 模式,SoftAP 模式下的名稱為 ESP_AP_NAME,密碼為 ESP_AP_PASS 變數所指定的字串,若再失敗則重啟 ESP8266。
  • line 19:無線網路設置如果成功,輸出 WiFi Connected.,這時就可以切換網路連線了。
ESPWebServoControl.ino, setup() 4-1 of  6
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
void setup() {
   delay(3000);

   Serial.begin( 115200 );
   Serial.println();
   Serial.println();

   /** WiFiManager */
   WiFiManager wifiManager;
   wifiManager.setMinimumSignalQuality();
   wifiManager.setTimeout(120);
   if ( !wifiManager.autoConnect( ESP_AP_NAME, ESP_AP_PASS ) ) {
      Serial.println("failed to connect and hit timeout");
      delay(3000);
      ESP.reset();
      delay(5000);
   }
   // 到這裡已經完成無線網路的連線
   Serial.print( "\nWiFi Connected.\n" );   


下面是關於 Web Server 的客戶端請求處理,處理關於 http://<IP>/platform 的客戶端請求的部分(line 22...65)。

連線到此網址後,Web Server 會去接收來自客戶端的資料,依據 action 後面所帶的值,再去區分處理的動作是雲台初始化、雲台資料上傳或雲台資料下載。

而從手機 APP 這端,實際所丟的資料是 JSON 格式字串。但經過處理之後,在 Web Server 這端,可以很方便的使用 arg( String key ) 函式輕鬆取出 key 所對應的值,不需要再做 JSON 字串解析的動作。

上面各部分的程式碼說明如下:
  • line 22:設定要處理的客戶端請求 URI 名稱;
  • line 24:取出 action 的值;
  • line 26...29:雲台初始化;
    • line 26:重新設定伺服馬達脈衝寬度的範圍,並且轉動到指定的角度。
    • line 28:輸出雲台各軸的資料。
    • line 29:回覆一個沒資料的狀態值,表示處理成功,手機端也會出現相對應的提示視窗,否則過一段時間後會出現錯誤的提示。
  • line 31...47:回傳雲台的資料;
    • line 32...44:建立一個要回傳給客戶端的 JSON 字串
      {"panpos":"...","tiltpos":"...","panmin":"...","panmax":"...","tiltmin":"...","tiltmax":"..."}
    • line 46:以 text/plain 的 HTTP 內文類型(Content-Type)傳送 JSON 字串,HTTP 狀態碼(Status Code) 200
  • line 49...61:儲存手機送過來的雲台資料;
    • line 50...57:取出手機傳送來的雲台資料值(同時轉換為整數),並暫時存放在 servo 結構陣列中,同時要將數據進行校驗和的計算並一併放入到結構陣列中。
    • line 58:將 servo 結構陣列中的資料寫入到 EEPROM。
    • line 60:處理完畢後回覆狀態碼給客戶端。
ESPWebServoControl.ino, setup() 4-2 of 6 
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
   /** Web Server Events Handle */
   WEBSERVER.on( "/platform", HTTP_POST, []() {  
      if( WEBSERVER.args() > 0 ) {
         String action = WEBSERVER.arg( "action" );
         if( action == "initial" ) {            // 雲台初始化
            platformInit();
            Serial.println( F("Platform initialization") );
            printGlobalSet();
            WEBSERVER.send( 200, "text/plain", "" );

         } else if ( action == "download" ) {   // 回傳雲台的資料
             String json = "{\"panpos\":\"";
            //** pan initial position
            json += String( servo[PAN].init.pos )  + "\",\"tiltpos\":\"";
            //** tilt initial position
            json += String( servo[TILT].init.pos ) + "\",\"panmin\":\"";
            //** pan min pulse width
            json += String( servo[PAN].init.min_pulse_width ) + "\",\"panmax\":\"";
            //** pan max pulse width
            json += String( servo[PAN].init.max_pulse_width ) + "\",\"tiltmin\":\"";
            //** tile min pulse width
            json += String( servo[TILT].init.min_pulse_width ) + "\",\"tiltmax\":\"";
            ///** tile max pulse width
            json += String( servo[TILT].init.max_pulse_width ) + "\"}";
            Serial.println(json);
            WEBSERVER.send( 200, "text/plain", json );
            Serial.println( F("Send platform data to APP") );

         } else if( action == "upload" ) {      // 儲存手機送過來的雲台資料
            servo[PAN].init.min_pulse_width = WEBSERVER.arg( "panmin" ).toInt();
            servo[PAN].init.max_pulse_width = WEBSERVER.arg( "panmax" ).toInt();
            servo[PAN].init.pos = WEBSERVER.arg( "panpos" ).toInt();
            servo[PAN].init.checksum = calculateChecksum( (uint8_t*)&servo[PAN].init, sizeof( init_t ) );
            servo[TILT].init.min_pulse_width = WEBSERVER.arg( "tiltmin" ).toInt();
            servo[TILT].init.max_pulse_width = WEBSERVER.arg( "tiltmax" ).toInt();
            servo[TILT].init.pos = WEBSERVER.arg( "tiltpos" ).toInt();
            servo[TILT].init.checksum = calculateChecksum( (uint8_t*)&servo[TILT].init, sizeof( init_t ) );
            writeGlobalSet();
            printGlobalSet();
            WEBSERVER.send( 200, "text/plain", "" );
            Serial.println( F("Got platform data from APP") );

         } else {}
      }
   });   // /platform
  

  • line 67...69:是 Web Server 的客戶端請求處理,處理任何不符合前述 http://<IP>/platform 的 HTTP POST 要求的部分(line 67...69)。
  • line 71:啟動 Web Server;
  • line 75:啟動 Web Socket;
  • line 76:設定 Web Socket 的事件處理函式名稱;
ESPWebServoControl.ino, setup() 4-3 of  6
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
   WEBSERVER.onNotFound([](){
      WEBSERVER.send( 404, "test/plain", "File not found." );
   });

   WEBSERVER.begin();
   Serial.println( "HTTP server started" );
   
   /** Web Socket Events Handle */
   WEBSOCKET.begin();
   WEBSOCKET.onEvent( wsEventsHandle );
   Serial.println( "Web socket server started " );   
  

下面是關於開機之後的 ESP8285 EEPROM 裡的雲台資料讀取,尤其是燒錄之後第一次的讀取,必須確保 EEPROM 在沒有任何資料的情況下,先行將全局變數的預設值確實寫入,這樣之後每一次開機讀取和寫入才能確保沒有問題;否則永遠只會使用預設值,所設定的值完全都沒有用。
  • line 80:初始化 EEPROM,並且設定要使用的大小,也就是 2 個 init_t 結構的大小;
  • line 82...103:看程式碼裡面的註解;
  • line 107:進行開機之後的雲台初始化;
ESPWebServoControl.ino, setup() 4-4 of 6 
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
   /** ESP8285 EEPROM */
   EEPROM.begin( 2 * sizeof( init_t ) );

   // 檢查 EEPROM 是否已有之前儲存的資料,如果是,讀出來看看是否有效!
   if( EEPROM.percentUsed() >= 0 ) {
      Serial.println( F("EEPROM has data from a previous run.") );
      Serial.print( EEPROM.percentUsed() );
      Serial.println( F("% of ESP flash space currently used" ) );
      // 讀取並確認 EEPROM 裡面的資料是否有效?如果有效,用它做為預設值。
      if( readGlobalSet() ) {
         Serial.println( "Use global set from EEPROM" );
         printGlobalSet();
      } else { 
         //** 讀取失敗,寫入預設的結構與數值
         Serial.println( "Failed to read global set from EEPROM, recover it!" );
         if( writeGlobalSet() ) {
            Serial.println( "Use default global set" );
            printGlobalSet();
         } else {
            Serial.println( "Global set read/write failure, check your code!" );
         }
      }
   } else {
      Serial.println( F("EEPROM size changed - EEPROM data zeroed - commit() to make permanent" ) );
   }

   /** Servo */
   // 雲台伺服馬達初始化
   platformInit();
}

/*--*//**---/*///**---*-*////***--*/*///***----*///--*/*///**--*/*//**--**/*//
* Web Socket 事件處理函式:

Web Socket 通訊中有很多事件會產生,但並不是所有的事件都需要去處理才行,其實只要處理感興趣和必須的事件就可以,如 wsEventHandle() 事件處理函式只處理了 Web Socket 的已斷線(line 3...5)、已連線(line 6...9)和接收到訊息(line 10...13)這三個事件,而最後一個事件(WStype_TEXT) 就是接收到手機搖桿資料的地方,要對其分配相對應的處理程式碼;這裡指定了另外一個函式 wsservohandle() 來處理。

搖桿的資料格式在上面 ** Web Socket 通訊測試: 小節已做過說明,所以可知 wsservohandle() 函式裡的字串 cmd 的第三個字元之後的數字,代表的就是角度值,對其轉換(line 18)為整數,然後將其值減一,並驗證其轉換是否有效,無效則馬上退出接下來的處理(line 19)。
  • cmd[0]:控制的是 PAN 或 TILT 伺服馬達;
  • cmd[1]:上下左右轉動的方向,這位元保留不使用;
  • cmd[2]:角度或速度模式,速度模式保留不使用,故只能是 'a'
取出角度值之後,接著要對前面三個字元進行分析。根據上面的解釋,不用考慮第二個字元,且第三個字元必須為 'a'(line 21)。

接著根據第一個字元(line 22, line 26)決定控制的伺服馬達是 PAN 還是 TILT,然後寫入角度值(line23, line 27)並記錄下當前角度值(line 24, line 28)。

最後,要把執行的結果以 JSON 字串格式發送回 APP(line 32...34),這樣 APP 才能知道現在的雲台轉動角度值,以及是否已被執行。

返回的值會顯示在 APP 的 雲台操作 頁面的 R 欄位後面,也能容易的用來判別搖桿的動作是否已被 ESP8285 執行過了的依據。
ESPWebServoControl.ino, 5 of 6 
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
void wsEventsHandle( uint8_t num, WStype_t type, uint8_t * payload, size_t length) {
   switch(type) {
      case WStype_DISCONNECTED:
         Serial.printf("[%u] Disconnected!\n", num);
         break;
      case WStype_CONNECTED: {
         IPAddress ip = WEBSOCKET.remoteIP(num);
         Serial.printf("[%u] Connected from %d.%d.%d.%d url: %s\n", num, ip[0], ip[1], ip[2], ip[3], payload);
      } break;
      case WStype_TEXT:
         Serial.printf("[%u] get Text: %s\n", num, payload);
         wsservohandle( num, String((char*)payload) );
         break;
   }
}

void wsservohandle( uint8_t client_id, const String& cmd ) {
   int val = (cmd.substring(3)).toInt();
   if( val-- <= 0 ) return;

   if( cmd[2] == 'a' ) {
      if( cmd[0] == 'p' ) {
         platform[PAN].write( val );
         servo[PAN].currpos = val;
      }
      else if( cmd[0] == 't' ) {
         platform[TILT].write( val );
         servo[TILT].currpos = val;
      }
   }

   String json = "{\"panangle\":\""    + String(servo[PAN].currpos) + 
               "\",\"tiltangle\":\""   + String(servo[TILT].currpos)  + "\"}";
   WEBSOCKET.sendTXT( client_id, json );
}

/*--*//**---/*///**---*-*////***--*/*///***----*///--*/*///**--*/*//**--**/*//
* loop():

Web Server 和 Web Socket 在 loop() 函式裡必須要加的程式碼,沒加這兩行就不會正常執行,就是把它們加上去了就好!
ESPWebServoControl.ino, 6 of  6
void loop() {
   WEBSERVER.handleClient();
   WEBSOCKET.loop();
}

【展示影片】

程式碼的說明不一定能夠清楚的呈現出實際動作的效果,但經由下面的展示影片,完全可以呼應出上面網頁的說明,它完整展示了雲台手機控制操作的整個流程。


【結論】

對於想要用手機來控制東西的想法已經很久,但是按鈕按一按開個燈、開個電風扇等...類的 APP 網路上很多,有些也不需要特別去寫 APP,用其他的 APP 也能達到同樣的效果,所以一直沒特別針對部落格的項目寫這部分的東西。

但是就是突然萌現了這個想法:是不是可以用手機來控制雲台的動作,單純的轉動動作?想想,用來練練手也不錯,就寫了這 APP!

雖然用的是 ESP8285 晶片模組,但是這個程式碼 ESP8266 晶片模組也可以用。

另外,即便沒有 APP,網頁也提供了其他的連線測試方法。若是真的想自己寫操作介面的話,可以用 HTML + JS 來做也能達到相同的效果。

另外,若是不想用韌體所提供的無線網路組態函式庫的話,可以把 setup() 裡面關於此部分的程式碼全部移除,然後用一般的連線方式連線到想連線的無線網路去,這樣也不失為另一種方法。

接著,繼續這個學習套件的系列,要再引入另一個模組:MPU6050(三軸加速度計與三軸陀螺儀模組),後面要用它實現雲台跟隨與自穩的運動控制。而要達到這個目的,首先要做的就是 MPU6050 的校正,也是下一篇部落格網頁的主題。


<< 部落格相關文章 >>


沒有留言:

張貼留言

留言屬名為"Unknown"或"不明"的用戶,大多這樣的留言都會直接被刪除掉,不會得到任何回覆!

發問問題,請描述清楚你(妳)的問題,別人回答前不會想去 "猜" 問題是什麼?

不知道怎麼發問,請看 [公告] 部落格提問須知 - 如何問問題 !