[TH] Arduino: ควบคุมการเคลื่อนที่หุ่นยนต์รถผ่านบราวเซอร์ด้วย esp8266 Part 2

จากบทความก่อนหน้าที่ใช้ esp8266 เพียงตัวเดียวสำหรับการควบคุม Agent ซึ่งจำนวนขาที่ไมโครคอนโทรลเลอร์ esp8266 (ตามที่เขียนไว้ในบทความเกี่ยวกับ machine.Pin ของ MicroPython) มีให้นั้นมีจำกัด และหลายขาถูกใช้งานขณะเริ่มระบบทำให้เกิดความผิดพลาดที่ไม่ได้ตั้งใจ เช่น ล้อหมุนเมื่อระบบเริ่มทำงาน และหยุดเมื่อระบบทำการบูตเสร็จ เป็นต้น ดังนั้น ในบทความนี้จึงเพิ่มบอร์ดไมโครคอนโทรลเลอร์ LGT8F328P เข้ามา ดังภาพที่ 1 หรือผู้อ่านอาจจะเปลี่ยนเป็นไมโครคอนโทรลเลอร์ตระกูล Arduino อื่น ๆ แทนได้ เช่น Arduino Nano หรือ Arduino Uno เป็นต้น โดยให้ LGT8F328P นั้นเป็นส่วนของ Actuator ที่ทำหน้าที่เคลื่อนที่ไปในสิ่งแวดล้อม คือ สามารถสั่งให้เดินหน้า ถอยหลัง เลี้ยวซ้าย เลี้ยวขวา และหยุดได้ ทำให้ลดภาระการทำงานของ esp8266 ลง และให้ทำงานตอบสนองการสื่อสาร WiFi ได้มากขึ้น

ภาพที่ 1 บอร์ด LGT8F328P ที่นำมาประกอบเข้ากับระบบหุ่นยนต์รถเพื่อใช้ควบคุมการเคลื่อนที่

อุปกรณ์

รายการอุปกรณ์สำหรับการทดลองตามภาพที่ 1 เป็นดังนี้

  1. NodeMCU และบอร์ดขยาย
  2. บอร์ด LGT8F328P สำหรับเป็น Actuator
  3. บอร์ดโมดูลขับมอเตอร์ไฟฟ้ากระแสตรง MX1508
  4. แหล่งจ่ายไฟแบบชาร์จได้
  5. หุ่นยนต์รถขับเคลื่อนด้วยมอเตอร์ไฟฟ้า 2 ตัว
    1. มอเตอร์ไฟฟ้ากระแสตรงสำหรับล้อซ้าย
    2. มอเตอร์ไฟฟ้ากระแสตรงสำหรับล้อขวา
    3. ชุดล้อสำหรับมอเตอร์ไฟฟ้าล้อซ้าย
    4. ชุดล้อสำหรับมอเตอร์ไฟฟ้าล้อขวา
    5. โครงยึดอุปกรณ์ตัวหุ่น

โค้ดโปรแกรม

การเชื่อมต่อขาของ LGT8F328P กับบอร์ดขับมอเตอร์ MX1508 เป็นดังนี้

  • D2 เข้ากับมอเตอร์ซ้ายขา 1
  • D3 ต่อเข้ากับมอเตอร์ซ้ายขา 2
  • D4 ต่อเข้ากับมอเตอร์ขวาขา 1
  • D5 ต่อเข้ากับมอเตอร์ขวาขา 2

นอกจากนี้ได้ต่อขา RX/TX ระหว่างบอร์ดไมโครคอนโทรลเลอร์ทั้ง 2 ตัวคือ esp8266 และ LGT8F328P เอาไว้ดังนี้

  • ขา Rx ของ esp8266 ต่อเข้ากับขา Tx ของ LGT8F328P
  • ขา Tx ของ esp8266 ต่อเข้ากับขา Rx ของ LGT8F328P

เพื่อความสะดวกในการสื่อสารกับบอร์ด esp8266 จึงเลือกใช้การสื่อสารอนุกรม โดยกำหนดโพรโทคอลการสื่อสารไว้ดังนี้

  • 0 แทนการสั่งให้หยุด
  • 1 แทนการเดินหน้า
  • 2 แทนการถอยหลัง
  • 3 แทนการเลี้ยวซ้าย
  • 4 แทนการเลี้ยวขวา

โค้ดโปรแกรมส่วนของ LGT8F328P เป็นดังนี้

#define MOTOR_L1  D2
#define MOTOR_L2  D3
#define MOTOR_R1  D4
#define MOTOR_R2  D5

