กลับไปหน้ารวมไฟล์
gps-clock-with-menu-and-arduino-078a20.md

ประโยชน์หลักๆ ของนาฬิกาอาร์ดูโน่ที่ใช้ GPS ตัวนี้คือการออกไปสำรวจข้างนอกบ้านนั่นเอง มันเป็นเครื่องมืออเนกประสงค์ขนาดกะทัดรัดที่ใช้จับเวลา ตำแหน่ง และข้อมูลการเคลื่อนไหวได้ในตัวเดียว นอกจากนี้ยังเอาไปใช้ในงานวิจัยภาคสนามที่ต้องการข้อมูล GPS แม่นยำและข้อมูลที่มีการบันทึกเวลาเพื่อการศึกษาทางวิทยาศาสตร์ได้ด้วย การถอดชิ้นส่วนครั้งนี้เป็นแหล่งความรู้ชั้นดีสำหรับนักเล่นและนักเรียนสายช่าง งั้นเรามาดูกันเลยดีกว่า!

ภาพรวมโปรเจค

"Celestial-Chrono" คือการนำ การซิงค์เวลาผ่านเครือข่ายดาวเทียม และ การวิเคราะห์บิตสตรีม NMEA-0183 มาทำให้เป็นจริงแบบจัดเต็ม ออกแบบมาเป็นเครื่องมือนำทางอิสระที่มีความแม่นยำสูง ระบบนี้จะดึงข้อมูลเวลาจากนาฬิกาอะตอมและพิกัดทางภูมิศาสตร์จากกลุ่มดาวเทียมระบบกำหนดตำแหน่งบนโลก (GPS) อุปกรณ์นี้มาพร้อมกับเมนูลอจิกขับเคลื่อนผ่าน I2C ที่ซับซ้อน ทำให้ผู้ใช้สามารถสลับระหว่างแดชบอร์ดแบบเรียลไทม์ การวิเคราะห์พิกัดเชิงพื้นที่ และการแสดงข้อมูลเทเลเมทรีในรูปแบบ QR-code ได้ การสร้างนี้เน้นไปที่การวินิจฉัยการแยกวิเคราะห์แพ็กเก็ต NMEA, การแสดงผลแบบฮิวริสติกส์บน SSD1306 และการวิเคราะห์ความแม่นยำของเวลาระดับต่ำกว่าไมโครวินาที

ฮาร์ดแวร์

ปุ่มกดสามารถต่อสายได้ตามนี้:

การต่อสายปุ่มกด

จอแสดงผล OLED เป็นอีกชิ้นส่วนที่มีประโยชน์สุดๆ และสามารถใช้ได้กับหัวข้อที่หลากหลาย ตั้งแต่การแสดงผลอินพุต, GUI (อินเทอร์เฟซผู้ใช้แบบกราฟิก) และอื่นๆ อีกเพียบ! มาดูการต่อสายกัน:

การต่อสาย OLED

โมดูล GPS เป็นหนึ่งในอุปกรณ์สื่อสารระยะไกลที่สะดวกและใช้ง่ายที่สุดในท้องตลาด แม้ว่าโมดูล LoRa และ GPS บางรุ่นจะส่งข้อมูลได้ไกลมาก แต่ NEO-6m ในกรณีนี้เราใช้เพื่อดึงข้อมูลเท่านั้น มาดูสรุปการทำงานของมันกัน:

  1. การสื่อสารกับดาวเทียม: รับสัญญาณจากดาวเทียม GPS เพื่อกำหนดตำแหน่งของมันบนโลก
  2. การส่งออกข้อมูล NMEA: ส่งประโยคมาตรฐาน NMEA ที่มีข้อมูลตำแหน่ง ความเร็ว และเวลาไปยัง Arduino ผ่านการสื่อสารแบบอนุกรม ข้อมูลเหล่านี้จะถูกแปลผ่านไลบรารี TinyGPS++
  3. ความต้องการเสาอากาศ: ต้องการเสาอากาศภายนอกหรือในตัวเพื่อรับสัญญาณดาวเทียมได้อย่างมีประสิทธิภาพ

นี่คือการต่อสายสำหรับโมดูล GPS Neo-6m:

การต่อสาย GPS

สำหรับอุปกรณ์ตัวนี้ เราจะใช้ปุ่มกดแค่ 3 จากทั้งหมด 4 ปุ่ม (ดูแผนผังด้านล่าง) แต่คุณสามารถใช้ปุ่มเหล่านี้จำนวนเท่าไหร่ก็ได้สำหรับแอปพลิเคชันอื่นๆ (มาแชร์ไอเดียกันในคอมเมนต์นะ อยากเห็นจังเลย)

