Costas loop - Page 1
Forum section: electronics / general

author: hamster_nz
author_rank: Super Contributor
timestamp: September 05, 2016, 08:56:35 pm
content: Hi,I'm prototyping/building my own GPS receiver in S/W and have hit a bit of trouble.- I can acquire the GPS space vehicles (work out rough frequency/phase of the L1 signal)- I can follow the Gold Codes as the move in phase relative to the local osc.- I can almost recover the BPSK NAV messagesBut I am having trouble with recovering the exact carrier frequency - so my I/Q plot looks like a circle (approx 300 units in radius), not the nice cleanThe standard technique is a Costas loop (https://en.wikipedia.org/wiki/Costas_loop), which I've tried implementing many different ways without success. Does anybody have any hints or tips? My questions are mainly about the bandwidth of the arm/loop filters.

author: uncle_bob
author_rank: Unknown
timestamp: September 05, 2016, 09:49:05 pm
content: HiHow stable is your local oscillator?What are you doing to estimate doppler change?There is nothing crazy about the loop estimation process. You feed the phase in and it effectively runs a PLL that locks up your software tuning process. The bandwidth is adjusted based on where you are in the acquisition process. Wide band is in the 100 Hz range in this case. Locked / narrow band can easily be < 10 Hz. In precision work with a proper doppler estimation and a good local oscillator, you might get below 0.1 Hz on the control loop bandwidth. Your plots look like either the bandwidth is way to high or there is a glitch in your phase estimation.Bob

author: hamster_nz
author_rank: Super Contributor
timestamp: September 05, 2016, 11:40:03 pm
content: Hi BobQuote from: uncle_bob on September 05, 2016, 09:49:05 pmUnsure - I am using a 1-bit ADC sample file off of the interweb while I wait for the real hardware to arrive. However others have successfully decoded it.QuoteAt the moment the initial estimate comes from a 10,912 point DTF (2ms of data) around the 4092 MHz IF, giving bins that are 500 Hz wide. Then by looking at the relative power levels in the bins either side itnarrows it down to under 200 Hz. Given that the C/A codes are every 1ms, this should give (and does seem to give) an initial guess that is within 2pi/5 radians (72 degrees) per sample of the ideal lock.Given the 'openness' of the I/Q plot (except for the BPSK 180 degree changes) it looks like there is plenty of signal, just that I am doing something dumb.QuoteI know my phase estimation is wrong - at the moment I'm multiplying the I & Q values to find out which quadrant I'm in, and then using that to tweak the 32-bit phase accumulator, but I can't work out what to do in the loop filter. - I'm guessing if filtered(I*Q) > 0 then I need to reduce the phase accumulator's step, if filtered(I*Q) < 0 I adjust it the other way. But the behavior of the filter is what is escaping me - how much bandwidth, how much gain... IIR,.. FIR...For testing I've taken a very long DFT, and used that to narrow the initial Phase Accumulator step to be as close as possible (< 2Hz off, less than 0.01 radians per sample), so it is start where the Costas loop where it should be in lock, and watched what is happening. It just drifts away...After a couple of nights playing with it I am thinking I am just doing it wrong - either the gain or bandwidth must be wrong.

