กลับไปหน้ารวมไฟล์
hardware-integration-of-rtpt-72cd7a.md

บทความหลัก: RTPT ใช้พิกัด GPS และเซอร์โวความแม่นยำสูงเพื่อชี้เลเซอร์ไปยังดาวเคราะห์ดวงใดก็ได้ในระบบสุริยะ มันเป็นตัวอย่างที่เจ๋งมากว่าอาร์ดูโน่เมก้าจัดการกับภาระงานหนักของเทเลเมทรีทางดาราศาสตร์ได้ยังไง

การนำทางบนฟากฟ้า: วิสัยทัศน์ของ RTPT

Real-Time Planet Tracker (RTPT) คือการผสานฮาร์ดแวร์-ซอฟต์แวร์ระดับสูง ที่ออกแบบมาเพื่อเชื่อมช่องว่างระหว่างพิกัดคณิตศาสตร์ที่ดูนามธรรม กับความเป็นจริงทางกายภาพ ในขณะที่แอปท้องฟ้าจำลองส่วนใหญ่แค่แสดงให้เห็นว่าดาวอังคารหรือดาวศุกร์ อยู่ที่ไหน บนหน้าจอ แต่ RTPT ใช้ กลไกเลเซอร์ Pan-Tilt อัตโนมัติ เพื่อชี้ไปยังดาวเคราะห์เหล่านั้นบนท้องฟ้ายามค่ำคืนจริงๆ (หรือบนเพดานสำหรับใช้สอนก็ได้) ด้วยการผสมผสานความแม่นยำของ GPS กับเซอร์โวขั้นสูง โปรเจกต์นี้จึงสร้าง "หอดูดาวส่วนตัว" ที่ปรับตามตำแหน่งที่แน่นอนของเราบนโลก

ของที่ต้องเตรียม:

  • Arduino Mega (หรือ MCU ที่มีแฟลชมากกว่า 33Kbyte) สำคัญมาก เพราะโค้ดสุดท้ายของพี่อยู่ที่ประมาณ 33KB และ ใส่ใน UNO ไม่ลงแน่นอน
  • U-BLOX GPS Receiver (NEO-6M) หรือโมดูล GPS อะไรก็ได้
  • MPU9250 (ไจโร-แอคเซลเลโร-แมกเนโต 9 แกน) สำหรับปรับค่าการหัน (Yaw) อัตโนมัติ (ใส่ก็ได้ ไม่ใส่ก็ได้)
  • กลไก Pan-tilt พร้อมเซอร์โว (เซอร์โว pan ควรเป็นแบบหมุนได้ 3.5 รอบ ส่วนเซอร์โว tilt เป็นเซอร์โวธรรมดา 180 องศาก็ได้)
  • เลเซอร์พอยน์เตอร์ – เอาไว้ติดกับเซอร์โว tilt เพื่อแสดงตำแหน่งดาวในห้องปิด

สมองของระบบ: ทำไมต้อง Arduino Mega?

หลุมพรางที่พบบ่อยในโปรเจกต์เทเลเมทรีขั้นสูงคือการใช้งานเกินขีดจำกัดหน่วยความจำของ Arduino Uno โปรเจกต์นี้ใช้ Arduino Mega 2560 อย่างชัดเจนเพราะไฟล์ไบนารีสุดท้ายใหญ่กว่า 33KB แฟลชเมมโมรีที่เยอะขึ้นเป็นสิ่งจำเป็นสำหรับ:

  1. การประมวลผล TinyGPS++: การประมวลผลประโยค NMEA จาก U-BLOX NEO-6M ต้องการการจัดการสตริงและการจัดการบัฟเฟอร์ที่ค่อนข้างหนัก
  2. การคำนวณ Ephemeris: การคำนวณ Right Ascension และ Declination ที่แม่นยำของดาวเคราะห์เกี่ยวข้องกับคณิตศาสตร์ทศนิยมที่ซับซ้อนและไลบรารีทางคณิตศาสตร์ที่กินหน่วยความจำเร็วมาก
  3. การทำงานหลายอย่างผ่าน Serial พร้อมกัน: พอร์ตซีเรียลฮาร์ดแวร์หลายพอร์ตของ Mega (ใช้ Serial2 สำหรับ GPS) ทำให้การติดตามราบรื่นโดยไม่มีแล็กที่มักมากับ SoftwareSerial

