Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Potential fix for tracking issues #4

Open
chrisdoble opened this issue Nov 18, 2024 · 0 comments
Open

Potential fix for tracking issues #4

chrisdoble opened this issue Nov 18, 2024 · 0 comments

Comments

@chrisdoble
Copy link

chrisdoble commented Nov 18, 2024

Hi,

Thanks for open sourcing this project and blogging about it! Your posts along with Bartosz Ciechanowski's inspired me to build my own GPS receiver.

I recently started implementing the tracking logic and ran into the same issue you described in your second post, namely that I couldn't track a signal for more than a few seconds. Decreasing the loop bandwidth helped a bit but I still lost the signal after a minute or so. Today I found a fix for my code and wanted to share it with you because I think it may also work for Gypsum.

Previously I performed carrier wipeoff and updated the carrier wave's frequency/phase shifts like this:

# Carrier wipeoff
ts = np.arange(samples_per_ms) / samples_per_s
shifted_samples = samples * np.exp(
    -1j
    * (
        2 * np.pi * carrier_frequency_shift_hz * (ts + samples_start_time)
        + carrier_phase_shift_rad
    )
)

# Costas loop
error = correlation.real * correlation.imag
carrier_frequency_shift_hz += beta * error
carrier_phase_shift_rad += alpha * error
carrier_phase_shift_rad %= 2 * np.pi

If we consider a single sample, this calculates its phase for carrier wipeoff as

2 * np.pi * carrier_frequency_shift_hz * t + carrier_phase_shift_rad

where t is the time at which the sample was taken. This could also be written as

$$ \theta = 2 \pi \Delta f(t)\ t + \phi. $$

In other words, it assumes that the current carrier frequency shift has applied since the receiver started (which probably isn't true), uses that to perform the majority of the wipeoff, and adds the current carrier phase shift as a final adjustment.

The fix required two changes:

 # Carrier wipeoff
 ts = np.arange(samples_per_ms) / samples_per_s
 shifted_samples = samples * np.exp(
     -1j
     * (
-        2 * np.pi * carrier_frequency_shift_hz * (ts + samples_start_time)
+        2 * np.pi * carrier_frequency_shift_hz * ts
         + carrier_phase_shift_rad
     )
 )
 
 # Costas loop
 error = correlation.real * correlation.imag
 carrier_frequency_shift += beta * error
-carrier_phase_shift_rad += alpha * error
+carrier_phase_shift_rad += (
+    alpha * error
+    + 2 * np.pi * carrier_frequency_shift_hz * tracking_interval
+)
 carrier_phase_shift_rad %= 2 * np.pi

where tracking_interval is the time in seconds between iterations of the tracking loop.

Again, if we consider a single sample, this calculates its phase for carrier wipeoff as

$$ \theta = \int_0^{t} 2 \pi \Delta f(t') \ d t' + \phi $$

i.e. it no longer assumes the current carrier frequency shift has applied since the receiver started. Instead it accumulates the frequency-shift-induced phase changes, allowing the frequency to change over time.

The code achieves this by adding the carrier_frequency_shift_hz term to carrier_phase_shift_rad which accounts for the phase shift that will occur between iterations of the tracking loop due to the frequency shift. This means that the phase of the first sample in each chunk will be equal to carrier_phase_shift_rad (plus some noise). From there we just need to undo the effects of the frequency shift on each subsequent sample which is what the 2 * np.pi * carrier_frequency_shift_hz * ts term does in carrier wipeoff — there's no need to add samples_start_time.

Hopefully that makes sense and is useful.

Thanks again for your great work!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant