GPS 모듈 예제 코드 (NEO-6M GPS Module)
1. GPS Module
GPS 모듈이 사용하는 프로토콜과 관련된 자료들은 아두이노 GPS 강좌 페이지에 간략하게 잘 소개되어 있습니다. 다만 GPS 소개 내용만 참고하시고 연결 방법 및 소스는 다른 방법으로 사용하는 것이 더 좋아보입니다.
여기서 소개하는 NEO-6M GPS 모듈 스펙입니다.
- U-blox/u-blox NEO-6M GPS module with antenna and build-in EEPROM
- This module is compatible with APM2 and APM2.5, and EEPROM can save all your configuration data.
- Interface: RS232 TTL
- Power: 3-5v
- Baudrate default:9600bps
- Without battery (it’s not safty by air mail).
- The battery Model is MS621FE,Need to welding
GPS 모듈은 National Marine Electronics Association (NMEA) 프로토콜이라 불리는 일련의 string을 출력해줍니다. 여기에 위치, 방향, 시간, 좌표 등의 데이터가 담겨져서 넘어옵니다. 따라서 이 string 에서 우리가 필요로 하는 값을 integer, long, float 등의 변수값으로 바꿔주는 작업이 필요합니다.
GPS 모듈은 아래와 같이 4개 section으로 구분해서 string 데이터를 출력해줍니다.
- $GPGGA: Global Positioning System Fix Data
- $GPGSV: GPS satellites in view
- $GPGSA: GPS DOP and active satellites
- $GPRMC: Recommended minimum specific GPS/Transit data
여기서 $GPRMC string에 GPS 사용을 위한 최소한의 필수 데이터가 있습니다. 아래는 $GPRMC string 상세내용입니다.
eg1. $GPRMC,081836,A,3751.65,S,14507.36,E,000.0,360.0,130998,011.3,E*62 eg2. $GPRMC,225446,A,4916.45,N,12311.12,W,000.5,054.7,191194,020.3,E*68 225446 Time of fix 22:54:46 UTC A Navigation receiver warning A = Valid position, V = Warning 4916.45,N Latitude 49 deg. 16.45 min. North 12311.12,W Longitude 123 deg. 11.12 min. West 000.5 Speed over ground, Knots 054.7 Course Made Good, degrees true 191194 UTC Date of fix, 19 November 1994 020.3,E Magnetic variation, 20.3 deg. East *68 mandatory checksum eg3. $GPRMC,220516,A,5133.82,N,00042.24,W,173.8,231.8,130694,004.2,W*70 1 2 3 4 5 6 7 8 9 10 11 12 1 220516 Time Stamp 2 A validity - A-ok, V-invalid 3 5133.82 current Latitude 4 N North/South 5 00042.24 current Longitude 6 W East/West 7 173.8 Speed in knots 8 231.8 True course 9 130694 Date Stamp 10 004.2 Variation 11 W East/West 12 *70 checksum eg4. for NMEA 0183 version 3.00 active the Mode indicator field is added $GPRMC,hhmmss.ss,A,llll.ll,a,yyyyy.yy,a,x.x,x.x,ddmmyy,x.x,a,m*hh Field # 1 = UTC time of fix 2 = Data status (A=Valid position, V=navigation receiver warning) 3 = Latitude of fix 4 = N or S of longitude 5 = Longitude of fix 6 = E or W of longitude 7 = Speed over ground in knots 8 = Track made good in degrees True 9 = UTC date of fix 10 = Magnetic variation degrees (Easterly var. subtracts from true course) 11 = E or W of magnetic variation 12 = Mode indicator, (A=Autonomous, D=Differential, E=Estimated, N=Data not valid) 13 = Checksum
위 내용에 따라 string을 읽어서 약간의 처리를 거쳐 출력하면 아래와 같이 출력됩니다.
$GPGGA,154653,4428.2011,N,00440.5161,W,0,00,,-00044.7,M,051.6,M,,*6C $GPGSA,A,1,,,,,,,,,,,,,,,*1E $GPGSV,3,1,10,02,50,290,003,10,25,24,045,35,27,56,145,00,,,,,,,,*78 $GPRMC,154653,V,4428.2011,N,00440.5161,W,000.5,342.8,050407,,,N*7F --------------- Time in UTC (HhMmSs): 154653 Status (A=OK,V=KO): V Latitude: 4428.2011 Direction (N/S): N Longitude: 00440.5161 Direction (E/W): W Speed in knots: 000.5 Direction in degrees: 342.8 Date in UTC (DdMmAa): 050407 Magnetic variation: Variation (E/W): Mode: A ---------------
경우에 따라서는 직접 이렇게 처리할 수도 있겠지만 라이브러리를 사용하면 훨씬 간편하게 코드를 작성할 수 있습니다.
2. 연결 방법, 라이브러리
일반적으로 많이 사용되는 GPS 모듈들은 Serial 통신을 지원합니다. 그래서 4개의 핀을 가지고 있습니다. (TX, RX, VCC, GND) 아두이노 공식 사이트에서는 TX-1번 핀, RX-0번 핀에 연결해서 하드웨어 시리얼 통신을 사용하는데 (Serial() 함수), 이렇게 하면 디버그용 메시지 출력을 할 수 없어 불편합니다.
그래서 보통 포럼을 보면 TX, RX 를 다른 디지털 핀에 연결하고 SoftwareSerial 라이브러리를 사용해서 통신하도록 작성한 코드가 많습니다. 일단 여기 예제에서는 TX, RX 핀을 아두이노의 11번, 10번 핀에 연결합니다.
그리고 GPS에서 데이터를 읽어오는 부분은 TinyGPS 를 이용해 쉽게 구현할 수 있습니다.
TinyGPS official page: http://arduiniana.org/libraries/tinygps/
TinyGPS download : https://www.pjrc.com/teensy/td_libs_TinyGPS.html (라이브러리만 다운로드 받아 쓰세요. 예제는 아두이노 호환 보드용이라 일반적인 아두이노 보드에서는 오동작합니다.)
3. 소스 코드 (스케치)
소스코드 출처: http://tny.cz/d19906cc
NEO-6M 모듈의 기본 Baudrate = 9600bps 입니다. mySerial.begin(9600)으로 초기화 하도록 합니다.
#include <SoftwareSerial.h> #include <TinyGPS.h> SoftwareSerial mySerial(10, 11); // RX, TX TinyGPS gps; void gpsdump(TinyGPS &gps); void printFloat(double f, int digits = 2); void setup() { // Open serial communications and wait for port to open: Serial.begin(9600); // set the data rate for the SoftwareSerial port mySerial.begin(9600); delay(1000); Serial.println("uBlox Neo 6M"); Serial.print("Testing TinyGPS library v. "); Serial.println(TinyGPS::library_version()); Serial.println("by Mikal Hart"); Serial.println(); Serial.print("Sizeof(gpsobject) = "); Serial.println(sizeof(TinyGPS)); Serial.println(); } void loop() // run over and over { bool newdata = false; unsigned long start = millis(); // Every 5 seconds we print an update while (millis() - start < 5000) { if (mySerial.available()) { char c = mySerial.read(); // Serial.print(c); // uncomment to see raw GPS data if (gps.encode(c)) { newdata = true; // break; // uncomment to print new data immediately! } } } if (newdata) { Serial.println("Acquired Data"); Serial.println("-------------"); gpsdump(gps); Serial.println("-------------"); Serial.println(); } } void gpsdump(TinyGPS &gps) { long lat, lon; float flat, flon; unsigned long age, date, time, chars; int year; byte month, day, hour, minute, second, hundredths; unsigned short sentences, failed; gps.get_position(&lat, &lon, &age); Serial.print("Lat/Long(10^-5 deg): "); Serial.print(lat); Serial.print(", "); Serial.print(lon); Serial.print(" Fix age: "); Serial.print(age); Serial.println("ms."); // On Arduino, GPS characters may be lost during lengthy Serial.print() // On Teensy, Serial prints to USB, which has large output buffering and // runs very fast, so it's not necessary to worry about missing 4800 // baud GPS characters. gps.f_get_position(&flat, &flon, &age); Serial.print("Lat/Long(float): "); printFloat(flat, 5); Serial.print(", "); printFloat(flon, 5); Serial.print(" Fix age: "); Serial.print(age); Serial.println("ms."); gps.get_datetime(&date, &time, &age); Serial.print("Date(ddmmyy): "); Serial.print(date); Serial.print(" Time(hhmmsscc): "); Serial.print(time); Serial.print(" Fix age: "); Serial.print(age); Serial.println("ms."); gps.crack_datetime(&year, &month, &day, &hour, &minute, &second, &hundredths, &age); Serial.print("Date: "); Serial.print(static_cast<int>(month)); Serial.print("/"); Serial.print(static_cast<int>(day)); Serial.print("/"); Serial.print(year); Serial.print(" Time: "); Serial.print(static_cast<int>(hour)); Serial.print(":"); Serial.print(static_cast<int>(minute)); Serial.print(":"); Serial.print(static_cast<int>(second)); Serial.print("."); Serial.print(static_cast<int>(hundredths)); Serial.print(" Fix age: "); Serial.print(age); Serial.println("ms."); Serial.print("Alt(cm): "); Serial.print(gps.altitude()); Serial.print(" Course(10^-2 deg): "); Serial.print(gps.course()); Serial.print(" Speed(10^-2 knots): "); Serial.println(gps.speed()); Serial.print("Alt(float): "); printFloat(gps.f_altitude()); Serial.print(" Course(float): "); printFloat(gps.f_course()); Serial.println(); Serial.print("Speed(knots): "); printFloat(gps.f_speed_knots()); Serial.print(" (mph): "); printFloat(gps.f_speed_mph()); Serial.print(" (mps): "); printFloat(gps.f_speed_mps()); Serial.print(" (kmph): "); printFloat(gps.f_speed_kmph()); Serial.println(); gps.stats(&chars, &sentences, &failed); Serial.print("Stats: characters: "); Serial.print(chars); Serial.print(" sentences: "); Serial.print(sentences); Serial.print(" failed checksum: "); Serial.println(failed); } void printFloat(double number, int digits) { // Handle negative numbers if (number < 0.0) { Serial.print('-'); number = -number; } // Round correctly so that print(1.999, 2) prints as "2.00" double rounding = 0.5; for (uint8_t i=0; i<digits; ++i) rounding /= 10.0; number += rounding; // Extract the integer part of the number and print it unsigned long int_part = (unsigned long)number; double remainder = number - (double)int_part; Serial.print(int_part); // Print the decimal point, but only if there are digits beyond if (digits > 0) Serial.print("."); // Extract digits from the remainder one at a time while (digits-- > 0) { remainder *= 10.0; int toPrint = int(remainder); Serial.print(toPrint); remainder -= toPrint; } }
일견 복잡해 보이지만 많은 부분이 디버깅용 string 출력 코드입니다.
- setup() 에서 초기화하고 loop() 시작하면서 GPS 데이터를 읽어옵니다.
- 그리고 GPS 데이터 셋을 모두 읽으면 newdata 변수가 true로 셋팅됩니다.
- 이제 gpsdump() 함수로 데이터를 Serial 로 출력해줍니다.
- 필요한 데이터가 있으면 gpsdump() 함수안에서 필요한 데이터를 찾아 저장한 다음 다른 곳에서 사용하면 됩니다.
위 예제 코드 외에도 GPS 라이브러리 안에 포함된 예제(test_with_gps_device.ino)를 사용해도 됩니다. 거의 유사하게 코드가 작성되어 있습니다.
주의!! GPS 모듈을 테스트 해보니 실내(창가)에서는 신호가 잡히지 않는 것 같습니다. GPS 모듈을 들고 야외로 가야 신호가 잡혔습니다. 그리고 야외로 나가더라도 GPS 신호를 잡아서 출력하기까지 상당한 시간 (제 경우는 2분 이상)이 소요되었습니다. 그리고 신호가 잡히지 않는 실내로 들어와도 이전에 잡은 GPS 위치를 계속 받는 등의 문제가 있었습니다. Neo-6M 모듈은 정확도를 요구하는 작업에 쓰기에는 부족할 것 같습니다.
위와 같은 문제 때문에 실외에서 테스트 하기 좋도록 예제 코드를 만들었습니다. 아래 링크에서 다운로드 받으면 됩니다.
들고 다니기 좋도록 아두이노 나노 + GPS + OLED(I2C, SSD1306) + 휴대폰용 보조 배터리를 붙였습니다. 아래처럼 연결하면 됩니다.
- GPS TX –> 아두이노 나노 D2
- GPS RX –> 아두이노 나노 D3
- GPS VCC –> 아두이노 나노 5V
- GPS GND –> 아두이노 나노 GND
- OLED VCC –> 아두이노 나노 3V3
- OLED GND –> 아두이노 나노 GND
- OLED SDA –> 아두이노 나노 A4
- OLED SCL –> 아두이노 나노 A5
야외에 들고 나가서 측정해보면 몇 분 지나 GPS 신호를 잡습니다. GPS 신호가 잡히면 위도, 경도 값을 소수점 2째 자리까지 화면에 표시해줍니다.