USBGPS 开放源代码项目  

 

项目维护人: 鲁郁    

ENGLISH



项目简介

硬件原理

固件

设备驱动

应用软件

下载

开发文档

开发心得与致谢


用户程序

这个部分是整个系统中最高层的代码。 固件,硬件和设备驱动一起提供了实时控制GP2021的接口, 但它们对GPS处理一无所知。 是应用层的代码通过这个接口实现了所有GPS处理。 从理论上说, 应用层的代码应该可以在不同的硬件接口上运行,当然需要做一些小的改动。 对一个初学者来说,要理解这部分,一些最基本的GPS理论是非常必要的。

基本的GPS理论
我们都知道,一个GPS接收机可以提供用户的位置,速度,和时间的估计(即P.V.T.估计)。 这里我以位置估计为例。
在ECEF坐标系,对于位置有3个未知量,x,y,z。 要计算这3个位置量,至少需要3个方程:

S1 = [(x1-x)2 + (y1-y)2 + (z1-z)2]1/2
S2 = [(x2-x)2 + (y2-y)2 + (z2-z)2]1/2
S3 = [(x3-x)2 + (y3-y)2 + (z3-z)2]1/2
这里 (xi,yi,zi) 是第i个卫星的坐标。 Si 是卫星i 到用户的距离。

卫星的位置可以由Ephemeris数据精确算出。 但如何知道精确的距离? 很容易想到用电磁波传播的时间来计算距离:用光速乘以传播的时间。 但如何知道精确的传播时间?有人会说:如果我们知道发送的时间和接收的时间,我们就可以得到传输的时间。 这个主意看起来可行,但如何才能知道精确的发送和接收时间?而且更要命的是,我们好像正在引入更多的未知量。

不要太沮丧了!好像我们引入了更多的未知量,其实不然。
首先,如果卫星的信号已经被锁定,我们至少可以知道精确的发送时间,具体实现如下:
至此为止,我们已经解决了发送时间。那么到达时间怎么算?没有办法。 有人想:为何不从本地接收机的时钟里得到到达时间?这个方案是不可能的,因为接收机的廉价时钟有很大的偏移和漂移( 除非每一个接收机都配一个铷或铯原子钟,但那样的接收机这个世界上没有多少人用得起。)。 如前所述,时间上的1毫秒偏差等于距离上的300公里的偏差。所以我们不可能得到每一个卫星信号的精确的到达时间。 这样我们还是有太多的未知量。

但相关器(这里是GP2021)可以在 同一时刻 锁存所有通道的测量值(CODE_SLEW,CODE_PHASE, CARRIER_CYCLE_LOW, CARRIER_DCO_PHASE,EPOCH,CODE_DCO_PHASE,CARRIER_CYCLE_HIGH,EPOCH_CHECK)。 这个功能实在是太好了,因为这意味着所有卫星的信号将共享一个到达时间。 于是我们只引入了一个未知量:接收机的时钟偏移tb。 对4个未知量(x,y,z,tb),4个方程就足够了:

S1' = [(x1-x)2 + (y1-y)2 + (z1-z)2]1/2 - Ctb
S2' = [(x2-x)2 + (y2-y)2 + (z2-z)2]1/2 - Ctb
S3' = [(x3-x)2 + (y3-y)2 + (z3-z)2]1/2 - Ctb
S4' = [(x4-x)2 + (y4-y)2 + (z4-z)2]1/2 - Ctb
Here (xi,yi,zi) 是第i个卫星的坐标. Si' 是卫星i 到用户的伪距离, C 光速, tb 是接收机的时钟偏移量。

上面的方程都是非线性的,用叠代法可解。具体的算法请参考任何一本数值计算的教科书。 现在我们知道4颗锁定的卫星信号对于定位已经足够了。 我的硬件具备锁定6颗卫星的能力,所以解算位置信息没有问题。 但DOPs(GDOP,VDOP,HDOP,TDOP,...)就要打一些折扣了。

基于以上分析,可以看出跟踪环在接收机设计中具有非常重要的地位。 跟踪环的性能表现直接影响接收机的性能。 以上述方程为例:卫星的位置计算离不开Ephemeris 数据; 而最新的Ephemeris 数据只有在环路锁定后才可以从解调的卫星报文中得到。 为了计算伪距,我们需要知道每一颗卫星精确的发送时间,而这只在那颗卫星被锁定后才可能。 所以在接收机中,其重要的一环就是信号的捕获和跟踪锁定。

GPS信号是典型的CDMA信号。它是一种扩展频谱的信号。经过长途跋涉达到地球, GPS信号已经十分微弱,实际上已经被背景热噪声所掩盖。 它只所以可以被检测并解调是因为伪码(C/A 码)的强自相关性。 所以在接收机中必须有两个环路:载波环和伪码环。伪码环用来解扩伪码,载波环用来解调载波。

