aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFelix Erckenbrecht <dg1yfe@stus-disco.de>2022-03-24 00:26:34 +0100
committerFelix Erckenbrecht <dg1yfe@stus-disco.de>2022-03-24 00:26:34 +0100
commit0fa86523ba04e7fe95856e20de74e56051defa48 (patch)
treecbd3355dc4cd5103103ed5c916611391d992445a
parent213a1ce21aa2eaca6356e6c94fb491b0b86e37a0 (diff)
downloadosmo-fl2k-0fa86523ba04e7fe95856e20de74e56051defa48.tar.gz
osmo-fl2k-0fa86523ba04e7fe95856e20de74e56051defa48.tar.bz2
osmo-fl2k-0fa86523ba04e7fe95856e20de74e56051defa48.zip
Work in progress: Refactor IQ mod to Ampliphase mod
-rw-r--r--src/fl2k_iq.c295
1 files changed, 209 insertions, 86 deletions
diff --git a/src/fl2k_iq.c b/src/fl2k_iq.c
index 9e8b845..49cd5f7 100644
--- a/src/fl2k_iq.c
+++ b/src/fl2k_iq.c
@@ -49,7 +49,10 @@
#include <pthread.h>
#include "osmo-fl2k.h"
-#include "rds_mod.h"
+
+
+enum inputType_E { INP_REAL, INP_COMPLEX };
+
#define BUFFER_SAMPLES_SHIFT 16
#define BUFFER_SAMPLES (1 << BUFFER_SAMPLES_SHIFT)
@@ -67,8 +70,10 @@ pthread_cond_t cb_cond;
pthread_cond_t iq_cond;
FILE *file;
-int8_t *txbuf = NULL;
-int8_t *ambuf = NULL;
+int8_t *itxbuf = NULL;
+int8_t *qtxbuf = NULL;
+int8_t *iambuf = NULL;
+int8_t *qambuf = NULL;
int8_t *buf1 = NULL;
int8_t *buf2 = NULL;
@@ -80,6 +85,10 @@ int input_freq = 48000;
complex float *ampbuf;
complex float *slopebuf;
+
+long int * pdbuf;
+long int * pdslopebuf;
+
int writepos, readpos;
int swap_iq = 0;
int ignore_eof = 0;
@@ -87,12 +96,15 @@ int ignore_eof = 0;
void usage(void)
{
fprintf(stderr,
- "fl2k_iq, an IQ modulator for FL2K VGA dongles\n\n"
+ "fl2k_ampliphase, a special modulator for FL2K VGA dongles\n\n"
"Usage:"
"\t[-d device index (default: 0)]\n"
"\t[-c center frequency (default: 1440 kHz)]\n"
"\t[-i input baseband sample rate (default: 48000 Hz)]\n"
"\t[-s samplerate in Hz (default: 96 MS/s)]\n"
+ "\t[-t type of input: real/complex (default: real)\n"
+ "\t input requirements: real - single channel (mono)\n"
+ "\t complex - dual channel (stereo)\n"
"\t[-w swap I & Q (invert spectrum)\n"
"\t[-e ignore EOF\n"
"\tfilename (use '-' to read from stdin)\n\n"
@@ -138,12 +150,15 @@ static void sighandler(int signum)
#define TRIG_TABLE_ORDER 8
#define TRIG_TABLE_SHIFT (32 - TRIG_TABLE_ORDER)
#define TRIG_TABLE_LEN (1 << TRIG_TABLE_ORDER)
-#define ANG_INCR (0xffffffff / DDS_2PI)
+//#define ANG_INCR (0xffffffff / DDS_2PI)
+#define ANG_INCR ((float)(0x100000000)) / DDS_2PI
+
+enum waveform_E { WF_SINE, WF_RECT };
struct trigonometric_table_S {
int initialized;
- int16_t sine[TRIG_TABLE_LEN];
- int16_t cosine[TRIG_TABLE_LEN];
+ int16_t quadrature[TRIG_TABLE_LEN];
+ int16_t inphase[TRIG_TABLE_LEN];
};
static struct trigonometric_table_S trig_table = { .initialized = 0 };
@@ -151,8 +166,16 @@ static struct trigonometric_table_S trig_table = { .initialized = 0 };
typedef struct {
float sample_freq;
float freq;
+ /* instantaneous phase */
unsigned long int phase;
+ /* phase increment */
unsigned long int phase_step;
+
+ /* for phase modulation */
+ long int phase_delta;
+ long int phase_slope;
+
+ /* for amplitude modulation */
complex float amplitude;
complex float ampslope;
} dds_t;
@@ -169,7 +192,13 @@ static inline void dds_set_amp(dds_t *dds, complex float amplitude, complex floa
dds->ampslope = ampslope;
}
-dds_t dds_init(float sample_freq, float freq, float phase, float amp)
+static inline void dds_set_phase(dds_t *dds, long int phase_delta, long int phase_slope)
+{
+ dds->phase_delta = phase_delta;
+ dds->phase_slope = phase_slope;
+}
+
+dds_t dds_init(float sample_freq, float freq, float phase, float amp, enum waveform_E waveform )
{
dds_t dds;
int i;
@@ -178,12 +207,19 @@ dds_t dds_init(float sample_freq, float freq, float phase, float amp)
dds.phase = phase * ANG_INCR;
dds_set_freq(&dds, freq);
dds_set_amp(&dds, amp, 0);
- /* Initialize sine table, prescaled for 16 bit signed integer */
+ /* Initialize quadrature table, prescaled for 16 bit signed integer */
if (!trig_table.initialized) {
float incr = 1.0 / (float)TRIG_TABLE_LEN;
for (i = 0; i < TRIG_TABLE_LEN; i++){
- trig_table.sine[i] = sin(incr * i * DDS_2PI) * 32767;
- trig_table.cosine[i] = cos(incr * i * DDS_2PI) * 32767;
+ if(waveform == WF_SINE){
+ trig_table.quadrature[i]= sin(incr * i * DDS_2PI) * 32767;
+ trig_table.inphase[i] = cos(incr * i * DDS_2PI) * 32767;
+ }
+ else{
+ /* rectangular / square output */
+ trig_table.quadrature[i]= sin(incr * i * DDS_2PI) >= 0 ? 32767 : -32767;
+ trig_table.inphase[i] = cos(incr * i * DDS_2PI) >= 0 ? 32767 : -32767;
+ }
}
trig_table.initialized = 1;
@@ -206,8 +242,8 @@ static inline int8_t dds_real(dds_t *dds)
//amp = 255;
amp_i = creal(dds->amplitude) * 23170.0; // 0..15, * 1/SQRT(2)
amp_q = cimag(dds->amplitude) * 23170.0;
- amp_i = amp_i * trig_table.sine[tmp]; // 0..31, * 1/SQRT(2)
- amp_q = amp_q * trig_table.cosine[tmp]; // 0..31, * 1/SQRT(2)
+ amp_i = amp_i * trig_table.quadrature[tmp]; // 0..31, * 1/SQRT(2)
+ amp_q = amp_q * trig_table.inphase[tmp]; // 0..31, * 1/SQRT(2)
amp8 = (int8_t) ((amp_i + amp_q) >> 24); // 0..31 >> 24 => 0..8
dds->amplitude += dds->ampslope;
return amp8;
@@ -220,6 +256,47 @@ static inline void dds_real_buf(dds_t *dds, int8_t *buf, int count)
buf[i] = dds_real(dds);
}
+
+static inline void dds_complex(dds_t *dds, int8_t * i, int8_t * q)
+{
+ int pi_i, pi_q;
+ int32_t amp_i, amp_q;
+
+ // get current carrier phase, add phase mod, calculate table index
+ pi_i = (dds->phase - dds->phase_delta) >> TRIG_TABLE_SHIFT;
+ pi_q = (dds->phase + dds->phase_delta) >> TRIG_TABLE_SHIFT;
+ // advance dds generator
+ dds->phase += dds->phase_step;
+
+ // add some extra phase modulation
+ dds->phase += dds->phase_delta;
+ dds->phase &= 0xffffffff;
+
+ //amp = 255;
+ amp_i = (int32_t) (creal(dds->amplitude) * 32767.0); // 0..15
+ amp_q = (int32_t) (cimag(dds->amplitude) * 32767.0);
+
+ amp_i = amp_i * trig_table.inphase[pi_i]; // 0..31
+ amp_q = amp_q * trig_table.quadrature[pi_q]; // 0..31
+
+ *i = (int8_t) (amp_i >> 24); // 0..31 >> 24 => 0..8
+ *q = (int8_t) (amp_q >> 24); // 0..31 >> 24 => 0..8
+
+ dds->amplitude += dds->ampslope;
+ dds->phase_delta += dds->phase_slope;
+ return;
+}
+
+
+static inline void dds_complex_buf(dds_t *dds, int8_t *ibuf, int8_t *qbuf, int count)
+{
+ int i;
+ for (i = 0; i < count; i++){
+ dds_complex(dds, &ibuf[i], &qbuf[i]);
+ }
+}
+
+
/* Signal generation and some helpers */
/* Generate the radio signal using the pre-calculated amplitude information
@@ -235,10 +312,12 @@ static void *iq_worker(void *arg)
int buf_prefilled = 0;
/* Prepare the oscillators */
- base_signal = dds_init(samp_rate, base_freq, 0, 1);
+ base_signal = dds_init(samp_rate, base_freq, 0, 1, WF_RECT);
while (!do_exit) {
- dds_set_amp(&base_signal, ampbuf[readpos], slopebuf[readpos]);
+ // dds_set_amp(&base_signal, ampbuf[readpos], slopebuf[readpos]);
+ /* phase modulate the oscillator */
+ dds_set_phase(&base_signal, pdbuf[readpos], pdslopebuf[readpos]);
readpos++;
readpos &= BUFFER_SAMPLES_MASK;
@@ -246,24 +325,29 @@ static void *iq_worker(void *arg)
if ((len + rf_to_baseband_sample_ratio) > FL2K_BUF_LEN) {
readlen = FL2K_BUF_LEN - len;
remaining = rf_to_baseband_sample_ratio - readlen;
- dds_real_buf(&base_signal, &ambuf[len], readlen);
+ dds_complex_buf(&base_signal, &iambuf[len], &qambuf[len],readlen);
if (buf_prefilled) {
/* swap buffers */
- tmp_ptr = ambuf;
- ambuf = txbuf;
- txbuf = tmp_ptr;
+ tmp_ptr = iambuf;
+ iambuf = itxbuf;
+ itxbuf = tmp_ptr;
+
+ tmp_ptr = qambuf;
+ qambuf = qtxbuf;
+ qtxbuf = tmp_ptr;
+
pthread_mutex_lock(&cb_mutex);
pthread_cond_wait(&cb_cond, &cb_mutex);
pthread_mutex_unlock(&cb_mutex);
}
- dds_real_buf(&base_signal, ambuf, remaining);
+ dds_complex_buf(&base_signal, iambuf, qambuf, remaining);
len = remaining;
buf_prefilled = 1;
} else {
- dds_real_buf(&base_signal, &ambuf[len], rf_to_baseband_sample_ratio);
+ dds_complex_buf(&base_signal, &iambuf[len], &qambuf[len], rf_to_baseband_sample_ratio);
len += rf_to_baseband_sample_ratio;
}
pthread_mutex_lock(&iq_mutex);
@@ -290,69 +374,86 @@ static inline int writelen(int maxlen)
return r;
}
-static inline float complex modulate_sample_iq(const int lastwritepos, const float complex lastamp, const float complex sample)
-{
- float complex amp;
- float complex slope;
-
- /* Calculate modulator amplitudes at this point to lessen
- * the calculations needed in the signal generator */
- amp = sample;
-
- /* What we do here is calculate a linear "slope" from
- the previous sample to this one. This is then used by
- the modulator to gently increase/decrease the amplitude
- with each sample without the need to recalculate
- the dds parameters. In fact this gives us a very
- efficient and pretty good interpolation filter. */
- slope = amp - lastamp;
- slope = slope * 1.0/ (float) rf_to_baseband_sample_ratio;
- slopebuf[writepos] = slope;
- ampbuf[writepos] = lastamp;
-
- return amp;
-}
-void iq_modulator()
+static inline float complex modulate_sample_ampliphase(const int lastwritepos, const float lastamp, const float sample, float modulationIndex)
{
- unsigned int i;
- size_t len;
- float freq;
- float complex lastamp = 0;
- int16_t baseband_buf[BASEBAND_BUF_SIZE][2];
- uint32_t lastwritepos = writepos;
- float complex sample;
-
- while (!do_exit) {
- int swap = swap_iq;
- len = writelen(BASEBAND_BUF_SIZE);
- if (len > 1) {
- len = fread(baseband_buf, 4, len, file);
-
- if (len == 0){
- if(ferror(file)){
- do_exit = 1;
- }
- if(!ignore_eof && feof(file)){
- do_exit = 1;
- }
- }
+ float amp;
+ float slope;
+
+ /* Calculate modulator amplitudes at this point to lessen
+ * the calculations needed in the signal generator */
+ amp = sample;
+
+ /* What we do here is calculate a linear "slope" from
+ the previous sample to this one. This is then used by
+ the modulator to gently increase/decrease the phase
+ with each sample without the need to recalculate
+ the dds parameters. In fact this gives us a very
+ efficient and pretty good interpolation filter. */
+ slope = amp - lastamp;
+ slope = slope * 1.0/ (float) rf_to_baseband_sample_ratio;
+ pdbuf[writepos] = (long int) lastamp * modulationIndex * ANG_INCR;
+ pdslopebuf[writepos] = (long int) slope * modulationIndex * ANG_INCR;
+
+ return amp;
+}
- for (i = 0; i < len; i++) {
- sample = (float) baseband_buf[i][0+swap] / 32768.0 + I * (float) baseband_buf[i][1-swap] / 32768.0;
- /* Modulate and buffer the sample */
- lastamp = modulate_sample_iq(lastwritepos, lastamp, sample);
- lastwritepos = writepos++;
- writepos %= BUFFER_SAMPLES;
- }
- } else {
- pthread_mutex_lock(&iq_mutex);
- pthread_cond_wait(&iq_cond, &iq_mutex);
- pthread_mutex_unlock(&iq_mutex);
- }
- }
+void ampliphase_modulator(enum inputType_E inputType)
+{
+ unsigned int i;
+ size_t len;
+ float freq;
+ float complex lastamp = 0;
+ int16_t baseband_buf_real[BASEBAND_BUF_SIZE];
+ int16_t baseband_buf_cplx[BASEBAND_BUF_SIZE][2];
+ uint32_t lastwritepos = writepos;
+ float complex sample;
+
+ while (!do_exit) {
+ int swap = swap_iq;
+ len = writelen(BASEBAND_BUF_SIZE);
+ if (len > 1) {
+ if(inputType == INP_REAL){
+ len = fread(baseband_buf_real, 1, len, file);
+ for(i = 0 ; i < len; i++){
+ /* input is -1.0 .. +1.0 (-32768 .. 32767)
+ * transform to 0.0 .. +1.0 (0 .. 32767)
+ * put into I part of BB
+ */
+ baseband_buf_cplx[i][0] = baseband_buf_real[i] / 2 + INT16_MAX/2;
+ /* Q part of BB is zero for AM */
+ baseband_buf_cplx[i][1] = 0;
+ }
+ }
+ else{
+ len = fread(baseband_buf_cplx, 2, len, file);
+ }
+
+ if (len == 0){
+ if(ferror(file)){
+ do_exit = 1;
+ }
+ if(!ignore_eof && feof(file)){
+ do_exit = 1;
+ }
+ }
+
+ for (i = 0; i < len; i++) {
+ sample = (float) baseband_buf_cplx[i][0+swap] / 32768.0 + I * (float) baseband_buf_cplx[i][1-swap] / 32768.0;
+
+ /* Modulate and buffer the sample */
+ lastamp = modulate_sample_ampliphase(lastwritepos, lastamp, sample);
+ lastwritepos = writepos++;
+ writepos %= BUFFER_SAMPLES;
+ }
+ } else {
+ pthread_mutex_lock(&iq_mutex);
+ pthread_cond_wait(&iq_cond, &iq_mutex);
+ pthread_mutex_unlock(&iq_mutex);
+ }
+ }
}
@@ -369,7 +470,8 @@ void fl2k_callback(fl2k_data_info_t *data_info)
pthread_cond_signal(&cb_cond);
data_info->sampletype_signed = 1;
- data_info->r_buf = (char *)txbuf;
+ data_info->r_buf = (char *)itxbuf;
+ data_info->g_buf = (char *)qtxbuf;
}
int main(int argc, char **argv)
@@ -381,6 +483,7 @@ int main(int argc, char **argv)
char *filename = NULL;
int option_index = 0;
int input_freq_specified = 0;
+ enum inputType_E input_type = INP_REAL;
#ifndef _WIN32
struct sigaction sigact, sigign;
@@ -392,7 +495,7 @@ int main(int argc, char **argv)
};
while (1) {
- opt = getopt_long(argc, argv, "ewd:c:i:s:", long_options, &option_index);
+ opt = getopt_long(argc, argv, "ewd:c:i:s:t:", long_options, &option_index);
/* end of options reached */
if (opt == -1)
@@ -414,6 +517,14 @@ int main(int argc, char **argv)
case 's':
samp_rate = (uint32_t)atof(optarg);
break;
+ case 't':
+ /* type */
+ if(strcasecmp(optarg, "complex") && strcasecmp(optarg, "real")){
+ fprintf(stderr, "Unknown parameter to -t : %s", optarg);
+ exit(1);
+ }
+ input_type = strcasecmp(optarg, "complex") == 0 ? INP_COMPLEX : INP_REAL;
+ break;
case 'w':
swap_iq = 1;
break;
@@ -449,20 +560,32 @@ int main(int argc, char **argv)
}
}
- /* allocate buffer */
+ /* allocate I buffer */
buf1 = malloc(FL2K_BUF_LEN);
buf2 = malloc(FL2K_BUF_LEN);
if (!buf1 || !buf2) {
fprintf(stderr, "malloc error!\n");
exit(1);
}
+ iambuf = buf1;
+ itxbuf = buf2;
+
+ /* allocate Q buffer */
+ buf1 = malloc(FL2K_BUF_LEN);
+ buf2 = malloc(FL2K_BUF_LEN);
+ if (!buf1 || !buf2) {
+ fprintf(stderr, "malloc error!\n");
+ exit(1);
+ }
+ qambuf = buf1;
+ qtxbuf = buf2;
- ambuf = buf1;
- txbuf = buf2;
- /* Decoded audio */
+ /* Baseband buffer */
slopebuf = malloc(BUFFER_SAMPLES * sizeof(float complex));
ampbuf = malloc(BUFFER_SAMPLES * sizeof(float complex));
+ pdbuf = malloc(BUFFER_SAMPLES * sizeof(long int));
+ pdslopebuf = malloc(BUFFER_SAMPLES * sizeof(long int));
readpos = 0;
writepos = 1;
@@ -518,7 +641,7 @@ int main(int argc, char **argv)
SetConsoleCtrlHandler( (PHANDLER_ROUTINE) sighandler, TRUE );
#endif
- iq_modulator();
+ ampliphase_modulator(input_type);
out:
fl2k_close(dev);