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