สุดท้ายแล้ว นี่คือแผนผังเต็มรูปแบบ:

แผนผังเต็มรูปแบบ

ลงลึกแบบช่างๆ

  • NEO-6M NMEA-0183 Serial Forensics:
    • The Serial-Stream Bit-Buffer: ตัว Neo-6M มันส่งข้อมูลแบบ asynchronous serial packets $(\text{RX/TX})$ ที่ความเร็ว $9600\text{ baud}$ งานของเราคือมาจับแพ็กเก็ตให้ได้ โดยดูที่หัวข้อความ (sentence headers) พวกนี้: $GPRMC (เวลา/ตำแหน่ง) และ $GPGGA (ความสูง/คุณภาพสัญญาณ) เป้าหมายคือให้ได้ "Fix-Time" ที่เร็วสุดๆ วิธีจัดเต็มคือใช้เสาอากาศภายนอก (external active antenna) เพื่อล็อกดาวเทียมให้ได้ $\ge 4$ ดวงแบบแน่นหนึบ จะได้ค่าตำแหน่งที่แม่นยำสุดๆ
    • SoftwareSerial Signal-Stiffness: เราจะสงวนฮาร์ดแวร์ UART ไว้ใช้ดีบั๊ก ดังนั้นลิงก์ GPS จะต่อผ่าน SoftwareSerial (พิน D10, D11) งานสำคัญคือต้องรับสตริง NMEA แบบ interrupt-driven ให้ครบ อย่าให้มีบิตไหนหลุดหายระหว่างที่ข้อมูลวิ่งจากโมดูล GPS เข้ามายังสมองของ Nano
  • I2C Unified Graphics & Menu Heuristics:
    • The U8G Graphics-Engine Pipeline: จอ SSD1306 เราใช้บัส I2C $(SDA/SCL)$ ขับ ต้องตั้งค่าให้ใช้โหมด "Fast" I2C ในไลบรารี U8G เพื่อรีเฟรช UI ได้ไวสุดๆ เคล็ดลับคือใช้ฟังก์ชัน drawBitmapP() ในการวาดภาพพวก QR code ขนาด $64\times 64$ และไอคอนแดชบอร์ด $16\times 16$ ที่เก็บไว้ใน PROGMEM เพื่อประหยัดพื้นที่ SRAM อันน้อยนิดของ Nano
    • HMI Debounce & State-Machine Analytics: ระบบเมนูใช้ปุ่มกด 3 ปุ่มเป็นอินพุต ต้องจัดการเรื่องเด้งของสัญญาณ (debouncing) แบบซอฟต์แวร์ $(flag\text{-based})$ เพื่อสลับระหว่างหน้าจอต่างๆ ได้อย่างลื่นไหล ไร้ดีเลย์ ระหว่างหน้าแสดงความเร็ว GPS กับหน้าเมนู

วิศวกรรมและการลงมือทำ

  • Temporal-Timestamp Persistence Forensics:
    • Leap-Second & Time-Zone Interpolation: ข้อมูลจาก GPS มันเป็นเวลา UTC หมดเลย งานของเราคือต้องเขียนลอจิกเพิ่ม offset เข้าไปเพื่อแสดงเวลาให้ตรงกับโซนเวลาของเรา และต้องแน่ใจว่าเวลาที่แสดงบนจอ OLED มันไม่ค่อย (time-drift $(\approx 10\text{-}100\text{ns})$) จนเกินไป
    • Antenna-Impedance Diagnostics: ตัว Neo-6M ต้องเห็นท้องฟ้าโล่งๆ นะ ต้องตรวจสอบความสมบูรณ์ของสัญญาณ (signal integrity) บนลาย PCB ระหว่างโมดูลกับเสาอากาศเซรามิก อย่าให้เกิดการสูญเสียสัญญาณ (path-loss) จนอัตราส่วนสัญญาณต่อสัญญาณรบกวน $(SNR)$ ตกลงไป เพราะจะทำให้ล็อกตำแหน่งไม่ได้
  • Structural Mechatronics & 3D-Printer Forensics:
    • ตัวเครื่องทั้งหมดจะอยู่ในเคสที่พิมพ์จากเครื่อง Ultimaker S3 ต้องออกแบบให้สแตนออฟภายในมีระยะเผื่อ (tolerances) ที่พอดี เพื่อให้หน้า OLED และเสาอากาศ Neo-6M อยู่ชิดกับผิวด้านนอกสุด งานนี้เพื่อความชัดเจนในการใช้งาน (ergonomic-clarity) และให้สัญญาณผ่านได้ดี (structural signal-transparency) นั่นเอง

