กลับไปหน้ารวมไฟล์
cutsie-whun-version-2-the-ultimate-balancing-robot-7cea57.md

ภาพรวม

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

ความแตกต่างของสัญญาณ PWM: ระหว่างตัวรับ RC กับ Arduino

  • สัญญาณ PWM จากตัวรับ RC จะส่งออกมาเป็นพัลส์หน่วยไมโครวินาที ระหว่างค่าต่ำสุด 1000 ถึงค่าสูงสุด 2000
  • สัญญาณ PWM สำหรับโมดูลมอเตอร์ H-Bridge ของ Arduino เป็นพัลส์กำลัง ระหว่างค่าต่ำสุด 0 ถึงค่าสูงสุด 255
  • เราใช้การขัดจังหวะ (Interrupts) ในการเก็บค่าสัญญาณจากตัวรับ RC นี่คือวิธีจับเวลาความยาวของพัลส์แล้วแปลงเป็นค่าที่ Arduino เอาไปใช้งานได้
  • การคำนวณแปลงค่าระหว่างสัญญาณสองแบบนี้เร็วและง่ายมาก จัดไปวัยรุ่น!

มุมมองของโปรเจกต์

โปรเจกต์นี้เป็นการสำรวจโลกของหุ่นยนต์และทฤษฎีการควบคุมแบบลงลึกเลยนะ ใช้ Arduino ร่วมกับ MPU6050 Inertial Measurement Unit (IMU) เราก็สามารถสร้างหุ่นยนต์สองล้อที่ตั้งตรงได้เอง เหมือน Segway เลยแหละ โปรเจกต์นี้เป็นสนามทดลองชั้นดีสำหรับการเรียนรู้เรื่อง PID control และการรวมข้อมูลเซนเซอร์แบบเรียลไทม์

การลงมือทำ: ศิลปะแห่งการทรงตัว

ความสามารถในการทรงตัวของหุ่นยนต์อาศัยลูปตอบสนองความเร็วสูง:

  • ชั้นการรับรู้: MPU6050 วัดมุมเอียง (pitch) ของหุ่นยนต์หลายร้อยครั้งต่อวินาที
  • ชั้นควบคุม: Arduino รันอัลกอริทึม PID (Proportional-Integral-Derivative) เพื่อคำนวณการตอบสนองของมอเตอร์ที่จำเป็นเพื่อต้านการเอียง
  • ชั้นตอบสนอง: มอเตอร์ DC แรงบิดสูง เร่งความเร็ปล้อในทิศทางที่หุ่นยนต์กำลังล้ม เพื่อย้ายจุดศูนย์ถ่วงกลับมาอยู่ใต้ตัวหุ่นยนต์

โครงสร้างพื้นฐานฮาร์ดแวร์

  • Arduino Uno: ตัวควบคุมหลัก จัดการลูป PID และประสานการเคลื่อนที่ของมอเตอร์
  • MPU6050 IMU: คือ "หูชั้นใน" ของหุ่นยนต์ ให้ข้อมูลการเคลื่อนไหว 6 แกนสำหรับคำนวณมุมที่แม่นยำ
  • มอเตอร์แรงบิดสูงพร้อมเอ็นโค้ดเดอร์: เอ็นโค้ดเดอร์สำคัญมากสำหรับวัดการหมุนของล้อ และทำให้แน่ใจว่ามอเตอร์หมุนด้วยความเร็วที่ PID controller ต้องการเป๊ะๆ
  • มอเตอร์ไดรเวอร์: แปลงสัญญาณดิจิทัลจาก Arduino เป็นพลังงานกระแสสูงสำหรับขับมอเตอร์
  • แบตเตอรี่ Li-ion/Lipo: จ่ายกระแสคายประจุสูงที่จำเป็นสำหรับการเคลื่อนไหวของมอเตอร์ที่รวดเร็ว

ตรรกะซอฟต์แวร์ & การปรับค่า PID

โค้ด Arduino คือหัวใจของความมั่นคงของหุ่นยนต์:

  1. การรวมข้อมูลเซนเซอร์: รวมข้อมูลจาก accelerometer และ gyroscope โดยใช้ Complementary หรือ Kalman filter เพื่อให้ได้มุมเอียงที่เสถียรและลดสัญญาณรบกวน
  2. ลูป PID: ฟังก์ชัน PID.calculate(targetAngle, currentAngle) จะสร้างค่าสัญญาณ PWM ออกมาให้มอเตอร์ไดรเวอร์
  3. การปรับแต่ง: การหาค่า Kp (Proportional), Ki (Integral), และ Kd (Derivative) ที่เหมาะสม เป็นกระบวนการลองผิดลองถูกที่กำหนดว่าหุ่นยนต์จะทรงตัวแบบดุดันหรือนุ่มนวลแค่ไหน สู้งานนะน้อง!

ตัวอย่าง

ด้านล่างคือตัวอย่างผลลัพธ์จากหน้าต่าง Serial Monitor นะ

Cycle #89
Clear distance to front:	0-In	0-Cm
	    Temperature:	21-C	71-F
   System Arming State:	Disarmed
    Transmission State: 	Neutral
	     Turn State:	Going Straight
	     Spin State:	Not Spinning
        Throttle State: 	0%
 Right Stick Horizontal:	Remote CH1	1496-126
   Right Stick Verticle:	Remote CH2	1512-130
  Left Stick Horizontal:	Remote CH4	1444-113
    Left Stick Verticle:	Remote CH3	1000-0
   Arming switch signal:	Remote CH5	1992-252
  Mode Dial(Delay time):	Remote CH6	1992-252
Gyro Motion-Acceleration:	AX-4148		AY-448		AZ17456
   Gyro Motion-Rotation:	GX-1034		GY361		GZ-105
      Gyro Acceleration:	AX-4116		AY-568		AZ17408
          Gyro Rotation:	GX-1023		GY363		GZ-53

โค้ดมันยาวหน่อยนะ อาจต้องใช้เวลามากกว่าสองสามนาทีในการทำความเข้าใจ (ก็แล้วแต่สกิลของแต่ละคนแหละ) ห้ามช็อตนะตัวนี้!

ด้านหลังจ้า

Mini Pro 5v กับ Gyro 6050

ตัวเรกูเลเตอร์ 5v กับบอร์ด H-Bridge สำหรับมอเตอร์

หน้าที่ของตัวรับสัญญาณรีโมท (RC Receiver) มีตามนี้เลยน้อง:

  • CH1 - เลี้ยวซ้าย/ขวา
  • CH2 - เกียร์เดินหน้า/ว่าง/ถอยหลัง
  • CH3 - คันเร่ง (Throttle)
  • CH4 - หมุนตัวซ้าย/ขวา (ใช้ได้ตอนเกียร์ว่างเท่านั้นนะ ระวังหักเพลานะตัวนี้!)
  • CH5 - สวิตช์ปลดอาวุธ/ติดอาวุธ (Arming/Disarming)
  • CH6 - ปรับความหน่วงของระบบ (เพิ่ม/ลด delay ในลูปหลัก)

ต่อยอดในอนาคตได้อีก (Future Expansion)

  • รีโมทแบบใหม่: ใส่บลูทูธหรือ WiFi (HC-05/ESP8266) ไปขับหุ่นยนต์สมดุลจากมือถือได้เลย จัดไปวัยรุ่น!
  • หลบหลีกอัตโนมัติ: ต่อเซ็นเซอร์อัลตราโซนิก (Ultrasonic) ให้มันหลบผนังกับเฟอร์นิเจอร์เอง
  • เดินทางเองได้: ใช้ GPS หรือ SLAM (Simultaneous Localization and Mapping) สำหรับการเดินทางที่ซับซ้อนขึ้น
  • ลอจิกแบบฟัซซี่ (Fuzzy Logic): เอามาปรับแต่ง PID Controller ให้สมดุลได้ดียิ่งขึ้นในสภาวะต่างๆ

โปรเจคนี้คือการผสมผสานระหว่าง วิศวกรรมเครื่องกล, อิเล็กทรอนิกส์, และอัลกอริทึมทฤษฎีการควบคุม อย่างลงตัวเลย สู้งานนะน้อง!

วิดีโอ

วิดีโออธิบายระบบพื้นฐาน

โค้ดตอนนี้ยาวประมาณ 440 บรรทัดแล้ววว ห้ามช็อตนะตัวนี้!

