From 50bdf0ee8113544b7f65c0e4754dbc2bff31d864 Mon Sep 17 00:00:00 2001 From: Dennis Sheirer Date: Sat, 4 Nov 2023 06:35:47 -0400 Subject: [PATCH] #1687 Adds support for RTL2832 with embedded FC0013 tuner. --- .../dsheirer/source/tuner/TunerFactory.java | 8 + .../configuration/TunerConfiguration.java | 2 + .../tuner/rtl/RTL2832TunerConfiguration.java | 2 + .../tuner/rtl/RTL2832TunerController.java | 20 +- .../tuner/rtl/fc0013/FC0013EmbeddedTuner.java | 842 ++++++++++++++++++ .../rtl/fc0013/FC0013TunerConfiguration.java | 95 ++ .../tuner/rtl/fc0013/FC0013TunerEditor.java | 404 +++++++++ .../tuner/rtl/r8x/R8xEmbeddedTuner.java | 19 +- .../rtl/r8x/r820t/R820TEmbeddedTuner.java | 13 +- .../rtl/r8x/r828d/R828DEmbeddedTuner.java | 13 +- .../source/tuner/usb/USBTunerController.java | 4 +- 11 files changed, 1399 insertions(+), 23 deletions(-) create mode 100644 src/main/java/io/github/dsheirer/source/tuner/rtl/fc0013/FC0013EmbeddedTuner.java create mode 100644 src/main/java/io/github/dsheirer/source/tuner/rtl/fc0013/FC0013TunerConfiguration.java create mode 100644 src/main/java/io/github/dsheirer/source/tuner/rtl/fc0013/FC0013TunerEditor.java diff --git a/src/main/java/io/github/dsheirer/source/tuner/TunerFactory.java b/src/main/java/io/github/dsheirer/source/tuner/TunerFactory.java index df4b8062a..5ddf648c4 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/TunerFactory.java +++ b/src/main/java/io/github/dsheirer/source/tuner/TunerFactory.java @@ -55,6 +55,9 @@ 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 static EmbeddedTuner getRtlEmbeddedTuner(TunerType tunerType, 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 static TunerConfiguration getTunerConfiguration(TunerType type, String un 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 @@ else if(discoveredRspTuner instanceof DiscoveredRspDuoTuner2 duoTuner2) { 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); diff --git a/src/main/java/io/github/dsheirer/source/tuner/configuration/TunerConfiguration.java b/src/main/java/io/github/dsheirer/source/tuner/configuration/TunerConfiguration.java index 6de3ee44e..f2fb8fd59 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/configuration/TunerConfiguration.java +++ b/src/main/java/io/github/dsheirer/source/tuner/configuration/TunerConfiguration.java @@ -31,6 +31,7 @@ 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 @@ @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"), diff --git a/src/main/java/io/github/dsheirer/source/tuner/rtl/RTL2832TunerConfiguration.java b/src/main/java/io/github/dsheirer/source/tuner/rtl/RTL2832TunerConfiguration.java index 8141240f5..587f2064c 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/rtl/RTL2832TunerConfiguration.java +++ b/src/main/java/io/github/dsheirer/source/tuner/rtl/RTL2832TunerConfiguration.java @@ -24,6 +24,7 @@ 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 @@ @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"), }) diff --git a/src/main/java/io/github/dsheirer/source/tuner/rtl/RTL2832TunerController.java b/src/main/java/io/github/dsheirer/source/tuner/rtl/RTL2832TunerController.java index 3c4951799..2104a62a9 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/rtl/RTL2832TunerController.java +++ b/src/main/java/io/github/dsheirer/source/tuner/rtl/RTL2832TunerController.java @@ -354,21 +354,28 @@ private void setIFFrequency(int frequency) throws LibUsbException */ 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 @@ private void setGPIOOutput(byte bitMask) throws LibUsbException */ 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); } /** diff --git a/src/main/java/io/github/dsheirer/source/tuner/rtl/fc0013/FC0013EmbeddedTuner.java b/src/main/java/io/github/dsheirer/source/tuner/rtl/fc0013/FC0013EmbeddedTuner.java new file mode 100644 index 000000000..7afd57cb4 --- /dev/null +++ b/src/main/java/io/github/dsheirer/source/tuner/rtl/fc0013/FC0013EmbeddedTuner.java @@ -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 + * **************************************************************************** + */ +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 + *

+ * 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; + } + } +} \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/source/tuner/rtl/fc0013/FC0013TunerConfiguration.java b/src/main/java/io/github/dsheirer/source/tuner/rtl/fc0013/FC0013TunerConfiguration.java new file mode 100644 index 000000000..b08b0609f --- /dev/null +++ b/src/main/java/io/github/dsheirer/source/tuner/rtl/fc0013/FC0013TunerConfiguration.java @@ -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 + * **************************************************************************** + */ +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; + } +} diff --git a/src/main/java/io/github/dsheirer/source/tuner/rtl/fc0013/FC0013TunerEditor.java b/src/main/java/io/github/dsheirer/source/tuner/rtl/fc0013/FC0013TunerEditor.java new file mode 100644 index 000000000..187f0f657 --- /dev/null +++ b/src/main/java/io/github/dsheirer/source/tuner/rtl/fc0013/FC0013TunerEditor.java @@ -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 + * **************************************************************************** + */ +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 +{ + 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 mSampleRateCombo; + + private JToggleButton mAgcToggleButton; + private JComboBox 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("LNA Gain. Set master gain to MANUAL to enable adjustment"); + } + + 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("Automatic Gain Control (AGC). "); + } + + 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("

RTL-2832 with " + getEmbeddedTuner().getTunerType().getLabel() + " Tuner

"); + + if(descriptor == null) + { + sb.append("No EEPROM Descriptor Available"); + } + else + { + sb.append("USB ID: "); + sb.append(descriptor.getVendorID()); + sb.append(":"); + sb.append(descriptor.getProductID()); + sb.append("
"); + + sb.append("Vendor: "); + sb.append(descriptor.getVendorLabel()); + sb.append("
"); + + sb.append("Product: "); + sb.append(descriptor.getProductLabel()); + sb.append("
"); + + sb.append("Serial: "); + sb.append(descriptor.getSerial()); + sb.append("
"); + + sb.append("IR Enabled: "); + sb.append(descriptor.irEnabled()); + sb.append("
"); + + sb.append("Remote Wake: "); + sb.append(descriptor.remoteWakeupEnabled()); + sb.append("
"); + } + + 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(); + } + } +} \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/source/tuner/rtl/r8x/R8xEmbeddedTuner.java b/src/main/java/io/github/dsheirer/source/tuner/rtl/r8x/R8xEmbeddedTuner.java index 3711d12eb..2f4da33af 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/rtl/r8x/R8xEmbeddedTuner.java +++ b/src/main/java/io/github/dsheirer/source/tuner/rtl/r8x/R8xEmbeddedTuner.java @@ -71,9 +71,14 @@ public R8xEmbeddedTuner(RTL2832TunerController.ControllerAdapter adapter, int vc } /** - * 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 R8xEmbeddedTuner(RTL2832TunerController.ControllerAdapter adapter, int vc @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 synchronized void writeRegister(Register register, byte value, boolean co 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 synchronized void writeRegister(Register register, byte value, boolean co */ 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 @@ protected void initializeRegisters(boolean controlI2C) throws UsbException { 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 @@ protected void initializeRegisters(boolean controlI2C) throws UsbException 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); } diff --git a/src/main/java/io/github/dsheirer/source/tuner/rtl/r8x/r820t/R820TEmbeddedTuner.java b/src/main/java/io/github/dsheirer/source/tuner/rtl/r8x/r820t/R820TEmbeddedTuner.java index 49444e0b1..c340210b0 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/rtl/r8x/r820t/R820TEmbeddedTuner.java +++ b/src/main/java/io/github/dsheirer/source/tuner/rtl/r8x/r820t/R820TEmbeddedTuner.java @@ -30,7 +30,8 @@ */ 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 TunerType getTunerType() } @Override - public byte getI2CAddress() + public byte getI2CWriteAddress() { - return I2C_ADDRESS; + return I2C_WRITE_ADDRESS; + } + + @Override + public byte getI2CReadAddress() + { + return I2C_READ_ADDRESS; } /** diff --git a/src/main/java/io/github/dsheirer/source/tuner/rtl/r8x/r828d/R828DEmbeddedTuner.java b/src/main/java/io/github/dsheirer/source/tuner/rtl/r8x/r828d/R828DEmbeddedTuner.java index 910c1f04f..36765fbcc 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/rtl/r8x/r828d/R828DEmbeddedTuner.java +++ b/src/main/java/io/github/dsheirer/source/tuner/rtl/r8x/r828d/R828DEmbeddedTuner.java @@ -30,7 +30,8 @@ */ 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 TunerType getTunerType() } @Override - public byte getI2CAddress() + public byte getI2CWriteAddress() { - return I2C_ADDRESS; + return I2C_WRITE_ADDRESS; + } + + @Override + public byte getI2CReadAddress() + { + return I2C_READ_ADDRESS; } /** diff --git a/src/main/java/io/github/dsheirer/source/tuner/usb/USBTunerController.java b/src/main/java/io/github/dsheirer/source/tuner/usb/USBTunerController.java index d6c2e8c7e..e10d34838 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/usb/USBTunerController.java +++ b/src/main/java/io/github/dsheirer/source/tuner/usb/USBTunerController.java @@ -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;