โค้ด

ก่อนเริ่ม ไปดาวน์โหลดไลบรารี U8g มาด้วยนะ แล้วเพิ่มเข้าไปในโปรเจค Arduino ผ่าน Sketch > Include Libraries > Add .ZIP Library ส่วนไลบรารี TinyGPS++ ก็ติดตั้งให้เรียบร้อย

มาแกะโค้ดกันทีละขั้นตอน:

Variables Declarations:

U8GLIB_SSD1306_128X64 u8g(U8G_I2C_OPT_DEV_0 | U8G_I2C_OPT_NO_ACK | U8G_I2C_OPT_FAST);
TinyGPSPlus gps;
SoftwareSerial gpsSerial(10, 11)); //RX, TX
  1. ตั้งค่าไลบรารี u8g เพื่อให้ทำงานเร็วและรีเฟรชได้ไว
  2. ตั้งค่าโมดูล GPS โดยใช้พิน 10 เป็น RX และพิน 11 เป็น TX
const unsigned char epd_bitmap_qr_code [] PROGMEM = {...};
const unsigned char bitmap_icon_dashboard[] PROGMEM = {...};
const unsigned char bitmap_icon_gps_speed[] PROGMEM = {...};
const unsigned char bitmap_icon_knob_over_oled[] PROGMEM = {...};
const unsigned char* bitmap_icons[8] = {
	 bitmap_icon_dashboard,
	 bitmap_icon_gps_speed,
	 bitmap_icon_knob_over_oled,
};
const unsigned char bitmap_scrollbar_background[] PROGMEM = {...};
const unsigned char bitmap_item_sel_outline[] PROGMEM = {...};
1.  `pd_bitmap_Aerospace_qr_code`: เก็บข้อมูล QR Code ขนาด 64x64 สำหรับอะไรก็ได้ที่อยากใส่ (ดูด้านล่างว่าจะสร้าง QR Code 64x64 ยังไง)
2.  `bitmap_icon_...`: เก็บข้อมูลไอคอนขนาด 16x16 พิกเซล สำหรับแต่ละเมนู
3.  `bitmap_icons[8]`: เก็บรายการเมนูตามลำดับที่จะแสดงบนหน้าจอ
4.  `bitmap_scrollbar_background[]`: แถบข้างสำหรับแสดงตำแหน่งของเคอร์เซอร์
5.  `bitmap_item_sel_outline`: วงแสงที่แสดงรอบๆ ไอเท็มที่ถูกเลือก

#define BUTTON_UP_PIN 12


#define BUTTON_SELECT_PIN 8


#define BUTTON_DOWN_PIN 4


1.  การประกาศพิน (Pin) สำหรับปุ่มต่างๆ (มีปุ่มเพิ่มเติมบนพิน 10 สำหรับเคสอื่นๆ)

int button_up_clicked = 0;


int button_select_clicked = 0;


int button_down_clicked = 0;


int item_selected = 0;


int item_sel_previous;


int item_sel_next;


int current_screen = 0;


1.  `button_up_clicked, button_select_clicked & button down clicked`: ตัวแปรสำหรับจัดการเดานซ์ (Debounce) ปุ่มกด (ค่าเป็น 1 เมื่อกด, 0 เมื่อปล่อย)
2.  `item_selected, item_sel_previous & item_sel_next`: ใช้ติดตามตำแหน่งเคอร์เซอร์ในรายการเมนู
3.  `current_screen`: ใช้ติดตามดัชนี (index) ของหน้าจอปัจจุบันจากอาร์เรย์ `menu_items`

float lat;


float lon;


int day;


int mon;


int yr;


int hr;


int minute;


int sec;


int speed;


int altitude;


1.  ตัวแปรพวกนี้เก็บข้อมูลจากโมดูล GPS ถ้าข้อมูลนั้นถูกต้องและอัปเดตแล้ว

int progress = 0;


char buffer[32];


1.  `progress`: ติดตามความคืบหน้าของ Progress Bar บนหน้าจอโหลด
2.  `buffer`: ใช้สร้างสตริงเพื่อแสดงความคืบหน้าบน Progress Bar

**ฟังก์ชัน Loop():**