#include <EnableInterrupt.h>
#include <NewPing.h>
#include "I2Cdev.h"
#include "MPU6050.h"
#include "Wire.h"
#define SERIAL_PORT_SPEED 57600
#define TRIGGER_PIN  3
#define ECHO_PIN     4
#define MAX_DISTANCE 500
NewPing sonar(TRIGGER_PIN, ECHO_PIN, MAX_DISTANCE);
#define OUTPUT_READABLE_ACCELGYRO
MPU6050 accelgyro;
#define heartbeat_pin 7
int heartbeatDelay=10;
int delayTime=0;
int count = 0;
int delayMultiplier=2;
int16_t ax, ay, az;
int16_t gx, gy, gz;
int16_t ax1, ay1, az1;
int16_t gx1, gy1, gz1;
int x, x1;
int y, y1;
int z, z1;
int temp=0;
int tempC=0;
int tempF=0;
#define RC_NUM_CHANNELS  6
#define RC_CH1  0
#define RC_CH2  1
#define RC_CH3  2
#define RC_CH4  3
#define RC_CH5  4
#define RC_CH6  5
#define RC_CH1_INPUT  A0
#define RC_CH2_INPUT  A1
#define RC_CH3_INPUT  A2
#define RC_CH4_INPUT  A3
#define RC_CH5_INPUT  8
#define RC_CH6_INPUT  9
uint16_t rc_values[RC_NUM_CHANNELS];
uint32_t rc_start[RC_NUM_CHANNELS];
volatile uint16_t rc_shared[RC_NUM_CHANNELS];
bool left_turn = false;
bool right_turn = false;
bool left_spin = false;
bool right_spin = false;
bool not_spinning=true;
bool straight = true;
bool reverse = false;
bool forward = false;
bool brakes = false;
bool neutral = true;
bool armed = false;
int rc_input_Deadzone = 20;
int rc_input_MIN = 1000;
int rc_input_MAX = 2000;
int pwm_MIN = 0;
int pwm_MAX = 255;
int pwm_ch1 = 0;
int pwm_ch2 = 0;
int pwm_ch3 = 0;
int pwm_ch4 = 0;
int pwm_ch5 = 0;
int pwm_ch6 = 0;
int arming_MIN = 20;
int arming_MAX = 230;
int forward_AT = 150;
int reverse_AT = 90;
int left_AT = 150;
int right_AT = 90;
int throttle=0;
int LF_motor_pin = 5;
int LR_motor_pin = 6;
int RF_motor_pin = 10;
int RR_motor_pin = 11;
int L_motor_speed = 0;
int R_motor_speed = 0;
int outMax = 255;
int outMin = 0;
float lastInput = 0;
double ITerm = 0;
double kp = 2;         // Proportional value
double ki = 0;           // Integral value
double kd = 0;           // Derivative value