author: uncle_bob
author_rank: Unknown
timestamp: September 06, 2016, 12:13:49 am
content: HiOk, let's back off a bit:What you are building is a phase locked loop. That's all it is. Your I/Q and loop process forms a phase detector. That detector has a sensitivity. Simply put, X radians of phase change gives you Y bits of output. The I/Q needs to be demodulated into phase to make this happen. The Arc Tangent function is your friend in this case.Once you have phase to bits worked out, you have a linear change in bits out as the phase shifts. That gets translated into a "signal" that changes your NCO in order to line it up with the incoming data. You process this bit stream through a very conventional PID (or PI) loop. The bandwidth of this loop can be less ( = a good thing) if you do forward correction on the doppler (it's shifting XXX Hz per sample).The control loop that drives the NCO is like any other. To much gain and it goes nuts. To little gain and it does not track. Exactly how that is all accomplished depends on your implementation.Bob

author: hamster_nz
author_rank: Super Contributor
timestamp: September 06, 2016, 01:04:42 am
content: Quote from: uncle_bob on September 06, 2016, 12:13:49 amThanks for the very clear thoughts!So to problems I am working over in my head are:The BSPK gives a flip to the other side of the I/Q graph, which can look to be a very large phase shift if I am naively tracking phase. As GPS NAV is 50 bps, a bit is 20ms long, so these can occur every 20 samples. This throws my phase detector nuts - how do I keep these out of the loop? Something like "if the shift is > pi/2, subtract pi, else if < pi/2 add pi" (which I will now have to try)Even if I got a stable frequency lock, the key is to get the points pretty much lying on the I axis, so that the I channel encodes the BPSK data. Achieving this requires some way to feed the absolute phase into the loop some how, so it locks down the absolute phase as well.Also, I'm trying to avoid trig functions so I can implement in programmable logic...Humm... plenty of thinking (and hopefully learning) going on here...

author: uncle_bob
author_rank: Unknown
timestamp: September 06, 2016, 01:14:11 am
content: HiUmmm ..... errrrr ....The heart of a Costas loop is the squaring function followed by a bandpass filter. Once you double the frequency (by squaring), all the phase shift goop goes away ....Bob

author: 6thimage
author_rank: Regular Contributor
timestamp: September 06, 2016, 05:58:40 pm
content: It might be worth you having a look at 'understanding gps: principles and applications' by Kaplan and Hegarty (you can find a pdf of it online quite easily). In it they list 4 costas loops to use, in order of complexity - the first is IxQ, second is sign(I)xQ, third is Q/I and fourth is atan(Q/I). The fourth is by far the best, but the second is incredibly easy to get working in hardware.One thing to mention is that a lot of GPS receivers use software for the error calculation and loop filters, and so don't care too much on the time it takes to compute them (or can use large tables). It is possible to do the error calculation and loop filtering in an FPGA, as long as you keep it to fixed points.Now for some links:GPS data is a pain to find, but the SETI project have two very strong GPS signals - they only contain single satellites but can be very useful to test your code againsthttp://setiquest.org/wiki/index.php/SetiQuest_Data_LinksThis website -http://www.acasper.org/2011/11/07/gps-signal-analysis/- has some information on how someone else has tracked themThere is some short baseband data available athttp://gfix.dk/matlab-gnss-sdr-book/gnss-signal-records/The only fully open source design I have ever found is Andrew Holme's (it uses a raspberry pi and an FPGA) -http://www.aholme.co.uk/GPS/Main.htmThere is also the piksi - where the software is open sourced (which runs on an arm cortex m4) but the FPGA is closed source -https://github.com/swift-nav/piksi_firmwareSwift-nav (who make the piksi) also have an open sourced software receiver written in python and c (using their own c library, which is also open sourced), but it is quite difficult to follow what part does what -https://github.com/swift-nav/peregrineIn fact, swift-nav's library is worth its own link -https://github.com/swift-nav/libswiftnavI have collected some baseband data from a spirent simulator, but the files are incredibly big (roughly 32 GB per hour, and most of the recordings were for 30 minutes to 2 hours). I've uploaded one of the smaller files I have to mega (never used it before, but they have a large upload size on their free tier) so hopefully it might be usefuldata file -https://mega.nz/#!athwHBQB!WTLbYw8jwf0sdxUQ3TQqH5wmHpV5JVPzVHgtyeZcxQEsimulator logs -https://mega.nz/#!6kIUjZpb!GznR4jTWg9Loe36Q2qx3o6e18rSg6-kr0uJCSmA4GGcThe data file is for a test I was performing, so it does not have any antenna attenuation (which is a little unrealistic, as patch antenna's receive very little from the horizon). It is recorded at 16.368 MHz with a centre frequency of 4.092 MHz. The first 512 bytes of the data file are blank (due to the way I designed the recorder - SD card connected to an FPGA), the data is 3 bits (without any complex data) and is stored in the top of each nibble. Below is a c function I use to load the data (my programs, separately to this function, open the file and fseek 512 bytes):Code:/* nibble 3 data format* two samples, stored in nibbles of byte, with no imaginary data** 76543210* smm*SMM*** s - sign, m - magnitude, * - unused, cases indicate sample** 7-5 first sample* 3-1 second sample*/static double n3_sample_table_double[]={ 1., /* 000 */ 3., /* 001 */ 5., /* 010 */ 7., /* 011 */-1., /* 100 */ -3., /* 101 */ -5., /* 110 */ -7. /* 111 */};int32_t data_n3_read(FILE *data_file, uint32_t num_samples, uint32_t pad, double complex **data,data_malloc dmalloc){size_t bytes_to_read=num_samples/2, data_counter=0;/* malloc the data */*data=(double complex*)dmalloc(sizeof(double complex)*(num_samples+pad));if(!*data)return 0;/* read data */while(bytes_to_read){size_t i, block_size=4096, bytes_read;uint8_t data_block;if(bytes_to_read<block_size)block_size=bytes_to_read;/* perform read */bytes_read=fread(data_block, 1, block_size, data_file);if(bytes_read<1)return -(int32_t)data_counter;/* store data */for(i=0; i<bytes_read; ++i, data_counter+=2){(*data)=(1. + I)*n3_sample_table_double[(data_block>>5)&0x7];(*data)[data_counter+1]=(1. + I)*n3_sample_table_double[(data_block>>1)&0x7];}/* update counter */bytes_to_read-=bytes_read;}/* pad data */for(; data_counter<(num_samples+pad); ++data_counter)(*data)=0.;return num_samples+pad;}Ignore the pad and dmalloc - they are there so that I could use it with the fftw library (which uses its own aligned version of malloc), with the padding being so I could use a 2^n length FFT.

author: hamster_nz
author_rank: Super Contributor
timestamp: September 06, 2016, 09:09:31 pm
content: Quote from: 6thimage on September 06, 2016, 05:58:40 pmGreat stuff - where is the "Thanks a million" button?I've been experimenting with the data file fromhttp://www.jks.com/gps/gps.html,which is about 80 seconds of single-bit data, with an 5.456MHz sample rate, with a LO of 4.092MHz (yes it aliases down to a LO of 1,364 MHzI think sign(I)*Q is what I've been looking for....Being digital I can also adjust the LO phase retrospectively,

author: hamster_nz
author_rank: Super Contributor
timestamp: September 08, 2016, 10:48:21 am
content: I got somewhere eventually, using a Frequency Locked Loop.For me, the key was to reduce the I/Q into the -3 to +3 range (just by left shifting, then use the following lookup table to convert to an angle (scaled to 0-255)Code:unsigned char angle_lookup ={ // i = -4 -3 -2 -1 0 1 2 30, 0, 0, 0, 0, 0, 0, 0, // q = -40,160,168,178,192,206,216,224, // -30,152,160,173,192,211,224,232, // -20,142,147,160,192,224,237,242, // -10,128,128,128, 0, 0, 0, 0, // -00,114,109, 96, 64, 32, 19, 14, // 10,104, 96, 83, 64, 45, 32, 24, // 20, 96, 88, 78, 64, 50, 40, 32}; // 3I'm still trying to get the Costas filter tracking to eliminate the approx 2Hz drift, but I can at least strip out the NAV messages.

author: hamster_nz
author_rank: Super Contributor
timestamp: September 14, 2016, 11:26:55 am
content: Might not have got my Costas loop to work, but am starting to extract orbital and time data from the signal:Code:=========== SV 29 ==============Orbit parameters:iode 31Cfb -0.000001522delta_n 12572M0 61.616402Cuc 0.000000000e 0.002519569Cus 0.000009390sqrt(A) 5217.634533 m^1/2A 27223710.119208 mToe 7242Fit 0aodo 0F=========== SV 30 ==============Orbit parameters:iode 3ACfb -0.000004211delta_n 13465M0 16.222033Cuc 0.000000000e 0.013084712Cus 0.000006450sqrt(A) 5219.346394 m^1/2A 27241576.776231 mToe 7242Fit 0aodo 0C=========== SV 31 ==============Orbit parameters:iode 13Cfb 0.000000227delta_n 11983M0 -52.977987Cuc 0.000000000e 0.007557237Cus 0.000004234sqrt(A) 5153.800608 m^1/2A 26561660.703736 mToe 7241Fit 0aodo 0EI guess I now need to learn a bit of Orbital Mechanics...

author: hamster_nz
author_rank: Super Contributor
timestamp: October 08, 2016, 09:54:07 am
content: It has taken a month of Sundays, but I learned a bit of Orbital Mechanics and can now go from a file of raw 1-bit ADC samples to a reasonable position fix. Considering that the Space Vehicles orbit at 27,200km I am dead chuffed with myself.Code is is about 1600 lines of C, using only standard libraries. Now to tweak, tidy, add maybe add ionosphere corrections, then convert the heavy bits to hardware.But man, the precision and accuracy makes it a real pain to debug this stuff. One little error and you are stuffed. I spent best part of a week tracking down where I had extracted the wrong number of bits for a clock correction constant...

author: uncle_bob
author_rank: Unknown
timestamp: October 09, 2016, 12:07:51 am
content: HiYou are only just at the startHow about SBAS?Bob

author: hamster_nz
author_rank: Super Contributor
timestamp: October 09, 2016, 05:28:11 am
content: Quote from: uncle_bob on October 09, 2016, 12:07:51 amBeing in the darkest reaches of the South Pacific I don't think that there is any SBAS service out here to make it useful!

author: uncle_bob
author_rank: Unknown
timestamp: October 10, 2016, 12:39:28 am
content: Quote from: hamster_nz on October 09, 2016, 05:28:11 amHiMaking getting it working even *more* complicatedBob