void loop() { if (current_screen == 0) { if ((digitalRead(BUTTON_UP_PIN) == LOW) && (button_up_clicked == 0)) { //item_selected goes down to previous item //If the item_selected is 0, wrap to last element } else if ((digitalRead(BUTTON_DOWN_PIN) == LOW) && (button_down_clicked == 0)) { //item selected goes up to next item //If the item selected is the last element, wrap to first index ( } if ((digitalRead(BUTTON_UP_PIN) == HIGH) && (button_up_clicked == 1)) {button_up_clicked = 0;} if ((digitalRead(BUTTON_DOWN_PIN) == HIGH) && (button_down_clicked == 1)) {button_down_clicked = 0;} } if ((digitalRead(BUTTON_SELECT_PIN) == LOW) && (button_select_clicked == 0)) { button_select_clicked = 1; if (current_screen == 0) { //Assign current screen to next screen (selected is pressed_ } else { //Set current screen to 0 } } if ((digitalRead(BUTTON_SELECT_PIN) == HIGH) && (button_select_clicked == 1)) {button_select_clicked = 0;} //Set item_sel_previous to previous index //Set item_sel_next to next index if (current_screen == 0) { u8g.firstPage(); do { //Draw the main menu with icons and menu names on specific locations based on index } while (u8g.nextPage()); } else if (current_screen == 1) { u8g.firstPage(); do { //Draw the QR code } while(u8g.nextPage()); } else if (current_screen == 2) { while (gpsSerial.available() > 0) { if (gps.encode(gpsSerial.read())) { //Get Data //Print Data //If selected is pressed, break out to menu screen } } } else if (current_screen == 3) { while (gpsSerial.available() > 0) { if (gps.encode(gpsSerial.read())) { //Get Data //Print Data //If selected is pressed, break out to menu screen } } } }


1.  **จัดการการนำทางด้วยปุ่ม:** มันจะตรวจสอบการกดปุ่ม (`BUTTON_UP_PIN, BUTTON_DOWN_PIN, BUTTON_SELECT_PIN`) และปรับดัชนี `item_selected` ตามนั้น ตัวอย่างเช่น กด "ขึ้น" จะลดไอเท็มที่เลือก ส่วนกด "ลง" จะเพิ่มไอเท็มที่เลือก พอถึงจุดสิ้นสุดของเมนู การเลือกจะวนกลับไปเริ่มใหม่ (wrap around) จัดไปวัยรุ่น!
2.  **การเดานซ์ (Debouncing):** ใช้แฟล็ก `button_up_clicked`, `button_down_clicked`, และ `button_select_clicked` เพื่อป้องกันไม่ให้การกดปุ่มซ้ำๆ ถูกนับหลายครั้ง พอปล่อยปุ่ม แฟล็กก็จะถูกรีเซ็ต ให้กดใหม่ได้อีกที ห้ามช็อตนะตัวนี้

3.  **การสลับหน้าจอ:** ขึ้นอยู่กับหน้าจอปัจจุบัน (`current_screen`) มันจะสลับไปหน้าจอต่างๆ เมื่อปุ่ม "`select`" ถูกกด มันจะไปหน้าจอถัดไป (`menu_item`) หรือกลับไปที่หน้าจอเมนู (`current_screen = 0`)
4.  **การแสดงเมนู:** ถ้า `current_screen == 0` เมนูจะถูกแสดงผล มันใช้ไลบรารีกราฟิก u8g เพื่อวาดรายการเมนูก่อนหน้า, ปัจจุบัน, และถัดไป พร้อมกับไอคอนที่เกี่ยวข้อง และแถบเลื่อนจะถูกอัปเดตเพื่อแสดงรายการที่ถูกเลือก
5.  **การแสดงข้อมูล GPS:** บนหน้าจอเฉพาะ ( `current_screen == 2` สำหรับตำแหน่ง/ความเร็ว/ความสูง และ `current_screen == 3` สำหรับเวลา/วันที่) โปรแกรมจะอ่านข้อมูล GPS และแสดงค่าต่างๆ (ละติจูด, ลองจิจูด, ความเร็ว, ความสูง, เวลา, และวันที่) บนจอแสดงผล มันจะตรวจสอบความถูกต้องของข้อมูล GPS ก่อนแสดง
6.  **การแสดง QR Code:** ถ้า `current_screen == 1` รูปภาพบิตแมปของ QR Code จะถูกแสดงบนหน้าจอโดยใช้ฟังก์ชัน `u8g.drawBitmapP()`

**ฟังก์ชัน PrintSomething():**

//Example Function void printAll() { u8g.firstPage(); do { u8g.setFont(u8g_font_6x13r); u8g.drawStr(0, 10, "Lat: "); u8g.drawStr(0, 25, String(lat, 6).c_str()); } while (u8g.nextPage()); }


1.  พิมพ์ข้อมูลที่เก็บมาโดยแปลงค่า `float` เป็น `string` โดยใช้การประกาศคลาส `String()`
2.  สตริงจะถูกแปลงเป็น `char[]` โดยใช้ฟังก์ชัน `c_str()` เพื่อพิมพ์บนจอแสดงผล OLED

**ฟังก์ชัน getData():**

//getSpeed() for example void getSpeed() { if (gps.speed.isValid()) { speed = gps.speed.mps(); } }


1.  ตรวจสอบว่าความเร็วถูกต้องหรือไม่โดยใช้ `gps.speed.isValid()` และถ้าถูกต้อง ก็จะเซ็ตค่า `speed` เป็นความเร็วที่วัดได้ในหน่วยเมตรต่อวินาที

> หมายเหตุ: ฟังก์ชันอื่นๆ ที่ขึ้นต้นด้วย get() ทำงานในลักษณะเดียวกัน

### คำแนะนำการสร้าง QR Code
1.  ทำให้ลิงก์สั้นที่สุด ไม่เกิน 15 ตัวอักษร
2.  ไปที่เว็บไซต์สร้างบาร์โค้ดแล้วเลือกประเภท MicroQR เพื่อสร้างบาร์โค้ดขนาด 32x32 พิกเซล
3.  ใช้เว็บไซต์แปลงรูปภาพเป็นโค้ด CPP เพื่อแปลง QR Code เป็นอาร์เรย์
4.  คัดลอก/วางอาร์เรย์นี้ลงใน `epd_bitmap_qr_code`

### สรุป
Celestial-Chrono คือสุดยอดของ **ระบบวินิจฉัย HMI แบบผสานรวมดาวเทียม** ด้วยการเชี่ยวชาญ **การวิเคราะห์ Bitstream NMEA จาก GPS** และ **การจัดการเมนูแบบอะซิงโครนัส** ทำให้เราได้เครื่องมือนำทางระดับมืออาชีพที่แข็งแกร่ง มันให้ความชัดเจนเชิงเวลาและพื้นที่แบบสมบูรณ์ผ่านการวินิจฉัยข้อมูลดาวเทียมขั้นสูง

---
*Satellite Sync: Mastering navigational telemetry through NMEA forensics.*

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

apps:
  - "1x TINKERCAD"
  - "1x Arduino IDE 2.0 (beta)"
author: "uniostar"
category: "Screens & Displays"
components:
  - "1x Arduino Nano (Computational Nav-Logic Hub)"
  - "1x Neo-6M GPS Module (Satellite Signal-Integrity Node)"
  - "1x 0.96 inch SSD1306 OLED (HMI Telemetry Interface)"
  - "3x Tactile Switches (Menu-Navigation Interconnects)"
  - "1x 3D-Printed Chassis (Ultimaker S3 Structural Forensics)"
description: "วัยรุ่นสายช่างคนไหนอยากทำอุปกรณ์ GPS แบบสแตนด์อโลนตึงๆ บ้าง? ตามมาดูเทคนิคการแยกชิ้นและประกอบเจ้า GPS Device ของพี่ ที่มาพร้อมกับหน้าจอ OLED เทพๆ กันเลย จัดไป!"
difficulty: "Intermediate"
documentationLinks: []
downloadableFiles:
  - "https://projects.arduinocontent.cc/e323bc8a-6777-4074-8cf9-b74e63cf9ea3.ino"
encryptedPayload: "U2FsdGVkX18z+ZI8FvEVvKfH9GY2VKxDKYUpm4JJ1KPeAdiBFBSSYQVd5faclBz0COCz74Ai2PozFH1nVVdOEtZYnQUXPPBdyk6LHC40cprjo9bVsncnvUChmb6ZYAWm"
heroImage: "https://raw.githubusercontent.com/bigboxthailand/arduino-assets@main/images/projects/gps-clock-with-menu-and-arduino-078a20_content_1.png"
lang: "en"
likes: 677
passwordHash: "5ae22875b1f670c4397330b9cd3cce943b3dc0c7a7c4933b7da4459b17428dec"
price: 2450
seoDescription: "Build a standalone GPS Clock with Arduino and OLED display. Learn to make a GPS Device with a custom Menu."
tags:
  - "gps-nmea-packet-forensics"
  - "sub-microsecond-timing-heuristics"
  - "u8g-graphics-engine-diagnostics"
  - "i2c-oled-visual-orchestration"
  - "standalone-navigational-analytics"
  - "arduino-nano"
title: "Celestial-Chrono: GPS NMEA-Packet Forensics & Sub-Microsecond Timing Heuristics"
tools:
  - "TinyGPS++ Library (NMEA-Parsing Engine)"
  - "U8G Graphics Library (Visual Buffer-Orchestration)"
videoLinks: []
views: 677