铁路工程卫星定位测量规范(标准号:TB 10054-2025 )

  • 卫星定位成果采用基于2000国家大地坐标系(CGCS 2000)参考椭球建立的铁路工程独立坐标系

  • 卫星定位高程采用1985国家高程基准

  • 北斗卫星定位测量时间基准采用北斗时BDT

接收机

误差来源

  • 传播误差:电离层、对流层、多径、雨衰
  • 干扰
  • 异常

适配不同采集器采集的数据需解决的问题: 频率、数据类型、搜索捕获方法。中频信号采集器使用说明(除参照文档): S1)配置芯片寄存器,进行烧录;S2)中频和采样频率,所采用的平台采样频率为采集器时钟:16.368MHz,默认采样率:16.368MHz ,X2=32.736MHz,其中采样率只由射频芯片设置。

MATLAB_SDR

Introduction to Software Defined GNSS Receivers and Signal Processing This course aims to provide attendees with a solid understanding of the fundamentals of satellite timing and navigation (satnav) software receivers and associated signal processing, with a focus towards implementation. The course is divided into multiple modules, each comprised of a short lecture followed by a software demo that reinforces the concepts and techniques covered. By the end of this course, attendees will have an easy-to-use satnav software receiver running on their laptop that takes multiband live-sky sampled data files, acquires and tracks visible open satnav signals, and outputs signal observables. This receiver is fully configured using JavaScript Object Notation (JSON) files such that modification of the source code is not required. It may be further extended to support numerous advanced research applications. Topics covered: Overview of satnav bands, signal structures, link budget, and receiver architecture FFT-based signal acquisition and adapting circular correlation to long spreading codes Software-based methods for correlation acceleration: bit-wise parallelism, multi-threading, SIMD Carrier tracking loops: FLL, PLL and FLL-aided-PLL Code tracking loops: DLL, non-coherent vs. coherent tracking, correlator spacing, and carrier aiding Tracking of open satnav signals: GPS, GLONASS, Galileo, BeiDou, NavIC, and SBAS Internal decision making and control procedures based on signal environment and application Measurement computation (pseudorange, accumulated doppler range/carrierphase) Direct instantiation for multi-frequency tracking (e.g. Galileo E1 to E5a/b)

  • 参考书籍 《A Software-Defined GPS and Galileo Receiver: A Single-Frequency Approach》《GPS原理与接收机设计》

  • 软件接收机流程:

    • S1:检查参数,绘制原始数据的时域图,频域图和直方图
    • S2:信号捕获,得到卫星号、码相位和载波频率(粗捕码(C/A),加密精确码(P(Y)));目的:确定可见卫星及卫星信号的载波频率、码相位的粗略值。
    • S3:信号跟踪,指定通道的码/载波跟踪
    • S4:计算位置
    • S5:判断是否需要继续处理数据,若需要继续处理数据,则回到步骤S1,反之,则结束

    基本知识

    • GPS从根本上讲是基于码分多址CDMA的扩频SS通信系统

    • GPS信号由载波(以L1(1575.42 MHz)/L2(1227.60 MHz)为频率的载波)、导航数据(卫星轨道的相关信息)和扩频序列每个卫星都有两个唯一的扩频列/扩频码字。第一个是粗捕码(C/A),另一个是加密精确码(P(Y)),C/A码为一个1023码片的序列,一个码片对应1 bit, 其不含任何信息,周期为1 ms,码速为1.023 MHz;P码约2.35e14码片,码速率为10.23 MHz,周期为七天。前者只在L1载波上调制,后者在L1和L2载波上均有调制)组成

    • 发射的信号从结构上分为载波、伪码和数据码。伪码和数据码先一起通过调制而依附在正弦波形式的载波上,然后卫星将调制后的载波信号播发出去。

    • 锁相环PLL复现输入卫星的准确相位和频率(已变换为中频),以完成载波剥离功能

    • 锁频环FLL复现近似的频率以完成载波玻璃过程,也称锁频环为自动频率控制AFC

    • C/A码发生器与P码发生器

    • 卫星信号的解调与调制

    • 卫星信号数据块

      • 第一数据块包含第1子帧中的第3字至第10字,又称始终数据块。(星期数WN、用户测距精度URA、卫星健康状况、时钟校正参数、群波延时校正值和时钟数据期号IODC)

      • 第二数据块由卫星信号的第2子帧和第3子帧数据块构成,提供卫星自身的星历参数

      • 第三数据块由卫星信号的第4子帧和第5子帧数据块构成,主要提供所有卫星的历书参数、电离层延时校正参数、GPS时间与UTC时间的关系以及卫星健康状态等数据信息

    • 载波复制

      数控振荡器NCO用来完成正弦载波和余弦载波的复制工作。载波复制过程分为两个过程:

      • 载波NCO输出一个阶梯形的周期信号
      • 正弦和余弦函数查询表分别讲阶梯形信号转换成数字式正弦和余弦载波复制信号

    • 二进制偏置载波(BOC)调制

      • 副载波频率,单位MHz
      • 扩频码速率,单位MChip/s

代码适配

由于源代码基于MATLAB 7.0开发,许多语法已经被弃用/不再支持,现需要进行适配修改。本项目基于MATLAB 2025进行修改。

修改1

postNavigation.m中源代码

for channelNr = activeChnList

修改为

for channelNr = 1:numel(activeChnList)
% channelNr 应为1或2或3......或8。但诡异的原因,channelNr可能意外的变成一行矩阵[1,2,3,4,5,6,7,8],导致接下来使用channelNr 的代码报错

修改2

calculatePseudoranges.m中源代码

%--- For all channels in the list ...
for channelNr = activeChnList

修改为

for channelNr = 1:numel(channelList)

修改3

postNavigation.m中源代码

activeChnList=intersect(find(satElev>=settings.elevationMask),readyChnList);

修改为

activeChnList=intersect(find(satElev>=settings.elevationMask),readyChnList,'legacy');for channelNr = 1:numel(channelList)

修改4

findPreambles.m中源代码

%% Analyze detected preamble like patterns ================================
    for i = 1:size(index) % For each occurrence

修改为

%% Analyze detected preamble like patterns ================================
    % for i = 1:size(index) % For each occurrence
    for i = 1:length(index)

主运行脚本 init.m

%% Clean up the environment first =========================================
clear; close all; clc;

format ('compact');	 % 设置为紧凑型
format ('long', 'g');

%--- Include folders with functions ---------------------------------------
addpath include             % The software receiver functions
addpath geoFunctions        % Position calculation related functions
addpath IFdata\kaikuodata\     % Add IF data

%% Initialize constants, settings =========================================
settings = initSettings();

%% Generate plot of raw data and ask if ready to start processing =========
try
    fprintf('Probing data (%s)...\n', settings.fileName)
    probeData(settings);
