SoftGNSS/tracking.py

401 lines
16 KiB
Python
Raw Normal View History

2025-10-22 16:08:12 +07:00
import numpy as np
from initialize import Result
class TrackingResult(Result):
def __init__(self, acqResult):
self._results = None
self._channels = acqResult.channels
self._settings = acqResult.settings
# ./tracking.m
def track(self, fid):
channel = self._channels
settings = self._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.
# Initialize tracking variables ==========================================
codePeriods = settings.msToProcess
# --- DLL variables --------------------------------------------------------
# Define early-late offset (in chips)
earlyLateSpc = settings.dllCorrelatorSpacing
# Summation interval
PDIcode = 0.001
# Calculate filter coefficient values
tau1code, tau2code = settings.calcLoopCoef(settings.dllNoiseBandwidth, settings.dllDampingRatio, 1.0)
# --- PLL variables --------------------------------------------------------
# Summation interval
PDIcarr = 0.001
# Calculate filter coefficient values
tau1carr, tau2carr = settings.calcLoopCoef(settings.pllNoiseBandwidth, settings.pllDampingRatio, 0.25)
# hwb=waitbar(0,'Tracking...')
# Initialize a temporary list of records
rec = []
# Start processing channels ==============================================
for channelNr in range(settings.numberOfChannels):
msToProcess = np.long(settings.msToProcess)
# Initialize fields for record(structured) array of tracked results
status = '-'
# The absolute sample in the record of the C/A code start:
absoluteSample = np.zeros(msToProcess)
# Freq of the C/A code:
codeFreq_ = np.inf * np.ones(msToProcess)
# Frequency of the tracked carrier wave:
carrFreq_ = np.inf * np.ones(msToProcess)
# Outputs from the correlators (In-phase):
I_P_ = np.zeros(msToProcess)
I_E_ = np.zeros(msToProcess)
I_L_ = np.zeros(msToProcess)
# Outputs from the correlators (Quadrature-phase):
Q_E_ = np.zeros(msToProcess)
Q_P_ = np.zeros(msToProcess)
Q_L_ = np.zeros(msToProcess)
# Loop discriminators
dllDiscr = np.inf * np.ones(msToProcess)
dllDiscrFilt = np.inf * np.ones(msToProcess)
pllDiscr = np.inf * np.ones(msToProcess)
pllDiscrFilt = np.inf * np.ones(msToProcess)
PRN = 0
# Only process if PRN is non zero (acquisition was successful)
if channel[channelNr].PRN != 0:
# Save additional information - each channel's tracked PRN
PRN = channel[channelNr].PRN
# 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)
# fid.seek(settings.skipNumberOfBytes + channel[channelNr].codePhase, 0)
fid.seek(int(settings.skipNumberOfBytes + channel[channelNr].codePhase), 0)
# Here PRN is the actual satellite ID instead of the 0-based index
caCode = settings.generateCAcode(channel[channelNr].PRN - 1)
caCode = np.r_[caCode[-1], caCode, caCode[0]]
# define initial code frequency basis of NCO
codeFreq = settings.codeFreqBasis
remCodePhase = 0.0
carrFreq = channel[channelNr].acquiredFreq
carrFreqBasis = channel[channelNr].acquiredFreq
remCarrPhase = 0.0
oldCodeNco = 0.0
oldCodeError = 0.0
oldCarrNco = 0.0
oldCarrError = 0.0
for loopCnt in range(np.long(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 loopCnt % 50 == 0:
try:
print ('Tracking: Ch %d' % (channelNr + 1) + ' of %d' % settings.numberOfChannels + \
'; PRN#%02d' % channel[channelNr].PRN + \
'; Completed %d' % loopCnt + ' of %d' % codePeriods + ' msec')
finally:
pass
# 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 = np.ceil((settings.codeLength - remCodePhase) / codePhaseStep)
blksize = np.long(blksize)
# interaction
rawSignal = np.fromfile(fid, settings.dataType, blksize)
samplesRead = len(rawSignal)
# If did not read in enough samples, then could be out of
# data - better exit
if samplesRead != blksize:
print ('Not able to read the specified number of samples for tracking, exiting!')
fid.close()
trackResults = None
return trackResults
# Set up all the code phase tracking information -------------------------
# Define index into early code vector
tcode = np.linspace(remCodePhase - earlyLateSpc,
blksize * codePhaseStep + remCodePhase - earlyLateSpc,
blksize, endpoint=False)
tcode2 = np.ceil(tcode)
earlyCode = caCode[np.longlong(tcode2)]
tcode = np.linspace(remCodePhase + earlyLateSpc,
blksize * codePhaseStep + remCodePhase + earlyLateSpc,
blksize, endpoint=False)
tcode2 = np.ceil(tcode)
lateCode = caCode[np.longlong(tcode2)]
tcode = np.linspace(remCodePhase,
blksize * codePhaseStep + remCodePhase,
blksize, endpoint=False)
tcode2 = np.ceil(tcode)
promptCode = caCode[np.longlong(tcode2)]
remCodePhase = tcode[blksize - 1] + codePhaseStep - 1023.0
# Generate the carrier frequency to mix the signal to baseband -----------
time = np.arange(0, blksize + 1) / settings.samplingFreq
trigarg = carrFreq * 2.0 * np.pi * time + remCarrPhase
remCarrPhase = trigarg[blksize] % (2 * np.pi)
carrCos = np.cos(trigarg[0:blksize])
carrSin = np.sin(trigarg[0:blksize])
# Generate the six standard accumulated values ---------------------------
# First mix to baseband
qBasebandSignal = carrCos * rawSignal
iBasebandSignal = carrSin * rawSignal
I_E = (earlyCode * iBasebandSignal).sum()
Q_E = (earlyCode * qBasebandSignal).sum()
I_P = (promptCode * iBasebandSignal).sum()
Q_P = (promptCode * qBasebandSignal).sum()
I_L = (lateCode * iBasebandSignal).sum()
Q_L = (lateCode * qBasebandSignal).sum()
# Find PLL error and update carrier NCO ----------------------------------
# Implement carrier loop discriminator (phase detector)
carrError = np.arctan(Q_P / I_P) / 2.0 / np.pi
carrNco = oldCarrNco + \
tau2carr / tau1carr * (carrError - oldCarrError) + \
carrError * (PDIcarr / tau1carr)
oldCarrNco = carrNco
oldCarrError = carrError
carrFreq = carrFreqBasis + carrNco
carrFreq_[loopCnt] = carrFreq
# Find DLL error and update code NCO -------------------------------------
codeError = (np.sqrt(I_E * I_E + Q_E * Q_E) - np.sqrt(I_L * I_L + Q_L * Q_L)) / (
np.sqrt(I_E * I_E + Q_E * Q_E) + np.sqrt(I_L * I_L + Q_L * Q_L))
codeNco = oldCodeNco + \
tau2code / tau1code * (codeError - oldCodeError) + \
codeError * (PDIcode / tau1code)
oldCodeNco = codeNco
oldCodeError = codeError
codeFreq = settings.codeFreqBasis - codeNco
codeFreq_[loopCnt] = codeFreq
# Record various measures to show in postprocessing ----------------------
# Record sample number (based on 8bit samples)
absoluteSample[loopCnt] = fid.tell()
dllDiscr[loopCnt] = codeError
dllDiscrFilt[loopCnt] = codeNco
pllDiscr[loopCnt] = carrError
pllDiscrFilt[loopCnt] = carrNco
I_E_[loopCnt] = I_E
I_P_[loopCnt] = I_P
I_L_[loopCnt] = I_L
Q_E_[loopCnt] = Q_E
Q_P_[loopCnt] = Q_P
Q_L_[loopCnt] = Q_L
# 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
status = channel[channelNr].status
rec.append((status, absoluteSample, codeFreq_, carrFreq_,
I_P_, I_E_, I_L_, Q_E_, Q_P_, Q_L_,
dllDiscr, dllDiscrFilt, pllDiscr, pllDiscrFilt, PRN))
trackResults = np.rec.fromrecords(rec,
dtype=[('status', 'S1'), ('absoluteSample', 'object'), ('codeFreq', 'object'),
('carrFreq', 'object'), ('I_P', 'object'), ('I_E', 'object'),
('I_L', 'object'),
('Q_E', 'object'), ('Q_P', 'object'), ('Q_L', 'object'),
('dllDiscr', 'object'),
('dllDiscrFilt', 'object'), ('pllDiscr', 'object'),
('pllDiscrFilt', 'object'),
('PRN', 'int64')])
self._results = trackResults
return
def plot(self):
import matplotlib as mpl
import matplotlib.gridspec as gs
import matplotlib.pyplot as plt
import numpy as np
# %% configure matplotlib (no LaTeX required)
mpl.rcdefaults()
mpl.rc('savefig', bbox='tight', transparent=False, format='png')
mpl.rc('axes', grid=True, linewidth=1.5, axisbelow=True)
mpl.rc('lines', linewidth=1.5, solid_joinstyle='bevel')
mpl.rc('figure', figsize=[8, 6], dpi=120)
mpl.rc('font', family='serif', size=8)
mpl.rc('mathtext', fontset='cm') # dùng mathtext thay cho LaTeX
# ./plotTracking.m (Python version)
trackResults = self._results
settings = self._settings
channelList = range(settings.numberOfChannels)
# Protection - if the list contains incorrect channel numbers
channelList = np.intersect1d(channelList, range(settings.numberOfChannels))
# === For all listed channels ==============================================
for channelNr in channelList:
# Tạo figure cho mỗi channel
f = plt.figure(channelNr + 200)
f.set_label('Channel ' + str(channelNr) +
' (PRN ' + str(trackResults[channelNr].PRN) + ') results')
# Bố cục 3x3
spec = gs.GridSpec(3, 3)
h11 = plt.subplot(spec[0, 0])
h12 = plt.subplot(spec[0, 1:])
h21 = plt.subplot(spec[1, 0])
h22 = plt.subplot(spec[1, 1:])
h31 = plt.subplot(spec[2, 0])
h32 = plt.subplot(spec[2, 1])
h33 = plt.subplot(spec[2, 2])
# Trục thời gian (s)
timeAxisInSeconds = np.arange(settings.msToProcess) / 1000.0
# --- Scatter plot (I_P vs Q_P)
h11.plot(trackResults[channelNr].I_P, trackResults[channelNr].Q_P, '.')
h11.grid()
h11.axis('equal')
h11.set(title='Discrete-Time Scatter Plot',
xlabel='I prompt', ylabel='Q prompt')
# --- Bits of navigation message (I_P theo thời gian)
h12.plot(timeAxisInSeconds, trackResults[channelNr].I_P)
h12.grid()
h12.set(title='Bits of the navigation message', xlabel='Time (s)')
h12.axis('tight')
# --- Raw PLL discriminator
h21.plot(timeAxisInSeconds, trackResults[channelNr].pllDiscr, 'r')
h21.grid()
h21.axis('tight')
h21.set(xlabel='Time (s)', ylabel='Amplitude',
title='Raw PLL discriminator')
# --- Correlation results (Early, Prompt, Late)
h22.plot(timeAxisInSeconds,
np.sqrt(trackResults[channelNr].I_E ** 2 +
trackResults[channelNr].Q_E ** 2).T,
timeAxisInSeconds,
np.sqrt(trackResults[channelNr].I_P ** 2 +
trackResults[channelNr].Q_P ** 2).T,
timeAxisInSeconds,
np.sqrt(trackResults[channelNr].I_L ** 2 +
trackResults[channelNr].Q_L ** 2).T,
'-*')
h22.grid()
h22.set(title='Correlation results', xlabel='Time (s)')
h22.axis('tight')
h22.legend([r'$\sqrt{I_{E}^2 + Q_{E}^2}$',
r'$\sqrt{I_{P}^2 + Q_{P}^2}$',
r'$\sqrt{I_{L}^2 + Q_{L}^2}$'])
# --- Filtered PLL discriminator
h31.plot(timeAxisInSeconds, trackResults[channelNr].pllDiscrFilt, 'b')
h31.grid()
h31.axis('tight')
h31.set(xlabel='Time (s)', ylabel='Amplitude',
title='Filtered PLL discriminator')
# --- Raw DLL discriminator
h32.plot(timeAxisInSeconds, trackResults[channelNr].dllDiscr, 'r')
h32.grid()
h32.axis('tight')
h32.set(xlabel='Time (s)', ylabel='Amplitude',
title='Raw DLL discriminator')
# --- Filtered DLL discriminator
h33.plot(timeAxisInSeconds, trackResults[channelNr].dllDiscrFilt, 'b')
h33.grid()
h33.axis('tight')
h33.set(xlabel='Time (s)', ylabel='Amplitude',
title='Filtered DLL discriminator')
# Show figure
plt.show()
# f.show()