Best Way to Mute RF Output When Not Transmitting

Hi everyone,

I’m currently working with a LimeSDR Mini 2.4 board, using it as a lab-grade transmitter to evaluate the performance and reliability of various sub-GHz receivers. The idea is to transmit radio packets with different RF parameters (e.g., output power, baseband signal characteristics, modulation depth, bit timing, etc.) and observe how many packets are successfully received -allowing me to quantify receiver performance across varying conditions.

So far, I’ve been able to generate and transmit these packets using python SoapySDR, which gives me the automation I need for more systematic testing (code included). I still need to fine-tune the TXTSP block, especially to apply the necessary SINC filtering when working with unfiltered baseband waveforms, but overall, the LimeSDR seems to offer all the flexibility and features I need for this task.

However, I’ve run into a challenge.

Since I want to test at various power levels, I need to adjust the TX gain dynamically. The problem is that increasing the gain also increases carrier (local oscillator) leakage, which becomes significant at high gain settings. For instance, at a gain of 30, I measure a leakage signal of around –50 dBm, even when no data is being transmitted. This is problematic for high-sensitivity receivers, as they may falsely detect packets or be desensitized by the leakage.

During active transmission, the issue is minimal due to the high signal-to-noise ratio. ASK modulation still achieves around 35 dB modulation depth, which is sufficient. But when no signal is being transmitted, I’d ideally like the RF output to drop below the sensitivity threshold of the receiver (-95 dBm), effectively implementing a “mute” function" between packets.

What I’ve Tried

My initial approach was to set the TX gain to zero between transmissions. Functionally, this does the job, but the delay between the host and the SDR is inconsistent and often exceeds 700 µs, making it unsuitable for precise timing requirements (see attached image).

I suspect this latency is due to the control path from host to device, and the internal steps involved in changing the gain setting.

My Question

Would it be possible to implement a more responsive mute switch? For example, could the FPGA monitor the TX baseband stream for all-zero samples and, when detected, either:

  1. Automatically reduce gain to 0, or
  2. Disable the RF output via some internal switch or control register?

Alternatively, are there other approaches I could consider, like using the internal RF switches, loopback paths, or external absorptive RF switches to achieve microsecond muting?

I’d really appreciate any ideas, tips, or experiences from the community on this. Has anyone tried something similar?

Thanks in advance!

Code I’ve used:

# tx.py
import numpy as np, time
import SoapySDR
from waveform import bits_to_wave, packet_to_bits

SDR_ARGS = "driver=lime"        # auto-detect first LimeSDR

FREQ     = 868.3e6              # Hz
RATE     = 1_000_000            # 1 MSps
BWIDTH   = 1_500_000            # Hz
GAIN_TX  = 30                    # dB; start moderate
MOD_DEPTH= 1.0                  # ASK high amplitude

def main():
    dev = SoapySDR.Device(SDR_ARGS)

    dev.setGain     (SoapySDR.SOAPY_SDR_TX, 0, 0)
    dev.setSampleRate(SoapySDR.SOAPY_SDR_TX, 0, RATE)
    dev.setFrequency (SoapySDR.SOAPY_SDR_TX, 0, FREQ)
    dev.setBandwidth(SoapySDR.SOAPY_SDR_TX, 0, BWIDTH)
    
    # Random radio packet 
    pkt = bytes([0xaa, 0x9a, 0x25, 0xee, 0xde, 0xed, 0xd9, 0x9d, 0x99, 0x55, 0x51, 0xe1, 0x1e, 0x11, 0x11, 0x11, 0x1d, 0x52, 0xc0])
    print(pkt.hex(' '))
    pkt = bytes([~b & 0xFF for b in pkt])
    print(pkt.hex(' '))

    # Convert the series of bits to np complex array containing the IQ samples. 
    env   = bits_to_wave(packet_to_bits(pkt), sample_rate=RATE, rise_fall=0)
    # ASK "modulation"
    iq    = (env * MOD_DEPTH).astype(np.complex64)   # pure real, imag = 0
    # Lime prefers multiples of 1024 samples
    pad = (-len(iq)) % 1024
    iq  = np.concatenate([iq, np.zeros(pad, np.complex64)])
    stream = dev.setupStream(SoapySDR.SOAPY_SDR_TX, SoapySDR.SOAPY_SDR_CF32)
    dev.activateStream(stream)

    for i in range(50):
      dev.setGain(SoapySDR.SOAPY_SDR_TX, 0, GAIN_TX)
      sr = dev.writeStream(stream, [iq], len(iq))
      print("TX writeStream result:", sr)
      dev.setGain(SoapySDR.SOAPY_SDR_TX, 0, 0)
      time.sleep(1)
    
    dev.deactivateStream(stream)
    dev.closeStream(stream)

