blob: 5c9900383e5cadbbfc8956f3aac4e60dc9c4d946 [file] [log] [blame]
dburgessb3a0ca42011-10-12 07:44:40 +00001/*
2* Copyright 2008, 2009 Free Software Foundation, Inc.
3*
4* This software is distributed under the terms of the GNU Affero Public License.
5* See the COPYING file in the main directory for details.
6*
7* This use of this software may be subject to additional restrictions.
8* See the LEGAL file in the main directory for details.
9
10 This program is free software: you can redistribute it and/or modify
11 it under the terms of the GNU Affero General Public License as published by
12 the Free Software Foundation, either version 3 of the License, or
13 (at your option) any later version.
14
15 This program is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 GNU Affero General Public License for more details.
19
20 You should have received a copy of the GNU Affero General Public License
21 along with this program. If not, see <http://www.gnu.org/licenses/>.
22
23*/
24
25
26/*
27 Compilation Flags
28
29 SWLOOPBACK compile for software loopback testing
30*/
31
32
33#include <stdint.h>
34#include <string.h>
35#include <stdlib.h>
36#include "Threads.h"
37#include "USRPDevice.h"
38
39#include <Logger.h>
40
kurtis.heimerl3758b162011-11-26 03:19:22 +000041#ifdef HAVE_CONFIG_H
42#include "config.h"
43#endif
dburgessb3a0ca42011-10-12 07:44:40 +000044
45using namespace std;
46
kurtis.heimerl79e71c92011-11-26 03:16:48 +000047enum dboardConfigType {
48 TXA_RXB,
49 TXB_RXA,
50 TXA_RXA,
51 TXB_RXB
52};
dburgessb3a0ca42011-10-12 07:44:40 +000053
kurtis.heimerl3758b162011-11-26 03:19:22 +000054#ifdef SINGLEDB
55const dboardConfigType dboardConfig = TXA_RXA;
56#else
kurtis.heimerl79e71c92011-11-26 03:16:48 +000057const dboardConfigType dboardConfig = TXA_RXB;
kurtis.heimerl3758b162011-11-26 03:19:22 +000058#endif
59
kurtis.heimerl79e71c92011-11-26 03:16:48 +000060const double USRPDevice::masterClockRate = 52.0e6;
dburgessb3a0ca42011-10-12 07:44:40 +000061
Thomas Tsoucb69f082013-04-08 14:18:26 -040062USRPDevice::USRPDevice(int sps, bool skipRx)
kurtis.heimerl965e7572011-11-26 03:16:54 +000063 : skipRx(skipRx)
dburgessb3a0ca42011-10-12 07:44:40 +000064{
65 LOG(INFO) << "creating USRP device...";
Thomas Tsoucb69f082013-04-08 14:18:26 -040066 decimRate = (unsigned int) round(masterClockRate/((GSMRATE) * (double) sps));
dburgessb3a0ca42011-10-12 07:44:40 +000067 actualSampleRate = masterClockRate/decimRate;
68 rxGain = 0;
69
70#ifdef SWLOOPBACK
71 samplePeriod = 1.0e6/actualSampleRate;
72 loopbackBufferSize = 0;
73 gettimeofday(&lastReadTime,NULL);
74 firstRead = false;
75#endif
76}
77
Thomas Tsoucb69f082013-04-08 14:18:26 -040078int USRPDevice::open(const std::string &)
dburgessb3a0ca42011-10-12 07:44:40 +000079{
dburgessb3a0ca42011-10-12 07:44:40 +000080 writeLock.unlock();
81
kurtis.heimerl965e7572011-11-26 03:16:54 +000082 LOG(INFO) << "opening USRP device..";
dburgessb3a0ca42011-10-12 07:44:40 +000083#ifndef SWLOOPBACK
84 string rbf = "std_inband.rbf";
85 //string rbf = "inband_1rxhb_1tx.rbf";
86 m_uRx.reset();
87 if (!skipRx) {
88 try {
89 m_uRx = usrp_standard_rx_sptr(usrp_standard_rx::make(0,decimRate,1,-1,
90 usrp_standard_rx::FPGA_MODE_NORMAL,
91 1024,16*8,rbf));
92#ifdef HAVE_LIBUSRP_3_2
93 m_uRx->set_fpga_master_clock_freq(masterClockRate);
94#endif
95 }
96
97 catch(...) {
98 LOG(ALERT) << "make failed on Rx";
99 m_uRx.reset();
Thomas Tsoucb69f082013-04-08 14:18:26 -0400100 return -1;
dburgessb3a0ca42011-10-12 07:44:40 +0000101 }
102
103 if (m_uRx->fpga_master_clock_freq() != masterClockRate)
104 {
105 LOG(ALERT) << "WRONG FPGA clock freq = " << m_uRx->fpga_master_clock_freq()
106 << ", desired clock freq = " << masterClockRate;
107 m_uRx.reset();
Thomas Tsoucb69f082013-04-08 14:18:26 -0400108 return -1;
dburgessb3a0ca42011-10-12 07:44:40 +0000109 }
110 }
111
112 try {
113 m_uTx = usrp_standard_tx_sptr(usrp_standard_tx::make(0,decimRate*2,1,-1,
114 1024,16*8,rbf));
115#ifdef HAVE_LIBUSRP_3_2
116 m_uTx->set_fpga_master_clock_freq(masterClockRate);
117#endif
118 }
119
120 catch(...) {
121 LOG(ALERT) << "make failed on Tx";
122 m_uTx.reset();
Thomas Tsoucb69f082013-04-08 14:18:26 -0400123 return -1;
dburgessb3a0ca42011-10-12 07:44:40 +0000124 }
125
126 if (m_uTx->fpga_master_clock_freq() != masterClockRate)
127 {
128 LOG(ALERT) << "WRONG FPGA clock freq = " << m_uTx->fpga_master_clock_freq()
129 << ", desired clock freq = " << masterClockRate;
130 m_uTx.reset();
Thomas Tsoucb69f082013-04-08 14:18:26 -0400131 return -1;
dburgessb3a0ca42011-10-12 07:44:40 +0000132 }
133
134 if (!skipRx) m_uRx->stop();
135 m_uTx->stop();
136
137#endif
138
kurtis.heimerl79e71c92011-11-26 03:16:48 +0000139 switch (dboardConfig) {
140 case TXA_RXB:
kurtis.heimerl79e71c92011-11-26 03:16:48 +0000141 txSubdevSpec = usrp_subdev_spec(0,0);
142 rxSubdevSpec = usrp_subdev_spec(1,0);
143 break;
144 case TXB_RXA:
kurtis.heimerl79e71c92011-11-26 03:16:48 +0000145 txSubdevSpec = usrp_subdev_spec(1,0);
146 rxSubdevSpec = usrp_subdev_spec(0,0);
147 break;
148 case TXA_RXA:
kurtis.heimerl79e71c92011-11-26 03:16:48 +0000149 txSubdevSpec = usrp_subdev_spec(0,0);
150 rxSubdevSpec = usrp_subdev_spec(0,0);
151 break;
152 case TXB_RXB:
kurtis.heimerl79e71c92011-11-26 03:16:48 +0000153 txSubdevSpec = usrp_subdev_spec(1,0);
154 rxSubdevSpec = usrp_subdev_spec(1,0);
155 break;
156 default:
kurtis.heimerl79e71c92011-11-26 03:16:48 +0000157 txSubdevSpec = usrp_subdev_spec(0,0);
158 rxSubdevSpec = usrp_subdev_spec(1,0);
159 }
160
kurtis.heimerlb9e237e2011-11-26 03:17:59 +0000161 m_dbTx = m_uTx->selected_subdev(txSubdevSpec);
162 m_dbRx = m_uRx->selected_subdev(rxSubdevSpec);
163
dburgessb3a0ca42011-10-12 07:44:40 +0000164 samplesRead = 0;
165 samplesWritten = 0;
166 started = false;
167
Thomas Tsoucb69f082013-04-08 14:18:26 -0400168 return NORMAL;
dburgessb3a0ca42011-10-12 07:44:40 +0000169}
170
171
172
173bool USRPDevice::start()
174{
175 LOG(INFO) << "starting USRP...";
176#ifndef SWLOOPBACK
177 if (!m_uRx && !skipRx) return false;
178 if (!m_uTx) return false;
179
180 if (!skipRx) m_uRx->stop();
181 m_uTx->stop();
182
183 writeLock.lock();
184 // power up and configure daughterboards
kurtis.heimerl79e71c92011-11-26 03:16:48 +0000185 m_dbTx->set_enable(true);
186 m_uTx->set_mux(m_uTx->determine_tx_mux_value(txSubdevSpec));
187 m_uRx->set_mux(m_uRx->determine_rx_mux_value(rxSubdevSpec));
188
189 if (!m_dbRx->select_rx_antenna(1))
190 m_dbRx->select_rx_antenna(0);
191
dburgessb3a0ca42011-10-12 07:44:40 +0000192 writeLock.unlock();
193
kurtis.heimerl79e71c92011-11-26 03:16:48 +0000194 // Set gains to midpoint
195 setTxGain((minTxGain() + maxTxGain()) / 2);
196 setRxGain((minRxGain() + maxRxGain()) / 2);
dburgessb3a0ca42011-10-12 07:44:40 +0000197
198 data = new short[currDataSize];
199 dataStart = 0;
200 dataEnd = 0;
201 timeStart = 0;
202 timeEnd = 0;
203 timestampOffset = 0;
204 latestWriteTimestamp = 0;
205 lastPktTimestamp = 0;
206 hi32Timestamp = 0;
207 isAligned = false;
208
209
210 if (!skipRx)
211 started = (m_uRx->start() && m_uTx->start());
212 else
213 started = m_uTx->start();
214 return started;
215#else
216 gettimeofday(&lastReadTime,NULL);
217 return true;
218#endif
219}
220
221bool USRPDevice::stop()
222{
223#ifndef SWLOOPBACK
224 if (!m_uRx) return false;
225 if (!m_uTx) return false;
226
dburgessb3a0ca42011-10-12 07:44:40 +0000227 delete[] currData;
228
229 started = !(m_uRx->stop() && m_uTx->stop());
230 return !started;
231#else
232 return true;
233#endif
234}
235
kurtis.heimerl79e71c92011-11-26 03:16:48 +0000236double USRPDevice::maxTxGain()
237{
238 return m_dbTx->gain_max();
239}
240
241double USRPDevice::minTxGain()
242{
243 return m_dbTx->gain_min();
244}
245
246double USRPDevice::maxRxGain()
247{
248 return m_dbRx->gain_max();
249}
250
251double USRPDevice::minRxGain()
252{
253 return m_dbRx->gain_min();
254}
255
dburgessb3a0ca42011-10-12 07:44:40 +0000256double USRPDevice::setTxGain(double dB) {
257
258 writeLock.lock();
259 if (dB > maxTxGain()) dB = maxTxGain();
260 if (dB < minTxGain()) dB = minTxGain();
261
kurtis.heimerl79e71c92011-11-26 03:16:48 +0000262 LOG(NOTICE) << "Setting TX gain to " << dB << " dB.";
dburgessb3a0ca42011-10-12 07:44:40 +0000263
ttsou711fdf62012-03-13 04:05:30 +0000264 if (!m_dbTx->set_gain(dB))
kurtis.heimerl4e59d662011-11-26 03:19:11 +0000265 LOG(ERR) << "Error setting TX gain";
dburgessb3a0ca42011-10-12 07:44:40 +0000266
267 writeLock.unlock();
268
269 return dB;
270}
271
272
273double USRPDevice::setRxGain(double dB) {
274
275 writeLock.lock();
276 if (dB > maxRxGain()) dB = maxRxGain();
277 if (dB < minRxGain()) dB = minRxGain();
278
kurtis.heimerlc3c7c3e2011-11-26 03:17:57 +0000279 LOG(NOTICE) << "Setting RX gain to " << dB << " dB.";
dburgessb3a0ca42011-10-12 07:44:40 +0000280
kurtis.heimerl79e71c92011-11-26 03:16:48 +0000281 if (!m_dbRx->set_gain(dB))
kurtis.heimerl4e59d662011-11-26 03:19:11 +0000282 LOG(ERR) << "Error setting RX gain";
kurtis.heimerl79e71c92011-11-26 03:16:48 +0000283
dburgessb3a0ca42011-10-12 07:44:40 +0000284 writeLock.unlock();
285
kurtis.heimerl79e71c92011-11-26 03:16:48 +0000286 return dB;
dburgessb3a0ca42011-10-12 07:44:40 +0000287}
288
289
290// NOTE: Assumes sequential reads
291int USRPDevice::readSamples(short *buf, int len, bool *overrun,
292 TIMESTAMP timestamp,
293 bool *underrun,
294 unsigned *RSSI)
295{
296#ifndef SWLOOPBACK
297 if (!m_uRx) return 0;
298
299 timestamp += timestampOffset;
300
301 if (timestamp + len < timeStart) {
302 memset(buf,0,len*2*sizeof(short));
303 return len;
304 }
305
306 if (underrun) *underrun = false;
307
308 uint32_t readBuf[2000];
309
310 while (1) {
311 //guestimate USB read size
312 int readLen=0;
313 {
314 int numSamplesNeeded = timestamp + len - timeEnd;
315 if (numSamplesNeeded <=0) break;
316 readLen = 512 * ((int) ceil((float) numSamplesNeeded/126.0));
317 if (readLen > 8000) readLen= (8000/512)*512;
318 }
319
320 // read USRP packets, parse and save A/D data as needed
321 readLen = m_uRx->read((void *)readBuf,readLen,overrun);
322 for(int pktNum = 0; pktNum < (readLen/512); pktNum++) {
323 // tmpBuf points to start of a USB packet
324 uint32_t* tmpBuf = (uint32_t *) (readBuf+pktNum*512/4);
325 TIMESTAMP pktTimestamp = usrp_to_host_u32(tmpBuf[1]);
326 uint32_t word0 = usrp_to_host_u32(tmpBuf[0]);
327 uint32_t chan = (word0 >> 16) & 0x1f;
328 unsigned payloadSz = word0 & 0x1ff;
329 LOG(DEBUG) << "first two bytes: " << hex << word0 << " " << dec << pktTimestamp;
330
331 bool incrementHi32 = ((lastPktTimestamp & 0x0ffffffffll) > pktTimestamp);
332 if (incrementHi32 && (timeStart!=0)) {
333 LOG(DEBUG) << "high 32 increment!!!";
334 hi32Timestamp++;
335 }
336 pktTimestamp = (((TIMESTAMP) hi32Timestamp) << 32) | pktTimestamp;
337 lastPktTimestamp = pktTimestamp;
338
339 if (chan == 0x01f) {
340 // control reply, check to see if its ping reply
341 uint32_t word2 = usrp_to_host_u32(tmpBuf[2]);
342 if ((word2 >> 16) == ((0x01 << 8) | 0x02)) {
343 timestamp -= timestampOffset;
344 timestampOffset = pktTimestamp - pingTimestamp + PINGOFFSET;
345 LOG(DEBUG) << "updating timestamp offset to: " << timestampOffset;
346 timestamp += timestampOffset;
347 isAligned = true;
348 }
349 continue;
350 }
351 if (chan != 0) {
352 LOG(DEBUG) << "chan: " << chan << ", timestamp: " << pktTimestamp << ", sz:" << payloadSz;
353 continue;
354 }
355 if ((word0 >> 28) & 0x04) {
356 if (underrun) *underrun = true;
357 LOG(DEBUG) << "UNDERRUN in TRX->USRP interface";
358 }
359 if (RSSI) *RSSI = (word0 >> 21) & 0x3f;
360
361 if (!isAligned) continue;
362
363 unsigned cursorStart = pktTimestamp - timeStart + dataStart;
364 while (cursorStart*2 > currDataSize) {
365 cursorStart -= currDataSize/2;
366 }
367 if (cursorStart*2 + payloadSz/2 > currDataSize) {
368 // need to circle around buffer
369 memcpy(data+cursorStart*2,tmpBuf+2,(currDataSize-cursorStart*2)*sizeof(short));
370 memcpy(data,tmpBuf+2+(currDataSize/2-cursorStart),payloadSz-(currDataSize-cursorStart*2)*sizeof(short));
371 }
372 else {
373 memcpy(data+cursorStart*2,tmpBuf+2,payloadSz);
374 }
375 if (pktTimestamp + payloadSz/2/sizeof(short) > timeEnd)
376 timeEnd = pktTimestamp+payloadSz/2/sizeof(short);
377
378 LOG(DEBUG) << "timeStart: " << timeStart << ", timeEnd: " << timeEnd << ", pktTimestamp: " << pktTimestamp;
379
380 }
381 }
382
383 // copy desired data to buf
384 unsigned bufStart = dataStart+(timestamp-timeStart);
385 if (bufStart + len < currDataSize/2) {
386 LOG(DEBUG) << "bufStart: " << bufStart;
387 memcpy(buf,data+bufStart*2,len*2*sizeof(short));
388 memset(data+bufStart*2,0,len*2*sizeof(short));
389 }
390 else {
391 LOG(DEBUG) << "len: " << len << ", currDataSize/2: " << currDataSize/2 << ", bufStart: " << bufStart;
392 unsigned firstLength = (currDataSize/2-bufStart);
393 LOG(DEBUG) << "firstLength: " << firstLength;
394 memcpy(buf,data+bufStart*2,firstLength*2*sizeof(short));
395 memset(data+bufStart*2,0,firstLength*2*sizeof(short));
396 memcpy(buf+firstLength*2,data,(len-firstLength)*2*sizeof(short));
397 memset(data,0,(len-firstLength)*2*sizeof(short));
398 }
399 dataStart = (bufStart + len) % (currDataSize/2);
400 timeStart = timestamp + len;
401
dburgessb3a0ca42011-10-12 07:44:40 +0000402 return len;
403
404#else
405 if (loopbackBufferSize < 2) return 0;
406 int numSamples = 0;
407 struct timeval currTime;
408 gettimeofday(&currTime,NULL);
409 double timeElapsed = (currTime.tv_sec - lastReadTime.tv_sec)*1.0e6 +
410 (currTime.tv_usec - lastReadTime.tv_usec);
411 if (timeElapsed < samplePeriod) {return 0;}
412 int numSamplesToRead = (int) floor(timeElapsed/samplePeriod);
413 if (numSamplesToRead < len) return 0;
414
415 if (numSamplesToRead > len) numSamplesToRead = len;
416 if (numSamplesToRead > loopbackBufferSize/2) {
417 firstRead =false;
418 numSamplesToRead = loopbackBufferSize/2;
419 }
420 memcpy(buf,loopbackBuffer,sizeof(short)*2*numSamplesToRead);
421 loopbackBufferSize -= 2*numSamplesToRead;
422 memcpy(loopbackBuffer,loopbackBuffer+2*numSamplesToRead,
423 sizeof(short)*loopbackBufferSize);
424 numSamples = numSamplesToRead;
425 if (firstRead) {
426 int new_usec = lastReadTime.tv_usec + (int) round((double) numSamplesToRead * samplePeriod);
427 lastReadTime.tv_sec = lastReadTime.tv_sec + new_usec/1000000;
428 lastReadTime.tv_usec = new_usec % 1000000;
429 }
430 else {
431 gettimeofday(&lastReadTime,NULL);
432 firstRead = true;
433 }
434 samplesRead += numSamples;
435
436 return numSamples;
437#endif
438}
439
440int USRPDevice::writeSamples(short *buf, int len, bool *underrun,
441 unsigned long long timestamp,
442 bool isControl)
443{
444 writeLock.lock();
445
446#ifndef SWLOOPBACK
447 if (!m_uTx) return 0;
448
kurtis.heimerlc8739b82011-11-02 00:06:34 +0000449 static uint32_t outData[128*20];
dburgessb3a0ca42011-10-12 07:44:40 +0000450
451 for (int i = 0; i < len*2; i++) {
452 buf[i] = host_to_usrp_short(buf[i]);
453 }
454
455 int numWritten = 0;
456 unsigned isStart = 1;
457 unsigned RSSI = 0;
458 unsigned CHAN = (isControl) ? 0x01f : 0x00;
459 len = len*2*sizeof(short);
460 int numPkts = (int) ceil((float)len/(float)504);
461 unsigned isEnd = (numPkts < 2);
462 uint32_t *outPkt = outData;
463 int pktNum = 0;
464 while (numWritten < len) {
465 // pkt is pointer to start of a USB packet
466 uint32_t *pkt = outPkt + pktNum*128;
467 isEnd = (len - numWritten <= 504);
468 unsigned payloadLen = ((len - numWritten) < 504) ? (len-numWritten) : 504;
469 pkt[0] = (isStart << 12 | isEnd << 11 | (RSSI & 0x3f) << 5 | CHAN) << 16 | payloadLen;
470 pkt[1] = timestamp & 0x0ffffffffll;
471 memcpy(pkt+2,buf+(numWritten/sizeof(short)),payloadLen);
472 numWritten += payloadLen;
473 timestamp += payloadLen/2/sizeof(short);
474 isStart = 0;
475 pkt[0] = host_to_usrp_u32(pkt[0]);
476 pkt[1] = host_to_usrp_u32(pkt[1]);
477 pktNum++;
478 }
479 m_uTx->write((const void*) outPkt,sizeof(uint32_t)*128*numPkts,NULL);
480
481 samplesWritten += len/2/sizeof(short);
482 writeLock.unlock();
483
484 return len/2/sizeof(short);
485#else
486 int retVal = len;
487 memcpy(loopbackBuffer+loopbackBufferSize,buf,sizeof(short)*2*len);
488 samplesWritten += retVal;
489 loopbackBufferSize += retVal*2;
490
491 return retVal;
492#endif
493}
494
495bool USRPDevice::updateAlignment(TIMESTAMP timestamp)
496{
497#ifndef SWLOOPBACK
498 short data[] = {0x00,0x02,0x00,0x00};
499 uint32_t *wordPtr = (uint32_t *) data;
kurtis.heimerlc8739b82011-11-02 00:06:34 +0000500 *wordPtr = host_to_usrp_u32(*wordPtr);
dburgessb3a0ca42011-10-12 07:44:40 +0000501 bool tmpUnderrun;
502 if (writeSamples((short *) data,1,&tmpUnderrun,timestamp & 0x0ffffffffll,true)) {
503 pingTimestamp = timestamp;
504 return true;
505 }
506 return false;
507#else
508 return true;
509#endif
510}
511
512#ifndef SWLOOPBACK
kurtis.heimerl79e71c92011-11-26 03:16:48 +0000513bool USRPDevice::setTxFreq(double wFreq)
514{
515 usrp_tune_result result;
dburgessb3a0ca42011-10-12 07:44:40 +0000516
kurtis.heimerlb9e237e2011-11-26 03:17:59 +0000517 if (m_uTx->tune(txSubdevSpec.side, m_dbTx, wFreq, &result)) {
kurtis.heimerl79e71c92011-11-26 03:16:48 +0000518 LOG(INFO) << "set TX: " << wFreq << std::endl
519 << " baseband freq: " << result.baseband_freq << std::endl
520 << " DDC freq: " << result.dxc_freq << std::endl
521 << " residual freq: " << result.residual_freq;
522 return true;
523 }
524 else {
kurtis.heimerle3320322011-11-28 06:26:08 +0000525 LOG(ALERT) << "set TX: " << wFreq << "failed" << std::endl
kurtis.heimerl79e71c92011-11-26 03:16:48 +0000526 << " baseband freq: " << result.baseband_freq << std::endl
527 << " DDC freq: " << result.dxc_freq << std::endl
528 << " residual freq: " << result.residual_freq;
529 return false;
530 }
531}
532
533bool USRPDevice::setRxFreq(double wFreq)
534{
535 usrp_tune_result result;
536
537 if (m_uRx->tune(0, m_dbRx, wFreq, &result)) {
538 LOG(INFO) << "set RX: " << wFreq << std::endl
539 << " baseband freq: " << result.baseband_freq << std::endl
540 << " DDC freq: " << result.dxc_freq << std::endl
541 << " residual freq: " << result.residual_freq;
542 return true;
543 }
544 else {
kurtis.heimerle3320322011-11-28 06:26:08 +0000545 LOG(ALERT) << "set RX: " << wFreq << "failed" << std::endl
kurtis.heimerl79e71c92011-11-26 03:16:48 +0000546 << " baseband freq: " << result.baseband_freq << std::endl
547 << " DDC freq: " << result.dxc_freq << std::endl
548 << " residual freq: " << result.residual_freq;
549 return false;
550 }
551
552}
dburgessb3a0ca42011-10-12 07:44:40 +0000553
554#else
555bool USRPDevice::setTxFreq(double wFreq) { return true;};
556bool USRPDevice::setRxFreq(double wFreq) { return true;};
557#endif
kurtis.heimerl965e7572011-11-26 03:16:54 +0000558
Thomas Tsoucb69f082013-04-08 14:18:26 -0400559RadioDevice *RadioDevice::make(int sps, bool skipRx)
kurtis.heimerl965e7572011-11-26 03:16:54 +0000560{
Thomas Tsoucb69f082013-04-08 14:18:26 -0400561 return new USRPDevice(sps, skipRx);
kurtis.heimerl965e7572011-11-26 03:16:54 +0000562}