GPS模块的授时精度
实验设备
购于淘宝亚博店的GPS模块。GPS模块是基于 ATGM336H-5N 的高性能 BDS/GNSS 定位导航模块。模块支持多种卫星导航系统,包括中国的北斗二号和北斗三号全部卫星,美国的 GPS,俄罗斯的 GLONASS,日本的 QZSS,可以同时接收以上卫星导航系统的卫星信号,并且实现联合定位、导航与授时,模块具有高灵敏度、低功耗、低成本等优势,适用于车载导航、手持定位、可穿戴设备。
报文解读
连接上位机后,收到如下所示的GPS报文:
[16:53:03] $GNVTG,,,,,,,,,M*2D
[16:53:03] $GNZDA,085302.300,29,03,2023,00,00*4C
[16:53:03] $GPTXT,01,01,01,ANTENNA OK*35
[16:53:03] $GNGGA,085302.400,,,,,0,00,25.5,,,,,,*72
[16:53:03] $GNGLL,,,,,085302.400,V,M*6F
[16:53:03] $GPGSA,A,1,,,,,,,,,,,,,25.5,25.5,25.5*02
[16:53:03] $BDGSA,A,1,,,,,,,,,,,,,25.5,25.5,25.5*13
[16:53:03] $GLGSA,A,1,,,,,,,,,,,,,25.5,25.5,25.5*1E
[16:53:03] $GPGSV,3,1,11,01,09,057,,03,38,047,,04,14,104,,06,50,287,*70
[16:53:03] $GPGSV,3,2,11,09,13,138,,11,16,257,,14,47,182,,17,66,029,*7E
[16:53:03] $GPGSV,3,3,11,19,52,333,,194,44,168,23,199,53,169,16*49
[16:53:03] $BDGSV,1,1,03,21,48,108,,22,22,050,,38,73,246,*57
[16:53:03] $GLGSV,1,1,01,65,41,115,21*54
[16:53:03] $GNRMC,085302.400,V,,,,,,,290323,,,M*51
[16:53:03] $GNVTG,,,,,,,,,M*2D
[16:53:03] $GNZDA,085302.400,29,03,2023,00,00*4B
[16:53:03] $GPTXT,01,01,01,ANTENNA OK*35
[16:53:03] $GNGGA,085302.500,,,,,0,00,25.5,,,,,,*73
[16:53:03] $GNGLL,,,,,085302.500,V,M*6E
[16:53:03] $GPGSA,A,1,,,,,,,,,,,,,25.5,25.5,25.5*02
[16:53:03] $BDGSA,A,1,,,,,,,,,,,,,25.5,25.5,25.5*13
[16:53:03] $GLGSA,A,1,,,,,,,,,,,,,25.5,25.5,25.5*1E
[16:53:03] $GPGSV,3,1,11,01,09,057,,03,38,047,,04,14,104,,06,50,287,*70
[16:53:03] $GPGSV,3,2,11,09,13,138,,11,16,257,,14,47,182,,17,66,029,*7E
[16:53:03] $GPGSV,3,3,11,19,52,333,,194,44,168,23,199,53,169,16*49
[16:53:03] $BDGSV,1,1,03,21,48,108,,22,22,050,,38,73,246,*57
上述报文为NMEA格式。NMEA是National Marine Electronics Association 的缩写,是美国国家海洋电子协会的简称,现在是GPS导航设备统一的RTCM标准协议。(百度百科即有各个字段的详细解释:https://baike.baidu.com/item/NMEA/9812575?fr=aladdin)
我们最关心的GPS授时所在的行:
[16:53:03] $GNGGA,085302.400,,,,,0,00,25.5,,,,,,*72
其对应的格式:
报文格式如下:
$GNGGA,[utc_time],[ weidu],[ NS],[ jingdu],[ EW],[state],[num],[hdop],[haiba_gao],[gao_danwei],[tuoqiu],[chafen_time],[chafen_id],[jiaoyan],[tagid],[power],[信号强度],[保留1],[保留2],[保留3]
数据示例:
$GNGGA,045449.000,3951.764319,N,11615.386554,E,2,19,0.70,57.726,M,-9.89,M,11,0000*69,1872,2d,19,0,0,0
*字段说明:
*1.$GNGGA;//包头
*2.utc_time;//字段1:UTC 时间,hhmmss.sss,时分秒格式
*3.weidu;//字段2:纬度ddmm.mmmm,度分格式(前导位数不足则补0)
*4.NS;//字段3:纬度N(北纬)或S(南纬)
*5.jingdu;//字段4:经度dddmm.mmmm,度分格式(前导位数不足则补0)
*6.EW;//字段5:经度E(东经)或W(西经)
*7.state;//字段6:定位质量GPS状态,0.初始化,1.单点定位,2.码差分,3.无效PPS,4.固定解,5.浮点解, 6.正在估算, 7.人工输入固定值,8.模拟模式,9.WAAS差分
*8.num;//字段7:正在使用的卫星数量(00 - 12)(前导位数不足则补0)
*9.hdop;//字段8:HDOP水平精度因子(0.5 - 99.9)
*10.haiba_gao;//字段9:天线距离海平面高度(-9999.9 - 99999.9)
*11.gao_danwei;//字段10:高度单位M表示米
*12.tuoqiu;//字段11:地球椭球面相对大地水准面的高度
*13.chafen_time;//字段12:差分时间(从最近一次接收到差分信号开始的秒数,如果不是差分定位将为空)
*14.chafen_id;//字段13:差分站ID号0000 - 1023(前导位数不足则补0,如果不是差分定位将为空)
*15.jiaoyan;//字段14:校验值
*16.tagid;//字段15,:设备id,低位在前高位在后HEX格式
*17.power;//字段16;电量,HEX格式,0~100
*18.卫星信号强度
*19.保留位1
*20.保留位2
*21.保留位3
Python解析串口数据
模块附带的解析代码非常简单,就是找相应字段,对应字段进行匹配以拿到感兴趣的数据。
# coding: utf-8
# last modified:20220824
import time
import serial
import re
utctime = ''
lat = ''
ulat = ''
lon = ''
ulon = ''
numSv = ''
msl = ''
cogt = ''
cogm = ''
sog = ''
kph = ''
gps_t = 0
ser = serial.Serial("COM5", 115200)
if ser.isOpen():
print("GPS Serial Opened! Baudrate=9600")
else:
print("GPS Serial Open Failed!")
def Convert_to_degrees(in_data1, in_data2):
len_data1 = len(in_data1)
str_data2 = "%05d" % int(in_data2)
temp_data = int(in_data1)
symbol = 1
if temp_data < 0:
symbol = -1
degree = int(temp_data / 100.0)
str_decimal = str(in_data1[len_data1-2]) + str(in_data1[len_data1-1]) + str(str_data2)
f_degree = int(str_decimal)/60.0/100000.0
# print("f_degree:", f_degree)
if symbol > 0:
result = degree + f_degree
else:
result = degree - f_degree
return result
def GPS_read():
global utctime
global lat
global ulat
global lon
global ulon
global numSv
global msl
global cogt
global cogm
global sog
global kph
global gps_t
if ser.inWaiting():
if ser.read(1) == b'G':
if ser.inWaiting():
if ser.read(1) == b'N':
if ser.inWaiting():
choice = ser.read(1)
if choice == b'G':
if ser.inWaiting():
if ser.read(1) == b'G':
if ser.inWaiting():
if ser.read(1) == b'A':
#utctime = ser.read(7)
GGA = ser.read(70)
GGA_g = re.findall(r"\w+(?=,)|(?<=,)\w+", str(GGA))
# print(GGA_g)
if len(GGA_g) < 13:
print("GPS no found")
gps_t = 0
return 0
else:
utctime = GGA_g[0]
# lat = GGA_g[2][0]+GGA_g[2][1]+'°'+GGA_g[2][2]+GGA_g[2][3]+'.'+GGA_g[3]+'\''
lat = "%.8f" % Convert_to_degrees(str(GGA_g[2]), str(GGA_g[3]))
ulat = GGA_g[4]
# lon = GGA_g[5][0]+GGA_g[5][1]+GGA_g[5][2]+'°'+GGA_g[5][3]+GGA_g[5][4]+'.'+GGA_g[6]+'\''
lon = "%.8f" % Convert_to_degrees(str(GGA_g[5]), str(GGA_g[6]))
ulon = GGA_g[7]
numSv = GGA_g[9]
msl = GGA_g[12]+'.'+GGA_g[13]+GGA_g[14]
#print(GGA_g)
gps_t = 1
return 1
elif choice == b'V':
if ser.inWaiting():
if ser.read(1) == b'T':
if ser.inWaiting():
if ser.read(1) == b'G':
if gps_t == 1:
VTG = ser.read(40)
VTG_g = re.findall(r"\w+(?=,)|(?<=,)\w+", str(VTG))
cogt = VTG_g[0]+'.'+VTG_g[1]+'T'
if VTG_g[3] == 'M':
cogm = '0.00'
sog = VTG_g[4]+'.'+VTG_g[5]
kph = VTG_g[7]+'.'+VTG_g[8]
elif VTG_g[3] != 'M':
cogm = VTG_g[3]+'.'+VTG_g[4]
sog = VTG_g[6]+'.'+VTG_g[7]
kph = VTG_g[9]+'.'+VTG_g[10]
#print(kph)
try:
while True:
if GPS_read():
print("*********************")
print('UTC Time:'+utctime)
print('Latitude:'+lat+ulat)
print('Longitude:'+lon+ulon)
print('Number of satellites:'+numSv)
print('Altitude:'+msl)
print('True north heading:'+cogt+'°')
print('Magnetic north heading:'+cogm+'°')
print('Ground speed:'+sog+'Kn')
print('Ground speed:'+kph+'Km/h')
print("*********************")
except KeyboardInterrupt:
ser.close()
print("GPS serial Close!")
运行时前需要安装pyserial库(如果安装了serial库需要先pip uninstall):
python -m serial.tools.list_ports //查看对应的串口名称
pip install pyserial
检视GPS模块授时精度
GPS 授时系统的误差通常分为两种类型:系统误差和随机误差。系统误差是由于GPS卫星时钟和地球上的时间标准之间存在差异,以及信号在大气中传播时的影响等因素引起的。这些误差在一定时间范围内是相对恒定的,可以通过对卫星的测量和纠正来消除或减小。随机误差是由于GPS信号在传输和接收过程中受到噪声和干扰的影响引起的。这些误差在短时间内是随机的,但随着时间的推移,它们可以累积并导致越来越大的误差。这些误差通常可以通过平均测量值来减小。
总体而言,GPS授时系统的误差通常在几纳秒到数微秒之间,但在某些情况下,如在大气扰动或信号干扰较强的环境中,误差可能会更大。为了提高精度,GPS系统采用了多种校准和纠正技术,例如差分GPS和精密GPS,以及使用更高质量的天线和接收器来接收信号。
直接使用上位机程序读取GPS授时会受到操作系统的影响(对一台PC连接两个GPS模块,使用上位机直接读取,授时差甚至会达到0.5s)。为了更准确地拿到GPS授时的精度,将GPS拿到的时间与电脑的UTC时间进行做差。将时间误差保存在内存中统一导出。
# coding: utf-8
# last modified:20220824
import time
import serial
import re
import datetime
import numpy as np
import matplotlib.pyplot as plt
utctime = ''
lat = ''
ulat = ''
lon = ''
ulon = ''
numSv = ''
msl = ''
cogt = ''
cogm = ''
sog = ''
kph = ''
gps_t = 0
Ser_name = "COM7"
Baudrate = 115200
ser = serial.Serial(Ser_name, 115200)
if ser.isOpen():
print("GPS Serial Opened! Baudrate="+str(Baudrate))
else:
print("GPS Serial Open Failed!")
def Convert_to_degrees(in_data1, in_data2):
len_data1 = len(in_data1)
str_data2 = "%05d" % int(in_data2)
temp_data = int(in_data1)
symbol = 1
if temp_data < 0:
symbol = -1
degree = int(temp_data / 100.0)
str_decimal = str(in_data1[len_data1-2]) + str(in_data1[len_data1-1]) + str(str_data2)
f_degree = int(str_decimal)/60.0/100000.0
# print("f_degree:", f_degree)
if symbol > 0:
result = degree + f_degree
else:
result = degree - f_degree
return result
gps_time_list = []
pc_time_list = []
def GPS_read():
global utctime
if ser.inWaiting():
if ser.read(1) == b'G':
if ser.inWaiting():
if ser.read(1) == b'N':
if ser.inWaiting():
choice = ser.read(1)
if choice == b'G':
if ser.inWaiting():
if ser.read(1) == b'G':
if ser.inWaiting():
if ser.read(1) == b'A':
utctime = ser.read(11) # 读取串口数据
gps_time_list.append(utctime) #记录gps报告时间
pc_time_list.append(time.time()) #记录电脑时间
return True
try:
while True:
GPS_read()
except KeyboardInterrupt:
ser.close()
print("GPS serial Close!")
print(gps_time_list)
print(pc_time_list)
print(len(gps_time_list))
# 求误差
errors = []
for i in range(0, len(gps_time_list)):
s = gps_time_list[i].decode("utf-8")
gps_t = datetime.datetime.strptime(s, ",%H%M%S.%f") + datetime.timedelta(days=45012,hours = 8)
print(gps_t)
gps_timestamp = gps_t.timestamp()
pc_timestamp = pc_time_list[i]
errors.append(gps_timestamp - pc_timestamp)
# gps_t = float(str(gps_time_list[i])[3:-1])
# print(gps_t)
print(errors)
# 存储文件
import pickle
with open(Ser_name +'_data1.pickle', 'wb') as f:
pickle.dump(errors, f)
# with open('COM5_data.pickle', 'rb') as f:
# errors_data1 = pickle.load(f)
# with open('COM7_data.pickle', 'rb') as f:
# errors_data2 = pickle.load(f)
# data = [errors_data1, errors_data2]
# labels = ['module 1', 'module 2']
# # 绘制箱线图
# fig, ax = plt.subplots()
# ax.boxplot(data, labels=labels)
# ax.set_title('Error Boxplot')
# ax.set_xlabel('Error')
# ax.set_ylabel('Value')
# plt.show()


【实验目的】检测GPS模块授时精度。排除操作系统的影响。
【实验设置】
1)第一个GPS模块接受30秒,串口拿到数据与当前时刻记录到内存中。统计GPS模块授时与PC时间的差值。重复5次。
2)两个GPS模块接收30s。
【实验结果】绘制了两个GPS模块授时与PC模块差的箱线图如上所示。可以看到:
1)一个GPS模块的授时误差相对稳定。不同GPS模块之间有差异(可能为电路公差,固定减去)
2)与PC时间的差大概为0.9秒左右,两个模块之间误差基本在0.03秒以内。
【实验记录】https://i.cnblogs.com/articles/edit;postId=17269656
更符合直觉的做法
使用一台电脑,对两个GPS模块的授时结果同时更新。(使用两个线程或两个进程。我认为应该是双线程)
import time
import serial
import re
import datetime
import pickle
import numpy as np
import matplotlib.pyplot as plt
import threading
Baudrate = 115200
expr_time = 30
ser1 = serial.Serial("COM5", Baudrate)
ser2 = serial.Serial("COM8", Baudrate)
if ser1.isOpen():
print("GPS Serial1 Opened! Baudrate="+str(Baudrate))
else:
print("GPS Serial1 Open Failed!")
if ser1.isOpen():
print("GPS Serial2 Opened! Baudrate="+str(Baudrate))
else:
print("GPS Serial2 Open Failed!")
gps_time_list1 = []
pc_time_list1 = []
gps_time_list2 = []
pc_time_list2 = []
def GPS_read(ser = ser1, name = "str1"):
global utctime
if ser.inWaiting():
if ser.read(1) == b'G':
if ser.inWaiting():
if ser.read(1) == b'N':
if ser.inWaiting():
choice = ser.read(1)
if choice == b'G':
if ser.inWaiting():
if ser.read(1) == b'G':
if ser.inWaiting():
if ser.read(1) == b'A':
utctime = ser.read(11) # 读取串口数据
# print(name)
# print(utctime)
if(name == 'str1'):
gps_time_list1.append(utctime) #记录gps报告时间
pc_time_list1.append(time.time()) #记录电脑时间
else:
gps_time_list2.append(utctime) #记录gps报告时间
pc_time_list2.append(time.time()) #记录电脑时间
return True
# define the worker. We let it runing 10 seconds
def read_serial1():
running = True
start_time = time.time()
while running:
GPS_read(ser = ser1, name='str1')
if(time.time() - start_time >= expr_time):
running = False
def read_serial2():
running = True
start_time = time.time()
while running:
GPS_read(ser = ser2, name='str2')
if(time.time() - start_time >= expr_time):
running = False
# read_serial1()
# Create and start the thread
thread1 = threading.Thread(target = read_serial1)
thread2 = threading.Thread(target = read_serial2)
thread1.start()
thread2.start()
# wait for the thread to finish
thread1.join()
thread2.join()
# print(gps_time_list1)
# print(gps_time_list2)
print('pc_time_list1:')
print(pc_time_list1)
print('pc_time_list2:')
print(pc_time_list2)
print(len(pc_time_list1))
print(len(pc_time_list2))
# Calculate errors between 2 moudules
errors1 = []
for i in range(0, len(gps_time_list1)):
s = gps_time_list1[i].decode("utf-8")
gps_t = datetime.datetime.strptime(s, ",%H%M%S.%f") + datetime.timedelta(days=45012,hours = 8)
# print(gps_t)
gps_timestamp = gps_t.timestamp()
pc_timestamp = pc_time_list1[i]
errors1.append(gps_timestamp - pc_timestamp)
errors2 = []
for i in range(0, len(gps_time_list2)):
s = gps_time_list2[i].decode("utf-8")
gps_t = datetime.datetime.strptime(s, ",%H%M%S.%f") + datetime.timedelta(days=45012,hours = 8)
# print(gps_t)
gps_timestamp = gps_t.timestamp()
pc_timestamp = pc_time_list2[i]
errors2.append(gps_timestamp - pc_timestamp)
errors = []
for i in range(0, len(gps_time_list2)):
# errors.append(errors1[i] - errors2[i] + (pc_time_list1[i] - pc_time_list2[i]))
errors.append(errors1[i] - errors2[i])
with open(str(time.time()) +'_gps_errors.pickle', 'wb') as f:
pickle.dump(errors, f)
print('errors1:')
print(errors1)
print('errors2:')
print(errors2)
print(errors)
fig, ax = plt.subplots()
ax.boxplot(errors)
ax.set_title('Different Sequence')
ax.set_xlabel('sequences')
ax.set_ylabel('timing errors')
# plt.show()
plt.savefig(str(time.time()) +'_gps_errors.png')


浙公网安备 33010602011771号