if __name__ == "__main__":
    main()

Correct, LimeSDR Mini is controlled through USB interface, USB cannot guarantee precise timing, as the bus is shared with other devices.

I’m not the FPGA gateware developer, but I believe it would be possible to implement a similar solution to what LimeSDR XTRX gateware is doing for TDD operation. Software is sending Tx packets with timestamps when they shoud be transmitted and during the transmission, FPGA is toggling on board antenna RF switches.
LimeSDR Mini v2.4 also has on board antenna RF switches, so maybe that could work.

A few Tips for developers of Software Defined Radios -

1). Reduce the noise by turning off the signal at a zero crossing.
2). Always insure that the signal amplitude is less than ± 1.0, some
of the SDRs generate bad signals at only few percent above that.
3). Reserve 1 CPU to transmit the data and 1 CPU to receive the data
- as you add features strange things happen as you start running out
- of cpu power.
4). I find that when I transmit in (1/20) second blocks that the output is
- usually (3/20) of second behind the input.

Thanks for the reply, @ricardas

Yes, the USB-induced overhead was exactly what pushed me toward modifying (or rather, hacking) the gateware. I’ve successfully cloned and built the LimeSDR_GW project, and I’m currently waiting for a JTAG programmer so I can flash the bitstream to the board.

In the meantime, I’ve been exploring different approaches to achieve effective RF output muting. Detecting zero samples shouldn’t be too difficult. I can monitor the DIQ1 data bus and trigger an action when all values are zero. More info about the data bus register can be found here.

The big question is: what action should I take once zero samples are detected to reliably mute the RF output and suppress the strong carrier leakage, especially at high gain settings?

Here are the options I’m considering:

  1. Reduce TX gain to 0

  2. Use onboard RF switches

    In theory, I could route the RF output to an onboard shunt or isolated path using internal switches. I’ve tried toggling these manually with limited success.

  3. Toggle the TXEN pin

    This might serve as a “hard” TX power-off. I’m curious if this disables just the digital chain or the entire RF path, and whether there are any delays or artifacts when toggling it rapidly.

  4. Something other

So, given that I can modify the FPGA logic, what’s the most effective and low-latency way to mute the RF output?

I’d really appreciate any insights into how the LMS7002M reacts to these control methods and which registers or control lines might offer the cleanest way to gate or shut down transmission between packets, ideally without introducing new leakage paths or delay.

Thanks again!

You don’t need JTAG to flash the bitstream, it can be flashed using software either limeGUI or limeFLASH. JTAG is required only for recovery from flashed gateware failure.

I guess you could use LMS7002M register 0x0100[0]EN_G_TRF bit to power down the Tx RF analogue part.

  • Reduce TX gain to 0

As @ricardas mentioned and you already noticed any control which is made trough USB will not give you deterministic timing. If you want to have at least some determinism you can access LMS7002 SPI bus from firmware and set gain registers to your desired value. You can use lms_spi_write() lms_spi_read() functions, check this for example:

In this case you will also have to implement some logic to bring TX status signal to firmware to monitor it and then write LMS registers.

  • Use onboard RF switchesIn theory, I could route the RF output to an onboard shunt or isolated path using internal switches. I’ve tried toggling these manually with limited success.

RF switches are controlled here:

Check if you set proper registers and if software does not overwrite those when board is configured.

RF switch control is described here:

  • Toggle the TXEN pin This might serve as a “hard” TX power-off. I’m curious if this disables just the digital chain or the entire RF path, and whether there are any delays or artifacts when toggling it rapidly.

Cannot comment on this, I am not very familiar with analog parts of LMS7002.

  • Something other

I think usually in TDD application we are controlling onboard RF switches and if external PA is used it is controlled trough GPIO with tx_ant_en signal.

Thanks for the info @ricardas

I tried toggling the 0x0100[0] EN_G_TRF bit via the LimeSuite GUI, and it definitely cuts the RF output down to a minimum, exactly the kind of muting I’m aiming for. This seems like a promising direction.

Now, the key questions are:

  • How long does it take for the effect of this register change to propagate to the RF path?
  • How quickly and reliably can I toggle this bit via SPI in real-time?
  • Is there any hardware pin or fast control signal (e.g., via FPGA logic) that can achieve the same effect as EN_G_TRF, but without the overhead of going through SPI transactions?

Ideally, I’d like to implement this muting mechanism in the FPGA directly—something like a logic signal that gates the RF output as efficiently as EN_G_TRF does, but triggered purely from internal logic (e.g., based on DIQ1 activity) without SPI delays.

Has anyone explored this path before or knows if EN_G_TRF is internally mapped to any pin or fast control mechanism accessible from FPGA?

Thanks!

Regardless of how I plan to mute the RF output, whether via external RF switches or by internally controlling the power amplifier drivers, I need a signal that precisely follows the RF emission (in the sub-microsecond range). Right now, I’m struggling to find such a signal.

My initial approach was sample detection, but this failed because the sample timing doesn’t perfectly align with the actual RF emission. It seems the samples are transferred to the LMS chip earlier and faster, so I can’t reliably monitor their value to trigger an RF mute. I’ve shifted focus to finding a signal that closely follows the RF emission.

As suggested, I looked at the tx_txant_en signal, which is routed to GPIO0, but I didn’t observe any output. From the code, it appears that it’s not connected to anything.

I also tested rx_en and tx_en.

  • rx_en goes low for about 2.6 seconds before the transmission but then remains high.
  • tx_en stays low the entire time, even while transmitting.

LMS7002Top.source.valid seems the most promising, as it does roughly follow the RF emission. However, there’s a timing mismatch: the RF emission lasts ~1.2 ms, but this signal remains high for ~2 ms. I haven’t aligned them fully in the time domain yet, but with such a large difference, I doubt it will work as a reliable RF mute trigger.

I’ve also tested:

  • LMS7002Top.source.ready
  • LMS7002Top.lms1.fields.txen
  • FPGACfg.tx_en
  • FPGACfg.tx_ptrn_en
  • FPGACfg.tcxo_en

None of these showed activity during transmission.

Could anyone help me identify a signal that accurately follows the RF emission? Any guidance or tips would be greatly appreciated!

Thanks in advance!

Yes, you’re right — the tx_ant_en signal is currently left unconnected in the new LimeSDR_GW. However, this functionality is implemented in the original LimeSDR Mini v2 gateware:

There is an inherent delay between digital samples and the actual RF signal due to processing through the digital and analog stages of the chip. As such, some latency is expected. The gateware includes an option to align the tx_ant_en signal with the actual RF transmission to compensate for this.

I ran some tests on this a while ago and documented the findings here:

You can use the tx_ant_en signal to control an external PA to mute RF output. Keep in mind that tx_ant_en indicates all TX samples — not just those with non-zero values.

You can reference the original gateware repository and implement a similar mechanism in LimeSDR_GW. Contributions to the LimeSDR_GW repo are very welcome! Feel free to reach out if you need assistance — I’d be happy to help :slightly_smiling_face:.

Hey @VytautasB,
I just wanted to let you know that I’ve tried the TX indication using the tx_ant_en signal and with some external RF switches I achieved the desired effect. Thanks a lot for the tip!

So far, this approach is good enough but I might need to expand it with the initial zero-mute idea. For now, I’ll leave that for some other time.

Best regards!

1 Like