catch
    % There was an error, print it and exit
    errStruct = lasterror;
    disp(errStruct.message);
    disp('  (run setSettings or change settings in "initSettings.m" to reconfigure)')    
    return;
end
    
disp('  Raw IF data plotted ')
disp('  (run setSettings or change settings in "initSettings.m" to reconfigure)')
disp(' ');
gnssStart = input('Enter "1" to initiate GNSS processing or "0" to exit : ');

if (gnssStart == 1)
    disp(' ');
    %start things rolling...
    postProcessing
end


变量设置脚本 initSettings.m

(1) IFfrequency GPS:信号数字中频频率,单位Hz。

(2) samplingFrequenry GPS:信号采样频率,单位Hz。

(3) msToProcess:此变量值为30000以保证所有的五个6s子帧全都被处理,第一个1000ms数据被忽略掉

(4) processBlockSize:跟踪数据长度。

(5) numOfChannels:通道数。这里要根据实际情况进行修改。

函数iniSettings生成settings结构体,最初阶段该函数由程序init调用执行。在每一次变量改变的时候,该程序都应该执行。有经验的用户可以直接修改settings里的一些数据。但是要注意,有一些变量有内在关联性会自动重新计算。在函数initSettings内部改变变量是最安全的,之后还将再次执行。

function settings = initSettings()
%Functions initializes and saves settings. Settings can be edited inside of
%the function, updated from the command line or updated using a dedicated
%GUI - "setSettings".  
%
%All settings are described inside function code.
%
%settings = initSettings()
%
%   Inputs: none
%
%   Outputs:
%       settings     - Receiver settings (a structure). 

% CVS record:
% $Id: initSettings.m,v 1.9.2.31 2006/08/18 11:41:57 dpl Exp $

%% Processing settings ====================================================
% Number of milliseconds to be processed used 36000 + any transients (see
% below - in Nav parameters) to ensure nav subframes are provided
settings.msToProcess        = 60000;        %[ms]

% Number of channels to be used for signal processing
% 用于信号处理的通道数
settings.numberOfChannels   = 12;

% Move the starting point of processing. Can be used to start the signal
% processing at any point in the data record (e.g. for long records). fseek
% function is used to move the file read point, therefore advance is byte based only. 
% 移动处理的起点,可用于在数据记录的任何点开始信号处理(例如长记录)
settings.skipNumberOfBytes     =120000000;

%% Raw signal file name and other parameter ===============================
% This is a "default" name of the data file (signal record) to be used in
% the post-processing mode
settings.fileName           = 'usbdata.bin';
% Data type used to store one sample 用于存储一个样本的数据类
settings.dataType           = 'int8';

% Intermediate, sampling and code frequencies
settings.IF                 =16.368e6; 		% 中频 [Hz]
settings.samplingFreq       =16.368e6; 		% 采样频率 [Hz]
settings.codeFreqBasis      = 1.023e6; 		% C/A码的码率 [Hz]

% Define number of chips in a code period 
settings.codeLength         = 1023;

%% Acquisition settings ===================================================
% Skips acquisition in the script postProcessing.m if set to 1 是否跳过捕获程序,如果置1则在postProcessing.m中跳过捕获程序
settings.skipAcquisition    = 0;
% List of satellites to look for. Some satellites can be excluded to speed up acquisition
% 需要捕获的卫星名单,为了加快捕获速度,可以排除一些卫星
settings.acqSatelliteList   = 1:32;         %[PRN numbers]
% Band around IF to search for satellite signal. Depends on max Doppler
% 最大多普勒频移的估算过程。指定卫星信号搜索的频率范围,为kHz的整数倍,并以IF为中心频率。用于捕获函数的步长为0.5kHz。
settings.acqSearchBand      = 14;           %[kHz]
% Threshold for the signal presence decision rule、
% 信号检测器的门限。
settings.acqThreshold       = 2.5;

%% Tracking loops settings 跟踪参数设置================================================
% Code tracking loop parameters
settings.dllDampingRatio         = 0.7;		% 衰减率 C/A码跟踪回路参数
settings.dllNoiseBandwidth       = 2;       % 噪声带宽 [Hz]
settings.dllCorrelatorSpacing    = 0.5;     % 相关器间距 [chips]

% Carrier tracking loop parameters
settings.pllDampingRatio         = 0.7;		% 衰减率 载波跟踪环路参数
settings.pllNoiseBandwidth       = 25;      %[Hz]

%% Navigation solution settings 导航参数 ===========================================

% Period for calculating pseudoranges and position
settings.navSolPeriod       = 500;          % 计算伪距和位置的周期 [ms]

% Elevation mask to exclude signals from satellites at low elevation
% 去除低仰角的卫星[degrees 0 - 90] 初始化取10
settings.elevationMask      = 10;           %[degrees 0 - 90]
% Enable/dissable use of tropospheric correction 启用/禁用对流层校正
settings.useTropCorr        = 1;            % 0 - Off
                                            % 1 - On
% True position of the antenna in UTM system (if known). Otherwise enter
% all NaN's and mean position will be used as a reference .
settings.truePosition.E     = nan;
settings.truePosition.N     = nan;
settings.truePosition.U     = nan;

%% Plot settings ==========================================================
% Enable/disable plotting of the tracking results for each channel
settings.plotTracking       = 1;            % 0 - Off
                                            % 1 - On

%% Constants ==============================================================

settings.c                  = 299792458;    % The speed of light, [m/s]
settings.startOffset        = 68.802;       %[ms] Initial sign. travel time

绘制原始数据(时域波形图、功率谱密度图/频域图和柱状图)

function probeData(varargin)
%Function plots raw data information: time domain plot, a frequency domain
%plot and a histogram. 
%
%The function can be called in two ways:
%   probeData(settings)
% or
%   probeData(fileName, settings)
%
%   Inputs:
%       fileName        - name of the data file. File name is read from
%                       settings if parameter fileName is not provided.
%
%       settings        - receiver settings. Type of data file, sampling
%                       frequency and the default filename are specified
%                       here. 

% CVS record:
% $Id: probeData.m,v 1.1.2.7 2006/08/22 13:46:00 dpl Exp $

%% Check the number of arguments ==========================================
if (nargin == 1)
    settings = deal(varargin{1});
    fileNameStr = settings.fileName;
elseif (nargin == 2)
    [fileNameStr, settings] = deal(varargin{1:2});
    if ~ischar(fileNameStr)
        error('File name must be a string');
    end
else
    error('Incorect number of arguments');
end
    
%% Generate plot of raw data ==============================================
[fid, message] = fopen(fileNameStr, 'rb');

