|
/* ACARS decoder with options to control input devices |
|
* |
|
* Known issues: |
|
* - Consumes too much CPU |
|
*/ |
|
#include <stdio.h> |
|
#include <stdint.h> |
|
#include <malloc.h> |
|
#include <string.h> |
|
#define _USE_MATH_DEFINES |
|
#include <math.h> |
|
#include <time.h> |
|
|
|
#define BUILD_RAW_IQ_FILES |
|
|
|
#ifdef WIN32 |
|
|
|
#include <Windows.h> |
|
|
|
#define BUILD_WINDOWS_MME |
|
|
|
#endif |
|
|
|
/* |
|
* DSP routines |
|
*/ |
|
|
|
typedef float real_t; |
|
|
|
#define _R(x) ((real_t)(x)) |
|
|
|
#define M_TWOPI _R(2.0 * M_PI) |
|
|
|
typedef struct _complex_t { |
|
real_t re; |
|
real_t im; |
|
} complex_t; |
|
|
|
/* |
|
* Buffer management |
|
*/ |
|
|
|
typedef struct _real_buffer_t { |
|
real_t *samples; |
|
size_t length; |
|
size_t allocated; |
|
} real_buffer_t; |
|
|
|
typedef struct _complex_buffer_t { |
|
complex_t *samples; |
|
size_t length; |
|
size_t allocated; |
|
} complex_buffer_t; |
|
|
|
static complex_t complex_mul(complex_t x, complex_t y) |
|
{ |
|
real_t x_re = x.re; |
|
real_t x_im = x.im; |
|
real_t y_re = y.re; |
|
real_t y_im = y.im; |
|
complex_t r = { x_re * y_re - x_im * y_im, x_re * y_im + x_im * y_re }; |
|
return r; |
|
} |
|
|
|
void buffer_init_real(real_buffer_t *buffer, size_t samples) |
|
{ |
|
buffer->samples = (real_t *)calloc(samples, sizeof(real_t)); |
|
buffer->length = 0; |
|
buffer->allocated = samples; |
|
} |
|
|
|
void buffer_init_complex(complex_buffer_t *buffer, size_t samples) |
|
{ |
|
buffer->samples = (complex_t *)calloc(samples, sizeof(complex_t)); |
|
buffer->length = 0; |
|
buffer->allocated = samples; |
|
} |
|
|
|
void buffer_free_real(real_buffer_t *buffer) |
|
{ |
|
free(buffer->samples); |
|
} |
|
|
|
#define BUFFER_CONSUMED(b, sc) \ |
|
do { \ |
|
(b)->length -= (sc); \ |
|
memmove(&(b)->samples[0], &(b)->samples[(sc)], (b)->length * sizeof((b)->samples[0])); \ |
|
} while (0) |
|
|
|
#define BUFFER_PRODUCED(b, sc) \ |
|
do { \ |
|
(b)->length += (sc); \ |
|
} while (0) |
|
|
|
void buffer_real_to_complex(const real_buffer_t *input, complex_buffer_t *output) |
|
{ |
|
size_t n; |
|
|
|
for (n = 0; n < input->length; ++n) { |
|
output->samples[n].re = input->samples[n]; |
|
output->samples[n].im = 0; |
|
} |
|
} |
|
|
|
/* |
|
* Windowing functions |
|
* See: https://en.wikipedia.org/wiki/Window_function#A_list_of_window_functions |
|
*/ |
|
|
|
/* The prototype for all windowing functions */ |
|
typedef real_t (*windowing_func_t)(size_t length, size_t i); |
|
|
|
/* Generalised cosine window (DC + 1 cosine term) */ |
|
real_t dsp_cosine_window_1(size_t length, size_t i, real_t a, real_t b) |
|
{ |
|
real_t w = M_TWOPI / (length - 1); |
|
return a - b * cosf(w * i); |
|
} |
|
|
|
/* Generalised cosine window (DC + 2 cosine terms) */ |
|
real_t dsp_cosine_window_2(size_t length, size_t i, real_t a, real_t b, real_t c) |
|
{ |
|
real_t w = M_TWOPI / (length - 1); |
|
return a - b * cosf(w * i) + c * cosf(2.0f * w * i); |
|
} |
|
|
|
/* Generalised cosine window (DC + 3 cosine terms) */ |
|
real_t dsp_cosine_window_3(size_t length, size_t i, real_t a, real_t b, real_t c, real_t d) |
|
{ |
|
real_t w = M_TWOPI / (length - 1); |
|
return a - b * cosf(w * i) + c * cosf(_R(2) * w * i) - d * cosf(_R(3) * w * i); |
|
} |
|
|
|
real_t dsp_hamming_window(size_t length, size_t i) |
|
{ |
|
return dsp_cosine_window_1(length, i, _R(0.54), _R(0.46)); |
|
} |
|
|
|
real_t dsp_blackman_window(size_t length, size_t i) |
|
{ |
|
return dsp_cosine_window_2(length, i, _R(0.42), _R(0.5), _R(0.08)); |
|
} |
|
|
|
/* Generates a "matched" FIR filter kernel for sine wave signal. |
|
* Basically, a half of the wave. |
|
* kernel: output buffer to write to; |
|
* length: kernel length; |
|
*/ |
|
void dsp_generate_matched_filter_sine(real_t *kernel, size_t length, real_t fc) |
|
{ |
|
real_t w = M_TWOPI * fc; |
|
real_t h, s; |
|
size_t n; |
|
|
|
s = 0; |
|
for (n = 1; n <= length; ++n) { |
|
h = sinf(w * n); |
|
kernel[n - 1] = h; |
|
s += h; |
|
} |
|
s = _R(1) / s; |
|
for (n = 0; n < length; ++n) { |
|
kernel[n] *= s; |
|
} |
|
} |
|
|
|
/* Generates a windowed-sinc FIR kernel. |
|
* kernel: output buffer to write to; |
|
* length: kernel length; |
|
* fc: filter cutoff (in fraction of sampling frequency); |
|
* window: pointer to a winfowing function |
|
*/ |
|
void dsp_generate_windowed_sinc(real_t *kernel, int length, real_t fc, windowing_func_t window) |
|
{ |
|
size_t half_length = length / 2; |
|
real_t w = M_TWOPI * fc; |
|
real_t sum = 0; |
|
int i; |
|
|
|
/* Generate */ |
|
for (i = 0; i < length; ++i) { |
|
int x = i - half_length; |
|
real_t h; |
|
|
|
h = x == 0 ? w : (sinf(w * x) / x); |
|
h *= window(length, i); |
|
kernel[i] = h; |
|
sum += h; |
|
} |
|
/* Normalize to 0 dB gain on DC */ |
|
sum = _R(1) / sum; |
|
for (i = 0; i < length; ++i) { |
|
kernel[i] *= sum; |
|
} |
|
} |
|
|
|
/* Perform spectral inversion of the FIR filter kernel. |
|
* This flips the response upside down. |
|
*/ |
|
void dsp_spectral_inversion(real_t *kernel, size_t length) |
|
{ |
|
size_t i; |
|
|
|
for (i = 0; i < length; ++i) { |
|
kernel[i] = -kernel[i]; |
|
} |
|
kernel[length / 2] += _R(1.0); |
|
} |
|
|
|
/* Perform spectral reversal of the FIR filter kernel. |
|
* This flips the response left-to-right. |
|
*/ |
|
void dsp_spectral_reversal(real_t *kernel, size_t length) |
|
{ |
|
size_t i; |
|
|
|
for (i = 0; i < length; i += 2) { |
|
kernel[i] = -kernel[i]; |
|
} |
|
} |
|
|
|
/* Heterodyne by multiplying by exp(j*df) |
|
* If: |
|
* input = Ai * exp(phi_i) * exp(j * w_i * t) |
|
* then by multiplying we get |
|
* output = Ai * 1 * exp(phi_i) * exp(j * w_i * t) * exp(j * df * t) = Ai * exp(phi_i) * exp(j * (w_i + df) * t) |
|
*/ |
|
void dsp_heterodyne(const complex_buffer_t *input, real_t df, complex_buffer_t *output) |
|
{ |
|
size_t n; |
|
real_t w = M_TWOPI * df; |
|
|
|
for (n = 0; n < input->length; ++n) { |
|
real_t m_re = cosf(w * n); |
|
real_t m_im = sinf(w * n); |
|
|
|
output->samples[n].re = input->samples[n].re * m_re - input->samples[n].im * m_im; |
|
output->samples[n].im = input->samples[n].re * m_im + input->samples[n].im * m_re; |
|
} |
|
} |
|
|
|
/* Generate a wave fragment starting at phi |
|
*/ |
|
void dsp_generate_wave_complex(complex_buffer_t *buffer, real_t w, real_t phi) |
|
{ |
|
size_t n; |
|
|
|
for (n = 0; n < buffer->allocated; ++n) { |
|
buffer->samples[n].re = cosf(w * n + phi); |
|
buffer->samples[n].im = sinf(w * n + phi); |
|
} |
|
buffer->length = buffer->allocated; |
|
} |
|
|
|
/* Perform one convolution step. |
|
* x: points to the last sample in the block |
|
* kernel: points to the kernel |
|
* length: length of the kernel block |
|
*/ |
|
static real_t dsp_convolution_step_real(const real_t *x, const real_t *kernel, int length) |
|
{ |
|
int k; |
|
real_t y; |
|
|
|
y = 0; |
|
for (k = 0; k < length; ++k) { |
|
y += x[-k] * kernel[k]; |
|
} |
|
return y; |
|
} |
|
|
|
/* This function is a bottleneck */ |
|
static complex_t dsp_convolution_step_complex(const complex_t *x, const complex_t *kernel, int length) |
|
{ |
|
int k; |
|
const complex_t *kernel_limit = kernel + length; |
|
complex_t y; |
|
real_t y_re, y_im; |
|
|
|
y_re = y_im = 0; |
|
while (kernel < kernel_limit) { |
|
real_t a_re = x->re; |
|
real_t a_im = x->im; |
|
real_t b_re = kernel->re; |
|
real_t b_im = kernel->im; |
|
y_re += a_re * b_re - a_im * b_im; |
|
y_im += a_re * b_im + a_im * b_re; |
|
kernel++; |
|
x--; |
|
} |
|
y.re = y_re; |
|
y.im = y_im; |
|
return y; |
|
} |
|
|
|
|
|
void dump_signal_real(const char *path, const real_buffer_t *src) |
|
{ |
|
FILE *fp; |
|
|
|
fp = fopen(path, "w"); |
|
if (fp) { |
|
size_t n; |
|
|
|
for (n = 0; n < src->length; ++n) { |
|
fprintf(fp, "%f\n", src->samples[n]); |
|
} |
|
|
|
fclose(fp); |
|
} |
|
} |
|
|
|
void dump_signal_complex(const char *path, const complex_buffer_t *src) |
|
{ |
|
FILE *fp; |
|
|
|
fp = fopen(path, "w"); |
|
if (fp) { |
|
size_t n; |
|
|
|
for (n = 0; n < src->length; ++n) { |
|
fprintf(fp, "%f,%f\n", src->samples[n].re, src->samples[n].im); |
|
} |
|
|
|
fclose(fp); |
|
} |
|
} |
|
|
|
/* |
|
* Receiver code |
|
*/ |
|
|
|
#define DBG_PRINT_STATES 0 |
|
#define DBG_PRINT_INTERVALS 0 |
|
|
|
/* 2400 +-0.02%; translates to +-0.48 samples per bit */ |
|
#define BIT_RATE 2400 |
|
|
|
#define PHASE_SAMPLES_COUNT 24 |
|
|
|
typedef enum _channel_state_t { |
|
CHANNEL_PHASE_LOCK, |
|
CHANNEL_MEASUREMENTS, |
|
CHANNEL_RECEIVE_BITS, |
|
} channel_state_t; |
|
|
|
typedef struct _channel_t { |
|
/* Number of samples for a bit */ |
|
int bit_interval; |
|
/* IF channel filter */ |
|
complex_buffer_t if_filter; |
|
real_buffer_t bb_filter; |
|
/* Baseband AM signal */ |
|
real_buffer_t bb_samples; |
|
real_buffer_t bb_filtered_samples; |
|
/* Phase detection sample */ |
|
complex_buffer_t wave_sample; |
|
/* State of the RX state machine */ |
|
channel_state_t state; |
|
/* Used in the CHANNEL_MEASUREMENTS state */ |
|
int phase_count; |
|
int measurement_index; |
|
real_buffer_t halfbit_samples; |
|
real_t dc_samples[1024]; |
|
real_t phase_samples[1024]; |
|
real_t phase_samples_shifted[1024]; |
|
real_t delta_samples[1024]; |
|
uint32_t shift_reg1, shift_reg2; |
|
real_t dc_avg; |
|
/* Used in the CHANNEL_RECEIVE_BITS state */ |
|
real_t phase_error_addend; |
|
real_t phase_error_accum; |
|
uint8_t current_bit; |
|
uint8_t shift_reg; |
|
uint8_t bit_parity; |
|
int bit_count; |
|
real_t byte_dc; |
|
|
|
char message_buffer[256]; |
|
int message_index; |
|
int message_state; |
|
uint16_t computed_crc; |
|
uint16_t received_crc; |
|
} channel_t; |
|
|
|
typedef struct _receiver_t { |
|
/* Input IF samples from hardware */ |
|
complex_buffer_t if_samples; |
|
/* RX channels */ |
|
size_t num_channels; |
|
channel_t *channels; |
|
} receiver_t; |
|
|
|
typedef struct _acars_header_t { |
|
uint8_t soh; |
|
uint8_t mode; |
|
char aircraft_reg[7]; |
|
uint8_t tak; |
|
uint8_t label[2]; |
|
uint8_t block_id; |
|
uint8_t stx; |
|
} acars_header_t; |
|
|
|
void channel_process_message(channel_t *chan) |
|
{ |
|
acars_header_t *header = (acars_header_t *)chan->message_buffer; |
|
char *body = &chan->message_buffer[sizeof(acars_header_t)]; |
|
uint8_t eom; |
|
|
|
eom = chan->message_buffer[chan->message_index - 1]; |
|
chan->message_buffer[chan->message_index - 1] = '\0'; |
|
|
|
printf("ACARS mode: %c ", header->mode); |
|
if (header->aircraft_reg[0]) { |
|
printf("Aircraft reg: %c%c%c%c%c%c%c\n", |
|
header->aircraft_reg[0], header->aircraft_reg[1], header->aircraft_reg[2], |
|
header->aircraft_reg[3], header->aircraft_reg[4], header->aircraft_reg[5], header->aircraft_reg[6]); |
|
} |
|
else { |
|
printf("Aircraft reg: <NUL>\n"); |
|
} |
|
|
|
printf("Label: %c%c ", header->label[0], header->label[1]); |
|
if (header->block_id) { |
|
printf("Block ID: %c ", header->block_id); |
|
} |
|
else { |
|
printf("Block ID: <NUL> "); |
|
} |
|
if (header->tak != 0x15) { |
|
printf("Tech ACK: %c\n", header->tak); |
|
} |
|
else { |
|
printf("Tech ACK: <NAK>\n"); |
|
} |
|
if (header->stx == 0x02) { |
|
printf("Message content:\n"); |
|
fwrite(body, strlen(body), 1, stdout); |
|
} |
|
printf("\n---8<--------------8<---------------8<-----[DATE TIME]--\n\n"); |
|
} |
|
|
|
|
|
void channel_abort_rx(channel_t *chan) |
|
{ |
|
chan->state = CHANNEL_PHASE_LOCK; |
|
chan->measurement_index = 0; |
|
chan->phase_count = 0; |
|
chan->phase_error_accum = 0; |
|
chan->phase_error_addend = 0; |
|
} |
|
|
|
uint16_t crc_ccitt_update(uint16_t crc, uint8_t data) |
|
{ |
|
data ^= (uint8_t)(crc); |
|
data ^= data << 4; |
|
return ((((uint16_t)data << 8) | (crc >> 8)) ^ (uint8_t)(data >> 4) ^ ((uint16_t)data << 3)); |
|
} |
|
|
|
void channel_process_octet(channel_t *chan) |
|
{ |
|
uint8_t byte; |
|
|
|
byte = chan->shift_reg & 0x7F; |
|
#if DBG_PRINT_STATES > 0 |
|
printf("%02X '%c' %c dc=%.5f/%.5f\n", chan->shift_reg & 0x7F, byte >= 0x20 ? byte : ' ', "P "[!!chan->bit_parity], chan->byte_dc, chan->dc_avg); |
|
#endif |
|
|
|
switch (chan->message_state) { |
|
case 0: |
|
switch (byte) { |
|
case 0x16: /* SYN */ |
|
case 0x2A: |
|
case 0x2B: |
|
break; |
|
case 0x01: /* SOH */ |
|
chan->message_buffer[chan->message_index++] = byte; |
|
chan->message_state = 1; |
|
chan->computed_crc = 0; |
|
break; |
|
default: |
|
/* Unexpected character */ |
|
printf("Unexpected character (expected SYN); RX aborted.\n"); |
|
channel_abort_rx(chan); |
|
break; |
|
} |
|
break; |
|
case 1: |
|
chan->message_buffer[chan->message_index++] = byte; |
|
chan->computed_crc = crc_ccitt_update(chan->computed_crc, chan->shift_reg); |
|
if (chan->message_index >= 14) { |
|
switch (byte) { |
|
case 0x02: /* STX */ |
|
chan->message_state = 2; |
|
break; |
|
case 0x03: /* ETX */ |
|
chan->received_crc = 0; |
|
chan->message_state = 3; |
|
break; |
|
default: |
|
/* Unexpected character */ |
|
printf("Unexpected character (expected STX); RX aborted.\n"); |
|
channel_abort_rx(chan); |
|
} |
|
} |
|
break; |
|
case 2: |
|
chan->message_buffer[chan->message_index++] = byte; |
|
chan->computed_crc = crc_ccitt_update(chan->computed_crc, chan->shift_reg); |
|
switch (byte) { |
|
case 0x03: /* ETX */ |
|
case 0x17: /* ETB */ |
|
chan->received_crc = 0; |
|
chan->message_state = 3; |
|
break; |
|
} |
|
break; |
|
case 3: |
|
/* 1st CRC byte received */ |
|
chan->received_crc = chan->shift_reg; |
|
chan->message_state = 4; |
|
break; |
|
case 4: |
|
/* 2st CRC byte received */ |
|
chan->received_crc |= (uint16_t)chan->shift_reg << 8; |
|
if (chan->received_crc != chan->computed_crc) { |
|
printf("CRC mismatch; RX aborted.\n"); |
|
channel_abort_rx(chan); |
|
} |
|
else { |
|
chan->message_state = 5; |
|
} |
|
break; |
|
case 5: |
|
/* Expect DEL */ |
|
if (byte == 0x7F) { /* DEL */ |
|
chan->message_state = 0; |
|
printf("Message received successfully.\n"); |
|
channel_process_message(chan); |
|
channel_abort_rx(chan); |
|
} |
|
else { |
|
/* Unexpected character */ |
|
printf("Unexpected character (expected DEL); RX aborted.\n"); |
|
channel_abort_rx(chan); |
|
} |
|
break; |
|
} |
|
} |
|
|
|
static real_t compute_mean(const real_t *samples, size_t length) |
|
{ |
|
real_t m; |
|
size_t n; |
|
|
|
m = 0; |
|
for (n = 0; n < length; ++n) { |
|
m += samples[n]; |
|
} |
|
|
|
return m / length; |
|
} |
|
|
|
static void compute_stats(const real_t *signal, size_t length, real_t *avg, real_t *var) |
|
{ |
|
size_t n; |
|
real_t m, v; |
|
|
|
m = v = 0; |
|
for (n = 0; n < length; ++n) { |
|
m += signal[n]; |
|
} |
|
m /= length; |
|
*avg = m; |
|
for (n = 0; n < length; ++n) { |
|
real_t d; |
|
d = m - signal[n]; |
|
v += d * d; |
|
} |
|
*var = v / (length - 1); |
|
} |
|
|
|
static void compute_linear_regression(const real_t *samples, size_t count, real_t *alpha, real_t *beta) |
|
{ |
|
size_t n; |
|
real_t sy, syy; |
|
real_t sxy; |
|
real_t sx, sxx; |
|
real_t b; |
|
|
|
/* https://en.wikipedia.org/wiki/Simple_linear_regression#Fitting_the_regression_line */ |
|
|
|
sxy = syy = sy = 0; |
|
for (n = 0; n < count; ++n) { |
|
real_t y = samples[n]; |
|
|
|
sy += y; |
|
syy += y * y; |
|
sxy += n * y; |
|
} |
|
/* http://www.trans4mind.com/personal_development/mathematics/index.html */ |
|
sx = (real_t)n * n / _R(2) + (real_t)n / _R(2); |
|
sxx = (real_t)n * n * n / _R(3) + (real_t)n * n / _R(2) + (real_t)n / _R(6); |
|
|
|
b = (n * sxy - sx * sy) / (n * sxx - sx * sx); |
|
*alpha = sy / n - b * sx / n; |
|
*beta = b; |
|
} |
|
|
|
#if 0 |
|
Let y = exp(jwt) + exp(-jwt) be the real-valued baseband signal. |
|
|
|
Suppose y has a DC term as well: |
|
y = A * (exp(jwt) + exp(-jwt)) + DC |
|
|
|
Adding the phase shift: |
|
y = A * (exp(j(wt + p)) + exp(-j(wt + p))) + DC |
|
|
|
Multiplying by exp(jwt): |
|
u = exp(jwt) * (A * (exp(j(wt + p)) + exp(-j(wt + p))) + DC) = |
|
= A * exp(jwt) * exp(j(wt + p)) + A * exp(jwt) * exp(-j(wt + p)) + DC * exp(jwt) = |
|
= A * exp(jwt + j(wt + p)) + A * exp(jwt - j(wt + p)) + DC * exp(jwt) = |
|
= A * exp(j2wt) * exp(jp) + A * exp(jp) + DC * exp(jwt) |
|
|
|
Computing the 4 samples at 0, pi/4, 2pi/4, and 3pi/4: |
|
|
|
sample 1: |
|
A * exp(0) * exp(jp) + A * exp(jp) + DC * exp(0) |
|
sample 2: |
|
A * exp(j(2w*0.25 + p)) + A * exp(jp) + DC * exp(jw*0.25) = |
|
= A * exp(j(w*0.5 + p)) + A * exp(jp) + DC * exp(jw*0.25) = |
|
= A * exp(jw*0.5) * exp(jp) + A * exp(jp) + DC * exp(jw*0.25) = |
|
= -A * exp(jp) + A * exp(jp) + DC * j |
|
sample 3: |
|
A * exp(j(2w*0.5 + p)) + A * exp(jp) + DC * exp(jw*0.5) = |
|
= A * exp(j(w + p)) + A * exp(jp) + DC * exp(jw*0.5) = |
|
= A * exp(jw) * exp(jp) + A * exp(jp) + DC * exp(jw*0.5) = |
|
= A * exp(jp) + A * exp(jp) - DC |
|
sample 4: |
|
A * exp(j(2w*0.75 + p)) + A * exp(jp) + DC * exp(jw*0.75) = |
|
= A * exp(jw*0.5) * exp(jp) + A * exp(jp) + DC * exp(jw*0.75) = |
|
= -A * exp(jp) + A * exp(jp) - DC * j |
|
|
|
Summing up: |
|
(A * exp(jp) + A * exp(jp) + DC) + (-A * exp(jp) + A * exp(jp) + DC * j) + |
|
(A * exp(jp) + A * exp(jp) - DC) + (-A * exp(jp) + A * exp(jp) - DC * j) = |
|
= 4A * exp(jp) |
|
|
|
Basically, 4 samples would do well (without noise). |
|
With noise added, it is better to average. |
|
|
|
Also: Samples 2,3,4 are simply rotated by pi/4, 2pi/4, and 3pi/4. |
|
#endif |
|
|
|
/* Phase estimation, version 0 |
|
* Straightforward implementation of the maths above |
|
*/ |
|
static real_t compute_phase_estimate_v0(const real_t *bb_samples, complex_t *wave_samples, size_t length) |
|
{ |
|
size_t n; |
|
complex_t bb = { 0, 0 }; |
|
complex_t cc = { 0, 0 }; |
|
complex_t cc0, cc1, cc2, cc3; |
|
|
|
for (n = 0; n < length / 4; ++n) { |
|
bb.re = bb_samples[n]; |
|
cc0 = complex_mul(bb, wave_samples[n]); |
|
bb.re = bb_samples[n + 1 * length / 4]; |
|
cc1 = complex_mul(bb, wave_samples[n + 1 * length / 4]); |
|
bb.re = bb_samples[n + 2 * length / 4]; |
|
cc2 = complex_mul(bb, wave_samples[n + 2 * length / 4]); |
|
bb.re = bb_samples[n + 3 * length / 4]; |
|
cc3 = complex_mul(bb, wave_samples[n + 3 * length / 4]); |
|
cc.re += cc0.re + cc1.re + cc2.re + cc3.re; |
|
cc.im += cc0.im + cc1.im + cc2.im + cc3.im; |
|
} |
|
return atan2f(cc.re, cc.im) * _R(M_1_PI); |
|
} |
|
|
|
/* Phase estimation, version 1 |
|
* Removes redundant multiplications |
|
*/ |
|
static real_t compute_phase_estimate_v1(const real_t *bb_samples, complex_t *wave_samples, size_t length) |
|
{ |
|
size_t n; |
|
complex_t bb; |
|
complex_t cc = { 0, 0 }; |
|
|
|
for (n = 0; n < length / 4; ++n) { |
|
bb.re = bb_samples[n + 0 * length / 4] - bb_samples[n + 2 * length / 4]; |
|
bb.im = bb_samples[n + 1 * length / 4] - bb_samples[n + 3 * length / 4]; |
|
bb = complex_mul(bb, wave_samples[n]); |
|
cc.re += bb.re; |
|
cc.im += bb.im; |
|
} |
|
return atan2f(cc.re, cc.im) * _R(M_1_PI); |
|
} |
|
|
|
/* Phase estimation, version 2 |
|
* Only 4 samples are used, the fastest and noisiest variation |
|
* Note: looks quite a lot like the Tayloe mixer... |
|
*/ |
|
static real_t compute_phase_estimate_v2(const real_t *bb_samples, complex_t *wave_samples, size_t length) |
|
{ |
|
return _R(M_1_PI) * atan2f( |
|
bb_samples[0 * length / 4] - bb_samples[2 * length / 4], |
|
bb_samples[1 * length / 4] - bb_samples[3 * length / 4]); |
|
} |
|
|
|
#define compute_phase_estimate compute_phase_estimate_v1 |
|
|
|
/* Estimate MSK bit value based on a "baseline" geometric interpretation: |
|
* Count # of samples above the line between the first and the last sample. |
|
* Works great; fails only with a very low level signal. |
|
*/ |
|
static int compute_baseline_estimate(const real_t *samples, size_t length, real_t *_baseline_delta, size_t *count) |
|
{ |
|
real_t baseline, baseline_delta; |
|
size_t above_baseline; |
|
size_t n; |
|
size_t lm1 = length - 1; |
|
|
|
baseline_delta = (samples[lm1] - samples[0]) / lm1; |
|
baseline = samples[0]; |
|
above_baseline = 0; |
|
for (n = 1; n < lm1; ++n) { |
|
baseline += baseline_delta; |
|
above_baseline += samples[n] > baseline; |
|
} |
|
*_baseline_delta = baseline_delta; |
|
*count = above_baseline; |
|
return (above_baseline <= 2) || (above_baseline >= (length - 2)); |
|
} |
|
|
|
|
|
static int channel_state_phase_lock(channel_t *chan, real_t *bb_samples) |
|
{ |
|
size_t bit_interval = chan->bit_interval; |
|
int shift = 0; |
|
real_t dc; |
|
real_t phase; |
|
real_t avg_0, var_0; |
|
real_t avg_pi, var_pi; |
|
|
|
/* DC is not needed really */ |
|
dc = compute_mean(bb_samples, bit_interval); |
|
phase = compute_phase_estimate(bb_samples, &chan->wave_sample.samples[0], bit_interval); |
|
/* Accumulate two phases: raw and shifted by pi (with wrapping) */ |
|
chan->phase_samples[chan->measurement_index] = phase; |
|
chan->phase_samples_shifted[chan->measurement_index] = phase > 0 ? phase - 1 : phase + 1; |
|
|
|
chan->measurement_index++; |
|
if (chan->measurement_index == PHASE_SAMPLES_COUNT) { |
|
chan->measurement_index = 0; |
|
} |
|
|
|
/* Accumulate X samples before doing anything */ |
|
if (chan->phase_count < PHASE_SAMPLES_COUNT) { |
|
chan->phase_count++; |
|
return 0; |
|
} |
|
/* Ensure we don't do expensive maths each bit interval */ |
|
chan->phase_count -= PHASE_SAMPLES_COUNT / 2; |
|
|
|
compute_stats(chan->phase_samples, PHASE_SAMPLES_COUNT, &avg_0, &var_0); |
|
compute_stats(chan->phase_samples_shifted, PHASE_SAMPLES_COUNT, &avg_pi, &var_pi); |
|
|
|
#if DBG_PRINT_STATES > 1 |
|
printf("var_0 = %.5f/%.5f, dc = %.5f\n", var_0, var_pi, dc); |
|
#endif |
|
|
|
/* If the phase offset has stabilised, we've found the pre-key */ |
|
if (var_0 > 0.04 && var_pi > 0.04) { |
|
return 0; |
|
} |
|
|
|
/* A value of one for phase average means a shift of pi, or half of the bit interval */ |
|
if (var_0 < var_pi) { |
|
shift = (int)(bit_interval * _R(-0.5) * avg_0); |
|
} |
|
else { |
|
avg_pi = avg_pi > 0 ? avg_pi - 1 : avg_pi + 1; |
|
shift = (int)(bit_interval * _R(-0.5) * avg_pi); |
|
} |
|
/* Adjust the bit interval window; ensure the shift is always positive */ |
|
if (shift < 0) { |
|
shift += bit_interval; |
|
} |
|
printf("Phase lock acquired with shift = %d sample(s); var = %.5f/%.5f\n", shift, var_0, var_pi); |
|
/* Switch to the next state */ |
|
chan->state = CHANNEL_MEASUREMENTS; |
|
chan->measurement_index = 0; |
|
return shift; |
|
} |
|
|
|
static int channel_state_measurements(channel_t *chan, real_t *bb_samples) |
|
{ |
|
size_t bit_interval = chan->bit_interval; |
|
real_t baseline_delta; |
|
size_t above_baseline1, above_baseline2; |
|
int bit1, bit2; |
|
|
|
/* Compute the baseline estimate for the current bit interval window */ |
|
bit1 = compute_baseline_estimate(bb_samples, bit_interval, &baseline_delta, &above_baseline1); |
|
/* Compute the baseline estimate for the shifted bit window; copying required */ |
|
memcpy(&chan->halfbit_samples.samples[bit_interval / 2], &bb_samples[0], sizeof(real_t) * bit_interval / 2); |
|
bit2 = compute_baseline_estimate(chan->halfbit_samples.samples, bit_interval, &baseline_delta, &above_baseline2); |
|
memcpy(&chan->halfbit_samples.samples[0], &bb_samples[bit_interval / 2], sizeof(real_t) * bit_interval / 2); |
|
/* If we hit a one, then it's the end of pre-key and we should move on */ |
|
if (bit1 || bit2) { |
|
real_t phase_drift_alpha, phase_drift_beta; |
|
int shift; |
|
/* Collect stats */ |
|
chan->dc_avg = compute_mean(chan->dc_samples, chan->measurement_index); |
|
compute_linear_regression(chan->phase_samples, chan->measurement_index, &phase_drift_alpha, &phase_drift_beta); |
|
chan->phase_error_accum = bit_interval * _R(-0.5) * (phase_drift_alpha + phase_drift_beta * chan->measurement_index); |
|
chan->phase_error_addend = bit_interval * _R(-0.5) * phase_drift_beta; |
|
shift = bit2 ? -(int)(bit_interval / 2) : 0; |
|
printf("Bit lock acquired after about %d bit intervals; polarity %s\n", chan->measurement_index, bit1 ? "positive" : "negative"); |
|
printf("Preamble DC level = %.5f\n", chan->dc_avg); |
|
printf("Phase regression alpha = %+.7f, beta = %+.7f\n", phase_drift_alpha, phase_drift_beta); |
|
printf("Bit shifts: due to polarity: %d, due to baud rate error: %+.3f\n", shift, chan->phase_error_accum); |
|
/* Switch to the next state */ |
|
chan->state = CHANNEL_RECEIVE_BITS; |
|
chan->message_index = 0; |
|
chan->message_state = 0; |
|
/* Received so far: 011 */ |
|
chan->shift_reg = 0x7F; |
|
chan->bit_count = 3; |
|
chan->current_bit = 0; |
|
chan->bit_parity = 0; |
|
chan->byte_dc = chan->dc_avg * chan->bit_count; |
|
return (int)shift; |
|
} |
|
/* Means we are still on pre-key */ |
|
chan->dc_samples[chan->measurement_index] = compute_mean(bb_samples, bit_interval); |
|
chan->phase_samples[chan->measurement_index] = compute_phase_estimate(bb_samples, &chan->wave_sample.samples[0], bit_interval); |
|
chan->measurement_index++; |
|
/* 456 is the value that follows from the ARINC 618, but increased here a bit */ |
|
if (chan->measurement_index > 460) { |
|
printf("Could not bit-lock.\n"); |
|
channel_abort_rx(chan); |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static void channel_state_receive_bits(channel_t *chan, real_t *bb_samples) |
|
{ |
|
size_t bit_interval = chan->bit_interval; |
|
real_t dc, baseline_delta; |
|
size_t above_baseline; |
|
int bit; |
|
|
|
dc = compute_mean(bb_samples, bit_interval); |
|
bit = compute_baseline_estimate(bb_samples, bit_interval, &baseline_delta, &above_baseline); |
|
chan->byte_dc += dc; |
|
|
|
/* Flip the state if we received a one */ |
|
if (bit) { |
|
chan->current_bit ^= 0x80; |
|
} |
|
/* Shift the data in */ |
|
chan->shift_reg = chan->current_bit | (chan->shift_reg >> 1); |
|
chan->bit_parity ^= chan->current_bit; |
|
#if DBG_PRINT_STATES > 1 |
|
printf("DC %.5f BD %+.5f %2d %d %02X %02X\n", dc, baseline_delta, above_baseline, bit, chan->current_bit, chan->shift_reg); |
|
#endif |
|
if (chan->bit_count < 7) { |
|
chan->bit_count++; |
|
return; |
|
} |
|
/* Process the received octet */ |
|
chan->byte_dc *= _R(1) / 8; |
|
if (chan->byte_dc / chan->dc_avg < _R(0.333)) { |
|
printf("Measured DC level is too low; RX aborted.\n"); |
|
channel_abort_rx(chan); |
|
return; |
|
} |
|
if (!chan->bit_parity) { |
|
/* TODO: try fixing? */ |
|
} |
|
channel_process_octet(chan); |
|
chan->bit_count = 0; |
|
chan->bit_parity = 0; |
|
chan->byte_dc = 0; |
|
} |
|
|
|
void channel_process_if(channel_t *chan, complex_buffer_t *if_samples) |
|
{ |
|
size_t bit_interval = chan->bit_interval; |
|
int i, j; |
|
|
|
/* Process IF samples: apply channel bandpass filter and demodulate AM */ |
|
i = chan->if_filter.length; |
|
j = chan->bb_samples.length; |
|
while (i < if_samples->length && j < chan->bb_samples.allocated) { |
|
complex_t bb; |
|
|
|
/* Apply IF filter; the most expensive operation */ |
|
bb = dsp_convolution_step_complex(&if_samples->samples[i], chan->if_filter.samples, chan->if_filter.length); |
|
/* AM detection */ |
|
chan->bb_samples.samples[j] = hypotf(bb.re, bb.im); |
|
++i; |
|
++j; |
|
} |
|
chan->bb_samples.length = j; |
|
|
|
/* Process baseband samples: apply matched filter */ |
|
i = chan->bb_filter.length; |
|
j = chan->bb_filtered_samples.length; |
|
while (i < chan->bb_samples.length && j < chan->bb_filtered_samples.allocated) { |
|
chan->bb_filtered_samples.samples[j] = dsp_convolution_step_real(&chan->bb_samples.samples[i], chan->bb_filter.samples, chan->bb_filter.length); |
|
++i; |
|
++j; |
|
} |
|
BUFFER_CONSUMED(&chan->bb_samples, i - chan->bb_filter.length); |
|
chan->bb_filtered_samples.length = j; |
|
|
|
/* Move through the (filtered) baseband samples by bit intervals */ |
|
for (i = bit_interval; i < chan->bb_filtered_samples.length; i += bit_interval) { |
|
|
|
#if DBG_PRINT_INTERVALS |
|
for (j = 0; j < bit_interval; ++j) { |
|
printf("%f,", chan->bb_filtered_samples.samples[i - bit_interval + j]); |
|
} |
|
printf("\n"); |
|
#endif |
|
|
|
switch (chan->state) { |
|
case CHANNEL_PHASE_LOCK: /* No lock; phase statistics collection in progress */ |
|
i += channel_state_phase_lock(chan, &chan->bb_filtered_samples.samples[i - bit_interval]); |
|
break; |
|
|
|
case CHANNEL_MEASUREMENTS: /* Phase locked, measuring baud rate error and DC */ |
|
i += channel_state_measurements(chan, &chan->bb_filtered_samples.samples[i - bit_interval]); |
|
break; |
|
|
|
case CHANNEL_RECEIVE_BITS: /* Bit locked */ |
|
channel_state_receive_bits(chan, &chan->bb_filtered_samples.samples[i - bit_interval]); |
|
break; |
|
} |
|
|
|
/* Corrections against the baud rate error */ |
|
chan->phase_error_accum += chan->phase_error_addend; |
|
if (chan->phase_error_accum > _R(0.5)) { |
|
i++; |
|
chan->phase_error_accum -= _R(1); |
|
} |
|
else if (chan->phase_error_accum < _R(-0.5)) { |
|
i--; |
|
chan->phase_error_accum += _R(1); |
|
} |
|
} |
|
/* NOTE: There is danger of having i < bit_interval... */ |
|
BUFFER_CONSUMED(&chan->bb_filtered_samples, i - bit_interval); |
|
} |
|
|
|
#define IF_FILTER_LENGTH 81 |
|
|
|
void channel_init(channel_t *chan, real_t fs, real_t channel_freq, size_t samples_per_buffer) |
|
{ |
|
real_buffer_t temp_if; |
|
real_t w0; |
|
|
|
chan->bit_interval = (int)(fs / BIT_RATE); |
|
|
|
/* Generate a channel selection filter */ |
|
buffer_init_real(&temp_if, IF_FILTER_LENGTH); |
|
temp_if.length = IF_FILTER_LENGTH; |
|
dsp_generate_windowed_sinc(temp_if.samples, temp_if.length, 0.5f * 5000.0f / fs, dsp_blackman_window); |
|
buffer_init_complex(&chan->if_filter, IF_FILTER_LENGTH); |
|
buffer_real_to_complex(&temp_if, &chan->if_filter); |
|
buffer_free_real(&temp_if); |
|
chan->if_filter.length = IF_FILTER_LENGTH; |
|
dsp_heterodyne(&chan->if_filter, channel_freq / fs, &chan->if_filter); |
|
/* Generate a matched baseband filter */ |
|
buffer_init_real(&chan->bb_filter, chan->bit_interval / 2); |
|
chan->bb_filter.length = chan->bit_interval / 2 - 1; |
|
dsp_generate_matched_filter_sine(chan->bb_filter.samples, chan->bit_interval / 2, BIT_RATE / fs); |
|
/* Generate a sampled wave for phase estimator */ |
|
w0 = M_TWOPI / chan->bit_interval; |
|
buffer_init_complex(&chan->wave_sample, chan->bit_interval); |
|
dsp_generate_wave_complex(&chan->wave_sample, w0, 0); |
|
/* A buffer to store baseband samples */ |
|
buffer_init_real(&chan->bb_samples, samples_per_buffer + IF_FILTER_LENGTH + 1); |
|
/* A buffer to store baseband samples after filtering */ |
|
buffer_init_real(&chan->bb_filtered_samples, samples_per_buffer + chan->bit_interval / 2 + 1); |
|
/* A buffer to store samples for a half-bit period */ |
|
buffer_init_real(&chan->halfbit_samples, chan->bit_interval); |
|
|
|
channel_abort_rx(chan); |
|
} |
|
|
|
|
|
void receiver_process(receiver_t *rx) |
|
{ |
|
size_t n; |
|
size_t if_consumed; |
|
|
|
for (n = 0; n < rx->num_channels; ++n) { |
|
channel_t *chan = &rx->channels[n]; |
|
|
|
channel_process_if(chan, &rx->if_samples); |
|
} |
|
if_consumed = rx->if_samples.length - IF_FILTER_LENGTH; |
|
BUFFER_CONSUMED(&rx->if_samples, if_consumed); |
|
} |
|
|
|
typedef struct _input_2ch_16b_t { |
|
uint16_t data[2]; |
|
} input_2ch_16b_t; |
|
|
|
void receiver_refill_from_buffer_s16_swapped(receiver_t *rx, const input_2ch_16b_t *buffer, size_t count) |
|
{ |
|
size_t i; |
|
complex_buffer_t *if_samples = &rx->if_samples; |
|
complex_t *samples = &if_samples->samples[if_samples->length]; |
|
|
|
if (count + if_samples->length > if_samples->allocated) { |
|
count = if_samples->allocated - if_samples->length; |
|
} |
|
|
|
for (i = 0; i < count; ++i) { |
|
samples[i].re = buffer[i].data[1] / _R(32768); |
|
samples[i].im = buffer[i].data[0] / _R(32768); |
|
} |
|
|
|
if_samples->length += i; |
|
} |
|
|
|
#ifdef BUILD_RAW_IQ_FILES |
|
|
|
size_t refill_samples(FILE *fp, complex_buffer_t *dest) |
|
{ |
|
size_t remaining_space = dest->allocated - dest->length; |
|
complex_t *ptr = &dest->samples[dest->length]; |
|
size_t count; |
|
|
|
count = 0; |
|
while (remaining_space--) { |
|
int16_t s[2]; |
|
|
|
if (fread(s, sizeof(int16_t), 2, fp) != 2) { |
|
break; |
|
} |
|
|
|
ptr->re = s[0] / 32768.0f; |
|
ptr->im = s[1] / 32768.0f; |
|
ptr++; |
|
count++; |
|
} |
|
|
|
dest->length = ptr - &dest->samples[0]; |
|
return count; |
|
} |
|
|
|
#endif |
|
|
|
#ifdef BUILD_WINDOWS_MME |
|
|
|
#include <MMSystem.h> |
|
|
|
static HANDLE wmme_event; |
|
static HWAVEIN wmme; |
|
static WAVEHDR wmme_buffer0, wmme_buffer1; |
|
|
|
void wmme_enumerate(void) |
|
{ |
|
size_t device_id, devices_available; |
|
WAVEINCAPSA caps; |
|
|
|
devices_available = waveInGetNumDevs(); |
|
fprintf(stderr, "WMME input devices available: %d\n", devices_available); |
|
for (device_id = 0; device_id < devices_available; ++device_id) { |
|
waveInGetDevCapsA(device_id, &caps, sizeof(caps)); |
|
fprintf(stderr, "#%d %s\n", device_id, &caps.szPname[0]); |
|
} |
|
} |
|
|
|
int wmme_init_buffer(PWAVEHDR buffer, size_t length) |
|
{ |
|
memset(buffer, 0, sizeof(*buffer)); |
|
buffer->dwBufferLength = length; |
|
buffer->lpData = (LPSTR)malloc(length); |
|
return buffer->lpData == NULL; |
|
} |
|
|
|
int wmme_init(int device_id, unsigned fs, size_t bits_per_sample, size_t samples_per_buffer) |
|
{ |
|
WAVEFORMATEX format; |
|
|
|
format.wFormatTag = WAVE_FORMAT_PCM; |
|
format.nChannels = 2; |
|
format.nSamplesPerSec = fs; |
|
format.wBitsPerSample = bits_per_sample; |
|
format.nBlockAlign = format.nChannels * format.wBitsPerSample / 8; |
|
format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign; |
|
format.cbSize = 0; |
|
|
|
wmme_event = CreateEvent(NULL, FALSE, FALSE, NULL); |
|
if (!wmme_event) { |
|
return 1; |
|
} |
|
|
|
if (waveInOpen(&wmme, device_id, &format, (DWORD_PTR)wmme_event, (DWORD_PTR)NULL, CALLBACK_EVENT | WAVE_FORMAT_DIRECT) != MMSYSERR_NOERROR) { |
|
fprintf(stderr, "WMME: Could not open the audio device #%d.\n", device_id); |
|
return 1; |
|
} |
|
|
|
wmme_init_buffer(&wmme_buffer0, samples_per_buffer * format.nBlockAlign); |
|
wmme_init_buffer(&wmme_buffer1, samples_per_buffer * format.nBlockAlign); |
|
|
|
waveInPrepareHeader(wmme, &wmme_buffer0, sizeof(wmme_buffer0)); |
|
waveInAddBuffer(wmme, &wmme_buffer0, sizeof(wmme_buffer0)); |
|
waveInPrepareHeader(wmme, &wmme_buffer1, sizeof(wmme_buffer1)); |
|
waveInAddBuffer(wmme, &wmme_buffer1, sizeof(wmme_buffer1)); |
|
wmme_buffer0.dwUser = format.nBlockAlign; |
|
wmme_buffer1.dwUser = format.nBlockAlign; |
|
|
|
return 0; |
|
} |
|
|
|
int wmme_process_buffer(receiver_t *rx, PWAVEHDR buffer) |
|
{ |
|
waveInUnprepareHeader(wmme, buffer, sizeof(*buffer)); |
|
|
|
receiver_refill_from_buffer_s16_swapped(rx, (input_2ch_16b_t *)buffer->lpData, buffer->dwBytesRecorded / buffer->dwUser); |
|
|
|
buffer->dwFlags = 0; |
|
waveInPrepareHeader(wmme, buffer, sizeof(*buffer)); |
|
waveInAddBuffer(wmme, buffer, sizeof(*buffer)); |
|
return 0; |
|
} |
|
|
|
int wmme_input_loop(receiver_t *rx) |
|
{ |
|
if (waveInStart(wmme) != MMSYSERR_NOERROR) { |
|
fprintf(stderr, "WMME: Could not start the recording.\n"); |
|
return 1; |
|
} |
|
|
|
for (;;) { |
|
/* Wait for the event */ |
|
WaitForSingleObject(wmme_event, INFINITE); |
|
|
|
/* Grab the buffer; convert the data */ |
|
if (wmme_buffer0.dwFlags & WHDR_DONE) { |
|
wmme_process_buffer(rx, &wmme_buffer0); |
|
} |
|
if (wmme_buffer1.dwFlags & WHDR_DONE) { |
|
wmme_process_buffer(rx, &wmme_buffer1); |
|
} |
|
|
|
/* Trigger the RX processing */ |
|
receiver_process(rx); |
|
} |
|
|
|
if (waveInStop(wmme) != MMSYSERR_NOERROR) { |
|
fprintf(stderr, "WMME: Could not stop the recording.\n"); |
|
return 1; |
|
} |
|
} |
|
|
|
void wmme_fini() |
|
{ |
|
if (wmme) { |
|
waveInClose(wmme); |
|
wmme = NULL; |
|
} |
|
if (wmme_event) { |
|
CloseHandle(wmme_event); |
|
wmme_event = NULL; |
|
} |
|
} |
|
|
|
#endif |
|
|
|
int main(int argc, char *argv[]) |
|
{ |
|
FILE *fp; |
|
receiver_t rx; |
|
channel_t chan; |
|
|
|
buffer_init_complex(&rx.if_samples, 8192); |
|
rx.channels = &chan; |
|
rx.num_channels = 1; |
|
|
|
channel_init(&chan, _R(192000), _R(+25000), 25000); |
|
|
|
fp = fopen(argv[1], "rb"); |
|
printf("Running...\n"); |
|
while (refill_samples(fp, &rx.if_samples)) { |
|
receiver_process(&rx); |
|
} |
|
|
|
return 0; |
|
} |