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')

posted @ 2023-03-29 19:56  Maxwell'Maxwill  阅读(0)  评论(0)    收藏  举报