Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
c33aef1
comms: keep the FT601 overlapped valid until FinishDataXfer releases it
claude Aug 3, 2026
0af9f6d
comms: report the actual FX3 bulk transfer size on Windows
claude Aug 3, 2026
ee228e5
comms: report USB transfers that complete short or failed
claude Aug 3, 2026
14dcb8c
comms: bound the PCIe control command length to the packet capacity
claude Aug 3, 2026
27fd923
streaming: apply the Rx UNIX_EPOCH offset once per user packet
claude Aug 3, 2026
154d834
streaming: survive asymmetric channel configs and null meta in Tx
claude Aug 3, 2026
e855a56
streaming: keep composite Tx sample-tick timestamps in ticks
claude Aug 3, 2026
a62b90e
streaming: keep the packet pool large enough for its staging overhead
claude Aug 3, 2026
a2861e5
protocols: make the custom parameter scaling safe for small values
claude Aug 3, 2026
a3f748e
protocols: don't build outgoing packets on top of device responses
claude Aug 3, 2026
310ab6d
LMS7002M: fix channel B config load, sync check and MCU write verify
claude Aug 3, 2026
c53520b
lms7002m: fix VDIV_VCO restore, RSSI delay, CGEN divider and state bu…
claude Aug 3, 2026
2765c9d
CDCM6208: fix fractional divider wrap, phantom register and input mux
claude Aug 3, 2026
e983c92
chips: fix Si5351C PLL packing and ADF4002 counter validation
claude Aug 3, 2026
715de2a
LimeSDR_X3: fix the LMS3 RF switch channel bit position
claude Aug 3, 2026
02d3d19
MMX8: route the stream data path through the module streamers
claude Aug 3, 2026
1cfb000
boards: configure the Tx test signal even when the Rx one is enabled
claude Aug 3, 2026
32236e0
LimeSDR_XTRX: terminate the GNSS UART test on timeout
claude Aug 3, 2026
199d1cd
CFR: derive the FIR odd flag from the actual tap count
claude Aug 3, 2026
ed141ab
FPGA: fail SetInterfaceFreq when the LMS7002M readback fails
claude Aug 3, 2026
b90be14
FPGA: skip bypassed clocks when programming PLL dividers
claude Aug 3, 2026
c07f37e
FFT: guard the window coefficients against the worker thread
claude Aug 3, 2026
fca44f7
API: validate the plugin channel count and release the calibration st…
claude Aug 3, 2026
853e279
legacyapi: report the Rx timestamp through LMS_RecvStream
claude Aug 3, 2026
c345286
legacyapi: destroy the shared stream only when no channels remain
claude Aug 3, 2026
d101e66
legacyapi: keep the current MAC selection in LMS_Read/WriteParam
claude Aug 3, 2026
eb97e79
legacyapi: guard the NCO frequency table pointer against null
claude Aug 3, 2026
0b9eae4
limeTRX: fail when a substream cannot be created
claude Aug 3, 2026
e616042
limeTRX: don't drop samples on partial Tx and check the output file
claude Aug 3, 2026
0b4cc2f
cli: fix missing stream null check and limePPSDO exit code
claude Aug 3, 2026
81731f8
limeConfig: resolve a relative --ini path against the working directory
claude Aug 3, 2026
ea0785f
limeConfig: free the device on the mid-loop error paths
claude Aug 3, 2026
0867e2a
limePPSDO: select the register map from the connected device
claude Aug 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 12 additions & 11 deletions cli/limeConfig.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -162,17 +162,10 @@ int main(int argc, char** argv)
if (!iniFilename.empty())
{
config.skipDefaults = true;
std::string_view cwd{ argv[0] };
const size_t slash0Pos = cwd.find_last_of("/\\"sv);
if (slash0Pos != std::string_view::npos)
{
cwd = cwd.substr(0, slash0Pos);
}

if (iniFilename[0] != '/') // is not global path
configFilepath = std::string{ cwd } + "/"s;

configFilepath += iniFilename;
// resolve a relative path against the working directory, the standard CLI
// behavior; keying it off argv[0] broke PATH invocation and pointed at the
// installed binary's directory
configFilepath = iniFilename;
}

