一、NMEA协议概述
NMEA 0183 是GPS导航设备的标准数据格式,采用ASCII文本协议,以$
开头、换行符结束,包含多种定位信息语句。
核心特性
-
数据格式:ASCII文本,波特率通常为4800/9600
-
语句结构:
$XXYYY,data1,data2,...,*CC<CR><LF>
-
XX
:设备类型(如GP=GPS,GL=GLONASS) -
YYY
:语句类型(如GGA、RMC) -
*CC
:校验和(异或校验)
-
二、常见NMEA语句解析
1. GGA(Global Positioning System Fix Data)
示例:
$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47
字段解析:
序号 | 字段 | 说明 | 示例值 |
---|---|---|---|
0 | 语句头 | GPGGA | GPGGA |
1 | UTC时间 | 格式hhmmss.ss | 123519 |
2 | 纬度 | 格式ddmm.mmmm | 4807.038 |
3 | 纬度半球 | N(北纬)/S(南纬) | N |
4 | 经度 | 格式dddmm.mmmm | 01131.000 |
5 | 经度半球 | E(东经)/W(西经) | E |
6 | 定位质量 | 0=无效,1=单点,2=差分 | 1 |
7 | 卫星数 | 参与解算的卫星数量 | 08 |
8 | HDOP | 水平精度因子 | 0.9 |
9 | 海拔高度 | 单位:米 | 545.4 |
10 | 大地水准面高 | 单位:米 | 46.9 |
11 | 差分龄期 | 最后差分数据更新时间(秒) | 空 |
12 | 差分站ID | 差分基准站编号 | 空 |
13 | 校验和 | 十六进制异或校验值 | *47 |
数据转换方法:
def dms_to_decimal(dms_str, hemisphere): """ 将度分格式转换为十进制小数 :param dms_str: "ddmm.mmmm" 格式字符串 :param hemisphere: 方向标识(N/S/E/W) :return: 十进制坐标值 """ degrees = float(dms_str[:2]) if len(dms_str) > 5 else float(dms_str[:3]) minutes = float(dms_str[2:]) if len(dms_str) > 5 else float(dms_str[3:]) decimal = degrees + minutes / 60 return decimal if hemisphere in ['N', 'E'] else -decimal
2. RMC(Recommended Minimum Specific GNSS Data)
示例:
$GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*6A
关键字段:
字段 | 说明 | 示例值 |
---|---|---|
2 | 状态(A=有效,V=无效) | A |
7 | 地面速度(节) | 022.4 |
8 | 航向(度) | 084.4 |
9 | UTC日期(DDMMYY) | 230394 |
10 | 磁偏角 | 003.1,W |
三、Python解析实现
1. 基础解析类
class NMEAParser: def __init__(self): self.parsers = { 'GGA': self.parse_gga, 'RMC': self.parse_rmc, 'VTG': self.parse_vtg, 'GSA': self.parse_gsa } def parse(self, sentence): """主解析方法""" if not self.validate_checksum(sentence): return None parts = sentence.strip().split(',') sentence_type = parts[0][3:6] # 提取GGA/RMC等类型 if sentence_type in self.parsers: return self.parsers[sentence_type](parts) return {'raw': sentence} @staticmethod def validate_checksum(sentence): """校验和验证""" try: data, checksum = sentence.split('*') calculated = 0 for c in data[1:]: # 忽略起始$ calculated ^= ord(c) return hex(calculated)[2:].upper() == checksum.strip() except: return False def parse_gga(self, parts): """解析GGA语句""" return { 'type': 'GGA', 'time': self.parse_time(parts[1]), 'latitude': self.dms_to_decimal(parts[2], parts[3]), 'longitude': self.dms_to_decimal(parts[4], parts[5]), 'quality': int(parts[6]), 'satellites': int(parts[7]), 'hdop': float(parts[8]), 'altitude': float(parts[9]), 'geoid_height': float(parts[11]), } def parse_rmc(self, parts): """解析RMC语句""" return { 'type': 'RMC', 'time': self.parse_time(parts[1]), 'status': parts[2], 'latitude': self.dms_to_decimal(parts[3], parts[4]), 'longitude': self.dms_to_decimal(parts[5], parts[6]), 'speed_knots': float(parts[7]), 'course': float(parts[8]), 'date': self.parse_date(parts[9]), 'magnetic_var': float(parts[10]) if parts[10] else 0.0 } @staticmethod def parse_time(time_str): """转换时间 hhmmss.ss → 秒数""" if not time_str: return 0.0 h = int(time_str[0:2]) m = int(time_str[2:4]) s = float(time_str[4:]) return h * 3600 + m * 60 + s @staticmethod def parse_date(date_str): """转换日期 DDMMYY → YYYY-MM-DD""" if len(date_str) != 6: return None day = date_str[0:2] month = date_str[2:4] year = f"20{date_str[4:6]}" # 假设为2000年后 return f"{year}-{month}-{day}"
2. 使用示例
# 实例化解析器 parser = NMEAParser() # 示例数据 gga_data = "$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47" rmc_data = "$GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*6A" # 解析数据 parsed_gga = parser.parse(gga_data) parsed_rmc = parser.parse(rmc_data) print("GGA解析结果:", parsed_gga) print("RMC解析结果:", parsed_rmc)
输出结果:
GGA解析结果: { 'type': 'GGA', 'time': 44719.0, 'latitude': 48.1173, # 48°07.038' N → 48 + 7.038/60 'longitude': 11.516666666666667, # 11°31.000' E 'quality': 1, 'satellites': 8, 'hdop': 0.9, 'altitude': 545.4, 'geoid_height': 46.9 } RMC解析结果: { 'type': 'RMC', 'time': 44719.0, 'status': 'A', 'latitude': 48.1173, 'longitude': 11.516666666666667, 'speed_knots': 22.4, 'course': 84.4, 'date': '2023-03-23', # 日期230394 → 2023-09-23(需根据实际年份调整) 'magnetic_var': -3.1 # W表示负值 }
四、高级功能扩展
1. 实时数据流处理
import serial class RealTimeParser: def __init__(self, port, baudrate=9600): self.ser = serial.Serial(port, baudrate, timeout=1) self.parser = NMEAParser() def start(self): buffer = "" while True: data = self.ser.read(1024).decode() buffer += data while '\n' in buffer: line, buffer = buffer.split('\n', 1) parsed = self.parser.parse(line.strip()) if parsed: self.handle_data(parsed) def handle_data(self, data): # 自定义数据处理(存储/显示) print(f"[{data['type']}] 位置: {data['latitude']:.6f}, {data['longitude']:.6f}")
2. 错误处理增强
def safe_parse(parser, sentence): try: return parser.parse(sentence) except (ValueError, IndexError) as e: print(f"解析错误: {e},原始数据: {sentence}") return None
3. 数据可视化(集成PyQt)
from PyQt5.QtCore import QObject, pyqtSignal class NMEAProcessor(QObject): new_position = pyqtSignal(float, float) # 经纬度信号 def __init__(self): super().__init__() self.parser = NMEAParser() def process_sentence(self, sentence): data = self.parser.parse(sentence) if data and 'latitude' in data and 'longitude' in data: self.new_position.emit(data['latitude'], data['longitude'])
五、校验和计算工具
def calculate_checksum(sentence): """ 计算NMEA校验和 :param sentence: 不含*CC的语句(如"GPGGA,123519,...") :return: 校验和字符串(如"47") """ check = 0 for c in sentence: check ^= ord(c) return f"{check:02X}"
六、调试与验证
测试用例:
# 验证校验和 test_sentence = "GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,," expected_checksum = "47" assert calculate_checksum(test_sentence) == expected_checksum # 验证坐标转换 assert abs(parser.dms_to_decimal("4807.038", "N") - 48.1173) < 0.0001
七、GPS语句含义及C语言解析
-
GPGSV:可见卫星信息
-
GPGLL:地理定位信息
-
GPRMC:推荐最小定位信息
-
GPVTG:地面速度信息
-
GPGGA:GPS定位信息
-
GPGSA:当前卫星信息
GPRMC 最小定位信息
例:$GPRMC,024813.640,A,3158.4608,N,11848.3737,E,10.05,324.27,150706,,,A*50
字段0:GPRMC,语句ID,表明该语句为RecommendedMinimumSpecificGPS/TRANSITData(RMC)推荐最小定位信息字段1:UTC时间,hhmmss.sss格式字段2:状态,A=定位,V=未定位字段3:纬度ddmm.mmmm,度分格式(前导位数不足则补0)字段4:纬度N(北纬)或S(南纬)字段5:经度dddmm.mmmm,度分格式(前导位数不足则补0)字段6:经度E(东经)或W(西经)字段7:速度,节,Knots字段8:方位角,度字段9:UTC日期,DDMMYY格式字段10:磁偏角,(000−180)度(前导位数不足则补0)字段11:磁偏角方向,E=东W=西字段12:模式,A=自动,D=差分,E=估测,N=数据无效(3.0协议内容)字段13:校验值(GPRMC,语句ID,表明该语句为RecommendedMinimumSpecificGPS/TRANSITData(RMC)推荐最小定位信息字段1:UTC时间,hhmmss.sss格式字段2:状态,A=定位,V=未定位字段3:纬度ddmm.mmmm,度分格式(前导位数不足则补0)字段4:纬度N(北纬)或S(南纬)字段5:经度dddmm.mmmm,度分格式(前导位数不足则补0)字段6:经度E(东经)或W(西经)字段7:速度,节,Knots字段8:方位角,度字段9:UTC日期,DDMMYY格式字段10:磁偏角,(000−180)度(前导位数不足则补0)字段11:磁偏角方向,E=东W=西字段12:模式,A=自动,D=差分,E=估测,N=数据无效(3.0协议内容)字段13:校验值(与*之间的数异或后的值)
GPGGA GPS定位数据
例:$GPGGA,092204.999,4250.5589,S,14718.5084,E,1,04,24.4,12.2,M,19.7,M,,0000*1F
字段0:GPGGA,语句ID,表明该语句为GlobalPositioningSystemFixData(GGA)GPS定位信息字段1:UTC时间,hhmmss.sss,时分秒格式字段2:纬度ddmm.mmmm,度分格式(前导位数不足则补0)字段3:纬度N(北纬)或S(南纬)字段4:经度dddmm.mmmm,度分格式(前导位数不足则补0)字段5:经度E(东经)或W(西经)字段6:GPS状态,0=不可用(FIXNOTvalid),1=单点定位(GPSFIX),2=差分定位(DGPS),3=无效PPS,4=实时差分定位(RTKFIX),5=RTKFLOAT,6=正在估算字段7:正在使用的卫星数量(00−12)(前导位数不足则补0)字段8:HDOP水平精度因子(0.5−99.9)字段9:海拔高度(−9999.9−99999.9)字段10:单位:M(米)字段11:地球椭球面相对大地水准面的高度WGS84水准面划分字段12:WGS84水准面划分单位:M(米)字段13:差分时间(从接收到差分信号开始的秒数,如果不是差分定位将为空)字段14:差分站ID号0000−1023(前导位数不足则补0,如果不是差分定位将为空)字段15:校验值(GPGGA,语句ID,表明该语句为GlobalPositioningSystemFixData(GGA)GPS定位信息字段1:UTC时间,hhmmss.sss,时分秒格式字段2:纬度ddmm.mmmm,度分格式(前导位数不足则补0)字段3:纬度N(北纬)或S(南纬)字段4:经度dddmm.mmmm,度分格式(前导位数不足则补0)字段5:经度E(东经)或W(西经)字段6:GPS状态,0=不可用(FIXNOTvalid),1=单点定位(GPSFIX),2=差分定位(DGPS),3=无效PPS,4=实时差分定位(RTKFIX),5=RTKFLOAT,6=正在估算字段7:正在使用的卫星数量(00−12)(前导位数不足则补0)字段8:HDOP水平精度因子(0.5−99.9)字段9:海拔高度(−9999.9−99999.9)字段10:单位:M(米)字段11:地球椭球面相对大地水准面的高度WGS84水准面划分字段12:WGS84水准面划分单位:M(米)字段13:差分时间(从接收到差分信号开始的秒数,如果不是差分定位将为空)字段14:差分站ID号0000−1023(前导位数不足则补0,如果不是差分定位将为空)字段15:校验值(与*之间的数异或后的值)
GPVTG 地面速度信息
例:$GPVTG,89.68,T,,M,0.00,N,0.0,K*5F
字段0:GPVTG,语句ID,表明该语句为TrackMadeGoodandGroundSpeed(VTG)地面速度信息字段1:运动角度,000−359,(前导位数不足则补0)字段2:T=真北参照系字段3:运动角度,000−359,(前导位数不足则补0)字段4:M=磁北参照系字段5:水平运动速度(0.00)(前导位数不足则补0)字段6:N=节,Knots字段7:水平运动速度(0.00)(前导位数不足则补0)字段8:K=公里/时,km/h字段9:校验值(GPVTG,语句ID,表明该语句为TrackMadeGoodandGroundSpeed(VTG)地面速度信息字段1:运动角度,000−359,(前导位数不足则补0)字段2:T=真北参照系字段3:运动角度,000−359,(前导位数不足则补0)字段4:M=磁北参照系字段5:水平运动速度(0.00)(前导位数不足则补0)字段6:N=节,Knots字段7:水平运动速度(0.00)(前导位数不足则补0)字段8:K=公里/时,km/h字段9:校验值(与*之间的数异或后的值)
GPGSV 可视卫星状态
例:$GPGSV,3,1,10,20,78,331,45,01,59,235,47,22,41,069,,13,32,252,45*70
字段0:GPGSV,语句ID,表明该语句为GPSSatellitesinView(GSV)可见卫星信息字段1:本次GSV语句的总数目(1−3)字段2:本条GSV语句是本次GSV语句的第几条(1−3)字段3:当前可见卫星总数(00−12)(前导位数不足则补0)字段4:PRN码(伪随机噪声码)(01−32)(前导位数不足则补0)字段5:卫星仰角(00−90)度(前导位数不足则补0)字段6:卫星方位角(00−359)度(前导位数不足则补0)字段7:信噪比(00-99)dbHz字段8:PRN码(伪随机噪声码)(01−32)(前导位数不足则补0)字段9:卫星仰角(00−90)度(前导位数不足则补0)字段10:卫星方位角(00−359)度(前导位数不足则补0)字段11:信噪比(00-99)dbHz字段12:PRN码(伪随机噪声码)(01−32)(前导位数不足则补0)字段13:卫星仰角(00−90)度(前导位数不足则补0)字段14:卫星方位角(00−359)度(前导位数不足则补0)字段15:信噪比(00-99)dbHz字段16:校验值(GPGSV,语句ID,表明该语句为GPSSatellitesinView(GSV)可见卫星信息字段1:本次GSV语句的总数目(1−3)字段2:本条GSV语句是本次GSV语句的第几条(1−3)字段3:当前可见卫星总数(00−12)(前导位数不足则补0)字段4:PRN码(伪随机噪声码)(01−32)(前导位数不足则补0)字段5:卫星仰角(00−90)度(前导位数不足则补0)字段6:卫星方位角(00−359)度(前导位数不足则补0)字段7:信噪比(00-99)dbHz字段8:PRN码(伪随机噪声码)(01−32)(前导位数不足则补0)字段9:卫星仰角(00−90)度(前导位数不足则补0)字段10:卫星方位角(00−359)度(前导位数不足则补0)字段11:信噪比(00-99)dbHz字段12:PRN码(伪随机噪声码)(01−32)(前导位数不足则补0)字段13:卫星仰角(00−90)度(前导位数不足则补0)字段14:卫星方位角(00−359)度(前导位数不足则补0)字段15:信噪比(00-99)dbHz字段16:校验值(与*之间的数异或后的值)
GPGSA 当前卫星信息
例:$GPGSA,A,3,01,20,19,13,,,,,,,,,40.4,24.4,32.2*0A
字段0:GPGSA,语句ID,表明该语句为GPSDOPandActiveSatellites(GSA)当前卫星信息字段1:定位模式(选择2D/3D),A=自动选择,M=手动选择字段2:定位类型,1=未定位,2=2D定位,3=3D定位字段3:PRN码(伪随机噪声码),第1信道正在使用的卫星PRN码编号(00)(前导位数不足则补0)字段4:PRN码(伪随机噪声码),第2信道正在使用的卫星PRN码编号(00)(前导位数不足则补0)字段5:PRN码(伪随机噪声码),第3信道正在使用的卫星PRN码编号(00)(前导位数不足则补0)字段6:PRN码(伪随机噪声码),第4信道正在使用的卫星PRN码编号(00)(前导位数不足则补0)字段7:PRN码(伪随机噪声码),第5信道正在使用的卫星PRN码编号(00)(前导位数不足则补0)字段8:PRN码(伪随机噪声码),第6信道正在使用的卫星PRN码编号(00)(前导位数不足则补0)字段9:PRN码(伪随机噪声码),第7信道正在使用的卫星PRN码编号(00)(前导位数不足则补0)字段10:PRN码(伪随机噪声码),第8信道正在使用的卫星PRN码编号(00)(前导位数不足则补0)字段11:PRN码(伪随机噪声码),第9信道正在使用的卫星PRN码编号(00)(前导位数不足则补0)字段12:PRN码(伪随机噪声码),第10信道正在使用的卫星PRN码编号(00)(前导位数不足则补0)字段13:PRN码(伪随机噪声码),第11信道正在使用的卫星PRN码编号(00)(前导位数不足则补0)字段14:PRN码(伪随机噪声码),第12信道正在使用的卫星PRN码编号(00)(前导位数不足则补0)字段15:PDOP综合位置精度因子(0.5−99.9)字段16:HDOP水平精度因子(0.5−99.9)字段17:VDOP垂直精度因子(0.5−99.9)字段18:校验值(GPGSA,语句ID,表明该语句为GPSDOPandActiveSatellites(GSA)当前卫星信息字段1:定位模式(选择2D/3D),A=自动选择,M=手动选择字段2:定位类型,1=未定位,2=2D定位,3=3D定位字段3:PRN码(伪随机噪声码),第1信道正在使用的卫星PRN码编号(00)(前导位数不足则补0)字段4:PRN码(伪随机噪声码),第2信道正在使用的卫星PRN码编号(00)(前导位数不足则补0)字段5:PRN码(伪随机噪声码),第3信道正在使用的卫星PRN码编号(00)(前导位数不足则补0)字段6:PRN码(伪随机噪声码),第4信道正在使用的卫星PRN码编号(00)(前导位数不足则补0)字段7:PRN码(伪随机噪声码),第5信道正在使用的卫星PRN码编号(00)(前导位数不足则补0)字段8:PRN码(伪随机噪声码),第6信道正在使用的卫星PRN码编号(00)(前导位数不足则补0)字段9:PRN码(伪随机噪声码),第7信道正在使用的卫星PRN码编号(00)(前导位数不足则补0)字段10:PRN码(伪随机噪声码),第8信道正在使用的卫星PRN码编号(00)(前导位数不足则补0)字段11:PRN码(伪随机噪声码),第9信道正在使用的卫星PRN码编号(00)(前导位数不足则补0)字段12:PRN码(伪随机噪声码),第10信道正在使用的卫星PRN码编号(00)(前导位数不足则补0)字段13:PRN码(伪随机噪声码),第11信道正在使用的卫星PRN码编号(00)(前导位数不足则补0)字段14:PRN码(伪随机噪声码),第12信道正在使用的卫星PRN码编号(00)(前导位数不足则补0)字段15:PDOP综合位置精度因子(0.5−99.9)字段16:HDOP水平精度因子(0.5−99.9)字段17:VDOP垂直精度因子(0.5−99.9)字段18:校验值(与*之间的数异或后的值)
代码分析
代码较多,这里只分析RMC、GSA和GSV三个语句的。
RMC解析
RMC结构体
//RMC数据结构体(推荐定位信息数据格式)
typedef struct
{
char utc[11]; // UTC时间,hhmmss.sss格式
unsigned char pos_status; // 状态,A=定位,V=未定位
double lat; // 纬度ddmm.mmmm,度分格式
char lat_dir; // 纬度N(北纬)或S(南纬)
double lon; // 经度dddmm.mmmm,度分格式
char lon_dir; // 经度E(东经)或W(西经)
double speed_Kn; // 速度
double track_true; // 方位角,度
char date[7]; // UTC日期,DDMMYY格式
double mag_var; // 磁偏角,(000 - 180)度
char var_dir; // 磁偏角方向,E=东W=西
char mode_ind; // 模式,A=自动,D=差分,E=估测,N=数据无效(3.0协议内容)
}RMC;
RMC解析函数
// RMC数据解析
static RMC rmc_data_parse(char *rmc_data) // 定义RMC结构体函数
{
RMC rmc;
unsigned char times = 0;
char *p;
char *s = strdup(rmc_data); // 将传入的数据拷贝一份,否则分割函数会影响原始数据
// strtok遇到两个连续的分割符(,)时,无法正常切割,所以自己写了个函数,在源文件中
p = strsplit(&s, ","); // 开始分割,按','切分,p为切割出来的字段
while (p)
{
switch (times) // times记录当前切割的位置
{
case 1: // UTC
strcpy(rmc.utc, p);
break;
case 2: // pos status
rmc.pos_status = p[0];
break;
case 3: // lat
rmc.lat = strtod(p, NULL);
break;
case 4: // lat dir
rmc.lat_dir = p[0];
break;
case 5: // lon
rmc.lon = strtod(p, NULL);
break;
case 6: // lon dir
rmc.lon_dir = p[0];
break;
case 7: // speen Kn
rmc.speed_Kn = strtod(p, NULL);
break;
case 8: // track true
rmc.track_true = strtod(p, NULL);
break;
case 9: // date
strcpy(rmc.date, p);
break;
case 10: // mag var
rmc.mag_var = strtod(p, NULL);
break;
case 11: // var dir
rmc.var_dir = p[0];
break;
case 12: // mode ind
rmc.mode_ind = p[0];
break;
default:
break;
}
p = strsplit(&s, ","); // 继续切割
times++;
}
free(s);
return rmc;
}
GSA解析
GSA结构体
#pragma pack(1) // 便于指针偏移取值
// 信道信息结构体
typedef struct
{
unsigned char total; // 总信道个数
unsigned char prn_ID; // prn信道
unsigned char prn; // PRN 码(伪随机噪声码)
}GSA_PRN;
#pragma pack()
//GPGSA数据结构体(当前卫星信息)
typedef struct
{
unsigned char mode_MA; // 定位模式(选择2D/3D),A=自动选择,M=手动选择
unsigned char mode_123; // 定位类型,1=未定位,2=2D定位,3=3D定位
double pdop; // PDOP综合位置精度因子(0.5 - 99.9)
double hdop; // HDOP水平精度因子(0.5 - 99.9)
double vdop; // VDOP垂直精度因子(0.5 - 99.9)
GSA_PRN *gsa_prn; // 存放信道信息
}GSA;
GSA解析函数
// 得到GSA数据中的信道信息,由于一帧GPS数据中,GSA语句的个数不一致,所以需要传入原始的GPS数据,找出所有的GSA字段
static GSA_PRN *get_prn_data(char *gps_data)
{
GSA_PRN *gsa_prn;
unsigned char times = 0;
unsigned char i;
unsigned char sentences_index = 0; // 累计找到gsa字段的个数
// 从语句中切割的字段
char *p;
// 存放拷贝的语句
char *s;
// 从原始数据中切割出来的字段
char *sentences;
// gsa语句的个数
int gsa_count;
// 统计GSA字段的个数
gsa_count = strstr_cnt(gps_data, PRE_GSA);
// 根据GSA语句的个数,动态分配内存
gsa_prn = (GSA_PRN *)malloc(sizeof(GSA_PRN) * (gsa_count * 12 + 1));
memset(gsa_prn, 0, sizeof(GSA_PRN) * (gsa_count * 12 + 1));
// 切割原始数据
sentences = strtok(gps_data, "\r\n");
while (sentences)
{
// 判断切割出来的语句是否是GSA语句
if (strstr(sentences, PRE_GSA))
{
// 每找到一条GSA语句,就加1
sentences_index++;
s = strdup(sentences);
// 开始切割语句
p = strsplit(&s, ",");
while (p)
{
if (times == 2)
{
// 每条GSA语句包含12个卫星信息
for (i=0; i<12; i++)
{
p = strsplit(&s, ",");
(gsa_prn+i+(sentences_index-1)*12)->total = (unsigned char)gsa_count * 12;
(gsa_prn+i+(sentences_index-1)*12)->prn_ID = i + (sentences_index - 1) * 12;
(gsa_prn+i+(sentences_index-1)*12)->prn = (unsigned char)strtol(p, NULL, 10);
}
}
// 继续下一次语句切割
p = strsplit(&s, ",");
times++;
}
times = 0;
}
// 继续下一次原始数据切割
sentences = strtok(NULL, "\r\n");
}
free(s);
return gsa_prn;
}
// GSA数据解析
//gsa_data: 传入的GSA语句
//gpsdata: 传入的原始GPS数据
static GSA gsa_data_parse(char *gsa_data, char *gpsdata)
{
GSA gsa;
unsigned char times = 0;
char *p;
char *end;
// GSA语句拷贝到s中,方便切割
char *s = strdup(gsa_data);
// 将原始数据拷贝一份
char *alldata = strdup(gpsdata);
p = strsplit(&s, ",");
while (p)
{
switch (times)
{
case 1: // mode_MA
gsa.mode_MA = p[0];
break;
case 2: // mode_123
gsa.mode_123 = p[0];
break;
case 3: // prn
// 获得所有GSA语句中的PRN信息,传入原始的GPS数据
gsa.gsa_prn = get_prn_data(alldata);
break;
case 15: // pdop
gsa.pdop = strtod(p, NULL);
break;
case 16: // hdop
gsa.hdop = strtod(p, NULL);
break;
case 17: // vdop
// 提取最后一个数据
end = (char *)malloc(sizeof(p));
strncpy(end, p, strlen(p)-3);
end[strlen(p)-3] = '\0';
gsa.vdop = strtod(end, NULL);
free(end);
default:
break;
}
p = strsplit(&s, ",");
times++;
}
free(s);
return gsa;
}
GSV解析
GSV结构体
// 可见卫星信息结构体
typedef struct
{
unsigned char prn; // PRN 码(伪随机噪声码)
unsigned char elev; // 卫星仰角(00 - 90)度
unsigned short azimuth; // 卫星方位角(00 - 359)度
unsigned char SNR; // 信噪比
}SAT_INFO;
#pragma pack()
// GPGSV数据结构体(可见卫星信息)
typedef struct
{
unsigned char msgs; // 本次GSV语句的总数目(1 - 3)
unsigned char msg; // 本条GSV语句是本次GSV语句的第几条(1 - 3)
unsigned char sats; // 当前可见卫星总数(00 - 12)
SAT_INFO *sat_info; // 卫星信息
}GSV;
GSV解析函数
/*
* function: 获取GSV字段中的GPS信息
* gps_data: 最原始的GPS字符串,用于找到所有的GSV语句
* stas: 卫星数量
* prefix: GSV信息字段前缀
*/
static SAT_INFO *get_sats_info(char *gps_data, unsigned char sats, char *prefix)
{
SAT_INFO *sats_info;
unsigned char times = 0;
// 保存GSV语句总数
unsigned char msgs = 0;
// 记录当前时第几条GSV语句
unsigned char msg = 0;
// 存放计算完的for循环次数
unsigned char for_times;
unsigned char i;
// 语句切割出来的字段
char *p;
// 拷贝一份语句便于切割
char *s;
// 从原始数据中切割出来的语句
char *sentences;
sats_info = (SAT_INFO *)malloc(sizeof(SAT_INFO) * (sats+1));
memset(sats_info, 0, sizeof(SAT_INFO) * (sats+1));
sentences = strtok(gps_data, "\r\n");
while (sentences)
{
if (strstr(sentences, prefix))
{
s = strdup(sentences);
p = strsplit(&s, ",");
while (p)
{
switch (times)
{
case 1: // msgs
msgs = (unsigned char) strtol(p, NULL, 10);
break;
case 2: // msg
msg = (unsigned char) strtol(p, NULL, 10);
break;
case 3: // sat info
// 计算当前GSV语句卫星信息的个数,也就是for循环的次数
for_times = (msgs == msg) ? ((sats % 4) ? sats % 4 : 4) : 4;
for (i = 0; i < for_times; i++)
{
// 从第4个字段开始,每4段代表一个卫星的信息
p = strsplit(&s, ",");
(sats_info+(msg-1)*4+i)->prn = (unsigned char) strtol(p, NULL, 10);
p = strsplit(&s, ",");
(sats_info+(msg-1)*4+i)->elev = (unsigned char) strtol(p, NULL, 10);
p = strsplit(&s, ",");
(sats_info+(msg-1)*4+i)->azimuth = (unsigned short) strtol(p, NULL, 10);
p = strsplit(&s, ",");
(sats_info+(msg-1)*4+i)->SNR = (unsigned char) strtol(p, NULL, 10);
}
break;
default:
break;
}
p = strsplit(&s, ",");
times++;
}
times = 0;
}
// 切割出下一个语句
sentences = strtok(NULL, "\r\n");
}
free(s);
return sats_info;
}
// GSV数据解析
// gsv_data: 传入的GSV语句,用于提取GSV语句总数和卫星总数
// gps_data: 原始的GPS数据,用于在函数get_sats_info中找到所有的GSV语句
// prefix: GSV语句的前缀,根据不同的定位组合方式,在一组GPS数据中可能包含GPGSV、GLGSV和GNGSV,根据需要传入
static GSV gsv_data_parse(char *gsv_data, char *gps_data, char *prefix)
{
GSV gsv;
unsigned char times = 0;
char *p;
char *s = strdup(gsv_data);
char *src_data = strdup(gps_data);
p = strsplit(&s, ",");
while (p)
{
switch (times)
{
case 1: // msgs
gsv.msgs = (unsigned char)strtol(p, NULL, 10);
break;
case 2: // msg
gsv.msg = (unsigned char)strtol(p, NULL, 10);
break;
case 3: // sats
gsv.sats = (unsigned char)strtol(p, NULL, 10);
// 获得所有GSV语句中的卫星信息。传入原始的GPS数据,卫星总数和GSV语句的ID
gsv.sat_info = get_sats_info(src_data, gsv.sats, prefix);
break;
default:
break;
}
p = strsplit(&s, ",");
times++;
}
free(s);
return gsv;
}