if (fid > 0)
    % Move the starting point of processing. Can be used to start the
    % signal processing at any point in the data record (e.g. for long
    % records).
    fseek(fid, settings.skipNumberOfBytes, 'bof');    
    
    % Find number of samples per spreading code
    samplesPerCode = round(settings.samplingFreq / ...
                           (settings.codeFreqBasis / settings.codeLength));
                      
    % Read 10ms of signal
    [data, count] = fread(fid, [1, 10*samplesPerCode], settings.dataType);
    
    fclose(fid);
    
    if (count < 10*samplesPerCode)
        % The file is to short
        error('Could not read enough data from the data file.');
    end
    
    %--- Initialization ---------------------------------------------------
    figure(100);
    clf(100);
    
    timeScale = 0 : 1/settings.samplingFreq : 5e-3;    
    
    %--- Time domain plot -------------------------------------------------
    % 画出时域波形图,按照采样频率进行采样,参数设置为每50个点显示一个
    subplot(2, 2, 1);
    plot(1000 * timeScale(1:round(samplesPerCode/50)), ...
        data(1:round(samplesPerCode/50)));
     
    axis tight;
    grid on;
    title ('Time domain plot');
    xlabel('Time (ms)'); ylabel('Amplitude');
    
    %--- Frequency domain plot --------------------------------------------
    subplot(2,2,2);
    % 使用pwelch()函数画出功率谱密度图
    pwelch(data-mean(data), 16384, 1024, 2048, settings.samplingFreq);%/1e6)
    
    axis tight;
    grid on;
    title ('Frequency domain plot');
    xlabel('Frequency (MHz)'); ylabel('Magnitude');
    
    %--- Histogram --------------------------------------------------------
    subplot(2, 2, 3.5);
    % 用hist()函数绘制柱状图
    hist(data, -128:128)
    
    dmax = max(abs(data))+1;
    axis tight;
    adata = axis;
    axis([-dmax dmax adata(3) adata(4)]);
    grid on;
    title ('Histogram'); 
    xlabel('Bin'); ylabel('Number in bin');
else
    %=== Error while opening the data file ================================
    error('Unable to read file %s: %s.', fileNameStr, message);
end % if (fid > 0)

数据相关图

接收机主流程程序 postProcessing.m

S1.1 Open the data file for the processing and seek to desired point.

S2.1 Acquire satellites

S3.1 Initialize channels (preRun.m).

S3.2 Pass the channel structure and the file identifier to the tracking function. It will read and process the data. The tracking results are stored in the trackResults structure. The results can be accessed this way (the results are stored each millisecond): trackResults(channelNumber). XXX(fromMillisecond : toMillisecond), where XXX is a field name of the result (e.g. I_P, codePhase etc.)

S4 Pass tracking results to the navigation solution function. It will decode navigation messages, find satellite positions, measure pseudoranges and find receiver position.

S5 Plot the results.

% Script postProcessing.m processes the raw signal from the specified data
% file (in settings) operating on blocks of 37 seconds of data.
%
% First it runs acquisition code identifying the satellites in the file,
% then the code and carrier for each of the satellites are tracked, storing
% the 1msec accumulations.  After processing all satellites in the 37 sec
% data block, then postNavigation is called. It calculates pseudoranges
% and attempts a position solutions. At the end plots are made for that
% block of data.
###################################################################################
%                         THE SCRIPT "RECIPE"
%
% 1.1) Open the data file for the processing and seek to desired point.
%
% 2.1) Acquire satellites
%
% 3.1) Initialize channels (preRun.m).
% 3.2) Pass the channel structure and the file identifier to the tracking
% function. It will read and process the data. The tracking results are
% stored in the trackResults structure. The results can be accessed this
% way (the results are stored each millisecond):
% trackResults(channelNumber).XXX(fromMillisecond : toMillisecond), where
% XXX is a field name of the result (e.g. I_P, codePhase etc.)
%
% 4) Pass tracking results to the navigation solution function. It will
% decode navigation messages, find satellite positions, measure
% pseudoranges and find receiver position.
%
% 5) Plot the results.
%% ==========================Initialization ===============================
disp ('Starting processing...');

[fid, message] = fopen(settings.fileName, 'rb');

%If success, then process the data
if (fid > 0)
    
    % Move the starting point of processing. Can be used to start the
    % signal processing at any point in the data record (e.g. good for long
    % records or for signal processing in blocks).
    fseek(fid, settings.skipNumberOfBytes, 'bof');

%% Acquisition ============================================================

    % Do acquisition if it is not disabled in settings or if the variable
    % acqResults does not exist.
    if ((settings.skipAcquisition == 0) || ~exist('acqResults', 'var'))
        
        % Find number of samples per spreading code
        samplesPerCode = round(settings.samplingFreq / ...
                           (settings.codeFreqBasis / settings.codeLength));
        
        % Read data for acquisition. 11ms of signal are needed for the fine
        % frequency estimation
        data = fread(fid, 11*samplesPerCode, settings.dataType)';

        %--- Do the acquisition -------------------------------------------
        disp ('   Acquiring satellites...');
        acqResults = acquisition(data, settings);

        plotAcquisition(acqResults);
    end

%% Initialize channels and prepare for the run ============================

    % Start further processing only if a GNSS signal was acquired (the
    % field FREQUENCY will be set to 0 for all not acquired signals)
    if (any(acqResults.carrFreq))
        channel = preRun(acqResults, settings);
        showChannelStatus(channel, settings);
    else
        % No satellites to track, exit
        disp('No GNSS signals detected, signal processing finished.');
        trackResults = [];
        return;
    end

%% Track the signal =======================================================
    startTime = now;
    disp (['   Tracking started at ', datestr(startTime)]);

    % Process all channels for given data block
    [trackResults, channel] = tracking(fid, channel, settings);

    % Close the data file
    fclose(fid);
    
    disp(['   Tracking is over (elapsed time ', ...
                                        datestr(now - startTime, 13), ')'])     

    % Auto save the acquisition & tracking results to a file to allow
    % running the positioning solution afterwards.
    disp('   Saving Acq & Tracking results to file "trackingResults.mat"')
    save('trackingResults', ...
                      'trackResults', 'settings', 'acqResults', 'channel');                  

%% Calculate navigation solutions =========================================
    disp('   Calculating navigation solutions...');
    navSolutions = postNavigation(trackResults, settings);

    disp('   Processing is complete for this data block');

%% Plot all results ===================================================
    disp ('   Ploting results...');
    if settings.plotTracking
        plotTracking(1:settings.numberOfChannels, trackResults, settings);
    end

    plotNavigation(navSolutions, settings);

    disp('Post processing of the signal is over.');

else
    % Error while opening the data file.
    error('Unable to read file %s: %s.', settings.fileName, message);
end % if (fid > 0)

捕获函数 acquisition.m

预处理步骤

  • 查找每个扩频码的样本数 Find number of samples per spreading code
  • 读取数据进行采集。需要11毫秒的信号用于精细频率估计. Read data for acquisition. 11ms of signal are needed for the fine frequency estimation

