เตรียมอุปกรณ์
1. GPS Module
จริงๆมีหลากหลายรูปแบบให้เลือกใช้ และส่วนใหญ่ก็จะใช้โปรโตคอลเดียวกันคือ NMEA ผ่าน Serial ที่ Buadrate 9600 ส่วนที่ผมเลือกตัวนี้เพราะความกระทัดรัดของมัน มีขนาดแค่เพียงเหรียญ 5 บาทเท่านั้น และมี Pin Out ดังนี้
ดำ -> GPS_GND
ขาว -> GPS_TX
เขียว -> GPS_RX
แดง -> GPS_VCC (3.3v / 5V)
หลังจากที่เราต่อไฟเลี้ยงแล้ว ผมต่อวงจรตามนี้ GPS_TX -> D4 , GPS_RX -> D5
2. Arduino
สามารถเลือกใช้ได้ตามสะดวก ถ้ามี USB Connector อยู่แล้วก็ไม่ต้องต่อ USB to Serial อีก และอีกเช่นเคย ผมเลือก Promini เพราะความกระทัดรัด และสเป็คเท่ากันกับ UNO และ NANO และมีราคาถูกกว่า
Coding
**** Hardware Serial คือ Register หนึ่งในไมโครคอนโทรลเลอร์ ใช้ในการอ่านค่าข้อมูล และส่งข้อมูลผ่าน Serial Comunication โดยตัว Register จะทำหน้าที่อ่านเฟรมข้อมูลที่เข้ามาตามความเร็ว (Buadrate) ที่กำหนดไว้และเก็บไว้ในรีจิสเตอร์รอให้คอนโทรลเลอร์มาอ่าน และนำข้อมูลที่เตรียมส่งที่เก็บไว้ในรีจิสเตอร์ ส่งไปตามความเร็วที่กำหนดไว้ ต่างจากซอฟแวร์ซีเรียลที่เปรียบเหมือนการอ่านค่า - เขียนค่า Digital Pin ด้วยโปรแกรมเพื่อให้สามารถรับ หรือส่งสัญญาณตามเงื่อนไขที่กำหนด ดังนั้นการใช้ซอฟแวร์ซีเรียล จะเสียเวลาในการประมวลผลมากกว่า และมีเสถียรภาพต่ำกว่าแบบฮาร์ดแวร์****
#include <SoftwareSerial.h> SoftwareSerial mySerial(4, 5); // RX, TX void setup() { // put your setup code here, to run once: Serial.begin(115200); mySerial.begin(9600); } void loop() { // put your main code here, to run repeatedly: if (mySerial.available()) { char ch_in = mySerial.read(); if (ch_in >= ' ' && ch_in <= '~') { // display if ch_in is a character Serial.write(ch_in); } else if (ch_in == '\n') Serial.print("[\\n]"); else if (ch_in == '\r') Serial.print("[\\r]"); else Serial.print("[?]"); if (ch_in == '\n') Serial.write('\n'); } }
เมื่อรันโค๊ดนี้ควรจะได้ผลลัพธ์ดังนี้
อันนี้เกิดจากผมทดสอบในห้อง ทำให้ดูเหมือนมันจะรับสัญญาณไม่ได้ ต่อไปผมจะจำแนกชนิดข้อมูล ตามตัวอักษร
ตั้งต้นของชุดข้อมูล
จากข้อมูลหลายอย่างที่ส่งมา ผมจะใช้แค่ 2 ชนิด คือ GGA และ RMC โดยมีโปรโตคอลตามนี้ (อ้างอิง)
ผมจะลองเจียนของ GGA ให้ดูเป็นแนวทางก่อนนะครับ อันดับแรกเราต้องแบ่ง String ในช่วงข้อมูลต่างๆให้ได้ แต่ผมไม่อยาก
ประกาศตัวแปร String ให้มากเกินไป ผมจึงใช้วิธีระบุตำแหน่งของเครื่องหมายจุลภาค แล้วใช้ sub string เพื่อดึงข้อความ
ตรงกลางออกมา ตามโค๊ดนี้
#include <SoftwareSerial.h> SoftwareSerial mySerial(4, 5); // RX, TX void setup() { // put your setup code here, to run once: Serial.begin(115200); mySerial.begin(9600); } void loop() { // put your main code here, to run repeatedly: GPS_loop(); } void GPS_loop() { static bool receiving = false; static String msg = ""; if (mySerial.available()) { char ch_in = mySerial.read(); if ( ch_in == '$') { msg = ""; receiving = true; } else if ( ch_in == '\r') { GPS_read(msg); receiving = false; } else if (receiving) { msg += ch_in; } } } void GPS_read(String &msg) { if (msg.substring(2, 5).equals("GGA")) { GPS_GGA_read(msg); } else if (msg.substring(2, 5).equals("RMC")) { GPS_RMC_read(msg); } } void GPS_GGA_read(String &msg) { Serial.println("GGA"); unsigned char comma_arr[14]; unsigned char comma_i = 0; unsigned char packet_checksum = 0; unsigned char CRC = msg.charAt(0); for (unsigned char i = 0 ; i < msg.length() ; i++) { if (comma_i > 14)return; if (msg.charAt(i) == ',') { comma_arr[comma_i] = i; comma_i++; } if (i > 0 && i < msg.length() - 3) { CRC = CRC ^ msg.charAt(i); } Serial.print(msg.charAt(i)); } Serial.println(); unsigned char CRC_rx = (hex_char_to_byte (msg.charAt(msg.length() - 2)) << 4) | hex_char_to_byte (msg.charAt(msg.length() - 1)); if (CRC != CRC_rx ) { Serial.println("CRC invalid!"); Serial.println(CRC, HEX); Serial.println((hex_char_to_byte (msg.charAt(msg.length() - 2)) << 4) | hex_char_to_byte (msg.charAt(msg.length() - 1)) , HEX); return; } if (msg.charAt(comma_arr[5] + 1) == '0') { Serial.println("GGA unavailable"); return; } Serial.print("UTC\t:"); Serial.println(msg.substring(comma_arr[0] + 1, comma_arr[1])); Serial.println("GGA available"); Serial.print("LAT\t:"); Serial.print(msg.substring(comma_arr[1] + 1, comma_arr[2])); Serial.println(msg.substring(comma_arr[2] + 1, comma_arr[3])); Serial.print("LONG\t:"); Serial.print(msg.substring(comma_arr[3] + 1, comma_arr[4])); Serial.println(msg.substring(comma_arr[4] + 1, comma_arr[5])); Serial.print("QUAL\t:"); Serial.println(msg.substring(comma_arr[5] + 1, comma_arr[6])); Serial.print("SAT\t:"); Serial.println(msg.substring(comma_arr[6] + 1, comma_arr[7])); Serial.print("ALT\t:"); Serial.println(msg.substring(comma_arr[8] + 1, comma_arr[9])); } void GPS_RMC_read(String &msg) { } unsigned char hex_char_to_byte (char hex_char) { if (hex_char >= '0' && hex_char <= '9')return hex_char - '0'; return (char_upper(hex_char) - 'A') + 10; } char char_upper(char ch) { if (ch >= 'a' && ch <= 'z') ch = ch - ('a' - 'A'); return ch; }
หลังจากเอาเครื่องไปวางไว้กลางแจ้ง ผมก็ได้ข้อมูลกลับมาตามนี้
ผมจะเขียนโปรแกรมในทำนองเดียวกัน สำหรับ RMC
#include <SoftwareSerial.h> SoftwareSerial mySerial(4, 5); // RX, TX void setup() { // put your setup code here, to run once: Serial.begin(115200); mySerial.begin(9600); } void loop() { // put your main code here, to run repeatedly: GPS_loop(); } void GPS_loop() { static bool receiving = false; static String msg = ""; if (mySerial.available()) { char ch_in = mySerial.read(); if ( ch_in == '$') { msg = ""; receiving = true; } else if ( ch_in == '\r') { GPS_read(msg); receiving = false; } else if (receiving) { msg += ch_in; } } } void GPS_read(String &msg) { if (msg.substring(2, 5).equals("GGA")) { GPS_GGA_read(msg); } else if (msg.substring(2, 5).equals("RMC")) { GPS_RMC_read(msg); } } void GPS_GGA_read(String &msg) { unsigned char comma_arr[14]; unsigned char comma_i = 0; unsigned char packet_checksum = 0; unsigned char CRC = msg.charAt(0); for (unsigned char i = 0 ; i < msg.length() ; i++) { if (comma_i > 14) { Serial.println("ERR"); Serial.println(comma_i); return; } if (msg.charAt(i) == ',') { comma_arr[comma_i] = i; comma_i++; } if (i > 0 && i < msg.length() - 3) { CRC = CRC ^ msg.charAt(i); } Serial.print(msg.charAt(i)); } Serial.println(); unsigned char CRC_rx = (hex_char_to_byte (msg.charAt(msg.length() - 2)) << 4) | hex_char_to_byte (msg.charAt(msg.length() - 1)); if (CRC != CRC_rx ) { Serial.println("CRC invalid!"); Serial.println(CRC, HEX); Serial.println((hex_char_to_byte (msg.charAt(msg.length() - 2)) << 4) | hex_char_to_byte (msg.charAt(msg.length() - 1)) , HEX); return; } if (msg.charAt(comma_arr[5] + 1) == '0') { Serial.println("GGA unavailable"); return; } Serial.print("UTC\t:"); Serial.println(msg.substring(comma_arr[0] + 1, comma_arr[1])); Serial.println("GGA available"); Serial.print("LAT\t:"); Serial.print(msg.substring(comma_arr[1] + 1, comma_arr[2])); Serial.println(msg.substring(comma_arr[2] + 1, comma_arr[3])); Serial.print("LONG\t:"); Serial.print(msg.substring(comma_arr[3] + 1, comma_arr[4])); Serial.println(msg.substring(comma_arr[4] + 1, comma_arr[5])); Serial.print("QUAL\t:"); Serial.println(msg.substring(comma_arr[5] + 1, comma_arr[6])); Serial.print("SAT\t:"); Serial.println(msg.substring(comma_arr[6] + 1, comma_arr[7])); Serial.print("ALT\t:"); Serial.println(msg.substring(comma_arr[8] + 1, comma_arr[9])); } void GPS_RMC_read(String &msg) { unsigned char comma_arr[14]; unsigned char comma_i = 0; unsigned char packet_checksum = 0; unsigned char CRC = msg.charAt(0); for (unsigned char i = 0 ; i < msg.length() ; i++) { if (comma_i > 12)return; if (msg.charAt(i) == ',') { comma_arr[comma_i] = i; comma_i++; } if (i > 0 && i < msg.length() - 3) { CRC = CRC ^ msg.charAt(i); } Serial.print(msg.charAt(i)); } Serial.println(); unsigned char CRC_rx = (hex_char_to_byte (msg.charAt(msg.length() - 2)) << 4) | hex_char_to_byte (msg.charAt(msg.length() - 1)); if (CRC != CRC_rx ) { Serial.println("CRC invalid!"); Serial.println(CRC, HEX); Serial.println((hex_char_to_byte (msg.charAt(msg.length() - 2)) << 4) | hex_char_to_byte (msg.charAt(msg.length() - 1)) , HEX); return; } if (msg.charAt(comma_arr[11] + 1) == 'N') { Serial.println("RMC unavailable"); return; } Serial.print("SPEED\t:"); Serial.println(msg.substring(comma_arr[6] + 1, comma_arr[7])); } unsigned char hex_char_to_byte (char hex_char) { if (hex_char >= '0' && hex_char <= '9')return hex_char - '0'; return (char_upper(hex_char) - 'A') + 10; } char char_upper(char ch) { if (ch >= 'a' && ch <= 'z') ch = ch - ('a' - 'A'); return ch; }
สำหรับผลลัพธ์ ผมสามารถอ่านค่าเวลา ตำแหน่ง และความเร็วได้แล้ว ในรูปแบบสตริง ส่วนจะเอาไปทำอะไรต่อเราก็สามารถ
นำสตริงไป cast เป็นตัวแปรชนิดอื่นๆต่อไปได้
สำหรับบทความนี้ เราจะจบลงตรงนี้ก่อน เพราะหลังจากนี้จะเะป็นการประยุกต์ใช้ ซึ่งอยู่นอกเหนือของเขตเนื้อหานี้
ส่วนจะเอาไปทำอะไรนั้น ก็คงต้องติดตามกันในบทความถัดๆไปครับ ^^
ไม่มีความคิดเห็น:
แสดงความคิดเห็น