Arduino筆記(75):NodeMCU使用網頁滑桿控制伺服馬達轉動

Arduino筆記(15):控制伺服馬達 Servo 這篇實作中,使用可變電阻控制伺服馬達的轉動,購入 ESP8266 相關的NodeMCU或 ESP32都具備無線網路的功能。我想讓多個伺服馬達動作,讓機器手臂可以夾取物品,屆時可透過網頁控制手臂各關節。這一篇要來實作,透過操控網頁上的滑桿,操控一個伺服馬達在 0 ~ 180間轉動。您可將伺服軸定位在 0 到 180º 的各個角度。使用脈衝寬度調製(PWM)信號控制伺服系統。這意味著發送到電機的PWM信號將決定軸的位置。
實作時,使用類似 SG90 小型伺服馬達時,可以直接從 NodeMCU 供電,但如果使用像是 MG996R 或多個伺服馬達時,就需要接外部電源為伺服馬達供應電源。


[安裝ESP32-Arduino-Servo-Library Library]

本實作需要安裝以下程式庫:
程式庫(Library)安裝方法請參考另一篇文章:  Arduino筆記:安裝 Arduino IDE 程式庫(Library)

[材料]

  • NodeMCU ESP-8266 x 1
  • MG996R 伺服馬達 x 1
  • 電壓轉換模組 x 1
  • 18650 電池盒 x 1
  • 18650 電池 x 2
  • 連接線 x n
  • 麵包板 x 1

[線路圖]

伺服馬達我使用型號是MG996R,因NodeMCU的電力無法推動馬達,改為外接5V電源提供。

[程式]

NodeMCU 的 D7等同 GPIO 13,程式中指定控制 Pin 為 13。

在HTML創建一個滑桿(slider)時,可使用<input>標記,這個<input>標籤指定一個欄位可讓用戶輸入數值。有好幾種輸入類型來定義滑桿,可使用「type」屬性和「range」值。在滑桿中,另需指定「min」和「max」屬性來定義最小和最大範圍。
<input type="range" min="0" max="180" class="slider" id="servoSlider" onchange="servo(this.value)"/>
您還需要定義其他屬性,例如:
▸ 滑桿風格的 class
▸ 用於更新網頁上顯示的當前位置的ID
▸ 當滑塊移動時,onchange 屬性將調用伺服函數以向NodeMCU發送HTTP請求。

另外還需要將 JavaScript 添加到 HTML文件中,需要使用<script>和</ script >標記將 JavaScript 代碼添加到 HTML文件中,此段程式讓當前滑桿位置更新網頁:
var slider = document.getElementById("servoSlider");
    var servoP = document.getElementById("servoPos");
    servoP.innerHTML = slider.value;
    slider.oninput = function() {
      slider.value = this.value;
      servoP.innerHTML = this.value;
    }

以下是完成程式,上傳到 NodeMCU後就可以透過網頁控制伺服馬達。
#include <ESP8266WiFi.h>
#include <Servo.h>

Servo myservo;  // 建立伺服馬達控制

// 伺服馬達的連接 GPIO
static const int servoPin = 13;

// 設定無線基地台SSID跟密碼
char ssid[]     = "MyHome";
char password[] = "12345678";

// 設定 web server port number 80
WiFiServer server(80);

// 儲存 HTTP request 的變數
String header;

// Decode HTTP GET value
String valueString = String(5);
int pos1 = 0;
int pos2 = 0;

void setup() {
  myservo.attach(servoPin);  // 將伺服馬達連接的GPIO pin連接伺服物件

  Serial.begin(115200);
  // 使用SSID 跟 password 連線基地台
  Serial.print("Connecting to ");
  Serial.println(ssid);

  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  // 使用COM Port 列出取得的IP address
  Serial.println("");
  Serial.println("WiFi connected.");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
  server.begin();
}

void loop(){
  WiFiClient client = server.available();   // 等待 clients 連線

  if (client) {                             // 假使新的用戶端連線
    Serial.println("New Client.");          // 從序列 Port印出訊息內容
    String currentLine = "";                // 清空這行的內容 
    while (client.connected()) {            // 當 client連線持續執行迴圈
      if (client.available()) {             // 假使從 client 有讀到字元
        char c = client.read();             // 讀取這個字元
        Serial.write(c);                    // 印出這個字元在串列視窗
        header += c;
        if (c == '\n') {                    // 假使是換行符號

          // 假使目前的一行是空白且有兩個新行,就結束 client HTTP 的要求
          if (currentLine.length() == 0) {
            // HTTP 表頭開始時,會有回應碼 response code (如: HTTP/1.1 200 OK)
            client.println("HTTP/1.1 200 OK");
            client.println("Content-type:text/html");
            client.println("Connection: close");
            client.println();

             // 顯示 HTML 網頁
            client.println("<!DOCTYPE html><html>");
            client.println("<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">");
            client.println("<link rel=\"icon\" href=\"data:,\">");

            // 設定 on/off 按鈕的CSS
            client.println("<style>body { text-align: center; font-family: \"Trebuchet MS\", Arial; margin-left:auto; margin-right:auto;}");
            client.println(".slider { width: 300px; }</style>");
            client.println("<script src=\"https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js\"></script>");
                     
            // 網頁表頭
            client.println("</head><body><h1>ESP8266 with Servo</h1>");
            client.println("<p>Position: <span id=\"servoPos\"></span></p>");          
            client.println("<input type=\"range\" min=\"0\" max=\"180\" class=\"slider\" id=\"servoSlider\" onchange=\"servo(this.value)\" value=\""+valueString+"\"/>");
            
            client.println("<script>var slider = document.getElementById(\"servoSlider\");");
            client.println("var servoP = document.getElementById(\"servoPos\"); servoP.innerHTML = slider.value;");
            client.println("slider.oninput = function() { slider.value = this.value; servoP.innerHTML = this.value; }");
            client.println("$.ajaxSetup({timeout:1000}); function servo(pos) { ");
            client.println("$.get(\"/?value=\" + pos + \"&\"); {Connection: close};}</script>");
           
            client.println("</body></html>");
            
            //GET /?value=180& HTTP/1.1
            if(header.indexOf("GET /?value=")>=0) {
              pos1 = header.indexOf('=');
              pos2 = header.indexOf('&');
              valueString = header.substring(pos1+1, pos2);
              
              // 旋轉伺服主機
              myservo.write(valueString.toInt());
              Serial.println(valueString); 
            }         
            // 使用空白行結束 HTTP回應
            client.println();

            break;
          } else {  // 假使有新的一行, 清除目前這一行
            currentLine = "";
          }
        } else if (c != '\r') {  // 讀取到的不是換行符號 
          currentLine += c;      // 增加一個字元在本行最後
        }
      }
    }
    // 清除表頭變數
    header = "";
    // 關閉連線 connection
    client.stop();
    Serial.println("Client disconnected.");
    Serial.println("");
  }
}

[結果]




[參考資料]

1 留言

  1. 你好 請問一下 我照你的接法接了以後 拉動BAR從0~180 但是馬達卻只轉了90度 是什麼原因

    回覆刪除

張貼留言

較新的 較舊