Skip to content

Commit

Permalink
Merge branch 'devel': OQPSK support
Browse files Browse the repository at this point in the history
  • Loading branch information
dbdexter-dev committed Aug 12, 2019
2 parents c3a285d + 185a81a commit 7ab2534
Show file tree
Hide file tree
Showing 11 changed files with 211 additions and 100 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
VERSION=\"0.2.1\"
VERSION=\"0.3-beta1\"

export CFLAGS += -pipe -march=native -Wall -std=c99 -pedantic -D_XOPEN_SOURCE=700 -DVERSION=${VERSION}
export LDFLAGS +=
Expand Down
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
# Meteor-M2 Demodulator
# Meteor-M2 series demodulator

This is a free, open-source LRPT demodulator for the Meteor-M2 Russian weather
satellite. It supports reading from a I/Q recording in .wav format,
satellite series. It supports reading from a I/Q recording in .wav format,
and it outputs an 8-bit soft-QPSK file, from which you can generate an image
with the help of LRPTofflineDecoder or
[meteor\_decoder](https://github.com/artlav/meteor_decoder).

Please note that, to get the best decoding performance, you should downsample
the I/Q recording to about 140KHz.

## Compling and installing

As usual, type `make` to compile the project, `make install` to install the
Expand All @@ -25,6 +22,9 @@ Usage: meteor_demod [options] file_in
-R, --refresh-rate <ms> Refresh the status screen every <ms> ms (default: 50ms in TUI mode, 5000ms in batch mode)
-B, --batch Do not use ncurses, write the message log to stdout instead
-q, --quiet Do not print status information
-m, --mode <mode> Specify the signal modulation scheme (default: qpsk)
Available modes: qpsk (Meteor-M 2), oqpsk (Meteor-M 2-2)
Advanced options:
-b, --pll-bw <bw> Set the PLL bandwidth to <bw> (default: 100)
Expand Down
164 changes: 115 additions & 49 deletions src/demod.c
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,14 @@

typedef struct {
Demod *self;
const char *out_fname;
FILE *fd;
} ThrArgs;

static void* demod_thr_run(void* args);
static void* demod_thr_qpsk(void* args);
static void* demod_thr_oqpsk(void* args);

Demod*
demod_init(Source *src, unsigned interp_mult, unsigned rrc_order, float rrc_alpha, float pll_bw, unsigned sym_rate)
demod_init(Source *src, unsigned interp_mult, unsigned rrc_order, float rrc_alpha, float pll_bw, unsigned sym_rate, ModScheme mode)
{
Demod *ret;

Expand All @@ -34,7 +35,8 @@ demod_init(Source *src, unsigned interp_mult, unsigned rrc_order, float rrc_alph

/* Initialize Costas loop */
pll_bw = 2*M_PI*pll_bw/sym_rate;
ret->cst = costas_init(pll_bw);
ret->cst = costas_init(pll_bw, mode);
ret->mode = mode;

/* Initialize the timing recovery variables */
ret->sym_rate = sym_rate;
Expand All @@ -53,10 +55,20 @@ demod_start(Demod *self, const char *fname)

args = safealloc(sizeof(*args));

args->out_fname = fname;

args->self = self;
if (!(args->fd = fopen(fname, "wb"))) {
fatal("Could not create/open output file");
free(args);
/* Not reached */
return;
}

pthread_create(&self->t, NULL, demod_thr_run, (void*)args);
if (self->mode == QPSK) {
pthread_create(&self->t, NULL, demod_thr_qpsk, (void*)args);
} else if (self->mode == OQPSK) {
pthread_create(&self->t, NULL, demod_thr_oqpsk, (void*)args);
}
}

int
Expand Down Expand Up @@ -98,6 +110,10 @@ demod_get_size(const Demod *self)
float
demod_get_freq(const Demod *self)
{
if (self->mode == OQPSK) {
return 2*self->cst->nco_freq*self->sym_rate/(2*M_PI);
}

return self->cst->nco_freq*self->sym_rate/(2*M_PI);
}

Expand Down Expand Up @@ -131,76 +147,126 @@ demod_join(Demod *self)
}

/* Static functions {{{ */
/* Append a symbol to a file */
void
demod_write_symbol(Demod *self, FILE *out_fd, float complex sym, int nobuffer)
{
static int offset = 0;
int8_t *const out_buf = self->out_buf;

out_buf[offset++] = clamp(crealf(sym)/2);
out_buf[offset++] = clamp(cimagf(sym)/2);

if (offset >= SYM_CHUNKSIZE - 1 || nobuffer) {
fwrite(out_buf, offset, 1, out_fd);
offset = 0;
}

pthread_mutex_lock(&self->mutex);
self->bytes_out_count += 2;
pthread_mutex_unlock(&self->mutex);
}

void*
demod_thr_run(void* x)
demod_thr_qpsk(void* x)
{
FILE *out_fd;
int i, count, buf_offset;
const ThrArgs *args = (ThrArgs*)x;
int i, count;
float complex tmp;
float complex before, mid, cur;
float resync_offset, resync_error, resync_period;
int8_t *out_buf;

const ThrArgs *args = (ThrArgs*)x;
Demod *self = args->self;
out_buf = self->out_buf;

resync_period = self->sym_period;

if (args->out_fname) {
if (!(out_fd = fopen(args->out_fname, "w"))) {
fatal("Could not open file for writing");
/* Not reached */
return NULL;
}
} else {
fatal("No output filename specified");
/* Not reached */
return NULL;
}

/* Main processing loop */
buf_offset = 0;
resync_offset = 0;
before = 0;
mid = 0;
cur = 0;

/* Main processing loop */
while (self->thr_is_running && (count = self->interp->read(self->interp, CHUNKSIZE))) {
for (i=0; i<count; i++) {
/* Symbol resampling */
tmp = self->interp->data[i];

/* symbol timing recovery (Gardner) */
if (resync_offset >= resync_period/2 && resync_offset < resync_period/2+1) {
mid = agc_apply(self->agc, self->interp->data[i]);
mid = agc_apply(self->agc, tmp);
} else if (resync_offset >= resync_period) {
cur = agc_apply(self->agc, self->interp->data[i]);
/* The current sample is in the correct time slot: process it */
/* Calculate the symbol timing error (Gardner algorithm) */
cur = agc_apply(self->agc, tmp);
resync_offset -= resync_period;
resync_error = (cimagf(cur) - cimagf(before)) * cimagf(mid);
resync_offset += (resync_error*resync_period/2000000.0);
before = cur;

/* Fine frequency/phase tuning */
cur = costas_resync(self->cst, cur);

/* Append the new samples to the output buffer */
out_buf[buf_offset++] = clamp(crealf(cur)/2);
out_buf[buf_offset++] = clamp(cimagf(cur)/2);

/* Write binary stream to file and/or to socket */
if (buf_offset >= SYM_CHUNKSIZE - 1) {
fwrite(out_buf, buf_offset, 1, out_fd);
buf_offset = 0;
}
pthread_mutex_lock(&self->mutex);
self->bytes_out_count += 2;
pthread_mutex_unlock(&self->mutex);
/* Costas loop frequency/phase tuning */
cur = costas_mix(self->cst, cur);
costas_correct_phase(self->cst, costas_delta(cur, cur));

/* Append the new samples to the output file */
demod_write_symbol(self, args->fd, cur, 0);
}
resync_offset++;
}
}

/* Write the remaining bytes */
fwrite(out_buf, buf_offset, 1, out_fd);
fclose(out_fd);
demod_write_symbol(self, args->fd, 0, 1);
fclose(args->fd);

free(x);
self->thr_is_running = 0;
return NULL;
}

void*
demod_thr_oqpsk(void *x)
{
int i, count;
float complex tmp, inphase, quad;
float complex before, mid, cur;
float prev_i;
float resync_error, resync_offset;
const ThrArgs *args = (ThrArgs*)x;
Demod *const self = args->self;

resync_offset = 0;
before = 0;
prev_i = 0;

/* Main processing loop */
while (self->thr_is_running && (count = self->interp->read(self->interp, CHUNKSIZE))) {
for (i=0; i<count; i++) {
tmp = self->interp->data[i];

if (resync_offset >= self->sym_period/2 && resync_offset < self->sym_period/2+1) {
inphase = costas_mix(self->cst, agc_apply(self->agc, tmp));
mid = prev_i + I * cimagf(inphase);
prev_i = crealf(inphase);

} else if (resync_offset >= self->sym_period) {
/* Symbol timing recovery (Gardner) */
quad = costas_mix(self->cst, agc_apply(self->agc, tmp));
cur = prev_i + I * cimagf(quad);
prev_i = crealf(quad);

resync_offset -= self->sym_period;
resync_error = (cimagf(quad) - cimagf(before)) * cimagf(mid);
resync_offset += (resync_error*self->sym_period/2000000.0);
before = cur;

/* Carrier tracking */
costas_correct_phase(self->cst, costas_delta(inphase, quad));
tmp = crealf(inphase) + I * cimagf(quad);

demod_write_symbol(self, args->fd, tmp, 0);
}
resync_offset++;
}
}

demod_write_symbol(self, args->fd, 0, 1);
fclose(args->fd);

free(x);
self->thr_is_running = 0;
Expand Down
4 changes: 3 additions & 1 deletion src/include/demod.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include "agc.h"
#include "pll.h"
#include "source.h"
#include "utils.h"

/* I/O chunk sizes */
#define CHUNKSIZE 32768
Expand All @@ -21,6 +22,7 @@ typedef struct {
Costas *cst;
float sym_period;
unsigned sym_rate;
ModScheme mode;
pthread_t t;

pthread_mutex_t mutex;
Expand All @@ -29,7 +31,7 @@ typedef struct {
int8_t out_buf[SYM_CHUNKSIZE];
} Demod;

Demod* demod_init(Source *src, unsigned interp_factor, unsigned rrc_order, float rrc_alpha, float pll_bw, unsigned sym_rate);
Demod* demod_init(Source *src, unsigned interp_factor, unsigned rrc_order, float rrc_alpha, float pll_bw, unsigned sym_rate, ModScheme mode);
void demod_start(Demod *self, const char *fname);
void demod_join(Demod *self);

Expand Down
3 changes: 2 additions & 1 deletion src/include/options.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,15 @@
#include <getopt.h>
#include <stdlib.h>

#define SHORTOPTS "a:b:Bf:ho:O:qr:R:s:S:vw"
#define SHORTOPTS "a:b:Bf:hm:o:O:qr:R:s:S:vw"

struct option longopts[] = {
{ "alpha", 1, NULL, 'a' },
{ "pll-bw", 1, NULL, 'b' },
{ "batch", 1, NULL, 'B' },
{ "fir-order", 1, NULL, 'f' },
{ "help", 0, NULL, 'h' },
{ "mode", 1, NULL, 'm' },
{ "output", 1, NULL, 'o' },
{ "oversamp", 1, NULL, 'O' },
{ "quiet", 0, NULL, 'q' },
Expand Down
9 changes: 7 additions & 2 deletions src/include/pll.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
#define METEOR_PLL_H

#include <complex.h>

#include "utils.h"

/* Costas loop default parameters */
#define COSTAS_BW 100
Expand All @@ -22,12 +22,17 @@ typedef struct {
float damping, bw;
int locked;
float moving_avg;
ModScheme mode;
} Costas;

Costas* costas_init(float bw);
Costas* costas_init(float bw, ModScheme mode);
float complex costas_resync(Costas *self, float complex samp);
float complex costas_mix(Costas *self, float complex samp);
void costas_free(Costas *self);

float costas_delta(float complex samp, float complex cosamp);

void costas_recompute_coeffs(Costas *self, float damping, float bw);
void costas_correct_phase(Costas *self, float err);

#endif
7 changes: 7 additions & 0 deletions src/include/utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@
#include <complex.h>
#include <stdlib.h>

typedef enum {
QPSK,
OQPSK,
} ModScheme;

int8_t clamp(float x);
float float_clamp(float x, float max_abs);
int slice(float x);
Expand All @@ -19,6 +24,8 @@ int dehumanize(const char *buf);
char* gen_fname(void);
void seconds_to_str(unsigned secs, char *buf);

ModScheme parse_mode(char *str);

void usage(const char *pname);
void fatal(const char *msg);
void splash(void);
Expand Down
Loading

0 comments on commit 7ab2534

Please sign in to comment.