目的:识别用户所有的可见卫星,若卫星空间可见,捕获过程中必须确定出信号的两个特性。确定可见卫星及卫星信号的载波频率、码相位的粗略值

  • 频率:卫星信号的频率。进行下变频时,GPS信号 L1的标称频率对应于中频的标称频率。然而,受卫星相对运动的影响,会产生多普勒效应。对于地球上静止的接收机,多普勒频移不会超过5kHz;对于卫星速度达到最大值,且用户速度很高,则产生的多普勒频移可高达10kHz。
  • 码相位:表示当前数据段中C/A码开始的位置。
  • 参数1:卫星由32个不同的PRN码进行区分
  • 参数2:码相位,指的是伪随机码在当前数据块中的时间同步信息。为产生与接收信号伪码相位完全对其的本地伪码,需要知道码相位,此时则可从信号中去除伪码。两个伪码只有在时延为零的时候才能得到最大相关值,即两个信号必须完全对齐才能去处接收信号的伪码。
  • 参数3:载波频率,在下变频的情况下指的是中频。接收到的L1频点1575.42MHz射频信号,在下变频器中经过混频后可获得中频信号。产生本地载波信号的前提时必须知道接收信号的频率,其目的时用于去处接收信号载波。

载波的生成

并行码相位搜索捕获方法

并行码相位搜索法: 当数字中频信号分别与I支路和Q支路上某一频率的复制正弦和复制余弦载波信号混频后,并行码相位搜索捕获算法并不是让这些混频结果i和q通过数字相关器直接与复制C/A码做相关运算,而是对复数形式的混频结果i+ jq进行傅里叶变换,然后将变换结果与复制C/A码傅里叶变换的共轭值相乘,接着将所得的乘积经傅里叶反变换得到在时域内的相关结果,最后对这些相关值进行检测来判断信号是否存在。我们稍后将会证明这个傅里叶反变换结果等于混频结果信号i+ jq与复制CIA码信号在当前搜索频带上各个码相位处的相关值。在完成了对当前频带的搜索与检测后,接收机接着让载波数控振荡器复制另一个频率值的正弦和余弦载波,然后类似地完成对下一个频带的搜索与检测。在对同一个卫星信号不同频带内的搜索过程中,复制CIA码的相位可保持不变,相应地其傅里叶变换及其共轭值也保持不变。当搜索另-个卫星信号时,接收机可让CIA码发生器复制相应的另一个CIA码,然后重复上述在各个不同频带中的信号搜索过程。

function acqResults = acquisition(longSignal, settings)
%Function performs cold start acquisition on the collected "data". It
%searches for GPS signals of all satellites, which are listed in field
%"acqSatelliteList" in the settings structure. Function saves code phase
%and frequency of the detected signals in the "acqResults" structure.
%
%acqResults = acquisition(longSignal, settings)
%
%   Inputs:
%       longSignal    - 11 ms of raw signal from the front-end 
%       settings      - Receiver settings. Provides information about
%                       sampling and intermediate frequencies and other
%                       parameters including the list of the satellites to
%                       be acquired.
%   Outputs:
%       acqResults    - Function saves code phases and frequencies of the 
%                       detected signals in the "acqResults" structure. The
%                       field "carrFreq" is set to 0 if the signal is not
%                       detected for the given PRN number. 
 
%CVS record:
%$Id: acquisition.m,v 1.1.2.12 2006/08/14 12:08:03 dpl Exp $

%% Initialization =========================================================

% Find number of samples per spreading code 每1码周期内(C/A码一周期为1ms)的采样点
samplesPerCode = round(settings.samplingFreq / (settings.codeFreqBasis / settings.codeLength));

% Create two 1msec vectors of data to correlate with and one with zero DC
% 取第一个码周期(1ms)内,原始信号的采样数据
signal1 = longSignal(1 : samplesPerCode);
% 取第二个码周期内的采样数据
signal2 = longSignal(samplesPerCode+1 : 2*samplesPerCode);
% 经过去除直流信号处理的原始信号数据,有11ms
signal0DC = longSignal - mean(longSignal); 

% Find sampling period 采样时间
ts = 1 / settings.samplingFreq;

% Find phase points of the local carrier wave  产生一个码片周期内的与原始信号同样多的相位点
phasePoints = (0 : (samplesPerCode-1)) * 2 * pi * ts;

% Number of the frequency bins for the given acquisition band (500Hz steps) f_unc/f_bin*2+1=频带数目
numberOfFrqBins = round(settings.acqSearchBand * 2) + 1;

% Generate all C/A codes and sample them according to the sampling freq. 本地生成的C/A码
caCodesTable = makeCaTable(settings);


%--- Initialize arrays to speed up the code -------------------------------
% Search results of all frequency bins and code shifts (for one satellite)
results     = zeros(numberOfFrqBins, samplesPerCode);

% Carrier frequencies of the frequency bins
frqBins     = zeros(1, numberOfFrqBins);


%--- Initialize acqResults ------------------------------------------------
% Carrier frequencies of detected signals
acqResults.carrFreq     = zeros(1, 32);
% C/A code phases of detected signals
acqResults.codePhase    = zeros(1, 32);
% Correlation peak ratios of the detected signals
acqResults.peakMetric   = zeros(1, 32);

fprintf('(');

% Perform search for all listed PRN numbers ...
for PRN = settings.acqSatelliteList

%% Correlate signals ======================================================   
    %--- Perform DFT of C/A code ------------------------------------------
    % 对C/A码发生器进行傅里叶变换,然后求复数共轭,得到该矩阵
    caCodeFreqDom = conj(fft(caCodesTable(PRN, :)));

    %--- Make the correlation for whole frequency band (for all freq. bins)
    % 对整个频带进行相关性分析
    for frqBinIndex = 1:numberOfFrqBins

        %--- Generate carrier wave frequency grid (0.5kHz step) -----------
        % 等间距产生载波(0.5kHz步长)
        frqBins(frqBinIndex) = settings.IF - (settings.acqSearchBand/2) * 1000 + 0.5e3 * (frqBinIndex - 1);

        %--- Generate local sine and cosine -------------------------------
        sinCarr = sin(frqBins(frqBinIndex) * phasePoints);
        cosCarr = cos(frqBins(frqBinIndex) * phasePoints);

        %--- "Remove carrier" from the signal 从信号中分离载波 -----------------------------
        I1      = sinCarr .* signal1;
        Q1      = cosCarr .* signal1;
        I2      = sinCarr .* signal2;
        Q2      = cosCarr .* signal2;

        %--- Convert the baseband signal to frequency domain 将基带信号转换到频域 --------------
        IQfreqDom1 = fft(I1 + j*Q1);
        IQfreqDom2 = fft(I2 + j*Q2);

        %--- Multiplication in the frequency domain (correlation in time domain) 频域相乘
        convCodeIQ1 = IQfreqDom1 .* caCodeFreqDom;
        convCodeIQ2 = IQfreqDom2 .* caCodeFreqDom;

        %--- Perform inverse DFT and store correlation results ------------
        acqRes1 = abs(ifft(convCodeIQ1)) .^ 2;
        acqRes2 = abs(ifft(convCodeIQ2)) .^ 2;
        
        %--- Check which msec had the greater power and save that, will
        %"blend" 1st and 2nd msec but will correct data bit issues
        % 此处是求解在同一频率单元中的哪一毫秒的相关值最大,并将其保存,此做法将
        % 会使第一秒和第二毫秒的数据进行混合。
        if (max(acqRes1) > max(acqRes2))
            results(frqBinIndex, :) = acqRes1;
        else
            results(frqBinIndex, :) = acqRes2;
        end
        
    end % frqBinIndex = 1:numberOfFrqBins