class RobotAgent {
  private:
  public:
    RobotAgent() {
      // Actuator
      pinMode(MOTOR_L1, OUTPUT);
      pinMode(MOTOR_L2, OUTPUT);
      pinMode(MOTOR_R1, OUTPUT);
      pinMode(MOTOR_R2, OUTPUT);
    }
    ~RobotAgent() {

    }
    void stop() {
      digitalWrite( MOTOR_L1, LOW );
      digitalWrite( MOTOR_L2, LOW );
      digitalWrite( MOTOR_R1, LOW );
      digitalWrite( MOTOR_R2, LOW );
      delay(5);
      digitalWrite( MOTOR_L1, HIGH );
      digitalWrite( MOTOR_L2, HIGH );
      digitalWrite( MOTOR_R1, HIGH );
      digitalWrite( MOTOR_R2, HIGH );
      delay(100);
      digitalWrite( MOTOR_L1, LOW );
      digitalWrite( MOTOR_L2, LOW );
      digitalWrite( MOTOR_R1, LOW );
      digitalWrite( MOTOR_R2, LOW );
    }
    void forward() {
      digitalWrite( MOTOR_L1, LOW );
      digitalWrite( MOTOR_L2, HIGH );
      digitalWrite( MOTOR_R1, HIGH );
      digitalWrite( MOTOR_R2, LOW );
    }
    void left() {
      digitalWrite( MOTOR_L1, LOW );
      digitalWrite( MOTOR_L2, HIGH );
      digitalWrite( MOTOR_R1, LOW );
      digitalWrite( MOTOR_R2, HIGH );

    }
    void right() {

      digitalWrite( MOTOR_L1, HIGH );
      digitalWrite( MOTOR_L2, LOW );
      digitalWrite( MOTOR_R1, HIGH );
      digitalWrite( MOTOR_R2, LOW );
    }
    void backward() {

      digitalWrite( MOTOR_L1, HIGH );
      digitalWrite( MOTOR_L2, LOW );
      digitalWrite( MOTOR_R1, LOW );
      digitalWrite( MOTOR_R2, HIGH );
    }
};

RobotAgent car;
int cmd;

void setup() {
  Serial.begin(9600);
}

void loop() {
  if (Serial.available()) {
    cmd = (int)Serial.read() - '0';
    switch (cmd) {
      case 0:
        car.stop();
        break;
      case 1:
        car.forward();
        break;
      case 2:
        car.backward();
        break;
      case 3:
        car.left();
        break;
      case 4:
        car.right();
        break;
    }
  }
}

สำหรับ esp8266 ได้ปรับปรุงจากตัวอย่างก่อนหน้านี้ให้เหลือเพียงการส่งคำสั่งไปในระบบสื่อสารอนุกรมที่เชื่อมต่อกับบอร์ด LGT8F328P ดังนี้

#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>

#define AP_NAME "JarutEx"
#define AP_PASSWD "123456789"

IPAddress myIP(192, 168, 4, 1);
IPAddress gwIP(192, 168, 4, 10);
IPAddress subnet(255, 255, 255, 0);

int motionState = 0;
String html;

ESP8266WebServer server(80);


void setup() {
  Serial.begin(9600);
  delay(1000);
  html.reserve(2048);               // prevent ram fragmentation
  motionState = 0;
  if (WiFi.softAPConfig( myIP, gwIP, subnet )) {
    if (WiFi.softAP(AP_NAME, AP_PASSWD, 8, false, 5)) {
    } else {
      while (true);
    }
  } else {
    while (true) {
    }
  }
  server.on("/", htmlPage);
  server.on("/stop", robotStop);
  server.on("/forward", robotForward);
  server.on("/backward", robotBackward);
  server.on("/left", robotLeft);
  server.on("/right", robotRight);

  server.begin();
}

void robotStop() {
  motionState = 0;
  Serial.println(motionState);
  htmlPage();
}
void robotForward() {
  motionState = 1;
  Serial.println(motionState);
  htmlPage();
}
void robotBackward() {
  motionState = 2;
  Serial.println(motionState);
  htmlPage();
}
void robotLeft() {
  motionState = 3;
  Serial.println(motionState);
  htmlPage();
}
void robotRight() {
  motionState = 4;
  Serial.println(motionState);
  htmlPage();
}