更多关于跟踪环路的描述和GPS接收机的基本原理超出这个网页的范围。感兴趣的话,请参考这些书目

源代码描述
应用程序中共有4个线程同时运行:screenthread, sampthread, navthread,gpsthread

线程之间的同步是通过事件(EVENT)和临界量(CRITICAL_SECTION)。 在我的代码里,有3个全局的事件变量:newdataEvent, gpsEvent, dispEvent。Sampthread 使用newdataEvent 通知Navthread有新数据。 Navthread 使用gpsEvent 告诉Gpsthread新的测量值已经准备好了。 DispEvent被用来通知screenThread更新显示。

我定义了一些类,具体描述如下表:

功能描述
IoInterface
与设备驱动通信,控制硬件,同时将GP2021的原始数据拷贝到navthread, 并从navthread得到控制指令帧
GPS_Nav
实现跟踪环路的4个状态:acquisition,confirm, pullin, tracking. 当信号被锁定后,读取测量值来计算精确的信号发送时间,
GPS_Func
提供所有GPS相关的功能函数,包括地理坐标和ECEF坐标相互转换, 从almanac数据和ephemeris数据计算卫星的位置坐标等, P.V.T.解算
TForm1
实现原始数据,跟踪环状态,以及处理结果的显示. 这个类同时还负责系统和全局变量的初始化.,


线程间时序关系如下图所示:


 timing relationship among threads


关于跟踪环,基本思想和Clifford的代码一样. 环路包括4个状态: acquisition, confirm, pullin, tracking. 这里我不想重复基本概念, 大家可以读一读Clifford的详细描述,这里是链接: http://home.earthlink.net/~cwkelley//receiver_operation.htm. 我只想说一下在这个项目中针对具体的USB硬件特性而做的一些特殊处理, 而这些特殊处理也是整个项目中最有技巧的部分.

从我的硬件和固件描述,可以看出应用程序每次从GP2021得到一帧数据, 每一帧数据包含了在3.6毫秒内6个通道的相关结果. 在3.6毫秒内,每个通道将会有3或4次相关运算发生. 于是跟踪环的代码要依据3或4次相关结果来控制GP2021, 而不是一次相关结果.
这就带来一个问题:如何依据3或4次相关结果来控制GP2021? 对于每一次相关结果,都有4个原始数据:TRACK_Q, TRACK_I, PROMPT_Q, PROMPT_I. 从这4个原始数据,可以算出4个新变量:

     TRACK_MAG = (TRACK_Q2+TRACK_I2)1/2
     PROMPT_MAG = (PROMPT_Q2+PROMPT_I2)1/2
     THETA = ATAN2(PROMPT_Q, PROMPT_I)
     CODE_PH = (TRACK_MAG - PROMPT_MAG)/(TRACK_MAG + PROMPT_MAG)

TRACK_MAG和PROMPT_MAG是用来在accquisition阶段捕获信号, THETA是用来控制载波环,CODE_PH是用来控制伪码环.

于是在每一帧,每一个通道将会有4(或3)TRACK_MAG,4(或3)PROMPT_MAG,4(或3)THETA,4(或3)CODE_PH. 为了方便起见,下面的分析我假定有4个相关结果.

由于一帧内的4个相关结果共享相同的载波环相位和伪码环相位, 所以很容易想到用TRACK_MAG和PROMPT_MAG的均值:

     MEAN_TRACK_MAG = (TRACK_MAG1+TRACK_MAG2+TRACK_MAG3+TRACK_MAG4)/4
     MEAN_PROMPT_MAG = (PROMPT_MAG1+PROMPT_MAG2+PROMPT_MAG3+PROMPT_MAG4)/4
于是利用模的均值可以得到新的CODE_PH:

     CODE_PH = (MEAN_TRACK_MAG - MEAN_PROMPT_MAG)/(MEAN_TRACK_MAG + MEAN_PROMPT_MAG)
至此,一切都没问题. 那么我们可以仍旧用THETA的均值吗?答案是否定的, 因为在1毫秒内,THETA可以有很大的变化,尤其在PULLIN阶段. 为了充分利用全部的4个相关结果, 需要计算THETA的总变化量. 因为ATAN2(x,y)函数的值域是[-PI,+PI],在THETA中可能会有PI-跳变,要么从+PI到-PI, 要么从-PI到+PI . 下图显示了这种情况:


 PI-phase jump


