41 |
41 |
#define LMS_MIN_BW_SUPPORTED 2.5e6 /* 2.5mHz, minimum supported by LMS */
|
42 |
42 |
#define LMS_CALIBRATE_BW_HZ OSMO_MAX(GSM_CARRIER_BW, LMS_MIN_BW_SUPPORTED)
|
43 |
43 |
|
44 |
|
static int compat_LMS_VCTCXORead(lms_device_t *dev, uint16_t *val, bool memory)
|
45 |
|
{
|
46 |
|
#if HAVE_LMS_VCTCXO_EEPROM_SAVING
|
47 |
|
return LMS_VCTCXORead(dev, val, memory);
|
48 |
|
#else
|
49 |
|
return LMS_VCTCXORead(dev, val);
|
50 |
|
#endif
|
51 |
|
}
|
52 |
|
|
53 |
|
static int compat_LMS_VCTCXOWrite(lms_device_t *dev, uint16_t val, bool memory)
|
54 |
|
{
|
55 |
|
#if HAVE_LMS_VCTCXO_EEPROM_SAVING
|
56 |
|
return LMS_VCTCXOWrite(dev, val, memory);
|
57 |
|
#else
|
58 |
|
return LMS_VCTCXOWrite(dev, val);
|
59 |
|
#endif
|
60 |
|
}
|
61 |
|
|
62 |
44 |
LMSDevice::LMSDevice(size_t tx_sps, size_t rx_sps, InterfaceType iface, size_t chans, double lo_offset,
|
63 |
45 |
const std::vector<std::string>& tx_paths,
|
64 |
46 |
const std::vector<std::string>& rx_paths):
|
... | ... | |
78 |
60 |
|
79 |
61 |
LMSDevice::~LMSDevice()
|
80 |
62 |
{
|
|
63 |
unsigned int i;
|
81 |
64 |
LOGC(DDEV, INFO) << "Closing LMS device";
|
82 |
65 |
if (m_lms_dev) {
|
|
66 |
/* disable all channels */
|
|
67 |
for (i=0; i<chans; i++) {
|
|
68 |
LMS_EnableChannel(m_lms_dev, LMS_CH_RX, i, false);
|
|
69 |
LMS_EnableChannel(m_lms_dev, LMS_CH_TX, i, false);
|
|
70 |
}
|
83 |
71 |
LMS_Close(m_lms_dev);
|
84 |
72 |
m_lms_dev = NULL;
|
85 |
73 |
}
|
... | ... | |
145 |
133 |
|
146 |
134 |
int LMSDevice::open(const std::string &args, int ref, bool swap_channels)
|
147 |
135 |
{
|
148 |
|
//lms_info_str_t dev_str;
|
149 |
136 |
lms_info_str_t* info_list;
|
150 |
|
lms_range_t range_lpfbw_rx, range_lpfbw_tx, range_sr;
|
151 |
|
float_type sr_host, sr_rf, lpfbw_rx, lpfbw_tx;
|
152 |
|
uint16_t dac_val;
|
|
137 |
const lms_dev_info_t* device_info;
|
|
138 |
lms_range_t range_sr;
|
|
139 |
float_type sr_host, sr_rf;
|
153 |
140 |
unsigned int i, n;
|
154 |
141 |
int rc, dev_id;
|
155 |
142 |
|
... | ... | |
188 |
175 |
|
189 |
176 |
delete [] info_list;
|
190 |
177 |
|
|
178 |
device_info = LMS_GetDeviceInfo(m_lms_dev);
|
|
179 |
|
|
180 |
if ((ref != REF_EXTERNAL) && (ref != REF_INTERNAL))
|
|
181 |
{
|
|
182 |
LOGC(DDEV, ALERT) << "Invalid reference type";
|
|
183 |
goto out_close;
|
|
184 |
}
|
|
185 |
|
|
186 |
/* if reference clock is external setup must happen _before_ calling LMS_Init */
|
|
187 |
/* FIXME make external reference frequency configurable */
|
|
188 |
if (ref == REF_EXTERNAL) {
|
|
189 |
LOGC(DDEV, INFO) << "Setting External clock reference to 10MHz";
|
|
190 |
/* Assume an external 10 MHz reference clock */
|
|
191 |
if (LMS_SetClockFreq(m_lms_dev, LMS_CLOCK_EXTREF, 10000000.0) < 0)
|
|
192 |
goto out_close;
|
|
193 |
}
|
|
194 |
|
191 |
195 |
LOGC(DDEV, INFO) << "Init LMS device";
|
192 |
196 |
if (LMS_Init(m_lms_dev) != 0) {
|
193 |
197 |
LOGC(DDEV, ERROR) << "LMS_Init() failed";
|
194 |
198 |
goto out_close;
|
195 |
199 |
}
|
196 |
200 |
|
|
201 |
/* LimeSDR-Mini does not have switches but needs soldering to select external/internal clock */
|
|
202 |
/* LimeNET-Micro also does not like selecting internal clock*/
|
|
203 |
/* also set device specific maximum tx levels selected by phasenoise measurements*/
|
|
204 |
if (strncmp(device_info->deviceName,"LimeSDR-USB",11)==0)
|
|
205 |
{
|
|
206 |
/* if reference clock is internal setup must happen _after_ calling LMS_Init */
|
|
207 |
if (ref == REF_INTERNAL) {
|
|
208 |
LOGC(DDEV, INFO) << "Setting Internal clock reference";
|
|
209 |
if (LMS_SetClockFreq(m_lms_dev, LMS_CLOCK_EXTREF, -1) < 0)
|
|
210 |
goto out_close;
|
|
211 |
}
|
|
212 |
maxTxGainClamp = 73.0;
|
|
213 |
}
|
|
214 |
else
|
|
215 |
if (strncmp(device_info->deviceName,"LimeSDR-Mini",12)==0)
|
|
216 |
maxTxGainClamp = 66.0;
|
|
217 |
else
|
|
218 |
maxTxGainClamp = 71.0; /* "LimeNET-Micro", etc FIXME pciE based LMS boards?*/
|
|
219 |
|
|
220 |
/* enable all used channels */
|
|
221 |
for (i=0; i<chans; i++) {
|
|
222 |
if (LMS_EnableChannel(m_lms_dev, LMS_CH_RX, i, true) < 0)
|
|
223 |
goto out_close;
|
|
224 |
|
|
225 |
if (LMS_EnableChannel(m_lms_dev, LMS_CH_TX, i, true) < 0)
|
|
226 |
goto out_close;
|
|
227 |
}
|
|
228 |
|
|
229 |
/* set samplerate */
|
197 |
230 |
if (LMS_GetSampleRateRange(m_lms_dev, LMS_CH_RX, &range_sr))
|
198 |
231 |
goto out_close;
|
199 |
232 |
print_range("Sample Rate", &range_sr);
|
... | ... | |
209 |
242 |
/* FIXME: make this device/model dependent, like UHDDevice:dev_param_map! */
|
210 |
243 |
ts_offset = static_cast<TIMESTAMP>(8.9e-5 * GSMRATE * tx_sps); /* time * sample_rate */
|
211 |
244 |
|
212 |
|
switch (ref) {
|
213 |
|
case REF_INTERNAL:
|
214 |
|
LOGC(DDEV, INFO) << "Setting Internal clock reference";
|
215 |
|
/* Ugly API: Selecting clock source implicit by writing to VCTCXO DAC ?!? */
|
216 |
|
if (compat_LMS_VCTCXORead(m_lms_dev, &dac_val, false) < 0)
|
217 |
|
goto out_close;
|
218 |
|
LOGC(DDEV, INFO) << "Setting VCTCXO to " << dac_val;
|
219 |
|
if (compat_LMS_VCTCXOWrite(m_lms_dev, dac_val, false) < 0)
|
220 |
|
goto out_close;
|
221 |
|
break;
|
222 |
|
case REF_EXTERNAL:
|
223 |
|
LOGC(DDEV, INFO) << "Setting External clock reference to " << 10000000.0;
|
224 |
|
/* Assume an external 10 MHz reference clock */
|
225 |
|
if (LMS_SetClockFreq(m_lms_dev, LMS_CLOCK_EXTREF, 10000000.0) < 0)
|
226 |
|
goto out_close;
|
227 |
|
break;
|
228 |
|
default:
|
229 |
|
LOGC(DDEV, ALERT) << "Invalid reference type";
|
230 |
|
goto out_close;
|
231 |
|
}
|
232 |
|
|
233 |
|
if (LMS_GetLPFBWRange(m_lms_dev, LMS_CH_RX, &range_lpfbw_rx))
|
234 |
|
goto out_close;
|
235 |
|
print_range("LPFBWRange Rx", &range_lpfbw_rx);
|
236 |
|
if (LMS_GetLPFBWRange(m_lms_dev, LMS_CH_RX, &range_lpfbw_tx))
|
237 |
|
goto out_close;
|
238 |
|
print_range("LPFBWRange Tx", &range_lpfbw_tx);
|
239 |
|
lpfbw_rx = OSMO_MIN(OSMO_MAX(1.4001e6, range_lpfbw_rx.min), range_lpfbw_rx.max);
|
240 |
|
lpfbw_tx = OSMO_MIN(OSMO_MAX(5.2e6, range_lpfbw_tx.min), range_lpfbw_tx.max);
|
241 |
|
|
242 |
|
LOGC(DDEV, INFO) << "LPFBW: Rx=" << lpfbw_rx << " Tx=" << lpfbw_tx;
|
243 |
|
|
|
245 |
/* configure antennas */
|
244 |
246 |
if (!set_antennas()) {
|
245 |
247 |
LOGC(DDEV, ALERT) << "LMS antenna setting failed";
|
246 |
|
return -1;
|
247 |
|
}
|
248 |
|
|
249 |
|
/* Perform Rx and Tx calibration */
|
250 |
|
for (i=0; i<chans; i++) {
|
251 |
|
LOGC(DDEV, INFO) << "Setting LPFBW chan " << i;
|
252 |
|
if (LMS_SetLPFBW(m_lms_dev, LMS_CH_RX, i, lpfbw_rx) < 0)
|
253 |
|
goto out_close;
|
254 |
|
if (LMS_SetLPFBW(m_lms_dev, LMS_CH_TX, i, lpfbw_tx) < 0)
|
255 |
|
goto out_close;
|
256 |
|
LOGC(DDEV, INFO) << "Calibrating chan " << i;
|
257 |
|
if (LMS_Calibrate(m_lms_dev, LMS_CH_RX, i, LMS_CALIBRATE_BW_HZ, 0) < 0)
|
258 |
|
goto out_close;
|
259 |
|
if (LMS_Calibrate(m_lms_dev, LMS_CH_TX, i, LMS_CALIBRATE_BW_HZ, 0) < 0)
|
260 |
|
goto out_close;
|
|
248 |
goto out_close;
|
261 |
249 |
}
|
262 |
250 |
|
263 |
251 |
samplesRead = 0;
|
... | ... | |
286 |
274 |
|
287 |
275 |
/* configure the channels/streams */
|
288 |
276 |
for (i=0; i<chans; i++) {
|
289 |
|
if (LMS_EnableChannel(m_lms_dev, LMS_CH_RX, i, true) < 0)
|
|
277 |
/* set Gains for calibration/filter setup */
|
|
278 |
/* TX gain to maximum */
|
|
279 |
setTxGain(maxTxGain(), i);
|
|
280 |
/* RX gain to midpoint */
|
|
281 |
setRxGain(((minRxGain() + maxRxGain()) / 2), i);
|
|
282 |
|
|
283 |
/* set upRx and Tx filters */
|
|
284 |
if (!do_filters(i))
|
290 |
285 |
return false;
|
291 |
|
|
292 |
|
if (LMS_EnableChannel(m_lms_dev, LMS_CH_TX, i, true) < 0)
|
|
286 |
/* Perform Rx and Tx calibration */
|
|
287 |
if (!do_calib(i))
|
293 |
288 |
return false;
|
294 |
289 |
|
295 |
|
// Set gains to midpoint
|
296 |
|
setTxGain((minTxGain() + maxTxGain()) / 2, i);
|
297 |
|
setRxGain((minRxGain() + maxRxGain()) / 2, i);
|
298 |
|
|
|
290 |
/* configure Streams */
|
299 |
291 |
m_lms_stream_rx[i] = {};
|
300 |
292 |
m_lms_stream_rx[i].isTx = false;
|
301 |
293 |
m_lms_stream_rx[i].channel = i;
|
... | ... | |
348 |
340 |
for (i=0; i<chans; i++) {
|
349 |
341 |
LMS_DestroyStream(m_lms_dev, &m_lms_stream_tx[i]);
|
350 |
342 |
LMS_DestroyStream(m_lms_dev, &m_lms_stream_rx[i]);
|
351 |
|
LMS_EnableChannel(m_lms_dev, LMS_CH_RX, i, false);
|
352 |
|
LMS_EnableChannel(m_lms_dev, LMS_CH_TX, i, false);
|
353 |
|
}
|
|
343 |
}
|
354 |
344 |
|
355 |
345 |
started = false;
|
356 |
346 |
return true;
|
357 |
347 |
}
|
358 |
348 |
|
|
349 |
/* do rx and tx calibration - depends on gain, freq and bw */
|
|
350 |
bool LMSDevice::do_calib(size_t chan)
|
|
351 |
{
|
|
352 |
LOGC(DDEV, INFO) << "Calibrating chan " << chan;
|
|
353 |
if (LMS_Calibrate(m_lms_dev, LMS_CH_RX, chan, LMS_CALIBRATE_BW_HZ, 0) < 0)
|
|
354 |
return false;
|
|
355 |
if (LMS_Calibrate(m_lms_dev, LMS_CH_TX, chan, LMS_CALIBRATE_BW_HZ, 0) < 0)
|
|
356 |
return false;
|
|
357 |
return true;
|
|
358 |
}
|
|
359 |
|
|
360 |
/* do filter config - depends on bw only? */
|
|
361 |
bool LMSDevice::do_filters(size_t chan)
|
|
362 |
{
|
|
363 |
lms_range_t range_lpfbw_rx, range_lpfbw_tx;
|
|
364 |
float_type lpfbw_rx, lpfbw_tx;
|
|
365 |
|
|
366 |
LOGC(DDEV, INFO) << "Setting filters on chan " << chan;
|
|
367 |
if (LMS_GetLPFBWRange(m_lms_dev, LMS_CH_RX, &range_lpfbw_rx))
|
|
368 |
return false;
|
|
369 |
print_range("LPFBWRange Rx", &range_lpfbw_rx);
|
|
370 |
if (LMS_GetLPFBWRange(m_lms_dev, LMS_CH_RX, &range_lpfbw_tx))
|
|
371 |
return false;
|
|
372 |
print_range("LPFBWRange Tx", &range_lpfbw_tx);
|
|
373 |
|
|
374 |
lpfbw_rx = OSMO_MIN(OSMO_MAX(1.4001e6, range_lpfbw_rx.min), range_lpfbw_rx.max);
|
|
375 |
lpfbw_tx = OSMO_MIN(OSMO_MAX(5.2e6, range_lpfbw_tx.min), range_lpfbw_tx.max);
|
|
376 |
|
|
377 |
LOGC(DDEV, INFO) << "LPFBW: Rx=" << lpfbw_rx << " Tx=" << lpfbw_tx;
|
|
378 |
|
|
379 |
LOGC(DDEV, INFO) << "Setting LPFBW chan " << chan;
|
|
380 |
if (LMS_SetLPFBW(m_lms_dev, LMS_CH_RX, chan, lpfbw_rx) < 0)
|
|
381 |
return false;
|
|
382 |
if (LMS_SetLPFBW(m_lms_dev, LMS_CH_TX, chan, lpfbw_tx) < 0)
|
|
383 |
return false;
|
|
384 |
return true;
|
|
385 |
}
|
|
386 |
|
359 |
387 |
double LMSDevice::maxTxGain()
|
360 |
388 |
{
|
361 |
|
return 73.0;
|
|
389 |
return maxTxGainClamp;
|
362 |
390 |
}
|
363 |
391 |
|
364 |
392 |
double LMSDevice::minTxGain()
|
... | ... | |
388 |
416 |
if (LMS_SetGaindB(m_lms_dev, LMS_CH_TX, chan, dB) < 0)
|
389 |
417 |
LOGC(DDEV, ERR) << "chan " << chan <<": Error setting TX gain to " << dB << " dB";
|
390 |
418 |
|
|
419 |
//FIXME crashes when called while stream is running
|
|
420 |
// do_calib(chan);
|
|
421 |
|
391 |
422 |
return dB;
|
392 |
423 |
}
|
393 |
424 |
|
... | ... | |
403 |
434 |
if (LMS_SetGaindB(m_lms_dev, LMS_CH_RX, chan, dB) < 0)
|
404 |
435 |
LOGC(DDEV, ERR) << "chan "<< chan << ": Error setting RX gain to " << dB << " dB";
|
405 |
436 |
|
|
437 |
//FIXME crashes when called while stream is running
|
|
438 |
// do_calib(chan);
|
|
439 |
|
406 |
440 |
return dB;
|
407 |
441 |
}
|
408 |
442 |
|
... | ... | |
548 |
582 |
}
|
549 |
583 |
|
550 |
584 |
GSM::Time LMSDevice::minLatency() {
|
551 |
|
/* Empirical data from a handful of
|
552 |
|
relatively recent machines shows that the B100 will underrun when
|
553 |
|
the transmit threshold is reduced to a time of 6 and a half frames,
|
554 |
|
so we set a minimum 7 frame threshold. */
|
555 |
|
return GSM::Time(6,7);
|
|
585 |
/* UNUSED on limesdr (only used on usrp1/2) */
|
|
586 |
return GSM::Time(0,0);
|
556 |
587 |
}
|
557 |
588 |
|
558 |
589 |
void LMSDevice::update_stream_stats(size_t chan, bool * underrun, bool * overrun)
|