เริ่มต้นกับ GPS

การใช้ GPS นั้นง่ายมากถ้าเราใช้ [TinyGPS++ Libraries] GPS ใช้การสื่อสารแบบ Serial พี่เลยต่อมันเข้ากับ Serial2 ของ Mega ซึ่งก็คือพิน 16 และพิน 17

หลังจากต่อเสร็จแล้ว ให้อัปโหลดโค้ดทดสอบจากไลบรารี่ชื่อ "FullExample" ไลบรารี่นี้ใช้ Software serial อย่าลืมเปลี่ยนมันเป็น Serial2 - และเปลี่ยน GPSBaud เป็น 9600 ด้วย นี่คือโค้ดที่พี่ปรับแล้ว ต้องเข้าใจโค้ดนี้ให้ดีนะ เพราะเราจะใช้แค่บางบรรทัดและฟังก์ชันที่ สำคัญ (ไม่ใช่ทั้งหมด) จากโค้ดนี้ (เช่น ฟังก์ชัน SmartDelay() เพื่ออัปเดตค่าจาก GPS):

#include <TinyGPS++.h>
/*
This sample code demonstrates the normal use of a TinyGPS++ (TinyGPSPlus) object.
It requires the use of SoftwareSerial, and aSerial2umes that you have a
4800-baud serial GPS device hooked up on pins 4(rx) and 3(tx).
*/
static const uint32_t GPSBaud = 9600;
// The TinyGPS++ object
TinyGPSPlus gps;
void setup()
{
Serial.begin(115200);
Serial2.begin(GPSBaud);
Serial.println(F(“FullExample.ino”));
Serial.println(F(“An extensive example of many interesting TinyGPS++ features”));
Serial.print(F(“Testing TinyGPS++ library v. “)); Serial.println(TinyGPSPlus::libraryVersion());
Serial.println(F(“by Mikal Hart”));
Serial.println();
Serial.println(F(“Sats HDOP Latitude   Longitude   Fix  Date       Time     Date Alt    Course Speed Card  Distance Course Card  Chars Sentences Checksum”));
Serial.println(F(”          (deg)      (deg)       Age                      Age  (m)    — from GPS —-  —- to London  —-  RX    RX        Fail”));
Serial.println(F(“—————————————————————————————————————————————“));
}
void loop()