左图显示了反馈信号相位超前时的4个THETA, 可以看出在THETA2到THETA3之间有+PI到-PI的相位跳变; 右图显示了反馈信号相位滞后时的4个THETA, 可以看出在THETA1到THETA2之间有-PI到+PI的相位跳变; 在我的代码里, 我作了PI-补偿如下( 摘自GpaNav.cpp ):

     ...
     ca_t = ch[idx].theta[l] - ch[idx].theta[l-1];
    
     if( fabs(ca_t) > PI )
      ca_theta = ca_t > 0? ca_t - 2*PI : ca_t + 2*PI;
     else
      ca_theta = ca_t;
     ch[idx].total_theta += ca_theta;
     ...
在这个PI补偿以后, ch[idx].total_theta存储着4次相关结果的总相位变化, 可以用来控制载波环路里的FLL.

另一个重要的技巧是在TRACKING阶段: 当TIC发生时, 应用代码要计算卫星信号精确的发送时间. 通常情况下, 这需要读取EPOCH寄存器的值, 从而得到20毫秒和1毫秒的精确分量(CHx_1MS_EPOCH and CHx_20MS_EPOCH). 这样做的前提是在数据位跳变的时刻(即20毫秒的跳变沿)将EPOCH复位. 在ISA硬件环境下, 很容易作到这一点; 但在USB硬件下,就不可能的, 缘于USB总线不能萃发访问. 我计算发送时间的方法是根据相关器的值一个毫秒一个毫秒的数, 这样的问题是会有1或2个毫秒的模糊. 这个模糊的解决就需要固件的配合了.

GP2021为每一个通道提供了一个只读的寄存器EPOCH_CHECK, 读取这个寄存器会得到CHx_1MS_EPOCH和the CHx_20MS_EPOCH的当前值. 在Zarlink的文档里, 这个寄存器的目的是为了验证EPOCH是否被软件正确设置了. 在这个项目里, 这个寄存器的目的就不再是如此了. 在固件里, 当TIC没有发生的时候, 固件发送6个通道的EPOCH_CHECK值. 这样在数据位跳变的时刻, 应用程序能知道CHx_1MS_EPOCH的值, 虽然不能复位EPOCH. 当TIC发生时,应用程序读取EPOCH的值, 这个值相对正确的EPOCH有一个偏移量, 这个偏移量就是EPOCH_CHECK的CHx_1MS_EPOCH. 具体解决1毫秒模糊的代码如下( 摘自GpaNav.cpp ):


     ...
    
     int ms_diff, tic_ms_mod20, compensate_ms;
    
     ms_diff = (frm_4ms_6ch.ch_cntl[idx].epoch) & 0xff;
     ms_diff -= ch[idx].epoch_ref; // minus epoch_check value
     if(ms_diff < 0) ms_diff += 20;// get the value of [0 -- 19]
     tic_ms_mod20 = ch[idx].TIC_mscount %20;
     if( fabs( ms_diff - tic_ms_mod20) < 10)
      compensate_ms = ms_diff - tic_ms_mod20;
     else // ms_diff and tic_ms_mod20 should be very close,
      {
      if( ms_diff > tic_ms_mod20)
        compensate_ms = -(tic_ms_mod20 - ms_diff + 20);
      else
       compensate_ms = (ms_diff - tic_ms_mod20 + 20);
      }
    
    
     ch[idx].tr_time = ch[idx].tr_time_HOW+(ch[idx].TIC_mscount+compensate_ms)*0.001+
     (frm_4ms_6ch.ch_cntl[idx].code_phase)/2.046e6 +
     (frm_4ms_6ch.ch_cntl[idx].cd_dco_phase)/2.046e6L/1024.;
    
     ...
这里ch[idx].epoch_ref是该通道的EPOCH_CHECK值, 而ms_diffch[idx].epoch - ch[idx].epoch_ref . compensate_ms应该被加到ch[idx].TIC_mscount, 以解决1毫秒模糊. compensate_ms的值很小, 比如-2,1,0. 在绝大数时间里, 这种方法可以计算出正确的发送时间;但偶尔(虽然很少发生), 它会不正确. 如果有人找到了更好更稳定的方法可以解决这个问题, 欢迎和我联系.

运行结果
这里是应用程序运行时的用户界面;
 screenshot of application


这里是在PULLIN和TRACKING阶段的载波环的频率;
 carrier loop doppler frequency


这里是更细致的载波环的频率的显示;
 finner carrier loop doppler frequency


这里是PULLIN和TRACKING阶段的数据比特的输出;
 data bit output


这里是PULLIN和TRACKING阶段的载波环的相位(THETA);
 carrier loop phase




一些有用的参考书目



Welcome to USBGPS Opensource Project Homepage
© Copyright 2004    Email:   Yu Lu