
Originally Posted by
cylon.kernel
Do tell more! That sounds super cool!
So I've got this 10GB file of 'baseband' data. It's a 2-channel WAV file, but rather than left and right stereo it's what is called I+Q. It's called baseband because rather than a radio signal at 2.2GHz it contains the same signal converted to a-3MHz to +3MHz frequency. And rather than the 48,000 samples per second of audio, it is sampled at 6,000,000 samples per second.
I+Q data is "In Phase" and "Quadrature" - you can think of it as line a scatter plot of x and y values. Here's the code that reads the samples from the file, and stores them at two 16 bit integers:
Code:
static int read_sample(FILE *f, int16_t samples[2]) {
uint8_t o[4];
if(fread(o, sizeof(o), 1, f) != 1) {
return 0;
}
samples[0] = (o[1]<<8)| o[0];
samples[1] = (o[3]<<8)| o[2];
return 1;
}
The Falcon 9 second stage uses frequency shift keying at a little over 3MB/s. For a binary zero it transmits on one frequency, for a one it transmits a different frequency, but both signals are transmitted at the same power level.
In the I+Q data, just like on an x/y graph, the stream of samples move in a circle around the origin (0,0) at a speed related to the frequency (the distance from the original is the power of the signal, which in this case is mostly constant).
So the guts of the DSP math is as follows
- get rid of noise - using a low pass 'FIR' filter on the left and right channels.
This is the core of the filter - it's just a multiply+accumulate of two arrays:
Code:
o->r = o->i = 0.0;
for(int i = 0; i < c->kernel_size; i++) {
o->r += c->data[i+c->data_consumed].r * c->kernel[i];
o->i += c->data[i+c->data_consumed].i * c->kernel[i];
}
- See how quickly the stream of samples 'spin' around the origin. This is a two step process.
works out what rotation is needed to move the last point onto the current point
Code:
o->r = d.r*c->last.r - d.i*c->last.i;
o->i = d.r*c->last.i + d.i*c->last.r;
c->last.r = d.r;
c->last.i = -d.i;
and this then converts that rotation into an angle between -PI and +PI radians
Code:
o[i] = atan2(c->d[i].i, c->d[i].r);
- Then look at that to recover the ones and zeros, using the changes in speed to work out where the bit transitions are. This gives you '1's and '0's
This is quite tricky to do as it needs a feedback loop to 'track' the bit transitions.
- Look for the pattern "0011010110011111111110000011101" (0x0x1ACFFC1D) - this is the standard sync pattern at the start of each data packet.
- Before sending, the data packets are scrabbled, by XORing with a pseudo random bit sequence. This can be removed by XORing with the same sequence.
This then give the raw data stream, I've got this far at the moment, so I can see any clear text in it:
Code:
1ACFFC1D 789974926 ( 1279:0) .XP.~...aH...g'e..A...}.....2....p...5...G..}.99752716090514771> solution x/y/z: 4251296.7 965514.9 4929588.6 PDOP=1.1 n
E0 58 50 11 7E F0 C3 0D 61 48 10 80 BD 67 27 65 1B 92 41 18 EF F0 7D 01 17 FE 08 00 32 03 03 02 00 70 12 09 A6 35 9E A7
This stream has another layer of packets in it which I need to decode to work out which bits are say video streams, and which are GPS data, but all the hard DSP stuff is done.
All the code is in GitHub - hamsternz/falcon9_pipeline: A software pipeline to decode the Falcon 9 telemetry from the 6MS/s baseband file. if you want to take a look....