%% Look for correlation peaks in the results ==============================
    % Find the highest peak and compare it to the second highest peak
    % The second peak is chosen not closer than 1 chip to the highest peak
    % 找到最高的互相关峰值,并把它和第二高峰做比较,第二个峰值选择不小于1个码片的最高峰值
    %--- Find the correlation peak and the carrier frequency --------------
    [peakSize frequencyBinIndex] = max(max(results, [], 2));

    %--- Find code phase of the same correlation peak ---------------------
    % codePhase 相关值最大时的相位值。找出相关峰值和载波频率
    [peakSize codePhase] = max(max(results));

    %--- Find 1 chip wide C/A code phase exclude range around the peak ----
    % samplesPerCodeChip 1码片宽的采样点数 值大概是38左右
    samplesPerCodeChip   = round(settings.samplingFreq / settings.codeFreqBasis);
    excludeRangeIndex1 = codePhase - samplesPerCodeChip;
    excludeRangeIndex2 = codePhase + samplesPerCodeChip;

    %--- Correct C/A code phase exclude range if the range includes array boundaries
    if excludeRangeIndex1 < 2
        codePhaseRange = excludeRangeIndex2 : (samplesPerCode + excludeRangeIndex1);                   
    elseif excludeRangeIndex2 >= samplesPerCode
        codePhaseRange = (excludeRangeIndex2 - samplesPerCode) : excludeRangeIndex1;
    else
        codePhaseRange = [1:excludeRangeIndex1, excludeRangeIndex2 : samplesPerCode];
    end

    %--- Find the second highest correlation peak in the same freq. bin ---
    % 这部分是求解互相关的第二大值
    secondPeakSize = max(results(frequencyBinIndex, codePhaseRange));

    %--- Store result -----------------------------------------------------
    % acqResults.peakMetric 最大相关和次大相关的比值
    acqResults.peakMetric(PRN) = peakSize/secondPeakSize;
    
    % If the result is above threshold, then there is a signal ...
    if (peakSize/secondPeakSize) > settings.acqThreshold

%% Fine resolution frequency search =======================================
        
        %--- Indicate PRN number of the detected signal -------------------
        fprintf('%02d ', PRN);
        
        %--- Generate 10msec long C/A codes sequence for given PRN --------
        caCode = generateCAcode(PRN);
        
        codeValueIndex = floor((ts * (1:10*samplesPerCode)) / (1/settings.codeFreqBasis));
        
        % 产生一个10ms的C/A码的矩阵,其采样频率已知                           
        longCaCode = caCode((rem(codeValueIndex, 1023) + 1));
    
        %--- Remove C/A code modulation from the original signal ----------
        % (Using detected C/A code phase)
        xCarrier = signal0DC(codePhase:(codePhase + 10*samplesPerCode-1)) .* longCaCode;
        
        %--- Find the next highest power of two and increase by 8x --------
        fftNumPts = 8*(2^(nextpow2(length(xCarrier))));
        
        %--- Compute the magnitude of the FFT, find maximum and the
        %associated carrier frequency 
        fftxc = abs(fft(xCarrier, fftNumPts)); 
        
        uniqFftPts = ceil((fftNumPts + 1) / 2);
        [fftMax, fftMaxIndex] = max(fftxc(5 : uniqFftPts-5));
        
        fftFreqBins = (0 : uniqFftPts-1) * settings.samplingFreq/fftNumPts;
        
        %--- Save properties of the detected satellite signal -------------
        % acqResults.carrFreq 追踪载频的结果
        acqResults.carrFreq(PRN)  = fftFreqBins(fftMaxIndex);
        acqResults.codePhase(PRN) = codePhase;
    
    else
        %--- No signal with this PRN --------------------------------------
        fprintf('. ');
    end   % if (peakSize/secondPeakSize) > settings.acqThreshold
    
end    % for PRN = satelliteList

%=== Acquisition is over ==================================================
fprintf(')\n');

捕获输出分析

跟踪 Tracking.m

载波跟踪

  • 目的:捕获仅能提供对频率和码相位参数的粗略估计。跟踪的主要目的时是这些估计值精确化。

基本的解调方案如下图所示

为此,跟踪部分需要产生两个本地信号:本地载波和本地码,以实现对某颗卫星的精确跟踪和解调

% Code tracking loop parameters
settings.dllDampingRatio         = 0.7;
settings.dllNoiseBandwidth       = 2;       % [Hz]
settings.dllCorrelatorSpacing    = 0.5;     % [chips]

% Carrier tracking loop parameters 
% 控制建立时间,前者的选择是过冲和建立时间折中的结果,通常是0.7,后者控制经过滤波器的噪声数量
settings.pllDampingRatio         = 0.7;
settings.pllNoiseBandwidth       = 25;      % [Hz]

卫星信号的解调过程

完整卫星信号 -> 滤波和下变频等处理,射频前端输出信号 -> A/D转换采样 -> 进行载波剥离,输入信号与本地载波相乘,变为基带信号 -> 通过信号与本地码做相关,从信号中剥离C/A码

为了成功解调出导航数据,需要产生准确的本地载波信号。锁相环PLL或锁频环FLL常用于跟踪子啊波信号。

  • Initialize result structure 初始化结果结构体

  • Initialize tracking variables 初始化追踪函数的变量

  • Start processing channels 处理被捕获通道的信号

  • GUI update 启动GUI显示处理进度条

  • Read next block of data 将数据每M个采样点分为一个“codePhaseStep”个小块(block)
  • Set up all the code phase tracking information 提前建立所有码相位跟踪需要使用的数据
  • Generate the carrier frequency to mix the signal to baseband 通过载频将中频信号混频后到基带

  • Generate the six standard accumulated values 产生6组积分值I/Q各三组

  • Find PLL error and update carrier NCO 计算载波环路的输入信号与复制信号的相位差异,并生成NCO(数控振荡器)命令

  • Find DLL error and update code NCO 码环鉴别器

  • Record various measures to show in postprocessing 记录最终的结果值