for (int moduleId : chipIndexes)
Expand All @@ -187,6 +180,7 @@ int main(int argc, char** argv)
if (!configFilepath.empty() && chip->LoadConfig(configFilepath) != OpStatus::Success)
{
cerr << "Error loading file: "sv << configFilepath << endl;
DeviceRegistry::freeDevice(device);
return EXIT_FAILURE;
}

Expand All @@ -199,14 +193,20 @@ int main(int argc, char** argv)
{
int rxPathIndex = AntennaNameToIndex(chipDescriptor.pathNames.at(TRXDir::Rx), rxAntennaName);
if (rxPathIndex < 0)
{
DeviceRegistry::freeDevice(device);
return EXIT_FAILURE;
}
config.channel[0].rx.path = rxPathIndex;
}
if (!txAntennaName.empty())
{
int txPathIndex = AntennaNameToIndex(chipDescriptor.pathNames.at(TRXDir::Tx), txAntennaName);
if (txPathIndex < 0)
{
DeviceRegistry::freeDevice(device);
return EXIT_FAILURE;
}
config.channel[0].tx.path = txPathIndex;
}

Expand All @@ -217,6 +217,7 @@ int main(int argc, char** argv)
if (device->Configure(config, moduleId) != OpStatus::Success)
{
cerr << "Failed to configure device (chip"sv << moduleId << ")"sv << std::endl;
DeviceRegistry::freeDevice(device);
return EXIT_FAILURE;
}
}
Expand Down
15 changes: 12 additions & 3 deletions cli/limePPSDO.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -223,17 +223,26 @@ bool PPSDODriver::updatePPSDORegList(vector<DeviceHandle>& handles, string& devN
bool regListUpdated = false;
DeviceHandle& mHandle = handles.front();

// Select the correct handle from list
// Select the same handle the connection used, not just the first in the list;
// matching a different device would program the wrong CSR addresses
if (!devName.empty())
{
const DeviceHandle wanted(std::string{ devName });
bool matched = false;
for (auto& iter : handles)
{
if (iter.name == devName)
if (iter.IsEqualIgnoringEmpty(wanted) || lime::cli::FuzzyHandleMatch(iter, devName))
{
mHandle = iter;
matched = true;
break;
}
}
if (!matched)
{
lime::error("limePPSDO: no device matches '%s'", devName.c_str());
return false;
}
}

switch (getDeviceMediaType(mHandle.media))
Expand Down Expand Up @@ -650,5 +659,5 @@ int main(int argc, char** argv)

driver.destroyCSR();
DeviceRegistry::freeDevice(device);
return EXIT_SUCCESS;
return runStatus == OpStatus::Success ? EXIT_SUCCESS : EXIT_FAILURE;
}
38 changes: 33 additions & 5 deletions cli/limeTRX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -348,10 +348,25 @@ static void TransmitLoop(TransmitLoopArgs* args)
}
if (samplesRemaining <= samplesBatchSize)
txMeta.flags = StreamTxMeta::EndOfBurst;
int32_t samplesSent = args->stream->Transmit(txSamples, samplesToSend, &txMeta);

txMeta.timestamp.AddTicks(samplesSent);
samplesRemaining -= samplesSent;
// Transmit can accept fewer than requested; resend the tail of this chunk
// before reading the next one, otherwise the unsent samples are skipped
int32_t chunkSent = 0;
while (chunkSent < samplesToSend)
{
complex16_t* offset[16];
for (int c = 0; c < args->channelCount; ++c)
offset[c] = txSamples[c] + chunkSent;
int32_t samplesSent = args->stream->Transmit(offset, samplesToSend - chunkSent, &txMeta);
if (samplesSent <= 0)
{
if (args->terminate->load(std::memory_order_relaxed))
break;
continue;
}
chunkSent += samplesSent;
txMeta.timestamp.AddTicks(samplesSent);
}
samplesRemaining -= chunkSent;
}
} while (args->loop && args->terminate->load(std::memory_order_relaxed) == false);
}
Expand Down Expand Up @@ -550,7 +565,14 @@ int main(int argc, char** argv)
break;
}

stream->Add(device->StreamCreate(streamCfg, index));
std::unique_ptr<RFStream> substream = device->StreamCreate(streamCfg, index);
if (!substream)
{
cerr << "Failed to create stream on chip " << index << endl;
DeviceRegistry::freeDevice(device);
return EXIT_FAILURE;
}
stream->Add(std::move(substream));
}