void htmlPage() {
  html = F(
           "<!DOCTYPE HTML>"
           "<html><head>"
           "<meta name='viewport' content='width=device-width, initial-scale=1'>"
           "<style>"

           ".button {  border: none;  color: white;  padding: 20px;  text-align: center;  text-decoration: none;"
           "  display: inline-block;  font-size: 14"
           "  px;  margin: 4px 2px;  cursor: pointer;  border-radius: 4%;"
           "  width: 100%; height: 100%;"
           "}"
           ".button1 {  background-color: #3ABC40; }"
           ".button2 {  background-color: #BC4040; }"
           "</style></head><body><table>"
         );
  if (motionState == 0) {
    html += F(
              "<tr>"
              "<td></td>"
              "<td><a href='/forward'><button class='button button2'>Forward</button></a></td>"
              "<td></td>"
              "</tr>"
              "<tr>"
              "<td><a href='/left'><button class='button button2'>Turn Left</button></a></td>"
              "<td><a href='/stop'><button  class='button button1'>Stop</button></a></td>"
              "<td><a href='/right'><button class='button button2'>Turn Right</button></a></td>"
              "</tr>"
              "<tr>"
              "<td></td>"
              "<td><a href='/backward'><button class='button button2'>Backward</button></a></td>"
              "<td></td>"
              "</tr>"
            );
  }
  else if (motionState == 1) {
    html += F(
              "<tr>"
              "<td></td>"
              "<td><a href='/forward'><button class='button button1'>Forward</button></a></td>"
              "<td></td>"
              "</tr>"
              "<tr>"
              "<td><a href='/left'><button class='button button2'>Turn Left</button></a></td>"
              "<td><a href='/stop'><button  class='button button2'>Stop</button></a></td>"
              "<td><a href='/right'><button class='button button2'>Turn Right</button></a></td>"
              "</tr>"
              "<tr>"
              "<td></td>"
              "<td><a href='/backward'><button class='button button2'>Backward</button></a></td>"
              "<td></td>"
              "</tr>"
            );

  }
  else if (motionState == 2) {
    html += F(
              "<tr>"
              "<td></td>"
              "<td><a href='/forward'><button class='button button2'>Forward</button></a></td>"
              "<td></td>"
              "</tr>"
              "<tr>"
              "<td><a href='/left'><button class='button button2'>Turn Left</button></a></td>"
              "<td><a href='/stop'><button  class='button button2'>Stop</button></a></td>"
              "<td><a href='/right'><button class='button button2'>Turn Right</button></a></td>"
              "</tr>"
              "<tr>"
              "<td></td>"
              "<td><a href='/backward'><button class='button button1'>Backward</button></a></td>"
              "<td></td>"
              "</tr>"
            );

  }
  else if (motionState == 3) {
    html += F(
              "<tr>"
              "<td></td>"
              "<td><a href='/forward'><button class='button button2'>Forward</button></a></td>"
              "<td></td>"
              "</tr>"
              "<tr>"
              "<td><a href='/left'><button class='button button1'>Turn Left</button></a></td>"
              "<td><a href='/stop'><button  class='button button2'>Stop</button></a></td>"
              "<td><a href='/right'><button class='button button2'>Turn Right</button></a></td>"
              "</tr>"
              "<tr>"
              "<td></td>"
              "<td><a href='/backward'><button class='button button2'>Backward</button></a></td>"
              "<td></td>"
              "</tr>"
            );

  }
  else if (motionState == 4) {
    html += F(
              "<tr>"
              "<td></td>"
              "<td><a href='/forward'><button class='button button2'>Forward</button></a></td>"
              "<td></td>"
              "</tr>"
              "<tr>"
              "<td><a href='/left'><button class='button button2'>Turn Left</button></a></td>"
              "<td><a href='/stop'><button  class='button button2'>Stop</button></a></td>"
              "<td><a href='/right'><button class='button button1'>Turn Right</button></a></td>"
              "</tr>"
              "<tr>"
              "<td></td>"
              "<td><a href='/backward'><button class='button button2'>Backward</button></a></td>"
              "<td></td>"
              "</tr>"
            );

  }
  html += F("</table></body></html>\r\n");
  server.send(200, "text/html", html);
}

void loop() {
  server.handleClient();
}

สรุป

จากบทความนี้จะพบว่า เราสามารถแก้ไขปัญหากรณีที่บอร์ด esp8266 มีจำนวนขาสำหรับควบคุมระบบไม่เพียงพอด้วยการใช้บอร์ด LGT8F328P มาเป็นตัวทำหน้าที่สั่งงานหรืออ่านค่ากับอุปกรณ์ภายนอก และออกแบบการสั่งงานผ่านชุดคำสั่งที่กำหนดเองสำหรับส่งและรับผ่านการสื่อสารอนุกรม สุดท้าย ขอให้สนุกกับการเขียนโปรแกรมครับ

(C) 2022, โดย อ.ดนัย เจษฎาฐิติกุล/อ.จารุต บุศราทิจ
ปรับปรุงเมื่อ 2022-01-03, 2022-02-15

ขอบคุณ รศ.ดร.เที่ยง เหมียดไธสง และ ผศ.ศิวาพร เหมียดไธสง ที่สนับสนุนอุปกรณ์การทดลองครับ