function [trackResults, channel]= tracking(fid, channel, settings)
% Performs code and carrier tracking for all channels.
%
%[trackResults, channel] = tracking(fid, channel, settings)
%
%   Inputs:
%       fid             - file identifier of the signal record.
%       channel         - PRN, carrier frequencies and code phases of all
%                       satellites to be tracked (prepared by preRum.m from
%                       acquisition results).
%       settings        - receiver settings.
%   Outputs:
%       trackResults    - tracking results (structure array). Contains
%                       in-phase prompt outputs and absolute starting 
%                       positions of spreading codes, together with other
%                       observation data from the tracking loops. All are
%                       saved every millisecond.

%CVS record:
%$Id: tracking.m,v 1.14.2.32 2007/01/30 09:45:12 dpl Exp $

%% Initialize result structure ============================================

% Channel status
trackResults.status         = '-';      % No tracked signal, or lost lock

% The absolute sample in the record of the C/A code start:
trackResults.absoluteSample = zeros(1, settings.msToProcess);

% Freq of the C/A code:
trackResults.codeFreq       = inf(1, settings.msToProcess);

% Frequency of the tracked carrier wave:
trackResults.carrFreq       = inf(1, settings.msToProcess);

% Outputs from the correlators (In-phase):
trackResults.I_P            = zeros(1, settings.msToProcess);
trackResults.I_E            = zeros(1, settings.msToProcess);
trackResults.I_L            = zeros(1, settings.msToProcess);

% Outputs from the correlators (Quadrature-phase):
trackResults.Q_E            = zeros(1, settings.msToProcess);
trackResults.Q_P            = zeros(1, settings.msToProcess);
trackResults.Q_L            = zeros(1, settings.msToProcess);

% Loop discriminators
trackResults.dllDiscr       = inf(1, settings.msToProcess);
trackResults.dllDiscrFilt   = inf(1, settings.msToProcess);
trackResults.pllDiscr       = inf(1, settings.msToProcess);
trackResults.pllDiscrFilt   = inf(1, settings.msToProcess);

%--- Copy initial settings for all channels -------------------------------
trackResults = repmat(trackResults, 1, settings.numberOfChannels);

%% Initialize tracking variables ==========================================

codePeriods = settings.msToProcess;     % For GPS one C/A code is one ms

%--- DLL variables --------------------------------------------------------
% Define early-late offset (in chips)
earlyLateSpc = settings.dllCorrelatorSpacing;

% Summation interval
PDIcode = 0.001;

% Calculate filter coefficient values
[tau1code, tau2code] = calcLoopCoef(settings.dllNoiseBandwidth, ...
                                    settings.dllDampingRatio, ...
                                    1.0);

%--- PLL variables --------------------------------------------------------
% Summation interval
PDIcarr = 0.001;

% Calculate filter coefficient values
[tau1carr, tau2carr] = calcLoopCoef(settings.pllNoiseBandwidth, ...
                                    settings.pllDampingRatio, ...
                                    0.25);
hwb = waitbar(0,'Tracking...');

%% Start processing channels ==============================================
for channelNr = 1:settings.numberOfChannels
    
    % Only process if PRN is non zero (acquisition was successful)
    if (channel(channelNr).PRN ~= 0)
        % Save additional information - each channel's tracked PRN
        trackResults(channelNr).PRN     = channel(channelNr).PRN;
        
        % Move the starting point of processing. Can be used to start the
        % signal processing at any point in the data record (e.g. for long
        % records). In addition skip through that data file to start at the
        % appropriate sample (corresponding to code phase). Assumes sample
        % type is schar (or 1 byte per sample) 
        fseek(fid, ...
              settings.skipNumberOfBytes + channel(channelNr).codePhase-1, ...
              'bof');


        % Get a vector with the C/A code sampled 1x/chip
        caCode = generateCAcode(channel(channelNr).PRN);
        % Then make it possible to do early and late versions
        caCode = [caCode(1023) caCode caCode(1)];

        %--- Perform various initializations ------------------------------

        % define initial code frequency basis of NCO
        codeFreq      = settings.codeFreqBasis;
        % define residual code phase (in chips)
        remCodePhase  = 0.0;
        % define carrier frequency which is used over whole tracking period
        carrFreq      = channel(channelNr).acquiredFreq;
        carrFreqBasis = channel(channelNr).acquiredFreq;
        % define residual carrier phase
        remCarrPhase  = 0.0;

        %code tracking loop parameters
        oldCodeNco   = 0.0;
        oldCodeError = 0.0;

        %carrier/Costas loop parameters
        oldCarrNco   = 0.0;
        oldCarrError = 0.0;

        %=== Process the number of specified code periods =================
        for loopCnt =  1:codePeriods
            
%% GUI update -------------------------------------------------------------
            % The GUI is updated every 50ms. This way Matlab GUI is still
            % responsive enough. At the same time Matlab is not occupied
            % all the time with GUI task.
            if (rem(loopCnt, 50) == 0)
                try
                    waitbar(loopCnt/codePeriods, ...
                            hwb, ...
                            ['Tracking: Ch ', int2str(channelNr), ...
                            ' of ', int2str(settings.numberOfChannels), ...
                            '; PRN#', int2str(channel(channelNr).PRN), ...
                            '; Completed ',int2str(loopCnt), ...
                            ' of ', int2str(codePeriods), ' msec']);                       
                catch
                    % The progress bar was closed. It is used as a signal
                    % to stop, "cancel" processing. Exit.
                    disp('Progress bar closed, exiting...');
                    return
                end
            end

%% Read next block of data ------------------------------------------------            
            % Find the size of a "block" or code period in whole samples
            
            % Update the phasestep based on code freq (variable) and
            % sampling frequency (fixed)
            codePhaseStep = codeFreq / settings.samplingFreq;
            
            blksize = ceil((settings.codeLength-remCodePhase) / codePhaseStep);
            
            % Read in the appropriate number of samples to process this
            % interation 
            [rawSignal, samplesRead] = fread(fid, ...
                                             blksize, settings.dataType);
            rawSignal = rawSignal';  %transpose vector
            
            % If did not read in enough samples, then could be out of 
            % data - better exit 
            if (samplesRead ~= blksize)
                disp('Not able to read the specified number of samples  for tracking, exiting!')
                fclose(fid);
                return
            end

%% Set up all the code phase tracking information -------------------------
            % Define index into early code vector
            tcode       = (remCodePhase-earlyLateSpc) : ...
                          codePhaseStep : ...
                          ((blksize-1)*codePhaseStep+remCodePhase-earlyLateSpc);
            tcode2      = ceil(tcode) + 1;
            earlyCode   = caCode(tcode2);
            
            % Define index into late code vector
            tcode       = (remCodePhase+earlyLateSpc) : ...
                          codePhaseStep : ...
                          ((blksize-1)*codePhaseStep+remCodePhase+earlyLateSpc);
            tcode2      = ceil(tcode) + 1;
            lateCode    = caCode(tcode2);
            
            % Define index into prompt code vector
            tcode       = remCodePhase : ...
                          codePhaseStep : ...
                          ((blksize-1)*codePhaseStep+remCodePhase);
            tcode2      = ceil(tcode) + 1;
            promptCode  = caCode(tcode2);
            
            remCodePhase = (tcode(blksize) + codePhaseStep) - 1023.0;