double Setpoint = 0; // ตั้งค่าเริ่มต้น (Setpoint) เป็น 0 ไปก่อนนะน้อง double Compute(double input) { double error = Setpoint - input; ITerm += (ki * error); if (ITerm > outMax) ITerm = outMax; else if (ITerm < outMin) ITerm = outMin; double dInput = (input - lastInput); // คำนวณค่าออกมาเลยจ้า PID Output double output = kp * error + ITerm + kd * dInput; if (output > outMax) output = outMax; else if (output < outMin) output = outMin; // จำค่าตัวแปรบางตัวไว้ใช้รอบหน้า อย่าลืมล่ะ lastInput = input; return output; } void SetSetpoint(double d) { Setpoint = d; } double GetSetPoint() { return Setpoint; } void rc_read_values() { noInterrupts(); memcpy(rc_values, (const void *)rc_shared, sizeof(rc_shared)); interrupts(); } void calc_input(uint8_t channel, uint8_t input_pin) { if (digitalRead(input_pin) == HIGH) { rc_start[channel] = micros(); } else { uint16_t rc_compare = (uint16_t)(micros() - rc_start[channel]); rc_shared[channel] = rc_compare; } } void calc_ch1() { calc_input(RC_CH1, RC_CH1_INPUT); if (rc_values[RC_CH1] < rc_input_MIN) { rc_values[RC_CH1] = rc_input_MIN; } if (rc_values[RC_CH1] > rc_input_MAX) { rc_values[RC_CH1] = rc_input_MAX; } } void calc_ch2() { calc_input(RC_CH2, RC_CH2_INPUT); if (rc_values[RC_CH2] < rc_input_MIN) { rc_values[RC_CH2] = rc_input_MIN; } if (rc_values[RC_CH2] > rc_input_MAX) { rc_values[RC_CH2] = rc_input_MAX; } } void calc_ch3() { calc_input(RC_CH3, RC_CH3_INPUT); if (rc_values[RC_CH3] < rc_input_MIN) { rc_values[RC_CH3] = rc_input_MIN; } if (rc_values[RC_CH3] > rc_input_MAX) { rc_values[RC_CH3] = rc_input_MAX; } } void calc_ch4() { calc_input(RC_CH4, RC_CH4_INPUT); if (rc_values[RC_CH4] < rc_input_MIN) { rc_values[RC_CH4] = rc_input_MIN; } if (rc_values[RC_CH4] > rc_input_MAX) { rc_values[RC_CH4] = rc_input_MAX; } } void calc_ch5() { calc_input(RC_CH5, RC_CH5_INPUT); if (rc_values[RC_CH5] < rc_input_MIN) { rc_values[RC_CH5] = rc_input_MIN; } if (rc_values[RC_CH5] > rc_input_MAX) { rc_values[RC_CH5] = rc_input_MAX; } } void calc_ch6() { calc_input(RC_CH6, RC_CH6_INPUT); if (rc_values[RC_CH6] < rc_input_MIN) { rc_values[RC_CH6] = rc_input_MIN; } if (rc_values[RC_CH6] > rc_input_MAX) { rc_values[RC_CH6] = rc_input_MAX; } } void print_rc_values() { Serial.print(" Right Stick Horizontal:\t"); Serial.print("Remote CH1\t"); Serial.print(rc_values[RC_CH1]); Serial.print("-"); Serial.println(pwm_ch1); Serial.print(" Right Stick Verticle:\t"); Serial.print("Remote CH2\t"); Serial.print(rc_values[RC_CH2]); Serial.print("-"); Serial.println(pwm_ch2); Serial.print(" Left Stick Horizontal:\t"); Serial.print("Remote CH4\t"); Serial.print(rc_values[RC_CH4]); Serial.print("-"); Serial.println(pwm_ch4); Serial.print(" Left Stick Verticle:\t"); Serial.print("Remote CH3\t"); Serial.print(rc_values[RC_CH3]); Serial.print("-"); Serial.println(pwm_ch3); Serial.print(" Arming switch signal:\t"); Serial.print("Remote CH5\t"); Serial.print(rc_values[RC_CH5]); Serial.print("-"); Serial.println(pwm_ch5); Serial.print(" Mode Dial(Delay time):\t"); Serial.print("Remote CH6\t"); Serial.print(rc_values[RC_CH6]); Serial.print("-"); Serial.println(pwm_ch6); } void set_rc_pwm() { pwm_ch1 = map(rc_values[RC_CH1], rc_input_MIN, rc_input_MAX, pwm_MIN, pwm_MAX); if (pwm_ch1 < pwm_MIN) { pwm_ch1 = pwm_MIN; } if (pwm_ch1 > pwm_MAX) { pwm_ch1 = pwm_MAX; } pwm_ch2 = map(rc_values[RC_CH2], rc_input_MIN, rc_input_MAX, pwm_MIN, pwm_MAX); if (pwm_ch2 < pwm_MIN) { pwm_ch2 = pwm_MIN; } if (pwm_ch2 > pwm_MAX) { pwm_ch2 = pwm_MAX; } pwm_ch3 = map(rc_values[RC_CH3], rc_input_MIN, rc_input_MAX, pwm_MIN, pwm_MAX); if (pwm_ch3 < pwm_MIN) { pwm_ch3 = pwm_MIN; } if (pwm_ch3 > pwm_MAX) { pwm_ch3 = pwm_MAX; } pwm_ch4 = map(rc_values[RC_CH4], rc_input_MIN, rc_input_MAX, pwm_MIN, pwm_MAX); if (pwm_ch4 < pwm_MIN) { pwm_ch4 = pwm_MIN; } if (pwm_ch4 > pwm_MAX) { pwm_ch4 = pwm_MAX; } }

