aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTom Tsou <tom@tsou.cc>2015-03-26 18:57:10 -0700
committerTom Tsou <tom@tsou.cc>2015-03-30 17:57:42 -0700
commit1a53985f088dd08d94f14153882ee2db047d8230 (patch)
tree80ba51a4a4cc890377ce2ab93f8292516d6437d9
parent994a1a8344bce80d56773553399896c83927707c (diff)
mcbts: Add multi-ARFCN implementation
-rw-r--r--Transceiver52M/Channelizer.cpp108
-rw-r--r--Transceiver52M/Channelizer.h34
-rw-r--r--Transceiver52M/ChannelizerBase.cpp249
-rw-r--r--Transceiver52M/ChannelizerBase.h39
-rw-r--r--Transceiver52M/Makefile.am15
-rw-r--r--Transceiver52M/Synthesis.cpp121
-rw-r--r--Transceiver52M/Synthesis.h35
-rw-r--r--Transceiver52M/Transceiver.cpp20
-rw-r--r--Transceiver52M/UHDDevice.cpp250
-rw-r--r--Transceiver52M/common/fft.c112
-rw-r--r--Transceiver52M/common/fft.h13
-rw-r--r--Transceiver52M/osmo-trx.cpp4
-rw-r--r--Transceiver52M/radioDevice.h11
-rw-r--r--Transceiver52M/radioInterface.h33
-rw-r--r--Transceiver52M/radioInterfaceMulti.cpp393
-rw-r--r--configure.ac1
16 files changed, 1330 insertions, 108 deletions
diff --git a/Transceiver52M/Channelizer.cpp b/Transceiver52M/Channelizer.cpp
new file mode 100644
index 0000000..4b163b9
--- /dev/null
+++ b/Transceiver52M/Channelizer.cpp
@@ -0,0 +1,108 @@
+/*
+ * Polyphase channelizer
+ *
+ * Copyright (C) 2012-2014 Tom Tsou <tom@tsou.cc>
+ * Copyright (C) 2015 Ettus Research LLC
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ * See the COPYING file in the main directory for details.
+ */
+
+#include <stdlib.h>
+#include <math.h>
+#include <assert.h>
+#include <string.h>
+#include <cstdio>
+
+#include "Logger.h"
+#include "Channelizer.h"
+
+extern "C" {
+#include "common/fft.h"
+#include "common/convolve.h"
+}
+
+static void deinterleave(const float *in, size_t ilen,
+ float **out, size_t olen, size_t m)
+{
+ size_t i, n;
+
+ for (i = 0; i < olen; i++) {
+ for (n = 0; n < m; n++) {
+ out[m - 1 - n][2 * i + 0] = in[2 * (i * m + n) + 0];
+ out[m - 1 - n][2 * i + 1] = in[2 * (i * m + n) + 1];
+ }
+ }
+}
+
+size_t Channelizer::inputLen() const
+{
+ return blockLen * m;
+}
+
+size_t Channelizer::outputLen() const
+{
+ return blockLen;
+}
+
+float *Channelizer::outputBuffer(size_t chan) const
+{
+ if (chan >= m)
+ return NULL;
+
+ return hInputs[chan];
+}
+
+/*
+ * Implementation based on material found in:
+ *
+ * "harris, fred, Multirate Signal Processing, Upper Saddle River, NJ,
+ * Prentice Hall, 2006."
+ */
+bool Channelizer::rotate(const float *in, size_t len)
+{
+ size_t hSize = 2 * hLen * sizeof(float);
+
+ if (!checkLen(blockLen, len))
+ return false;
+
+ deinterleave(in, len, hInputs, blockLen, m);
+
+ /*
+ * Convolve through filterbank while applying and saving sample history
+ */
+ for (size_t i = 0; i < m; i++) {
+ memcpy(&hInputs[i][2 * -hLen], hist[i], hSize);
+ memcpy(hist[i], &hInputs[i][2 * (blockLen - hLen)], hSize);
+
+ convolve_real(hInputs[i], blockLen,
+ subFilters[i], hLen,
+ hOutputs[i], blockLen,
+ 0, blockLen, 1, 0);
+ }
+
+ cxvec_fft(fftHandle);
+
+ return true;
+}
+
+/* Setup channelizer paramaters */
+Channelizer::Channelizer(size_t m, size_t blockLen, size_t hLen)
+ : ChannelizerBase(m, blockLen, hLen)
+{
+}
+
+Channelizer::~Channelizer()
+{
+}
diff --git a/Transceiver52M/Channelizer.h b/Transceiver52M/Channelizer.h
new file mode 100644
index 0000000..fdd6779
--- /dev/null
+++ b/Transceiver52M/Channelizer.h
@@ -0,0 +1,34 @@
+#ifndef _CHANNELIZER_RX_H_
+#define _CHANNELIZER_RX_H_
+
+#include "ChannelizerBase.h"
+
+class Channelizer : public ChannelizerBase {
+public:
+ /** Constructor for channelizing filter bank
+ @param m number of physical channels
+ @param blockLen number of samples per output of each iteration
+ @param hLen number of taps in each constituent filter path
+ */
+ Channelizer(size_t m, size_t blockLen, size_t hLen = 16);
+ ~Channelizer();
+
+ /* Return required input and output buffer lengths */
+ size_t inputLen() const;
+ size_t outputLen() const;
+
+ /** Rotate "input commutator" and drive samples through filterbank
+ @param in complex input vector
+ @param iLen number of samples in buffer (must match block length)
+ @return false on error and true otherwise
+ */
+ bool rotate(const float *in, size_t iLen);
+
+ /** Get buffer for an output path
+ @param chan channel number of filterbank
+ @return NULL on error and pointer to buffer otherwise
+ */
+ float *outputBuffer(size_t chan) const;
+};
+
+#endif /* _CHANNELIZER_RX_H_ */
diff --git a/Transceiver52M/ChannelizerBase.cpp b/Transceiver52M/ChannelizerBase.cpp
new file mode 100644
index 0000000..1576821
--- /dev/null
+++ b/Transceiver52M/ChannelizerBase.cpp
@@ -0,0 +1,249 @@
+/*
+ * Polyphase channelizer
+ *
+ * Copyright (C) 2012-2014 Tom Tsou <tom@tsou.cc>
+ * Copyright (C) 2015 Ettus Research LLC
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ * See the COPYING file in the main directory for details.
+ */
+
+#include <malloc.h>
+#include <math.h>
+#include <assert.h>
+#include <string.h>
+#include <cstdio>
+
+#include "Logger.h"
+#include "ChannelizerBase.h"
+
+extern "C" {
+#include "common/fft.h"
+}
+
+static float sinc(float x)
+{
+ if (x == 0.0f)
+ return 0.999999999999f;
+
+ return sin(M_PI * x) / (M_PI * x);
+}
+
+/*
+ * There are more efficient reversal algorithms, but we only reverse at
+ * initialization so we don't care.
+ */
+static void reverse(float *buf, size_t len)
+{
+ float tmp[2 * len];
+ memcpy(tmp, buf, 2 * len * sizeof(float));
+
+ for (size_t i = 0; i < len; i++) {
+ buf[2 * i + 0] = tmp[2 * (len - 1 - i) + 0];
+ buf[2 * i + 1] = tmp[2 * (len - 1 - i) + 1];
+ }
+}
+
+/*
+ * Create polyphase filterbank
+ *
+ * Implementation based material found in,
+ *
+ * "harris, fred, Multirate Signal Processing, Upper Saddle River, NJ,
+ * Prentice Hall, 2006."
+ */
+bool ChannelizerBase::initFilters()
+{
+ size_t protoLen = m * hLen;
+ float *proto;
+ float sum = 0.0f, scale = 0.0f;
+ float midpt = (float) (protoLen - 1.0) / 2.0;
+
+ /*
+ * Allocate 'M' partition filters and the temporary prototype
+ * filter. Coefficients are real only and must be 16-byte memory
+ * aligned for SSE usage.
+ */
+ proto = new float[protoLen];
+ if (!proto)
+ return false;
+
+ subFilters = (float **) malloc(sizeof(float *) * m);
+ if (!subFilters)
+ return false;
+
+ for (size_t i = 0; i < m; i++) {
+ subFilters[i] = (float *)
+ memalign(16, hLen * 2 * sizeof(float));
+ }
+
+ /*
+ * Generate the prototype filter with a Blackman-harris window.
+ * Scale coefficients with DC filter gain set to unity divided
+ * by the number of channels.
+ */
+ float a0 = 0.35875;
+ float a1 = 0.48829;
+ float a2 = 0.14128;
+ float a3 = 0.01168;
+
+ for (size_t i = 0; i < protoLen; i++) {
+ proto[i] = sinc(((float) i - midpt) / (float) m);
+ proto[i] *= a0 -
+ a1 * cos(2 * M_PI * i / (protoLen - 1)) +
+ a2 * cos(4 * M_PI * i / (protoLen - 1)) -
+ a3 * cos(6 * M_PI * i / (protoLen - 1));
+ sum += proto[i];
+ }
+ scale = (float) m / sum;
+
+ /*
+ * Populate partition filters and reverse the coefficients per
+ * convolution requirements.
+ */
+ for (size_t i = 0; i < hLen; i++) {
+ for (size_t n = 0; n < m; n++) {
+ subFilters[n][2 * i + 0] = proto[i * m + n] * scale;
+ subFilters[n][2 * i + 1] = 0.0f;
+ }
+ }
+
+ for (size_t i = 0; i < m; i++)
+ reverse(subFilters[i], hLen);
+
+ delete proto;
+
+ return true;
+}
+
+bool ChannelizerBase::initFFT()
+{
+ size_t size;
+
+ if (fftInput || fftOutput || fftHandle)
+ return false;
+
+ size = blockLen * m * 2 * sizeof(float);
+ fftInput = (float *) fft_malloc(size);
+ memset(fftInput, 0, size);
+
+ size = (blockLen + hLen) * m * 2 * sizeof(float);
+ fftOutput = (float *) fft_malloc(size);
+ memset(fftOutput, 0, size);
+
+ if (!fftInput | !fftOutput) {
+ LOG(ALERT) << "Memory allocation error";
+ return false;
+ }
+
+ fftHandle = init_fft(0, m, blockLen, blockLen + hLen,
+ fftInput, fftOutput, hLen);
+ return true;
+}
+
+bool ChannelizerBase::mapBuffers()
+{
+ if (!fftHandle) {
+ LOG(ALERT) << "FFT buffers not initialized";
+ return false;
+ }
+
+ hInputs = (float **) malloc(sizeof(float *) * m);
+ hOutputs = (float **) malloc(sizeof(float *) * m);
+ if (!hInputs | !hOutputs)
+ return false;
+
+ for (size_t i = 0; i < m; i++) {
+ hInputs[i] = &fftOutput[2 * (i * (blockLen + hLen) + hLen)];
+ hOutputs[i] = &fftInput[2 * (i * blockLen)];
+ }
+
+ return true;
+}
+
+/*
+ * Setup filterbank internals
+ */
+bool ChannelizerBase::init()
+{
+ /*
+ * Filterbank coefficients, fft plan, history, and output sample
+ * rate conversion blocks
+ */
+ if (!initFilters()) {
+ LOG(ALERT) << "Failed to initialize channelizing filter";
+ return false;
+ }
+
+ hist = (float **) malloc(sizeof(float *) * m);
+ for (size_t i = 0; i < m; i++) {
+ hist[i] = new float[2 * hLen];
+ memset(hist[i], 0, 2 * hLen * sizeof(float));
+ }
+
+ if (!initFFT()) {
+ LOG(ALERT) << "Failed to initialize FFT";
+ return false;
+ }
+
+ mapBuffers();
+
+ return true;
+}
+
+/* Check vector length validity */
+bool ChannelizerBase::checkLen(size_t innerLen, size_t outerLen)
+{
+ if (outerLen != innerLen * m) {
+ LOG(ALERT) << "Invalid outer length " << innerLen
+ << " is not multiple of " << blockLen;
+ return false;
+ }
+
+ if (innerLen != blockLen) {
+ LOG(ALERT) << "Invalid inner length " << outerLen
+ << " does not equal " << blockLen;
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * Setup channelizer paramaters
+ */
+ChannelizerBase::ChannelizerBase(size_t m, size_t blockLen, size_t hLen)
+ : fftInput(NULL), fftOutput(NULL), fftHandle(NULL)
+{
+ this->m = m;
+ this->hLen = hLen;
+ this->blockLen = blockLen;
+}
+
+ChannelizerBase::~ChannelizerBase()
+{
+ free_fft(fftHandle);
+
+ for (size_t i = 0; i < m; i++) {
+ free(subFilters[i]);
+ delete hist[i];
+ }
+
+ fft_free(fftInput);
+ fft_free(fftOutput);
+
+ free(hInputs);
+ free(hOutputs);
+ free(hist);
+}
diff --git a/Transceiver52M/ChannelizerBase.h b/Transceiver52M/ChannelizerBase.h
new file mode 100644
index 0000000..7da506b
--- /dev/null
+++ b/Transceiver52M/ChannelizerBase.h
@@ -0,0 +1,39 @@
+#ifndef _CHANNELIZER_BASE_H_
+#define _CHANNELIZER_BASE_H_
+
+class ChannelizerBase {
+protected:
+ ChannelizerBase(size_t m, size_t blockLen, size_t hLen);
+ ~ChannelizerBase();
+
+ /* Channelizer parameters */
+ size_t m;
+ size_t hLen;
+ size_t blockLen;
+
+ /* Channelizer filterbank sub-filters */
+ float **subFilters;
+
+ /* Input/Output buffers */
+ float **hInputs, **hOutputs, **hist;
+ float *fftInput, *fftOutput;
+
+ /* Pointer to opaque FFT instance */
+ struct fft_hdl *fftHandle;
+
+ /* Initializer internals */
+ bool initFilters();
+ bool initFFT();
+ void releaseFilters();
+
+ /* Map overlapped FFT and filter I/O buffers */
+ bool mapBuffers();
+
+ /* Buffer length validity checking */
+ bool checkLen(size_t innerLen, size_t outerLen);
+public:
+ /* Initilize channelizer/synthesis filter internals */
+ bool init();
+};
+
+#endif /* _CHANNELIZER_BASE_H_ */
diff --git a/Transceiver52M/Makefile.am b/Transceiver52M/Makefile.am
index bfde2a6..7b19ad4 100644
--- a/Transceiver52M/Makefile.am
+++ b/Transceiver52M/Makefile.am
@@ -57,12 +57,17 @@ COMMON_SOURCES = \
radioBuffer.cpp \
sigProcLib.cpp \
signalVector.cpp \
- Transceiver.cpp
+ Transceiver.cpp \
+ ChannelizerBase.cpp \
+ Channelizer.cpp \
+ Synthesis.cpp \
+ common/fft.c
libtransceiver_la_SOURCES = \
$(COMMON_SOURCES) \
Resampler.cpp \
radioInterfaceResamp.cpp \
+ radioInterfaceMulti.cpp \
radioInterfaceDiversity.cpp
bin_PROGRAMS = osmo-trx
@@ -79,10 +84,14 @@ noinst_HEADERS = \
Transceiver.h \
USRPDevice.h \
Resampler.h \
+ ChannelizerBase.h \
+ Channelizer.h \
+ Synthesis.h \
common/convolve.h \
common/convert.h \
common/scale.h \
- common/mult.h
+ common/mult.h \
+ common/fft.h
osmo_trx_SOURCES = osmo-trx.cpp
osmo_trx_LDADD = \
@@ -96,5 +105,5 @@ libtransceiver_la_SOURCES += USRPDevice.cpp
osmo_trx_LDADD += $(USRP_LIBS)
else
libtransceiver_la_SOURCES += UHDDevice.cpp
-osmo_trx_LDADD += $(UHD_LIBS)
+osmo_trx_LDADD += $(UHD_LIBS) $(FFTWF_LIBS)
endif
diff --git a/Transceiver52M/Synthesis.cpp b/Transceiver52M/Synthesis.cpp
new file mode 100644
index 0000000..0f8197a
--- /dev/null
+++ b/Transceiver52M/Synthesis.cpp
@@ -0,0 +1,121 @@
+/*
+ * Polyphase synthesis filter
+ *
+ * Copyright (C) 2012-2014 Tom Tsou <tom@tsou.cc>
+ * Copyright (C) 2015 Ettus Research LLC
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ * See the COPYING file in the main directory for details.
+ */
+
+#include <stdlib.h>
+#include <math.h>
+#include <assert.h>
+#include <string.h>
+#include <cstdio>
+
+#include "Logger.h"
+#include "Synthesis.h"
+
+extern "C" {
+#include "common/fft.h"
+#include "common/convolve.h"
+}
+
+static void interleave(float **in, size_t ilen,
+ float *out, size_t m)
+{
+ size_t i, n;
+
+ for (i = 0; i < ilen; i++) {
+ for (n = 0; n < m; n++) {
+ out[2 * (i * m + n) + 0] = in[n][2 * i + 0];
+ out[2 * (i * m + n) + 1] = in[n][2 * i + 1];
+ }
+ }
+}
+
+size_t Synthesis::inputLen() const
+{
+ return blockLen;
+}
+
+size_t Synthesis::outputLen() const
+{
+ return blockLen * m;
+}
+
+float *Synthesis::inputBuffer(size_t chan) const
+{
+ if (chan >= m)
+ return NULL;
+
+ return hOutputs[chan];
+}
+
+bool Synthesis::resetBuffer(size_t chan)
+{
+ if (chan >= m)
+ return false;
+
+ memset(hOutputs[chan], 0, blockLen * 2 * sizeof(float));
+
+ return true;
+}
+
+/*
+ * Implementation based on material found in:
+ *
+ * "harris, fred, Multirate Signal Processing, Upper Saddle River, NJ,
+ * Prentice Hall, 2006."
+ */
+bool Synthesis::rotate(float *out, size_t len)
+{
+ size_t hSize = 2 * hLen * sizeof(float);
+
+ if (!checkLen(blockLen, len)) {
+ std::cout << "Length fail" << std::endl;
+ exit(1);
+ return false;
+ }
+
+ cxvec_fft(fftHandle);
+
+ /*
+ * Convolve through filterbank while applying and saving sample history
+ */
+ for (size_t i = 0; i < m; i++) {
+ memcpy(&hInputs[i][2 * -hLen], hist[i], hSize);
+ memcpy(hist[i], &hInputs[i][2 * (blockLen - hLen)], hSize);
+
+ convolve_real(hInputs[i], blockLen,
+ subFilters[i], hLen,
+ hOutputs[i], blockLen,
+ 0, blockLen, 1, 0);
+ }
+
+ /* Interleave into output vector */
+ interleave(hOutputs, blockLen, out, m);
+
+ return true;
+}
+
+Synthesis::Synthesis(size_t m, size_t blockLen, size_t hLen)
+ : ChannelizerBase(m, blockLen, hLen)
+{
+}
+
+Synthesis::~Synthesis()
+{
+}
diff --git a/Transceiver52M/Synthesis.h b/Transceiver52M/Synthesis.h
new file mode 100644
index 0000000..4100283
--- /dev/null
+++ b/Transceiver52M/Synthesis.h
@@ -0,0 +1,35 @@
+#ifndef _SYNTHESIS_H_
+#define _SYNTHESIS_H_
+
+#include "ChannelizerBase.h"
+
+class Synthesis : public ChannelizerBase {
+public:
+ /** Constructor for synthesis filterbank
+ @param m number of physical channels
+ @param blockLen number of samples per output of each iteration
+ @param hLen number of taps in each constituent filter path
+ */
+ Synthesis(size_t m, size_t blockLen, size_t hLen = 16);
+ ~Synthesis();
+
+ /* Return required input and output buffer lengths */
+ size_t inputLen() const;
+ size_t outputLen() const;
+
+ /** Rotate "output commutator" and drive samples through filterbank
+ @param out complex output vector
+ @param oLen number of samples in buffer (must match block length * m)
+ @return false on error and true otherwise
+ */
+ bool rotate(float *out, size_t oLen);
+
+ /** Get buffer for an input path
+ @param chan channel number of filterbank
+ @return NULL on error and pointer to buffer otherwise
+ */
+ float *inputBuffer(size_t chan) const;
+ bool resetBuffer(size_t chan);
+};
+
+#endif /* _SYNTHESIS_H_ */
diff --git a/Transceiver52M/Transceiver.cpp b/Transceiver52M/Transceiver.cpp
index 942eb7d..1a93e4a 100644
--- a/Transceiver52M/Transceiver.cpp
+++ b/Transceiver52M/Transceiver.cpp
@@ -657,6 +657,11 @@ void Transceiver::driveControl(size_t chan)
int msgLen = -1;
buffer[0] = '\0';
+ if (chan >= mChans) {
+ LOG(ALERT) << "Command on invalid channel";
+ return;
+ }
+
msgLen = mCtrlSockets[chan]->read(buffer);
if (msgLen < 1) {
@@ -679,11 +684,15 @@ void Transceiver::driveControl(size_t chan)
LOG(INFO) << "command is " << buffer;
if (strcmp(command,"POWEROFF")==0) {
- stop();
- sprintf(response,"RSP POWEROFF 0");
+ if (chan) {
+ sprintf(response,"RSP POWEROFF 1");
+ } else {
+ stop();
+ sprintf(response,"RSP POWEROFF 0");
+ }
}
else if (strcmp(command,"POWERON")==0) {
- if (!start())
+ if (chan || !start())
sprintf(response,"RSP POWERON 1");
else
sprintf(response,"RSP POWERON 0");
@@ -696,7 +705,6 @@ void Transceiver::driveControl(size_t chan)
sprintf(response,"RSP SETMAXDLY 0 %d",maxDelay);
}
else if (strcmp(command,"SETRXGAIN")==0) {
- //set expected maximum time-of-arrival
int newGain;
sscanf(buffer,"%3s %s %d",cmdcheck,command,&newGain);
newGain = mRadioInterface->setRxGain(newGain, chan);
@@ -755,9 +763,7 @@ void Transceiver::driveControl(size_t chan)
// set TSC
unsigned TSC;
sscanf(buffer, "%3s %s %d", cmdcheck, command, &TSC);
- if (mOn)
- sprintf(response, "RSP SETTSC 1 %d", TSC);
- else if (chan && (TSC != mTSC))
+ if (mOn || (chan && (TSC != mTSC)))
sprintf(response, "RSP SETTSC 1 %d", TSC);
else {
mTSC = TSC;
diff --git a/Transceiver52M/UHDDevice.cpp b/Transceiver52M/UHDDevice.cpp
index 5dbcff3..6386dee 100644
--- a/Transceiver52M/UHDDevice.cpp
+++ b/Transceiver52M/UHDDevice.cpp
@@ -1,8 +1,10 @@
/*
* Device support for Ettus Research UHD driver
- * Written by Thomas Tsou <ttsou@vt.edu>
*
- * Copyright 2010,2011 Free Software Foundation, Inc.
+ * Copyright (C) 2010-2014 Free Software Foundation, Inc.
+ * Copyright (C) 2015 Ettus Research LLC
+ *
+ * Author: Tom Tsou <tom@tsou.cc>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
@@ -33,6 +35,7 @@
#endif
#define B2XX_CLK_RT 26e6
+#define B2XX_MC_CLK_RT (5.6e6 * 4)
#define E1XX_CLK_RT 52e6
#define B100_BASE_RT 400000
#define USRP2_BASE_RT 390625
@@ -53,6 +56,7 @@ enum uhd_dev_type {
B100,
B200,
B210,
+ B2XX_MCBTS,
E1XX,
E3XX,
X3XX,
@@ -78,24 +82,26 @@ struct uhd_dev_offset {
* USRP1 with timestamps is not supported by UHD.
*/
static struct uhd_dev_offset uhd_offsets[NUM_USRP_TYPES * 2] = {
- { USRP1, 1, 0.0, "USRP1 not supported" },
- { USRP1, 4, 0.0, "USRP1 not supported"},
- { USRP2, 1, 1.2184e-4, "N2XX 1 SPS" },
- { USRP2, 4, 8.0230e-5, "N2XX 4 SPS" },
- { B100, 1, 1.2104e-4, "B100 1 SPS" },
- { B100, 4, 7.9307e-5, "B100 4 SPS" },
- { B200, 1, 9.9692e-5, "B200 1 SPS" },
- { B200, 4, 6.9248e-5, "B200 4 SPS" },
- { B210, 1, 9.9692e-5, "B210 1 SPS" },
- { B210, 4, 6.9248e-5, "B210 4 SPS" },
- { E1XX, 1, 9.5192e-5, "E1XX 1 SPS" },
- { E1XX, 4, 6.5571e-5, "E1XX 4 SPS" },
- { E3XX, 1, 1.5000e-4, "E3XX 1 SPS" },
- { E3XX, 4, 1.2740e-4, "E3XX 4 SPS" },
- { X3XX, 1, 1.5360e-4, "X3XX 1 SPS"},
- { X3XX, 4, 1.1264e-4, "X3XX 4 SPS"},
- { UMTRX, 1, 9.9692e-5, "UmTRX 1 SPS" },
- { UMTRX, 4, 7.3846e-5, "UmTRX 4 SPS" },
+ { USRP1, 1, 0.0, "USRP1 not supported" },
+ { USRP1, 4, 0.0, "USRP1 not supported"},
+ { USRP2, 1, 1.2184e-4, "N2XX 1 SPS" },
+ { USRP2, 4, 8.0230e-5, "N2XX 4 SPS" },
+ { B100, 1, 1.2104e-4, "B100 1 SPS" },
+ { B100, 4, 7.9307e-5, "B100 4 SPS" },
+ { B200, 1, 9.9692e-5, "B200 1 SPS" },
+ { B200, 4, 6.9248e-5, "B200 4 SPS" },
+ { B210, 1, 9.9692e-5, "B210 1 SPS" },
+ { B210, 4, 6.9248e-5, "B210 4 SPS" },
+ { B2XX_MCBTS, 1, 1.1143e-4, "B200/B210 1 SPS Multi-ARFCN" },
+ { B2XX_MCBTS, 4, 9.2500e-5, "B200/B210 4 SPS Multi-ARFCN" },
+ { E1XX, 1, 9.5192e-5, "E1XX 1 SPS" },
+ { E1XX, 4, 6.5571e-5, "E1XX 4 SPS" },
+ { E3XX, 1, 1.5000e-4, "E3XX 1 SPS" },
+ { E3XX, 4, 1.2740e-4, "E3XX 4 SPS" },
+ { X3XX, 1, 1.5360e-4, "X3XX 1 SPS"},
+ { X3XX, 4, 1.1264e-4, "X3XX 4 SPS"},
+ { UMTRX, 1, 9.9692e-5, "UmTRX 1 SPS" },
+ { UMTRX, 4, 7.3846e-5, "UmTRX 4 SPS" },
};
/*
@@ -107,20 +113,28 @@ static struct uhd_dev_offset special_offsets[] = {
{ UMTRX, 4, 5.2103e-5, "UmTRX diversity, 4 SPS" },
};
-static double get_dev_offset(enum uhd_dev_type type,
- int sps, bool diversity = false)
+static double get_dev_offset(enum uhd_dev_type dev,
+ int sps, RadioDevice::InterfaceType iface)
{
struct uhd_dev_offset *offset = NULL;
/* Reject USRP1 */
- if (type == USRP1) {
+ if (dev == USRP1) {
LOG(ALERT) << "Invalid device type";
return 0.0;
}
+ /* Multi-ARFCN supports B2XX only for now */
+ if (iface == RadioDevice::MULTI_ARFCN) {
+ if ((dev == B200) || (dev == B210))
+ dev = B2XX_MCBTS;
+ else
+ return 0.0;
+ }
+
/* Special cases (e.g. diversity receiver) */
- if (diversity) {
- if (type != UMTRX) {
+ if (iface == RadioDevice::DIVERSITY) {
+ if (dev != UMTRX) {
LOG(ALERT) << "Diversity on UmTRX only";
return 0.0;
}
@@ -136,7 +150,7 @@ static double get_dev_offset(enum uhd_dev_type type,
} else {
/* Search for matching offset value */
for (int i = 0; i < NUM_USRP_TYPES * 2; i++) {
- if ((type == uhd_offsets[i].type) &&
+ if ((dev == uhd_offsets[i].type) &&
(sps == uhd_offsets[i].sps)) {
offset = &uhd_offsets[i];
break;
@@ -159,19 +173,33 @@ static double get_dev_offset(enum uhd_dev_type type,
* The base rate is either GSM symbol rate, 270.833 kHz, or the minimum
* usable channel spacing of 400 kHz.
*/
-static double select_rate(uhd_dev_type type, int sps, bool diversity = false)
+static double select_rate(uhd_dev_type dev, size_t sps,
+ RadioDevice::InterfaceType iface)
{
- if (diversity && (type == UMTRX)) {
- return GSMRATE * 4;
- } else if (diversity) {
- LOG(ALERT) << "Diversity supported on UmTRX only";
- return -9999.99;
+ if (iface == RadioDevice::DIVERSITY) {
+ if (dev == UMTRX) {
+ return GSMRATE * 4;
+ } else {
+ LOG(ALERT) << "Diversity supported on UmTRX only";
+ return -9999.99;
+ }
}
if ((sps != 4) && (sps != 1))
return -9999.99;
- switch (type) {
+ if (iface == RadioDevice::MULTI_ARFCN) {
+ switch (dev) {
+ case B200:
+ case B210:
+ return 7 * GSM_CHAN_SPACING;
+ default:
+ LOG(ALERT) << "Invalid device combination";
+ return -9999.99;
+ }
+ }
+
+ switch (dev) {
case USRP2:
case X3XX:
return USRP2_BASE_RT * sps;
@@ -187,7 +215,7 @@ static double select_rate(uhd_dev_type type, int sps, bool diversity = false)
break;
}
- LOG(ALERT) << "Unknown device type " << type;
+ LOG(ALERT) << "Unknown device type " << dev;
return -9999.99;
}
@@ -281,7 +309,7 @@ private:
*/
class uhd_device : public RadioDevice {
public:
- uhd_device(size_t sps, size_t chans, bool diversity, double offset);
+ uhd_device(size_t sps, InterfaceType type, size_t chans, double offset);
~uhd_device();
int open(const std::string &args, bool extref);
@@ -302,8 +330,8 @@ public:
bool setTxFreq(double wFreq, size_t chan);
bool setRxFreq(double wFreq, size_t chan);
- inline TIMESTAMP initialWriteTimestamp() { return ts_initial * sps; }
- inline TIMESTAMP initialReadTimestamp() { return ts_initial; }
+ TIMESTAMP initialWriteTimestamp();
+ TIMESTAMP initialReadTimestamp();
inline double fullScaleInputValue() { return 32000 * TX_AMPL; }
inline double fullScaleOutputValue() { return 32000; }
@@ -366,8 +394,8 @@ private:
std::vector<smpl_buf *> rx_buffers;
void init_gains();
- int set_master_clk(double rate);
- int set_rates(double tx_rate, double rx_rate);
+ bool set_master_clk();
+ bool set_rates(double tx_rate, double rx_rate);
bool parse_dev_type();
bool flush_recv(size_t num_pkts);
int check_rx_md_err(uhd::rx_metadata_t &md, ssize_t num_smpls);
@@ -379,7 +407,7 @@ private:
bool set_freq(double freq, size_t chan, bool tx);
Thread *async_event_thrd;
- bool diversity;
+ InterfaceType iface;
Mutex tune_lock;
};
@@ -410,7 +438,7 @@ void uhd_msg_handler(uhd::msg::type_t type, const std::string &msg)
LOG(WARNING) << msg;
break;
case uhd::msg::error:
- LOG(ERR) << msg;
+ LOG(ALERT) << msg;
break;
case uhd::msg::fastpath:
break;
@@ -423,7 +451,8 @@ static void thread_enable_cancel(bool cancel)
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
}
-uhd_device::uhd_device(size_t sps, size_t chans, bool diversity, double offset)
+uhd_device::uhd_device(size_t sps, InterfaceType iface,
+ size_t chans, double offset)
: tx_gain_min(0.0), tx_gain_max(0.0),
rx_gain_min(0.0), rx_gain_max(0.0),
tx_spp(0), rx_spp(0),
@@ -433,7 +462,7 @@ uhd_device::uhd_device(size_t sps, size_t chans, bool diversity, double offset)
this->sps = sps;
this->chans = chans;
this->offset = offset;
- this->diversity = diversity;
+ this->iface = iface;
}
uhd_device::~uhd_device()
@@ -470,45 +499,49 @@ void uhd_device::init_gains()
}
-int uhd_device::set_master_clk(double clk_rate)
+bool uhd_device::set_master_clk()
{
- double actual, offset, limit = 1.0;
+ double req, actual, offset, limit = 1.0;
+
+ if (iface == MULTI_ARFCN)
+ req = B2XX_MC_CLK_RT;
+ else if (dev_type == E1XX)
+ req = E1XX_CLK_RT;
+ else if (dev_type == E3XX)
+ req = B2XX_CLK_RT;
+ else if ((dev_type == B200) || (dev_type == B210))
+ req = B2XX_CLK_RT;
+ else
+ return true;
try {
- usrp_dev->set_master_clock_rate(clk_rate);
+ usrp_dev->set_master_clock_rate(req);
} catch (const std::exception &ex) {
- LOG(ALERT) << "UHD clock rate setting failed: " << clk_rate;
+ LOG(ALERT) << "UHD clock rate setting failed: " << req;
LOG(ALERT) << ex.what();
- return -1;
+ return false;
}
actual = usrp_dev->get_master_clock_rate();
- offset = fabs(clk_rate - actual);
+ offset = fabs(req - actual);
if (offset > limit) {
LOG(ALERT) << "Failed to set master clock rate";
- LOG(ALERT) << "Requested clock rate " << clk_rate;
+ LOG(ALERT) << "Requested clock rate " << req;
LOG(ALERT) << "Actual clock rate " << actual;
- return -1;
+ return false;
}
- return 0;
+ return true;
}
-int uhd_device::set_rates(double tx_rate, double rx_rate)
+bool uhd_device::set_rates(double tx_rate, double rx_rate)
{
double offset_limit = 1.0;
double tx_offset, rx_offset;
- /* B2XX and E1xx are the only device where we set FPGA clocking */
- if ((dev_type == B200) || (dev_type == B210) || (dev_type == E3XX)) {
- if (set_master_clk(B2XX_CLK_RT) < 0)
- return -1;
- }
- else if (dev_type == E1XX) {
- if (set_master_clk(E1XX_CLK_RT) < 0)
- return -1;
- }
+ if (!set_master_clk())
+ return false;
// Set sample rates
try {
@@ -517,7 +550,7 @@ int uhd_device::set_rates(double tx_rate, double rx_rate)
} catch (const std::exception &ex) {
LOG(ALERT) << "UHD rate setting failed";
LOG(ALERT) << ex.what();
- return -1;
+ return false;
}
this->tx_rate = usrp_dev->get_tx_rate();
this->rx_rate = usrp_dev->get_rx_rate();
@@ -528,14 +561,17 @@ int uhd_device::set_rates(double tx_rate, double rx_rate)
LOG(ALERT) << "Actual sample rate differs from desired rate";
LOG(ALERT) << "Tx/Rx (" << this->tx_rate << "/"
<< this->rx_rate << ")";
- return -1;
+ return false;
}
- return 0;
+ return true;
}
double uhd_device::setTxGain(double db, size_t chan)
{
+ if (iface == MULTI_ARFCN)
+ chan = 0;
+
if (chan >= tx_gains.size()) {
LOG(ALERT) << "Requested non-existent channel" << chan;
return 0.0f;
@@ -549,6 +585,19 @@ double uhd_device::setTxGain(double db, size_t chan)
return tx_gains[chan];
}
+TIMESTAMP uhd_device::initialWriteTimestamp()
+{
+ if (iface == MULTI_ARFCN)
+ return ts_initial;
+
+ return ts_initial * sps;
+}
+
+TIMESTAMP uhd_device::initialReadTimestamp()
+{
+ return ts_initial;
+}
+
double uhd_device::setRxGain(double db, size_t chan)
{
if (chan >= rx_gains.size()) {
@@ -566,6 +615,9 @@ double uhd_device::setRxGain(double db, size_t chan)
double uhd_device::getRxGain(size_t chan)
{
+ if (iface == MULTI_ARFCN)
+ chan = 0;
+
if (chan >= rx_gains.size()) {
LOG(ALERT) << "Requested non-existent channel " << chan;
return 0.0f;
@@ -680,30 +732,39 @@ int uhd_device::open(const std::string &args, bool extref)
return -1;
// Verify and set channels
- if ((dev_type == B210) && (chans == 2)) {
- } else if ((dev_type == UMTRX) && (chans == 2)) {
- uhd::usrp::subdev_spec_t subdev_spec("A:0 B:0");
- usrp_dev->set_tx_subdev_spec(subdev_spec);
- usrp_dev->set_rx_subdev_spec(subdev_spec);
+ if (iface == MULTI_ARFCN) {
+ if ((dev_type != B200) && (dev_type != B210)) {
+ LOG(ALERT) << "Unsupported device configuration";
+ return -1;
+ }
+
+ chans = 1;
+ } else if (chans == 2) {
+ if (dev_type == B210) {
+ } else if (dev_type == UMTRX) {
+ uhd::usrp::subdev_spec_t subdev_spec("A:0 B:0");
+ usrp_dev->set_tx_subdev_spec(subdev_spec);
+ usrp_dev->set_rx_subdev_spec(subdev_spec);
+ } else {
+ LOG(ALERT) << "Invalid device configuration";
+ return -1;
+ }
} else if (chans != 1) {
LOG(ALERT) << "Invalid channel combination for device";
return -1;
}
- tx_freqs.resize(chans);
- rx_freqs.resize(chans);
- tx_gains.resize(chans);
- rx_gains.resize(chans);
- rx_buffers.resize(chans);
-
if (extref)
usrp_dev->set_clock_source("external");
// Set rates
double _rx_rate;
- double _tx_rate = select_rate(dev_type, sps);
- if (diversity)
- _rx_rate = select_rate(dev_type, 1, true);
+ double _tx_rate = select_rate(dev_type, sps, iface);
+
+ if (iface == DIVERSITY)
+ _rx_rate = select_rate(dev_type, 1, iface);
+ else if (iface == MULTI_ARFCN)
+ _rx_rate = _tx_rate;
else
_rx_rate = _tx_rate / sps;
@@ -712,6 +773,12 @@ int uhd_device::open(const std::string &args, bool extref)
if (set_rates(_tx_rate, _rx_rate) < 0)
return -1;
+ tx_freqs.resize(chans);
+ rx_freqs.resize(chans);
+ tx_gains.resize(chans);
+ rx_gains.resize(chans);
+ rx_buffers.resize(chans);
+
/* Create TX and RX streamers */
uhd::stream_args_t stream_args("sc16");
for (size_t i = 0; i < chans; i++)
@@ -730,7 +797,7 @@ int uhd_device::open(const std::string &args, bool extref)
rx_buffers[i] = new smpl_buf(buf_len, rx_rate);
// Set receive chain sample offset
- double offset = get_dev_offset(dev_type, sps, diversity);
+ double offset = get_dev_offset(dev_type, sps, iface);
if (offset == 0.0) {
LOG(ALERT) << "Unsupported configuration, no correction applied";
ts_offset = 0;
@@ -744,8 +811,10 @@ int uhd_device::open(const std::string &args, bool extref)
// Print configuration
LOG(INFO) << "\n" << usrp_dev->get_pp_string();
- if (diversity)
+ if (iface == DIVERSITY)
return DIVERSITY;
+ if (iface == MULTI_ARFCN)
+ return MULTI_ARFCN;
switch (dev_type) {
case B100:
@@ -753,10 +822,11 @@ int uhd_device::open(const std::string &args, bool extref)
case USRP2:
case X3XX:
return RESAMP_100M;
- case B200:
- case B210:
+ return RESAMP_64M;
case E1XX:
case E3XX:
+ case B200:
+ case B210:
default:
break;
}
@@ -822,7 +892,7 @@ bool uhd_device::start()
LOG(INFO) << "Starting USRP...";
if (started) {
- LOG(ERR) << "Device already started";
+ LOG(ALERT) << "Device already started";
return false;
}
@@ -874,7 +944,7 @@ int uhd_device::check_rx_md_err(uhd::rx_metadata_t &md, ssize_t num_smpls)
uhd::time_spec_t ts;
if (!num_smpls) {
- LOG(ERR) << str_code(md);
+ LOG(ALERT) << str_code(md);
switch (md.error_code) {
case uhd::rx_metadata_t::ERROR_CODE_TIMEOUT:
@@ -1014,7 +1084,7 @@ int uhd_device::writeSamples(std::vector<short *> &bufs, int len, bool *underrun
// No control packets
if (isControl) {
- LOG(ERR) << "Control packets not supported";
+ LOG(ALERT) << "Control packets not supported";
return 0;
}
@@ -1196,7 +1266,7 @@ bool uhd_device::recv_async_msg()
if ((md.event_code != uhd::async_metadata_t::EVENT_CODE_UNDERFLOW) &&
(md.event_code != uhd::async_metadata_t::EVENT_CODE_TIME_ERROR)) {
- LOG(ERR) << str_code(md);
+ LOG(ALERT) << str_code(md);
}
}
@@ -1419,8 +1489,8 @@ std::string smpl_buf::str_code(ssize_t code)
}
}
-RadioDevice *RadioDevice::make(size_t sps, size_t chans,
- bool diversity, double offset)
+RadioDevice *RadioDevice::make(size_t sps, InterfaceType type,
+ size_t chans, double offset)
{
- return new uhd_device(sps, chans, diversity, offset);
+ return new uhd_device(sps, type, chans, offset);
}
diff --git a/Transceiver52M/common/fft.c b/Transceiver52M/common/fft.c
new file mode 100644
index 0000000..18b2de7
--- /dev/null
+++ b/Transceiver52M/common/fft.c
@@ -0,0 +1,112 @@
+/*
+ * Fast Fourier transform
+ *
+ * Copyright (C) 2012 Tom Tsou <tom@tsou.cc>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ * See the COPYING file in the main directory for details.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <fftw3.h>
+
+#include "fft.h"
+
+struct fft_hdl {
+ float *fft_in;
+ float *fft_out;
+ int len;
+ fftwf_plan fft_plan;
+};
+
+/*! \brief Initialize FFT backend
+ * \param[in] reverse FFT direction
+ * \param[in] m FFT length
+ * \param[in] istride input stride count
+ * \param[in] ostride output stride count
+ * \param[in] in input buffer (FFTW aligned)
+ * \param[in] out output buffer (FFTW aligned)
+ * \param[in] ooffset initial offset into output buffer
+ *
+ * If the reverse is non-NULL, then an inverse FFT will be used. This is a
+ * wrapper for advanced non-contiguous FFTW usage. See FFTW documentation for
+ * further details.
+ *
+ * http://www.fftw.org/doc/Advanced-Complex-DFTs.html
+ *
+ * It is currently unknown how the offset of the output buffer affects FFTW
+ * memory alignment.
+ */
+struct fft_hdl *init_fft(int reverse, int m, int istride, int ostride,
+ float *in, float *out, int ooffset)
+{
+ int rank = 1;
+ int n[] = { m };
+ int howmany = istride;
+ int idist = 1;
+ int odist = 1;
+ int *inembed = n;
+ int *onembed = n;
+ fftwf_complex *obuffer, *ibuffer;
+
+ struct fft_hdl *hdl = (struct fft_hdl *) malloc(sizeof(struct fft_hdl));
+ if (!hdl)
+ return NULL;
+
+ int direction = FFTW_FORWARD;
+ if (reverse)
+ direction = FFTW_BACKWARD;
+
+ ibuffer = (fftwf_complex *) in;
+ obuffer = (fftwf_complex *) out + ooffset;
+
+ hdl->fft_in = in;
+ hdl->fft_out = out;
+ hdl->fft_plan = fftwf_plan_many_dft(rank, n, howmany,
+ ibuffer, inembed, istride, idist,
+ obuffer, onembed, ostride, odist,
+ direction, FFTW_MEASURE);
+ return hdl;
+}
+
+void *fft_malloc(size_t size)
+{
+ return fftwf_malloc(size);
+}
+
+void fft_free(void *ptr)
+{
+ free(ptr);
+}
+
+/*! \brief Free FFT backend resources
+ */
+void free_fft(struct fft_hdl *hdl)
+{
+ fftwf_destroy_plan(hdl->fft_plan);
+ free(hdl);
+}
+
+/*! \brief Run multiple DFT operations with the initialized plan
+ * \param[in] hdl handle to an intitialized fft struct
+ *
+ * Input and output buffers are configured with init_fft().
+ */
+int cxvec_fft(struct fft_hdl *hdl)
+{
+ fftwf_execute(hdl->fft_plan);
+ return 0;
+}
diff --git a/Transceiver52M/common/fft.h b/Transceiver52M/common/fft.h
new file mode 100644
index 0000000..fb7bede
--- /dev/null
+++ b/Transceiver52M/common/fft.h
@@ -0,0 +1,13 @@
+#ifndef _FFT_H_
+#define _FFT_H_
+
+struct fft_hdl;
+
+struct fft_hdl *init_fft(int reverse, int m, int istride, int ostride,
+ float *in, float *out, int ooffset);
+void *fft_malloc(size_t size);
+void fft_free(void *ptr);
+void free_fft(struct fft_hdl *hdl);
+int cxvec_fft(struct fft_hdl *hdl);
+
+#endif /* _FFT_H_ */
diff --git a/Transceiver52M/osmo-trx.cpp b/Transceiver52M/osmo-trx.cpp
index db0b2b1..eae7042 100644
--- a/Transceiver52M/osmo-trx.cpp
+++ b/Transceiver52M/osmo-trx.cpp
@@ -356,6 +356,7 @@ int main(int argc, char *argv[])
RadioDevice *usrp;
RadioInterface *radio = NULL;
Transceiver *trx = NULL;
+ RadioDevice::InterfaceType iface = RadioDevice::NORMAL;
struct trx_config config;
handle_options(argc, argv, &config);
@@ -373,8 +374,7 @@ int main(int argc, char *argv[])
srandom(time(NULL));
/* Create the low level device object */
- usrp = RadioDevice::make(config.sps, config.chans,
- config.diversity, config.offset);
+ usrp = RadioDevice::make(config.sps, iface, config.chans, config.offset);
type = usrp->open(config.dev_args, config.extref);
if (type < 0) {
LOG(ALERT) << "Failed to create radio device" << std::endl;
diff --git a/Transceiver52M/radioDevice.h b/Transceiver52M/radioDevice.h
index 6273bcc..e53e8e6 100644
--- a/Transceiver52M/radioDevice.h
+++ b/Transceiver52M/radioDevice.h
@@ -22,7 +22,8 @@
#include "config.h"
#endif
-#define GSMRATE 1625e3/6
+#define GSMRATE (1625e3 / 6.0)
+#define GSM_CHAN_SPACING 400000.0
/** a 64-bit virtual timestamp for radio data */
typedef unsigned long long TIMESTAMP;
@@ -35,10 +36,12 @@ class RadioDevice {
enum TxWindowType { TX_WINDOW_USRP1, TX_WINDOW_FIXED };
/* Radio interface types */
- enum RadioInterfaceType { NORMAL, RESAMP_64M, RESAMP_100M, DIVERSITY };
+ enum InterfaceType {
+ NORMAL, RESAMP_64M, RESAMP_100M, MULTI_ARFCN, DIVERSITY
+ };
- static RadioDevice *make(size_t sps, size_t chans = 1,
- bool diversity = false, double offset = 0.0);
+ static RadioDevice *make(size_t sps, InterfaceType type,
+ size_t chans = 1, double offset = 0.0);
/** Initialize the USRP */
virtual int open(const std::string &args = "", bool extref = false)=0;
diff --git a/Transceiver52M/radioInterface.h b/Transceiver52M/radioInterface.h
index d5d65aa..f51642b 100644
--- a/Transceiver52M/radioInterface.h
+++ b/Transceiver52M/radioInterface.h
@@ -22,6 +22,8 @@
#include "radioClock.h"
#include "radioBuffer.h"
#include "Resampler.h"
+#include "Channelizer.h"
+#include "Synthesis.h"
static const unsigned gSlotLen = 148; ///< number of symbols per slot, not counting guard periods
@@ -100,13 +102,13 @@ public:
RadioClock* getClock(void) { return &mClock;};
/** set transmit frequency */
- bool tuneTx(double freq, size_t chan = 0);
+ virtual bool tuneTx(double freq, size_t chan = 0);
/** set receive frequency */
virtual bool tuneRx(double freq, size_t chan = 0);
/** set receive gain */
- double setRxGain(double dB, size_t chan = 0);
+ virtual double setRxGain(double dB, size_t chan = 0);
/** get receive gain */
double getRxGain(size_t chan = 0);
@@ -163,6 +165,33 @@ public:
void close();
};
+class RadioInterfaceMulti : public RadioInterface {
+private:
+ bool pushBuffer();
+ void pullBuffer();
+
+ signalVector *outerSendBuffer;
+ signalVector *outerRecvBuffer;
+ std::vector<signalVector *> history;
+ std::vector<bool> active;
+
+ Resampler *dnsampler;
+ Resampler *upsampler;
+ Channelizer *channelizer;
+ Synthesis *synthesis;
+
+public:
+ RadioInterfaceMulti(RadioDevice* wRadio, size_t wSPS = 4, size_t chans = 1);
+ ~RadioInterfaceMulti();
+
+ bool init(int type);
+ void close();
+
+ bool tuneTx(double freq, size_t chan);
+ bool tuneRx(double freq, size_t chan);
+ double setRxGain(double dB, size_t chan);
+};
+
class RadioInterfaceDiversity : public RadioInterface {
public:
RadioInterfaceDiversity(RadioDevice* wRadio,
diff --git a/Transceiver52M/radioInterfaceMulti.cpp b/Transceiver52M/radioInterfaceMulti.cpp
new file mode 100644
index 0000000..768d901
--- /dev/null
+++ b/Transceiver52M/radioInterfaceMulti.cpp
@@ -0,0 +1,393 @@
+/*
+ * Multi-carrier radio interface
+ *
+ * Copyright (C) 2015 Ettus Research LLC
+ *
+ * Author: Tom Tsou <tom@tsou.cc>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ * See the COPYING file in the main directory for details.
+ */
+
+#include <radioInterface.h>
+#include <Logger.h>
+
+#include "Resampler.h"
+
+extern "C" {
+#include "convert.h"
+}
+
+/* Resampling parameters for 64 MHz clocking */
+#define RESAMP_64M_INRATE 65
+#define RESAMP_64M_OUTRATE 96
+
+/* Universal resampling parameters */
+#define NUMCHUNKS 24
+
+#define MCHANS 7
+
+static size_t resamp_inrate = 0;
+static size_t inchunk = 0;
+static size_t resamp_outrate = 0;
+static size_t outchunk = 0;
+
+RadioInterfaceMulti::RadioInterfaceMulti(RadioDevice *wRadio,
+ size_t sps, size_t chans)
+ : RadioInterface(wRadio, sps, chans),
+ outerSendBuffer(NULL), outerRecvBuffer(NULL),
+ dnsampler(NULL), upsampler(NULL), channelizer(NULL), synthesis(NULL)
+{
+}
+
+RadioInterfaceMulti::~RadioInterfaceMulti()
+{
+ close();
+}
+
+void RadioInterfaceMulti::close()
+{
+ delete outerSendBuffer;
+ delete outerRecvBuffer;
+ delete dnsampler;
+ delete upsampler;
+ delete channelizer;
+ delete synthesis;
+
+ outerSendBuffer = NULL;
+ outerRecvBuffer = NULL;
+ dnsampler = NULL;
+ upsampler = NULL;
+ channelizer = NULL;
+ synthesis = NULL;
+
+ mReceiveFIFO.resize(0);
+ powerScaling.resize(0);
+ history.resize(0);
+ active.resize(0);
+
+ RadioInterface::close();
+}
+
+static int getLogicalChan(size_t pchan, size_t chans)
+{
+ switch (chans) {
+ case 1:
+ if (pchan == 0)
+ return 0;
+ else
+ return -1;
+ break;
+ case 3:
+ if (pchan == 0)
+ return 1;
+ if (pchan == 1)
+ return 0;
+ if (pchan == 6)
+ return 2;
+ else
+ return -1;
+ break;
+ case 5:
+ if (pchan == 0)
+ return 2;
+ if (pchan == 1)
+ return 1;
+ if (pchan == 2)
+ return 0;
+ if (pchan == 5)
+ return 4;
+ if (pchan == 6)
+ return 3;
+ else
+ return -1;
+ break;
+ default:
+ break;
+ };
+
+ return -1;
+}
+
+static int getFreqShift(size_t chans)
+{
+ switch (chans) {
+ case 1:
+ return 0;
+ case 3:
+ return 1;
+ case 5:
+ return 2;
+ default:
+ break;
+ };
+
+ return -1;
+}
+
+/* Initialize I/O specific objects */
+bool RadioInterfaceMulti::init(int type)
+{
+ float cutoff = 1.0f;
+
+ if (mChans > 5) {
+ LOG(ALERT) << "Unsupported channel configuration " << mChans;
+ return false;
+ }
+
+ close();
+
+ sendBuffer.resize(mChans);
+ recvBuffer.resize(mChans);
+ convertSendBuffer.resize(1);
+ convertRecvBuffer.resize(1);
+
+ mReceiveFIFO.resize(mChans);
+ powerScaling.resize(mChans);
+ history.resize(mChans);
+ active.resize(MCHANS);
+
+ resamp_inrate = RESAMP_64M_INRATE;
+ resamp_outrate = RESAMP_64M_OUTRATE;
+
+ inchunk = resamp_inrate * 4;
+ outchunk = resamp_outrate * 4;
+
+ if (inchunk * NUMCHUNKS < 157 * mSPSTx * 2) {
+ LOG(ALERT) << "Invalid inner chunk size " << inchunk;
+ return false;
+ }
+
+ dnsampler = new Resampler(resamp_inrate, resamp_outrate);
+ if (!dnsampler->init(1.0)) {
+ LOG(ALERT) << "Rx resampler failed to initialize";
+ return false;
+ }
+
+ upsampler = new Resampler(resamp_outrate, resamp_inrate * mSPSTx);
+ if (!upsampler->init(cutoff)) {
+ LOG(ALERT) << "Tx resampler failed to initialize";
+ return false;
+ }
+
+ channelizer = new Channelizer(MCHANS, outchunk);
+ if (!channelizer->init()) {
+ LOG(ALERT) << "Rx channelizer failed to initialize";
+ return false;
+ }
+
+ synthesis = new Synthesis(MCHANS, outchunk);
+ if (!synthesis->init()) {
+ LOG(ALERT) << "Tx synthesis filter failed to initialize";
+ return false;
+ }
+
+ /*
+ * Allocate high and low rate buffers. The high rate receive
+ * buffer and low rate transmit vectors feed into the resampler
+ * and requires headroom equivalent to the filter length. Low
+ * rate buffers are allocated in the main radio interface code.
+ */
+ for (size_t i = 0; i < mChans; i++) {
+ sendBuffer[i] = new RadioBuffer(NUMCHUNKS, inchunk * mSPSTx,
+ upsampler->len(), true);
+ recvBuffer[i] = new RadioBuffer(NUMCHUNKS, inchunk,
+ 0, false);
+ history[i] = new signalVector(dnsampler->len());
+
+ active[i] = false;
+ synthesis->resetBuffer(i);
+ }
+
+ outerSendBuffer = new signalVector(synthesis->outputLen());
+ outerRecvBuffer = new signalVector(channelizer->inputLen());
+
+ convertSendBuffer[0] = new short[2 * synthesis->outputLen()];
+ convertRecvBuffer[0] = new short[2 * channelizer->inputLen()];
+
+ /* Configure channels */
+ switch (mChans) {
+ case 1:
+ active[0] = true;
+ break;
+ case 3:
+ active[0] = true;
+ active[1] = true;
+ active[6] = true;
+ break;
+ case 5:
+ active[0] = true;
+ active[1] = true;
+ active[2] = true;
+ active[5] = true;
+ active[6] = true;
+ break;
+ default:
+ LOG(ALERT) << "Unsupported channel combination";
+ return false;
+ }
+
+ return true;
+}
+
+/* Receive a timestamped chunk from the device */
+void RadioInterfaceMulti::pullBuffer()
+{
+ bool local_underrun;
+ size_t num;
+ float *buf;
+
+ if (recvBuffer[0]->getFreeSegments() <= 0)
+ return;
+
+ /* Outer buffer access size is fixed */
+ num = mRadio->readSamples(convertRecvBuffer,
+ outerRecvBuffer->size(),
+ &overrun,
+ readTimestamp,
+ &local_underrun);
+ if (num != channelizer->inputLen()) {
+ LOG(ALERT) << "Receive error " << num << ", " << channelizer->inputLen();
+ return;
+ }
+
+ convert_short_float((float *) outerRecvBuffer->begin(),
+ convertRecvBuffer[0], 2 * outerRecvBuffer->size());
+
+ underrun |= local_underrun;
+ readTimestamp += num;
+
+ channelizer->rotate((float *) outerRecvBuffer->begin(),
+ outerRecvBuffer->size());
+
+ for (size_t i = 0; i < MCHANS; i++) {
+ if (!active[i])
+ continue;
+
+ int lchan = getLogicalChan(i, mChans);
+ if (lchan < 0) {
+ LOG(ALERT) << "Bad logical channel " << lchan;
+ continue;
+ }
+
+ /* Update history */
+ buf = channelizer->outputBuffer(i);
+ size_t cLen = channelizer->outputLen();
+ size_t hLen = dnsampler->len();
+ size_t hSize = 2 * hLen * sizeof(float);
+
+ memcpy(&buf[2 * -hLen], history[lchan]->begin(), hSize);
+ memcpy(history[lchan]->begin(), &buf[2 * (cLen - hLen)], hSize);
+
+ /* Write to the end of the inner receive buffer */
+ if (!dnsampler->rotate(channelizer->outputBuffer(i),
+ channelizer->outputLen(),
+ recvBuffer[lchan]->getWriteSegment(),
+ recvBuffer[lchan]->getSegmentLen())) {
+ LOG(ALERT) << "Sample rate upsampling error";
+ }
+ }
+}
+
+/* Send a timestamped chunk to the device */
+bool RadioInterfaceMulti::pushBuffer()
+{
+ if (sendBuffer[0]->getAvailSegments() <= 0)
+ return false;
+
+ for (size_t i = 0; i < MCHANS; i++) {
+ if (!active[i]) {
+ synthesis->resetBuffer(i);
+ continue;
+ }
+
+ int lchan = getLogicalChan(i, mChans);
+
+ if (!upsampler->rotate(sendBuffer[lchan]->getReadSegment(),
+ sendBuffer[lchan]->getSegmentLen(),
+ synthesis->inputBuffer(i),
+ synthesis->inputLen())) {
+ LOG(ALERT) << "Sample rate downsampling error";
+ }
+ }
+
+ synthesis->rotate((float *) outerSendBuffer->begin(),
+ outerSendBuffer->size());
+
+ convert_float_short(convertSendBuffer[0],
+ (float *) outerSendBuffer->begin(),
+ 1.0 / (float) mChans, 2 * outerSendBuffer->size());
+
+ size_t num = mRadio->writeSamples(convertSendBuffer,
+ outerSendBuffer->size(),
+ &underrun,
+ writeTimestamp);
+ if (num != outerSendBuffer->size()) {
+ LOG(ALERT) << "Transmit error " << num;
+ }
+
+ writeTimestamp += num;
+
+ return true;
+}
+
+/* Frequency comparison limit */
+#define FREQ_DELTA_LIMIT 10.0
+
+static bool fltcmp(double a, double b)
+{
+ return fabs(a - b) < FREQ_DELTA_LIMIT ? true : false;
+}
+
+bool RadioInterfaceMulti::tuneTx(double freq, size_t chan)
+{
+ if (chan >= mChans)
+ return false;
+
+ double shift = (double) getFreqShift(mChans);
+
+ if (!chan)
+ return mRadio->setTxFreq(freq + shift * GSM_CHAN_SPACING);
+
+ double center = mRadio->getTxFreq();
+ if (!fltcmp(freq, center + (double) (chan - shift) * GSM_CHAN_SPACING))
+ return false;
+
+ return true;
+}
+
+bool RadioInterfaceMulti::tuneRx(double freq, size_t chan)
+{
+ if (chan >= mChans)
+ return false;
+
+ double shift = (double) getFreqShift(mChans);
+
+ if (!chan)
+ return mRadio->setRxFreq(freq + shift * GSM_CHAN_SPACING);
+
+ double center = mRadio->getRxFreq();
+ if (!fltcmp(freq, center + (double) (chan - shift) * GSM_CHAN_SPACING))
+ return false;
+
+ return true;
+}
+
+double RadioInterfaceMulti::setRxGain(double db, size_t chan)
+{
+ if (!chan)
+ return mRadio->setRxGain(db);
+ else
+ return mRadio->getRxGain();
+}
diff --git a/configure.ac b/configure.ac
index ae4ea3b..3a5fbfe 100644
--- a/configure.ac
+++ b/configure.ac
@@ -94,6 +94,7 @@ AS_IF([test "x$with_usrp1" = "xyes"], [
AS_IF([test "x$with_usrp1" != "xyes"],[
PKG_CHECK_MODULES(UHD, uhd >= 003.004.000)
AC_DEFINE(USE_UHD, 1, Define to 1 if using UHD)
+ PKG_CHECK_MODULES(FFTWF, fftw3f)
])
AS_IF([test "x$with_singledb" = "xyes"], [