在 Windows CE 與 Windows Mobile 裝置上實作 GPS 功能,核心在於 GPS 接收器。它如同收音機般從多顆衛星接收訊號,經由內建韌體演算法計算位置、速度等資訊,再依照 NMEA 0183 協定格式輸出。NMEA 全稱「National Marine Electronics Association」,早期用於航海設備,現廣泛應用於 GPS 裝置,因此速度單位仍採用「節」(Knot,海浬/小時)。
每筆 NMEA 句子具有以下特徵:
$ 符號開頭GP 代表通用 GPS,P 開頭為廠商擴充)RMC、GGA)* 結尾,後接兩位十六進位 Checksum($ 到 * 之間所有字元的 XOR 運算)<CR><LF> 換行結束範例資料:
$GPRMC,000006,A,3754.6240,S,14509.7720,E,010.8,313.1,010108,011.8,E*6A
$GPGGA,201033,3754.6240,S,14509.7720,E,1,05,1.7,91.1,M,-1.1,M,,*75
$GPGSA,A,3,,05,10,,,,21,,29,30,,,2.9,1.7,1.3*32
$GPGSV,3,3,12,29,74,163,41,30,53,337,40,31,09,266,00,37,00,000,00*78
$PGRME,6.3,M,11.9,M,13.5,M*25
$PGRMB,0.0,200,,,,K,,N,W*28
$PGRMM,WGS 84*06
解析流程:接收句子 → 驗證格式與 Checksum → 依 Talker+Type 分派 → 逐欄位提取所需資訊。以下為 C# 核心實作:
public bool Parse(string sentence)
{
string rawData = sentence;
try
{
if (!IsValid(sentence)) return false;
// 移除 $ 與 * 及其後校驗碼
sentence = sentence.Substring(1, sentence.IndexOf('*') - 1);
string[] words = GetWords(sentence);
switch (words[0])
{
case "GPRMC": return ParseGPRMC(words);
case "GPGGA": return ParseGPGGA(words);
case "GPGSA": return ParseGPGSA(words);
case "GPGSV": return ParseGPGSV(words);
default: return false;
}
}
catch (Exception e)
{
Console.WriteLine(e.Message + rawData);
return false;
}
}
包含 UTC 時間、定位狀態、經緯度、速度、方向、日期等。解析時需注意經緯度格式為「度分.秒」,需轉換為十進位度數;南緯/西經以負號表示。
private bool ParseGPRMC(string[] words)
{
// UTC 時間 (欄位1) 與日期 (欄位9)
if (words[1].Length > 0 && words[9].Length > 0)
{
int utcHours = int.Parse(words[1].Substring(0, 2));
int utcMinutes = int.Parse(words[1].Substring(2, 2));
int utcSeconds = int.Parse(words[1].Substring(4, 2));
int utcMilliseconds = words[1].Length > 7 ? int.Parse(words[1].Substring(7)) : 0;
int utcDay = int.Parse(words[9].Substring(0, 2));
int utcMonth = int.Parse(words[9].Substring(2, 2));
int utcYear = int.Parse(words[9].Substring(4, 2)) + 2000;
utcDateTime = new DateTime(utcYear, utcMonth, utcDay,
utcHours, utcMinutes, utcSeconds, utcMilliseconds);
}
// 定位狀態: A=有效, V=無效
fixStatus = (words[2][0] == 'A') ? FixStatus.Obtained : FixStatus.Lost;
// 經緯度 (欄位3-6)
if (words[3].Length > 0 && words[4].Length == 1 &&
words[5].Length > 0 && words[6].Length == 1)
{
latitude.Hours = int.Parse(words[3].Substring(0, 2));
latitude.Minutes = int.Parse(words[3].Substring(2, 2));
latitude.Seconds = Math.Round(double.Parse(words[3].Substring(5, 4)) * 6 / 1000.0, 3);
if ("S" == words[4]) latitude.Hours = -latitude.Hours;
longitude.Hours = int.Parse(words[5].Substring(0, 3));
longitude.Minutes = int.Parse(words[5].Substring(3, 2));
longitude.Seconds = Math.Round(double.Parse(words[5].Substring(6, 4)) * 6 / 1000.0, 3);
if ("W" == words[6]) longitude.Hours = -longitude.Hours;
}
// 速度 (欄位7, 節) 轉 km/h
if (words[7].Length > 0)
velocity = decimal.Parse(words[7], NmeaCultureInfo) * KMpHPerKnot;
// 方向角 (欄位8)
if (words[8].Length > 0)
azimuth = decimal.Parse(words[8], NmeaCultureInfo);
return true;
}
提供固定品質、使用衛星數、水平精度因子 (HDOP)、海拔高度等關鍵資訊。
private bool ParseGPGGA(string[] words)
{
// 固定品質 (欄位6)
if (words[6].Length > 0)
{
switch (int.Parse(words[6]))
{
case 0: differentialGpsType = DifferentialGpsType.NotSet; break;
case 1: differentialGpsType = DifferentialGpsType.SPS; break; // 標準定位
case 2: differentialGpsType = DifferentialGpsType.DSPS; break; // DGPS 地面站
case 3: differentialGpsType = DifferentialGpsType.PPS; break; // 精密定位
case 4: differentialGpsType = DifferentialGpsType.RTK; break; // 即時動態
default: differentialGpsType = DifferentialGpsType.NotSet; break;
}
}
// 使用衛星數 (欄位7)
if (words[7].Length > 0)
satellitesInUsed = int.Parse(words[7]);
// HDOP (欄位8)
if (words[8].Length > 0)
horizontalDilutionOfPrecision = decimal.Parse(words[8]);
// 海拔 (欄位9, 公尺)
if (words[9].Length > 0)
altitude = decimal.Parse(words[9]);
return true;
}
描述定位模式 (2D/3D)、定位方法、參與定位的衛星 PRN 號、位置/水平/垂直精度因子 (PDOP/HDOP/VDOP)。
多句子分段傳輸,每句最多 4 顆衛星,包含衛星 PRN、仰角、方位角、訊雜比 (SNR)。

理解 NMEA 解析雖看似「重造輪子」,但廠商擴充句子 (如 $PGRMx) 往往不被 GPS Intermediate Driver 支援,自行解析仍具必要性。下篇將探討廠商擴充句子處理與效能優化。