rx_require = rx ? channelCount : 0;
Expand Down Expand Up @@ -590,6 +612,12 @@ int main(int argc, char** argv)
{
std::cout << "Rx data to file: "sv << rxFilename << std::endl;
rxFile.open(rxFilename + ".sigmf-data", std::ofstream::out | std::ofstream::binary);
if (!rxFile)
{
cerr << "Failed to open Rx output file: "sv << rxFilename << ".sigmf-data"sv << endl;
DeviceRegistry::freeDevice(device);
return EXIT_FAILURE;
}
}

float peakAmplitude = 0;
Expand Down
6 changes: 6 additions & 0 deletions cli/utilities/frontendDelayTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,12 @@ int main(int argc, char** argv)
stream.hintSampleRate = device->GetSampleRate(0, TRXDir::Rx, 0);

auto trx = device->StreamCreate(stream, 0);
if (!trx)
{
cerr << "Failed to create stream" << endl;
DeviceRegistry::freeDevice(device);
return EXIT_FAILURE;
}

std::vector<int> sampleOffsets;
OpStatus ret = MeasureChannelDelays(trx.get(), trx.get(), chirp, channelCount, sampleRate, 0, sampleOffsets);
Expand Down
4 changes: 2 additions & 2 deletions embedded/lms7002m/calibrations.c
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,8 @@ static void lms7002m_set_defaults_sx(lms7002m_context* self)
for (uint8_t i = 0; i < sizeof(SXAddr) / sizeof(uint16_t); ++i)
lms7002m_spi_write(self, SXAddr[i], SXdefVals[i]);

//keep 0x0120[7:0]ICT_VCO bias value intact
lms7002m_spi_modify_csr(self, LMS7002M_VDIV_VCO, 0xB9FF);
//keep 0x0120[7:0]ICT_VCO bias value intact, restore only VDIV_VCO (0x0120[15:8] default 0xB9)
lms7002m_spi_modify_csr(self, LMS7002M_VDIV_VCO, 0xB9);
}

static bool lms7002m_is_pll_tuned(lms7002m_context* self, const bool isTx)
Expand Down
12 changes: 9 additions & 3 deletions embedded/lms7002m/lms7002m.c
Original file line number Diff line number Diff line change
Expand Up @@ -357,9 +357,12 @@ lime_Result lms7002m_set_frequency_cgen(lms7002m_context* self, uint32_t freq_Hz
}

//VCO frequency selection according to F_CLKH
const uint16_t iHdiv_high = (cgen_vco_max / freq_Hz / 2) - 1;
const uint16_t iHdiv_low = (cgen_vco_min / freq_Hz / 2);
const uint16_t div_outch_cgen = ((iHdiv_low + iHdiv_high) / 2) & 0xFF;
const uint32_t iHdiv_high = (cgen_vco_max / freq_Hz / 2) - 1;
const uint32_t iHdiv_low = (cgen_vco_min / freq_Hz / 2);
// clamp to the divider field's maximum instead of wrapping, low frequencies
// otherwise get rejected even though a divider of 256 can deliver them
const uint32_t iHdiv_mid = (iHdiv_low + iHdiv_high) / 2;
const uint16_t div_outch_cgen = iHdiv_mid > 255 ? 255 : iHdiv_mid;
uint64_t vco = 2 * (div_outch_cgen + 1) * freq_Hz;
if (vco <= cgen_vco_min || vco >= cgen_vco_max)
{
Expand Down Expand Up @@ -2010,6 +2013,9 @@ static uint16_t lms7002m_get_rssi_delay(lms7002m_context* self)
const uint64_t ref_clk = lms7002m_get_reference_clock(self);
const uint64_t ref_clk_tsp = lms7002m_get_reference_clock_tsp(self, false);
uint64_t delay = (uint64_t)sampleCount * decimation * ref_clk / ref_clk_tsp / 6;
// saturate instead of truncating: high AGC averaging produces delays above 16 bits
if (delay > 0xFFFF)
delay = 0xFFFF;
return (0xFFFF) - (uint16_t)(delay);
}

Expand Down
4 changes: 4 additions & 0 deletions embedded/lms7002m/lms7002m_context.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ typedef struct lms7002m_context {
lms7002m_hooks hooks;

uint32_t reference_clock_hz; ///< Common reference clock for CGEN, SXR, SXT

// calibration save/restore scratch, per chip so contexts don't clobber each other
uint16_t chip_state_0x0020;
uint16_t chip_state_data[359]; // UPDATE IF THE SAVED RANGES CHANGE
} lms7002m_context;