%% Generate the carrier frequency to mix the signal to baseband -----------
            time    = (0:blksize) ./ settings.samplingFreq;
            
            % Get the argument to sin/cos functions
            trigarg = ((carrFreq * 2.0 * pi) .* time) + remCarrPhase;
            remCarrPhase = rem(trigarg(blksize+1), (2 * pi));
            
            % Finally compute the signal to mix the collected data to bandband
            carrCos = cos(trigarg(1:blksize));
            carrSin = sin(trigarg(1:blksize));

%% Generate the six standard accumulated values ---------------------------
            % First mix to baseband
            qBasebandSignal = carrCos .* rawSignal;
            iBasebandSignal = carrSin .* rawSignal;

            % Now get early, late, and prompt values for each
            I_E = sum(earlyCode  .* iBasebandSignal);
            Q_E = sum(earlyCode  .* qBasebandSignal);
            I_P = sum(promptCode .* iBasebandSignal);
            Q_P = sum(promptCode .* qBasebandSignal);
            I_L = sum(lateCode   .* iBasebandSignal);
            Q_L = sum(lateCode   .* qBasebandSignal);
            
%% Find PLL error and update carrier NCO ----------------------------------

            % Implement carrier loop discriminator (phase detector)
            carrError = atan(Q_P / I_P) / (2.0 * pi);
            
            % Implement carrier loop filter and generate NCO command
            carrNco = oldCarrNco + (tau2carr/tau1carr) * ...
                (carrError - oldCarrError) + carrError * (PDIcarr/tau1carr);
            oldCarrNco   = carrNco;
            oldCarrError = carrError;

            % Modify carrier freq based on NCO command
            carrFreq = carrFreqBasis + carrNco;

            trackResults(channelNr).carrFreq(loopCnt) = carrFreq;

%% Find DLL error and update code NCO -------------------------------------
            codeError = (sqrt(I_E * I_E + Q_E * Q_E) - sqrt(I_L * I_L + Q_L * Q_L)) / ...
                (sqrt(I_E * I_E + Q_E * Q_E) + sqrt(I_L * I_L + Q_L * Q_L));
            
            % Implement code loop filter and generate NCO command
            codeNco = oldCodeNco + (tau2code/tau1code) * ...
                (codeError - oldCodeError) + codeError * (PDIcode/tau1code);
            oldCodeNco   = codeNco;
            oldCodeError = codeError;
            
            % Modify code freq based on NCO command
            codeFreq = settings.codeFreqBasis - codeNco;
            
            trackResults(channelNr).codeFreq(loopCnt) = codeFreq;

%% Record various measures to show in postprocessing ----------------------
            % Record sample number (based on 8bit samples)
            trackResults(channelNr).absoluteSample(loopCnt) = ftell(fid);

            trackResults(channelNr).dllDiscr(loopCnt)       = codeError;
            trackResults(channelNr).dllDiscrFilt(loopCnt)   = codeNco;
            trackResults(channelNr).pllDiscr(loopCnt)       = carrError;
            trackResults(channelNr).pllDiscrFilt(loopCnt)   = carrNco;

            trackResults(channelNr).I_E(loopCnt) = I_E;
            trackResults(channelNr).I_P(loopCnt) = I_P;
            trackResults(channelNr).I_L(loopCnt) = I_L;
            trackResults(channelNr).Q_E(loopCnt) = Q_E;
            trackResults(channelNr).Q_P(loopCnt) = Q_P;
            trackResults(channelNr).Q_L(loopCnt) = Q_L;
        end % for loopCnt

        % If we got so far, this means that the tracking was successful
        % Now we only copy status, but it can be update by a lock detector
        % if implemented
        trackResults(channelNr).status  = channel(channelNr).status;        
        
    end % if a PRN is assigned
end % for channelNr 

% Close the waitbar
close(hwb)

相关图

伪距、位置等解算 plotNavigation.m

function [navSolutions, eph] = postNavigation(trackResults, settings)
%Function calculates navigation solutions for the receiver (pseudoranges,
%positions). At the end it converts coordinates from the WGS84 system to
%the UTM, geocentric or any additional coordinate system.
%
%[navSolutions, eph] = postNavigation(trackResults, settings)
%
%   Inputs:
%       trackResults    - results from the tracking function (structure
%                       array).
%       settings        - receiver settings.
%   Outputs:
%       navSolutions    - contains measured pseudoranges, receiver
%                       clock error, receiver coordinates in several
%                       coordinate systems (at least ECEF and UTM).
%       eph             - received ephemerides of all SV (structure array).

%CVS record:
%$Id: postNavigation.m,v 1.1.2.22 2006/08/09 17:20:11 dpl Exp $

%% Check is there enough data to obtain any navigation solution ===========
% It is necessary to have at least three subframes (number 1, 2 and 3) to
% find satellite coordinates. Then receiver position can be found too.
% The function requires all 5 subframes, because the tracking starts at
% arbitrary point. Therefore the first received subframes can be any three
% from the 5.
% One subframe length is 6 seconds, therefore we need at least 30 sec long
% record (5 * 6 = 30 sec = 30000ms). We add extra seconds for the cases,
% when tracking has started in a middle of a subframe.

if (settings.msToProcess < 36000) || (sum([trackResults.status] ~= '-') < 4)
    % Show the error message and exit
    disp('Record is to short or too few satellites tracked. Exiting!');
    navSolutions = [];
    eph          = [];
    return
end

%% Find preamble start positions ==========================================

[subFrameStart, activeChnList] = findPreambles(trackResults, settings);

%% Decode ephemerides =====================================================

