[ ] consider moving this file to root
[ ] sep SVN repo, automake etc?
-[ ] list each fdmdv.m script (ut, mod, demod) and Cprogram/src and what it does
+[ ] list each fdmdv.m script (ut, mod, demod) and C program/src and what it does
[ ] example usage
[ ] repair fdmdv_ut and mod/demod after new statres exposed and var renames
[X] Get file based mod and demod working again
[ ] test with freq offsets
[ ] measure execution speed
[ ] document use of fdmdv_ut and fdmdv_demod + PathSim
+[ ] more positibe form of sync reqd for DV frames?
+ + like using track/acquire bit
+[ ] more robust track/acquite state machine?
+ + e.g. hang on thr fades?
[ ] block diagram
[ ] maybe in ascii art
[ ] blog posts(s)
fclose(f);
endfunction
+
% Saves hanning window coeffs to a text file in the form of a C array
function hanning_file(filename)
endfunction
+function png_file(fig, pngfilename)
+ figure(fig);
+
+ pngname = sprintf("%s.png",pngfilename);
+ print(pngname, '-dpng', "-S500,500")
+ pngname = sprintf("%s_large.png",pngfilename);
+ print(pngname, '-dpng', "-S800,600")
+endfunction
+
% Initialise ----------------------------------------------------
global pilot_bit;
% Version 2
%
-function fdmdv_demod(rawfilename, nbits)
+function fdmdv_demod(rawfilename, nbits, pngname)
fdmdv; % include modem code
% frequency offset estimation and correction
[pilot prev_pilot pilot_lut_index prev_pilot_lut_index] = get_pilot(pilot_lut_index, prev_pilot_lut_index, nin);
- foff_coarse = rx_est_freq_offset(rx_fdm, pilot, prev_pilot, nin);
+ [foff_coarse S1 S2] = rx_est_freq_offset(rx_fdm, pilot, prev_pilot, nin);
if track == 0
foff = foff_coarse;
end
axis([0 secs 0 1.5]);
title('Test Frame Sync')
+
+
endfunction
library_includedir = $(prefix)/include
library_include_HEADERS = codec2.h
-bin_PROGRAMS = c2demo c2enc c2dec c2sim fdmdv_mod
+bin_PROGRAMS = c2demo c2enc c2dec c2sim fdmdv_mod fdmdv_demod
c2demo_SOURCES = c2demo.c
c2demo_LDADD = $(lib_LTLIBRARIES)
c2sim_LDADD = $(lib_LTLIBRARIES)
c2sim_LDFLAGS = $(LIBS)
-fdmdv_mod_SOURCES = fdmdv_mod.c fdmdv.c fft.c kiss_fft.c octave.c
+fdmdv_mod_SOURCES = fdmdv_mod.c fdmdv.c fft.c kiss_fft.c
fdmdv_mod_LDFLAGS = $(LIBS)
+
+fdmdv_demod_SOURCES = fdmdv_demod.c fdmdv.c fft.c kiss_fft.c octave.c
+fdmdv_demod_LDFLAGS = $(LIBS)
host_triplet = @host@
noinst_PROGRAMS = generate_codebook$(EXEEXT) genlspdtcb$(EXEEXT)
bin_PROGRAMS = c2demo$(EXEEXT) c2enc$(EXEEXT) c2dec$(EXEEXT) \
- c2sim$(EXEEXT) fdmdv_mod$(EXEEXT)
+ c2sim$(EXEEXT) fdmdv_mod$(EXEEXT) fdmdv_demod$(EXEEXT)
subdir = src
DIST_COMMON = $(library_include_HEADERS) $(srcdir)/Makefile.am \
$(srcdir)/Makefile.in
am_c2sim_OBJECTS = c2sim.$(OBJEXT)
c2sim_OBJECTS = $(am_c2sim_OBJECTS)
c2sim_DEPENDENCIES = $(am__DEPENDENCIES_1)
-am_fdmdv_mod_OBJECTS = fdmdv_mod.$(OBJEXT) fdmdv.$(OBJEXT) \
+am_fdmdv_demod_OBJECTS = fdmdv_demod.$(OBJEXT) fdmdv.$(OBJEXT) \
fft.$(OBJEXT) kiss_fft.$(OBJEXT) octave.$(OBJEXT)
+fdmdv_demod_OBJECTS = $(am_fdmdv_demod_OBJECTS)
+fdmdv_demod_LDADD = $(LDADD)
+am_fdmdv_mod_OBJECTS = fdmdv_mod.$(OBJEXT) fdmdv.$(OBJEXT) \
+ fft.$(OBJEXT) kiss_fft.$(OBJEXT)
fdmdv_mod_OBJECTS = $(am_fdmdv_mod_OBJECTS)
fdmdv_mod_LDADD = $(LDADD)
generate_codebook_SOURCES = generate_codebook.c
LINK = $(LIBTOOL) --tag=CC --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
$(AM_LDFLAGS) $(LDFLAGS) -o $@
SOURCES = $(libcodec2_la_SOURCES) $(c2dec_SOURCES) $(c2demo_SOURCES) \
- $(c2enc_SOURCES) $(c2sim_SOURCES) $(fdmdv_mod_SOURCES) \
- generate_codebook.c genlspdtcb.c
+ $(c2enc_SOURCES) $(c2sim_SOURCES) $(fdmdv_demod_SOURCES) \
+ $(fdmdv_mod_SOURCES) generate_codebook.c genlspdtcb.c
DIST_SOURCES = $(libcodec2_la_SOURCES) $(c2dec_SOURCES) \
$(c2demo_SOURCES) $(c2enc_SOURCES) $(c2sim_SOURCES) \
- $(fdmdv_mod_SOURCES) generate_codebook.c genlspdtcb.c
+ $(fdmdv_demod_SOURCES) $(fdmdv_mod_SOURCES) \
+ generate_codebook.c genlspdtcb.c
library_includeHEADERS_INSTALL = $(INSTALL_HEADER)
HEADERS = $(library_include_HEADERS)
ETAGS = etags
c2sim_SOURCES = c2sim.c
c2sim_LDADD = $(lib_LTLIBRARIES)
c2sim_LDFLAGS = $(LIBS)
-fdmdv_mod_SOURCES = fdmdv_mod.c fdmdv.c fft.c kiss_fft.c octave.c
+fdmdv_mod_SOURCES = fdmdv_mod.c fdmdv.c fft.c kiss_fft.c
fdmdv_mod_LDFLAGS = $(LIBS)
+fdmdv_demod_SOURCES = fdmdv_demod.c fdmdv.c fft.c kiss_fft.c octave.c
+fdmdv_demod_LDFLAGS = $(LIBS)
all: all-am
.SUFFIXES:
c2sim$(EXEEXT): $(c2sim_OBJECTS) $(c2sim_DEPENDENCIES)
@rm -f c2sim$(EXEEXT)
$(LINK) $(c2sim_LDFLAGS) $(c2sim_OBJECTS) $(c2sim_LDADD) $(LIBS)
+fdmdv_demod$(EXEEXT): $(fdmdv_demod_OBJECTS) $(fdmdv_demod_DEPENDENCIES)
+ @rm -f fdmdv_demod$(EXEEXT)
+ $(LINK) $(fdmdv_demod_LDFLAGS) $(fdmdv_demod_OBJECTS) $(fdmdv_demod_LDADD) $(LIBS)
fdmdv_mod$(EXEEXT): $(fdmdv_mod_OBJECTS) $(fdmdv_mod_DEPENDENCIES)
@rm -f fdmdv_mod$(EXEEXT)
$(LINK) $(fdmdv_mod_LDFLAGS) $(fdmdv_mod_OBJECTS) $(fdmdv_mod_LDADD) $(LIBS)
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/c2enc.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/c2sim.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fdmdv.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fdmdv_demod.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fdmdv_mod.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fft.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/generate_codebook.Po@am__quote@
\*---------------------------------------------------------------------------*/
-void codec2_destroy(struct FDMDV *fdmdv)
+void fdmdv_destroy(struct FDMDV *fdmdv)
{
assert(fdmdv != NULL);
free(fdmdv);
COMP rx_fdm_fcorr[M+M/P];
COMP rx_baseband[NC+1][M+M/P];
COMP rx_filt[NC+1][P+1];
- float rx_timing;
float env[NT*P];
- COMP rx_symbols[NC+1];
/* freq offset estimation and correction */
fdm_downconvert(rx_baseband, rx_fdm_fcorr, fdmdv->phase_rx, fdmdv->freq, *nin);
rx_filter(rx_filt, rx_baseband, fdmdv->rx_filter_memory, *nin);
- rx_timing = rx_est_timing(rx_symbols, rx_filt, rx_baseband, fdmdv->rx_filter_mem_timing, env, fdmdv->rx_baseband_mem_timing, *nin);
- foff_fine = qpsk_to_bits(rx_bits, sync_bit, fdmdv->prev_rx_symbols, rx_symbols);
- memcpy(fdmdv->prev_rx_symbols, rx_symbols, sizeof(COMP)*(NC+1));
+ fdmdv->rx_timing = rx_est_timing(fdmdv->rx_symbols, rx_filt, rx_baseband, fdmdv->rx_filter_mem_timing, env, fdmdv->rx_baseband_mem_timing, *nin);
+ foff_fine = qpsk_to_bits(rx_bits, sync_bit, fdmdv->prev_rx_symbols, fdmdv->rx_symbols);
+ memcpy(fdmdv->prev_rx_symbols, fdmdv->rx_symbols, sizeof(COMP)*(NC+1));
/* freq offset estimation state machine */
fdmdv->foff -= TRACK_COEFF*foff_fine;
}
+/*---------------------------------------------------------------------------*\
+
+ FUNCTION....: fdmdv_get_demod_stats()
+ AUTHOR......: David Rowe
+ DATE CREATED: 1 May 2012
+
+ Fills a structure with a bunch of demod information.
+
+\*---------------------------------------------------------------------------*/
+
+void fdmdv_get_demod_stats(struct FDMDV *fdmdv, struct FDMDV_STATS *fdmdv_stats)
+{
+ COMP pi_on_4;
+ int c;
+
+ pi_on_4.real = cos(PI/4.0);
+ pi_on_4.imag = sin(PI/4.0);
+
+ fdmdv_stats->snr = 0.0; /* TODO - implement SNR estimation */
+ fdmdv_stats->fest_coarse_fine = fdmdv->coarse_fine;
+ fdmdv_stats->foff = fdmdv->foff;
+ fdmdv_stats->rx_timing = fdmdv->rx_timing/M;
+ fdmdv_stats->clock_offset = 0.0; /* TODO - implement clock offset estimation */
+
+ /* adjust for phase offset to make suitable for scatter plot */
+
+ assert((NC+1) == FDMDV_NSYM);
+
+ for(c=0; c<NC+1; c++)
+ fdmdv_stats->rx_symbols[c] = cmult(cmult(fdmdv->rx_symbols[c], cconj(fdmdv->prev_rx_symbols[c])), pi_on_4);
+
+}
+
References:
- [1] http://n1su.com/fdmdv/FDMDV_Docs_Rel_1_4b.pdf
+ [1] http://n1su.com/fdmdv/FDMDV_Docs_Rel_1_4b.pdf
\*---------------------------------------------------------------------------*/
#define FDMDV_BITS_PER_FRAME 28 /* odd/even frames 56 bits, 1400 bit/s */
#define FDMDV_SAMPLES_PER_FRAME 160 /* 8000 Hz sample rate */
#define FDMDV_SCALE 1000 /* suggested scaling for 16 bit shorts */
+#define FDMDV_NSYM 15
struct FDMDV;
struct FDMDV_STATS {
- float snr; /* estimated SNR of rx signal in dB */
- COMP *rx_symbols; /* NC+1 latest received symbols, for scatter plot */
- int fest_track; /* == 0, freq est in acquire mode, == 1 in track mode */
- float foff; /* estimated freq offset in Hz */
- float timing; /* timing offset 0..1 as fraction of symbol period */
- float clock_offset; /* Estimated tx/rx sample clock offset in ppm */
+ float snr; /* estimated SNR of rx signal in dB */
+ COMP rx_symbols[FDMDV_NSYM]; /* latest received symbols, for scatter plot */
+ int fest_coarse_fine; /* freq est state, 0-coarse 1-fine */
+ float foff; /* estimated freq offset in Hz */
+ float rx_timing; /* timing offset -1..1 as fraction of symbol period */
+ float clock_offset; /* Estimated tx/rx sample clock offset in ppm */
};
struct FDMDV *fdmdv_create(void);
void fdmdv_get_test_bits(struct FDMDV *fdmdv_state, int tx_bits[]);
void fdmdv_put_test_bits(struct FDMDV *f, int *sync, int *bit_errors, int rx_bits[]);
-float fdmdv_get_demod_stats(struct FDMDV *fdmdv_state, struct FDMDV_STATS *fdmdv_stats);
+void fdmdv_get_demod_stats(struct FDMDV *fdmdv_state, struct FDMDV_STATS *fdmdv_stats);
void fdmdv_get_waterfall_line(struct FDMDV *fdmdv_state, float magnitudes[], int *magnitude_points);
#endif
--- /dev/null
+/*---------------------------------------------------------------------------*\
+
+ FILE........: fdmdv_demod.c
+ AUTHOR......: David Rowe
+ DATE CREATED: April 30 2012
+
+ Given an input raw file (8kHz, 16 bit shorts) of FDMDV modem samples
+ outputs a file of bits. The output file is assumed to be arranged
+ as codec frames of 56 bits (7 bytes) which are received as two 28
+ bit modem frames.
+
+\*---------------------------------------------------------------------------*/
+
+
+/*
+ Copyright (C) 2012 David Rowe
+
+ All rights reserved.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License version 2, as
+ published by the Free Software Foundation. This program is
+ distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
+ License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with this program; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <errno.h>
+
+#include "fdmdv.h"
+#include "octave.h"
+
+#define BITS_PER_CODEC_FRAME (2*FDMDV_BITS_PER_FRAME)
+#define BYTES_PER_CODEC_FRAME (BITS_PER_CODEC_FRAME/8)
+
+/* lof of information we want to sump to Octave */
+
+#define MAX_FRAMES 50*60
+
+void dump_to_octave(struct FDMDV *fdmdv, FILE *f);
+
+int main(int argc, char *argv[])
+{
+ FILE *fin, *fout;
+ struct FDMDV *fdmdv;
+ char packed_bits[BYTES_PER_CODEC_FRAME];
+ int rx_bits[FDMDV_BITS_PER_FRAME];
+ int codec_bits[2*FDMDV_BITS_PER_FRAME];
+ float rx_fdm[FDMDV_SAMPLES_PER_FRAME];
+ short rx_fdm_scaled[FDMDV_SAMPLES_PER_FRAME];
+ int i, bit, byte, c;
+ int nin;
+ int sync_bit;
+ int state, next_state;
+
+ int frames;
+ FILE *foct;
+ struct FDMDV_STATS stats;
+ COMP rx_symbols_log[FDMDV_NSYM][MAX_FRAMES];
+ int coarse_fine_log[MAX_FRAMES];
+ float rx_timing_log[MAX_FRAMES];
+ float foff_log[MAX_FRAMES];
+
+ if (argc < 3) {
+ printf("usage: %s InputModemRawFile OutputBitFile [OctaveDumpFile]\n", argv[0]);
+ printf("e.g %s hts1a_fdmdv.raw hts1a.c2\n", argv[0]);
+ exit(1);
+ }
+
+ if (strcmp(argv[1], "-") == 0) fin = stdin;
+ else if ( (fin = fopen(argv[1],"rb")) == NULL ) {
+ fprintf(stderr, "Error opening input modem sample file: %s: %s.\n",
+ argv[1], strerror(errno));
+ exit(1);
+ }
+
+ if (strcmp(argv[2], "-") == 0) fout = stdout;
+ else if ( (fout = fopen(argv[2],"wb")) == NULL ) {
+ fprintf(stderr, "Error opening output bit file: %s: %s.\n",
+ argv[2], strerror(errno));
+ exit(1);
+ }
+
+ fdmdv = fdmdv_create();
+ frames = 0;
+ state = 0;
+ nin = FDMDV_SAMPLES_PER_FRAME;
+
+ while(fread(rx_fdm_scaled, sizeof(short), nin, fin) == nin)
+ {
+ for(i=0; i<FDMDV_SAMPLES_PER_FRAME; i++)
+ rx_fdm[i] = rx_fdm_scaled[i]/FDMDV_SCALE;
+ fdmdv_demod(fdmdv, rx_bits, &sync_bit, rx_fdm, &nin);
+
+ /* log data for optional Octave dump */
+
+ if (frames < MAX_FRAMES) {
+ fdmdv_get_demod_stats(fdmdv, &stats);
+ for(c=0; c<FDMDV_NSYM; c++)
+ rx_symbols_log[c][frames] = stats.rx_symbols[c];
+ foff_log[frames] = stats.foff;
+ rx_timing_log[frames] = stats.rx_timing;
+ coarse_fine_log[frames] = stats.fest_coarse_fine;
+ frames++;
+ }
+ else
+ printf("MAX_FRAMES exceed in Octave log, log truncated\n");
+
+ /* state machine to output codec bits only if we have a 0,1
+ sync bit sequence */
+
+ next_state = state;
+ switch (state) {
+ case 0:
+ if (sync_bit == 0) {
+ next_state = 1;
+ memcpy(codec_bits, rx_bits, FDMDV_BITS_PER_FRAME*sizeof(int));
+ }
+ else
+ next_state = 0;
+ break;
+ case 1:
+ if (sync_bit == 1) {
+ memcpy(&codec_bits[FDMDV_BITS_PER_FRAME], rx_bits, FDMDV_BITS_PER_FRAME*sizeof(int));
+
+ /* pack bits, MSB received first */
+
+ bit = 7; byte = 0;
+ memset(packed_bits, 0, BYTES_PER_CODEC_FRAME);
+ for(i=0; i<BITS_PER_CODEC_FRAME; i++) {
+ packed_bits[byte] |= (codec_bits[i] << bit);
+ bit--;
+ if (bit < 0) {
+ bit = 7;
+ byte++;
+ }
+ }
+ assert(byte == BYTES_PER_CODEC_FRAME);
+
+ fwrite(packed_bits, sizeof(char), BYTES_PER_CODEC_FRAME, fout);
+ }
+ next_state = 0;
+ break;
+ }
+ state = next_state;
+
+ /* if this is in a pipeline, we probably don't want the usual
+ buffering to occur */
+
+ if (fout == stdout) fflush(stdout);
+ if (fin == stdin) fflush(stdin);
+ }
+
+ /* Optional dump to Octave log file */
+
+ if ( (foct = fopen(argv[3],"wt")) == NULL ) {
+ fprintf(stderr, "Error opening Octave dump file: %s: %s.\n",
+ argv[3], strerror(errno));
+ exit(1);
+ }
+ else {
+ octave_save_complex(foct, "rx_symbols_log_c", (COMP*)rx_symbols_log, FDMDV_NSYM, MAX_FRAMES, MAX_FRAMES);
+ octave_save_float(foct, "foff_log_c", foff_log, 1, MAX_FRAMES);
+ octave_save_float(foct, "rx_timing_log_c", rx_timing_log, 1, MAX_FRAMES);
+ octave_save_int(foct, "coarse_fine_log_c", coarse_fine_log, 1, MAX_FRAMES);
+ fclose(foct);
+ }
+
+ fclose(fin);
+ fclose(fout);
+ fdmdv_destroy(fdmdv);
+
+ return 0;
+}
+
\*---------------------------------------------------------------------------*/
struct FDMDV {
+ /* test data (test frame) states */
+
int current_test_bit;
int rx_test_bits_mem[NTEST_BITS];
+ /* Modulator */
+
int tx_pilot_bit;
COMP prev_tx_symbols[NC+1];
COMP tx_filter_memory[NC+1][NFILTER];
COMP phase_tx[NC+1];
COMP freq[NC+1];
+ /* Pilot generation at demodulator */
+
COMP pilot_lut[NPILOT_LUT];
int pilot_lut_index;
int prev_pilot_lut_index;
+ /* freq offset estimation states */
+
COMP pilot_baseband1[NPILOTBASEBAND];
COMP pilot_baseband2[NPILOTBASEBAND];
COMP pilot_lpf1[NPILOTLPF];
COMP S1[MPILOTFFT];
COMP S2[MPILOTFFT];
+ /* freq offset correction states */
+
float foff;
COMP foff_rect;
COMP foff_phase_rect;
- COMP phase_rx[NC+1];
- COMP rx_filter_memory[NC+1][NFILTER];
- COMP rx_filter_mem_timing[NC+1][NT*P];
- COMP rx_baseband_mem_timing[NC+1][NFILTERTIMING];
- COMP prev_rx_symbols[NC+1];
+ /* Demodulator */
+
+ COMP phase_rx[NC+1];
+ COMP rx_filter_memory[NC+1][NFILTER];
+ COMP rx_filter_mem_timing[NC+1][NT*P];
+ COMP rx_baseband_mem_timing[NC+1][NFILTERTIMING];
+ float rx_timing;
+ COMP rx_symbols[NC+1];
+ COMP prev_rx_symbols[NC+1];
+ /* freq est state machine */
+
int fest_state;
int coarse_fine;
};
/*---------------------------------------------------------------------------*\
- FILE........: tfdmdv_mod.c
+ FILE........: fdmdv_mod.c
AUTHOR......: David Rowe
DATE CREATED: April 28 2012
#include <errno.h>
#include "fdmdv.h"
-#include "octave.h"
#define BITS_PER_CODEC_FRAME (2*FDMDV_BITS_PER_FRAME)
#define BYTES_PER_CODEC_FRAME (BITS_PER_CODEC_FRAME/8)
char packed_bits[BYTES_PER_CODEC_FRAME];
int tx_bits[2*FDMDV_BITS_PER_FRAME];
COMP tx_fdm[2*FDMDV_SAMPLES_PER_FRAME];
- short tx_fdm_scaled[FDMDV_SAMPLES_PER_FRAME];
+ short tx_fdm_scaled[2*FDMDV_SAMPLES_PER_FRAME];
int frames;
int i, bit, byte;
int sync_bit;
fclose(fin);
fclose(fout);
- codec2_destroy(fdmdv);
+ fdmdv_destroy(fdmdv);
return 0;
}