{ static const double LONDON_LAT = 51.508131, LONDON_LON = -0.128002; printInt(gps.satellites.value(), gps.satellites.isValid(), 5); printInt(gps.hdop.value(), gps.hdop.isValid(), 5); printFloat(gps.location.lat(), gps.location.isValid(), 11, 6); printFloat(gps.location.lng(), gps.location.isValid(), 12, 6); printInt(gps.location.age(), gps.location.isValid(), 5); printDateTime(gps.date, gps.time); printFloat(gps.altitude.meters(), gps.altitude.isValid(), 7, 2); printFloat(gps.course.deg(), gps.course.isValid(), 7, 2); printFloat(gps.speed.kmph(), gps.speed.isValid(), 6, 2); printStr(gps.course.isValid() ? TinyGPSPlus::cardinal(gps.course.value()) : “*** “, 6); unsigned long distanceKmToLondon = (unsigned long)TinyGPSPlus::distanceBetween( gps.location.lat(), gps.location.lng(), LONDON_LAT, LONDON_LON) / 1000; printInt(distanceKmToLondon, gps.location.isValid(), 9); double courseToLondon = TinyGPSPlus::courseTo( gps.location.lat(), gps.location.lng(), LONDON_LAT, LONDON_LON); printFloat(courseToLondon, gps.location.isValid(), 7, 2); const char cardinalToLondon = TinyGPSPlus::cardinal(courseToLondon); printStr(gps.location.isValid() ? cardinalToLondon : “** “, 6); printInt(gps.charsProcessed(), true, 6); printInt(gps.sentencesWithFix(), true, 10); printInt(gps.failedChecksum(), true, 9); Serial.println(); smartDelay(1000); if (millis() > 5000 && gps.charsProcessed() < 10) Serial.println(F(“No GPS data received: check wiring”)); } // ฟังก์ชัน delay แบบพิเศษของเรา ตัวนี้จะคอยป้อนข้อมูลให้ GPS object อยู่ตลอดเวลา static void smartDelay(unsigned long ms) { unsigned long start = millis(); do { while (Serial2.available()) gps.encode(Serial2.read()); } while (millis() – start < ms); } static void printFloat(float val, bool valid, int len, int prec) { if (!valid) { while (len– > 1) Serial.print(‘’); Serial.print(‘ ‘); } else { Serial.print(val, prec); int vi = abs((int)val); int flen = prec + (val < 0.0 ? 2 : 1); // . and – flen += vi >= 1000 ? 4 : vi >= 100 ? 3 : vi >= 10 ? 2 : 1; for (int i=flen; i<len; ++i) Serial.print(‘ ‘); } smartDelay(0); } static void printInt(unsigned long val, bool valid, int len) { char sz[32] = “*****************”; if (valid) sprintf(sz, “%ld”, val); sz[len] = 0; for (int i=strlen(sz); i<len; ++i) sz[i] = ‘ ‘; if (len > 0) sz[len-1] = ‘ ‘; Serial.print(sz); smartDelay(0); } static void printDateTime(TinyGPSDate &d, TinyGPSTime &t) { if (!d.isValid()) { Serial.print(F(“********* “)); } else { char sz[32]; sprintf(sz, “%02d/%02d/%02d “, d.month(), d.day(), d.year()); Serial.print(sz); } if (!t.isValid()) { Serial.print(F(“******** “)); } else { char sz[32]; sprintf(sz, “%02d:%02d:%02d “, t.hour(), t.minute(), t.second()); Serial.print(sz); } printInt(d.age(), d.isValid(), 5); smartDelay(0); } static void printStr(const char *str, int len) { int slen = strlen(str); for (int i=0; i<len; ++i) Serial.print(i smartDelay(0); }


เราก็จะหยิบโค้ดบางส่วนจากตัวอย่าง "Deviceexample" ในไลบรารี TinyGPS++ มาใช้ด้วยนะ นี่คือโค้ดหลักเลย:

```cpp
#include <TinyGPS++.h>
/*
โค้ดตัวอย่างนี้จะแสดงการใช้งานปกติของ object TinyGPS++ (TinyGPSPlus)
มันต้องใช้ SoftwareSerial และสมมติว่าคุณต่ออุปกรณ์ GPS แบบ serial 4800-baud ไว้ที่ขา 4(rx) และ 3(tx) แล้วนะ
*/
static const uint32_t GPSBaud = 9600;
// Object TinyGPS++ ของเรา
TinyGPSPlus gps;
void setup()
{
Serial.begin(115200);
Serial2.begin(GPSBaud);
Serial.println(F(“DeviceExample.ino”));
Serial.println(F(“A simple demonstration of TinyGPS++ with an attached GPS module”));
Serial.print(F(“Testing TinyGPS++ library v. “)); Serial.println(TinyGPSPlus::libraryVersion());
Serial.println(F(“by Mikal Hart”));
Serial.println();
}
void loop()
{
// สเก็ตช์นี้จะแสดงข้อมูลทุกครั้งที่มีประโยคใหม่ถูกเข้ารหัสถูกต้อง
while (Serial2.available() > 0)
if (gps.encode(Serial2.read()))
displayInfo();
if (millis() > 5000 && gps.charsProcessed() < 10)
{
Serial.println(F(“No GPS detected: check wiring.”));
while(true);
}
}
void displayInfo()
{
Serial.print(F(“Location: “));
if (gps.location.isValid())
{
Serial.print(gps.location.lat(), 6);
Serial.print(F(“,”));
Serial.print(gps.location.lng(), 6);
}
else
{
Serial.print(F(“INVALID”));
}
Serial.print(F(”  Date/Time: “));
if (gps.date.isValid())
{
Serial.print(gps.date.month());
Serial.print(F(“/”));
Serial.print(gps.date.day());
Serial.print(F(“/”));
Serial.print(gps.date.year());
}
else
{
Serial.print(F(“INVALID”));
}
Serial.print(F(” “));
if (gps.time.isValid())
{
if (gps.time.hour() < 10) Serial.print(F(“0”));
Serial.print(gps.time.hour());
Serial.print(F(“:”));
if (gps.time.minute() < 10) Serial.print(F(“0”));
Serial.print(gps.time.minute());
Serial.print(F(“:”));
if (gps.time.second() < 10) Serial.print(F(“0”));
Serial.print(gps.time.second());
Serial.print(F(“.”));
if (gps.time.centisecond() < 10) Serial.print(F(“0”));
Serial.print(gps.time.centisecond());
}
else
{
Serial.print(F(“INVALID”));
}
Serial.println();
}

เหตุผลที่พี่ให้ดูโค้ดพวกนี้ก็เพราะว่า เราจะใช้ SmartDelay ในการอัปเดตค่าจาก GPS buffer ยังไงล่ะ:

static void smartDelay(unsigned long ms)
{
unsigned long start = millis();
do
{
while (Serial2.available())
gps.encode(Serial2.read());
} while (millis() – start < ms);
}

และเราจะใช้ฟังก์ชันพวกนี้ในการดึงค่าพวก ละติจูด, ลองจิจูด, ปี, เดือน, วันที่, ชั่วโมง, นาที ออกมา:

gps.location.lat()
gps.location.lng()
gps.date.year()
gps.date.month()
gps.date.day()
gps.time.hour()
gps.time.minute()

เอาล่ะ พร้อมกับ GPS แล้ว ไปดูกลไกแพน-ทิลท์กับเลเซอร์กันดีกว่า

เราต้องหา เซอร์โว 3.5 รอบ สำหรับ PAN และ เซอร์โว 180° ปกติสำหรับ TILT พี่จะไม่ลงลึกเรื่องหลักการแพน-ทิลท์มากนัก แต่จะเน้นไปที่การต่อสายและการแมปค่ามากกว่า

หลักการ: กลไกแพน-ทิลท์ที่เราใช้เนี่ย มันเหมือนกับที่ใช้ขยับกล้องโทรทรรศน์ใหญ่ๆในหอดูดาวเลยนะ!

  • แพน-ทิลท์สามารถหมุนไปทางซ้าย-ขวาได้ประมาณ 360° (มอเตอร์แพน) และสามารถก้ม-เงยขึ้นลงได้ประมาณ 180° (มอเตอร์ทิลท์)
  • เลเซอร์พอยน์เตอร์จะถูกติดตั้งบนกลไกแพน-ทิลท์นี้
  • มุมแอซิมัท (Azimuth) ที่คำนวณได้จะถูกไล่ตามด้วยมอเตอร์แพน ส่วนมุมเอเลเวชัน (Elevation) จะถูกไล่ตามด้วยมอเตอร์ทิลท์

เริ่มจากต่อสายก่อน เราใช้ Pin 9 สำหรับเซอร์โวแพน และ Pin 10 สำหรับเซอร์โวทิลท์ จัดไปวัยรุ่น

การขับเคลื่อนแบบแม่นยำ: เซอร์โว 3.5 รอบ

ในการติดตามเนี่ย "ความละเอียด" คือหัวใจสำคัญ แทนที่จะใช้เซอร์โวมาตรฐาน $180^\circ$ ระบบ RTPT ใช้ เซอร์โวหลายรอบ HS-785HB สำหรับแกนแอซิมัท (แพน):

  • ครอบคลุมเต็มวง: เซอร์โว $3.5$ รอบทำให้หมุนได้เต็ม $360^\circ$ ด้วยความละเอียดที่มากกว่าเซอร์โวธรรมดาเยอะเลย
  • การแมปด้วยซอฟต์แวร์: โค้ดใช้ฟังก์ชัน map() แบบเฉพาะทางเพื่อแปลงมุมแอซิมัท $0-360^\circ$ ให้เป็นช่วงพัลส์-วิธ ($5-29$) ที่แน่นอน ทำให้เลเซอร์ชี้ตามตำแหน่งดาวเคราะห์เป้าหมายในโลกจริงได้แม่นยำ
  • ฟีดแบ็คจากเลเซอร์: การติดตั้งเลเซอร์พอยน์เตอร์บนแกนทิลท์ทำให้ระบบแสดงผลตำแหน่งดาวเคราะห์ให้เห็นได้ทันที เป็นเครื่องมือที่เจ๋งมากสำหรับการสาธิต STEM งานนี้ห้ามช็อตนะตัวนี้

การแมปเซอร์โว (Servo mapping)

แค่ดูตัวอย่างจากไลบรารีเซอร์โว ก็เข้าใจแล้วว่าทำให้เซอร์โวขยับยังไง เราแค่ต้องใช้โค้ดสามบรรทัดจากไลบรารีสำหรับแต่ละเซอร์โว

เราต้องประกาศออบเจ็กต์ Servo แล้วกำหนดขา (pin) และตั้งค่าเริ่มต้นให้มัน:

#include
Servo myservoAz; // สร้างออบเจ็กต์เซอร์โวเพื่อควบคุมเซอร์โว
Servo myservoEl;
myservoAz.attach(9); // กำหนดขาที่ต่อเซอร์โว
myservoEl.attach(10);
myservoAz.write(5);   // ตั้งค่าเริ่มต้นให้เซอร์โวไปที่ตำแหน่งศูนย์
myservoEl.write(2);   // สำหรับเซอร์โวของพี่ มันคือ 5 (สำหรับเซอร์โวแพน/อซิมัท) และ 2 (สำหรับเซอร์โวทิลท์)

เซอร์โวแพนของเราเป็นแบบ 3.5 รอบ (turn) และรับค่าอินพุตจาก 5 ถึง 29 เพื่อหมุนจาก 0° ถึง 360° จากนั้นเราก็ทำการแมปค่าซึ่งง่ายมาก สมมติตัวแปร "Azim" ให้ค่าอซิมัทมา และตัวแปร "Elevation" ให้ค่าอัลติจูดมา เราก็แมปแบบนี้:

Azi = map(Azim, 0, 360, 5, 29); // สำหรับเซอร์โว 3.5 รอบของพี่ที่หมุนได้ 360° ทำงานบนค่าตั้งแต่ 5 - 29
Az = (int)Azi;
Elev = map(Elevation, -90, 90, 2, 178); // สำหรับเซอร์โว 180° ของพี่ที่ทำงานบนค่าตั้งแต่ 0 - 180
El = (int)Elev;

Azi และ El คือค่าที่แมปถูกต้องแล้ว และสามารถส่งเข้าไปให้แพนทิลท์ได้แบบนี้:

myservoAz.write(Az);
 myservoEl.write(El);

ตอนนี้สิ่งที่ต้องทำก็คือ แปลงการคำนวณอซิมัท-อัลติจูดจากโพสต์ก่อนหน้านี้ให้เป็นโค้ด Arduino แล้วใช้ข้อมูลจาก GPS มาคำนวณและส่งผลลัพธ์ไปให้เซอร์โว

หมายเหตุ: น้องต้องจัดตำแหน่งตั้งต้นของแพนทิลท์ให้ตรงกับ 0° อซิมัทของตำแหน่งจริงของน้องให้ได้นะ (ก็คือทิศเหนือนั่นแหละ!) ไม่งั้นมันจะชี้ดาวพลุ๊ตแทนดาวอังคารแน่ะ

แผนผังการทำงาน: จาก GPS สู่เซอร์โว

เวิร์กโฟลว์นี้คือคลาสมาสเตอร์ในการจัดการข้อมูลแบบซิงโครนัสเลย:

  1. จับสัญญาณ GPS: ใช้ smartDelay() เพื่อ "ป้อน" ข้อมูลให้บัฟเฟอร์ GPS ในขณะที่ลูปการคำนวณยังทำงานอยู่
  2. ประสานพิกัด: ระบบดึงค่าละติจูด ลองจิจูด และเวลาปัจจุบัน (UTC) มาคำนวณหา "เวลาดาวฤกษ์ท้องถิ่น" (Local Sidereal Time) ของผู้สังเกต
  3. ปรับสมดุลอัตโนมัติ: ถ้าเพิ่ม MPU9250 เข้าไป ระบบจะสามารถชดเชยการวางตัวของฐานได้อัตโนมัติ หมายความว่าไม่ต้องมานั่งจัดอุปกรณ์ให้ตรงทิศเหนือจริง (True North) ด้วยมือให้เมื่อย

ในโพสต์หน้า พี่จะเอา MPU9250 มาบูรณาการด้วย เพื่อให้เซอร์โวแพนของพี่ตรวจจับทิศเหนือของตำแหน่งนั้นๆ ได้เอง และปรับตัวชี้ตำแหน่งดาวเคราะห์ให้อัตโนมัติ แม้ว่าพี่จะไม่ตั้งมันไว้ที่อซิมัท 0° ก็ตาม (ขั้นตอนนี้เป็นตัวเลือกนะ อยากเพิ่มเมื่อไหร่ค่อยทำ)

เจ้า RTPT นี้คือหลักฐานเชิงประจักษ์ระดับโปรที่เปลี่ยนข้อมูลดาราศาสตร์ซับซ้อนให้กลายเป็นประสบการณ์ที่จับต้องและโต้ตอบได้ สุดยอดไปเลยวัยรุ่น!

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

apps:
  - "1x Arduino IDE"
  - "1x TinyGPS++ Library"
author: "paulplusx"
category: "Sensors & Environment"
components:
  - "1x Arduino Mega 2560 (Required for 33KB+ code)"
  - "1x U-BLOX GPS Receiver (NEO-6M)"
  - "1x Pan-Tilt Mechanism"
  - "1x HS-785HB 3.5-Turn Servo (for Pan/Azimuth)"
  - "1x 180° Micro Servo (for Tilt/Elevation)"
  - "1x Laser Pointer (for visual tracking)"
  - "1x MPU9250 (Optional - for Yaw stabilization)"
description: "โปรเจกต์นี้คือการเอาอุปกรณ์ RTPT มาต่อเข้ากับฮาร์ดแวร์ตัวอื่นๆ ให้ทำงานร่วมกันได้แบบตึงๆ เรียกได้ว่าเป็นงานบูรณาการที่ทั้งเท่และเทพ วัยรุ่นสายช่างต้องลองจัดไป!"
difficulty: "Intermediate"
documentationLinks:
  - "https://paulplusx.wordpress.com/2016/03/03/rtpts_hw/"
downloadableFiles: []
encryptedPayload: "U2FsdGVkX19iDagcde9RqmGiQ2sB3DE9v6m9UxZ8+IixXmhTUILvThTEe4RSCDKoTBRLWSWZ36fcAUn/DWp/qhPmaq29bWZYSpsTf/zTvpI="
heroImage: "https://cdn.jsdelivr.net/gh/bigboxthailand/arduino-assets@main/images/projects/hardware-integration-of-rtpt-72cd7a_cover.jpg"
lang: "en"
likes: 1
passwordHash: "5ebbadb74f1e32441f9671ad36561c963395e37d79f8a1c26701d88cb4fcdc78"
price: 2450
seoDescription: "Build a Real-Time Planet Tracker with Arduino Mega and GPS. Track planets automatically with high-precision servos and laser-guided telemetry."
tags:
  - "astronomy"
  - "gps"
  - "robotics"
  - "telemetry"
  - "planet-tracking"
title: "ฮาร์ดแวร์อินทิเกรชั่น RTPT งานง่ายแต่หล่อ"
tools:
  - "1x Soldering iron"
videoLinks:
  - "https://www.youtube.com/embed/yqCEA0beu64"
views: 3837