% for channelNr = activeChnList
for channelNr = 1:numel(activeChnList)

    %=== Convert tracking output to navigation bits =======================

    %--- Copy 5 sub-frames long record from tracking output ---------------
    navBitsSamples = trackResults(channelNr).I_P(subFrameStart(channelNr) - 20 : ...
                               subFrameStart(channelNr) + (1500 * 20) -1)';

    %--- Group every 20 vales of bits into columns ------------------------
    navBitsSamples = reshape(navBitsSamples, ...
                             20, (size(navBitsSamples, 1) / 20));

    %--- Sum all samples in the bits to get the best estimate -------------
    navBits = sum(navBitsSamples);

    %--- Now threshold and make 1 and 0 -----------------------------------
    % The expression (navBits > 0) returns an array with elements set to 1
    % if the condition is met and set to 0 if it is not met.
    navBits = (navBits > 0);

    %--- Convert from decimal to binary -----------------------------------
    % The function ephemeris expects input in binary form. In Matlab it is
    % a string array containing only "0" and "1" characters.
    navBitsBin = dec2bin(navBits);
    
    %=== Decode ephemerides and TOW of the first sub-frame ================
    [eph(trackResults(channelNr).PRN), TOW] = ...
                            ephemeris(navBitsBin(2:1501)', navBitsBin(1));

    %--- Exclude satellite if it does not have the necessary nav data -----
    if (isempty(eph(trackResults(channelNr).PRN).IODC) || ...
        isempty(eph(trackResults(channelNr).PRN).IODE_sf2) || ...
        isempty(eph(trackResults(channelNr).PRN).IODE_sf3))

        %--- Exclude channel from the list (from further processing) ------
        activeChnList = setdiff(activeChnList, channelNr);
    end    
end

%% Check if the number of satellites is still above 3 =====================
if (isempty(activeChnList) || (size(activeChnList, 2) < 4))
    % Show error message and exit
    disp('Too few satellites with ephemeris data for postion calculations. Exiting!');
    navSolutions = [];
    eph          = [];
    return
end

%% Initialization =========================================================

% Set the satellite elevations array to INF to include all satellites for
% the first calculation of receiver position. There is no reference point
% to find the elevation angle as there is no receiver position estimate at
% this point.
satElev  = inf(1, settings.numberOfChannels);

% Save the active channel list. The list contains satellites that are
% tracked and have the required ephemeris data. In the next step the list
% will depend on each satellite's elevation angle, which will change over
% time.  
readyChnList = activeChnList;

transmitTime = TOW;

%##########################################################################
%#   Do the satellite and receiver position calculations                  #
%##########################################################################

%% Initialization of current measurement ==================================
for currMeasNr = 1:fix((settings.msToProcess - max(subFrameStart)) / settings.navSolPeriod)
    fprintf('currMeasNr is %d\r\n', currMeasNr)
    % Exclude satellites, that are belove elevation mask 
    % activeChnList = intersect(find(satElev >= settings.elevationMask), readyChnList);
    activeChnList=intersect(find(satElev>=settings.elevationMask),readyChnList,'legacy');

    % Save list of satellites used for position calculation
    navSolutions.channel.PRN(activeChnList, currMeasNr) = [trackResults(activeChnList).PRN]; 

    % These two lines help the skyPlot function. The satellites excluded
    % do to elevation mask will not "jump" to possition (0,0) in the sky
    % plot.
    navSolutions.channel.el(:, currMeasNr) = NaN(settings.numberOfChannels, 1);
    navSolutions.channel.az(:, currMeasNr) = NaN(settings.numberOfChannels, 1);

%% Find pseudoranges ======================================================
    navSolutions.channel.rawP(:, currMeasNr) = calculatePseudoranges(...
            trackResults, ...
            subFrameStart + settings.navSolPeriod * (currMeasNr-1), ...
            activeChnList, settings);

%% Find satellites positions and clocks corrections =======================
    [satPositions, satClkCorr] = satpos(transmitTime, ...
                                        [trackResults(activeChnList).PRN], ...
                                        eph, settings);

%% Find receiver position =================================================

    % 3D receiver position can be found only if signals from more than 3
    % satellites are available  
    if size(activeChnList, 2) > 3

        %=== Calculate receiver position ==================================
        [xyzdt, ...
         navSolutions.channel.el(activeChnList, currMeasNr), ...
         navSolutions.channel.az(activeChnList, currMeasNr), ...
         navSolutions.DOP(:, currMeasNr)] = ...
            leastSquarePos(satPositions, ...
                           navSolutions.channel.rawP(activeChnList, currMeasNr)' + satClkCorr * settings.c, ...
                           settings);

        %--- Save results -------------------------------------------------
        navSolutions.X(currMeasNr)  = xyzdt(1);
        navSolutions.Y(currMeasNr)  = xyzdt(2);
        navSolutions.Z(currMeasNr)  = xyzdt(3);
        navSolutions.dt(currMeasNr) = xyzdt(4);

        % Update the satellites elevations vector
        satElev = navSolutions.channel.el(:, currMeasNr);

        %=== Correct pseudorange measurements for clocks errors ===========
        navSolutions.channel.correctedP(activeChnList, currMeasNr) = ...
                navSolutions.channel.rawP(activeChnList, currMeasNr) + ...
                satClkCorr' * settings.c + navSolutions.dt(currMeasNr);

%% Coordinate conversion ==================================================

        %=== Convert to geodetic coordinates ==============================
        [navSolutions.latitude(currMeasNr), ...
         navSolutions.longitude(currMeasNr), ...
         navSolutions.height(currMeasNr)] = cart2geo(...
                                            navSolutions.X(currMeasNr), ...
                                            navSolutions.Y(currMeasNr), ...
                                            navSolutions.Z(currMeasNr), ...
                                            5);

        %=== Convert to UTM coordinate system =============================
        navSolutions.utmZone = findUtmZone(navSolutions.latitude(currMeasNr), ...
                                           navSolutions.longitude(currMeasNr));
        
        [navSolutions.E(currMeasNr), ...
         navSolutions.N(currMeasNr), ...
         navSolutions.U(currMeasNr)] = cart2utm(xyzdt(1), xyzdt(2), ...
                                                xyzdt(3), ...
                                                navSolutions.utmZone);
        
    else % if size(activeChnList, 2) > 3 
        %--- There are not enough satellites to find 3D position ----------
        disp(['   Measurement No. ', num2str(currMeasNr), ': Not enough information for position solution.']);

        %--- Set the missing solutions to NaN. These results will be
        %excluded automatically in all plots. For DOP it is easier to use
        %zeros. NaN values might need to be excluded from results in some
        %of further processing to obtain correct results.
        navSolutions.X(currMeasNr)           = NaN;
        navSolutions.Y(currMeasNr)           = NaN;
        navSolutions.Z(currMeasNr)           = NaN;
        navSolutions.dt(currMeasNr)          = NaN;
        navSolutions.DOP(:, currMeasNr)      = zeros(5, 1);
        navSolutions.latitude(currMeasNr)    = NaN;
        navSolutions.longitude(currMeasNr)   = NaN;
        navSolutions.height(currMeasNr)      = NaN;
        navSolutions.E(currMeasNr)           = NaN;
        navSolutions.N(currMeasNr)           = NaN;
        navSolutions.U(currMeasNr)           = NaN;

        navSolutions.channel.az(activeChnList, currMeasNr) = NaN(1, length(activeChnList));
        navSolutions.channel.el(activeChnList, currMeasNr) = NaN(1, length(activeChnList));

        % TODO: Know issue. Satellite positions are not updated if the
        % satellites are excluded do to elevation mask. Therefore rasing
        % satellites will be not included even if they will be above
        % elevation mask at some point. This would be a good place to
        % update positions of the excluded satellites.

    end % if size(activeChnList, 2) > 3

    %=== Update the transmit time ("measurement time") ====================
    transmitTime = transmitTime + settings.navSolPeriod / 1000;

end %for currMeasNr...