From 4fcc2bd3520c6866e89937f5544676f89ccb3999 Mon Sep 17 00:00:00 2001 From: Thomas Tsou Date: Wed, 5 Oct 2011 22:11:19 -0400 Subject: [PATCH] transceiver: remove old resampling transceiver --- public-trunk/Makefile.am | 1 - public-trunk/Transceiver/Complex.h | 277 ---- public-trunk/Transceiver/Makefile.am | 88 -- public-trunk/Transceiver/README | 39 - public-trunk/Transceiver/README.Talgorithm | 15 - public-trunk/Transceiver/Transceiver.cpp | 814 ----------- public-trunk/Transceiver/Transceiver.h | 214 --- public-trunk/Transceiver/UHDDevice.cpp | 882 ------------ public-trunk/Transceiver/USRPDevice.cpp | 537 ------- public-trunk/Transceiver/USRPDevice.h | 192 --- public-trunk/Transceiver/USRPping.cpp | 90 -- public-trunk/Transceiver/radioInterface.cpp | 431 ------ public-trunk/Transceiver/radioInterface.h | 240 ---- public-trunk/Transceiver/rcvLPF_651.h | 29 - public-trunk/Transceiver/runTransceiver.cpp | 66 - public-trunk/Transceiver/sendLPF_961.h | 27 - public-trunk/Transceiver/sigProcLib.cpp | 1399 ------------------- public-trunk/Transceiver/sigProcLib.h | 384 ----- public-trunk/Transceiver/std_inband.rbf | Bin 176507 -> 0 bytes public-trunk/configure.ac | 1 - 20 files changed, 5726 deletions(-) delete mode 100644 public-trunk/Transceiver/Complex.h delete mode 100644 public-trunk/Transceiver/Makefile.am delete mode 100644 public-trunk/Transceiver/README delete mode 100644 public-trunk/Transceiver/README.Talgorithm delete mode 100644 public-trunk/Transceiver/Transceiver.cpp delete mode 100644 public-trunk/Transceiver/Transceiver.h delete mode 100644 public-trunk/Transceiver/UHDDevice.cpp delete mode 100644 public-trunk/Transceiver/USRPDevice.cpp delete mode 100644 public-trunk/Transceiver/USRPDevice.h delete mode 100644 public-trunk/Transceiver/USRPping.cpp delete mode 100644 public-trunk/Transceiver/radioInterface.cpp delete mode 100644 public-trunk/Transceiver/radioInterface.h delete mode 100644 public-trunk/Transceiver/rcvLPF_651.h delete mode 100644 public-trunk/Transceiver/runTransceiver.cpp delete mode 100644 public-trunk/Transceiver/sendLPF_961.h delete mode 100644 public-trunk/Transceiver/sigProcLib.cpp delete mode 100644 public-trunk/Transceiver/sigProcLib.h delete mode 100755 public-trunk/Transceiver/std_inband.rbf diff --git a/public-trunk/Makefile.am b/public-trunk/Makefile.am index e409d9c..c127f10 100644 --- a/public-trunk/Makefile.am +++ b/public-trunk/Makefile.am @@ -31,7 +31,6 @@ SUBDIRS = \ SIP \ GSM \ SMS \ - Transceiver \ Transceiver52M \ TRXManager \ Control \ diff --git a/public-trunk/Transceiver/Complex.h b/public-trunk/Transceiver/Complex.h deleted file mode 100644 index d3d3350..0000000 --- a/public-trunk/Transceiver/Complex.h +++ /dev/null @@ -1,277 +0,0 @@ -/**@file templates for Complex classes -unlike the built-in complex<> templates, these inline most operations for speed -*/ - -/* -* Copyright 2008 Free Software Foundation, Inc. -* -* This software is distributed under the terms of the GNU Affero Public License. -* See the COPYING file in the main directory for details. -* -* This use of this software may be subject to additional restrictions. -* See the LEGAL file in the main directory for details. - - 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 . - -*/ - - - - -#ifndef COMPLEXCPP_H -#define COMPLEXCPP_H - -#include -#include - - -template class Complex { - -public: - - Real r, i; - - /**@name constructors */ - //@{ - /**@name from real */ - //@{ - Complex(Real real, Real imag) {r=real; i=imag;} // x=complex(a,b) - Complex(Real real) {r=real; i=0;} // x=complex(a) - //@} - /**@name from nothing */ - //@{ - Complex() {r=(Real)0; i=(Real)0;} // x=complex() - //@} - /**@name from other complex */ - //@{ - Complex(const Complex& z) {r=z.r; i=z.i;} // x=complex(z) - Complex(const Complex& z) {r=z.r; i=z.i;} // x=complex(z) - Complex(const Complex& z) {r=z.r; i=z.i;} // x=complex(z) - //@} - //@} - - /**@name casting up from basic numeric types */ - //@{ - Complex& operator=(char a) { r=(Real)a; i=(Real)0; return *this; } - Complex& operator=(int a) { r=(Real)a; i=(Real)0; return *this; } - Complex& operator=(long int a) { r=(Real)a; i=(Real)0; return *this; } - Complex& operator=(short a) { r=(Real)a; i=(Real)0; return *this; } - Complex& operator=(float a) { r=(Real)a; i=(Real)0; return *this; } - Complex& operator=(double a) { r=(Real)a; i=(Real)0; return *this; } - Complex& operator=(long double a) { r=(Real)a; i=(Real)0; return *this; } - //@} - - /**@name arithmetic */ - //@{ - /**@ binary operators */ - //@{ - Complex operator+(const Complex& a) const { return Complex(r+a.r, i+a.i); } - Complex operator+(Real a) const { return Complex(r+a,i); } - Complex operator-(const Complex& a) const { return Complex(r-a.r, i-a.i); } - Complex operator-(Real a) const { return Complex(r-a,i); } - Complex operator*(const Complex& a) const { return Complex(r*a.r-i*a.i, r*a.i+i*a.r); } - Complex operator*(Real a) const { return Complex(r*a, i*a); } - Complex operator/(const Complex& a) const { return operator*(a.inv()); } - Complex operator/(Real a) const { return Complex(r/a, i/a); } - //@} - /*@name component-wise product */ - //@{ - Complex operator&(const Complex& a) const { return Complex(r*a.r, i*a.i); } - //@} - /*@name inplace operations */ - //@{ - Complex& operator+=(const Complex&); - Complex& operator-=(const Complex&); - Complex& operator*=(const Complex&); - Complex& operator/=(const Complex&); - Complex& operator+=(Real); - Complex& operator-=(Real); - Complex& operator*=(Real); - Complex& operator/=(Real); - //@} - //@} - - /**@name comparisons */ - //@{ - bool operator==(const Complex& a) const { return ((i==a.i)&&(r==a.r)); } - bool operator!=(const Complex& a) const { return ((i!=a.i)||(r!=a.r)); } - bool operator<(const Complex& a) const { return norm2()(const Complex& a) const { return norm2()>a.norm2(); } - //@} - - /// reciprocation - Complex inv() const; - - // unary functions -- inlined - /**@name unary functions */ - //@{ - /**@name inlined */ - //@{ - Complex conj() const { return Complex(r,-i); } - Real norm2() const { return i*i+r*r; } - Complex flip() const { return Complex(i,r); } - Real real() const { return r;} - Real imag() const { return i;} - Complex neg() const { return Complex(-r, -i); } - bool isZero() const { return ((r==(Real)0) && (i==(Real)0)); } - //@} - /**@name not inlined due to outside calls */ - //@{ - Real abs() const { return ::sqrt(norm2()); } - Real arg() const { return ::atan2(i,r); } - float dB() const { return 10.0*log10(norm2()); } - Complex exp() const { return expj(i)*(::exp(r)); } - Complex unit() const; ///< unit phasor with same angle - Complex log() const { return Complex(::log(abs()),arg()); } - Complex pow(double n) const { return expj(arg()*n)*(::pow(abs(),n)); } - Complex sqrt() const { return pow(0.5); } - //@} - //@} - -}; - - -/**@name standard Complex manifestations */ -//@{ -typedef Complex complex; -typedef Complex dcomplex; -typedef Complex complex16; -typedef Complex complex32; -//@} - - -template inline Complex Complex::inv() const -{ - Real nVal; - - nVal = norm2(); - return Complex(r/nVal, -i/nVal); -} - -template Complex& Complex::operator+=(const Complex& a) -{ - r += a.r; - i += a.i; - return *this; -} - -template Complex& Complex::operator*=(const Complex& a) -{ - operator*(a); - return *this; -} - -template Complex& Complex::operator-=(const Complex& a) -{ - r -= a.r; - i -= a.i; - return *this; -} - -template Complex& Complex::operator/=(const Complex& a) -{ - operator/(a); - return *this; -} - - -/* op= style operations with reals */ - -template Complex& Complex::operator+=(Real a) -{ - r += a; - return *this; -} - -template Complex& Complex::operator*=(Real a) -{ - r *=a; - i *=a; - return *this; -} - -template Complex& Complex::operator-=(Real a) -{ - r -= a; - return *this; -} - -template Complex& Complex::operator/=(Real a) -{ - r /= a; - i /= a; - return *this; -} - - -template Complex Complex::unit() const -{ - Real absVal = abs(); - return (Complex(r/absVal, i/absVal)); -} - - - -/**@name complex functions outside of the Complex<> class. */ -//@{ - -/** this allows type-commutative multiplication */ -template Complex operator*(Real a, const Complex& z) -{ - return Complex(z.r*a, z.i*a); -} - - -/** this allows type-commutative addition */ -template Complex operator+(Real a, const Complex& z) -{ - return Complex(z.r+a, z.i); -} - - -/** this allows type-commutative subtraction */ -template Complex operator-(Real a, const Complex& z) -{ - return Complex(z.r-a, z.i); -} - - - -/// e^jphi -template Complex expj(Real phi) -{ - return Complex(cos(phi),sin(phi)); -} - -/// phasor expression of a complex number -template Complex phasor(Real C, Real phi) -{ - return (expj(phi)*C); -} - -/// formatted stream output -template std::ostream& operator<<(std::ostream& os, const Complex& z) -{ - os << z.r << ' '; - //os << z.r << ", "; - //if (z.i>=0) { os << "+"; } - os << z.i << "j"; - os << "\n"; - return os; -} - -//@} - - -#endif diff --git a/public-trunk/Transceiver/Makefile.am b/public-trunk/Transceiver/Makefile.am deleted file mode 100644 index 046098d..0000000 --- a/public-trunk/Transceiver/Makefile.am +++ /dev/null @@ -1,88 +0,0 @@ -# -# Copyright 2008 Free Software Foundation, Inc. -# -# This software is distributed under the terms of the GNU Affero Public License. -# See the COPYING file in the main directory for details. -# -# 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 . -# - -include $(top_srcdir)/Makefile.common - -if UHD -AM_CPPFLAGS = $(STD_DEFINES_AND_INCLUDES) $(UHD_CFLAGS) -else -AM_CPPFLAGS = $(STD_DEFINES_AND_INCLUDES) $(USRP_CFLAGS) -endif -AM_CXXFLAGS = -Wall -O3 -g -pthread - -rev2dir = $(datadir)/usrp/rev2 -rev4dir = $(datadir)/usrp/rev4 - -dist_rev2_DATA = std_inband.rbf -dist_rev4_DATA = std_inband.rbf - -EXTRA_DIST = \ - README \ - README.Talgorithm - -noinst_LTLIBRARIES = libtransceiver.la - -libtransceiver_la_SOURCES = \ - radioInterface.cpp \ - sigProcLib.cpp \ - Transceiver.cpp - -noinst_PROGRAMS = \ - USRPping \ - transceiver - -noinst_HEADERS = \ - Complex.h \ - radioInterface.h \ - rcvLPF_651.h \ - sendLPF_961.h \ - sigProcLib.h \ - Transceiver.h \ - USRPDevice.h - -USRPping_SOURCES = USRPping.cpp -USRPping_LDADD = \ - libtransceiver.la \ - $(COMMON_LA) - -transceiver_SOURCES = runTransceiver.cpp -transceiver_LDADD = \ - libtransceiver.la \ - $(GSM_LA) \ - $(COMMON_LA) - -if UHD -libtransceiver_la_SOURCES += UHDDevice.cpp -transceiver_LDADD += $(UHD_LIBS) -USRPping_LDADD += $(UHD_LIBS) -else -libtransceiver_la_SOURCES += USRPDevice.cpp -transceiver_LDADD += $(USRP_LIBS) -USRPping_LDADD += $(USRP_LIBS) -endif - -MOSTLYCLEANFILES += - -#radioInterface.cpp -#ComplexTest.cpp -#sigProcLibTest.cpp -#sweepGenerator.cpp -#testRadio.cpp - diff --git a/public-trunk/Transceiver/README b/public-trunk/Transceiver/README deleted file mode 100644 index 203afc8..0000000 --- a/public-trunk/Transceiver/README +++ /dev/null @@ -1,39 +0,0 @@ -The Transceiver - -The transceiver consists of three modules: - --- transceiver - --- radioInterface - --- USRPDevice - -The USRPDevice module is basically a driver that reads/writes -packets to a USRP with two RFX900 daughterboards, board -A is the Tx chain and board B is the Rx chain. - -The radioInterface module is basically an interface b/w the -transceiver and the USRP. It operates the basestation clock -based upon the sample count of received USRP samples. Packets -from the USRP are queued and segmented into GSM bursts that are -passed up to the transceiver; bursts from the transceiver are -passed down to the USRP. Our current implementation includes -a polyphase resampler, since there is no 64e6/N sample rate that -nicely matches a multiple of the GSM symbol rate of 1.625e6/6. -A better implementation would involve running the USRP off of a -13Mhz clock; then the resampler can be discarded altogether. - -The transceiver basically operates "layer 0" of the GSM stack, -performing the modulation, detection, and demodulation of GSM -bursts. It communicates with the GSM stack via three UDP sockets, -one socket for data, one for control messages, and one socket to -pass clocking information. The transceiver contains a priority -queue to sort to-be-transmitted bursts, and a filler table to fill -in timeslots that do not have bursts in the priority queue. The -transceiver tries to stay ahead of the basestation clock, adapting -its latency when underruns are reported by the radioInterface/USRP. -Received bursts (from the radioInterface) pass through a simple -energy detector, a RACH or midamble correlator, and a DFE-based demodulator. - -NOTE: There's a SWLOOPBACK #define statement, where the USRP is replaced -with a memory buffer. In this mode, data written to the USRP is actually stored -in a buffer, and read commands to the USRP simply pull data from this buffer. -This was very useful in early testing, and still may be useful in testing basic -Transceiver and radioInterface functionality. diff --git a/public-trunk/Transceiver/README.Talgorithm b/public-trunk/Transceiver/README.Talgorithm deleted file mode 100644 index 037d613..0000000 --- a/public-trunk/Transceiver/README.Talgorithm +++ /dev/null @@ -1,15 +0,0 @@ -Basic model: - -Have channel H = {h_0, h_1, ..., h_{K-1}}. -Have received sequence Y = {y_0, ..., y_{K+N}}. -Have transmitted sequence X = {x_0, ..., x_{N-1}}. -Denote state S_n = {x_n, x_{n-1}, ..., x_{n-L}}. - -Define a bag as an unordered collection with two operations, add and take. -We have three bags: - S: a bag of survivors. - F: a bag of available data structures. - -At time n, start with a non-empty bag S of survivors from time n-1. -Take a member out of S, and create all possible branches and their corresponding metrics. If metric ratio is above T, discard branch. Otherwise, check branch against entry in pruning table P. If branch metric is smaller than the existing entry's metric in P, then replace entry with branch. Otherwise, discard branch. -Once all possible branches of S have been created and pruned, S should be empty.Empty pruning table back into S, thus P is now empty. Repeat. diff --git a/public-trunk/Transceiver/Transceiver.cpp b/public-trunk/Transceiver/Transceiver.cpp deleted file mode 100644 index cef8b3d..0000000 --- a/public-trunk/Transceiver/Transceiver.cpp +++ /dev/null @@ -1,814 +0,0 @@ -/* -* Copyright 2008, 2009 Free Software Foundation, Inc. -* -* This software is distributed under the terms of the GNU Affero Public License. -* See the COPYING file in the main directory for details. -* -* This use of this software may be subject to additional restrictions. -* See the LEGAL file in the main directory for details. - - 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 . - -*/ - - -/* - Compilation switches - TRANSMIT_LOGGING write every burst on the given slot to a log -*/ - - -#include "config.h" -#include "Transceiver.h" -#include -#include - - -Transceiver::Transceiver(int wBasePort, - const char *TRXAddress, - int wSamplesPerSymbol, - GSM::Time wTransmitLatency, - RadioInterface *wRadioInterface) - :mDataSocket(wBasePort+2,TRXAddress,wBasePort+102), - mControlSocket(wBasePort+1,TRXAddress,wBasePort+101), - mClockSocket(wBasePort,TRXAddress,wBasePort+100) -{ - //GSM::Time startTime(0,0); - //GSM::Time startTime(gHyperframe/2 - 4*216*60,0); - GSM::Time startTime(random() % gHyperframe,0); - - mSamplesPerSymbol = wSamplesPerSymbol; - mRadioInterface = wRadioInterface; - mTransmitLatency = wTransmitLatency; - mTransmitDeadlineClock = startTime; - mLastClockUpdateTime = startTime; - mLatencyUpdateTime = startTime; - mRadioInterface->getClock()->set(startTime); - - // generate pulse and setup up signal processing library - gsmPulse = generateGSMPulse(2,mSamplesPerSymbol); - LOG(DEBUG) << "gsmPulse: " << *gsmPulse; - sigProcLibSetup(mSamplesPerSymbol); - - txFullScale = mRadioInterface->fullScaleInputValue(); - rxFullScale = mRadioInterface->fullScaleOutputValue(); - - // initialize filler tables with dummy bursts, initialize other per-timeslot variables - for (int i = 0; i < 8; i++) { - signalVector* modBurst = modulateBurst(gDummyBurst,*gsmPulse, - 8 + (i % 4 == 0), - mSamplesPerSymbol); -//if (i!=0) scaleVector(*modBurst,0.0001); - fillerModulus[i]=26; - for (int j = 0; j < 102; j++) { - fillerTable[j][i] = new signalVector(*modBurst); - } - delete modBurst; - mChanType[i] = NONE; - channelResponse[i] = NULL; - DFEForward[i] = NULL; - DFEFeedback[i] = NULL; - channelEstimateTime[i] = startTime; - } - - mOn = false; - mTxFreq = 0.0; - mRxFreq = 0.0; - mPower = -10; - mEnergyThreshold = 250.0; // based on empirical data - prevFalseDetectionTime = startTime; -} - -Transceiver::~Transceiver() -{ - delete gsmPulse; - sigProcLibDestroy(); - mTransmitPriorityQueue.clear(); -} - - -void Transceiver::addRadioVector(BitVector &burst, - int RSSI, - GSM::Time &wTime) -{ - // modulate and stick into queue - signalVector* modBurst = modulateBurst(burst,*gsmPulse, - 8 + (wTime.TN() % 4 == 0), - mSamplesPerSymbol); - scaleVector(*modBurst,pow(10,-RSSI/10)); - radioVector *newVec = new radioVector(*modBurst,wTime); - mTransmitPriorityQueue.write(newVec); - - delete modBurst; -} - -#ifdef TRANSMIT_LOGGING -void Transceiver::unModulateVector(signalVector wVector) -{ - SoftVector *burst = demodulateBurst(wVector, - *gsmPulse, - mSamplesPerSymbol, - 1.0,0.0); - LOG(DEEPDEBUG) << "LOGGED BURST: " << *burst; - -/* - unsigned char burstStr[gSlotLen+1]; - SoftVector::iterator burstItr = burst->begin(); - for (int i = 0; i < gSlotLen; i++) { - // FIXME: Demod bits are inverted! - burstStr[i] = (unsigned char) ((*burstItr++)*255.0); - } - burstStr[gSlotLen]='\0'; - LOG(DEEPDEBUG) << "LOGGED BURST: " << burstStr; -*/ - delete burst; -} -#endif - -void Transceiver::pushRadioVector(GSM::Time &nowTime) -{ - - // dump stale bursts, if any - while ((mTransmitPriorityQueue.size() > 0) && - (mTransmitPriorityQueue.nextTime() < nowTime)) { - // Even if the burst is stale, put it in the fillter table. - // (It might be an idle pattern.) - LOG(NOTICE) << "dumping STALE burst in TRX->USRP interface"; - const GSM::Time& nextTime = mTransmitPriorityQueue.nextTime(); - int TN = nextTime.TN(); - int modFN = nextTime.FN() % fillerModulus[TN]; - delete fillerTable[modFN][TN]; - fillerTable[modFN][TN] = mTransmitPriorityQueue.readNoBlock(); - } - - int TN = nowTime.TN(); - int modFN = nowTime.FN() % fillerModulus[nowTime.TN()]; - - // if queue contains data at the desired timestamp, stick it into FIFO - if ((mTransmitPriorityQueue.size() > 0) && - (mTransmitPriorityQueue.nextTime() == nowTime)) { - radioVector *next = mTransmitPriorityQueue.readNoBlock(); - LOG(DEEPDEBUG) << "transmitFIFO: wrote burst " << next << " at time: " << nowTime; - delete fillerTable[modFN][TN]; - fillerTable[modFN][TN] = new signalVector(*(next)); - mTransmitFIFO->write(next); -#ifdef TRANSMIT_LOGGING - if (nowTime.TN()==TRANSMIT_LOGGING) { - unModulateVector(*(fillerTable[modFN][TN])); - } -#endif - return; - } - - // otherwise, pull filler data, and push to radio FIFO - radioVector *tmpVec = new radioVector(*fillerTable[modFN][TN],nowTime); - mTransmitFIFO->write(tmpVec); -#ifdef TRANSMIT_LOGGING - if (nowTime.TN()==TRANSMIT_LOGGING) - unModulateVector(*fillerTable[modFN][TN]); -#endif - -} - -void Transceiver::setModulus(int timeslot) -{ - switch (mChanType[timeslot]) { - case NONE: - case I: - case II: - case III: - fillerModulus[timeslot] = 26; - break; - case IV: - case VI: - case V: - fillerModulus[timeslot] = 51; - break; - //case V: - case VII: - fillerModulus[timeslot] = 102; - break; - default: - break; - } -} - - -Transceiver::CorrType Transceiver::expectedCorrType(GSM::Time currTime) -{ - - unsigned burstTN = currTime.TN(); - unsigned burstFN = currTime.FN(); - - switch (mChanType[burstTN]) { - case NONE: - return OFF; - break; - case I: - return TSC; - /*if (burstFN % 26 == 25) - return IDLE; - else - return TSC;*/ - break; - case II: - if (burstFN % 2 == 1) - return IDLE; - else - return TSC; - break; - case III: - return TSC; - break; - case IV: - case VI: - if ((burstFN % 51) % 10 < 2) - return RACH; - else - return OFF; - break; - case V: { - int mod51 = burstFN % 51; - if ((mod51 <= 36) && (mod51 >= 14)) - return RACH; - else if ((mod51 == 4) || (mod51 == 5)) - return RACH; - else if ((mod51 == 45) || (mod51 == 46)) - return RACH; - else - return TSC; - break; - } - case VII: - if (burstFN%51 == 12) return IDLE; - if (burstFN%51 == 13) return IDLE; - if (burstFN%51 == 14) return IDLE; - return TSC; - break; - case LOOPBACK: - if ((burstFN % 51 <= 50) && (burstFN % 51 >=48)) - return IDLE; - else - return TSC; - break; - default: - return OFF; - break; - } - -} - -SoftVector *Transceiver::pullRadioVector(GSM::Time &wTime, - int &RSSI, - int &timingOffset) -{ - radioVector *rxBurst = NULL; - rxBurst = mReceiveFIFO->read(); - if (!rxBurst) return NULL; - - LOG(DEEPDEBUG) << "receiveFIFO: read radio vector at time: " << rxBurst->time() << ", new size: " << mReceiveFIFO->size(); - - // receive chain is offset from transmit chain - rxBurst->time(rxBurst->time()); - - int timeslot = rxBurst->time().TN(); - - CorrType corrType = expectedCorrType(rxBurst->time()); - - if ((corrType==OFF) || (corrType==IDLE)) { - delete rxBurst; - return NULL; - } - - // check to see if received burst has sufficient energy - signalVector *vectorBurst = rxBurst; - complex amplitude = 0.0; - float TOA = 0.0; - float avgPwr = 0.0; - if (!energyDetect(*vectorBurst,20*mSamplesPerSymbol,mEnergyThreshold,&avgPwr)) { - LOG(DEEPDEBUG) << "Estimated Energy: " << sqrt(avgPwr) << ", at time " << rxBurst->time(); - double framesElapsed = rxBurst->time()-prevFalseDetectionTime; - if (framesElapsed > 50) { // if we haven't had any false detections for a while, lower threshold - mEnergyThreshold -= 10.0; - if (mEnergyThreshold < 0.0) - mEnergyThreshold = 0.0; - - prevFalseDetectionTime = rxBurst->time(); - } - delete rxBurst; - return NULL; - } - LOG(DEEPDEBUG) << "Estimated Energy: " << sqrt(avgPwr) << ", at time " << rxBurst->time(); - - // run the proper correlator - bool success = false; - if (corrType==TSC) { - LOG(DEEPDEBUG) << "looking for TSC at time: " << rxBurst->time(); - signalVector *channelResp; - double framesElapsed = rxBurst->time()-channelEstimateTime[timeslot]; - bool estimateChannel = false; - if ((framesElapsed > 50) || (channelResponse[timeslot]==NULL)) { - if (channelResponse[timeslot]) delete channelResponse[timeslot]; - if (DFEForward[timeslot]) delete DFEForward[timeslot]; - if (DFEFeedback[timeslot]) delete DFEFeedback[timeslot]; - channelResponse[timeslot] = NULL; - DFEForward[timeslot] = NULL; - DFEFeedback[timeslot] = NULL; - estimateChannel = true; - } - float chanOffset; - success = analyzeTrafficBurst(*vectorBurst, - mTSC, - 3.0, - mSamplesPerSymbol, - &litude, - &TOA, //); - estimateChannel, - &channelResp, - &chanOffset); - if (success) { - LOG(DEBUG) << "FOUND TSC!!!!!! " << amplitude << " " << TOA; - mEnergyThreshold -= 1.0F; - if (mEnergyThreshold < 0.0) mEnergyThreshold = 0.0; - SNRestimate[timeslot] = amplitude.norm2()/(mEnergyThreshold*mEnergyThreshold+1.0); // this is not highly accurate - if (estimateChannel) { - LOG(DEBUG) << "estimating channel..."; - channelResponse[timeslot] = channelResp; - chanRespOffset[timeslot] = chanOffset; - chanRespAmplitude[timeslot] = amplitude; - scaleVector(*channelResp, complex(1.0,0.0)/amplitude); - designDFE(*channelResp, SNRestimate[timeslot], 7, &DFEForward[timeslot], &DFEFeedback[timeslot]); - channelEstimateTime[timeslot] = rxBurst->time(); - LOG(DEBUG) << "SNR: " << SNRestimate[timeslot] << ", DFE forward: " << *DFEForward[timeslot] << ", DFE backward: " << *DFEFeedback[timeslot]; - } - } - else { - double framesElapsed = rxBurst->time()-prevFalseDetectionTime; - LOG(DEEPDEBUG) << "wTime: " << rxBurst->time() << ", pTime: " << prevFalseDetectionTime << ", fElapsed: " << framesElapsed; - mEnergyThreshold += 10.0F*exp(-framesElapsed); - prevFalseDetectionTime = rxBurst->time(); - channelResponse[timeslot] = NULL; - } - } - else { - // RACH burst - success = detectRACHBurst(*vectorBurst, - 5.0, // detection threshold - mSamplesPerSymbol, - &litude, - &TOA); - if (success) { - LOG(DEBUG) << "FOUND RACH!!!!!! " << amplitude << " " << TOA; - mEnergyThreshold -= 1.0F; - if (mEnergyThreshold < 0.0) mEnergyThreshold = 0.0; - channelResponse[timeslot] = NULL; - } - else { - double framesElapsed = rxBurst->time()-prevFalseDetectionTime; - mEnergyThreshold += 10.0F*exp(-framesElapsed); - prevFalseDetectionTime = rxBurst->time(); - } - } - LOG(DEBUG) << "energy Threshold = " << mEnergyThreshold; - - // demodulate burst - SoftVector *burst = NULL; - if ((rxBurst) && (success)) { - if (corrType==RACH) { - burst = demodulateBurst(*vectorBurst, - *gsmPulse, - mSamplesPerSymbol, - amplitude,TOA); - } - else { // TSC - scaleVector(*vectorBurst,complex(1.0,0.0)/amplitude); - burst = equalizeBurst(*vectorBurst, - TOA-chanRespOffset[timeslot], - mSamplesPerSymbol, - *DFEForward[timeslot], - *DFEFeedback[timeslot]); - } - wTime = rxBurst->time(); - RSSI = (int) floor(20.0*log10(rxFullScale/amplitude.abs())); - LOG(DEBUG) << "RSSI: " << RSSI; - timingOffset = (int) round(TOA*256.0/mSamplesPerSymbol); - } - - //if (burst) LOG(DEEPDEBUG) << "burst: " << *burst << '\n'; - - delete rxBurst; - - return burst; -} - -void Transceiver::start() -{ - - mReceiveFIFOServiceLoopThread.start((void * (*)(void*))ReceiveFIFOServiceLoopAdapter,(void*) this); - mTransmitFIFOServiceLoopThread.start((void * (*)(void*))TransmitFIFOServiceLoopAdapter,(void*) this); - mControlServiceLoopThread.start((void * (*)(void*))ControlServiceLoopAdapter,(void*) this); - mTransmitPriorityQueueServiceLoopThread.start((void * (*)(void*))TransmitPriorityQueueServiceLoopAdapter,(void*) this); - - mLock.unlock(); - - writeClockInterface(); - - generateRACHSequence(*gsmPulse,mSamplesPerSymbol); - -} - - -void Transceiver::reset() -{ - mLock.lock(); - mTransmitPriorityQueue.clear(); - mTransmitFIFO->clear(); - mReceiveFIFO->clear(); - mLock.unlock(); -} - - -void Transceiver::driveControl() -{ - mLock.lock(); - - int MAX_PACKET_LENGTH = 100; - - // check control socket - char buffer[MAX_PACKET_LENGTH]; - int msgLen = -1; - buffer[0] = '\0'; - - msgLen = mControlSocket.read(buffer); - - if (msgLen < 1) { - mLock.unlock(); - return; - } - - char cmdcheck[4]; - char command[MAX_PACKET_LENGTH]; - char response[MAX_PACKET_LENGTH]; - - sscanf(buffer,"%3s %s",cmdcheck,command); - - writeClockInterface(); - - if (strcmp(cmdcheck,"CMD")!=0) { - LOG(WARN) << "bogus message on control interface"; - mLock.unlock(); - return; - } - LOG(INFO) << "command is " << buffer; - - if (strcmp(command,"POWEROFF")==0) { - // turn off transmitter/demod - sprintf(response,"RSP POWEROFF 0"); - } - else if (strcmp(command,"POWERON")==0) { - // turn on transmitter/demod - if (!mTxFreq || !mRxFreq) - sprintf(response,"RSP POWERON 1"); - else { - sprintf(response,"RSP POWERON 0"); - if (!mOn) { - mPower = -20; - mRadioInterface->start(); - mOn = true; - } - } - } - 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); - sprintf(response,"RSP SETRXGAIN 0 %d",newGain); - } - else if (strcmp(command,"NOISELEV")==0) { - if (mOn) { - sprintf(response,"RSP NOISELEV 0 %d", - (int) round(20.0*log10(rxFullScale/mEnergyThreshold))); - } - else { - sprintf(response,"RSP NOISELEV 1 0"); - } - } - else if (strcmp(command,"SETPOWER")==0) { - // set output power in dB - int dbPwr; - sscanf(buffer,"%3s %s %d",cmdcheck,command,&dbPwr); - if (!mOn) - sprintf(response,"RSP SETPOWER 1 %d",dbPwr); - else { - mPower = dbPwr; - mRadioInterface->setPowerAttenuation(dbPwr); - sprintf(response,"RSP SETPOWER 0 %d",dbPwr); - } - } - else if (strcmp(command,"ADJPOWER")==0) { - // adjust power in dB steps - int dbStep; - sscanf(buffer,"%3s %s %d",cmdcheck,command,&dbStep); - if (!mOn) - sprintf(response,"RSP ADJPOWER 1 %d",mPower); - else { - mPower += dbStep; - sprintf(response,"RSP ADJPOWER 0 %d",mPower); - } - } -#define FREQOFFSET 0//11.2e3 - else if (strcmp(command,"RXTUNE")==0) { - // tune receiver - int freqKhz; - sscanf(buffer,"%3s %s %d",cmdcheck,command,&freqKhz); - if (mOn) - sprintf(response,"RSP RXTUNE 1 %d",freqKhz); - else { - mRxFreq = freqKhz*1.0e3+FREQOFFSET; - if (!mRadioInterface->tuneRx(mRxFreq)) { - LOG(ALARM) << "RX failed to tune"; - sprintf(response,"RSP RXTUNE 1 %d",freqKhz); - } - else - sprintf(response,"RSP RXTUNE 0 %d",freqKhz); - } - } - else if (strcmp(command,"TXTUNE")==0) { - // tune txmtr - int freqKhz; - sscanf(buffer,"%3s %s %d",cmdcheck,command,&freqKhz); - if (mOn) - sprintf(response,"RSP TXTUNE 1 %d",freqKhz); - else { - //freqKhz = 890e3; - mTxFreq = freqKhz*1.0e3+FREQOFFSET; - if (!mRadioInterface->tuneTx(mTxFreq)) { - LOG(ALARM) << "TX failed to tune"; - sprintf(response,"RSP TXTUNE 1 %d",freqKhz); - } - else - sprintf(response,"RSP TXTUNE 0 %d",freqKhz); - } - } - else if (strcmp(command,"SETTSC")==0) { - // set TSC - int TSC; - sscanf(buffer,"%3s %s %d",cmdcheck,command,&TSC); - if (mOn) - sprintf(response,"RSP SETTSC 1 %d",TSC); - else { - mTSC = TSC; - generateMidamble(*gsmPulse,mSamplesPerSymbol,TSC); - sprintf(response,"RSP SETTSC 0 %d",TSC); - } - } - else if (strcmp(command,"SETSLOT")==0) { - // set TSC - int corrCode; - int timeslot; - sscanf(buffer,"%3s %s %d %d",cmdcheck,command,×lot,&corrCode); - if ((timeslot < 0) || (timeslot > 7)) { - LOG(WARN) << "bogus message on control interface"; - sprintf(response,"RSP SETSLOT 1 %d %d",timeslot,corrCode); - mLock.unlock(); - return; - } - mChanType[timeslot] = (ChannelCombination) corrCode; - setModulus(timeslot); - sprintf(response,"RSP SETSLOT 0 %d %d",timeslot,corrCode); - - } - else { - LOG(WARN) << "bogus command " << command << " on control interface."; - } - - mControlSocket.write(response,strlen(response)+1); - - mLock.unlock(); -} - -bool Transceiver::driveTransmitPriorityQueue() -{ - - char buffer[gSlotLen+50]; - - // check data socket - size_t msgLen = mDataSocket.read(buffer); - - if (msgLen!=gSlotLen+1+4+1) { - LOG(ERROR) << "badly formatted packet on GSM->TRX interface"; - return false; - } - - int timeSlot = (int) buffer[0]; - uint64_t frameNum = 0; - for (int i = 0; i < 4; i++) - frameNum = (frameNum << 8) | (0x0ff & buffer[i+1]); - - /* - if (GSM::Time(frameNum,timeSlot) > mTransmitDeadlineClock + GSM::Time(51,0)) { - // stale burst - //LOG(DEBUG) << "FAST! "<< GSM::Time(frameNum,timeSlot); - //writeClockInterface(); - }*/ - -/* - DAB -- Just let these go through the demod. - if (GSM::Time(frameNum,timeSlot) < mTransmitDeadlineClock) { - // stale burst from GSM core - LOG(NOTICE) << "STALE packet on GSM->TRX interface at time "<< GSM::Time(frameNum,timeSlot); - return false; - } -*/ - - // periodically update GSM core clock - if (mTransmitDeadlineClock > mLastClockUpdateTime + GSM::Time(216,0)) - writeClockInterface(); - - - LOG(DEEPDEBUG) << "rcvd. burst at: " << GSM::Time(frameNum,timeSlot); - - int RSSI = (int) buffer[5]; - BitVector newBurst(gSlotLen); - BitVector::iterator itr = newBurst.begin(); - char *bufferItr = buffer+6; - while (itr < newBurst.end()) - *itr++ = *bufferItr++; - - GSM::Time currTime = GSM::Time(frameNum,timeSlot); - - addRadioVector(newBurst,RSSI,currTime); - - LOG(DEEPDEBUG) "added burst - time: " << currTime << ", RSSI: " << RSSI; // << ", data: " << newBurst; - - return true; - - -} - -void Transceiver::driveReceiveFIFO() -{ - - SoftVector *rxBurst = NULL; - int RSSI; - int TOA; // in 1/256 of a symbol - GSM::Time burstTime; - - rxBurst = pullRadioVector(burstTime,RSSI,TOA); - - if (rxBurst) { - - /*LOG(DEEPDEBUG) << ("burst parameters: " - << " time: " << burstTime - << " RSSI: " << RSSI - << " TOA: " << TOA - << " bits: " << *rxBurst; - */ - char burstString[gSlotLen+10]; - burstString[0] = burstTime.TN(); - for (int i = 0; i < 4; i++) - burstString[1+i] = (burstTime.FN() >> ((3-i)*8)) & 0x0ff; - burstString[5] = RSSI; - burstString[6] = (TOA >> 8) & 0x0ff; - burstString[7] = TOA & 0x0ff; - SoftVector::iterator burstItr = rxBurst->begin(); - - for (unsigned int i = 0; i < gSlotLen; i++) { - burstString[8+i] =(char) round((*burstItr++)*255.0); - } - burstString[gSlotLen+9] = '\0'; - delete rxBurst; - - mDataSocket.write(burstString,gSlotLen+10); - } - -} - -void Transceiver::driveTransmitFIFO() -{ - - /** - Features a carefully controlled latency mechanism, to - assure that transmit packets arrive at the radio/USRP - before they need to be transmitted. - - Deadline clock indicates the burst that needs to be - pushed into the FIFO right NOW. If transmit queue does - not have a burst, stick in filler data. - */ - - - RadioClock *radioClock = (mRadioInterface->getClock()); - - if (mOn) { - radioClock->wait(); // wait until clock updates - while (radioClock->get() + mTransmitLatency > - mTransmitDeadlineClock) { -#ifndef USE_UHD - // if underrun, then we're not providing bursts to radio/USRP fast - // enough. Need to increase latency by one GSM frame. - if (mRadioInterface->isUnderrun()) { - // only do latency update every 10 frames, so we don't over update - if (radioClock->get() > mLatencyUpdateTime + GSM::Time(10,0)) { - mTransmitLatency = mTransmitLatency + GSM::Time(1,0); - LOG(INFO) << "new latency: " << mTransmitLatency; - mLatencyUpdateTime = radioClock->get(); - } - } - else { - // if underrun hasn't occurred in the last sec (216 frames) drop - // transmit latency by a timeslot - if (mTransmitLatency > GSM::Time(1,1)) { - if (radioClock->get() > mLatencyUpdateTime + GSM::Time(216,0)) { - mTransmitLatency.decTN(); - LOG(INFO) << "reduced latency: " << mTransmitLatency; - mLatencyUpdateTime = radioClock->get(); - } - } - } -#endif - // time to push burst to transmit FIFO - pushRadioVector(mTransmitDeadlineClock); - mTransmitDeadlineClock.incTN(); - } - - } - // FIXME -- This should not be a hard spin. - // But any delay here causes us to throw omni_thread_fatal. - //else radioClock->wait(); -} - - - -void Transceiver::writeClockInterface() -{ - char command[50]; - // FIXME -- This should be adaptive. - sprintf(command,"IND CLOCK %llu",(unsigned long long) (mTransmitDeadlineClock.FN())+2); - - LOG(INFO) << "ClockInterface: sending " << command; - - mClockSocket.write(command,strlen(command)+1); - - mLastClockUpdateTime = mTransmitDeadlineClock; - -} - - - - -void *TransmitFIFOServiceLoopAdapter(Transceiver *transceiver) -{ - while (1) { - transceiver->driveTransmitFIFO(); - pthread_testcancel(); - } - return NULL; -} - -void *ReceiveFIFOServiceLoopAdapter(Transceiver *transceiver) -{ - while (1) { - transceiver->driveReceiveFIFO(); - pthread_testcancel(); - } - return NULL; -} - -void *ControlServiceLoopAdapter(Transceiver *transceiver) -{ - while (1) { - transceiver->driveControl(); - pthread_testcancel(); - } - return NULL; -} - -void *TransmitPriorityQueueServiceLoopAdapter(Transceiver *transceiver) -{ - while (1) { - bool stale = false; - // Flush the UDP packets until a successful transfer. - while (!transceiver->driveTransmitPriorityQueue()) { - stale = true; - } - if (stale) { - // If a packet was stale, remind the GSM stack of the clock. - transceiver->writeClockInterface(); - } - pthread_testcancel(); - } - return NULL; -} diff --git a/public-trunk/Transceiver/Transceiver.h b/public-trunk/Transceiver/Transceiver.h deleted file mode 100644 index 827ed92..0000000 --- a/public-trunk/Transceiver/Transceiver.h +++ /dev/null @@ -1,214 +0,0 @@ -/* -* Copyright 2008 Free Software Foundation, Inc. -* -* This software is distributed under the terms of the GNU Affero Public License. -* See the COPYING file in the main directory for details. -* -* This use of this software may be subject to additional restrictions. -* See the LEGAL file in the main directory for details. - - 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 . - -*/ - - - -/* - Compilation switches - TRANSMIT_LOGGING write every burst on the given slot to a log -*/ - -#include "radioInterface.h" -#include "Interthread.h" -#include "GSMCommon.h" -#include "Sockets.h" - -#include -#include - -/** Define this to be the slot number to be logged. */ -//#define TRANSMIT_LOGGING 1 - -/** The Transceiver class, responsible for physical layer of basestation */ -class Transceiver { - -private: - - GSM::Time mTransmitLatency; ///< latency between basestation clock and transmit deadline clock - GSM::Time mLatencyUpdateTime; ///< last time latency was updated - - UDPSocket mDataSocket; ///< socket for writing to/reading from GSM core - UDPSocket mControlSocket; ///< socket for writing/reading control commands from GSM core - UDPSocket mClockSocket; ///< socket for writing clock updates to GSM core - - VectorQueue mTransmitPriorityQueue; ///< priority queue of transmit bursts received from GSM core - VectorFIFO* mTransmitFIFO; ///< radioInterface FIFO of transmit bursts - VectorFIFO* mReceiveFIFO; ///< radioInterface FIFO of receive bursts - - mutable Mutex mLock; ///< internal lock used for timeslicing threads - mutable Mutex mDataSocketLock; ///< lock for handling the data socket - - Thread mTransmitFIFOServiceLoopThread; ///< thread to push bursts into transmit FIFO - Thread mReceiveFIFOServiceLoopThread; ///< thread for pulling bursts from receive FIFO - Thread mControlServiceLoopThread; ///< thread to process control messages from GSM core - Thread mTransmitPriorityQueueServiceLoopThread;///< thread to process transmit bursts from GSM core - - GSM::Time mTransmitDeadlineClock; ///< deadline for pushing bursts into transmit FIFO - GSM::Time mLastClockUpdateTime; ///< last time clock update was sent up to core - - RadioInterface *mRadioInterface; ///< associated radioInterface object - double txFullScale; ///< full scale input to radio - double rxFullScale; ///< full scale output to radio - - /** Codes for burst types of received bursts*/ - typedef enum { - OFF, ///< timeslot is off - TSC, ///< timeslot should contain a normal burst - RACH, ///< timeslot should contain an access burst - IDLE ///< timeslot is an idle (or dummy) burst - } CorrType; - - - /** Codes for channel combinations */ - typedef enum { - NONE, ///< Channel is inactive - I, ///< TCH/FS - II, ///< TCH/HS, idle every other slot - III, ///< TCH/HS - IV, ///< FCCH+SCH+CCCH+BCCH, uplink RACH - V, ///< FCCH+SCH+CCCH+BCCH+SDCCH/4+SACCH/4, uplink RACH+SDCCH/4 - VI, ///< CCCH+BCCH, uplink RACH - VII, ///< SDCCH/8 + SACCH/8 - LOOPBACK ///< similar go VII, used in loopback testing - } ChannelCombination; - - - /** unmodulate a modulated burst */ -#ifdef TRANSMIT_LOGGING - void unModulateVector(signalVector wVector); -#endif - - /** modulate and add a burst to the transmit queue */ - void addRadioVector(BitVector &burst, - int RSSI, - GSM::Time &wTime); - - /** Push modulated burst into transmit FIFO corresponding to a particular timestamp */ - void pushRadioVector(GSM::Time &nowTime); - - /** Pull and demodulate a burst from the receive FIFO */ - SoftVector *pullRadioVector(GSM::Time &wTime, - int &RSSI, - int &timingOffset); - - /** Set modulus for specific timeslot */ - void setModulus(int timeslot); - - /** return the expected burst type for the specified timestamp */ - CorrType expectedCorrType(GSM::Time currTime); - - /** send messages over the clock socket */ - void writeClockInterface(void); - - signalVector *gsmPulse; ///< the GSM shaping pulse for modulation - - int mSamplesPerSymbol; ///< number of samples per GSM symbol - - bool mOn; ///< flag to indicate that transceiver is powered on - ChannelCombination mChanType[8]; ///< channel types for all timeslots - double mTxFreq; ///< the transmit frequency - double mRxFreq; ///< the receive frequency - int mPower; ///< the transmit power in dB - unsigned mTSC; ///< the midamble sequence code - double mEnergyThreshold; ///< threshold to determine if received data is potentially a GSM burst - GSM::Time prevFalseDetectionTime; ///< last timestamp of a false energy detection - int fillerModulus[8]; ///< modulus values of all timeslots, in frames - signalVector *fillerTable[102][8]; ///< table of modulated filler waveforms for all timeslots - - GSM::Time channelEstimateTime[8]; ///< last timestamp of each timeslot's channel estimate - signalVector *channelResponse[8]; ///< most recent channel estimate of all timeslots - float SNRestimate[8]; ///< most recent SNR estimate of all timeslots - signalVector *DFEForward[8]; ///< most recent DFE feedforward filter of all timeslots - signalVector *DFEFeedback[8]; ///< most recent DFE feedback filter of all timeslots - float chanRespOffset[8]; ///< most recent timing offset, e.g. TOA, of all timeslots - complex chanRespAmplitude[8]; ///< most recent channel amplitude of all timeslots - -public: - - /** Transceiver constructor - @param wBasePort base port number of UDP sockets - @param TRXAddress IP address of the TRX manager, as a string - @param wSamplesPerSymbol number of samples per GSM symbol - @param wTransmitLatency initial setting of transmit latency - @param radioInterface associated radioInterface object - */ - Transceiver(int wBasePort, - const char *TRXAddress, - int wSamplesPerSymbol, - GSM::Time wTransmitLatency, - RadioInterface *wRadioInterface); - - /** Destructor */ - ~Transceiver(); - - /** start the Transceiver */ - void start(); - - /** attach the radioInterface receive FIFO */ - void receiveFIFO(VectorFIFO *wFIFO) { mReceiveFIFO = wFIFO;} - - /** attach the radioInterface transmit FIFO */ - void transmitFIFO(VectorFIFO *wFIFO) { mTransmitFIFO = wFIFO;} - - -protected: - - /** drive reception and demodulation of GSM bursts */ - void driveReceiveFIFO(); - - /** drive transmission of GSM bursts */ - void driveTransmitFIFO(); - - /** drive handling of control messages from GSM core */ - void driveControl(); - - /** - drive modulation and sorting of GSM bursts from GSM core - @return true if a burst was transferred successfully - */ - bool driveTransmitPriorityQueue(); - - friend void *TransmitFIFOServiceLoopAdapter(Transceiver *); - - friend void *ReceiveFIFOServiceLoopAdapter(Transceiver *); - - friend void *ControlServiceLoopAdapter(Transceiver *); - - friend void *TransmitPriorityQueueServiceLoopAdapter(Transceiver *); - - void reset(); -}; - -/** transmit FIFO thread loop */ -void *TransmitFIFOServiceLoopAdapter(Transceiver *); - -/** receive FIFO thread loop */ -void *ReceiveFIFOServiceLoopAdapter(Transceiver *); - -/** control message handler thread loop */ -void *ControlServiceLoopAdapter(Transceiver *); - -/** transmit queueing thread loop */ -void *TransmitPriorityQueueServiceLoopAdapter(Transceiver *); - diff --git a/public-trunk/Transceiver/UHDDevice.cpp b/public-trunk/Transceiver/UHDDevice.cpp deleted file mode 100644 index 8399c2d..0000000 --- a/public-trunk/Transceiver/UHDDevice.cpp +++ /dev/null @@ -1,882 +0,0 @@ -/* -* Copyright 2008, 2009, 2010 Free Software Foundation, Inc. -* -* This software is distributed under the terms of the GNU Affero Public License. -* See the COPYING file in the main directory for details. -* -* This use of this software may be subject to additional restrictions. -* See the LEGAL file in the main directory for details. - - 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 . - -*/ - -#include "../Transceiver52M/radioDevice.h" -#include "Threads.h" -#include "Logger.h" -#include -#include - -/* - use_ext_ref - Enable external 10MHz clock reference - - master_clk_rt - Master clock frequency - - rx_smpl_offset - Timing correction in seconds between receive and - transmit timestamps. This value corrects for delays on - on the RF side of the timestamping point of the device. - This value is generally empirically measured. - - smpl_buf_sz - The receive sample buffer size in bytes. - - tx_ampl - Transmit amplitude must be between 0 and 1.0 -*/ -const bool use_ext_ref = false; -const double master_clk_rt = 100e6; -const double rx_smpl_offset = .00005; -const size_t smpl_buf_sz = (1 << 20); -const float tx_ampl = .3; - -/** Timestamp conversion - @param timestamp a UHD or OpenBTS timestamp - @param rate sample rate - @return the converted timestamp -*/ -uhd::time_spec_t convert_time(TIMESTAMP ticks, double rate) -{ - double secs = (double) ticks / rate; - return uhd::time_spec_t(secs); -} - -TIMESTAMP convert_time(uhd::time_spec_t ts, double rate) -{ - TIMESTAMP ticks = ts.get_full_secs() * rate; - return ts.get_tick_count(rate) + ticks; -} - -/* - Sample Buffer - Allows reading and writing of timed samples using OpenBTS - or UHD style timestamps. Time conversions are handled - internally or accessable through the static convert calls. -*/ -class smpl_buf { -public: - /** Sample buffer constructor - @param len number of 32-bit samples the buffer should hold - @param rate sample clockrate - @param timestamp - */ - smpl_buf(size_t len, double rate); - ~smpl_buf(); - - /** Query number of samples available for reading - @param timestamp time of first sample - @return number of available samples or error - */ - ssize_t avail_smpls(TIMESTAMP timestamp) const; - ssize_t avail_smpls(uhd::time_spec_t timestamp) const; - - /** Read and write - @param buf pointer to buffer - @param len number of samples desired to read or write - @param timestamp time of first stample - @return number of actual samples read or written or error - */ - ssize_t read(void *buf, size_t len, TIMESTAMP timestamp); - ssize_t read(void *buf, size_t len, uhd::time_spec_t timestamp); - ssize_t write(void *buf, size_t len, TIMESTAMP timestamp); - ssize_t write(void *buf, size_t len, uhd::time_spec_t timestamp); - - /** Buffer status string - @return a formatted string describing internal buffer state - */ - std::string str_status() const; - - /** Formatted error string - @param code an error code - @return a formatted error string - */ - static std::string str_code(ssize_t code); - - enum err_code { - ERROR_TIMESTAMP = -1, - ERROR_READ = -2, - ERROR_WRITE = -3, - ERROR_OVERFLOW = -4 - }; - -private: - uint32_t *data; - size_t buf_len; - - double clk_rt; - - TIMESTAMP time_start; - TIMESTAMP time_end; - - size_t data_start; - size_t data_end; -}; - -/* - uhd_device - UHD implementation of the Device interface. Timestamped samples - are sent to and received from the device. An intermediate buffer - on the receive side collects and aligns packets of samples. - Events and errors such as underruns are reported asynchronously - by the device and received in a separate thread. -*/ -class uhd_device : public RadioDevice { -public: - uhd_device(double rate, bool skip_rx); - ~uhd_device(); - - bool open(); - bool start(); - bool stop(); - void restart(uhd::time_spec_t ts); - void setPriority(); - - int readSamples(short *buf, int len, bool *overrun, - TIMESTAMP timestamp, bool *underrun, unsigned *RSSI); - - int writeSamples(short *buf, int len, bool *underrun, - TIMESTAMP timestamp, bool isControl); - - bool updateAlignment(TIMESTAMP timestamp); - - bool setTxFreq(double wFreq); - bool setRxFreq(double wFreq); - - inline TIMESTAMP initialWriteTimestamp() { return 0; } - inline TIMESTAMP initialReadTimestamp() { return 0; } - - inline double fullScaleInputValue() { return 32000 * tx_ampl; } - inline double fullScaleOutputValue() { return 32000; } - - double setRxGain(double db); - double getRxGain(void) { return rx_gain; } - double maxRxGain(void) { return rx_gain_max; } - double minRxGain(void) { return rx_gain_min; } - - double setTxGain(double db); - double maxTxGain(void) { return tx_gain_max; } - double minTxGain(void) { return tx_gain_min; } - - double getTxFreq() { return tx_freq; } - double getRxFreq() { return rx_freq; } - - inline double getSampleRate() { return actual_smpl_rt; } - inline double numberRead() { return rx_pkt_cnt; } - inline double numberWritten() { return 0; } - - /** Receive and process asynchronous message - @return true if message received or false on timeout or error - */ - bool recv_async_msg(); - - enum err_code { - ERROR_TIMING = -1, - ERROR_UNRECOVERABLE = -2, - ERROR_UNHANDLED = -3, - }; - -private: - uhd::usrp::single_usrp::sptr usrp_dev; - - double desired_smpl_rt, actual_smpl_rt; - - double tx_gain, tx_gain_min, tx_gain_max; - double rx_gain, rx_gain_min, rx_gain_max; - - double tx_freq, rx_freq; - size_t tx_spp, rx_spp; - - bool started; - bool aligned; - bool skip_rx; - - size_t rx_pkt_cnt; - size_t drop_cnt; - uhd::time_spec_t prev_ts; - - TIMESTAMP ts_offset; - smpl_buf *rx_smpl_buf; - - void init_gains(); - void set_ref_clk(bool ext_clk); - double set_rates(double rate); - bool flush_recv(size_t num_pkts); - int check_rx_md_err(uhd::rx_metadata_t &md, ssize_t num_smpls); - - std::string str_code(uhd::rx_metadata_t metadata); - std::string str_code(uhd::async_metadata_t metadata); - - Thread async_event_thrd; -}; - -void *async_event_loop(uhd_device *dev) -{ - while (1) { - dev->recv_async_msg(); - pthread_testcancel(); - } -} - -uhd_device::uhd_device(double rate, bool skip_rx) - : desired_smpl_rt(rate), actual_smpl_rt(0), - tx_gain(0.0), tx_gain_min(0.0), tx_gain_max(0.0), - rx_gain(0.0), rx_gain_min(0.0), rx_gain_max(0.0), - tx_freq(0.0), rx_freq(0.0), tx_spp(0), rx_spp(0), - started(false), aligned(false), rx_pkt_cnt(0), drop_cnt(0), - prev_ts(0,0), ts_offset(0), rx_smpl_buf(NULL) -{ - this->skip_rx = skip_rx; -} - -uhd_device::~uhd_device() -{ - stop(); - - if (rx_smpl_buf) - delete rx_smpl_buf; -} - -void uhd_device::init_gains() -{ - uhd::gain_range_t range; - - range = usrp_dev->get_tx_gain_range(); - tx_gain_min = range.start(); - tx_gain_max = range.stop(); - - range = usrp_dev->get_rx_gain_range(); - rx_gain_min = range.start(); - rx_gain_max = range.stop(); - - usrp_dev->set_tx_gain((tx_gain_min + tx_gain_max) / 2); - usrp_dev->set_rx_gain((rx_gain_min + rx_gain_max) / 2); - - tx_gain = usrp_dev->get_tx_gain(); - rx_gain = usrp_dev->get_rx_gain(); - - return; -} - -void uhd_device::set_ref_clk(bool ext_clk) -{ - uhd::clock_config_t clk_cfg; - - clk_cfg.pps_source = uhd::clock_config_t::PPS_SMA; - - if (ext_clk) - clk_cfg.ref_source = uhd::clock_config_t::REF_SMA; - else - clk_cfg.ref_source = uhd::clock_config_t::REF_INT; - - usrp_dev->set_clock_config(clk_cfg); - - return; -} - -double uhd_device::set_rates(double rate) -{ - double actual_rt, actual_clk_rt; - - // Set master clock rate - usrp_dev->set_master_clock_rate(master_clk_rt); - actual_clk_rt = usrp_dev->get_master_clock_rate(); - - if (actual_clk_rt != master_clk_rt) { - LOG(ERROR) << "Failed to set master clock rate"; - return -1.0; - } - - // Set sample rates - usrp_dev->set_tx_rate(rate); - usrp_dev->set_rx_rate(rate); - actual_rt = usrp_dev->get_tx_rate(); - - if (actual_rt != rate) { - LOG(ERROR) << "Actual sample rate differs from desired rate"; - return -1.0; - } - if (usrp_dev->get_rx_rate() != actual_rt) { - LOG(ERROR) << "Transmit and receive sample rates do not match"; - return -1.0; - } - - return actual_rt; -} - -double uhd_device::setTxGain(double db) -{ - usrp_dev->set_tx_gain(db); - tx_gain = usrp_dev->get_tx_gain(); - - LOG(INFO) << "Set TX gain to " << tx_gain << "dB"; - - return tx_gain; -} - -double uhd_device::setRxGain(double db) -{ - usrp_dev->set_rx_gain(db); - rx_gain = usrp_dev->get_rx_gain(); - - LOG(INFO) << "Set RX gain to " << rx_gain << "dB"; - - return rx_gain; -} - -bool uhd_device::open() -{ - LOG(INFO) << "creating USRP device..."; - - // Use the first available USRP2 / N2xx - uhd::device_addr_t dev_addr("type=usrp2"); - try { - usrp_dev = uhd::usrp::single_usrp::make(dev_addr); - } - - catch(...) { - LOG(ERROR) << "Failed to find USRP2 / N2xx device"; - return false; - } - - // Number of samples per over-the-wire packet - tx_spp = usrp_dev->get_device()->get_max_send_samps_per_packet(); - rx_spp = usrp_dev->get_device()->get_max_recv_samps_per_packet(); - - // Set rates - actual_smpl_rt = set_rates(desired_smpl_rt); - if (actual_smpl_rt < 0) - return false; - - // Create receive buffer - size_t buf_len = smpl_buf_sz / sizeof(uint32_t); - rx_smpl_buf = new smpl_buf(buf_len, actual_smpl_rt); - - // Set receive chain sample offset - ts_offset = (TIMESTAMP)(rx_smpl_offset * actual_smpl_rt); - - // Initialize and shadow gain values - init_gains(); - - // Set reference clock - set_ref_clk(use_ext_ref); - - // Print configuration - LOG(INFO) << usrp_dev->get_pp_string(); - - return true; -} - -bool uhd_device::flush_recv(size_t num_pkts) -{ - uhd::rx_metadata_t md; - size_t num_smpls; - uint32_t buff[rx_spp]; - float timeout; - - // Use .01 sec instead of the default .1 sec - timeout = .01; - - for (size_t i = 0; i < num_pkts; i++) { - num_smpls = usrp_dev->get_device()->recv( - buff, - rx_spp, - md, - uhd::io_type_t::COMPLEX_INT16, - uhd::device::RECV_MODE_ONE_PACKET, - timeout); - - if (!num_smpls) { - switch (md.error_code) { - case uhd::rx_metadata_t::ERROR_CODE_TIMEOUT: - return true; - default: - continue; - } - } - } - - return true; -} - -void uhd_device::restart(uhd::time_spec_t ts) -{ - uhd::stream_cmd_t cmd = uhd::stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS; - usrp_dev->issue_stream_cmd(cmd); - - flush_recv(50); - - usrp_dev->set_time_now(ts); - aligned = false; - - cmd = uhd::stream_cmd_t::STREAM_MODE_START_CONTINUOUS; - cmd.stream_now = true; - usrp_dev->issue_stream_cmd(cmd); -} - -bool uhd_device::start() -{ - LOG(INFO) << "Starting USRP..."; - - if (started) { - LOG(ERROR) << "Device already started"; - return false; - } - - setPriority(); - - // Start asynchronous event (underrun check) loop - async_event_thrd.start((void * (*)(void*))async_event_loop, (void*)this); - - // Start streaming - restart(uhd::time_spec_t(0.0)); - - // Display usrp time - double time_now = usrp_dev->get_time_now().get_real_secs(); - LOG(INFO) << "The current time is " << time_now << " seconds"; - - started = true; - return true; -} - -bool uhd_device::stop() -{ - uhd::stream_cmd_t stream_cmd = - uhd::stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS; - - usrp_dev->issue_stream_cmd(stream_cmd); - - started = false; - return true; -} - -void uhd_device::setPriority() -{ - uhd::set_thread_priority_safe(); - return; -} - -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(ERROR) << str_code(md); - - switch (md.error_code) { - case uhd::rx_metadata_t::ERROR_CODE_TIMEOUT: - return ERROR_UNRECOVERABLE; - case uhd::rx_metadata_t::ERROR_CODE_OVERFLOW: - case uhd::rx_metadata_t::ERROR_CODE_LATE_COMMAND: - case uhd::rx_metadata_t::ERROR_CODE_BROKEN_CHAIN: - case uhd::rx_metadata_t::ERROR_CODE_BAD_PACKET: - default: - return ERROR_UNHANDLED; - } - } - - // Missing timestamp - if (!md.has_time_spec) { - LOG(ERROR) << "UHD: Received packet missing timestamp"; - return ERROR_UNRECOVERABLE; - } - - ts = md.time_spec; - - // Monotonicity check - if (ts < prev_ts) { - LOG(ERROR) << "UHD: Loss of monotonic: " << ts.get_real_secs(); - LOG(ERROR) << "UHD: Previous time: " << prev_ts.get_real_secs(); - return ERROR_TIMING; - } else { - prev_ts = ts; - } - - return 0; -} - -int uhd_device::readSamples(short *buf, int len, bool *overrun, - TIMESTAMP timestamp, bool *underrun, unsigned *RSSI) -{ - ssize_t rc; - uhd::time_spec_t ts; - uhd::rx_metadata_t metadata; - uint32_t pkt_buf[rx_spp]; - - if (skip_rx) - return 0; - - // Shift read time with respect to transmit clock - timestamp += ts_offset; - - ts = convert_time(timestamp, actual_smpl_rt); - LOG(DEEPDEBUG) << "Requested timestamp = " << ts.get_real_secs(); - - // Check that timestamp is valid - rc = rx_smpl_buf->avail_smpls(timestamp); - if (rc < 0) { - LOG(ERROR) << rx_smpl_buf->str_code(rc); - LOG(ERROR) << rx_smpl_buf->str_status(); - return 0; - } - - // Receive samples from the usrp until we have enough - while (rx_smpl_buf->avail_smpls(timestamp) < len) { - size_t num_smpls = usrp_dev->get_device()->recv( - (void*)pkt_buf, - rx_spp, - metadata, - uhd::io_type_t::COMPLEX_INT16, - uhd::device::RECV_MODE_ONE_PACKET); - - rx_pkt_cnt++; - - // Check for errors - rc = check_rx_md_err(metadata, num_smpls); - switch (rc) { - case ERROR_UNRECOVERABLE: - LOG(ERROR) << "UHD: Unrecoverable error, exiting."; - exit(-1); - case ERROR_TIMING: - restart(prev_ts); - case ERROR_UNHANDLED: - return 0; - } - - - ts = metadata.time_spec; - LOG(DEEPDEBUG) << "Received timestamp = " << ts.get_real_secs(); - - rc = rx_smpl_buf->write(pkt_buf, - num_smpls, - metadata.time_spec); - - // Continue on local overrun, exit on other errors - if ((rc < 0)) { - LOG(ERROR) << rx_smpl_buf->str_code(rc); - LOG(ERROR) << rx_smpl_buf->str_status(); - if (rc != smpl_buf::ERROR_OVERFLOW) - return 0; - } - } - - // We have enough samples - rc = rx_smpl_buf->read(buf, len, timestamp); - if ((rc < 0) || (rc != len)) { - LOG(ERROR) << rx_smpl_buf->str_code(rc); - LOG(ERROR) << rx_smpl_buf->str_status(); - return 0; - } - - return len; -} - -int uhd_device::writeSamples(short *buf, int len, bool *underrun, - unsigned long long timestamp,bool isControl) -{ - uhd::tx_metadata_t metadata; - metadata.has_time_spec = true; - metadata.start_of_burst = false; - metadata.end_of_burst = false; - metadata.time_spec = convert_time(timestamp, actual_smpl_rt); - - // No control packets - if (isControl) { - LOG(ERROR) << "Control packets not supported"; - return 0; - } - - // Drop a fixed number of packets (magic value) - if (!aligned) { - drop_cnt++; - - if (drop_cnt == 1) { - LOG(DEBUG) << "Aligning transmitter: stop burst"; - metadata.end_of_burst = true; - } else if (drop_cnt < 30) { - LOG(DEEPDEBUG) << "Aligning transmitter: packet advance"; - *underrun = true; - return len; - } else { - LOG(DEBUG) << "Aligning transmitter: start burst"; - metadata.start_of_burst = true; - aligned = true; - drop_cnt = 0; - } - } - - size_t num_smpls = usrp_dev->get_device()->send(buf, - len, - metadata, - uhd::io_type_t::COMPLEX_INT16, - uhd::device::SEND_MODE_FULL_BUFF); - - if (num_smpls != (unsigned)len) - LOG(ERROR) << "UHD: Sent fewer samples than requested"; - - return num_smpls; -} - -bool uhd_device::updateAlignment(TIMESTAMP timestamp) -{ - aligned = false; - return true; -} - -bool uhd_device::setTxFreq(double wFreq) -{ - uhd::tune_result_t tr = usrp_dev->set_tx_freq(wFreq); - LOG(INFO) << tr.to_pp_string(); - tx_freq = usrp_dev->get_tx_freq(); - - return true; -} - -bool uhd_device::setRxFreq(double wFreq) -{ - uhd::tune_result_t tr = usrp_dev->set_rx_freq(wFreq); - LOG(INFO) << tr.to_pp_string(); - rx_freq = usrp_dev->get_rx_freq(); - - return true; -} - -bool uhd_device::recv_async_msg() -{ - uhd::async_metadata_t metadata; - if (!usrp_dev->get_device()->recv_async_msg(metadata)) - return false; - - // Assume that any error requires resynchronization - if (metadata.event_code != uhd::async_metadata_t::EVENT_CODE_BURST_ACK) { - aligned = false; - LOG(ERROR) << str_code(metadata); - } - - return true; -} - -std::string uhd_device::str_code(uhd::rx_metadata_t metadata) -{ - std::ostringstream ost("UHD: "); - - switch (metadata.error_code) { - case uhd::rx_metadata_t::ERROR_CODE_NONE: - ost << "No error"; - break; - case uhd::rx_metadata_t::ERROR_CODE_TIMEOUT: - ost << "No packet received, implementation timed-out"; - break; - case uhd::rx_metadata_t::ERROR_CODE_LATE_COMMAND: - ost << "A stream command was issued in the past"; - break; - case uhd::rx_metadata_t::ERROR_CODE_BROKEN_CHAIN: - ost << "Expected another stream command"; - break; - case uhd::rx_metadata_t::ERROR_CODE_OVERFLOW: - ost << "An internal receive buffer has filled"; - break; - case uhd::rx_metadata_t::ERROR_CODE_BAD_PACKET: - ost << "The packet could not be parsed"; - break; - default: - ost << "Unknown error " << metadata.error_code; - } - - if (metadata.has_time_spec) - ost << " at " << metadata.time_spec.get_real_secs() << " sec."; - - return ost.str(); -} - -std::string uhd_device::str_code(uhd::async_metadata_t metadata) -{ - std::ostringstream ost("UHD: "); - - switch (metadata.event_code) { - case uhd::async_metadata_t::EVENT_CODE_BURST_ACK: - ost << "A packet was successfully transmitted"; - break; - case uhd::async_metadata_t::EVENT_CODE_UNDERFLOW: - ost << "An internal send buffer has emptied"; - break; - case uhd::async_metadata_t::EVENT_CODE_SEQ_ERROR: - ost << "Packet loss between host and device"; - break; - case uhd::async_metadata_t::EVENT_CODE_TIME_ERROR: - ost << "Packet time was too late or too early"; - break; - case uhd::async_metadata_t::EVENT_CODE_UNDERFLOW_IN_PACKET: - ost << "Underflow occurred inside a packet"; - break; - case uhd::async_metadata_t::EVENT_CODE_SEQ_ERROR_IN_BURST: - ost << "Packet loss within a burst"; - break; - default: - ost << "Unknown error " << metadata.event_code; - } - - if (metadata.has_time_spec) - ost << " at " << metadata.time_spec.get_real_secs() << " sec."; - - return ost.str(); -} - -smpl_buf::smpl_buf(size_t len, double rate) - : buf_len(len), clk_rt(rate), - time_start(0), time_end(0), data_start(0), data_end(0) -{ - data = new uint32_t[len]; -} - -smpl_buf::~smpl_buf() -{ - delete[] data; -} - -ssize_t smpl_buf::avail_smpls(TIMESTAMP timestamp) const -{ - if (timestamp < time_start) - return ERROR_TIMESTAMP; - else if (timestamp >= time_end) - return 0; - else - return time_end - timestamp; -} - -ssize_t smpl_buf::avail_smpls(uhd::time_spec_t timespec) const -{ - return avail_smpls(convert_time(timespec, clk_rt)); -} - -ssize_t smpl_buf::read(void *buf, size_t len, TIMESTAMP timestamp) -{ - // Check for valid read - if (timestamp < time_start) - return ERROR_TIMESTAMP; - if (timestamp >= time_end) - return 0; - if (len >= buf_len) - return ERROR_READ; - - // How many samples should be copied - size_t num_smpls = time_end - timestamp; - if (num_smpls > len); - num_smpls = len; - - // Starting index - size_t read_start = data_start + (timestamp - time_start); - - // Read it - if (read_start + num_smpls < buf_len) { - size_t numBytes = len * 2 * sizeof(short); - memcpy(buf, data + read_start, numBytes); - } else { - size_t first_cp = (buf_len - read_start) * 2 * sizeof(short); - size_t second_cp = len * 2 * sizeof(short) - first_cp; - - memcpy(buf, data + read_start, first_cp); - memcpy((char*) buf + first_cp, data, second_cp); - } - - data_start = (read_start + len) % buf_len; - time_start = timestamp + len; - - if (time_start > time_end) - return ERROR_READ; - else - return num_smpls; -} - -ssize_t smpl_buf::read(void *buf, size_t len, uhd::time_spec_t ts) -{ - return read(buf, len, convert_time(ts, clk_rt)); -} - -ssize_t smpl_buf::write(void *buf, size_t len, TIMESTAMP timestamp) -{ - // Check for valid write - if ((len == 0) || (len >= buf_len)) - return ERROR_WRITE; - if ((timestamp + len) <= time_end) - return ERROR_TIMESTAMP; - - // Starting index - size_t write_start = (data_start + (timestamp - time_start)) % buf_len; - - // Write it - if ((write_start + len) < buf_len) { - size_t numBytes = len * 2 * sizeof(short); - memcpy(data + write_start, buf, numBytes); - } else { - size_t first_cp = (buf_len - write_start) * 2 * sizeof(short); - size_t second_cp = len * 2 * sizeof(short) - first_cp; - - memcpy(data + write_start, buf, first_cp); - memcpy(data, (char*) buf + first_cp, second_cp); - } - - data_end = (write_start + len) % buf_len; - time_end = timestamp + len; - - if (((write_start + len) > buf_len) && (data_end > data_start)) - return ERROR_OVERFLOW; - else if (time_end <= time_start) - return ERROR_WRITE; - else - return len; -} - -ssize_t smpl_buf::write(void *buf, size_t len, uhd::time_spec_t ts) -{ - return write(buf, len, convert_time(ts, clk_rt)); -} - -std::string smpl_buf::str_status() const -{ - std::ostringstream ost("Sample buffer: "); - - ost << "length = " << buf_len; - ost << ", time_start = " << time_start; - ost << ", time_end = " << time_end; - ost << ", data_start = " << data_start; - ost << ", data_end = " << data_end; - - return ost.str(); -} - -std::string smpl_buf::str_code(ssize_t code) -{ - switch (code) { - case ERROR_TIMESTAMP: - return "Sample buffer: Requested timestamp is not valid"; - case ERROR_READ: - return "Sample buffer: Read error"; - case ERROR_WRITE: - return "Sample buffer: Write error"; - case ERROR_OVERFLOW: - return "Sample buffer: Overrun"; - default: - return "Sample buffer: Unknown error"; - } -} - -RadioDevice *RadioDevice::make(double smpl_rt, bool skip_rx) -{ - return new uhd_device(smpl_rt, skip_rx); -} diff --git a/public-trunk/Transceiver/USRPDevice.cpp b/public-trunk/Transceiver/USRPDevice.cpp deleted file mode 100644 index 0c6ee21..0000000 --- a/public-trunk/Transceiver/USRPDevice.cpp +++ /dev/null @@ -1,537 +0,0 @@ -/* -* Copyright 2008, 2009 Free Software Foundation, Inc. -* -* This software is distributed under the terms of the GNU Affero Public License. -* See the COPYING file in the main directory for details. -* -* This use of this software may be subject to additional restrictions. -* See the LEGAL file in the main directory for details. - - 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 . - -*/ - - -/* - Compilation Flags - - SWLOOPBACK compile for software loopback testing -*/ - - -#include -#include -#include -#include "Threads.h" -#include "USRPDevice.h" - -#include - - -using namespace std; - -enum dboardConfigType { - TXA_RXB, - TXB_RXA, - TXA_RXA, - TXB_RXB -}; - -const dboardConfigType dboardConfig = TXA_RXB; -const double USRPDevice::masterClockRate = 64.0e6; - -USRPDevice::USRPDevice (double _desiredSampleRate, bool wSkipRx) - : skipRx(wSkipRx) -{ - LOG(INFO) << "creating USRP device..."; - decimRate = (unsigned int) round(masterClockRate/_desiredSampleRate); - actualSampleRate = masterClockRate/decimRate; - -#ifdef SWLOOPBACK - samplePeriod = 1.0e6/actualSampleRate; - loopbackBufferSize = 0; - gettimeofday(&lastReadTime,NULL); - firstRead = false; -#endif -} - -bool USRPDevice::open() -{ - LOG(INFO) << "opening USRP device..."; -#ifndef SWLOOPBACK - string rbf = "std_inband.rbf"; - //string rbf = "inband_1rxhb_1tx.rbf"; - m_uRx.reset(); - if (!skipRx) { - try { - m_uRx = usrp_standard_rx_sptr(usrp_standard_rx::make(0,decimRate,1,-1, - usrp_standard_rx::FPGA_MODE_NORMAL, - 1024,16*8,rbf)); -#ifdef HAVE_LIBUSRP_3_2 - m_uRx->set_fpga_master_clock_freq(masterClockRate); -#endif - } - - catch(...) { - LOG(ERROR) << "make failed on Rx"; - m_uRx.reset(); - return false; - } - - if (m_uRx->fpga_master_clock_freq() != masterClockRate) - { - LOG(ERROR) << "WRONG FPGA clock freq = " << m_uRx->fpga_master_clock_freq() - << ", desired clock freq = " << masterClockRate; - m_uRx.reset(); - return false; - } - } - - try { - m_uTx = usrp_standard_tx_sptr(usrp_standard_tx::make(0,decimRate*2,1,-1, - 1024,16*8,rbf)); -#ifdef HAVE_LIBUSRP_3_2 - m_uTx->set_fpga_master_clock_freq(masterClockRate); -#endif - } - - catch(...) { - LOG(ERROR) << "make failed on Tx"; - m_uTx.reset(); - return false; - } - - if (m_uTx->fpga_master_clock_freq() != masterClockRate) - { - LOG(ERROR) << "WRONG FPGA clock freq = " << m_uTx->fpga_master_clock_freq() - << ", desired clock freq = " << masterClockRate; - m_uTx.reset(); - return false; - } - - if (!skipRx) m_uRx->stop(); - m_uTx->stop(); - -#endif - - switch (dboardConfig) { - case TXA_RXB: - txSubdevSpec = usrp_subdev_spec(0,0); - rxSubdevSpec = usrp_subdev_spec(1,0); - break; - case TXB_RXA: - txSubdevSpec = usrp_subdev_spec(1,0); - rxSubdevSpec = usrp_subdev_spec(0,0); - break; - case TXA_RXA: - txSubdevSpec = usrp_subdev_spec(0,0); - rxSubdevSpec = usrp_subdev_spec(0,0); - break; - case TXB_RXB: - txSubdevSpec = usrp_subdev_spec(1,0); - rxSubdevSpec = usrp_subdev_spec(1,0); - break; - default: - txSubdevSpec = usrp_subdev_spec(0,0); - rxSubdevSpec = usrp_subdev_spec(1,0); - } - - m_dbTx = m_uTx->selected_subdev(txSubdevSpec); - m_dbRx = m_uRx->selected_subdev(rxSubdevSpec); - - samplesRead = 0; - samplesWritten = 0; - started = false; - - return true; -} - -bool USRPDevice::start() -{ - LOG(INFO) << "starting USRP..."; -#ifndef SWLOOPBACK - if (!m_uRx && !skipRx) return false; - if (!m_uTx) return false; - - if (!skipRx) m_uRx->stop(); - m_uTx->stop(); - - // Transmit settings - gain at midpoint - m_dbTx->set_gain((m_dbRx->gain_min() + m_dbRx->gain_max()) / 2); - m_dbTx->set_enable(true); - m_uTx->set_mux(m_uTx->determine_tx_mux_value(txSubdevSpec)); - - // Receive settings - gain at max, antenna RX2 (if available) - m_dbRx->set_gain(m_dbRx->gain_max()); - m_uRx->set_mux(m_uRx->determine_rx_mux_value(rxSubdevSpec)); - - if (!m_dbRx->select_rx_antenna(1)) - m_dbRx->select_rx_antenna(0); - - data = new short[currDataSize]; - dataStart = 0; - dataEnd = 0; - timeStart = 0; - timeEnd = 0; - timestampOffset = 0; - latestWriteTimestamp = 0; - lastPktTimestamp = 0; - hi32Timestamp = 0; - isAligned = false; - - if (!skipRx) - started = (m_uRx->start() && m_uTx->start()); - else - started = m_uTx->start(); - return started; -#else - gettimeofday(&lastReadTime,NULL); - return true; -#endif -} - -bool USRPDevice::stop() -{ -#ifndef SWLOOPBACK - if (!m_uRx) return false; - if (!m_uTx) return false; - - delete[] currData; - - started = !(m_uRx->stop() && m_uTx->stop()); - return !started; -#else - return true; -#endif -} - -double USRPDevice::maxTxGain() -{ - return m_dbTx->gain_max(); -} - -double USRPDevice::minTxGain() -{ - return m_dbTx->gain_min(); -} - -double USRPDevice::maxRxGain() -{ - return m_dbRx->gain_max(); -} - -double USRPDevice::minRxGain() -{ - return m_dbRx->gain_min(); -} - -double USRPDevice::setTxGain(double dB) -{ - if (dB > maxTxGain()) dB = maxTxGain(); - if (dB < minTxGain()) dB = minTxGain(); - - LOG(NOTICE) << "Setting TX gain to " << dB << " dB."; - - if (!m_dbRx->set_gain(dB)) - LOG(ERROR) << "Error setting TX gain"; - - return dB; -} - -double USRPDevice::setRxGain(double dB) -{ - if (dB > maxRxGain()) dB = maxRxGain(); - if (dB < minRxGain()) dB = minRxGain(); - - LOG(NOTICE) << "Setting RX gain to " << dB << " dB."; - - if (!m_dbRx->set_gain(dB)) { - LOG(ERROR) << "Error setting RX gain"; - } else { - rxGain = dB; - } - - return rxGain; -} - -// NOTE: Assumes sequential reads -int USRPDevice::readSamples(short *buf, int len, bool *overrun, - TIMESTAMP timestamp, - bool *underrun, - unsigned *RSSI) -{ -#ifndef SWLOOPBACK - if (!m_uRx) return 0; - - timestamp += timestampOffset; - - if (timestamp + len < timeStart) { - memset(buf,0,len*2*sizeof(short)); - return len; - } - - if (underrun) *underrun = false; - - uint32_t *readBuf = NULL; - - while (1) { - //guestimate USB read size - int readLen=0; - { - int numSamplesNeeded = timestamp + len - timeEnd; - if (numSamplesNeeded <=0) break; - readLen = 512 * ((int) ceil((float) numSamplesNeeded/126.0)); - } - - // read USRP packets, parse and save A/D data as needed - if (readBuf!=NULL) delete[] readBuf; - readBuf = new uint32_t[readLen/4]; - readLen = m_uRx->read((void *)readBuf,readLen,overrun); - for(int pktNum = 0; pktNum < (readLen/512); pktNum++) { - // tmpBuf points to start of a USB packet - uint32_t* tmpBuf = (uint32_t *) (readBuf+pktNum*512/4); - TIMESTAMP pktTimestamp = usrp_to_host_u32(tmpBuf[1]); - uint32_t word0 = usrp_to_host_u32(tmpBuf[0]); - uint32_t chan = (word0 >> 16) & 0x1f; - unsigned payloadSz = word0 & 0x1ff; - LOG(DEBUG) << "first two bytes: " << hex << word0 << " " << dec << pktTimestamp; - - bool incrementHi32 = ((lastPktTimestamp & 0x0ffffffffll) > pktTimestamp); - if (incrementHi32 && (timeStart!=0)) { - LOG(DEBUG) << "high 32 increment!!!"; - hi32Timestamp++; - } - pktTimestamp = (((TIMESTAMP) hi32Timestamp) << 32) | pktTimestamp; - lastPktTimestamp = pktTimestamp; - - if (chan == 0x01f) { - // control reply, check to see if its ping reply - uint32_t word2 = usrp_to_host_u32(tmpBuf[2]); - if ((word2 >> 16) == ((0x01 << 8) | 0x02)) { - timestamp -= timestampOffset; - timestampOffset = pktTimestamp - pingTimestamp + PINGOFFSET; - LOG(DEBUG) << "updating timestamp offset to: " << timestampOffset; - timestamp += timestampOffset; - isAligned = true; - } - continue; - } - if (chan != 0) { - LOG(DEBUG) << "chan: " << chan << ", timestamp: " << pktTimestamp << ", sz:" << payloadSz; - continue; - } - if ((word0 >> 28) & 0x04) { - if (underrun) *underrun = true; - LOG(DEBUG) << "UNDERRUN in TRX->USRP interface"; - } - if (RSSI) *RSSI = (word0 >> 21) & 0x3f; - - if (!isAligned) continue; - - unsigned cursorStart = pktTimestamp - timeStart + dataStart; - while (cursorStart*2 > currDataSize) { - cursorStart -= currDataSize/2; - } - if (cursorStart*2 + payloadSz/2 > currDataSize) { - // need to circle around buffer - memcpy(data+cursorStart*2,tmpBuf+2,(currDataSize-cursorStart*2)*sizeof(short)); - memcpy(data,tmpBuf+2+(currDataSize/2-cursorStart),payloadSz-(currDataSize-cursorStart*2)*sizeof(short)); - } - else { - memcpy(data+cursorStart*2,tmpBuf+2,payloadSz); - } - if (pktTimestamp + payloadSz/2/sizeof(short) > timeEnd) - timeEnd = pktTimestamp+payloadSz/2/sizeof(short); - - LOG(DEBUG) << "timeStart: " << timeStart << ", timeEnd: " << timeEnd << ", pktTimestamp: " << pktTimestamp; - - } - } - - // copy desired data to buf - unsigned bufStart = dataStart+(timestamp-timeStart); - if (bufStart + len < currDataSize/2) { - LOG(DEBUG) << "bufStart: " << bufStart; - memcpy(buf,data+bufStart*2,len*2*sizeof(short)); - memset(data+bufStart*2,0,len*2*sizeof(short)); - } - else { - LOG(DEBUG) << "len: " << len << ", currDataSize/2: " << currDataSize/2 << ", bufStart: " << bufStart; - unsigned firstLength = (currDataSize/2-bufStart); - LOG(DEBUG) << "firstLength: " << firstLength; - memcpy(buf,data+bufStart*2,firstLength*2*sizeof(short)); - memset(data+bufStart*2,0,firstLength*2*sizeof(short)); - memcpy(buf+firstLength*2,data,(len-firstLength)*2*sizeof(short)); - memset(data,0,(len-firstLength)*2*sizeof(short)); - } - dataStart = (bufStart + len) % (currDataSize/2); - timeStart = timestamp + len; - if (readBuf!=NULL) delete[] readBuf; - - return len; - -#else - if (loopbackBufferSize < 2) return 0; - int numSamples = 0; - struct timeval currTime; - gettimeofday(&currTime,NULL); - double timeElapsed = (currTime.tv_sec - lastReadTime.tv_sec)*1.0e6 + - (currTime.tv_usec - lastReadTime.tv_usec); - if (timeElapsed < samplePeriod) {return 0;} - int numSamplesToRead = (int) floor(timeElapsed/samplePeriod); - if (numSamplesToRead < len) return 0; - - if (numSamplesToRead > len) numSamplesToRead = len; - if (numSamplesToRead > loopbackBufferSize/2) { - firstRead =false; - numSamplesToRead = loopbackBufferSize/2; - } - memcpy(buf,loopbackBuffer,sizeof(short)*2*numSamplesToRead); - loopbackBufferSize -= 2*numSamplesToRead; - memcpy(loopbackBuffer,loopbackBuffer+2*numSamplesToRead, - sizeof(short)*loopbackBufferSize); - numSamples = numSamplesToRead; - if (firstRead) { - int new_usec = lastReadTime.tv_usec + (int) round((double) numSamplesToRead * samplePeriod); - lastReadTime.tv_sec = lastReadTime.tv_sec + new_usec/1000000; - lastReadTime.tv_usec = new_usec % 1000000; - } - else { - gettimeofday(&lastReadTime,NULL); - firstRead = true; - } - samplesRead += numSamples; - - return numSamples; -#endif -} - -int USRPDevice::writeSamples(short *buf, int len, bool *underrun, - unsigned long long timestamp, - bool isControl) -{ -#ifndef SWLOOPBACK - if (!m_uTx) return 0; - - for (int i = 0; i < len*2; i++) { - buf[i] = host_to_usrp_short(buf[i]); - } - - int numWritten = 0; - unsigned isStart = 1; - unsigned RSSI = 0; - unsigned CHAN = (isControl) ? 0x01f : 0x00; - len = len*2*sizeof(short); - int numPkts = (int) ceil((float)len/(float)504); - unsigned isEnd = (numPkts < 2); - uint32_t *outPkt = new uint32_t[128*numPkts]; - int pktNum = 0; - while (numWritten < len) { - // pkt is pointer to start of a USB packet - uint32_t *pkt = outPkt + pktNum*128; - isEnd = (len - numWritten <= 504); - unsigned payloadLen = ((len - numWritten) < 504) ? (len-numWritten) : 504; - pkt[0] = (isStart << 12 | isEnd << 11 | (RSSI & 0x3f) << 5 | CHAN) << 16 | payloadLen; - pkt[1] = timestamp & 0x0ffffffffll; - memcpy(pkt+2,buf+(numWritten/sizeof(short)),payloadLen); - numWritten += payloadLen; - timestamp += payloadLen/2/sizeof(short); - isStart = 0; - pkt[0] = host_to_usrp_u32(pkt[0]); - pkt[1] = host_to_usrp_u32(pkt[1]); - pktNum++; - } - m_uTx->write((const void*) outPkt,sizeof(uint32_t)*128*numPkts,NULL); - delete[] outPkt; - - samplesWritten += len/2/sizeof(short); - return len/2/sizeof(short); -#else - int retVal = len; - memcpy(loopbackBuffer+loopbackBufferSize,buf,sizeof(short)*2*len); - samplesWritten += retVal; - loopbackBufferSize += retVal*2; - - return retVal; -#endif -} - -bool USRPDevice::updateAlignment(TIMESTAMP timestamp) -{ -#ifndef SWLOOPBACK - short data[] = {0x00,0x02,0x00,0x00}; - uint32_t *wordPtr = (uint32_t *) data; - *wordPtr = host_to_usrp_u32(*wordPtr); - bool tmpUnderrun; - if (writeSamples((short *) data,1,&tmpUnderrun,timestamp & 0x0ffffffffll,true)) { - pingTimestamp = timestamp; - return true; - } - return false; -#else - return true; -#endif -} - -#ifndef SWLOOPBACK -bool USRPDevice::setTxFreq(double wFreq) -{ - usrp_tune_result result; - - if (m_uTx->tune(txSubdevSpec.side, m_dbTx, wFreq, &result)) { - LOG(INFO) << "set TX: " << wFreq << std::endl - << " baseband freq: " << result.baseband_freq << std::endl - << " DDC freq: " << result.dxc_freq << std::endl - << " residual freq: " << result.residual_freq; - return true; - } - else { - LOG(ERROR) << "set TX: " << wFreq << "failed" << std::endl - << " baseband freq: " << result.baseband_freq << std::endl - << " DDC freq: " << result.dxc_freq << std::endl - << " residual freq: " << result.residual_freq; - return false; - } -} - -bool USRPDevice::setRxFreq(double wFreq) -{ - usrp_tune_result result; - - if (m_uRx->tune(0, m_dbRx, wFreq, &result)) { - LOG(INFO) << "set RX: " << wFreq << std::endl - << " baseband freq: " << result.baseband_freq << std::endl - << " DDC freq: " << result.dxc_freq << std::endl - << " residual freq: " << result.residual_freq; - return true; - } - else { - LOG(ERROR) << "set RX: " << wFreq << "failed" << std::endl - << " baseband freq: " << result.baseband_freq << std::endl - << " DDC freq: " << result.dxc_freq << std::endl - << " residual freq: " << result.residual_freq; - return false; - } -} - -#else -bool USRPDevice::setTxFreq(double wFreq) { return true;}; -bool USRPDevice::setRxFreq(double wFreq) { return true;}; -#endif - - -RadioDevice *RadioDevice::make(double desiredSampleRate, bool skipRx) -{ - return new USRPDevice(desiredSampleRate, skipRx); -} diff --git a/public-trunk/Transceiver/USRPDevice.h b/public-trunk/Transceiver/USRPDevice.h deleted file mode 100644 index f019d66..0000000 --- a/public-trunk/Transceiver/USRPDevice.h +++ /dev/null @@ -1,192 +0,0 @@ -/* -* Copyright 2008 Free Software Foundation, Inc. -* -* This software is distributed under the terms of the GNU Affero Public License. -* See the COPYING file in the main directory for details. -* -* This use of this software may be subject to additional restrictions. -* See the LEGAL file in the main directory for details. - - 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 . - -*/ - -#ifndef _USRP_DEVICE_H_ -#define _USRP_DEVICE_H_ - -#ifdef HAVE_CONFIG_H -#include "config.h" -#endif - -#ifdef HAVE_LIBUSRP_3_3 // [ -# include -# include -# include -#else // HAVE_LIBUSRP_3_3 ][ -# include "usrp_standard.h" -# include "usrp_bytesex.h" -# include "usrp_prims.h" -#endif // !HAVE_LIBUSRP_3_3 ] -#include -#include -#include -#include -#include "../Transceiver52M/radioDevice.h" - - -/** Define types which are not defined in libusrp-3.1 */ -#ifndef HAVE_LIBUSRP_3_2 -#include -typedef boost::shared_ptr usrp_standard_tx_sptr; -typedef boost::shared_ptr usrp_standard_rx_sptr; -#endif // HAVE_LIBUSRP_3_2 - - -/** A class to handle a USRP rev 4, with a two RFX900 daughterboards */ -class USRPDevice : public RadioDevice { - -private: - - static const double masterClockRate;///< the USRP clock rate - double desiredSampleRate; ///< the desired sampling rate - usrp_standard_rx_sptr m_uRx; ///< the USRP receiver - usrp_standard_tx_sptr m_uTx; ///< the USRP transmitter - - db_base_sptr m_dbRx; ///< rx daughterboard - db_base_sptr m_dbTx; ///< tx daughterboard - usrp_subdev_spec rxSubdevSpec; - usrp_subdev_spec txSubdevSpec; - - double actualSampleRate; ///< the actual USRP sampling rate - unsigned int decimRate; ///< the USRP decimation rate - - unsigned long long samplesRead; ///< number of samples read from USRP - unsigned long long samplesWritten; ///< number of samples sent to USRP - - bool started; ///< flag indicates USRP has started - bool skipRx; ///< set if USRP is transmit-only. - - static const unsigned int currDataSize_log2 = 18; - static const unsigned long currDataSize = (1 << currDataSize_log2); - short *data; - unsigned long dataStart; - unsigned long dataEnd; - TIMESTAMP timeStart; - TIMESTAMP timeEnd; - bool isAligned; - - short *currData; ///< internal data buffer when reading from USRP - TIMESTAMP currTimestamp; ///< timestamp of internal data buffer - unsigned currLen; ///< size of internal data buffer - - TIMESTAMP timestampOffset; ///< timestamp offset b/w Tx and Rx blocks - TIMESTAMP latestWriteTimestamp; ///< timestamp of most recent ping command - TIMESTAMP pingTimestamp; ///< timestamp of most recent ping response - static const TIMESTAMP PINGOFFSET = 272; ///< undetermined delay b/w ping response timestamp and true receive timestamp - unsigned long hi32Timestamp; - unsigned long lastPktTimestamp; - - double rxGain; - -#ifdef SWLOOPBACK - short loopbackBuffer[1000000]; - int loopbackBufferSize; - double samplePeriod; - - struct timeval startTime; - struct timeval lastReadTime; - bool firstRead; -#endif - - public: - - /** Object constructor */ - USRPDevice (double _desiredSampleRate, bool skipRx); - - /** Instantiate the USRP */ - bool open(); - - /** Start the USRP */ - bool start(); - - /** Stop the USRP */ - bool stop(); - - /** Set priority not supported */ - void setPriority() { return; } - - /** - Read samples from the USRP. - @param buf preallocated buf to contain read result - @param len number of samples desired - @param overrun Set if read buffer has been overrun, e.g. data not being read fast enough - @param timestamp The timestamp of the first samples to be read - @param underrun Set if USRP does not have data to transmit, e.g. data not being sent fast enough - @param RSSI The received signal strength of the read result - @return The number of samples actually read - */ - int readSamples(short *buf, int len, bool *overrun, - TIMESTAMP timestamp = 0xffffffff, - bool *underrun = NULL, - unsigned *RSSI = NULL); - /** - Write samples to the USRP. - @param buf Contains the data to be written. - @param len number of samples to write. - @param underrun Set if USRP does not have data to transmit, e.g. data not being sent fast enough - @param timestamp The timestamp of the first sample of the data buffer. - @param isControl Set if data is a control packet, e.g. a ping command - @return The number of samples actually written - */ - int writeSamples(short *buf, int len, bool *underrun, - TIMESTAMP timestamp = 0xffffffff, - bool isControl = false); - - /** Update the alignment between the read and write timestamps */ - bool updateAlignment(TIMESTAMP timestamp); - - /** Set the transmitter frequency */ - bool setTxFreq(double wFreq); - - /** Set the receiver frequency */ - bool setRxFreq(double wFreq); - - inline TIMESTAMP initialWriteTimestamp() { return 0; } - inline TIMESTAMP initialReadTimestamp() { return 0; } - - inline double fullScaleInputValue() { return 13500.0; } - inline double fullScaleOutputValue() { return 9450.0; } - - inline double setRxGain(double dB); - inline double getRxGain(void) { return rxGain; } - inline double maxRxGain(void); - inline double minRxGain(void); - - inline double setTxGain(double dB); - inline double maxTxGain(void); - inline double minTxGain(void); - - /** Return internal status values */ - inline double getTxFreq() { return 0;} - inline double getRxFreq() { return 0;} - inline double getSampleRate() {return actualSampleRate;} - inline double numberRead() { return samplesRead; } - inline double numberWritten() { return samplesWritten;} - - - -}; - -#endif // _USRP_DEVICE_H_ - diff --git a/public-trunk/Transceiver/USRPping.cpp b/public-trunk/Transceiver/USRPping.cpp deleted file mode 100644 index 3a8c706..0000000 --- a/public-trunk/Transceiver/USRPping.cpp +++ /dev/null @@ -1,90 +0,0 @@ -/* -* Copyright 2008, 2009 Free Software Foundation, Inc. -* -* This software is distributed under the terms of the GNU Affero Public License. -* See the COPYING file in the main directory for details. -* -* This use of this software may be subject to additional restrictions. -* See the LEGAL file in the main directory for details. - - 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 . - -*/ - - - -#include -#include -#include "Transceiver.h" -#include -#include - -using namespace std; - -ConfigurationTable gConfig; - -int main(int argc, char *argv[]) { - - // Configure logger. - if (argc>1) gLogInit(argv[1]); - else gLogInit("INFO"); - if (argc>2) gSetLogFile(argv[2]); - - RadioDevice *usrp = RadioDevice::make(400e3); - if (!usrp->open()) { - cerr << "Device open failed. Exiting..." << endl; - exit(1); - } - - TIMESTAMP timestamp; - - usrp->setTxFreq(890.0e6); - usrp->setRxFreq(890.0e6); - - usrp->start(); - - LOG(INFO) << "Looping..."; - bool underrun; - - //short data[]={0x00,0x02}; - - usrp->updateAlignment(20000); - usrp->updateAlignment(21000); - - int numpkts = 2; - short data2[156*2*numpkts]; - for (int i = 0; i < 156*numpkts; i++) { - data2[i<<1] = 10000;//4096*cos(2*3.14159*(i % 126)/126); - data2[(i<<1) + 1] = 10000;//4096*sin(2*3.14159*(i % 126)/126); - } - - for (int i = 0; i < 1; i++) - usrp->writeSamples((short*) data2,156*numpkts,&underrun,102000+i*1000); - - timestamp = 19000; - while (1) { - short readBuf[512*2]; - int rd = usrp->readSamples(readBuf,512,&underrun,timestamp); - if (rd) { - LOG(INFO) << "rcvd. data@:" << timestamp; - /*for (int i = 0; i < 512; i++) { - uint32_t *wordPtr = (uint32_t *) &readBuf[2*i]; - *wordPtr = usrp_to_host_u32(*wordPtr); - printf ("%d: %d %d\n", timestamp+i,readBuf[2*i],readBuf[2*i+1]); - } */ - timestamp += rd; - } - } - -} diff --git a/public-trunk/Transceiver/radioInterface.cpp b/public-trunk/Transceiver/radioInterface.cpp deleted file mode 100644 index ff395c1..0000000 --- a/public-trunk/Transceiver/radioInterface.cpp +++ /dev/null @@ -1,431 +0,0 @@ -/* -* Copyright 2008, 2009 Free Software Foundation, Inc. -* -* This software is distributed under the terms of the GNU Affero Public License. -* See the COPYING file in the main directory for details. -* -* This use of this software may be subject to additional restrictions. -* See the LEGAL file in the main directory for details. - - 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 . - -*/ - -//#define NDEBUG -#include "config.h" -#include "radioInterface.h" -#include - - -GSM::Time VectorQueue::nextTime() const -{ - GSM::Time retVal; - mLock.lock(); - while (mQ.size()==0) mWriteSignal.wait(mLock); - retVal = mQ.top()->time(); - mLock.unlock(); - return retVal; -} - -RadioInterface::RadioInterface(RadioDevice *wUsrp, - int wReceiveOffset, - int wSamplesPerSymbol, - GSM::Time wStartTime) - -{ - underrun = false; - - sendHistory = new signalVector(INHISTORY); - rcvHistory = NULL; - sendBuffer = NULL; - rcvBuffer = NULL; - sendLPF = NULL; - rcvLPF = NULL; - mOn = false; - - usrp = wUsrp; - receiveOffset = wReceiveOffset; - samplesPerSymbol = wSamplesPerSymbol; - mClock.set(wStartTime); - powerScaling = 1.0; -} - -RadioInterface::~RadioInterface(void) { - if (sendBuffer!=NULL) delete sendBuffer; - if (rcvBuffer!=NULL) delete rcvBuffer; - if (sendHistory!=NULL) delete sendHistory; - if (rcvHistory!=NULL) delete rcvHistory; - if (sendLPF!=NULL) delete sendLPF; - if (rcvLPF!=NULL) delete rcvLPF; - mTransmitFIFO.clear(); - mReceiveFIFO.clear(); -} - -double RadioInterface::fullScaleInputValue(void) { - return usrp->fullScaleInputValue(); -} - -double RadioInterface::fullScaleOutputValue(void) { - return usrp->fullScaleOutputValue(); -} - -void RadioInterface::setPowerAttenuation(double atten) -{ - double rfGain, digAtten; - - rfGain = usrp->setTxGain(usrp->maxTxGain() - atten); - digAtten = atten - usrp->maxTxGain() + rfGain; - - if (digAtten < 1.0) - powerScaling = 1.0; - else - powerScaling = 1.0/sqrt(pow(10, (digAtten/10.0))); -} - -short *RadioInterface::USRPifyVector(signalVector &wVector) -{ - - short *retVector = new short[2*wVector.size()]; - - signalVector::iterator itr = wVector.begin(); - short *shortItr = retVector; - while (itr < wVector.end()) { - *shortItr++ = itr->real(); - *shortItr++ = itr->imag(); - itr++; - } - - return retVector; - -} - -signalVector *RadioInterface::unUSRPifyVector(short *shortVector, int numSamples) - -{ - - signalVector *newVector = new signalVector(numSamples); - - signalVector::iterator itr = newVector->begin(); - short *shortItr = shortVector; - - while (itr < newVector->end()) { - *itr++ = Complex(*shortItr, *(shortItr+1)); - shortItr += 2; - } - - return newVector; - -} - - -bool started = false; - -#define POLYPHASESPAN 10 - -void RadioInterface::pushBuffer(void) { - - if (sendBuffer->size() < INCHUNK) { - return; - } - - int numChunks = sendBuffer->size()/INCHUNK; - - signalVector* truncatedBuffer = new signalVector(numChunks*INCHUNK); - sendBuffer->segmentCopyTo(*truncatedBuffer,0,numChunks*INCHUNK); - - if (!sendLPF) { - int P = OUTRATE; int Q = INRATE; - float cutoffFreq = (P < Q) ? (1.0/(float) Q) : (1.0/(float) P); - sendLPF = createLPF(cutoffFreq,651,P); - } - - // resample data to USRP sample rate - signalVector *inputVector = new signalVector(*sendHistory,*truncatedBuffer); - signalVector *resampledVector = polyphaseResampleVector(*inputVector, - OUTRATE, - INRATE,sendLPF); - delete inputVector; - - // Set transmit gain and power here. - scaleVector(*resampledVector, powerScaling * usrp->fullScaleInputValue()); - - short *resampledVectorShort = USRPifyVector(*resampledVector); - - // start the USRP when we actually have data to send to the USRP. - if (!started) { - started = true; - LOG(INFO) << "Starting USRP"; - usrp->start(); - LOG(DEBUG) << "USRP started"; - usrp->updateAlignment(10000); - usrp->updateAlignment(10000); - } - - // send resampleVector - writingRadioLock.lock(); - int samplesWritten = usrp->writeSamples(resampledVectorShort+OUTHISTORY*2, - (resampledVector->size()-OUTHISTORY), - &underrun, - writeTimestamp); - //LOG(DEEPDEBUG) << "writeTimestamp: " << writeTimestamp << ", samplesWritten: " << samplesWritten; - writeTimestamp += (TIMESTAMP) samplesWritten; - wroteRadioSignal.signal(); - writingRadioLock.unlock(); - - LOG(DEEPDEBUG) << "converted " << truncatedBuffer->size() - << " transceiver samples into " << samplesWritten - << " radio samples "; - - - delete resampledVector; - delete []resampledVectorShort; - - // update the history of sent data - truncatedBuffer->segmentCopyTo(*sendHistory,truncatedBuffer->size()-INHISTORY, - INHISTORY); - - // update the buffer, i.e. keep the samples we didn't send - signalVector *tmp = sendBuffer; - sendBuffer = new signalVector(sendBuffer->size()-truncatedBuffer->size()); - tmp->segmentCopyTo(*sendBuffer,truncatedBuffer->size(), - sendBuffer->size()); - delete tmp; - delete truncatedBuffer; - -} - - -void RadioInterface::pullBuffer(void) -{ - - writingRadioLock.lock(); - // These timestamps are in samples @ 400 kHz. - while (readTimestamp > writeTimestamp - (TIMESTAMP) 2*OUTCHUNK) { - LOG(DEEPDEBUG) << "waiting..." << readTimestamp << " " << writeTimestamp; - wroteRadioSignal.wait(writingRadioLock); - //wroteRadioSignal.wait(writingRadioLock,1); - } - writingRadioLock.unlock(); - - bool localUnderrun; - - // receive receiveVector - short* shortVector = new short[OUTCHUNK*2]; - int samplesRead = usrp->readSamples(shortVector,OUTCHUNK,&overrun,readTimestamp,&localUnderrun); - underrun |= localUnderrun; - readTimestamp += (TIMESTAMP) samplesRead; - while (samplesRead < OUTCHUNK) { - int oldSamplesRead = samplesRead; - samplesRead += usrp->readSamples(shortVector+2*samplesRead, - OUTCHUNK-samplesRead, - &overrun, - readTimestamp, - &localUnderrun); - underrun |= localUnderrun; - readTimestamp += (TIMESTAMP) (samplesRead - oldSamplesRead); - } - - signalVector *receiveVector = unUSRPifyVector(shortVector,samplesRead); - delete []shortVector; - - if (!rcvLPF) { - int P = INRATE; int Q = OUTRATE; - float cutoffFreq = (P < Q) ? (1.0/(float) Q) : (1.0/(float) P); - rcvLPF = createLPF(cutoffFreq,961,P); - } - - signalVector *retVector = NULL; - - if (!rcvHistory) { - rcvHistory = new signalVector(OUTHISTORY); - rcvHistory->fill(0); - } - - // resample received data to multiple of GSM symbol rate - signalVector inputVector(*rcvHistory,*receiveVector); - retVector = polyphaseResampleVector(inputVector, - INRATE,OUTRATE,rcvLPF); - - // push sampled data to back of receive buffer - signalVector *tmp = retVector; - retVector = new signalVector(retVector->size()-INHISTORY); - tmp->segmentCopyTo(*retVector,INHISTORY,tmp->size()-INHISTORY); - delete tmp; - - LOG(DEEPDEBUG) << "converted " << receiveVector->size() - << " radio samples into " << retVector->size() - << " transceiver samples "; - - // update history of received data - receiveVector->segmentCopyTo(*rcvHistory,receiveVector->size()-OUTHISTORY,OUTHISTORY); - - delete receiveVector; - - if (rcvBuffer) { - signalVector *tmp = rcvBuffer; - rcvBuffer = new signalVector(*tmp,*retVector); - delete tmp; - delete retVector; - } - else - rcvBuffer = retVector; - - -} - -bool RadioInterface::tuneTx(double freq) -{ - if (mOn) return false; - return usrp->setTxFreq(freq); -} - -bool RadioInterface::tuneRx(double freq) -{ - if (mOn) return false; - return usrp->setRxFreq(freq); -} - -double RadioInterface::setRxGain(double dB) -{ - if (usrp) - return usrp->setRxGain(dB); - else - return -1; -} - -double RadioInterface::getRxGain(void) -{ - if (usrp) - return usrp->getRxGain(); - else - return -1; -} - -void RadioInterface::start() -{ - LOG(INFO) << "starting radio interface..."; - writeTimestamp = 20000; - readTimestamp = 20000; - - mTransmitRadioServiceLoopThread.start((void* (*)(void*))TransmitRadioServiceLoopAdapter, - (void*)this); - mReceiveRadioServiceLoopThread.start((void* (*)(void*))ReceiveRadioServiceLoopAdapter, - (void*)this); - mAlignRadioServiceLoopThread.start((void * (*)(void*))AlignRadioServiceLoopAdapter, - (void*)this); - mOn = true; - LOG(DEBUG) << "radio interface started!"; -} - - -void *TransmitRadioServiceLoopAdapter(RadioInterface *radioInterface) -{ - while (1) { - radioInterface->driveTransmitRadio(); - pthread_testcancel(); - } - return NULL; -} - -void *ReceiveRadioServiceLoopAdapter(RadioInterface *radioInterface) -{ - radioInterface->setPriority(); - - while (1) { - radioInterface->driveReceiveRadio(); - pthread_testcancel(); - } - return NULL; -} - -void *AlignRadioServiceLoopAdapter(RadioInterface *radioInterface) -{ - while (1) { - radioInterface->alignRadio(); - pthread_testcancel(); - } - return NULL; -} - -void RadioInterface::alignRadio() { - sleep(60); - usrp->updateAlignment(writeTimestamp+ (TIMESTAMP) 10000); -} - -void RadioInterface::driveTransmitRadio() { - - radioVector *radioBurst = NULL; - - radioBurst = mTransmitFIFO.read(); - - LOG(DEEPDEBUG) << "transmitFIFO: read radio vector at time: " << radioBurst->time(); - - signalVector *newBurst = radioBurst; - if (sendBuffer) { - signalVector *tmp = sendBuffer; - sendBuffer = new signalVector(*sendBuffer,*newBurst); - delete tmp; - } - else - sendBuffer = new signalVector(*newBurst); - - delete radioBurst; - - pushBuffer(); -} - -void RadioInterface::driveReceiveRadio() { - pullBuffer(); - - if (!rcvBuffer) { - return;} - - GSM::Time rcvClock = mClock.get(); - rcvClock.decTN(receiveOffset); - unsigned tN = rcvClock.TN(); - int rcvSz = rcvBuffer->size(); - int readSz = 0; - const int symbolsPerSlot = gSlotLen + 8; - - // while there's enough data in receive buffer, form received - // GSM bursts and pass up to Transceiver - // Using the 157-156-156-156 symbols per timeslot format. - while (rcvSz > (symbolsPerSlot + (tN % 4 == 0))*samplesPerSymbol) { - signalVector rxVector(rcvBuffer->begin(), - readSz, - (symbolsPerSlot + (tN % 4 == 0))*samplesPerSymbol); - GSM::Time tmpTime = rcvClock; - if (rcvClock.FN() >= 0) { - LOG(DEEPDEBUG) << "FN: " << rcvClock.FN(); - radioVector* rxBurst = new radioVector(rxVector,tmpTime); - mReceiveFIFO.write(rxBurst); - } - mClock.incTN(); - rcvClock.incTN(); - if (mReceiveFIFO.size() >= 16) mReceiveFIFO.wait(8); - - LOG(DEEPDEBUG) << "receiveFIFO: wrote radio vector at time: " << mClock.get() << ", new size: " << mReceiveFIFO.size() ; - readSz += (symbolsPerSlot+(tN % 4 == 0))*samplesPerSymbol; - rcvSz -= (symbolsPerSlot+(tN % 4 == 0))*samplesPerSymbol; - - tN = rcvClock.TN(); - } - - signalVector *tmp = new signalVector(rcvBuffer->size()-readSz); - rcvBuffer->segmentCopyTo(*tmp,readSz,tmp->size()); - delete rcvBuffer; - rcvBuffer = tmp; - -} - diff --git a/public-trunk/Transceiver/radioInterface.h b/public-trunk/Transceiver/radioInterface.h deleted file mode 100644 index e2d4336..0000000 --- a/public-trunk/Transceiver/radioInterface.h +++ /dev/null @@ -1,240 +0,0 @@ -/* -* Copyright 2008 Free Software Foundation, Inc. -* -* This software is distributed under the terms of the GNU Affero Public License. -* See the COPYING file in the main directory for details. -* -* This use of this software may be subject to additional restrictions. -* See the LEGAL file in the main directory for details. - - 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 . - -*/ - - - -#include "sigProcLib.h" -#include "../Transceiver52M/radioDevice.h" -#include "GSMCommon.h" -#include "Interthread.h" - -/** samples per GSM symbol */ -#define SAMPSPERSYM 1 - -/** parameters for polyphase resampling */ -#define INRATE (65*SAMPSPERSYM) -#define OUTRATE (96) -#define INHISTORY (INRATE*2) -#define OUTHISTORY (OUTRATE*2) -#define INCHUNK (INRATE*9) -#define OUTCHUNK (OUTRATE*9) - -/** class used to organize GSM bursts by GSM timestamps */ -class radioVector : public signalVector { - -private: - - GSM::Time mTime; ///< the burst's GSM timestamp - -public: - /** constructor */ - radioVector(const signalVector& wVector, - GSM::Time& wTime): signalVector(wVector),mTime(wTime) {}; - - /** timestamp read and write operators */ - GSM::Time time() const { return mTime;} - void time(const GSM::Time& wTime) { mTime = wTime;} - - /** comparison operator, used for sorting */ - bool operator>(const radioVector& other) const {return mTime > other.mTime;} - -}; - -/** a priority queue of radioVectors, i.e. GSM bursts, sorted so that earliest element is at top */ -class VectorQueue : public InterthreadPriorityQueue { - -public: - - /** the top element of the queue */ - GSM::Time nextTime() const; - -}; - -/** a FIFO of radioVectors */ -class VectorFIFO : public InterthreadQueueWithWait {}; - -/** the basestation clock class */ -class RadioClock { - -private: - - GSM::Time mClock; - Mutex mLock; - Signal updateSignal; - -public: - - /** Set clock */ - void set(const GSM::Time& wTime) { mLock.lock(); mClock = wTime; updateSignal.signal(); mLock.unlock();} - //void set(const GSM::Time& wTime) { mLock.lock(); mClock = wTime; updateSignal.broadcast(); mLock.unlock();} - - /** Increment clock */ - void incTN() { mLock.lock(); mClock.incTN(); updateSignal.signal(); mLock.unlock();} - //void incTN() { mLock.lock(); mClock.incTN(); updateSignal.broadcast(); mLock.unlock();} - - /** Get clock value */ - GSM::Time get() { mLock.lock(); GSM::Time retVal = mClock; mLock.unlock(); return retVal;} - - /** Wait until clock has changed */ - void wait() {mLock.lock(); updateSignal.wait(mLock,1); mLock.unlock();} - // FIXME -- If we take away the timeout, a lot of threads don't start. Why? - //void wait() {mLock.lock(); updateSignal.wait(mLock); mLock.unlock();} - -}; - - -/** class to interface the transceiver with the USRP */ -class RadioInterface { - -private: - - Thread mTransmitRadioServiceLoopThread; ///< thread that handles transmission of GSM bursts - Thread mReceiveRadioServiceLoopThread; ///< thread that handles reception of GSM bursts - Thread mAlignRadioServiceLoopThread; ///< thread that synchronizes transmit and receive sections - - VectorFIFO mTransmitFIFO; ///< FIFO that holds transmit bursts - VectorFIFO mReceiveFIFO; ///< FIFO that holds receive bursts - - signalVector* sendHistory; ///< block of previous transmitted samples - signalVector* rcvHistory; ///< block of previous received samples - - RadioDevice *usrp; ///< the USRP object - - signalVector* sendBuffer; ///< block of samples to be transmitted - signalVector* rcvBuffer; ///< block of received samples to be processed - - signalVector* sendLPF; ///< polyphase filter for resampling transmit bursts - signalVector* rcvLPF; ///< polyphase filter for resampling receive bursts - - mutable Signal wroteRadioSignal; ///< signal that indicates samples sent to USRP - mutable Mutex writingRadioLock; ///< mutex to lock receive thread when transmit thread is writing - - bool underrun; ///< indicates writes to USRP are too slow - bool overrun; ///< indicates reads from USRP are too slow - TIMESTAMP writeTimestamp; ///< sample timestamp of next packet written to USRP - TIMESTAMP readTimestamp; ///< sample timestamp of next packet read from USRP - - RadioClock mClock; ///< the basestation clock! - - int samplesPerSymbol; ///< samples per GSM symbol - int receiveOffset; ///< offset b/w transmit and receive GSM timestamps, in timeslots - - bool mOn; ///< indicates radio is on - - float powerScaling; - - /** format samples to USRP */ - short *USRPifyVector(signalVector &wVector); - - /** format samples from USRP */ - signalVector *unUSRPifyVector(short *shortVector, int numSamples); - - /** push GSM bursts into the transmit buffer */ - void pushBuffer(void); - - /** pull GSM bursts from the receive buffer */ - void pullBuffer(void); - -public: - - /** start the interface */ - void start(); - - /** constructor */ - RadioInterface(RadioDevice* wUsrp = NULL, - int receiveOffset = 3, - int wSamplesPerSymbol = SAMPSPERSYM, - GSM::Time wStartTime = GSM::Time(0)); - - /** destructor */ - ~RadioInterface(); - - /** check for underrun, resets underrun value */ - bool isUnderrun() { bool retVal = underrun; underrun = false; return retVal;} - - /** attach an existing USRP to this interface */ - void attach(RadioDevice *wUsrp) {if (!mOn) usrp = wUsrp;} - - /** return the transmit FIFO */ - VectorFIFO* transmitFIFO() { return &mTransmitFIFO;} - - /** return the receive FIFO */ - VectorFIFO* receiveFIFO() { return &mReceiveFIFO;} - - /** return the basestation clock */ - RadioClock* getClock(void) { return &mClock;}; - - /** set transmit frequency */ - bool tuneTx(double freq); - - /** set receive frequency */ - bool tuneRx(double freq); - - /** set receive gain */ - double setRxGain(double dB); - - /** get receive gain */ - double getRxGain(void); - - /** drive transmission of GSM bursts */ - void driveTransmitRadio(); - - /** drive reception of GSM bursts */ - void driveReceiveRadio(); - - void setPowerAttenuation(double atten); - - /** returns the full-scale transmit amplitude **/ - double fullScaleInputValue(); - - /** returns the full-scale receive amplitude **/ - double fullScaleOutputValue(); - - /** set thread priority */ - void setPriority() { usrp->setPriority(); } - -protected: - - /** drive synchronization of Tx/Rx of USRP */ - void alignRadio(); - - /** reset the interface */ - void reset(); - - friend void *TransmitRadioServiceLoopAdapter(RadioInterface*); - - friend void *ReceiveRadioServiceLoopAdapter(RadioInterface*); - - friend void *AlignRadioServiceLoopAdapter(RadioInterface*); - -}; - -/** transmit thread loop */ -void *TransmitRadioServiceLoopAdapter(RadioInterface*); - -/** receive thread loop */ -void *ReceiveRadioServiceLoopAdapter(RadioInterface*); - -/** synchronization thread loop */ -void *AlignRadioServiceLoopAdapter(RadioInterface*); diff --git a/public-trunk/Transceiver/rcvLPF_651.h b/public-trunk/Transceiver/rcvLPF_651.h deleted file mode 100644 index c718a1b..0000000 --- a/public-trunk/Transceiver/rcvLPF_651.h +++ /dev/null @@ -1,29 +0,0 @@ -/* -* Copyright 2008 Free Software Foundation, Inc. -* Copyright 2009 Free Software Foundation, Inc. -* -* This software is distributed under the terms of the GNU Affero Public License. -* See the COPYING file in the main directory for details. -* -* This use of this software may be subject to additional restrictions. -* See the LEGAL file in the main directory for details. - - 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 . - -*/ - - - - -float rcvLPF_651[] = { -0.000920,-0.000928,-0.000935,-0.000941,-0.000946,-0.000950,-0.000953,-0.000954,-0.000955,-0.000954,-0.000952,-0.000949,-0.000945,-0.000940,-0.000933,-0.000926,-0.000917,-0.000907,-0.000896,-0.000884,-0.000871,-0.000856,-0.000841,-0.000824,-0.000806,-0.000788,-0.000768,-0.000747,-0.000725,-0.000702,-0.000678,-0.000653,-0.000627,-0.000600,-0.000572,-0.000543,-0.000514,-0.000483,-0.000452,-0.000420,-0.000388,-0.000354,-0.000320,-0.000285,-0.000250,-0.000214,-0.000178,-0.000141,-0.000103,-0.000066,-0.000027,0.000011,0.000050,0.000089,0.000128,0.000167,0.000207,0.000246,0.000286,0.000326,0.000365,0.000404,0.000444,0.000483,0.000521,0.000560,0.000598,0.000636,0.000673,0.000710,0.000746,0.000782,0.000817,0.000851,0.000884,0.000917,0.000949,0.000981,0.001011,0.001040,0.001068,0.001096,0.001122,0.001147,0.001171,0.001194,0.001216,0.001236,0.001255,0.001273,0.001289,0.001304,0.001318,0.001330,0.001341,0.001350,0.001358,0.001364,0.001368,0.001371,0.001373,0.001372,0.001370,0.001367,0.001362,0.001355,0.001346,0.001336,0.001324,0.001311,0.001295,0.001278,0.001260,0.001239,0.001217,0.001194,0.001168,0.001141,0.001113,0.001083,0.001051,0.001017,0.000982,0.000946,0.000908,0.000869,0.000828,0.000785,0.000742,0.000697,0.000650,0.000603,0.000554,0.000504,0.000453,0.000401,0.000347,0.000293,0.000238,0.000182,0.000125,0.000067,0.000008,-0.000051,-0.000111,-0.000171,-0.000232,-0.000293,-0.000354,-0.000416,-0.000479,-0.000541,-0.000603,-0.000666,-0.000728,-0.000790,-0.000852,-0.000914,-0.000976,-0.001037,-0.001097,-0.001157,-0.001217,-0.001276,-0.001334,-0.001391,-0.001447,-0.001502,-0.001556,-0.001609,-0.001661,-0.001712,-0.001761,-0.001808,-0.001855,-0.001899,-0.001942,-0.001983,-0.002023,-0.002060,-0.002096,-0.002130,-0.002161,-0.002191,-0.002218,-0.002243,-0.002266,-0.002286,-0.002304,-0.002319,-0.002332,-0.002343,-0.002350,-0.002355,-0.002358,-0.002357,-0.002354,-0.002348,-0.002339,-0.002327,-0.002312,-0.002294,-0.002273,-0.002249,-0.002222,-0.002191,-0.002158,-0.002121,-0.002082,-0.002039,-0.001993,-0.001944,-0.001891,-0.001835,-0.001777,-0.001714,-0.001649,-0.001581,-0.001509,-0.001434,-0.001356,-0.001275,-0.001191,-0.001104,-0.001013,-0.000920,-0.000823,-0.000724,-0.000622,-0.000517,-0.000409,-0.000298,-0.000184,-0.000068,0.000051,0.000173,0.000297,0.000424,0.000553,0.000684,0.000818,0.000954,0.001092,0.001232,0.001374,0.001518,0.001664,0.001812,0.001961,0.002112,0.002265,0.002419,0.002574,0.002731,0.002888,0.003047,0.003207,0.003367,0.003529,0.003691,0.003853,0.004016,0.004180,0.004343,0.004507,0.004671,0.004835,0.004999,0.005162,0.005325,0.005488,0.005650,0.005811,0.005972,0.006132,0.006290,0.006448,0.006604,0.006759,0.006913,0.007065,0.007216,0.007364,0.007511,0.007656,0.007799,0.007940,0.008079,0.008215,0.008349,0.008481,0.008609,0.008736,0.008859,0.008980,0.009097,0.009212,0.009323,0.009432,0.009537,0.009638,0.009737,0.009832,0.009923,0.010011,0.010095,0.010175,0.010252,0.010325,0.010394,0.010459,0.010520,0.010577,0.010630,0.010678,0.010723,0.010764,0.010800,0.010832,0.010860,0.010884,0.010903,0.010918,0.010929,0.010935,0.010937,0.010935,0.010929,0.010918,0.010903,0.010884,0.010860,0.010832,0.010800,0.010764,0.010723,0.010678,0.010630,0.010577,0.010520,0.010459,0.010394,0.010325,0.010252,0.010175,0.010095,0.010011,0.009923,0.009832,0.009737,0.009638,0.009537,0.009432,0.009323,0.009212,0.009097,0.008980,0.008859,0.008736,0.008609,0.008481,0.008349,0.008215,0.008079,0.007940,0.007799,0.007656,0.007511,0.007364,0.007216,0.007065,0.006913,0.006759,0.006604,0.006448,0.006290,0.006132,0.005972,0.005811,0.005650,0.005488,0.005325,0.005162,0.004999,0.004835,0.004671,0.004507,0.004343,0.004180,0.004016,0.003853,0.003691,0.003529,0.003367,0.003207,0.003047,0.002888,0.002731,0.002574,0.002419,0.002265,0.002112,0.001961,0.001812,0.001664,0.001518,0.001374,0.001232,0.001092,0.000954,0.000818,0.000684,0.000553,0.000424,0.000297,0.000173,0.000051,-0.000068,-0.000184,-0.000298,-0.000409,-0.000517,-0.000622,-0.000724,-0.000823,-0.000920,-0.001013,-0.001104,-0.001191,-0.001275,-0.001356,-0.001434,-0.001509,-0.001581,-0.001649,-0.001714,-0.001777,-0.001835,-0.001891,-0.001944,-0.001993,-0.002039,-0.002082,-0.002121,-0.002158,-0.002191,-0.002222,-0.002249,-0.002273,-0.002294,-0.002312,-0.002327,-0.002339,-0.002348,-0.002354,-0.002357,-0.002358,-0.002355,-0.002350,-0.002343,-0.002332,-0.002319,-0.002304,-0.002286,-0.002266,-0.002243,-0.002218,-0.002191,-0.002161,-0.002130,-0.002096,-0.002060,-0.002023,-0.001983,-0.001942,-0.001899,-0.001855,-0.001808,-0.001761,-0.001712,-0.001661,-0.001609,-0.001556,-0.001502,-0.001447,-0.001391,-0.001334,-0.001276,-0.001217,-0.001157,-0.001097,-0.001037,-0.000976,-0.000914,-0.000852,-0.000790,-0.000728,-0.000666,-0.000603,-0.000541,-0.000479,-0.000416,-0.000354,-0.000293,-0.000232,-0.000171,-0.000111,-0.000051,0.000008,0.000067,0.000125,0.000182,0.000238,0.000293,0.000347,0.000401,0.000453,0.000504,0.000554,0.000603,0.000650,0.000697,0.000742,0.000785,0.000828,0.000869,0.000908,0.000946,0.000982,0.001017,0.001051,0.001083,0.001113,0.001141,0.001168,0.001194,0.001217,0.001239,0.001260,0.001278,0.001295,0.001311,0.001324,0.001336,0.001346,0.001355,0.001362,0.001367,0.001370,0.001372,0.001373,0.001371,0.001368,0.001364,0.001358,0.001350,0.001341,0.001330,0.001318,0.001304,0.001289,0.001273,0.001255,0.001236,0.001216,0.001194,0.001171,0.001147,0.001122,0.001096,0.001068,0.001040,0.001011,0.000981,0.000949,0.000917,0.000884,0.000851,0.000817,0.000782,0.000746,0.000710,0.000673,0.000636,0.000598,0.000560,0.000521,0.000483,0.000444,0.000404,0.000365,0.000326,0.000286,0.000246,0.000207,0.000167,0.000128,0.000089,0.000050,0.000011,-0.000027,-0.000066,-0.000103,-0.000141,-0.000178,-0.000214,-0.000250,-0.000285,-0.000320,-0.000354,-0.000388,-0.000420,-0.000452,-0.000483,-0.000514,-0.000543,-0.000572,-0.000600,-0.000627,-0.000653,-0.000678,-0.000702,-0.000725,-0.000747,-0.000768,-0.000788,-0.000806,-0.000824,-0.000841,-0.000856,-0.000871,-0.000884,-0.000896,-0.000907,-0.000917,-0.000926,-0.000933,-0.000940,-0.000945,-0.000949,-0.000952,-0.000954,-0.000955,-0.000954,-0.000953,-0.000950,-0.000946,-0.000941,-0.000935,-0.000928, 0.0}; diff --git a/public-trunk/Transceiver/runTransceiver.cpp b/public-trunk/Transceiver/runTransceiver.cpp deleted file mode 100644 index 1373fc1..0000000 --- a/public-trunk/Transceiver/runTransceiver.cpp +++ /dev/null @@ -1,66 +0,0 @@ -/* -* Copyright 2008, 2009 Free Software Foundation, Inc. -* -* This software is distributed under the terms of the GNU Affero Public License. -* See the COPYING file in the main directory for details. -* -* This use of this software may be subject to additional restrictions. -* See the LEGAL file in the main directory for details. - - 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 . - -*/ - - - - -#include "Transceiver.h" -#include "GSMCommon.h" -#include - -#include -#include - -ConfigurationTable gConfig; - -using namespace std; - -int main(int argc, char *argv[]) { - - // Configure logger. - if (argc<2) { - cerr << argv[0] << " [logFilePath]" << endl; - cerr << "Log levels are ERROR, ALARM, WARN, NOTICE, INFO, DEBUG, DEEPDEBUG" << endl; - exit(0); - } - gLogInit(argv[1]); - if (argc>2) gSetLogFile(argv[2]); - - srandom(time(NULL)); - - RadioDevice *usrp = RadioDevice::make(400.0e3); - if (!usrp->open()) { - cerr << "Device open failed. Exiting..." << endl; - exit(1); - } - - RadioInterface* radio = new RadioInterface(usrp,3); - Transceiver *trx = new Transceiver(5700,"127.0.0.1",SAMPSPERSYM,GSM::Time(3,0),radio); - trx->transmitFIFO(radio->transmitFIFO()); - trx->receiveFIFO(radio->receiveFIFO()); - - trx->start(); - //int i = 0; - while(1) { sleep(1); }//i++; if (i==60) break;} -} diff --git a/public-trunk/Transceiver/sendLPF_961.h b/public-trunk/Transceiver/sendLPF_961.h deleted file mode 100644 index ea3d674..0000000 --- a/public-trunk/Transceiver/sendLPF_961.h +++ /dev/null @@ -1,27 +0,0 @@ -/* -* Copyright 2008 Free Software Foundation, Inc. -* -* This software is distributed under the terms of the GNU Affero Public License. -* See the COPYING file in the main directory for details. -* -* This use of this software may be subject to additional restrictions. -* See the LEGAL file in the main directory for details. - - 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 . - -*/ - - - -float sendLPF_961[] = { -0.000422,-0.000408,-0.000394,-0.000379,-0.000364,-0.000348,-0.000332,-0.000315,-0.000298,-0.000280,-0.000262,-0.000243,-0.000224,-0.000205,-0.000185,-0.000165,-0.000145,-0.000125,-0.000104,-0.000083,-0.000062,-0.000040,-0.000019,0.000003,0.000025,0.000047,0.000069,0.000091,0.000113,0.000135,0.000157,0.000179,0.000200,0.000222,0.000244,0.000265,0.000286,0.000307,0.000328,0.000348,0.000368,0.000388,0.000407,0.000426,0.000445,0.000463,0.000481,0.000498,0.000515,0.000531,0.000547,0.000562,0.000576,0.000590,0.000604,0.000616,0.000628,0.000640,0.000650,0.000660,0.000669,0.000678,0.000686,0.000693,0.000699,0.000704,0.000709,0.000712,0.000715,0.000717,0.000719,0.000719,0.000718,0.000717,0.000715,0.000712,0.000708,0.000703,0.000698,0.000691,0.000684,0.000676,0.000667,0.000657,0.000646,0.000634,0.000622,0.000609,0.000595,0.000580,0.000565,0.000548,0.000531,0.000513,0.000495,0.000476,0.000456,0.000435,0.000414,0.000392,0.000370,0.000347,0.000323,0.000299,0.000275,0.000250,0.000224,0.000199,0.000172,0.000146,0.000119,0.000091,0.000064,0.000036,0.000008,-0.000020,-0.000048,-0.000077,-0.000105,-0.000134,-0.000163,-0.000191,-0.000220,-0.000248,-0.000277,-0.000305,-0.000333,-0.000361,-0.000388,-0.000415,-0.000442,-0.000469,-0.000495,-0.000521,-0.000546,-0.000571,-0.000595,-0.000619,-0.000642,-0.000665,-0.000687,-0.000708,-0.000729,-0.000749,-0.000768,-0.000786,-0.000803,-0.000820,-0.000836,-0.000851,-0.000865,-0.000878,-0.000890,-0.000901,-0.000911,-0.000920,-0.000928,-0.000935,-0.000941,-0.000946,-0.000950,-0.000953,-0.000954,-0.000955,-0.000954,-0.000952,-0.000949,-0.000945,-0.000940,-0.000933,-0.000926,-0.000917,-0.000907,-0.000896,-0.000884,-0.000871,-0.000856,-0.000841,-0.000824,-0.000806,-0.000788,-0.000768,-0.000747,-0.000725,-0.000702,-0.000678,-0.000653,-0.000627,-0.000600,-0.000572,-0.000543,-0.000514,-0.000483,-0.000452,-0.000420,-0.000388,-0.000354,-0.000320,-0.000285,-0.000250,-0.000214,-0.000178,-0.000141,-0.000103,-0.000066,-0.000027,0.000011,0.000050,0.000089,0.000128,0.000167,0.000207,0.000246,0.000286,0.000326,0.000365,0.000404,0.000444,0.000483,0.000521,0.000560,0.000598,0.000636,0.000673,0.000710,0.000746,0.000782,0.000817,0.000851,0.000884,0.000917,0.000949,0.000981,0.001011,0.001040,0.001068,0.001096,0.001122,0.001147,0.001171,0.001194,0.001216,0.001236,0.001255,0.001273,0.001289,0.001304,0.001318,0.001330,0.001341,0.001350,0.001358,0.001364,0.001368,0.001371,0.001373,0.001372,0.001370,0.001367,0.001362,0.001355,0.001346,0.001336,0.001324,0.001311,0.001295,0.001278,0.001260,0.001239,0.001217,0.001194,0.001168,0.001141,0.001113,0.001083,0.001051,0.001017,0.000982,0.000946,0.000908,0.000869,0.000828,0.000785,0.000742,0.000697,0.000650,0.000603,0.000554,0.000504,0.000453,0.000401,0.000347,0.000293,0.000238,0.000182,0.000125,0.000067,0.000008,-0.000051,-0.000111,-0.000171,-0.000232,-0.000293,-0.000354,-0.000416,-0.000479,-0.000541,-0.000603,-0.000666,-0.000728,-0.000790,-0.000852,-0.000914,-0.000976,-0.001037,-0.001097,-0.001157,-0.001217,-0.001276,-0.001334,-0.001391,-0.001447,-0.001502,-0.001556,-0.001609,-0.001661,-0.001712,-0.001761,-0.001808,-0.001855,-0.001899,-0.001942,-0.001983,-0.002023,-0.002060,-0.002096,-0.002130,-0.002161,-0.002191,-0.002218,-0.002243,-0.002266,-0.002286,-0.002304,-0.002319,-0.002332,-0.002343,-0.002350,-0.002355,-0.002358,-0.002357,-0.002354,-0.002348,-0.002339,-0.002327,-0.002312,-0.002294,-0.002273,-0.002249,-0.002222,-0.002191,-0.002158,-0.002121,-0.002082,-0.002039,-0.001993,-0.001944,-0.001891,-0.001835,-0.001777,-0.001714,-0.001649,-0.001581,-0.001509,-0.001434,-0.001356,-0.001275,-0.001191,-0.001104,-0.001013,-0.000920,-0.000823,-0.000724,-0.000622,-0.000517,-0.000409,-0.000298,-0.000184,-0.000068,0.000051,0.000173,0.000297,0.000424,0.000553,0.000684,0.000818,0.000954,0.001092,0.001232,0.001374,0.001518,0.001664,0.001812,0.001961,0.002112,0.002265,0.002419,0.002574,0.002731,0.002888,0.003047,0.003207,0.003367,0.003529,0.003691,0.003853,0.004016,0.004180,0.004343,0.004507,0.004671,0.004835,0.004999,0.005162,0.005325,0.005488,0.005650,0.005811,0.005972,0.006132,0.006290,0.006448,0.006604,0.006759,0.006913,0.007065,0.007216,0.007364,0.007511,0.007656,0.007799,0.007940,0.008079,0.008215,0.008349,0.008481,0.008609,0.008736,0.008859,0.008980,0.009097,0.009212,0.009323,0.009432,0.009537,0.009638,0.009737,0.009832,0.009923,0.010011,0.010095,0.010175,0.010252,0.010325,0.010394,0.010459,0.010520,0.010577,0.010630,0.010678,0.010723,0.010764,0.010800,0.010832,0.010860,0.010884,0.010903,0.010918,0.010929,0.010935,0.010937,0.010935,0.010929,0.010918,0.010903,0.010884,0.010860,0.010832,0.010800,0.010764,0.010723,0.010678,0.010630,0.010577,0.010520,0.010459,0.010394,0.010325,0.010252,0.010175,0.010095,0.010011,0.009923,0.009832,0.009737,0.009638,0.009537,0.009432,0.009323,0.009212,0.009097,0.008980,0.008859,0.008736,0.008609,0.008481,0.008349,0.008215,0.008079,0.007940,0.007799,0.007656,0.007511,0.007364,0.007216,0.007065,0.006913,0.006759,0.006604,0.006448,0.006290,0.006132,0.005972,0.005811,0.005650,0.005488,0.005325,0.005162,0.004999,0.004835,0.004671,0.004507,0.004343,0.004180,0.004016,0.003853,0.003691,0.003529,0.003367,0.003207,0.003047,0.002888,0.002731,0.002574,0.002419,0.002265,0.002112,0.001961,0.001812,0.001664,0.001518,0.001374,0.001232,0.001092,0.000954,0.000818,0.000684,0.000553,0.000424,0.000297,0.000173,0.000051,-0.000068,-0.000184,-0.000298,-0.000409,-0.000517,-0.000622,-0.000724,-0.000823,-0.000920,-0.001013,-0.001104,-0.001191,-0.001275,-0.001356,-0.001434,-0.001509,-0.001581,-0.001649,-0.001714,-0.001777,-0.001835,-0.001891,-0.001944,-0.001993,-0.002039,-0.002082,-0.002121,-0.002158,-0.002191,-0.002222,-0.002249,-0.002273,-0.002294,-0.002312,-0.002327,-0.002339,-0.002348,-0.002354,-0.002357,-0.002358,-0.002355,-0.002350,-0.002343,-0.002332,-0.002319,-0.002304,-0.002286,-0.002266,-0.002243,-0.002218,-0.002191,-0.002161,-0.002130,-0.002096,-0.002060,-0.002023,-0.001983,-0.001942,-0.001899,-0.001855,-0.001808,-0.001761,-0.001712,-0.001661,-0.001609,-0.001556,-0.001502,-0.001447,-0.001391,-0.001334,-0.001276,-0.001217,-0.001157,-0.001097,-0.001037,-0.000976,-0.000914,-0.000852,-0.000790,-0.000728,-0.000666,-0.000603,-0.000541,-0.000479,-0.000416,-0.000354,-0.000293,-0.000232,-0.000171,-0.000111,-0.000051,0.000008,0.000067,0.000125,0.000182,0.000238,0.000293,0.000347,0.000401,0.000453,0.000504,0.000554,0.000603,0.000650,0.000697,0.000742,0.000785,0.000828,0.000869,0.000908,0.000946,0.000982,0.001017,0.001051,0.001083,0.001113,0.001141,0.001168,0.001194,0.001217,0.001239,0.001260,0.001278,0.001295,0.001311,0.001324,0.001336,0.001346,0.001355,0.001362,0.001367,0.001370,0.001372,0.001373,0.001371,0.001368,0.001364,0.001358,0.001350,0.001341,0.001330,0.001318,0.001304,0.001289,0.001273,0.001255,0.001236,0.001216,0.001194,0.001171,0.001147,0.001122,0.001096,0.001068,0.001040,0.001011,0.000981,0.000949,0.000917,0.000884,0.000851,0.000817,0.000782,0.000746,0.000710,0.000673,0.000636,0.000598,0.000560,0.000521,0.000483,0.000444,0.000404,0.000365,0.000326,0.000286,0.000246,0.000207,0.000167,0.000128,0.000089,0.000050,0.000011,-0.000027,-0.000066,-0.000103,-0.000141,-0.000178,-0.000214,-0.000250,-0.000285,-0.000320,-0.000354,-0.000388,-0.000420,-0.000452,-0.000483,-0.000514,-0.000543,-0.000572,-0.000600,-0.000627,-0.000653,-0.000678,-0.000702,-0.000725,-0.000747,-0.000768,-0.000788,-0.000806,-0.000824,-0.000841,-0.000856,-0.000871,-0.000884,-0.000896,-0.000907,-0.000917,-0.000926,-0.000933,-0.000940,-0.000945,-0.000949,-0.000952,-0.000954,-0.000955,-0.000954,-0.000953,-0.000950,-0.000946,-0.000941,-0.000935,-0.000928,-0.000920,-0.000911,-0.000901,-0.000890,-0.000878,-0.000865,-0.000851,-0.000836,-0.000820,-0.000803,-0.000786,-0.000768,-0.000749,-0.000729,-0.000708,-0.000687,-0.000665,-0.000642,-0.000619,-0.000595,-0.000571,-0.000546,-0.000521,-0.000495,-0.000469,-0.000442,-0.000415,-0.000388,-0.000361,-0.000333,-0.000305,-0.000277,-0.000248,-0.000220,-0.000191,-0.000163,-0.000134,-0.000105,-0.000077,-0.000048,-0.000020,0.000008,0.000036,0.000064,0.000091,0.000119,0.000146,0.000172,0.000199,0.000224,0.000250,0.000275,0.000299,0.000323,0.000347,0.000370,0.000392,0.000414,0.000435,0.000456,0.000476,0.000495,0.000513,0.000531,0.000548,0.000565,0.000580,0.000595,0.000609,0.000622,0.000634,0.000646,0.000657,0.000667,0.000676,0.000684,0.000691,0.000698,0.000703,0.000708,0.000712,0.000715,0.000717,0.000718,0.000719,0.000719,0.000717,0.000715,0.000712,0.000709,0.000704,0.000699,0.000693,0.000686,0.000678,0.000669,0.000660,0.000650,0.000640,0.000628,0.000616,0.000604,0.000590,0.000576,0.000562,0.000547,0.000531,0.000515,0.000498,0.000481,0.000463,0.000445,0.000426,0.000407,0.000388,0.000368,0.000348,0.000328,0.000307,0.000286,0.000265,0.000244,0.000222,0.000200,0.000179,0.000157,0.000135,0.000113,0.000091,0.000069,0.000047,0.000025,0.000003,-0.000019,-0.000040,-0.000062,-0.000083,-0.000104,-0.000125,-0.000145,-0.000165,-0.000185,-0.000205,-0.000224,-0.000243,-0.000262,-0.000280,-0.000298,-0.000315,-0.000332,-0.000348,-0.000364,-0.000379,-0.000394,-0.000408}; diff --git a/public-trunk/Transceiver/sigProcLib.cpp b/public-trunk/Transceiver/sigProcLib.cpp deleted file mode 100644 index 086fe3c..0000000 --- a/public-trunk/Transceiver/sigProcLib.cpp +++ /dev/null @@ -1,1399 +0,0 @@ -/* -* Copyright 2008 Free Software Foundation, Inc. -* -* This software is distributed under the terms of the GNU Affero Public License. -* See the COPYING file in the main directory for details. -* -* This use of this software may be subject to additional restrictions. -* See the LEGAL file in the main directory for details. - - 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 . - -*/ - - - -#define NDEBUG - -#include "sigProcLib.h" -#include "GSMCommon.h" -#include "sendLPF_961.h" -#include "rcvLPF_651.h" - -#include - -#define TABLESIZE 1024 - -/** Lookup tables for trigonometric approximation */ -float cosTable[TABLESIZE+1]; // add 1 element for wrap around -float sinTable[TABLESIZE+1]; - -/** Constants */ -static const float M_PI_F = (float)M_PI; -static const float M_2PI_F = (float)(2.0*M_PI); -static const float M_1_2PI_F = 1/M_2PI_F; - -/** Static vectors that contain a precomputed +/- f_b/4 sinusoid */ -signalVector *GMSKRotation = NULL; -signalVector *GMSKReverseRotation = NULL; - -/** Static ideal RACH and midamble correlation waveforms */ -typedef struct { - signalVector *sequence; - float TOA; - complex gain; -} CorrelationSequence; - -CorrelationSequence *gMidambles[] = {NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL}; -CorrelationSequence *gRACHSequence = NULL; - -void sigProcLibDestroy(void) { - if (GMSKRotation) { - delete GMSKRotation; - GMSKRotation = NULL; - } - if (GMSKReverseRotation) { - delete GMSKReverseRotation; - GMSKReverseRotation = NULL; - } - for (int i = 0; i < 8; i++) { - if (gMidambles[i]!=NULL) { - if (gMidambles[i]->sequence) delete gMidambles[i]->sequence; - delete gMidambles[i]; - gMidambles[i] = NULL; - } - } - if (gRACHSequence) { - if (gRACHSequence->sequence) delete gRACHSequence->sequence; - delete gRACHSequence; - gRACHSequence = NULL; - } -} - - - -// dB relative to 1.0. -// if > 1.0, then return 0 dB -float dB(float x) { - - float arg = 1.0F; - float dB = 0.0F; - - if (x >= 1.0F) return 0.0F; - if (x <= 0.0F) return -200.0F; - - float prevArg = arg; - float prevdB = dB; - float stepSize = 16.0F; - float dBstepSize = 12.0F; - while (stepSize > 1.0F) { - do { - prevArg = arg; - prevdB = dB; - arg /= stepSize; - dB -= dBstepSize; - } while (arg > x); - arg = prevArg; - dB = prevdB; - stepSize *= 0.5F; - dBstepSize -= 3.0F; - } - return ((arg-x)*(dB-3.0F) + (x-arg*0.5F)*dB)/(arg - arg*0.5F); - -} - -// 10^(-dB/10), inverse of dB func. -float dBinv(float x) { - - float arg = 1.0F; - float dB = 0.0F; - - if (x >= 0.0F) return 1.0F; - if (x <= -200.0F) return 0.0F; - - float prevArg = arg; - float prevdB = dB; - float stepSize = 16.0F; - float dBstepSize = 12.0F; - while (stepSize > 1.0F) { - do { - prevArg = arg; - prevdB = dB; - arg /= stepSize; - dB -= dBstepSize; - } while (dB > x); - arg = prevArg; - dB = prevdB; - stepSize *= 0.5F; - dBstepSize -= 3.0F; - } - - return ((dB-x)*(arg*0.5F)+(x-(dB-3.0F))*(arg))/3.0F; - -} - -float vectorNorm2(const signalVector &x) -{ - signalVector::const_iterator xPtr = x.begin(); - float Energy = 0.0; - for (;xPtr != x.end();xPtr++) { - Energy += xPtr->norm2(); - } - return Energy; -} - - -float vectorPower(const signalVector &x) -{ - return vectorNorm2(x)/x.size(); -} - -/** compute cosine via lookup table */ -float cosLookup(const float x) -{ - float arg = x*M_1_2PI_F; - while (arg > 1.0F) arg -= 1.0F; - while (arg < 0.0F) arg += 1.0F; - - const float argT = arg*((float)TABLESIZE); - const int argI = (int)argT; - const float delta = argT-argI; - const float iDelta = 1.0F-delta; - return iDelta*cosTable[argI] + delta*cosTable[argI+1]; -} - -/** compute sine via lookup table */ -float sinLookup(const float x) -{ - float arg = x*M_1_2PI_F; - while (arg > 1.0F) arg -= 1.0F; - while (arg < 0.0F) arg += 1.0F; - - const float argT = arg*((float)TABLESIZE); - const int argI = (int)argT; - const float delta = argT-argI; - const float iDelta = 1.0F-delta; - return iDelta*sinTable[argI] + delta*sinTable[argI+1]; -} - - -/** compute e^(-jx) via lookup table. */ -complex expjLookup(float x) -{ - float arg = x*M_1_2PI_F; - while (arg > 1.0F) arg -= 1.0F; - while (arg < 0.0F) arg += 1.0F; - - const float argT = arg*((float)TABLESIZE); - const int argI = (int)argT; - const float delta = argT-argI; - const float iDelta = 1.0F-delta; - return complex(iDelta*cosTable[argI] + delta*cosTable[argI+1], - iDelta*sinTable[argI] + delta*sinTable[argI+1]); -} - -/** Library setup functions */ -void initTrigTables() { - for (int i = 0; i < TABLESIZE+1; i++) { - cosTable[i] = cos(2.0*M_PI*i/TABLESIZE); - sinTable[i] = sin(2.0*M_PI*i/TABLESIZE); - } -} - -void initGMSKRotationTables(int samplesPerSymbol) { - GMSKRotation = new signalVector(157*samplesPerSymbol); - GMSKReverseRotation = new signalVector(157*samplesPerSymbol); - signalVector::iterator rotPtr = GMSKRotation->begin(); - signalVector::iterator revPtr = GMSKReverseRotation->begin(); - float phase = 0.0; - while (rotPtr != GMSKRotation->end()) { - *rotPtr++ = expjLookup(phase); - *revPtr++ = expjLookup(-phase); - phase += M_PI_F/2.0F/(float) samplesPerSymbol; - } -} - -void sigProcLibSetup(int samplesPerSymbol) { - initTrigTables(); - initGMSKRotationTables(samplesPerSymbol); -} - -void GMSKRotate(signalVector &x) { - signalVector::iterator xPtr = x.begin(); - signalVector::iterator rotPtr = GMSKRotation->begin(); - if (x.isRealOnly()) { - while (xPtr < x.end()) { - *xPtr = *rotPtr++ * (xPtr->real()); - xPtr++; - } - } - else { - while (xPtr < x.end()) { - *xPtr = *rotPtr++ * (*xPtr); - xPtr++; - } - } -} - -void GMSKReverseRotate(signalVector &x) { - signalVector::iterator xPtr= x.begin(); - signalVector::iterator rotPtr = GMSKReverseRotation->begin(); - if (x.isRealOnly()) { - while (xPtr < x.end()) { - *xPtr = *rotPtr++ * (xPtr->real()); - xPtr++; - } - } - else { - while (xPtr < x.end()) { - *xPtr = *rotPtr++ * (*xPtr); - xPtr++; - } - } -} - - -signalVector* convolve(const signalVector *a, - const signalVector *b, - signalVector *c, - ConvType spanType) -{ - if ((a==NULL) || (b==NULL)) return NULL; - int La = a->size(); - int Lb = b->size(); - - int startIndex; - unsigned int outSize; - switch (spanType) { - case FULL_SPAN: - startIndex = 0; - outSize = La+Lb-1; - break; - case OVERLAP_ONLY: - startIndex = La; - outSize = abs(La-Lb)+1; - break; - case START_ONLY: - startIndex = 0; - outSize = La; - break; - case WITH_TAIL: - startIndex = Lb; - outSize = La; - break; - case NO_DELAY: - if (Lb % 2) - startIndex = Lb/2; - else - startIndex = Lb/2-1; - outSize = La; - break; - default: - return NULL; - } - - - if (c==NULL) - c = new signalVector(outSize); - else if (c->size()!=outSize) - return NULL; - - signalVector::const_iterator aStart = a->begin(); - signalVector::const_iterator bStart = b->begin(); - signalVector::const_iterator aEnd = a->end(); - signalVector::const_iterator bEnd = b->end(); - signalVector::iterator cPtr = c->begin(); - int t = startIndex; - int stopIndex = startIndex + outSize; - switch (b->getSymmetry()) { - case NONE: - { - while (t < stopIndex) { - signalVector::const_iterator aP = aStart+t; - signalVector::const_iterator bP = bStart; - if (a->isRealOnly() && b->isRealOnly()) { - float sum = 0.0; - while (bP < bEnd) { - if (aP < aStart) break; - if (aP < aEnd) sum += (aP->real())*(bP->real()); - aP--; - bP++; - } - *cPtr++ = sum; - } - else if (a->isRealOnly()) { - complex sum = 0.0; - while (bP < bEnd) { - if (aP < aStart) break; - if (aP < aEnd) sum += (*bP)*(aP->real()); - aP--; - bP++; - } - *cPtr++ = sum; - } - else if (b->isRealOnly()) { - complex sum = 0.0; - while (bP < bEnd) { - if (aP < aStart) break; - if (aP < aEnd) sum += (*aP)*(bP->real()); - aP--; - bP++; - } - *cPtr++ = sum; - } - else { - complex sum = 0.0; - while (bP < bEnd) { - if (aP < aStart) break; - if (aP < aEnd) sum += (*aP)*(*bP); - aP--; - bP++; - } - *cPtr++ = sum; - } - t++; - } - } - break; - case ABSSYM: - { - complex sum = 0.0; - bool isOdd = (bool) (Lb % 2); - if (isOdd) - bEnd = bStart + (Lb+1)/2; - else - bEnd = bStart + Lb/2; - while (t < stopIndex) { - signalVector::const_iterator aP = aStart+t; - signalVector::const_iterator aPsym = aP-Lb; - signalVector::const_iterator bP = bStart; - sum = 0.0; - while (bP < bEnd) { - if (aP < aStart) break; - if (aP == aPsym) - sum+= (*aP)*(*bP); - else if ((aP < aEnd) && (aPsym >= aStart)) - sum+= ((*aP)+(*aPsym))*(*bP); - else if (aP < aEnd) - sum += (*aP)*(*bP); - else if (aPsym >= aStart) - sum += (*aPsym)*(*bP); - aP--; - aPsym++; - bP++; - } - *cPtr++ = sum; - t++; - } - } - break; - default: - return NULL; - break; - } - - - return c; -} - - -signalVector* generateGSMPulse(int symbolLength, - int samplesPerSymbol) -{ - - int numSamples = samplesPerSymbol*symbolLength + 1; - signalVector *x = new signalVector(numSamples); - signalVector::iterator xP = x->begin(); - int centerPoint = (numSamples-1)/2; - for (int i = 0; i < numSamples; i++) { - float arg = (float) (i-centerPoint)/(float) samplesPerSymbol; - *xP++ = 0.96*exp(-1.1380*arg*arg-0.527*arg*arg*arg*arg); // GSM pulse approx. - } - - float avgAbsval = sqrtf(vectorNorm2(*x)/samplesPerSymbol); - xP = x->begin(); - for (int i = 0; i < numSamples; i++) - *xP++ /= avgAbsval; - x->isRealOnly(true); - return x; -} - -signalVector* frequencyShift(signalVector *y, - signalVector *x, - float freq, - float startPhase, - float *finalPhase) -{ - - if (!x) return NULL; - - if (y==NULL) { - y = new signalVector(x->size()); - y->isRealOnly(x->isRealOnly()); - if (y==NULL) return NULL; - } - - if (y->size() < x->size()) return NULL; - - float phase = startPhase; - signalVector::iterator yP = y->begin(); - signalVector::iterator xPEnd = x->end(); - signalVector::iterator xP = x->begin(); - - if (x->isRealOnly()) { - while (xP < xPEnd) { - (*yP++) = expjLookup(phase)*( (xP++)->real() ); - phase += freq; - } - } - else { - while (xP < xPEnd) { - (*yP++) = (*xP++)*expjLookup(phase); - phase += freq; - } - } - - - if (finalPhase) *finalPhase = phase; - - return y; -} - - -signalVector* correlate(signalVector *a, - signalVector *b, - signalVector *c, - ConvType spanType) -{ - - signalVector *tmp = new signalVector(b->size()); - tmp->isRealOnly(b->isRealOnly()); - signalVector::iterator bP = b->begin(); - signalVector::iterator bPEnd = b->end(); - signalVector::iterator tmpP = tmp->end()-1; - if (!b->isRealOnly()) { - while (bP < bPEnd) { - *tmpP-- = bP->conj(); - bP++; - } - } - else { - while (bP < bPEnd) { - *tmpP-- = bP->real(); - bP++; - } - } - - c = convolve(a,tmp,c,spanType); - - delete tmp; - - return c; -} - - -/* soft output slicer */ -bool vectorSlicer(signalVector *x) -{ - - signalVector::iterator xP = x->begin(); - signalVector::iterator xPEnd = x->end(); - while (xP < xPEnd) { - *xP = (complex) (0.5*(xP->real()+1.0F)); - if (xP->real() > 1.0) *xP = 1.0; - if (xP->real() < 0.0) *xP = 0.0; - xP++; - } - return true; -} - -signalVector *modulateBurst(const BitVector &wBurst, - const signalVector &gsmPulse, - int guardPeriodLength, - int samplesPerSymbol) -{ - - int burstSize = samplesPerSymbol*(wBurst.size()+guardPeriodLength); - signalVector *modBurst = new signalVector(burstSize); - modBurst->isRealOnly(true); - modBurst->fill(0.0); - signalVector::iterator modBurstItr = modBurst->begin(); - -#if 0 - // if wBurst is already differentially decoded - *modBurstItr = 2.0*(wBurst[0] & 0x01)-1.0; - signalVector::iterator prevVal = modBurstItr; - for (unsigned int i = 1; i < wBurst.size(); i++) { - modBurstItr += samplesPerSymbol; - if (wBurst[i] & 0x01) - *modBurstItr = *prevVal * complex(0.0,1.0); - else - *modBurstItr = *prevVal * complex(0.0,-1.0); - prevVal = modBurstItr; - } -#else - // if wBurst are the raw bits - for (unsigned int i = 0; i < wBurst.size(); i++) { - *modBurstItr = 2.0*(wBurst[i] & 0x01)-1.0; - modBurstItr += samplesPerSymbol; - } - - // shift up pi/2 - // ignore starting phase, since spec allows for discontinuous phase - GMSKRotate(*modBurst); -#endif - modBurst->isRealOnly(false); - - // filter w/ pulse shape - signalVector *shapedBurst = convolve(modBurst,&gsmPulse,NULL,NO_DELAY); - - delete modBurst; - - return shapedBurst; - -} - -float sinc(float x) -{ - if ((x >= 0.01F) || (x <= -0.01F)) return (sinLookup(x)/x); - return 1.0F; -} - -void delayVector(signalVector &wBurst, - float delay) -{ - - int intOffset = (int) floor(delay); - float fracOffset = delay - intOffset; - signalVector *shiftedBurst = NULL; - - // do fractional shift first, only do it for reasonable offsets - if (fabs(fracOffset) > 1e-2) { - // create sinc function - signalVector *sincVector = new signalVector(21); - sincVector->isRealOnly(true); - signalVector::iterator sincBurstItr = sincVector->begin(); - for (int i = 0; i < 21; i++) - *sincBurstItr++ = (complex) sinc(M_PI_F*(i-10-fracOffset)); - - shiftedBurst = convolve(&wBurst,sincVector,shiftedBurst,NO_DELAY); - - delete sincVector; - } - else - shiftedBurst = &wBurst; - - if (intOffset < 0) { - intOffset = -intOffset; - signalVector::iterator wBurstItr = wBurst.begin(); - signalVector::iterator shiftedItr = shiftedBurst->begin()+intOffset; - while (shiftedItr < shiftedBurst->end()) - *wBurstItr++ = *shiftedItr++; - while (wBurstItr < wBurst.end()) - *wBurstItr++ = 0.0; - } - else { - signalVector::iterator wBurstItr = wBurst.end()-1; - signalVector::iterator shiftedItr = shiftedBurst->end()-1-intOffset; - while (shiftedItr >= shiftedBurst->begin()) - *wBurstItr-- = *shiftedItr--; - while (wBurstItr >= wBurst.begin()) - *wBurstItr-- = 0.0; - } - - if (shiftedBurst != &wBurst) delete shiftedBurst; -} - -signalVector *gaussianNoise(int length, - float variance, - complex mean) -{ - - signalVector *noise = new signalVector(length); - signalVector::iterator nPtr = noise->begin(); - float stddev = sqrtf(variance); - while (nPtr < noise->end()) { - float u1 = (float) rand()/ (float) RAND_MAX; - while (u1==0.0) - u1 = (float) rand()/ (float) RAND_MAX; - float u2 = (float) rand()/ (float) RAND_MAX; - float arg = 2.0*M_PI*u2; - *nPtr = mean + stddev*complex(cos(arg),sin(arg))*sqrtf(-2.0*log(u1)); - nPtr++; - } - - return noise; -} - -complex interpolatePoint(const signalVector &inSig, - float ix) -{ - - int start = (int) (floor(ix) - 10); - if (start < 0) start = 0; - int end = (int) (floor(ix) + 11); - if ((unsigned) end > inSig.size()-1) end = inSig.size()-1; - - complex pVal = 0.0; - if (!inSig.isRealOnly()) { - for (int i = start; i < end; i++) - pVal += inSig[i] * sinc(M_PI_F*(i-ix)); - } - else { - for (int i = start; i < end; i++) - pVal += inSig[i].real() * sinc(M_PI_F*(i-ix)); - } - - return pVal; -} - - - -complex peakDetect(const signalVector &rxBurst, - float *peakIndex, - float *avgPwr) -{ - - - complex maxVal = 0.0; - float maxIndex = -1; - float sumPower = 0.0; - - for (unsigned int i = 0; i < rxBurst.size(); i++) { - float samplePower = rxBurst[i].norm2(); - if (samplePower > maxVal.real()) { - maxVal = samplePower; - maxIndex = i; - } - sumPower += samplePower; - } - - // interpolate around the peak - // to save computation, we'll use early-late balancing - float earlyIndex = maxIndex-1; - float lateIndex = maxIndex+1; - - float incr = 0.5; - while (incr > 1.0/1024.0) { - complex earlyP = interpolatePoint(rxBurst,earlyIndex); - complex lateP = interpolatePoint(rxBurst,lateIndex); - if (earlyP < lateP) - earlyIndex += incr; - else if (earlyP > lateP) - earlyIndex -= incr; - else break; - incr /= 2.0; - lateIndex = earlyIndex + 2.0; - } - - maxIndex = earlyIndex + 1.0; - maxVal = interpolatePoint(rxBurst,maxIndex); - - if (peakIndex!=NULL) - *peakIndex = maxIndex; - - if (avgPwr!=NULL) - *avgPwr = (sumPower-maxVal.norm2()) / (rxBurst.size()-1); - - return maxVal; - -} - -void scaleVector(signalVector &x, - complex scale) -{ - signalVector::iterator xP = x.begin(); - signalVector::iterator xPEnd = x.end(); - if (!x.isRealOnly()) { - while (xP < xPEnd) { - *xP = *xP * scale; - xP++; - } - } - else { - while (xP < xPEnd) { - *xP = xP->real() * scale; - xP++; - } - } -} - -/** in-place conjugation */ -void conjugateVector(signalVector &x) -{ - if (x.isRealOnly()) return; - signalVector::iterator xP = x.begin(); - signalVector::iterator xPEnd = x.end(); - while (xP < xPEnd) { - *xP = xP->conj(); - xP++; - } -} - - -// in-place addition!! -bool addVector(signalVector &x, - signalVector &y) -{ - signalVector::iterator xP = x.begin(); - signalVector::iterator yP = y.begin(); - signalVector::iterator xPEnd = x.end(); - signalVector::iterator yPEnd = y.end(); - while ((xP < xPEnd) && (yP < yPEnd)) { - *xP = *xP + *yP; - xP++; yP++; - } - return true; -} - -void offsetVector(signalVector &x, - complex offset) -{ - signalVector::iterator xP = x.begin(); - signalVector::iterator xPEnd = x.end(); - if (!x.isRealOnly()) { - while (xP < xPEnd) { - *xP += offset; - xP++; - } - } - else { - while (xP < xPEnd) { - *xP = xP->real() + offset; - xP++; - } - } -} - -bool generateMidamble(signalVector &gsmPulse, - int samplesPerSymbol, - int TSC) -{ - - if ((TSC < 0) || (TSC > 7)) - return false; - - if ((gMidambles[TSC]) && (gMidambles[TSC]->sequence!=NULL)) - delete gMidambles[TSC]->sequence; - - signalVector emptyPulse(1); - *(emptyPulse.begin()) = 1.0; - - // only use middle 16 bits of each TSC - signalVector *middleMidamble = modulateBurst(gTrainingSequence[TSC].segment(5,16), - emptyPulse, - 0, - samplesPerSymbol); - signalVector *midamble = modulateBurst(gTrainingSequence[TSC], - gsmPulse, - 0, - samplesPerSymbol); - - if (midamble == NULL) return false; - if (middleMidamble == NULL) return false; - - // NOTE: Because ideal TSC 16-bit midamble is 66 symbols into burst, - // the ideal TSC has an + 180 degree phase shift, - // due to the pi/2 frequency shift, that - // needs to be accounted for. - // 26-midamble is 61 symbols into burst, has +90 degree phase shift. - scaleVector(*middleMidamble,complex(-1.0,0.0)); - scaleVector(*midamble,complex(0.0,1.0)); - - signalVector *autocorr = correlate(midamble,middleMidamble,NULL,NO_DELAY); - - if (autocorr == NULL) return false; - - gMidambles[TSC] = new CorrelationSequence; - gMidambles[TSC]->sequence = middleMidamble; - - gMidambles[TSC]->gain = peakDetect(*autocorr,&gMidambles[TSC]->TOA,NULL); - gMidambles[TSC]->TOA -= 5*samplesPerSymbol; - - delete autocorr; - delete midamble; - - return true; -} - -bool generateRACHSequence(signalVector &gsmPulse, - int samplesPerSymbol) -{ - - if ((gRACHSequence) && (gRACHSequence->sequence!=NULL)) - delete gRACHSequence->sequence; - - signalVector *RACHSeq = modulateBurst(gRACHSynchSequence, - gsmPulse, - 0, - samplesPerSymbol); - - assert(RACHSeq); - - signalVector *autocorr = correlate(RACHSeq,RACHSeq,NULL,NO_DELAY); - - assert(autocorr); - - gRACHSequence = new CorrelationSequence; - gRACHSequence->sequence = RACHSeq; - - gRACHSequence->gain = peakDetect(*autocorr,&gRACHSequence->TOA,NULL); - - delete autocorr; - - return true; - -} - - -bool detectRACHBurst(signalVector &rxBurst, - float detectThreshold, - int samplesPerSymbol, - complex *amplitude, - float* TOA) -{ - - signalVector *correlatedRACH = correlate(&rxBurst,gRACHSequence->sequence, - NULL, - NO_DELAY); - assert(correlatedRACH); - - float meanPower; - complex peakAmpl = peakDetect(*correlatedRACH,TOA,&meanPower); - - float valleyPower = 0.0; - - // check for bogus results - if ((*TOA < 0.0) || (*TOA > correlatedRACH->size())) { - delete correlatedRACH; - *amplitude = 0.0; - return false; - } - complex *peakPtr = correlatedRACH->begin() + (int) rint(*TOA); - - LOG(DEEPDEBUG) << "RACH corr: " << *correlatedRACH; - - float numSamples = 0.0; - for (int i = 57*samplesPerSymbol; i <= 107*samplesPerSymbol;i++) { - if (peakPtr+i >= correlatedRACH->end()) - break; - valleyPower += (peakPtr+i)->norm2(); - numSamples++; - } - - if (numSamples < 2) { - delete correlatedRACH; - *amplitude = 0.0; - return false; - } - - float RMS = sqrtf(valleyPower/(float) numSamples)+0.00001; - float peakToMean = peakAmpl.abs()/RMS; - - LOG(DEEPDEBUG) << "RACH peakAmpl=" << peakAmpl << " RMS=" << RMS << " peakToMean=" << peakToMean; - *amplitude = peakAmpl/(gRACHSequence->gain); - - *TOA = (*TOA) - gRACHSequence->TOA - 8*samplesPerSymbol; - - delete correlatedRACH; - - LOG(DEEPDEBUG) << "RACH thresh: " << peakToMean; - - return (peakToMean > detectThreshold); -} - -bool energyDetect(signalVector &rxBurst, - unsigned windowLength, - float detectThreshold, - float *avgPwr) -{ - - signalVector::const_iterator windowItr = rxBurst.begin(); //+rxBurst.size()/2 - 5*windowLength/2; - float energy = 0.0; - if (windowLength > rxBurst.size()) windowLength = rxBurst.size(); - for (unsigned i = 0; i < windowLength; i++) { - energy += windowItr->norm2(); - windowItr++; - } - if (avgPwr) *avgPwr = energy/windowLength; - LOG(DEEPDEBUG) << "detected energy: " << energy/windowLength; - return (energy/windowLength > detectThreshold*detectThreshold); -} - - -bool analyzeTrafficBurst(signalVector &rxBurst, - unsigned TSC, - float detectThreshold, - int samplesPerSymbol, - complex *amplitude, - float *TOA, - bool requestChannel, - signalVector **channelResponse, - float *channelResponseOffset) -{ - - assert(TSC<8); - assert(amplitude); - assert(TOA); - assert(gMidambles[TSC]); - - signalVector burstSegment(rxBurst.begin(),samplesPerSymbol*56,36*samplesPerSymbol); - - signalVector *correlatedBurst = correlate(&burstSegment, //&rxBurst, - gMidambles[TSC]->sequence, - NULL,NO_DELAY); - assert(correlatedBurst); - - float meanPower; - *amplitude = peakDetect(*correlatedBurst,TOA,&meanPower); - float valleyPower = 0.0; //amplitude->norm2(); - complex *peakPtr = correlatedBurst->begin() + (int) rint(*TOA); - - // check for bogus results - if ((*TOA < 0.0) || (*TOA > correlatedBurst->size())) { - delete correlatedBurst; - *amplitude = 0.0; - return false; - } - - int numRms = 0; - for (int i = 2*samplesPerSymbol; i <= 5*samplesPerSymbol;i++) { - if (peakPtr - i >= correlatedBurst->begin()) { - valleyPower += (peakPtr-i)->norm2(); - numRms++; - } - if (peakPtr + i < correlatedBurst->end()) { - valleyPower += (peakPtr+i)->norm2(); - numRms++; - } - } - - if (numRms < 2) { - // check for bogus results - delete correlatedBurst; - *amplitude = 0.0; - return false; - } - - float RMS = sqrtf(valleyPower/(float)numRms)+0.00001; - float peakToMean = (amplitude->abs())/RMS; - - // NOTE: Because ideal TSC is 66 symbols into burst, - // the ideal TSC has an +/- 180 degree phase shift, - // due to the pi/4 frequency shift, that - // needs to be accounted for. - - *amplitude = (*amplitude)/gMidambles[TSC]->gain; - *TOA = (*TOA)-gMidambles[TSC]->TOA; - - (*TOA) = (*TOA) - (66-56)*samplesPerSymbol; - LOG(DEEPDEBUG) << "TCH peakAmpl=" << amplitude->abs() << " RMS=" << RMS << " peakToMean=" << peakToMean << " TOA=" << *TOA; - - LOG(DEEPDEBUG) << "autocorr: " << *correlatedBurst; - - if (requestChannel && (peakToMean > detectThreshold)) { - float TOAoffset = gMidambles[TSC]->TOA+(66-56)*samplesPerSymbol; - delayVector(*correlatedBurst,-(*TOA)); - // midamble only allows estimation of a 6-tap channel - signalVector channelVector(6*samplesPerSymbol); - float maxEnergy = -1.0; - int maxI = -1; - for (int i = 0; i < 7; i++) { - if (TOAoffset+(i-5)*samplesPerSymbol + channelVector.size() > correlatedBurst->size()) continue; - if (TOAoffset+(i-5)*samplesPerSymbol < 0) continue; - correlatedBurst->segmentCopyTo(channelVector,(int) floor(TOAoffset+(i-5)*samplesPerSymbol),channelVector.size()); - float energy = vectorNorm2(channelVector); - if (energy > 0.95*maxEnergy) { - maxI = i; - maxEnergy = energy; - } - } - - *channelResponse = new signalVector(channelVector.size()); - correlatedBurst->segmentCopyTo(**channelResponse,(int) floor(TOAoffset+(maxI-5)*samplesPerSymbol),(*channelResponse)->size()); - scaleVector(**channelResponse,complex(1.0,0.0)/gMidambles[TSC]->gain); - LOG(DEEPDEBUG) << "channelResponse: " << **channelResponse; - - if (channelResponseOffset) - *channelResponseOffset = 5*samplesPerSymbol-maxI; - - } - - delete correlatedBurst; - - return (peakToMean > detectThreshold); - -} - -signalVector *decimateVector(signalVector &wVector, - int decimationFactor) -{ - - if (decimationFactor <= 1) return NULL; - - signalVector *decVector = new signalVector(wVector.size()/decimationFactor); - decVector->isRealOnly(wVector.isRealOnly()); - - signalVector::iterator vecItr = decVector->begin(); - for (unsigned int i = 0; i < wVector.size();i+=decimationFactor) - *vecItr++ = wVector[i]; - - return decVector; -} - - -SoftVector *demodulateBurst(const signalVector &rxBurst, - const signalVector &gsmPulse, - int samplesPerSymbol, - complex channel, - float TOA) - -{ - - signalVector *demodBurst = new signalVector(rxBurst); - - scaleVector(*demodBurst,((complex) 1.0)/channel); - - delayVector(*demodBurst,-TOA); - signalVector *shapedBurst = demodBurst; - - // shift up by a quarter of a frequency - // ignore starting phase, since spec allows for discontinuous phase - GMSKReverseRotate(*shapedBurst); - - // run through slicer - if (samplesPerSymbol > 1) { - signalVector *decShapedBurst = decimateVector(*shapedBurst,samplesPerSymbol); - delete shapedBurst; - shapedBurst = decShapedBurst; - } - - LOG(DEEPDEBUG) << "shapedBurst: " << *shapedBurst; - - vectorSlicer(shapedBurst); - - SoftVector *burstBits = new SoftVector(shapedBurst->size()); - - SoftVector::iterator burstItr = burstBits->begin(); - signalVector::iterator shapedItr = shapedBurst->begin(); - for (; shapedItr < shapedBurst->end(); shapedItr++) - *burstItr++ = shapedItr->real(); - - delete shapedBurst; - - return burstBits; - -} - - -// 1.0 is sampling frequency -// must satisfy cutoffFreq > 1/filterLen -signalVector *createLPF(float cutoffFreq, - int filterLen, - float gainDC) -{ - /* - signalVector *LPF = new signalVector(filterLen); - LPF->isRealOnly(true); - signalVector::iterator itr = LPF->begin(); - double sum = 0.0; - for (int i = 0; i < filterLen; i++) { - float ys = sinc(M_2PI_F*cutoffFreq*((float)i-(float)(filterLen+1)/2.0F)); - float yg = 4.0F * cutoffFreq; - float yw = 0.53836F - 0.46164F * cos(((float)i)*M_2PI_F/(float)(filterLen+1)); - *itr++ = (complex) ys*yg*yw; - sum += ys*yg*yw; - } - */ - double sum = 0.0; - signalVector *LPF; - signalVector::iterator itr; - if (filterLen == 651) { // receive LPF - LPF = new signalVector(651); - LPF->isRealOnly(true); - itr = LPF->begin(); - for (int i = 0; i < filterLen; i++) { - *itr++ = complex(rcvLPF_651[i],0.0); - sum += rcvLPF_651[i]; - } - } - else { - LPF = new signalVector(961); - LPF->isRealOnly(true); - itr = LPF->begin(); - for (int i = 0; i < filterLen; i++) { - *itr++ = complex(sendLPF_961[i],0.0); - sum += sendLPF_961[i]; - } - } - - float normFactor = gainDC/sum; //sqrtf(gainDC/vectorNorm2(*LPF)); - // normalize power - itr = LPF->begin(); - for (int i = 0; i < filterLen; i++) { - *itr = *itr*normFactor; - itr++; - } - return LPF; - -} - - - -#define POLYPHASESPAN 10 - -// assumes filter group delay is 0.5*(length of filter) -signalVector *polyphaseResampleVector(signalVector &wVector, - int P, int Q, - signalVector *LPF) - -{ - - bool deleteLPF = false; - - if (LPF==NULL) { - float cutoffFreq = (P < Q) ? (1.0/(float) Q) : (1.0/(float) P); - LPF = createLPF(cutoffFreq/3.0,100*POLYPHASESPAN+1,Q); - deleteLPF = true; - } - - signalVector *resampledVector = new signalVector((int) ceil(wVector.size()*(float) P / (float) Q)); - resampledVector->fill(0); - resampledVector->isRealOnly(wVector.isRealOnly()); - signalVector::iterator newItr = resampledVector->begin(); - - //FIXME: need to update for real-only vectors - int outputIx = (LPF->size()-1)/2/Q; //((P > Q) ? P : Q); - while (newItr < resampledVector->end()) { - int outputBranch = (outputIx*Q) % P; - int inputOffset = (outputIx*Q - outputBranch)/P; - signalVector::const_iterator inputItr = wVector.begin() + inputOffset; - signalVector::const_iterator filtItr = LPF->begin() + outputBranch; - while (inputItr >= wVector.end()) { - inputItr--; - filtItr+=P; - } - complex sum = 0.0; - if (!LPF->isRealOnly()) { - while ( (inputItr >= wVector.begin()) && (filtItr < LPF->end()) ) { - sum += (*inputItr)*(*filtItr); - inputItr--; - filtItr += P; - } - } - else { - while ( (inputItr >= wVector.begin()) && (filtItr < LPF->end()) ) { - sum += (*inputItr)*(filtItr->real()); - inputItr--; - filtItr += P; - } - } - *newItr = sum; - newItr++; - outputIx++; - } - - if (deleteLPF) delete LPF; - - return resampledVector; -} - - -signalVector *resampleVector(signalVector &wVector, - float expFactor, - complex endPoint) - -{ - - if (expFactor < 1.0) return NULL; - - signalVector *retVec = new signalVector((int) ceil(wVector.size()*expFactor)); - - float t = 0.0; - - signalVector::iterator retItr = retVec->begin(); - while (retItr < retVec->end()) { - unsigned tLow = (unsigned int) floor(t); - unsigned tHigh = tLow + 1; - if (tLow > wVector.size()-1) break; - if (tHigh > wVector.size()) break; - complex lowPoint = wVector[tLow]; - complex highPoint = (tHigh == wVector.size()) ? endPoint : wVector[tHigh]; - complex a = (tHigh-t); - complex b = (t-tLow); - *retItr = (a*lowPoint + b*highPoint); - t += 1.0/expFactor; - } - - return retVec; - -} - - -// Assumes symbol-spaced sampling!!! -// Based upon paper by Al-Dhahir and Cioffi -bool designDFE(signalVector &channelResponse, - float SNRestimate, - int Nf, - signalVector **feedForwardFilter, - signalVector **feedbackFilter) -{ - - signalVector G0(Nf); - signalVector G1(Nf); - signalVector::iterator G0ptr = G0.begin(); - signalVector::iterator G1ptr = G1.begin(); - signalVector::iterator chanPtr = channelResponse.begin(); - - int nu = channelResponse.size()-1; - - *G0ptr = 1.0/sqrtf(SNRestimate); - for(int j = 0; j <= nu; j++) { - *G1ptr = chanPtr->conj(); - G1ptr++; chanPtr++; - } - - signalVector *L[Nf]; - signalVector::iterator Lptr; - // There's a worning here on some compilers. It's OK. - float d; - for(int i = 0; i < Nf; i++) { - d = G0.begin()->norm2() + G1.begin()->norm2(); - L[i] = new signalVector(Nf+nu); - Lptr = L[i]->begin()+i; - G0ptr = G0.begin(); G1ptr = G1.begin(); - while ((G0ptr < G0.end()) && (Lptr < L[i]->end())) { - *Lptr = (*G0ptr*(G0.begin()->conj()) + *G1ptr*(G1.begin()->conj()) )/d; - Lptr++; - G0ptr++; - G1ptr++; - } - complex k = (*G1.begin())/(*G0.begin()); - - if (i != Nf-1) { - signalVector G0new = G1; - scaleVector(G0new,k.conj()); - addVector(G0new,G0); - - signalVector G1new = G0; - scaleVector(G1new,k*(-1.0)); - addVector(G1new,G1); - delayVector(G1new,-1.0); - - scaleVector(G0new,1.0/sqrtf(1.0+k.norm2())); - scaleVector(G1new,1.0/sqrtf(1.0+k.norm2())); - G0 = G0new; - G1 = G1new; - } - } - - *feedbackFilter = new signalVector(nu); - L[Nf-1]->segmentCopyTo(**feedbackFilter,Nf,nu); - scaleVector(**feedbackFilter,(complex) -1.0); - conjugateVector(**feedbackFilter); - - signalVector v(Nf); - signalVector::iterator vStart = v.begin(); - signalVector::iterator vPtr; - *(vStart+Nf-1) = (complex) 1.0; - for(int k = Nf-2; k >= 0; k--) { - Lptr = L[k]->begin()+k+1; - vPtr = vStart + k+1; - complex v_k = 0.0; - for (int j = k+1; j < Nf; j++) { - v_k -= (*vPtr)*(*Lptr); - vPtr++; Lptr++; - } - *(vStart + k) = v_k; - } - - *feedForwardFilter = new signalVector(Nf); - signalVector::iterator w = (*feedForwardFilter)->begin(); - for (int i = 0; i < Nf; i++) { - delete L[i]; - complex w_i = 0.0; - int endPt = ( nu < (Nf-1-i) ) ? nu : (Nf-1-i); - vPtr = vStart+i; - chanPtr = channelResponse.begin(); - for (int k = 0; k < endPt+1; k++) { - w_i += (*vPtr)*(chanPtr->conj()); - vPtr++; chanPtr++; - } - *w = w_i/d; - w++; - } - - - return true; - -} - -// Assumes symbol-rate sampling!!!! -SoftVector *equalizeBurst(signalVector &rxBurst, - float TOA, - int samplesPerSymbol, - signalVector &w, // feedforward filter - signalVector &b) // feedback filter -{ - - delayVector(rxBurst,-TOA); - - signalVector* postForwardFull = convolve(&rxBurst,&w,NULL,FULL_SPAN); - - signalVector* postForward = new signalVector(rxBurst.size()); - postForwardFull->segmentCopyTo(*postForward,w.size()-1,rxBurst.size()); - delete postForwardFull; - - signalVector::iterator dPtr = postForward->begin(); - signalVector::iterator dBackPtr; - signalVector::iterator rotPtr = GMSKRotation->begin(); - signalVector::iterator revRotPtr = GMSKReverseRotation->begin(); - - signalVector *DFEoutput = new signalVector(postForward->size()); - signalVector::iterator DFEItr = DFEoutput->begin(); - - // NOTE: can insert the midamble and/or use midamble to estimate BER - for (; dPtr < postForward->end(); dPtr++) { - dBackPtr = dPtr-1; - signalVector::iterator bPtr = b.begin(); - while ( (bPtr < b.end()) && (dBackPtr >= postForward->begin()) ) { - *dPtr = *dPtr + (*bPtr)*(*dBackPtr); - bPtr++; - dBackPtr--; - } - *dPtr = *dPtr * (*revRotPtr); - *DFEItr = *dPtr; - // make decision on symbol - *dPtr = (dPtr->real() > 0.0) ? 1.0 : -1.0; - //*DFEItr = *dPtr; - *dPtr = *dPtr * (*rotPtr); - DFEItr++; - rotPtr++; - revRotPtr++; - } - - vectorSlicer(DFEoutput); - - SoftVector *burstBits = new SoftVector(postForward->size()); - SoftVector::iterator burstItr = burstBits->begin(); - DFEItr = DFEoutput->begin(); - for (; DFEItr < DFEoutput->end(); DFEItr++) - *burstItr++ = DFEItr->real(); - - delete postForward; - - delete DFEoutput; - - return burstBits; -} diff --git a/public-trunk/Transceiver/sigProcLib.h b/public-trunk/Transceiver/sigProcLib.h deleted file mode 100644 index 5e7f005..0000000 --- a/public-trunk/Transceiver/sigProcLib.h +++ /dev/null @@ -1,384 +0,0 @@ -/* -* Copyright 2008 Free Software Foundation, Inc. -* -* This software is distributed under the terms of the GNU Affero Public License. -* See the COPYING file in the main directory for details. -* -* This use of this software may be subject to additional restrictions. -* See the LEGAL file in the main directory for details. - - 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 . - -*/ - - - -#include "Vector.h" -#include "Complex.h" -#include "GSMTransfer.h" - - -using namespace GSM; - -/** Indicated signalVector symmetry */ -enum Symmetry { - NONE = 0, - ABSSYM = 1 -}; - -/** Convolution type indicator */ -enum ConvType { - FULL_SPAN = 0, - OVERLAP_ONLY = 1, - START_ONLY = 2, - WITH_TAIL = 3, - NO_DELAY = 4, - UNDEFINED = 255 -}; - -/** the core data structure of the Transceiver */ -class signalVector: public Vector -{ - - private: - - Symmetry symmetry; ///< the symmetry of the vector - bool realOnly; ///< true if vector is real-valued, not complex-valued - - public: - - /** Constructors */ - signalVector(int dSize=0, Symmetry wSymmetry = NONE): - Vector(dSize), - realOnly(false) - { - symmetry = wSymmetry; - }; - - signalVector(complex* wData, size_t start, - size_t span, Symmetry wSymmetry = NONE): - Vector(NULL,wData+start,wData+start+span), - realOnly(false) - { - symmetry = wSymmetry; - }; - - signalVector(const signalVector &vec1, const signalVector &vec2): - Vector(vec1,vec2), - realOnly(false) - { - symmetry = vec1.symmetry; - }; - - signalVector(const signalVector &wVector): - Vector(wVector.size()), - realOnly(false) - { - wVector.copyTo(*this); - symmetry = wVector.getSymmetry(); - }; - - /** symmetry operators */ - Symmetry getSymmetry() const { return symmetry;}; - void setSymmetry(Symmetry wSymmetry) { symmetry = wSymmetry;}; - - /** real-valued operators */ - bool isRealOnly() const { return realOnly;}; - void isRealOnly(bool wOnly) { realOnly = wOnly;}; -}; - -/** Convert a linear number to a dB value */ -float dB(float x); - -/** Convert a dB value into a linear value */ -float dBinv(float x); - -/** Compute the energy of a vector */ -float vectorNorm2(const signalVector &x); - -/** Compute the average power of a vector */ -float vectorPower(const signalVector &x); - -/** Setup the signal processing library */ -void sigProcLibSetup(int samplesPerSymbol); - -/** Destroy the signal processing library */ -void sigProcLibDestroy(void); - -/** - Convolve two vectors. - @param a,b The vectors to be convolved. - @param c, A preallocated vector to hold the convolution result. - @param spanType The type/span of the convolution. - @return The convolution result. -*/ -signalVector* convolve(const signalVector *a, - const signalVector *b, - signalVector *c, - ConvType spanType); - -/** - Generate the GSM pulse. - @param samplesPerSymbol The number of samples per GSM symbol. - @param symbolLength The size of the pulse. - @return The GSM pulse. -*/ -signalVector* generateGSMPulse(int samplesPerSymbol, - int symbolLength); - -/** - Frequency shift a vector. - @param y The frequency shifted vector. - @param x The vector to-be-shifted. - @param freq The digital frequency shift - @param startPhase The starting phase of the oscillator - @param finalPhase The final phase of the oscillator - @return The frequency shifted vector. -*/ -signalVector* frequencyShift(signalVector *y, - signalVector *x, - float freq = 0.0, - float startPhase = 0.0, - float *finalPhase=NULL); - -/** - Correlate two vectors. - @param a,b The vectors to be correlated. - @param c, A preallocated vector to hold the correlation result. - @param spanType The type/span of the correlation. - @return The correlation result. -*/ -signalVector* correlate(signalVector *a, - signalVector *b, - signalVector *c, - ConvType spanType); - -/** Operate soft slicer on real-valued portion of vector */ -bool vectorSlicer(signalVector *x); - -/** GMSK modulate a GSM burst of bits */ -signalVector *modulateBurst(const BitVector &wBurst, - const signalVector &gsmPulse, - int guardPeriodLength, - int samplesPerSymbol); - -/** Sinc function */ -float sinc(float x); - -/** Delay a vector */ -void delayVector(signalVector &wBurst, - float delay); - -/** Add two vectors in-place */ -bool addVector(signalVector &x, - signalVector &y); - -/** Generate a vector of gaussian noise */ -signalVector *gaussianNoise(int length, - float variance = 1.0, - complex mean = complex(0.0)); - -/** - Given a non-integer index, interpolate a sample. - @param inSig The signal from which to interpolate. - @param ix The index. - @return The interpolated signal value. -*/ -complex interpolatePoint(const signalVector &inSig, - float ix); - -/** - Given a correlator output, locate the correlation peak. - @param rxBurst The correlator result. - @param peakIndex Pointer to value to receive interpolated peak index. - @param avgPower Power to value to receive mean power. - @return Peak value. -*/ -complex peakDetect(const signalVector &rxBurst, - float *peakIndex, - float *avgPwr); - -/** - Apply a scalar to a vector. - @param x The vector of interest. - @param scale The scalar. -*/ -void scaleVector(signalVector &x, - complex scale); - -/** - Add a constant offset to a vecotr. - @param x The vector of interest. - @param offset The offset. -*/ -void offsetVector(signalVector &x, - complex offset); - -/** - Generate a modulated GSM midamble, stored within the library. - @param gsmPulse The GSM pulse used for modulation. - @param samplesPerSymbol The number of samples per GSM symbol. - @param TSC The training sequence [0..7] - @return Success. -*/ -bool generateMidamble(signalVector &gsmPulse, - int samplesPerSymbol, - int TSC); -/** - Generate a modulated RACH sequence, stored within the library. - @param gsmPulse The GSM pulse used for modulation. - @param samplesPerSymbol The number of samples per GSM symbol. - @return Success. -*/ -bool generateRACHSequence(signalVector &gsmPulse, - int samplesPerSymbol); - -/** - Energy detector, checks to see if received burst energy is above a threshold. - @param rxBurst The received GSM burst of interest. - @param windowLength The number of burst samples used to compute burst energy - @param detectThreshold The detection threshold, a linear value. - @param avgPwr The average power of the received burst. - @return True if burst energy is above threshold. -*/ -bool energyDetect(signalVector &rxBurst, - unsigned windowLength, - float detectThreshold, - float *avgPwr = NULL); - -/** - RACH correlator/detector. - @param rxBurst The received GSM burst of interest. - @param detectThreshold The threshold that the received burst's post-correlator SNR is compared against to determine validity. - @param samplesPerSymbol The number of samples per GSM symbol. - @param amplitude The estimated amplitude of received RACH burst. - @param TOA The estimate time-of-arrival of received RACH burst. - @return True if burst SNR is larger that the detectThreshold value. -*/ -bool detectRACHBurst(signalVector &rxBurst, - float detectThreshold, - int samplesPerSymbol, - complex *amplitude, - float* TOA); - -/** - Normal burst correlator, detector, channel estimator. - @param rxBurst The received GSM burst of interest. - - @param detectThreshold The threshold that the received burst's post-correlator SNR is compared against to determine validity. - @param samplesPerSymbol The number of samples per GSM symbol. - @param amplitude The estimated amplitude of received RACH burst. - @param TOA The estimate time-of-arrival of received RACH burst. - @param requestChannel Set to true if channel estimation is desired. - @param channelResponse The estimated channel. - @param channelResponseOffset The time offset b/w the first sample of the channel response and the reported TOA. - @return True if burst SNR is larger that the detectThreshold value. -*/ -bool analyzeTrafficBurst(signalVector &rxBurst, - unsigned TSC, - float detectThreshold, - int samplesPerSymbol, - complex *amplitude, - float *TOA, - bool requestChannel = false, - signalVector** channelResponse = NULL, - float *channelResponseOffset = NULL); - -/** - Decimate a vector. - @param wVector The vector of interest. - @param decimationFactor The amount of decimation, i.e. the decimation factor. - @return The decimated signal vector. -*/ -signalVector *decimateVector(signalVector &wVector, - int decimationFactor); - -/** - Demodulates a received burst using a soft-slicer. - @param rxBurst The burst to be demodulated. - @param gsmPulse The GSM pulse. - @param samplesPerSymbol The number of samples per GSM symbol. - @param channel The amplitude estimate of the received burst. - @param TOA The time-of-arrival of the received burst. - @return The demodulated bit sequence. -*/ -SoftVector *demodulateBurst(const signalVector &rxBurst, - const signalVector &gsmPulse, - int samplesPerSymbol, - complex channel, - float TOA); - -/** - Creates a simple Kaiser-windowed low-pass FIR filter. - @param cutoffFreq The digital 3dB bandwidth of the filter. - @param filterLen The number of taps in the filter. - @param gainDC The DC gain of the filter. - @return The desired LPF -*/ -signalVector *createLPF(float cutoffFreq, - int filterLen, - float gainDC = 1.0); - -/** - Change sampling rate of a vector via polyphase resampling. - @param wVector The vector to be resampled. - @param P The numerator, i.e. the amount of upsampling. - @param Q The denominator, i.e. the amount of downsampling. - @param LPF An optional low-pass filter used in the resampling process. - @return A vector resampled at P/Q of the original sampling rate. -*/ -signalVector *polyphaseResampleVector(signalVector &wVector, - int P, int Q, - signalVector *LPF); - -/** - Change the sampling rate of a vector via linear interpolation. - @param wVector The vector to be resampled. - @param expFactor Ratio of new sampling rate/original sampling rate. - @param endPoint ??? - @return A vector resampled a expFactor*original sampling rate. -*/ -signalVector *resampleVector(signalVector &wVector, - float expFactor, - complex endPoint); - -/** - Design the necessary filters for a decision-feedback equalizer. - @param channelResponse The multipath channel that we're mitigating. - @param SNRestimate The signal-to-noise estimate of the channel, a linear value - @param Nf The number of taps in the feedforward filter. - @param feedForwardFilter The designed feed forward filter. - @param feedbackFilter The designed feedback filter. - @return True if DFE can be designed. -*/ -bool designDFE(signalVector &channelResponse, - float SNRestimate, - int Nf, - signalVector **feedForwardFilter, - signalVector **feedbackFilter); - -/** - Equalize/demodulate a received burst via a decision-feedback equalizer. - @param rxBurst The received burst to be demodulated. - @param TOA The time-of-arrival of the received burst. - @param samplesPerSymbol The number of samples per GSM symbol. - @param w The feed forward filter of the DFE. - @param b The feedback filter of the DFE. - @return The demodulated bit sequence. -*/ -SoftVector *equalizeBurst(signalVector &rxBurst, - float TOA, - int samplesPerSymbol, - signalVector &w, - signalVector &b); diff --git a/public-trunk/Transceiver/std_inband.rbf b/public-trunk/Transceiver/std_inband.rbf deleted file mode 100755 index 63842b74a1e5ab8922f179fa1f769092903cfccc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 176507 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

2`UdMeIUn_zTFdoXM`m5jCuZkMX+h~O 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