#ifdef __cplusplus
Expand Down
18 changes: 7 additions & 11 deletions embedded/lms7002m/save_state.c
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
#include "lms7002m_context.h"
#include "privates.h"
#include "spi.h"

struct lms7002m_context;

static const uint16_t chipStateAddr[][2] = {
{ 0x0021, 0x002F }, // LimeLight
{ 0x0081, 0x0082 }, // EN_DIR Configuration + AFE
Expand All @@ -23,24 +22,21 @@ static const uint16_t chipStateAddr[][2] = {
{ 0x0500, 0x05A7 }, // GFIR3
{ 0x05C0, 0x05C0 }, // DC Calibration Configuration
};
static uint16_t x0020state;
static uint16_t chipStateData[359]; // UPDATE IF SOMETHING CHANGES

void lms7002m_save_chip_state(struct lms7002m_context* self, bool wr)
{
uint16_t dest = 0;
const uint16_t ch = lms7002m_spi_read(self, 0x0020);
if (!wr)
x0020state = ch;
self->chip_state_0x0020 = ch;

for (uint8_t i = 0; i < sizeof(chipStateAddr) / sizeof(chipStateAddr[0]); ++i)
{
for (uint16_t addr = chipStateAddr[i][0]; addr <= chipStateAddr[i][1]; ++addr)
{
if (wr)
lms7002m_spi_write(self, addr, chipStateData[dest]);
lms7002m_spi_write(self, addr, self->chip_state_data[dest]);
else
chipStateData[dest] = lms7002m_spi_read(self, addr);
self->chip_state_data[dest] = lms7002m_spi_read(self, addr);
++dest;
}
}
Expand All @@ -52,9 +48,9 @@ void lms7002m_save_chip_state(struct lms7002m_context* self, bool wr)
for (uint16_t addr = 0x011C; addr <= 0x0123; ++addr)
{
if (wr)
lms7002m_spi_write(self, addr, chipStateData[dest]);
lms7002m_spi_write(self, addr, self->chip_state_data[dest]);
else
chipStateData[dest] = lms7002m_spi_read(self, addr);
self->chip_state_data[dest] = lms7002m_spi_read(self, addr);
++dest;
}
}
Expand All @@ -70,7 +66,7 @@ void lms7002m_save_chip_state(struct lms7002m_context* self, bool wr)
reg = lms7002m_spi_read(self, 0x0020);
lms7002m_spi_write(self, 0x0020, reg & ~0xAA00);
lms7002m_spi_write(self, 0x0020, reg);
lms7002m_spi_write(self, 0x0020, x0020state);
lms7002m_spi_write(self, 0x0020, self->chip_state_0x0020);
}
else
{
Expand Down
4 changes: 3 additions & 1 deletion embedded/lms7002m/spi_batch.c
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,9 @@ int lms7002m_spi_batch_flush(struct spi_batch_t* batch, struct lms7002m_context*
{
mosi[i] = (1 << 31) | (batch->addr[i] << 16);
if (batch->mask[i] != 0xFFFF)
mosi[i] |= (miso[miso_i++] & (~batch->mask[i])) | batch->value[i];
// the transact hook is not required to zero the upper miso half; keep it
// out of the address/write bits (~mask promotes to int with bits 16..31 set)
mosi[i] |= (miso[miso_i++] & 0xFFFFu & (uint32_t)(uint16_t)(~batch->mask[i])) | batch->value[i];
else
mosi[i] |= batch->value[i];
}
Expand Down
18 changes: 17 additions & 1 deletion src/API/LimePlugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,13 @@ int LimePlugin_Stop(LimePluginContext* context)
port.stream->Stop();
port.stream.reset();
}
// release the calibration stream too, otherwise it outlives the devices it
// references and LimePlugin_Destroy frees them from under it
if (port.calibrationStream)
{
port.calibrationStream->Stop();
port.calibrationStream.reset();
}
}
return 0;
}
Expand Down Expand Up @@ -585,6 +592,14 @@ static void GatherConfigSettings(ConfigSettings* param, LimeSettingsProvider* se
GetSetting(settings, &param->iniFilename, "%s_ini", prefix);
GetSetting(settings, &param->lpfBandwidthScale, "%s_lpf_bandwidth_scale", prefix);
GetSetting(settings, &param->maxChannelsToUse, "%s_max_channels_to_use", prefix);
if (param->maxChannelsToUse < 0 || param->maxChannelsToUse > lime::SDRConfig::MAX_CHANNEL_COUNT)
{
Log(LogLevel::Error,
"max_channels_to_use (%i) out of range, clamping to %i",
param->maxChannelsToUse,
lime::SDRConfig::MAX_CHANNEL_COUNT);
param->maxChannelsToUse = std::clamp<int>(param->maxChannelsToUse, 0, lime::SDRConfig::MAX_CHANNEL_COUNT);
}
GetSetting(settings, &param->double_freq_conversion_to_lower_side, "%s_double_freq_conversion_to_lower_side", prefix);
std::string linkFormatStr;
if (GetSetting(settings, &linkFormatStr, "%s_linkFormat", prefix))
Expand Down Expand Up @@ -781,7 +796,8 @@ int LimePlugin_Init(LimePluginContext* context, lime::SDRDevice::LogCallbackType
if (LoadDevicesConfigurationFile(context) != OpStatus::Success)
return -1;

TransferSettingsToDevicesConfig(context->rfdev, configProvider);
if (TransferSettingsToDevicesConfig(context->rfdev, configProvider) != OpStatus::Success)
return -1;
} catch (std::logic_error& e)
{
fprintf(stderr, "Logic error: %s", e.what());
Expand Down
5 changes: 3 additions & 2 deletions src/DSP/CFR/CrestFactorReduction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ void CrestFactorReduction::Configure(const CrestFactorReduction::Config& state)
WriteRegister(TX_HB_DEL, useOversample);
WriteRegister(SLEEP_CFR, cfr.sleep);
WriteRegister(BYPASS_CFR, cfr.bypass);
WriteRegister(ODD_CFR, fir.coefficientsCount % 2);
// ODD_CFR is programmed from the CFR order by UpdateHannCoeff below
WriteRegister(BYPASSGAIN_CFR, cfr.bypassGain);

assert(fir.coefficientsCount <= 32);
Expand Down Expand Up @@ -274,7 +274,8 @@ void CrestFactorReduction::SetFIRCoefficients(const int16_t* coefficients, uint1
m_Comms->Transact(mosi.data(), nullptr, mosi.size());
mosi.clear();

WriteRegister(ODD_FIR, Filt_N % 2);
// parity of the actual tap count, not of the fixed hardware register width
WriteRegister(ODD_FIR, count % 2);
WriteRegister(SLEEP_FIR, 0);
}

Expand Down
10 changes: 8 additions & 2 deletions src/DSP/FFT/FFT.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,10 @@ void FFT::SetWindowFunction(WindowFunctionType windowFunction)
if (currentWindowType != windowFunction)
{
currentWindowType = windowFunction;
GenerateWindowCoefficients(windowFunction, mFFTSize, mWindowCoeffs);
std::vector<float> coeffs;
GenerateWindowCoefficients(windowFunction, mFFTSize, coeffs);
std::unique_lock lk{ windowMutex };
mWindowCoeffs.swap(coeffs);
}
}

Expand Down Expand Up @@ -196,7 +199,10 @@ void FFT::ProcessLoop()
}

// auto t1 = std::chrono::high_resolution_clock::now();
FFT_Calculate(fftdata, samples, fftBins, mWindowCoeffs);
{
std::unique_lock lk{ windowMutex };
FFT_Calculate(fftdata, samples, fftBins, mWindowCoeffs);
}
++resultsDone;

for (std::size_t ch = 0; ch < fftBins.size(); ++ch)
Expand Down
3 changes: 2 additions & 1 deletion src/DSP/FFT/FFT.h
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ class LIME_API FFT

WindowFunctionType currentWindowType;
std::vector<float> mWindowCoeffs;
std::mutex windowMutex; ///< guards mWindowCoeffs between the caller and the worker thread

std::atomic<bool> doWork{};
std::condition_variable inputAvailable;
Expand All @@ -82,7 +83,7 @@ class LIME_API FFT
CallbackType resultsCallback{};
void* mUserData{};

std::size_t avgCount = 100;
std::atomic<std::size_t> avgCount{ 100 };
std::size_t mFFTSize;
};

Expand Down
Loading
Loading