pwm_ch5 = map(rc_values[RC_CH5], rc_input_MIN, rc_input_MAX, pwm_MIN, pwm_MAX); if (pwm_ch5 < pwm_MIN) { pwm_ch5 = pwm_MIN; } if (pwm_ch5 > pwm_MAX) { pwm_ch5 = pwm_MAX; } pwm_ch6 = map(rc_values[RC_CH6], rc_input_MIN, rc_input_MAX, pwm_MIN, pwm_MAX); if (pwm_ch6 < pwm_MIN) { pwm_ch6 = pwm_MIN; } if (pwm_ch6 > pwm_MAX) { pwm_ch6 = pwm_MAX; } } void Ping() { Serial.print("Clear distance to front:\t"); Serial.print(sonar.ping_in()); Serial.print("-In\t"); Serial.print(sonar.ping_cm()); Serial.println("-Cm"); } void delay_time(){ delayTime=pwm_ch6*delayMultiplier; delay(delayTime); } void arming_state() { Serial.print(" System Arming State:\t"); if (pwm_ch5 <= arming_MIN) { armed = true; Serial.println("Armed"); } else if (pwm_ch5 >= arming_MAX) { armed = false; Serial.println("Disarmed"); } } void spin_state(){ Serial.print("\t Spin State:\t"); if (pwm_ch4 > left_AT) { left_spin = true; right_spin = false; not_spinning = false; Serial.println("Spinning Left"); } if (pwm_ch4 &

ข้อมูล Frontmatter ดั้งเดิม

apps:
  - "1x Arduino IDE"
  - "1x PID Library (Arduino)"
  - "1x MPU6050 Library (Jeff Rowberg)"
author: "shassandanish1"
category: "Sensors & Environment"
components:
  - "1x Hot glue gun (generic)"
  - "1x ZX Distance and Gesture Sensor"
  - "1x SparkFun Motor Driver - Dual TB6612FNG (1A)"
  - "1x Voltage Regulator Module"
  - "1x SparkFun Triple Axis Accelerometer and Gyro Breakout - MPU-6050"
  - "1x Arduino Pro Mini 328 - 5V/16MHz"
  - "1x Soldering iron (generic)"
description: "นี่คือโปรเจกต์ #6 จากซีรีส์ MEGA BREAD ที่ตึงมาก! Cutsie Whun คือหุ่นยนต์ 2 ล้อทรงตัวสุดซิ่ง ควบคุมด้วยรีโมต ความเร็วจัดเต็ม ผ่านการออกแบบและเขียนโค้ดมาอย่างหนัก รับรองว่าผลลัพธ์ออกมาเทพๆ แน่นอน!"
difficulty: "Intermediate"
documentationLinks: []
downloadableFiles: []
encryptedPayload: "U2FsdGVkX1/VlYYI03D/RW7G1JMZqBtsMkxwIKsfr3jbo5NRTftDnoBOI4QZAtSzUy/oRHdVxYnvfcVFsOdZjPr/W/Qa+ar8ZJE6Epahuko="
heroImage: "https://cdn.jsdelivr.net/gh/bigboxthailand/arduino-assets@main/images/projects/cutsie-whun-version-2-the-ultimate-balancing-robot-7cea57_cover.JPG"
lang: "en"
likes: 7
passwordHash: "9eaa0fd259bb7241ef320eb748b6f3321a52bc56b3a77bcae437c7b8430359be"
price: 2450
seoDescription: "Explore CUTSIE WHUN Version 2, a fast and tough 2 wheeled balancing robot controlled via RC. Part of the MEGA BREAD series project #6."
tags:
  - "robot"
  - "balancing"
  - "cutsie-whun"
  - "arduino"
  - "advanced"
title: "CUTSIE WHUN รุ่น 2 - หุ่นยนต์ทรงตัวสุดเท่ งานง่ายแต่หล่อ!"
tools: []
videoLinks:
  - "https://www.youtube.com/embed/TLqGr5-tsg0"
views: 6369