mcbts: Add multi-ARFCN radio support

Add new radio interface "radioInterfaceMulti" for multi-carrier
support.

Only USRP B200/B210 devices are supported because of sample
rate requirements (3.2 Msps).

Only 4 SPS operation Tx/RX is supported.

8-PSK is supported.

Other options may be added at a later time

Signed-off-by: Tom Tsou <tom.tsou@ettus.com>
This commit is contained in:
Tom Tsou 2016-06-24 14:25:39 -07:00
parent 35222296fe
commit 7676427816
7 changed files with 512 additions and 25 deletions

View File

@ -67,6 +67,7 @@ libtransceiver_la_SOURCES = \
$(COMMON_SOURCES) \
Resampler.cpp \
radioInterfaceResamp.cpp \
radioInterfaceMulti.cpp \
radioInterfaceDiversity.cpp
bin_PROGRAMS = osmo-trx

View File

@ -35,6 +35,7 @@
#endif
#define B2XX_CLK_RT 26e6
#define B2XX_MCBTS_CLK_RT 3.2e6
#define E1XX_CLK_RT 52e6
#define B100_BASE_RT 400000
#define USRP2_BASE_RT 390625
@ -61,6 +62,7 @@ enum uhd_dev_type {
B100,
B200,
B210,
B2XX_MCBTS,
E1XX,
E3XX,
X3XX,
@ -110,6 +112,7 @@ static struct uhd_dev_offset uhd_offsets[] = {
{ B200, 4, 1, B2XX_TIMING_4SPS, "B200 4/1 Tx/Rx SPS" },
{ B210, 1, 1, B2XX_TIMING_1SPS, "B210 1 SPS" },
{ B210, 4, 1, B2XX_TIMING_4SPS, "B210 4/1 Tx/Rx SPS" },
{ B2XX_MCBTS, 4, 4, 1.07188e-4, "B200/B210 4 SPS Multi-ARFCN" },
{ E1XX, 1, 1, 9.5192e-5, "E1XX 1 SPS" },
{ E1XX, 4, 1, 6.5571e-5, "E1XX 4/1 Tx/Rx SPS" },
{ E3XX, 1, 1, 1.84616e-4, "E3XX 1 SPS" },
@ -150,9 +153,20 @@ static double select_rate(uhd_dev_type type, int sps,
return -9999.99;
}
if ((sps != 4) && (sps != 1))
return -9999.99;
if (iface == RadioDevice::MULTI_ARFCN) {
switch (type) {
case B2XX_MCBTS:
return 4 * MCBTS_SPACING;
default:
LOG(ALERT) << "Invalid device combination";
return -9999.99;
}
}
switch (type) {
case USRP2:
case X3XX:
@ -542,12 +556,15 @@ int uhd_device::set_rates(double tx_rate, double rx_rate)
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) {
} else if (dev_type == E1XX) {
if (set_master_clk(E1XX_CLK_RT) < 0)
return -1;
} else if (dev_type == B2XX_MCBTS) {
if (set_master_clk(B2XX_MCBTS_CLK_RT) < 0)
return -1;
}
// Set sample rates
try {
usrp_dev->set_tx_rate(tx_rate);
@ -574,6 +591,9 @@ int uhd_device::set_rates(double tx_rate, double rx_rate)
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;
@ -620,6 +640,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;
@ -762,11 +785,24 @@ int uhd_device::open(const std::string &args, bool extref, bool swap_channels)
}
// Verify and set channels
if ((dev_type == B210) && (chans == 2)) {
} else if ((dev_type == UMTRX) && (chans == 2)) {
uhd::usrp::subdev_spec_t subdev_spec(swap_channels?"B:0 A:0":"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;
}
dev_type = B2XX_MCBTS;
chans = 1;
} else if (chans == 2) {
if (dev_type == B210) {
} else if (dev_type == UMTRX) {
uhd::usrp::subdev_spec_t subdev_spec(swap_channels?"B:0 A:0":"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;
@ -839,6 +875,8 @@ int uhd_device::open(const std::string &args, bool extref, bool swap_channels)
if (iface == DIVERSITY)
return DIVERSITY;
if (iface == MULTI_ARFCN)
return MULTI_ARFCN;
switch (dev_type) {
case B100:
@ -1295,7 +1333,7 @@ double uhd_device::getRxFreq(size_t chan)
*/
TIMESTAMP uhd_device::initialWriteTimestamp()
{
if (rx_sps == tx_sps)
if ((iface == MULTI_ARFCN) || (rx_sps == tx_sps))
return ts_initial;
else
return ts_initial * tx_sps;

View File

@ -74,6 +74,7 @@ struct trx_config {
bool extref;
Transceiver::FillerType filler;
bool diversity;
bool mcbts;
double offset;
double rssi_offset;
bool swap_channels;
@ -127,7 +128,7 @@ bool testConfig()
*/
bool trx_setup_config(struct trx_config *config)
{
std::string refstr, fillstr, divstr, edgestr;
std::string refstr, fillstr, divstr, mcstr, edgestr;
if (!testConfig())
return false;
@ -163,13 +164,29 @@ bool trx_setup_config(struct trx_config *config)
config->diversity = DEFAULT_DIVERSITY;
}
/* Diversity only supported on 2 channels */
if (config->diversity)
if (!config->chans)
config->chans = DEFAULT_CHANS;
if (config->mcbts && ((config->chans < 0) || (config->chans > 5))) {
std::cout << "Unsupported number of channels" << std::endl;
return false;
}
/* Diversity only supported on 2 channels without multi-carrier */
if (config->diversity && config->mcbts) {
std::cout << "Multi-carrier diversity unsupported" << std::endl;
return false;
}
if (config->diversity && (config->chans != 2)) {
std::cout << "Setting channels to 2 for diversity" << std::endl;
config->chans = 2;
}
edgestr = config->edge ? "Enabled" : "Disabled";
refstr = config->extref ? "Enabled" : "Disabled";
divstr = config->diversity ? "Enabled" : "Disabled";
mcstr = config->mcbts ? "Enabled" : "Disabled";
switch (config->filler) {
case Transceiver::FILLER_DUMMY:
fillstr = "Dummy bursts";
@ -200,6 +217,7 @@ bool trx_setup_config(struct trx_config *config)
ost << " EDGE support............ " << edgestr << std::endl;
ost << " External Reference...... " << refstr << std::endl;
ost << " C0 Filler Table......... " << fillstr << std::endl;
ost << " Multi-Carrier........... " << mcstr << std::endl;
ost << " Diversity............... " << divstr << std::endl;
ost << " Tuning offset........... " << config->offset << std::endl;
ost << " RSSI to dBm offset...... " << config->rssi_offset << std::endl;
@ -235,6 +253,10 @@ RadioInterface *makeRadioInterface(struct trx_config *config,
radio = new RadioInterfaceDiversity(usrp, config->tx_sps,
config->chans);
break;
case RadioDevice::MULTI_ARFCN:
radio = new RadioInterfaceMulti(usrp, config->tx_sps,
config->rx_sps, config->chans);
break;
default:
LOG(ALERT) << "Unsupported radio interface configuration";
return NULL;
@ -309,6 +331,7 @@ static void print_help()
" -p Base port number\n"
" -e Enable EDGE receiver\n"
" -d Enable dual channel diversity receiver\n"
" -m Enable multi-ARFCN transceiver (default=disabled)\n"
" -x Enable external 10 MHz reference\n"
" -s Tx samples-per-symbol (1 or 4)\n"
" -b Rx samples-per-symbol (1 or 4)\n"
@ -334,13 +357,14 @@ static void handle_options(int argc, char **argv, struct trx_config *config)
config->rach_delay = 0;
config->extref = false;
config->filler = Transceiver::FILLER_ZERO;
config->mcbts = false;
config->diversity = false;
config->offset = 0.0;
config->rssi_offset = 0.0;
config->swap_channels = false;
config->edge = false;
while ((option = getopt(argc, argv, "ha:l:i:p:c:dxfo:s:b:r:A:R:Se")) != -1) {
while ((option = getopt(argc, argv, "ha:l:i:p:c:dmxfo:s:b:r:A:R:Se")) != -1) {
switch (option) {
case 'h':
print_help();
@ -361,6 +385,9 @@ static void handle_options(int argc, char **argv, struct trx_config *config)
case 'c':
config->chans = atoi(optarg);
break;
case 'm':
config->mcbts = true;
break;
case 'd':
config->diversity = true;
break;
@ -403,8 +430,8 @@ static void handle_options(int argc, char **argv, struct trx_config *config)
}
}
/* Force 4 SPS for EDGE configurations */
if (config->edge) {
/* Force 4 SPS for EDGE or multi-ARFCN configurations */
if ((config->edge) || (config->mcbts)) {
config->tx_sps = 4;
config->rx_sps = 4;
}
@ -412,7 +439,8 @@ static void handle_options(int argc, char **argv, struct trx_config *config)
if (config->edge && (config->filler == Transceiver::FILLER_NORM_RAND))
config->filler = Transceiver::FILLER_EDGE_RAND;
if ((config->tx_sps != 1) && (config->tx_sps != 4)) {
if ((config->tx_sps != 1) && (config->tx_sps != 4) &&
(config->rx_sps != 1) && (config->rx_sps != 4)) {
printf("Unsupported samples-per-symbol %i\n\n", config->tx_sps);
print_help();
exit(0);
@ -455,6 +483,9 @@ int main(int argc, char *argv[])
srandom(time(NULL));
/* Create the low level device object */
if (config.mcbts)
iface = RadioDevice::MULTI_ARFCN;
usrp = RadioDevice::make(config.tx_sps, config.rx_sps, iface,
config.chans, config.offset);
type = usrp->open(config.dev_args, config.extref, config.swap_channels);

View File

@ -23,6 +23,7 @@
#endif
#define GSMRATE (1625e3/6)
#define MCBTS_SPACING 800000.0
/** a 64-bit virtual timestamp for radio data */
typedef unsigned long long TIMESTAMP;
@ -39,6 +40,7 @@ class RadioDevice {
NORMAL,
RESAMP_64M,
RESAMP_100M,
MULTI_ARFCN,
DIVERSITY,
};

View File

@ -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
@ -101,7 +103,7 @@ 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);
@ -164,6 +166,34 @@ 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* radio, size_t tx_sps,
size_t rx_sps, 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,

View File

@ -0,0 +1,387 @@
/*
* Multi-carrier radio interface
*
* Copyright (C) 2016 Ettus Research LLC
*
* Author: Tom Tsou <tom.tsou@ettus.com>
*
* 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_INRATE 65
#define RESAMP_OUTRATE (96 / 2)
/* Universal resampling parameters */
#define NUMCHUNKS 24
#define MCHANS 4
RadioInterfaceMulti::RadioInterfaceMulti(RadioDevice *radio, size_t tx_sps,
size_t rx_sps, size_t chans)
: RadioInterface(radio, tx_sps, rx_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 2:
if (pchan == 0)
return 0;
if (pchan == 3)
return 1;
else
return -1;
break;
case 3:
if (pchan == 1)
return 0;
if (pchan == 0)
return 1;
if (pchan == 3)
return 2;
else
return -1;
break;
default:
break;
};
return -1;
}
static int getFreqShift(size_t chans)
{
switch (chans) {
case 1:
return 0;
case 2:
return 0;
case 3:
return 1;
default:
break;
};
return -1;
}
/* Initialize I/O specific objects */
bool RadioInterfaceMulti::init(int type)
{
float cutoff = 1.0f;
size_t inchunk = 0, outchunk = 0;
if (mChans > MCHANS - 1) {
LOG(ALERT) << "Invalid 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, false);
inchunk = RESAMP_INRATE * 4;
outchunk = RESAMP_OUTRATE * 4;
if (inchunk * NUMCHUNKS < 625 * 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);
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,
upsampler->len(), true);
recvBuffer[i] = new RadioBuffer(NUMCHUNKS, inchunk,
0, false);
history[i] = new signalVector(dnsampler->len());
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 2:
active[0] = true;
active[3] = true;
break;
case 3:
active[0] = true;
active[1] = true;
active[3] = 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 pchan = 0; pchan < MCHANS; pchan++) {
if (!active[pchan])
continue;
int lchan = getLogicalChan(pchan, mChans);
if (lchan < 0) {
LOG(ALERT) << "Invalid logical channel " << pchan;
continue;
}
/*
* Update history by writing into the head portion of the
* channelizer output buffer. For this to work, filter length of
* the polyphase channelizer partition filter should be equal to
* or larger than the resampling filter.
*/
buf = channelizer->outputBuffer(pchan);
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);
float *wr_segment = recvBuffer[lchan]->getWriteSegment();
/* Write to the end of the inner receive buffer */
if (!dnsampler->rotate(channelizer->outputBuffer(pchan),
channelizer->outputLen(),
wr_segment,
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 pchan = 0; pchan < MCHANS; pchan++) {
if (!active[pchan]) {
synthesis->resetBuffer(pchan);
continue;
}
int lchan = getLogicalChan(pchan, mChans);
if (lchan < 0) {
LOG(ALERT) << "Invalid logical channel " << pchan;
continue;
}
if (!upsampler->rotate(sendBuffer[lchan]->getReadSegment(),
sendBuffer[lchan]->getSegmentLen(),
synthesis->inputBuffer(pchan),
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 * MCBTS_SPACING);
double center = mRadio->getTxFreq();
if (!fltcmp(freq, center + (double) (chan - shift) * MCBTS_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 * MCBTS_SPACING);
double center = mRadio->getRxFreq();
if (!fltcmp(freq, center + (double) (chan - shift) * MCBTS_SPACING))
return false;
return true;
}
double RadioInterfaceMulti::setRxGain(double db, size_t chan)
{
if (!chan)
return mRadio->setRxGain(db);
else
return mRadio->getRxGain();
}

View File

@ -80,7 +80,6 @@ static Complex<float> psk8_table[8] = {
#define DOWNSAMPLE_OUT_LEN 156
static Resampler *dnsampler = NULL;
static signalVector *dnsampler_in = NULL;
/*
* RACH and midamble correlation waveforms. Store the buffer separately
@ -163,7 +162,6 @@ void sigProcLibDestroy()
delete GSMPulse1;
delete GSMPulse4;
delete dnsampler;
delete dnsampler_in;
GMSKRotation1 = NULL;
GMSKRotation4 = NULL;
@ -1936,13 +1934,15 @@ int detectEdgeBurst(signalVector &rxBurst, unsigned tsc, float thresh,
signalVector *downsampleBurst(signalVector &burst)
{
size_t ilen = DOWNSAMPLE_IN_LEN, olen = DOWNSAMPLE_OUT_LEN;
signalVector *in, *out;
signalVector *out = new signalVector(olen);
memcpy(dnsampler_in->begin(), burst.begin(), ilen * 2 * sizeof(float));
in = new signalVector(DOWNSAMPLE_IN_LEN, dnsampler->len());
out = new signalVector(DOWNSAMPLE_OUT_LEN);
memcpy(in->begin(), burst.begin(), DOWNSAMPLE_IN_LEN * 2 * sizeof(float));
dnsampler->rotate((float *) dnsampler_in->begin(), ilen,
(float *) out->begin(), olen);
dnsampler->rotate((float *) in->begin(), DOWNSAMPLE_IN_LEN,
(float *) out->begin(), DOWNSAMPLE_OUT_LEN);
delete in;
return out;
};
@ -2128,8 +2128,6 @@ bool sigProcLibSetup()
goto fail;
}
dnsampler_in = new signalVector(DOWNSAMPLE_IN_LEN, dnsampler->len());
return true;
fail: