mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 16:47:31 -05:00
#1687 Adds support for RTL2832 with embedded FC0013 tuner.
This commit is contained in:
@@ -55,6 +55,9 @@ import io.github.dsheirer.source.tuner.rtl.RTL2832UnknownTunerEditor;
|
||||
import io.github.dsheirer.source.tuner.rtl.e4k.E4KEmbeddedTuner;
|
||||
import io.github.dsheirer.source.tuner.rtl.e4k.E4KTunerConfiguration;
|
||||
import io.github.dsheirer.source.tuner.rtl.e4k.E4KTunerEditor;
|
||||
import io.github.dsheirer.source.tuner.rtl.fc0013.FC0013EmbeddedTuner;
|
||||
import io.github.dsheirer.source.tuner.rtl.fc0013.FC0013TunerConfiguration;
|
||||
import io.github.dsheirer.source.tuner.rtl.fc0013.FC0013TunerEditor;
|
||||
import io.github.dsheirer.source.tuner.rtl.r8x.R8xTunerEditor;
|
||||
import io.github.dsheirer.source.tuner.rtl.r8x.r820t.R820TEmbeddedTuner;
|
||||
import io.github.dsheirer.source.tuner.rtl.r8x.r820t.R820TTunerConfiguration;
|
||||
@@ -368,6 +371,7 @@ public class TunerFactory
|
||||
return switch(tunerType)
|
||||
{
|
||||
case ELONICS_E4000 -> new E4KEmbeddedTuner(adapter);
|
||||
case FITIPOWER_FC0013 -> new FC0013EmbeddedTuner(adapter);
|
||||
case RAFAELMICRO_R820T -> new R820TEmbeddedTuner(adapter);
|
||||
case RAFAELMICRO_R828D -> new R828DEmbeddedTuner(adapter);
|
||||
default -> throw new SourceException("Unsupported/Unrecognized Tuner Type: " + tunerType);
|
||||
@@ -387,6 +391,8 @@ public class TunerFactory
|
||||
return new AirspyTunerConfiguration(uniqueID);
|
||||
case ELONICS_E4000:
|
||||
return new E4KTunerConfiguration(uniqueID);
|
||||
case FITIPOWER_FC0013:
|
||||
return new FC0013TunerConfiguration(uniqueID);
|
||||
case FUNCUBE_DONGLE_PRO:
|
||||
return new FCD1TunerConfiguration(uniqueID);
|
||||
case FUNCUBE_DONGLE_PRO_PLUS:
|
||||
@@ -479,6 +485,8 @@ public class TunerFactory
|
||||
{
|
||||
case ELONICS_E4000:
|
||||
return new E4KTunerEditor(userPreferences, tunerManager, discoveredTuner);
|
||||
case FITIPOWER_FC0013:
|
||||
return new FC0013TunerEditor(userPreferences, tunerManager, discoveredTuner);
|
||||
case RAFAELMICRO_R820T:
|
||||
case RAFAELMICRO_R828D:
|
||||
return new R8xTunerEditor(userPreferences, tunerManager, discoveredTuner);
|
||||
|
||||
@@ -31,6 +31,7 @@ import io.github.dsheirer.source.tuner.fcd.proplusV2.FCD2TunerConfiguration;
|
||||
import io.github.dsheirer.source.tuner.hackrf.HackRFTunerConfiguration;
|
||||
import io.github.dsheirer.source.tuner.recording.RecordingTunerConfiguration;
|
||||
import io.github.dsheirer.source.tuner.rtl.e4k.E4KTunerConfiguration;
|
||||
import io.github.dsheirer.source.tuner.rtl.fc0013.FC0013TunerConfiguration;
|
||||
import io.github.dsheirer.source.tuner.rtl.r8x.r820t.R820TTunerConfiguration;
|
||||
import io.github.dsheirer.source.tuner.rtl.r8x.r828d.R828DTunerConfiguration;
|
||||
import io.github.dsheirer.source.tuner.sdrplay.RspTunerConfiguration;
|
||||
@@ -43,6 +44,7 @@ import io.github.dsheirer.source.tuner.sdrplay.RspTunerConfiguration;
|
||||
@JsonSubTypes.Type(value = AirspyTunerConfiguration.class, name = "airspyTunerConfiguration"),
|
||||
@JsonSubTypes.Type(value = AirspyHfTunerConfiguration.class, name = "airspyHfTunerConfiguration"),
|
||||
@JsonSubTypes.Type(value = E4KTunerConfiguration.class, name = "e4KTunerConfiguration"),
|
||||
@JsonSubTypes.Type(value = FC0013TunerConfiguration.class, name = "fc0013TunerConfiguration"),
|
||||
@JsonSubTypes.Type(value = FCD1TunerConfiguration.class, name = "fcd1TunerConfiguration"),
|
||||
@JsonSubTypes.Type(value = FCD2TunerConfiguration.class, name = "fcd2TunerConfiguration"),
|
||||
@JsonSubTypes.Type(value = HackRFTunerConfiguration.class, name = "hackRFTunerConfiguration"),
|
||||
|
||||
@@ -24,6 +24,7 @@ import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
|
||||
import io.github.dsheirer.source.tuner.configuration.TunerConfiguration;
|
||||
import io.github.dsheirer.source.tuner.rtl.e4k.E4KTunerConfiguration;
|
||||
import io.github.dsheirer.source.tuner.rtl.fc0013.FC0013TunerConfiguration;
|
||||
import io.github.dsheirer.source.tuner.rtl.r8x.r820t.R820TTunerConfiguration;
|
||||
import io.github.dsheirer.source.tuner.rtl.r8x.r828d.R828DTunerConfiguration;
|
||||
|
||||
@@ -33,6 +34,7 @@ import io.github.dsheirer.source.tuner.rtl.r8x.r828d.R828DTunerConfiguration;
|
||||
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
|
||||
@JsonSubTypes({
|
||||
@JsonSubTypes.Type(value = E4KTunerConfiguration.class, name = "e4KTunerConfiguration"),
|
||||
@JsonSubTypes.Type(value = FC0013TunerConfiguration.class, name = "fc0013TunerConfiguration"),
|
||||
@JsonSubTypes.Type(value = R820TTunerConfiguration.class, name = "r820TTunerConfiguration"),
|
||||
@JsonSubTypes.Type(value = R828DTunerConfiguration.class, name = "r828DTunerConfiguration"),
|
||||
})
|
||||
|
||||
@@ -354,21 +354,28 @@ public class RTL2832TunerController extends USBTunerController
|
||||
*/
|
||||
public String getUniqueID()
|
||||
{
|
||||
if(mDescriptor != null && mDescriptor.hasSerial())
|
||||
if(mDescriptor != null)
|
||||
{
|
||||
if(hasEmbeddedTuner())
|
||||
if(mDescriptor.hasSerial())
|
||||
{
|
||||
return TunerClass.RTL2832 + "/" + getTunerType().getLabel() + " " + mDescriptor.getSerial();
|
||||
if(hasEmbeddedTuner())
|
||||
{
|
||||
return TunerClass.RTL2832 + "/" + getTunerType().getLabel() + " " + mDescriptor.getSerial();
|
||||
}
|
||||
else
|
||||
{
|
||||
return TunerClass.RTL2832 + " " + mDescriptor.getSerial();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return TunerClass.RTL2832 + " " + mDescriptor.getSerial();
|
||||
return "RTL-2832 USB Bus:" + mBus + " Port:" + mPortAddress;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
int serial = (0xFF & getDeviceDescriptor().iSerialNumber());
|
||||
return TunerClass.RTL2832.toString() + " " + serial;
|
||||
return TunerClass.RTL2832 + " " + serial;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -593,10 +600,9 @@ public class RTL2832TunerController extends USBTunerController
|
||||
*/
|
||||
private void enableI2CRepeater(boolean enabled) throws LibUsbException
|
||||
{
|
||||
Page page = Page.ONE;
|
||||
short address = 1;
|
||||
int value = (enabled ? 0x18 : 0x10);
|
||||
writeDemodRegister(page, address, value, 1);
|
||||
writeDemodRegister(Page.ONE, address, value, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,842 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.source.tuner.rtl.fc0013;
|
||||
|
||||
import io.github.dsheirer.source.SourceException;
|
||||
import io.github.dsheirer.source.tuner.TunerType;
|
||||
import io.github.dsheirer.source.tuner.configuration.TunerConfiguration;
|
||||
import io.github.dsheirer.source.tuner.rtl.EmbeddedTuner;
|
||||
import io.github.dsheirer.source.tuner.rtl.RTL2832TunerController;
|
||||
import java.text.DecimalFormat;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.usb4java.LibUsbException;
|
||||
|
||||
/**
|
||||
* Fitipower FC0013 Tuner
|
||||
* <p>
|
||||
* Ported from: https://github.com/osmocom/rtl-sdr/blob/master/src/tuner_fc0013.c
|
||||
*
|
||||
* Register: Original Contents / Shadow Contents - Comments
|
||||
* 0x00: .. / 00 - Dummy register for 0-index
|
||||
* 0x01: 05 / 09
|
||||
* 0x02: 10 / 16
|
||||
* 0x03: 00 / 00
|
||||
* 0x04: 00 / 00
|
||||
* 0x05: 0A / 17
|
||||
* 0x06: 0A / 02 - Low Pass Filter Bandwidth
|
||||
* 0x07: 00 / 2A - 0x2x setup for 28.8MHz crystal
|
||||
* 0x08: B0 / FF - AGC clock divide by 256, AGC gain 1/256, Loop bw 1/8
|
||||
* 0x09: 6E / 6E - Disable loop through, enable loop through: 0x6F
|
||||
* 0x0A: B8 / B8 - Disable LO Test Buffer
|
||||
* 0x0B: 82 / 82
|
||||
* 0x0C: 7C / FC - Depending on AGC Up-Down mode, may need 0xF8
|
||||
* 0x0D: 00 / 01 - AGC not forcing & LNA forcing, we may need 0x02
|
||||
* 0x0E: 00 / 00
|
||||
* 0x0F: 00 / 00
|
||||
* 0x10: 00 / 00
|
||||
* 0x11: 08 / 00
|
||||
* 0x12: 00 / 00
|
||||
* 0x13: 00 / 00
|
||||
* 0x14: C0 / 50 - DVB-t=High Gain, UHF=middle gain (0x48), or low gain (0x40)
|
||||
* 0x15: 01 / 01
|
||||
*/
|
||||
public class FC0013EmbeddedTuner extends EmbeddedTuner
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(FC0013EmbeddedTuner.class);
|
||||
private DecimalFormat FREQUENCY_FORMAT = new DecimalFormat("0.000000");
|
||||
private static final long MINIMUM_SUPPORTED_FREQUENCY = 13_500_000;
|
||||
private static final long MAXIMUM_SUPPORTED_FREQUENCY = 1_907_999_890l;
|
||||
private static final double USABLE_BANDWIDTH_PERCENT = 0.95;
|
||||
private static final int DC_SPIKE_AVOID_BUFFER = 15000;
|
||||
//Hardware I2C address
|
||||
private static final byte I2C_ADDRESS = (byte) 0xC6;
|
||||
//Hardware crystal oscillator frequency - used by the frequency divider
|
||||
private static final int XTAL_FREQUENCY = 28_800_000;
|
||||
private static final int XTAL_FREQUENCY_DIVIDED_BY_2 = XTAL_FREQUENCY / 2;
|
||||
//Initial register settings to apply to the tuner
|
||||
private static byte[] REGISTERS = {(byte) 0x00, (byte) 0x09, (byte) 0x16, (byte) 0x00, (byte) 0x00, (byte) 0x17,
|
||||
(byte) 0x02, (byte) 0x2A, (byte) 0xFF, (byte) 0x6E, (byte) 0xB8, (byte) 0x82, (byte) 0xFE, (byte) 0x01,
|
||||
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x50, (byte) 0x01};
|
||||
private long mTunedFrequency = MINIMUM_SUPPORTED_FREQUENCY;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param adapter for controlling the RTL2832
|
||||
*/
|
||||
public FC0013EmbeddedTuner(RTL2832TunerController.ControllerAdapter adapter)
|
||||
{
|
||||
super(adapter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TunerType getTunerType()
|
||||
{
|
||||
return TunerType.FITIPOWER_FC0013;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getMinimumFrequencySupported()
|
||||
{
|
||||
return MINIMUM_SUPPORTED_FREQUENCY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getMaximumFrequencySupported()
|
||||
{
|
||||
return MAXIMUM_SUPPORTED_FREQUENCY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDcSpikeHalfBandwidth()
|
||||
{
|
||||
return DC_SPIKE_AVOID_BUFFER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getUsableBandwidthPercent()
|
||||
{
|
||||
return USABLE_BANDWIDTH_PERCENT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the tuner configuration settings.
|
||||
* @param tunerConfig containing settings to apply
|
||||
* @throws SourceException if there is an error.
|
||||
*/
|
||||
@Override
|
||||
public void apply(TunerConfiguration tunerConfig) throws SourceException
|
||||
{
|
||||
if(tunerConfig instanceof FC0013TunerConfiguration config)
|
||||
{
|
||||
try
|
||||
{
|
||||
setGain(config.getAGC(), config.getLnaGain());
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
throw new SourceException("Error while applying tuner config", e);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new IllegalArgumentException("Unrecognized tuner config [" + tunerConfig.getClass() + "]");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the value to the FC0013 register and optionally controls the I2C repeater
|
||||
*
|
||||
* @param register to write
|
||||
* @param value to write
|
||||
* @param controlI2CRepeater true to turn on the I2C repeater before and after the write operation. If this is
|
||||
* set to false, you need to have previously enabled the I2C repeater before invoking this method call.
|
||||
*/
|
||||
private void write(Register register, byte value, boolean controlI2CRepeater)
|
||||
{
|
||||
getAdapter().writeI2CRegister(I2C_ADDRESS, register.byteValue(), value, controlI2CRepeater);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the value to the FC0013 register and optionally controls the I2C repeater
|
||||
*
|
||||
* @param register to write
|
||||
* @param value to write
|
||||
* @param mask to use when reading the full register byte that has ones in the bits that should be untouched and
|
||||
* zeros in the bits that should be cleared prior to applying (or'ing) the value into the register.
|
||||
* @param controlI2CRepeater true to turn on the I2C repeater before and after the write operation. If this is
|
||||
* set to false, you need to have previously enabled the I2C repeater before invoking this method call.
|
||||
*/
|
||||
public void writeMaskedRegister(Register register, byte value, byte mask, boolean controlI2CRepeater)
|
||||
{
|
||||
byte content = (byte) (readRegister(register, controlI2CRepeater) & 0xFF);
|
||||
content &= mask;
|
||||
content |= value;
|
||||
write(register, content, controlI2CRepeater);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the value to a field within an FC0013 register and optionally controls the I2C repeater
|
||||
*
|
||||
* @param field to write
|
||||
* @param value to write
|
||||
* @param controlI2CRepeater true to turn on the I2C repeater before and after the write operation. If this is
|
||||
* set to false, you need to have previously enabled the I2C repeater before invoking this method call.
|
||||
*/
|
||||
private void write(Field field, byte value, boolean controlI2CRepeater)
|
||||
{
|
||||
writeMaskedRegister(field.getRegister(), value, field.getMask(), controlI2CRepeater);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the FC0013 tuner register value.
|
||||
*
|
||||
* @param register to read
|
||||
* @param controlI2CRepeater true to turn on the I2C repeater before and after the write operation. If this is
|
||||
* set to false, you need to have previously enabled the I2C repeater before invoking this method call.
|
||||
* @return register value.
|
||||
*/
|
||||
private int readRegister(Register register, boolean controlI2CRepeater)
|
||||
{
|
||||
return getAdapter().readI2CRegister(I2C_ADDRESS, register.byteValue(), controlI2CRepeater);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSamplingMode(RTL2832TunerController.SampleMode mode) throws LibUsbException
|
||||
{
|
||||
//No-op
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSampleRateFilters(int sampleRate) throws SourceException
|
||||
{
|
||||
//No-op
|
||||
}
|
||||
|
||||
/**
|
||||
* Currently tuned frequency.
|
||||
* @return frequency
|
||||
* @throws SourceException never
|
||||
*/
|
||||
public long getTunedFrequency() throws SourceException
|
||||
{
|
||||
return mTunedFrequency;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the frequency value to the tuner. Note: this is managed by the frequency controller along with PPM adjust.
|
||||
* @param frequency in Hertz to apply to the tuner
|
||||
* @throws SourceException if there is an error.
|
||||
*/
|
||||
@Override
|
||||
public synchronized void setTunedFrequency(long frequency) throws SourceException
|
||||
{
|
||||
getAdapter().getLock().lock();
|
||||
|
||||
try
|
||||
{
|
||||
//Capture current I2C repeater state and enable if necessary.
|
||||
boolean i2CEnabledState = getAdapter().isI2CRepeaterEnabled();
|
||||
if(!i2CEnabledState)
|
||||
{
|
||||
getAdapter().enableI2CRepeater();
|
||||
}
|
||||
|
||||
setVhfTracking(frequency);
|
||||
setFilters(frequency);
|
||||
|
||||
FrequencyDivider divider = FrequencyDivider.fromFrequency(frequency);
|
||||
boolean vcoSelect = ((double)frequency / (double)divider.getIntegralFrequencyMultiplier()) >= 212.5;
|
||||
int integral = (int)(frequency / divider.getIntegralFrequencyMultiplier());
|
||||
int pm = integral / 8; //pm is units of 8x integrals
|
||||
pm = Math.max(pm, 11);
|
||||
pm = Math.min(pm, 31);
|
||||
|
||||
int am = integral - (pm * 8); //am is units of 1x integrals
|
||||
if(am < 2)
|
||||
{
|
||||
am += 8;
|
||||
pm--;
|
||||
}
|
||||
am = Math.min(am, 15);
|
||||
integral = pm * 8 + am;
|
||||
|
||||
int residual = (int)(frequency - (integral * divider.getIntegralFrequencyMultiplier()));
|
||||
|
||||
//fractional is units of 1/65536th of 2x integrals
|
||||
int fractional = (int)Math.round(residual / divider.getFractionalFrequencyMultiplier());
|
||||
|
||||
if(pm < 11 || pm > 31 || am < 2 || am > 15 || fractional < 0 || fractional > 65535)
|
||||
{
|
||||
String message = "FC0013 - no valid PLL combination for frequency [" + frequency + "] using divider [" +
|
||||
divider + "] pm [" + pm + "] am [" + am + "] fractional [" + fractional + "]";
|
||||
mLog.error(message);
|
||||
throw new SourceException(message);
|
||||
}
|
||||
|
||||
setFrequencyValues(divider, pm, am, fractional, vcoSelect);
|
||||
|
||||
//Restore the I2C repeater to previous state
|
||||
if(!i2CEnabledState)
|
||||
{
|
||||
getAdapter().disableI2CRepeater();
|
||||
}
|
||||
}
|
||||
catch(LibUsbException e)
|
||||
{
|
||||
mLog.error("FC0013 tuner error while setting tuned frequency [" + frequency + "]", e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
getAdapter().getLock().unlock();
|
||||
}
|
||||
|
||||
mTunedFrequency = frequency;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply initial settings to the tuner
|
||||
*/
|
||||
protected void initTuner()
|
||||
{
|
||||
getAdapter().enableI2CRepeater();
|
||||
boolean i2CRepeaterControl = false;
|
||||
|
||||
//Write initial register settings to the tuner
|
||||
for(int x = 1; x < REGISTERS.length; x++)
|
||||
{
|
||||
write(Register.values()[x], REGISTERS[x], i2CRepeaterControl);
|
||||
}
|
||||
|
||||
write(Field.R0D_AGC, (byte)0x08, false);
|
||||
write(Register.R13, (byte)0x0A, false);
|
||||
getAdapter().disableI2CRepeater();
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the frequency settings and calibrates the PLL. Note: I2C repeater must be enabled before invoking.
|
||||
* @param divider to use
|
||||
* @param pm units of 8x integrals
|
||||
* @param am units of 1x integrals
|
||||
* @param fractional value.
|
||||
* @param vcoSelect to enable (true) or disable (false)
|
||||
* @throws SourceException if the settings are invalid or the PLL can't be calibrated with the settings.
|
||||
* @return true if the frequency settings were applied successfully.
|
||||
*/
|
||||
private boolean setFrequencyValues(FrequencyDivider divider, int pm, int am, int fractional, boolean vcoSelect)
|
||||
throws SourceException
|
||||
{
|
||||
if(pm < 11 || pm > 31)
|
||||
{
|
||||
throw new IllegalArgumentException("PM value [" + pm + "] must be in range 11-31");
|
||||
}
|
||||
if(am < 2 || am > 15)
|
||||
{
|
||||
throw new IllegalArgumentException("AM value [" + am + "] must be in range 2-15");
|
||||
}
|
||||
if(fractional < 0 || fractional > 65535)
|
||||
{
|
||||
throw new IllegalArgumentException("Fractional value [" + fractional + "] must be in range 0-65,535");
|
||||
}
|
||||
double exactFrequency = divider.calculate(pm, am, fractional);
|
||||
long frequency = (long)exactFrequency;
|
||||
byte register5 = divider.getRegister5();
|
||||
register5 |= 0x07; //modified for Realtek demod
|
||||
|
||||
byte register6 = divider.getRegister6();
|
||||
if(vcoSelect)
|
||||
{
|
||||
register6 |= 0x08;
|
||||
}
|
||||
|
||||
//Write the integral units to the PLL
|
||||
write(Register.R01, (byte)(am & 0xFF), false);
|
||||
write(Register.R02, (byte)(pm & 0xFF), false);
|
||||
|
||||
//Write the fractional units to the PLL
|
||||
write(Register.R03, (byte)((fractional >> 8) & 0xFF), false);
|
||||
write(Register.R04, (byte)(fractional & 0xFF), false);
|
||||
|
||||
//Set the frequency divider, bandwidth, and enable the clock output
|
||||
write(Register.R05, register5, false);
|
||||
write(Register.R06, register6, false);
|
||||
|
||||
//Whatever register 11 does ... seems like it's a gain thing.
|
||||
int tmp = readRegister(Register.R11, false);
|
||||
if(divider == FrequencyDivider.D64)
|
||||
{
|
||||
write(Register.R11, (byte)(tmp | 0x04), false);
|
||||
}
|
||||
else
|
||||
{
|
||||
write(Register.R11, (byte)(tmp & 0xFB), false);
|
||||
}
|
||||
|
||||
//Perform VCO Calibration
|
||||
write(Register.R0E, (byte)0x80, false);
|
||||
write(Register.R0E, (byte)0x00, false);
|
||||
write(Register.R0E, (byte)0x00, false);
|
||||
int calibration = readRegister(Register.R0E, false) & 0x3F;
|
||||
|
||||
boolean recalibrateRequired = false;
|
||||
if(vcoSelect && calibration > 0x3C)
|
||||
{
|
||||
register6 &= ~0x08;
|
||||
recalibrateRequired = true;
|
||||
}
|
||||
else if(!vcoSelect && calibration < 0x02)
|
||||
{
|
||||
register6 |= 0x08;
|
||||
recalibrateRequired = true;
|
||||
}
|
||||
|
||||
if(recalibrateRequired)
|
||||
{
|
||||
write(Register.R06, register6, false);
|
||||
write(Register.R0E, (byte)0x80, false);
|
||||
write(Register.R0E, (byte)0x00, false);
|
||||
write(Register.R0E, (byte)0x00, false);
|
||||
calibration = readRegister(Register.R0E, false) & 0x3F;
|
||||
if((!vcoSelect & calibration < 0x02) || (vcoSelect & calibration > 0x3C))
|
||||
{
|
||||
String msg = "Unable to tune frequency [" + fractional + "] PLL calibration [" + Integer.toHexString(calibration).toUpperCase() + "] out of limits [02-3C]";
|
||||
mLog.error(msg);
|
||||
throw new SourceException(msg);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the tuner filters according to the tuned frequency. Note: I2C repeater must be enabled before invoking.
|
||||
* @param frequency of the tuner
|
||||
*/
|
||||
private void setFilters(long frequency)
|
||||
{
|
||||
byte vhf = (byte)0x00; //default to disabled
|
||||
byte uhf = (byte)0x00; //default to disabled
|
||||
|
||||
if(frequency < 300_000_000)
|
||||
{
|
||||
vhf = (byte)0x10; //enable
|
||||
}
|
||||
else if(frequency < 862_000_000)
|
||||
{
|
||||
uhf = (byte)0x40;
|
||||
}
|
||||
else
|
||||
{
|
||||
//The original source code seems to target a GPS filter, but it's using the same uhf filter setting.
|
||||
uhf = (byte)0x40;
|
||||
}
|
||||
|
||||
write(Field.R07_VHF_FILTER, vhf, false);
|
||||
write(Field.R14_UHF_FILTER, uhf, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets VHF tracking according to the tuned frequency. Note: I2C repeater must be enabled before invoking.
|
||||
* @param frequency for the tuner
|
||||
*/
|
||||
private void setVhfTracking(long frequency)
|
||||
{
|
||||
int tmp = readRegister(Register.R1D, false);
|
||||
|
||||
//Clear the middle 3x bits that we're going to set and leave the remaining bits untouched.
|
||||
tmp &= 0xE3;
|
||||
|
||||
if(frequency <= 177_500_000)
|
||||
{
|
||||
tmp |= 0x1C; //VHF Track 7
|
||||
}
|
||||
else if(frequency <= 184_500_000)
|
||||
{
|
||||
tmp |= 0x18; //VHF Track 6
|
||||
}
|
||||
else if(frequency <= 191_500_000)
|
||||
{
|
||||
tmp |= 0x14; //VHF Track 5
|
||||
}
|
||||
else if(frequency <= 198_500_000)
|
||||
{
|
||||
tmp |= 0x10; //VHF Track 4
|
||||
}
|
||||
else if(frequency <= 205_500_000)
|
||||
{
|
||||
tmp |= 0x0C; //VHF Track 3
|
||||
}
|
||||
else if(frequency <= 219_500_000)
|
||||
{
|
||||
tmp |= 0x08; //VHF Track 2
|
||||
}
|
||||
else if(frequency <= 300_000_000)
|
||||
{
|
||||
tmp |= 0x04; //VHF Track 1
|
||||
}
|
||||
else
|
||||
{
|
||||
tmp |= 0x1C; //UHF & GPS
|
||||
}
|
||||
|
||||
write(Register.R1D, (byte) tmp, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the gain for the tuner. Note: if agc=true, the manual gain setting is ignored.
|
||||
* @param agc automatic gain control.
|
||||
* @param manualGain setting.
|
||||
*/
|
||||
public void setGain(boolean agc, LNAGain manualGain)
|
||||
{
|
||||
getAdapter().getLock().lock();
|
||||
|
||||
try
|
||||
{
|
||||
boolean repeaterState = getAdapter().isI2CRepeaterEnabled();
|
||||
if(!repeaterState)
|
||||
{
|
||||
getAdapter().enableI2CRepeater();
|
||||
}
|
||||
|
||||
if(agc)
|
||||
{
|
||||
write(Field.R0D_AGC, (byte)0x00, false);
|
||||
write(Register.R13, (byte)0x0A, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
write(Field.R14_LNA_GAIN, manualGain.byteValue(), false);
|
||||
write(Field.R0D_AGC, (byte)0x08, false);
|
||||
write(Register.R13, (byte)0x0A, false);
|
||||
}
|
||||
|
||||
if(!repeaterState)
|
||||
{
|
||||
getAdapter().disableI2CRepeater();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
getAdapter().getLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enumeration of FC0013 registers. Provides convenience method for accessing the entry's ordinal position as a
|
||||
* byte value for reading/writing.
|
||||
*/
|
||||
private enum Register
|
||||
{
|
||||
R00, R01, R02, R03, R04, R05, R06, R07, R08, R09, R0A, R0B, R0C, R0D, R0E, R0F, R10, R11, R12, R13, R14, R15,
|
||||
R16, R17, R18, R19, R1A, R1B, R1C, R1D;
|
||||
|
||||
/**
|
||||
* Returns the ordinal position of the entry within the enumeration as a byte value.
|
||||
*/
|
||||
public byte byteValue()
|
||||
{
|
||||
return (byte) ordinal();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Masked tuner register fields enumeration
|
||||
*/
|
||||
private enum Field
|
||||
{
|
||||
R06_BANDWIDTH(Register.R06, 0xC0),
|
||||
R07_VHF_FILTER(Register.R07, 0xEF),
|
||||
R0D_AGC(Register.R0D, 0xF7),
|
||||
R14_LNA_GAIN(Register.R14, 0xE0),
|
||||
R14_UHF_FILTER(Register.R14, 0x1F);
|
||||
|
||||
private Register mRegister;
|
||||
private byte mMask;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param register for the field
|
||||
* @param mask for the field
|
||||
*/
|
||||
Field(Register register, int mask)
|
||||
{
|
||||
mRegister = register;
|
||||
mMask = (byte) mask;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register for the field.
|
||||
* @return register
|
||||
*/
|
||||
public Register getRegister()
|
||||
{
|
||||
return mRegister;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bit field mask for the register that masks out the target field bits prior to applying a value.
|
||||
* @return mask value.
|
||||
*/
|
||||
public byte getMask()
|
||||
{
|
||||
return mMask;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* LNA gain values enumeration
|
||||
*/
|
||||
public enum LNAGain
|
||||
{
|
||||
G00("0 LOW", 0x02),
|
||||
G01("1", 0x03),
|
||||
G02("2", 0x05),
|
||||
G03("3", 0x04),
|
||||
G04("4", 0x00),
|
||||
G05("5", 0x07),
|
||||
G06("6", 0x01),
|
||||
G07("7", 0x06),
|
||||
|
||||
G08("8", 0x0F),
|
||||
G09("9", 0x0E),
|
||||
G10("10", 0x0D),
|
||||
G11("11", 0x0C),
|
||||
G12("12", 0x0B),
|
||||
G13("13", 0x0A),
|
||||
G14("14", 0x09),
|
||||
G15("15", 0x08),
|
||||
|
||||
G16("16", 0x17),
|
||||
G17("17", 0x16),
|
||||
G18("18", 0x15),
|
||||
G19("19", 0x14),
|
||||
G20("20", 0x13),
|
||||
G21("21", 0x12),
|
||||
G22("22", 0x11),
|
||||
G23("23 HIGH", 0x10);
|
||||
|
||||
private String mLabel;
|
||||
private byte mValue;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param label to show to the user
|
||||
* @param value to apply to the register
|
||||
*/
|
||||
LNAGain(String label, int value)
|
||||
{
|
||||
mLabel = label;
|
||||
mValue = (byte) value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Byte value for the gain setting.
|
||||
*
|
||||
* @return byte value.
|
||||
*/
|
||||
public byte byteValue()
|
||||
{
|
||||
return mValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return mLabel;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SDM PLL Frequency Divider and Supported Frequency Ranges
|
||||
*
|
||||
* Note: each divider frequency range is overlapping with other divider frequency range(s) and also within each
|
||||
* frequency divider there are overlapping frequency range across each PM value. Each unit of PM is 8x the integral
|
||||
* and each unit of AM is 1x the integral. You can specify a range of 2-15 units of AM which causes overlap when
|
||||
* each unit of PM represents 8x units of AM.
|
||||
*
|
||||
* So, for a requested frequency, there are likely multiple combinations of Divider/PM/AM/Fractional that will
|
||||
* tune the frequency, each with varying degrees of correctness.
|
||||
*
|
||||
* For each divider range, minimum and maximum frequency is calculated as:
|
||||
* Min: PM = 11, AM = 2, FRACTIONAL = 0
|
||||
* Max: PM = 31, AM = 15, FRACTIONAL = 65,535, or the highest setting that can be calibrated by the VCO.
|
||||
*
|
||||
* integral = XTAL_FREQ / 2 / divider
|
||||
*
|
||||
* Where: (integral * ((PM * 8) + AM)) + (2 * integral / 65,536 * fractional)
|
||||
*
|
||||
* Minimum tunable frequency step size is (2 * integral / 65,536).
|
||||
*/
|
||||
public enum FrequencyDivider
|
||||
{
|
||||
D96(96, true, 0x82, 13_500_000, 39_749_997),
|
||||
D64(64, false, 0x02, 20_250_000, 59_624_996), //Special one, why?
|
||||
D48(48, true, 0x42, 27_000_000, 79_499_995),
|
||||
D32(32, false, 0x82, 40_500_000, 119_249_993),
|
||||
D24(24, true, 0x22, 54_000_000, 158_999_990),
|
||||
D16(16, false, 0x42, 81_000_000, 238_499_986),
|
||||
D12(12, true, 0x12, 108_000_000, 317_999_981),
|
||||
D08(8, false, 0x22, 162_000_000, 476_999_972),
|
||||
D06(6, true, 0x0A, 235_200_000, 635_999_963),
|
||||
D04(4, false, 0x12, 514_900_000, 953_999_945),
|
||||
D02(2, false, 0x0A, 648_000_000, 1_907_999_890);
|
||||
|
||||
private int mDivider;
|
||||
private boolean mIs3xMode;
|
||||
private int mRegister5;
|
||||
private long mMinimumFrequency;
|
||||
private long mMaximumFrequency;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param divider quantity
|
||||
* @param is3xMode indicates 3x divider (true) or 2x divider (false)
|
||||
* @param register5 setting
|
||||
* @param minimumFrequency supported by the divider
|
||||
* @param maximumFrequency supported by the divider
|
||||
*/
|
||||
FrequencyDivider(int divider, boolean is3xMode, int register5, long minimumFrequency, long maximumFrequency)
|
||||
{
|
||||
mDivider = divider;
|
||||
mIs3xMode = is3xMode;
|
||||
mRegister5 = register5;
|
||||
mMinimumFrequency = minimumFrequency;
|
||||
mMaximumFrequency = maximumFrequency;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register 5 frequency multiplier setting
|
||||
*/
|
||||
public byte getRegister5()
|
||||
{
|
||||
return (byte)(mRegister5 & 0xFF);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register 6 multiplier mode setting (2x or 3x)
|
||||
*/
|
||||
public byte getRegister6()
|
||||
{
|
||||
//0xAx OR Enable clock (0x20) OR bandwidth set to 6 MHz (0x80).
|
||||
return is3xMode() ? (byte)0xA0 : (byte)0xA2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the frequency for this frequency divider.
|
||||
* @param pm - 8x integral units
|
||||
* @param am - 1x integral units
|
||||
* @param fractional integral units
|
||||
* @param vcoSelect to indicate if the frequency requires VCO select (ie AM + 2)
|
||||
* @return frequency
|
||||
*/
|
||||
public double getFrequency(int pm, int am, int fractional, boolean vcoSelect)
|
||||
{
|
||||
if(vcoSelect)
|
||||
{
|
||||
return calculate(pm, am, fractional);
|
||||
}
|
||||
else
|
||||
{
|
||||
return calculate(pm, am - 2, fractional);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the frequency requires VCO select for this divider.
|
||||
* @param frequency to evaluate
|
||||
* @return true if vco select
|
||||
*/
|
||||
public boolean isVcoSelect(double frequency)
|
||||
{
|
||||
return (frequency * getDivider()) > 3_060_000_000l;
|
||||
}
|
||||
|
||||
private double calculate(int pm, int am, int fractional)
|
||||
{
|
||||
return (((8l * pm) + am) * getIntegralFrequencyMultiplier()) + (getFractionalFrequencyMultiplier() * fractional);
|
||||
}
|
||||
|
||||
/**
|
||||
* Integral unit of frequency multiplier supported by the PLL for the specified frequency divider.
|
||||
* @return integral frequency multiplier.
|
||||
*/
|
||||
public int getIntegralFrequencyMultiplier()
|
||||
{
|
||||
return XTAL_FREQUENCY_DIVIDED_BY_2 / mDivider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fractional (1/65536th) unit of frequency of 2x integrals that is supported by the 16-bit fractional PLL.
|
||||
* @return fractional frequency multiplier
|
||||
*/
|
||||
public double getFractionalFrequencyMultiplier()
|
||||
{
|
||||
return XTAL_FREQUENCY / mDivider / 65_536d;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this divider is mode 2x (false) or mode 3x (true) as a divider of the base crystal frequency.
|
||||
* @return true if 3x mode.
|
||||
*/
|
||||
public boolean is3xMode()
|
||||
{
|
||||
return mIs3xMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Divider value (of the base crystal frequency).
|
||||
* @return divider
|
||||
*/
|
||||
public int getDivider()
|
||||
{
|
||||
return mDivider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Minimum frequency supported by this divider.
|
||||
* @return minimum frequency (Hz).
|
||||
*/
|
||||
public long getMinimumFrequency()
|
||||
{
|
||||
return mMinimumFrequency;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maximum frequency supported by this divider.
|
||||
* @return maximum frequency (Hz).
|
||||
*/
|
||||
public long getMaximumFrequency()
|
||||
{
|
||||
return mMaximumFrequency;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the frequency is contained by the min/max values of the current frequency divider entry.
|
||||
* @param frequency to test
|
||||
* @return true if contained.
|
||||
*/
|
||||
public boolean contains(long frequency)
|
||||
{
|
||||
return mMinimumFrequency <= frequency && frequency <= mMaximumFrequency;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the matching frequency divider for the specified frequency, or returns divider 16x, if the frequency
|
||||
* is outside any supported frequency ranges.
|
||||
*
|
||||
* @param frequency - desired frequency
|
||||
* @return - FrequencyDivider to use for the specified frequency
|
||||
*/
|
||||
public static FrequencyDivider fromFrequency(long frequency)
|
||||
{
|
||||
for(FrequencyDivider divider : FrequencyDivider.values())
|
||||
{
|
||||
if(divider.contains(frequency))
|
||||
{
|
||||
return divider;
|
||||
}
|
||||
}
|
||||
|
||||
return FrequencyDivider.D16;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.source.tuner.rtl.fc0013;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
|
||||
import io.github.dsheirer.source.tuner.TunerType;
|
||||
import io.github.dsheirer.source.tuner.rtl.RTL2832TunerConfiguration;
|
||||
|
||||
/**
|
||||
* RTL-2832 with embedded FC0013 tuner configuration
|
||||
*/
|
||||
public class FC0013TunerConfiguration extends RTL2832TunerConfiguration
|
||||
{
|
||||
private FC0013EmbeddedTuner.LNAGain mLnaGain = FC0013EmbeddedTuner.LNAGain.G10;
|
||||
private boolean mAgc = false;
|
||||
|
||||
/**
|
||||
* Default constructor for JAXB
|
||||
*/
|
||||
public FC0013TunerConfiguration()
|
||||
{
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
@Override
|
||||
public TunerType getTunerType()
|
||||
{
|
||||
return TunerType.FITIPOWER_FC0013;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param uniqueID for the tuner
|
||||
*/
|
||||
public FC0013TunerConfiguration(String uniqueID)
|
||||
{
|
||||
super(uniqueID);
|
||||
}
|
||||
|
||||
/**
|
||||
* LNA gain value.
|
||||
* @return value
|
||||
*/
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "lna_gain")
|
||||
public FC0013EmbeddedTuner.LNAGain getLnaGain()
|
||||
{
|
||||
return mLnaGain;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the LNA gain value.
|
||||
* @param lnaGain value.
|
||||
*/
|
||||
public void setLnaGain(FC0013EmbeddedTuner.LNAGain lnaGain)
|
||||
{
|
||||
mLnaGain = lnaGain;
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatic Gain Control (AGC) state
|
||||
* @return AGC enabled state
|
||||
*/
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "agc")
|
||||
public boolean getAGC()
|
||||
{
|
||||
return mAgc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the AGC
|
||||
* @param enabled state of the AGC
|
||||
*/
|
||||
public void setAGC(boolean enabled)
|
||||
{
|
||||
mAgc = enabled;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,404 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.source.tuner.rtl.fc0013;
|
||||
|
||||
import io.github.dsheirer.preference.UserPreferences;
|
||||
import io.github.dsheirer.source.SourceException;
|
||||
import io.github.dsheirer.source.tuner.manager.DiscoveredTuner;
|
||||
import io.github.dsheirer.source.tuner.manager.TunerManager;
|
||||
import io.github.dsheirer.source.tuner.rtl.RTL2832Tuner;
|
||||
import io.github.dsheirer.source.tuner.rtl.RTL2832TunerController;
|
||||
import io.github.dsheirer.source.tuner.rtl.RTL2832TunerController.SampleRate;
|
||||
import io.github.dsheirer.source.tuner.ui.TunerEditor;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.usb4java.LibUsbException;
|
||||
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JComboBox;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JSeparator;
|
||||
import javax.swing.JToggleButton;
|
||||
import javax.swing.SpinnerNumberModel;
|
||||
|
||||
/**
|
||||
* FC0013 Tuner Editor
|
||||
*/
|
||||
public class FC0013TunerEditor extends TunerEditor<RTL2832Tuner, FC0013TunerConfiguration>
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(FC0013TunerEditor.class);
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final FC0013EmbeddedTuner.LNAGain DEFAULT_LNA_GAIN = FC0013EmbeddedTuner.LNAGain.G14;
|
||||
private JButton mTunerInfoButton;
|
||||
private JToggleButton mBiasTButton;
|
||||
private JComboBox<SampleRate> mSampleRateCombo;
|
||||
|
||||
private JToggleButton mAgcToggleButton;
|
||||
private JComboBox<FC0013EmbeddedTuner.LNAGain> mLNAGainCombo;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param userPreferences for wide-band recordings
|
||||
* @param tunerManager for saving configurations
|
||||
* @param discoveredTuner to edit
|
||||
*/
|
||||
public FC0013TunerEditor(UserPreferences userPreferences, TunerManager tunerManager, DiscoveredTuner discoveredTuner)
|
||||
{
|
||||
super(userPreferences, tunerManager, discoveredTuner);
|
||||
init();
|
||||
tunerStatusUpdated();
|
||||
}
|
||||
|
||||
/**
|
||||
* Access the FC0013 embedded tuner
|
||||
* @return tuner if there is a tuner, or null otherwise
|
||||
*/
|
||||
private FC0013EmbeddedTuner getEmbeddedTuner()
|
||||
{
|
||||
if(hasTuner())
|
||||
{
|
||||
return (FC0013EmbeddedTuner) getTuner().getController().getEmbeddedTuner();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private String getLogPrefix()
|
||||
{
|
||||
return getEmbeddedTuner().getTunerType().getLabel() + " Tuner Controller - ";
|
||||
}
|
||||
|
||||
private void init()
|
||||
{
|
||||
setLayout(new MigLayout("fill,wrap 3", "[right][grow,fill][fill]",
|
||||
"[][][][][][][][][][][][][][][][grow]"));
|
||||
|
||||
add(new JLabel("Tuner:"));
|
||||
add(getTunerIdLabel());
|
||||
add(getTunerInfoButton());
|
||||
|
||||
add(new JLabel("Status:"));
|
||||
add(getTunerStatusLabel());
|
||||
add(getBiasTButton(), "wrap");
|
||||
|
||||
add(getButtonPanel(), "span,align left");
|
||||
|
||||
add(new JSeparator(), "span,growx,push");
|
||||
|
||||
add(new JLabel("Frequency (MHz):"));
|
||||
add(getFrequencyPanel(), "wrap");
|
||||
|
||||
add(new JLabel("Sample Rate:"));
|
||||
add(getSampleRateCombo(), "wrap");
|
||||
|
||||
add(new JSeparator(), "span,growx,push");
|
||||
|
||||
JPanel gainPanel = new JPanel();
|
||||
gainPanel.add(new JLabel("Gain"));
|
||||
gainPanel.add(getAgcToggleButton());
|
||||
gainPanel.add(new JLabel("LNA:"));
|
||||
add(gainPanel);
|
||||
add(getLNAGainCombo(), "wrap");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void tunerStatusUpdated()
|
||||
{
|
||||
setLoading(true);
|
||||
|
||||
if(hasTuner())
|
||||
{
|
||||
getTunerIdLabel().setText(getTuner().getPreferredName());
|
||||
}
|
||||
else
|
||||
{
|
||||
getTunerIdLabel().setText(getDiscoveredTuner().getId());
|
||||
}
|
||||
|
||||
String status = getDiscoveredTuner().getTunerStatus().toString();
|
||||
if(getDiscoveredTuner().hasErrorMessage())
|
||||
{
|
||||
status += " - " + getDiscoveredTuner().getErrorMessage();
|
||||
}
|
||||
getTunerStatusLabel().setText(status);
|
||||
getButtonPanel().updateControls();
|
||||
getFrequencyPanel().updateControls();
|
||||
|
||||
if(hasTuner())
|
||||
{
|
||||
getBiasTButton().setEnabled(true);
|
||||
getBiasTButton().setSelected(getConfiguration().isBiasT());
|
||||
getTunerInfoButton().setEnabled(true);
|
||||
getSampleRateCombo().setEnabled(true);
|
||||
getSampleRateCombo().setSelectedItem(getConfiguration().getSampleRate());
|
||||
getAgcToggleButton().setEnabled(true);
|
||||
getAgcToggleButton().setSelected(getConfiguration().getAGC());
|
||||
getLNAGainCombo().setEnabled(!getConfiguration().getAGC());
|
||||
getLNAGainCombo().setSelectedItem(getConfiguration().getLnaGain());
|
||||
}
|
||||
else
|
||||
{
|
||||
getBiasTButton().setEnabled(false);
|
||||
getBiasTButton().setSelected(false);
|
||||
getTunerInfoButton().setEnabled(false);
|
||||
getSampleRateCombo().setEnabled(false);
|
||||
getAgcToggleButton().setEnabled(false);
|
||||
getLNAGainCombo().setEnabled(false);
|
||||
}
|
||||
|
||||
updateSampleRateToolTip();
|
||||
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bias-T toggle button
|
||||
* @return bias-t button
|
||||
*/
|
||||
private JToggleButton getBiasTButton()
|
||||
{
|
||||
if(mBiasTButton == null)
|
||||
{
|
||||
mBiasTButton = new JToggleButton("Bias-T");
|
||||
mBiasTButton.setEnabled(false);
|
||||
mBiasTButton.addActionListener(e -> {
|
||||
if(!isLoading())
|
||||
{
|
||||
getTuner().getController().setBiasT(mBiasTButton.isSelected());
|
||||
save();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return mBiasTButton;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hyperlink button that provides tuner information
|
||||
*/
|
||||
private JButton getTunerInfoButton()
|
||||
{
|
||||
if(mTunerInfoButton == null)
|
||||
{
|
||||
mTunerInfoButton = new JButton("Info");
|
||||
mTunerInfoButton.setEnabled(false);
|
||||
mTunerInfoButton.addActionListener(e -> JOptionPane.showMessageDialog(FC0013TunerEditor.this,
|
||||
getTunerInfo(), "Tuner Info", JOptionPane.INFORMATION_MESSAGE));
|
||||
}
|
||||
|
||||
return mTunerInfoButton;
|
||||
}
|
||||
|
||||
private JComboBox getLNAGainCombo()
|
||||
{
|
||||
if(mLNAGainCombo == null)
|
||||
{
|
||||
mLNAGainCombo = new JComboBox<>(FC0013EmbeddedTuner.LNAGain.values());
|
||||
mLNAGainCombo.setEnabled(false);
|
||||
mLNAGainCombo.addActionListener(arg0 ->
|
||||
{
|
||||
if(!isLoading())
|
||||
{
|
||||
try
|
||||
{
|
||||
FC0013EmbeddedTuner.LNAGain lnaGain = (FC0013EmbeddedTuner.LNAGain) mLNAGainCombo.getSelectedItem();
|
||||
|
||||
if(lnaGain == null)
|
||||
{
|
||||
lnaGain = DEFAULT_LNA_GAIN;
|
||||
}
|
||||
|
||||
if(mLNAGainCombo.isEnabled())
|
||||
{
|
||||
getEmbeddedTuner().setGain(getAgcToggleButton().isSelected(), lnaGain);
|
||||
}
|
||||
|
||||
save();
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
JOptionPane.showMessageDialog(FC0013TunerEditor.this, getLogPrefix() +
|
||||
"couldn't apply the LNA gain setting - " + e.getLocalizedMessage());
|
||||
mLog.error(getLogPrefix() + "couldn't apply LNA gain setting - ", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
mLNAGainCombo.setToolTipText("<html>LNA Gain. Set master gain to <b>MANUAL</b> to enable adjustment</html>");
|
||||
}
|
||||
|
||||
return mLNAGainCombo;
|
||||
}
|
||||
|
||||
private JComboBox getSampleRateCombo()
|
||||
{
|
||||
if(mSampleRateCombo == null)
|
||||
{
|
||||
mSampleRateCombo = new JComboBox<>(SampleRate.values());
|
||||
mSampleRateCombo.setEnabled(false);
|
||||
mSampleRateCombo.addActionListener(e ->
|
||||
{
|
||||
if(!isLoading())
|
||||
{
|
||||
SampleRate sampleRate = (SampleRate) mSampleRateCombo.getSelectedItem();
|
||||
|
||||
try
|
||||
{
|
||||
getTuner().getController().setSampleRate(sampleRate);
|
||||
save();
|
||||
}
|
||||
catch(SourceException | LibUsbException eSampleRate)
|
||||
{
|
||||
JOptionPane.showMessageDialog(FC0013TunerEditor.this,
|
||||
getLogPrefix() + "couldn't apply the sample rate setting [" +
|
||||
sampleRate.getLabel() + "] " + eSampleRate.getLocalizedMessage());
|
||||
|
||||
mLog.error(getLogPrefix() + "couldn't apply sample rate setting [" + sampleRate.getLabel() +
|
||||
"]", eSampleRate);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return mSampleRateCombo;
|
||||
}
|
||||
|
||||
private JToggleButton getAgcToggleButton()
|
||||
{
|
||||
if(mAgcToggleButton == null)
|
||||
{
|
||||
mAgcToggleButton = new JToggleButton("AGC");
|
||||
mAgcToggleButton.setEnabled(false);
|
||||
mAgcToggleButton.addActionListener(arg0 ->
|
||||
{
|
||||
if(!isLoading())
|
||||
{
|
||||
try
|
||||
{
|
||||
boolean agc = getAgcToggleButton().isSelected();
|
||||
FC0013EmbeddedTuner.LNAGain lnaGain = (FC0013EmbeddedTuner.LNAGain)getLNAGainCombo().getSelectedItem();
|
||||
getEmbeddedTuner().setGain(agc, lnaGain);
|
||||
getLNAGainCombo().setEnabled(!agc);
|
||||
save();
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
JOptionPane.showMessageDialog(FC0013TunerEditor.this, getLogPrefix() +
|
||||
"couldn't set AGC" + e.getLocalizedMessage());
|
||||
mLog.error(getLogPrefix() + "couldn't set AGC", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
mAgcToggleButton.setToolTipText("<html>Automatic Gain Control (AGC). </html>");
|
||||
}
|
||||
|
||||
return mAgcToggleButton;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the sample rate tooltip according to the tuner controller's lock state.
|
||||
*/
|
||||
private void updateSampleRateToolTip()
|
||||
{
|
||||
if(hasTuner() && getTuner().getTunerController().isLockedSampleRate())
|
||||
{
|
||||
getSampleRateCombo().setToolTipText("Sample Rate is locked. Disable decoding channels to unlock.");
|
||||
}
|
||||
else if(hasTuner())
|
||||
{
|
||||
getSampleRateCombo().setToolTipText("Select a sample rate for the tuner");
|
||||
}
|
||||
else
|
||||
{
|
||||
getSampleRateCombo().setToolTipText("No tuner available");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTunerLockState(boolean locked)
|
||||
{
|
||||
getFrequencyPanel().updateControls();
|
||||
getSampleRateCombo().setEnabled(!locked);
|
||||
updateSampleRateToolTip();
|
||||
}
|
||||
|
||||
private String getTunerInfo()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
RTL2832TunerController.Descriptor descriptor = getTuner().getController().getDescriptor();
|
||||
sb.append("<html><h3>RTL-2832 with " + getEmbeddedTuner().getTunerType().getLabel() + " Tuner</h3>");
|
||||
|
||||
if(descriptor == null)
|
||||
{
|
||||
sb.append("No EEPROM Descriptor Available");
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append("<b>USB ID: </b>");
|
||||
sb.append(descriptor.getVendorID());
|
||||
sb.append(":");
|
||||
sb.append(descriptor.getProductID());
|
||||
sb.append("<br>");
|
||||
|
||||
sb.append("<b>Vendor: </b>");
|
||||
sb.append(descriptor.getVendorLabel());
|
||||
sb.append("<br>");
|
||||
|
||||
sb.append("<b>Product: </b>");
|
||||
sb.append(descriptor.getProductLabel());
|
||||
sb.append("<br>");
|
||||
|
||||
sb.append("<b>Serial: </b>");
|
||||
sb.append(descriptor.getSerial());
|
||||
sb.append("<br>");
|
||||
|
||||
sb.append("<b>IR Enabled: </b>");
|
||||
sb.append(descriptor.irEnabled());
|
||||
sb.append("<br>");
|
||||
|
||||
sb.append("<b>Remote Wake: </b>");
|
||||
sb.append(descriptor.remoteWakeupEnabled());
|
||||
sb.append("<br>");
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save()
|
||||
{
|
||||
if(hasConfiguration() && !isLoading())
|
||||
{
|
||||
FC0013TunerConfiguration config = getConfiguration();
|
||||
config.setBiasT(getTuner().getController().isBiasT());
|
||||
config.setFrequency(getFrequencyControl().getFrequency());
|
||||
double value = ((SpinnerNumberModel)getFrequencyCorrectionSpinner().getModel()).getNumber().doubleValue();
|
||||
config.setFrequencyCorrection(value);
|
||||
config.setAutoPPMCorrectionEnabled(getAutoPPMCheckBox().isSelected());
|
||||
|
||||
config.setSampleRate((SampleRate)getSampleRateCombo().getSelectedItem());
|
||||
config.setAGC(getAgcToggleButton().isSelected());
|
||||
FC0013EmbeddedTuner.LNAGain lnaGain = (FC0013EmbeddedTuner.LNAGain)getLNAGainCombo().getSelectedItem();
|
||||
config.setLnaGain(lnaGain);
|
||||
saveConfiguration();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -71,9 +71,14 @@ public abstract class R8xEmbeddedTuner extends EmbeddedTuner
|
||||
}
|
||||
|
||||
/**
|
||||
* I2C Address for the embedded tuner
|
||||
* I2C Write Address for the embedded tuner
|
||||
*/
|
||||
public abstract byte getI2CAddress();
|
||||
public abstract byte getI2CWriteAddress();
|
||||
|
||||
/**
|
||||
* I2C Read Address for the embedded tuner
|
||||
*/
|
||||
public abstract byte getI2CReadAddress();
|
||||
|
||||
/**
|
||||
* Applies the tuner configuration values to this embedded tuner
|
||||
@@ -83,8 +88,6 @@ public abstract class R8xEmbeddedTuner extends EmbeddedTuner
|
||||
@Override
|
||||
public void apply(TunerConfiguration tunerConfig) throws SourceException
|
||||
{
|
||||
//Invoke super for frequency, frequency correction and autoPPM
|
||||
|
||||
if(tunerConfig instanceof R8xTunerConfiguration config)
|
||||
{
|
||||
try
|
||||
@@ -120,7 +123,7 @@ public abstract class R8xEmbeddedTuner extends EmbeddedTuner
|
||||
|
||||
if(value != current)
|
||||
{
|
||||
getAdapter().writeI2CRegister(getI2CAddress(), (byte) register.getRegister(), value, controlI2C);
|
||||
getAdapter().writeI2CRegister(getI2CWriteAddress(), (byte) register.getRegister(), value, controlI2C);
|
||||
mShadowRegister[register.getRegister()] = value;
|
||||
}
|
||||
}
|
||||
@@ -130,7 +133,7 @@ public abstract class R8xEmbeddedTuner extends EmbeddedTuner
|
||||
*/
|
||||
public int readRegister(Register register, boolean controlI2C) throws UsbException
|
||||
{
|
||||
return getAdapter().readI2CRegister(getI2CAddress(), (byte) register.getRegister(), controlI2C);
|
||||
return getAdapter().readI2CRegister(getI2CReadAddress(), (byte) register.getRegister(), controlI2C);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -459,7 +462,7 @@ public abstract class R8xEmbeddedTuner extends EmbeddedTuner
|
||||
{
|
||||
for(int x = 5; x < mShadowRegister.length; x++)
|
||||
{
|
||||
getAdapter().writeI2CRegister(getI2CAddress(), (byte) x, mShadowRegister[x], controlI2C);
|
||||
getAdapter().writeI2CRegister(getI2CWriteAddress(), (byte) x, mShadowRegister[x], controlI2C);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -469,7 +472,7 @@ public abstract class R8xEmbeddedTuner extends EmbeddedTuner
|
||||
protected int getStatusRegister(int register, boolean controlI2C) throws UsbException
|
||||
{
|
||||
ByteBuffer buffer = ByteBuffer.allocateDirect(5);
|
||||
getAdapter().read(getI2CAddress(), RTL2832TunerController.Block.I2C, buffer);
|
||||
getAdapter().read(getI2CWriteAddress(), RTL2832TunerController.Block.I2C, buffer);
|
||||
return bitReverse(buffer.get(register) & 0xFF);
|
||||
}
|
||||
|
||||
|
||||
@@ -30,7 +30,8 @@ import javax.usb.UsbException;
|
||||
*/
|
||||
public class R820TEmbeddedTuner extends R8xEmbeddedTuner
|
||||
{
|
||||
private static final byte I2C_ADDRESS = (byte) 0x34;
|
||||
private static final byte I2C_WRITE_ADDRESS = (byte) 0x34;
|
||||
private static final byte I2C_READ_ADDRESS = (byte) 0x35;
|
||||
private static final int VCO_POWER_REF = 2;
|
||||
|
||||
/**
|
||||
@@ -49,9 +50,15 @@ public class R820TEmbeddedTuner extends R8xEmbeddedTuner
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte getI2CAddress()
|
||||
public byte getI2CWriteAddress()
|
||||
{
|
||||
return I2C_ADDRESS;
|
||||
return I2C_WRITE_ADDRESS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte getI2CReadAddress()
|
||||
{
|
||||
return I2C_READ_ADDRESS;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -30,7 +30,8 @@ import javax.usb.UsbException;
|
||||
*/
|
||||
public class R828DEmbeddedTuner extends R8xEmbeddedTuner
|
||||
{
|
||||
private static final byte I2C_ADDRESS = (byte) 0x74;
|
||||
private static final byte I2C_WRITE_ADDRESS = (byte) 0x74;
|
||||
private static final byte I2C_READ_ADDRESS = (byte) 0x75;
|
||||
private static final int VCO_POWER_REF = 1;
|
||||
/**
|
||||
* RTL-SDR.com blog V4 dongle indicator.
|
||||
@@ -56,9 +57,15 @@ public class R828DEmbeddedTuner extends R8xEmbeddedTuner
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte getI2CAddress()
|
||||
public byte getI2CWriteAddress()
|
||||
{
|
||||
return I2C_ADDRESS;
|
||||
return I2C_WRITE_ADDRESS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte getI2CReadAddress()
|
||||
{
|
||||
return I2C_READ_ADDRESS;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -56,8 +56,8 @@ public abstract class USBTunerController extends TunerController
|
||||
protected static final byte USB_BULK_TRANSFER_ENDPOINT = (byte) 0x81;
|
||||
private static final long USB_BULK_TRANSFER_TIMEOUT_MS = 2000l;
|
||||
|
||||
private int mBus;
|
||||
private String mPortAddress;
|
||||
protected int mBus;
|
||||
protected String mPortAddress;
|
||||
private Context mDeviceContext = new Context();
|
||||
private Device mDevice;
|
||||
private DeviceHandle mDeviceHandle;
|
||||
|
||||
Reference in New Issue
Block a user