diff --git a/build.gradle b/build.gradle index e36d4af3..e2da044d 100644 --- a/build.gradle +++ b/build.gradle @@ -127,6 +127,7 @@ dependencies { implementation 'io.github.dsheirer:jbladerf:2.6.0' //bladeRF tuners - GitHub release, see settings.gradle implementation 'io.github.dsheirer:jairspy:1.0.12' //Airspy tuners - GitHub release, see settings.gradle implementation 'io.github.dsheirer:jhydrasdr:1.1.4' //HydraSDR tuners - GitHub release, see settings.gradle + implementation 'io.github.dsheirer:jrtl:1.4.0.1' //RTL-SDR tuners - GitHub release, see settings.gradle implementation 'io.github.dsheirer:radio-reference-api:18.0.0' implementation 'io.github.dsheirer:usb4java-native-libraries:1.3.3' //OSX & Windows aarch64 native libs implementation 'io.github.dsheirer:usb4java:1.3.5' //Fork with Loader support for OSX Multi-Version diff --git a/settings.gradle b/settings.gradle index 0cc4eac9..ede6cff6 100644 --- a/settings.gradle +++ b/settings.gradle @@ -17,6 +17,14 @@ dependencyResolutionManagement { content { includeModule 'io.github.dsheirer', 'jairspy' } } + //jRTL is published as a GitHub release, with the native libraries for all platforms in the jar + ivy { + url = 'https://github.com/DSheirer/jRTL/releases/download' + patternLayout { artifact 'v[revision]/[artifact]-[revision].[ext]' } + metadataSources { artifact() } + content { includeModule 'io.github.dsheirer', 'jrtl' } + } + //jHydraSDR is published as a GitHub release, with the native libraries for all platforms in the jar ivy { url = 'https://github.com/DSheirer/jHydraSDR/releases/download' diff --git a/src/main/java/io/github/dsheirer/source/tuner/TunerClass.java b/src/main/java/io/github/dsheirer/source/tuner/TunerClass.java index 6af936cc..68220270 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/TunerClass.java +++ b/src/main/java/io/github/dsheirer/source/tuner/TunerClass.java @@ -118,6 +118,23 @@ public enum TunerClass case 0x0CCD00D3: //TERRATEC CINERGY T REV3 case 0x0CCD00D7: //TERRATEC T STICK PLUS case 0x0CCD00E0: //TERRATEC NOXON REV2 + case 0x04136680: //DIGITALNOW QUAD DVB-T PCI-E CARD + case 0x04136F0F: //LEADTEK WINFAST DTV DONGLE MINI D + case 0x0458707F: //GENIUS TVGO DVB-T03 USB DONGLE (VER. B) + case 0x0CCD00B5: //TERRATEC NOXON DAB STICK - RADIO ENERGY + case 0x0CCD00B8: //TERRATEC BR DAB STICK + case 0x0CCD00B9: //TERRATEC WDR DAB STICK + case 0x0CCD00C0: //TERRATEC MUELLERVERLAG DAB STICK + case 0x15545020: //PIXELVIEW PV-DT235U(RN) + case 0x15F40131: //ASTROMETA DVB-T/DVB-T2 + case 0x15F40133: //HANFTEK DAB+FM+DVB-T + case 0x1B80D394: //DIKOM USB-DVBT HD + case 0x1B80D397: //KWORLD KW-UB450-T USB DVB-T PICO TV + case 0x1B80D3A8: //ASUS U3100MINI_PLUS_V2 + case 0x1B80D3AF: //SVEON STV27 DVB-T USB & FM + case 0x1B80D3B0: //SVEON STV21 DVB-T USB & FM + case 0x1D191104: //MSI DIGIVOX MICRO HD + case 0x1F4DA803: //SWEEX DVB-T USB return RTL2832; case 0x1D506089: //HACKRF ONE case 0x1D50CC15: //HACKRF ONE 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 bb8b9e56..02a814a2 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/TunerFactory.java +++ b/src/main/java/io/github/dsheirer/source/tuner/TunerFactory.java @@ -56,20 +56,15 @@ import io.github.dsheirer.source.tuner.manager.DiscoveredTuner; import io.github.dsheirer.source.tuner.manager.TunerManager; import io.github.dsheirer.source.tuner.recording.RecordingTunerConfiguration; import io.github.dsheirer.source.tuner.recording.RecordingTunerEditor; -import io.github.dsheirer.source.tuner.rtl.EmbeddedTuner; import io.github.dsheirer.source.tuner.rtl.RTL2832Tuner; import io.github.dsheirer.source.tuner.rtl.RTL2832TunerController; +import io.github.dsheirer.source.tuner.rtl.RTL2832TunerEditor; 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.fc0012.FC0012TunerConfiguration; 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.fc2580.FC2580TunerConfiguration; import io.github.dsheirer.source.tuner.rtl.r8x.r820t.R820TTunerConfiguration; -import io.github.dsheirer.source.tuner.rtl.r8x.r828d.R828DEmbeddedTuner; import io.github.dsheirer.source.tuner.rtl.r8x.r828d.R828DTunerConfiguration; import io.github.dsheirer.source.tuner.sdrplay.DiscoveredRspTuner; import io.github.dsheirer.source.tuner.sdrplay.RspTuner; @@ -394,25 +389,6 @@ public class TunerFactory } } - /** - * Creates an RTL-2832 embedded tuner instance. - * @param tunerType to create - * @param adapter exposing the RTL-2832 tuner controller functions - * @return embedded tuner - */ - public static EmbeddedTuner getRtlEmbeddedTuner(TunerType tunerType, - RTL2832TunerController.ControllerAdapter adapter) throws SourceException - { - 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); - }; - } - /** * Creates a tuner configuration for the specified tuner type, unique ID and name */ @@ -428,8 +404,12 @@ public class TunerFactory return new BladeRfTunerConfiguration(uniqueID); case ELONICS_E4000: return new E4KTunerConfiguration(uniqueID); + case FITIPOWER_FC0012: + return new FC0012TunerConfiguration(uniqueID); case FITIPOWER_FC0013: return new FC0013TunerConfiguration(uniqueID); + case FCI_FC2580: + return new FC2580TunerConfiguration(uniqueID); case FUNCUBE_DONGLE_PRO: return new FCD1TunerConfiguration(uniqueID); case FUNCUBE_DONGLE_PRO_PLUS: @@ -532,12 +512,12 @@ public class TunerFactory switch(discoveredTuner.getTuner().getTunerType()) { case ELONICS_E4000: - return new E4KTunerEditor(userPreferences, tunerManager, discoveredTuner); + case FCI_FC2580: + case FITIPOWER_FC0012: case FITIPOWER_FC0013: - return new FC0013TunerEditor(userPreferences, tunerManager, discoveredTuner); case RAFAELMICRO_R820T: case RAFAELMICRO_R828D: - return new R8xTunerEditor(userPreferences, tunerManager, discoveredTuner); + return new RTL2832TunerEditor(userPreferences, tunerManager, discoveredTuner); } } return new RTL2832UnknownTunerEditor(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 5d4f6843..7cc87531 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 @@ -33,7 +33,9 @@ import io.github.dsheirer.source.tuner.hackrf.HackRFTunerConfiguration; import io.github.dsheirer.source.tuner.hydrasdr.HydraSdrTunerConfiguration; import io.github.dsheirer.source.tuner.recording.RecordingTunerConfiguration; import io.github.dsheirer.source.tuner.rtl.e4k.E4KTunerConfiguration; +import io.github.dsheirer.source.tuner.rtl.fc0012.FC0012TunerConfiguration; import io.github.dsheirer.source.tuner.rtl.fc0013.FC0013TunerConfiguration; +import io.github.dsheirer.source.tuner.rtl.fc2580.FC2580TunerConfiguration; 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; @@ -47,7 +49,9 @@ import io.github.dsheirer.source.tuner.sdrplay.RspTunerConfiguration; @JsonSubTypes.Type(value = AirspyHfTunerConfiguration.class, name = "airspyHfTunerConfiguration"), @JsonSubTypes.Type(value = BladeRfTunerConfiguration.class, name = "bladeRfTunerConfiguration"), @JsonSubTypes.Type(value = E4KTunerConfiguration.class, name = "e4KTunerConfiguration"), + @JsonSubTypes.Type(value = FC0012TunerConfiguration.class, name = "fc0012TunerConfiguration"), @JsonSubTypes.Type(value = FC0013TunerConfiguration.class, name = "fc0013TunerConfiguration"), + @JsonSubTypes.Type(value = FC2580TunerConfiguration.class, name = "fc2580TunerConfiguration"), @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/EmbeddedTuner.java b/src/main/java/io/github/dsheirer/source/tuner/rtl/EmbeddedTuner.java deleted file mode 100644 index c8d37e2f..00000000 --- a/src/main/java/io/github/dsheirer/source/tuner/rtl/EmbeddedTuner.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * ***************************************************************************** - * Copyright (C) 2014-2022 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; - -import io.github.dsheirer.source.SourceException; -import io.github.dsheirer.source.tuner.TunerType; -import io.github.dsheirer.source.tuner.configuration.TunerConfiguration; -import org.usb4java.LibUsbException; - -import javax.usb.UsbException; - -/** - * Tuner embedded in an RTL-2832 USB tuner. - * - * Note: since the RTL2832TunerController instance is constructed without knowing the embedded tuner type, we construct - * an instance of the actual tuner once the type is discovered and we use an adapter to access the non-public USB - * controls and interfaces needed by the embedded tuner. This access is through the RtlTunerControllerAdapter. - */ -public abstract class EmbeddedTuner -{ - private RTL2832TunerController.ControllerAdapter mAdapter; - - /** - * Constructs an instance - * @param adapter exposing the interfaces of the parent RTL2832TunerController - */ - public EmbeddedTuner(RTL2832TunerController.ControllerAdapter adapter) - { - mAdapter = adapter; - } - - /** - * Access the interfaces of the parent RTL tuner controller via the adapter - * @return - */ - protected RTL2832TunerController.ControllerAdapter getAdapter() - { - return mAdapter; - } - - /** - * Initialize this embedded tuner. The calling method should ensure that the I2C repeater is enabled before - * calling this method. - */ - protected abstract void initTuner() throws UsbException; - - /** - * Type of embedded tuner - */ - public abstract TunerType getTunerType(); - - /** - * Apply the tuner configuration settings to this embedded tuner - * @param tunerConfig containing settings - * @throws SourceException if there is an error in applying the settings - */ - public abstract void apply(TunerConfiguration tunerConfig) throws SourceException; - - /** - * Minimum frequency supported by this tuner - */ - public abstract long getMinimumFrequencySupported(); - - /** - * Maximum frequency supported by this tuner - */ - public abstract long getMaximumFrequencySupported(); - - /** - * Bandwidth of the DC spike that is unusable for this tuner - */ - //TODO: can we get rid of this now? - public abstract int getDcSpikeHalfBandwidth(); - - /** - * Percentage of the bandwidth that is usable for this tuner - */ - public abstract double getUsableBandwidthPercent(); - - /** - * Sets the center tuned frequency - * @param frequency in Hertz - * @throws SourceException if there is an error - */ - public abstract void setTunedFrequency(long frequency) throws SourceException; - - /** - * Sets the sample mode for the tuner - * @param mode to set - * @throws LibUsbException - */ - public abstract void setSamplingMode(RTL2832TunerController.SampleMode mode) throws LibUsbException; - - /** - * Sets the filters according to the specified sample rate - * @param sampleRate for filter setup - * @throws SourceException if there is an error - */ - public abstract void setSampleRateFilters(int sampleRate) throws SourceException; -} 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 e115553e..95853204 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 @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2024 Dennis Sheirer + * Copyright (C) 2014-2026 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 @@ -24,7 +24,9 @@ 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.fc0012.FC0012TunerConfiguration; import io.github.dsheirer.source.tuner.rtl.fc0013.FC0013TunerConfiguration; +import io.github.dsheirer.source.tuner.rtl.fc2580.FC2580TunerConfiguration; import io.github.dsheirer.source.tuner.rtl.r8x.r820t.R820TTunerConfiguration; import io.github.dsheirer.source.tuner.rtl.r8x.r828d.R828DTunerConfiguration; @@ -34,7 +36,9 @@ 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 = FC0012TunerConfiguration.class, name = "fc0012TunerConfiguration"), @JsonSubTypes.Type(value = FC0013TunerConfiguration.class, name = "fc0013TunerConfiguration"), + @JsonSubTypes.Type(value = FC2580TunerConfiguration.class, name = "fc2580TunerConfiguration"), @JsonSubTypes.Type(value = R820TTunerConfiguration.class, name = "r820TTunerConfiguration"), @JsonSubTypes.Type(value = R828DTunerConfiguration.class, name = "r828DTunerConfiguration"), }) @@ -43,20 +47,92 @@ public abstract class RTL2832TunerConfiguration extends TunerConfiguration { private RTL2832TunerController.SampleRate mSampleRate = RTL2832TunerController.SampleRate.RATE_2_400MHZ; private boolean mBiasTEnabled = false; + private int mGain; + private boolean mAGC = false; /** * Default constructor to support Jackson * @param minimumFrequency tunable * @param maximumFrequency tunable + * @param defaultGain in tenths of a dB */ - public RTL2832TunerConfiguration(long minimumFrequency, long maximumFrequency) + public RTL2832TunerConfiguration(long minimumFrequency, long maximumFrequency, int defaultGain) { super(minimumFrequency, maximumFrequency); + mGain = defaultGain; } - public RTL2832TunerConfiguration(String uniqueID) + /** + * Constructs an instance + * @param uniqueID for the tuner + * @param defaultGain in tenths of a dB + */ + public RTL2832TunerConfiguration(String uniqueID, int defaultGain) { super(uniqueID); + mGain = defaultGain; + } + + /** + * Tuner gain, applied when AGC is off. + * @return gain in tenths of a dB. + */ + @JacksonXmlProperty(isAttribute = true, localName = "gain") + public int getGain() + { + return mGain; + } + + /** + * Sets the tuner gain, applied when AGC is off. + * @param gain in tenths of a dB + */ + public void setGain(int gain) + { + mGain = gain; + } + + /** + * Indicates if the tuner's automatic gain control is enabled. + */ + @JacksonXmlProperty(isAttribute = true, localName = "agc") + public boolean isAGC() + { + return mAGC; + } + + /** + * Sets the enabled state of the tuner's automatic gain control. + */ + public void setAGC(boolean agc) + { + mAGC = agc; + } + + /** + * Parses a gain value from the name of a gain enumeration entry used by legacy tuner configurations, for + * example GAIN_327, GAIN_PLUS_200, GAIN_MINUS_50, PLUS_240 or MINUS_10. + * @param name of the legacy enumeration entry + * @return gain value, or null if the name doesn't contain a gain value. + */ + protected static Integer parseLegacyGain(String name) + { + if(name != null) + { + String value = name.substring(name.lastIndexOf('_') + 1); + + try + { + int gain = Integer.parseInt(value); + return name.contains("MINUS") ? -gain : gain; + } + catch(NumberFormatException nfe) + { + //Not a gain value + } + } + + return null; } @JacksonXmlProperty(isAttribute = true, localName = "sample_rate") 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 1ec80f40..046b7610 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 @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2023 Dennis Sheirer + * Copyright (C) 2014-2026 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 @@ -19,112 +19,587 @@ package io.github.dsheirer.source.tuner.rtl; import io.github.dsheirer.buffer.ByteNativeBufferFactory; -import io.github.dsheirer.buffer.INativeBufferFactory; +import io.github.dsheirer.buffer.INativeBuffer; +import io.github.dsheirer.rtlsdr.DeviceInfo; +import io.github.dsheirer.rtlsdr.RtlSdr; +import io.github.dsheirer.rtlsdr.RtlSdrDevice; +import io.github.dsheirer.rtlsdr.RtlSdrException; +import io.github.dsheirer.rtlsdr.SampleBuffer; +import io.github.dsheirer.sample.Listener; import io.github.dsheirer.source.SourceException; import io.github.dsheirer.source.tuner.ITunerErrorListener; import io.github.dsheirer.source.tuner.TunerClass; -import io.github.dsheirer.source.tuner.TunerFactory; +import io.github.dsheirer.source.tuner.TunerController; import io.github.dsheirer.source.tuner.TunerType; import io.github.dsheirer.source.tuner.configuration.TunerConfiguration; -import io.github.dsheirer.source.tuner.usb.USBTunerController; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.nio.charset.Charset; +import io.github.dsheirer.source.tuner.manager.TunerManager; +import io.github.dsheirer.source.tuner.rtl.e4k.E4KTunerConfiguration; +import io.github.dsheirer.source.tuner.rtl.fc0012.FC0012TunerConfiguration; +import io.github.dsheirer.source.tuner.rtl.fc0013.FC0013TunerConfiguration; +import io.github.dsheirer.source.tuner.rtl.fc2580.FC2580TunerConfiguration; +import io.github.dsheirer.source.tuner.rtl.r8x.R8xTunerConfiguration; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; -import java.util.concurrent.locks.ReentrantLock; +import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.usb4java.Context; +import org.usb4java.Device; +import org.usb4java.DeviceDescriptor; import org.usb4java.DeviceHandle; +import org.usb4java.DeviceList; import org.usb4java.LibUsb; -import org.usb4java.LibUsbException; - -import javax.usb.UsbDisconnectedException; -import javax.usb.UsbException; /** - * RTL-2832 tuner controller implementation. + * RTL-2832 tuner controller, implemented with the jRTL library (librtlsdr from rtl-sdr-blog). + * + * The tuner is discovered with LibUsb at a USB bus and port, like all other USB tuners, however librtlsdr owns the + * USB device, its transfers, and the embedded tuner chip. librtlsdr opens devices by index, so the controller + * finds the index of the device at this controller's bus and port by its USB serial string. */ -public class RTL2832TunerController extends USBTunerController +public class RTL2832TunerController extends TunerController { private static final Logger mLog = LoggerFactory.getLogger(RTL2832TunerController.class); - - public static final int TWO_TO_22_POWER = 4194304; - public static final int USB_TRANSFER_BUFFER_SIZE = 65_536; - public static final byte CONTROL_ENDPOINT_IN = (byte) (LibUsb.ENDPOINT_IN | LibUsb.REQUEST_TYPE_VENDOR); - public static final byte CONTROL_ENDPOINT_OUT = (byte) (LibUsb.ENDPOINT_OUT | LibUsb.REQUEST_TYPE_VENDOR); - public static final long TIMEOUT_US = 1000000l; //uSeconds - public static final byte REQUEST_ZERO = (byte) 0; - public static final byte EEPROM_ADDRESS = (byte) 0xA0; - public static final byte[] FIR_FILTER_COEFFICIENTS = {(byte) 0xCA, (byte) 0xDC, (byte) 0xD7, (byte) 0xD8, - (byte) 0xE0, (byte) 0xF2, (byte) 0x0E, (byte) 0x35, (byte) 0x06, (byte) 0x50, (byte) 0x9C, (byte) 0x0D, - (byte) 0x71, (byte) 0x11, (byte) 0x14, (byte) 0x71, (byte) 0x74, (byte) 0x19, (byte) 0x41, (byte) 0xA5}; public static final SampleRate DEFAULT_SAMPLE_RATE = SampleRate.RATE_2_400MHZ; - private SampleRate mSampleRate = DEFAULT_SAMPLE_RATE; - private INativeBufferFactory mNativeBufferFactory = new ByteNativeBufferFactory(); - private int mOscillatorFrequency = 28800000; //28.8 MHz + public static final long FREQUENCY_DEFAULT = 101_100_000; + //Bytes per USB transfer, each containing 32,768 complex samples of interleaved unsigned 8-bit I/Q + private static final int USB_TRANSFER_BUFFER_SIZE = 65_536; + private static final int USB_TRANSFER_BUFFER_COUNT = 16; + private static final int EEPROM_SIZE = 256; + private static final int LIBUSB_ERROR_PIPE = -9; + private static final int CONTROL_ATTEMPTS = 3; + //Total IF gain values supported by the E4000 IF gain stages 1-6, in dB + private static final int[][] E4K_IF_STAGE_GAINS = {{-3, 6}, {0, 3, 6, 9}, {0, 3, 6, 9}, {0, 1, 2}, + {3, 6, 9, 12, 15}, {3, 6, 9, 12, 15}}; + public static final int E4K_IF_GAIN_MINIMUM = 3; + /** + * The IF gain stages total up to 56 dB, but the E4000 IF output saturates at about 40 dB of total IF gain, even + * with the minimum LNA gain. Above that, the output level stays flat and then falls, by about 20 dB with all stages + * at maximum, so the IF gain is limited to 42 dB. + */ + public static final int E4K_IF_GAIN_MAXIMUM = 42; + + private final int mBus; + private final String mPortAddress; + private final ByteNativeBufferFactory mNativeBufferFactory = new ByteNativeBufferFactory(); + private RtlSdrDevice mDevice; private Descriptor mDescriptor; - private EmbeddedTuner mEmbeddedTuner; - private long mTunedFrequency = 0; + private TunerType mTunerType = TunerType.UNKNOWN; + private int mUsbSerialNumberIndex = 0; + private SampleRate mSampleRate = DEFAULT_SAMPLE_RATE; + private int[] mTunerGains = new int[0]; private boolean mBiasTEnabled = false; + private boolean mStreaming = false; /** - * Abstract tuner controller device. Use the static gettype() method - * to determine the tuner type, and construct the corresponding child - * tuner controller class for that tuner type. + * Constructs an instance + * @param bus usb + * @param portAddress usb + * @param tunerErrorListener to receive error notifications from this controller */ public RTL2832TunerController(int bus, String portAddress, ITunerErrorListener tunerErrorListener) { - super(bus, portAddress, tunerErrorListener); - } - - @Override - protected INativeBufferFactory getNativeBufferFactory() - { - return mNativeBufferFactory; - } - - @Override - protected int getTransferBufferSize() - { - return USB_TRANSFER_BUFFER_SIZE; + super(tunerErrorListener); + mBus = bus; + mPortAddress = portAddress; } @Override public int getBufferSampleCount() { - return getTransferBufferSize() / 2; - } - - /** - * The embedded tuner used with this RTL2832 USB tuner - */ - public EmbeddedTuner getEmbeddedTuner() - { - return mEmbeddedTuner; - } - - public boolean hasEmbeddedTuner() - { - return mEmbeddedTuner != null; - } - - /** - * Sets the tuned frequency in the embedded tuner - * @param frequency in Hertz - * @throws SourceException if there is an error - */ - @Override - public void setTunedFrequency(long frequency) throws SourceException - { - getEmbeddedTuner().setTunedFrequency(frequency); - mTunedFrequency = frequency; + return USB_TRANSFER_BUFFER_SIZE / 2; } @Override - public long getTunedFrequency() throws SourceException + public void start() throws SourceException { - return mTunedFrequency; + int index = findDeviceIndex(); + + getLock().lock(); + + try + { + mDevice = RtlSdr.open(index); + mDescriptor = readDescriptor(mDevice); + mTunerType = getTunerType(mDevice.tunerType()); + + switch(mTunerType) + { + case ELONICS_E4000: + configureTunerLimits(E4KTunerConfiguration.MINIMUM_TUNABLE_FREQUENCY_HZ, + E4KTunerConfiguration.MAXIMUM_TUNABLE_FREQUENCY_HZ, + E4KTunerConfiguration.DC_SPIKE_AVOID_BUFFER, E4KTunerConfiguration.USABLE_BANDWIDTH_PERCENT); + break; + case FCI_FC2580: + configureTunerLimits(FC2580TunerConfiguration.MINIMUM_TUNABLE_FREQUENCY_HZ, + FC2580TunerConfiguration.MAXIMUM_TUNABLE_FREQUENCY_HZ, + FC2580TunerConfiguration.DC_SPIKE_AVOID_BUFFER, FC2580TunerConfiguration.USABLE_BANDWIDTH_PERCENT); + break; + case FITIPOWER_FC0012: + configureTunerLimits(FC0012TunerConfiguration.MINIMUM_TUNABLE_FREQUENCY_HZ, + FC0012TunerConfiguration.MAXIMUM_TUNABLE_FREQUENCY_HZ, + FC0012TunerConfiguration.DC_SPIKE_AVOID_BUFFER, FC0012TunerConfiguration.USABLE_BANDWIDTH_PERCENT); + break; + case FITIPOWER_FC0013: + configureTunerLimits(FC0013TunerConfiguration.MINIMUM_TUNABLE_FREQUENCY_HZ, + FC0013TunerConfiguration.MAXIMUM_TUNABLE_FREQUENCY_HZ, + FC0013TunerConfiguration.DC_SPIKE_AVOID_BUFFER, FC0013TunerConfiguration.USABLE_BANDWIDTH_PERCENT); + break; + case RAFAELMICRO_R820T: + case RAFAELMICRO_R828D: + configureTunerLimits(R8xTunerConfiguration.MINIMUM_TUNABLE_FREQUENCY_HZ, + R8xTunerConfiguration.MAXIMUM_TUNABLE_FREQUENCY_HZ, + R8xTunerConfiguration.DC_SPIKE_AVOID_BUFFER, R8xTunerConfiguration.USABLE_BANDWIDTH_PERCENT); + break; + default: + throw new SourceException("Unsupported RTL-2832 embedded tuner type: " + mDevice.tunerType()); + } + + mTunerGains = mDevice.tunerGains(); + + //Frequency correction is applied by the frequency controller and the channelizer, not by librtlsdr + mDevice.setFrequencyCorrection(0); + //Tune before setting the sample rate, otherwise librtlsdr retunes to 0 Hz when it sets the sample rate + setFrequency(FREQUENCY_DEFAULT); + setSampleRate(DEFAULT_SAMPLE_RATE); + } + catch(RtlSdrException | SourceException | RuntimeException | LinkageError e) + { + closeDevice(mDevice); + mDevice = null; + mTunerType = TunerType.UNKNOWN; + throw new SourceException("Can't start RTL-2832 tuner - " + e.getMessage(), e); + } + finally + { + getLock().unlock(); + } + } + + @Override + public void stop() + { + RtlSdrDevice device; + + getLock().lock(); + + try + { + device = mDevice; + mDevice = null; + mStreaming = false; + } + finally + { + getLock().unlock(); + } + + mNativeBufferBroadcaster.clear(); + closeDevice(device); + } + + /** + * Applies the frequency limits and usable bandwidth for the embedded tuner type. + */ + private void configureTunerLimits(long minimum, long maximum, int dcSpikeHalfBandwidth, double usableBandwidth) + { + setMinimumFrequency(minimum); + setMaximumFrequency(maximum); + setMiddleUnusableHalfBandwidth(dcSpikeHalfBandwidth); + setUsableBandwidthPercentage(usableBandwidth); + } + + /** + * Closes the device, which also stops streaming. The bias-T is turned off first, since librtlsdr leaves the + * GPIO state unchanged when the device is closed. + * @param device to close, or null + */ + private void closeDevice(RtlSdrDevice device) + { + if(device != null) + { + try + { + device.stopRx(); + device.setBiasTee(false); + } + catch(RtlSdrException | RuntimeException e) + { + mLog.error("Error turning off RTL-2832 bias-T", e); + } + + try + { + device.close(); + } + catch(RtlSdrException | RuntimeException e) + { + mLog.error("Error closing RTL-2832 device", e); + } + } + } + + /** + * Maps the librtlsdr tuner type to the sdrtrunk tuner type. + */ + private static TunerType getTunerType(io.github.dsheirer.rtlsdr.Tuner tuner) + { + return switch(tuner) + { + case E4000 -> TunerType.ELONICS_E4000; + case FC0012 -> TunerType.FITIPOWER_FC0012; + case FC0013 -> TunerType.FITIPOWER_FC0013; + case FC2580 -> TunerType.FCI_FC2580; + case R820T -> TunerType.RAFAELMICRO_R820T; + case R828D -> TunerType.RAFAELMICRO_R828D; + default -> TunerType.UNKNOWN; + }; + } + + /** + * Reads the 256-byte EEPROM descriptor. + * @return descriptor or null if the EEPROM can't be read + */ + private static Descriptor readDescriptor(RtlSdrDevice device) + { + try + { + return new Descriptor(device.readEeprom(0, EEPROM_SIZE)); + } + catch(RtlSdrException | RuntimeException e) + { + mLog.error("Error reading the RTL-2832 EEPROM device descriptor", e); + } + + return null; + } + + /** + * Finds the librtlsdr index of the device at this controller's USB bus and port. + * + * librtlsdr enumerates devices in the order of the libusb device list and doesn't expose the bus or port of a + * device, so the device is matched by its USB serial string. When more than one device has the same serial + * string (some dongles share a serial of 00000001), the device is matched by its position among the devices + * with that serial. A device without a readable serial is matched by its position among all RTL-2832 devices. + * + * @return device index for librtlsdr + * @throws SourceException if the device isn't found or can't be accessed. + */ + private int findDeviceIndex() throws SourceException + { + UsbIdentity identity = readUsbIdentity(); + List devices = RtlSdr.devices(); + + if(devices.isEmpty()) + { + throw new SourceException("librtlsdr didn't find any RTL-2832 devices"); + } + + if(identity.serial() != null && !identity.serial().isEmpty()) + { + List matches = devices.stream().filter(d -> identity.serial().equals(d.usbStrings().serial())) + .toList(); + + if(identity.serialPosition() < matches.size()) + { + return matches.get(identity.serialPosition()).index(); + } + } + + if(identity.position() < devices.size()) + { + return devices.get(identity.position()).index(); + } + + throw new SourceException("Couldn't find the librtlsdr device for USB bus [" + mBus + "] port [" + + mPortAddress + "]"); + } + + /** + * USB identity of this controller's device: its serial string, its position among the RTL-2832 devices with the + * same serial string, and its position among all RTL-2832 devices, in libusb device list order. + */ + private record UsbIdentity(String serial, int serialPosition, int position) {} + + /** + * Reads the USB identity of the device at this controller's USB bus and port. Devices are closed again before + * returning so that librtlsdr can open them. + */ + private UsbIdentity readUsbIdentity() throws SourceException + { + Context context = new Context(); + int status = LibUsb.init(context); + + if(status != LibUsb.SUCCESS) + { + throw new SourceException("Can't initialize libusb library - " + LibUsb.errorName(status)); + } + + try + { + DeviceList deviceList = new DeviceList(); + int count = LibUsb.getDeviceList(context, deviceList); + + if(count < 0) + { + throw new SourceException("Can't get the USB device list - " + LibUsb.errorName(count)); + } + + try + { + //RTL-2832 devices that precede this controller's device in the device list + List earlierDevices = new ArrayList<>(); + List earlierDescriptors = new ArrayList<>(); + + for(Device device : deviceList) + { + DeviceDescriptor descriptor = new DeviceDescriptor(); + + if(LibUsb.getDeviceDescriptor(device, descriptor) != LibUsb.SUCCESS || + TunerClass.lookup(descriptor.idVendor(), descriptor.idProduct()) != TunerClass.RTL2832) + { + continue; + } + + if(LibUsb.getBusNumber(device) == mBus && LibUsb.getPortNumber(device) > 0 && + mPortAddress != null && mPortAddress.equals(TunerManager.getPortAddress(device))) + { + mUsbSerialNumberIndex = 0xFF & descriptor.iSerialNumber(); + String serial = readSerial(device, descriptor, true); + int serialPosition = 0; + + if(serial != null) + { + for(int x = 0; x < earlierDevices.size(); x++) + { + if(serial.equals(readSerial(earlierDevices.get(x), earlierDescriptors.get(x), false))) + { + serialPosition++; + } + } + } + + return new UsbIdentity(serial, serialPosition, earlierDevices.size()); + } + + earlierDevices.add(device); + earlierDescriptors.add(descriptor); + } + } + finally + { + LibUsb.freeDeviceList(deviceList, true); + } + } + finally + { + LibUsb.exit(context); + } + + throw new SourceException("Couldn't find USB device at bus [" + mBus + "] port [" + mPortAddress + "]"); + } + + /** + * Reads the device's USB serial string. + * @param device to read + * @param descriptor for the device + * @param required true to throw an exception when the device can't be opened, false to return null + * @return serial or null if the device doesn't have a serial string + * @throws SourceException if the device is required and it can't be opened. + */ + private static String readSerial(Device device, DeviceDescriptor descriptor, boolean required) throws SourceException + { + if(descriptor.iSerialNumber() == 0) + { + return null; + } + + DeviceHandle handle = new DeviceHandle(); + int status = LibUsb.open(device, handle); + + if(status != LibUsb.SUCCESS) + { + if(!required) + { + return null; + } + + if(status == LibUsb.ERROR_ACCESS) + { + mLog.error("Access to USB tuner denied - (windows) reinstall zadig driver or (linux) check udev rules"); + throw new SourceException("access denied - if using linux, install udev rules"); + } + + mLog.error("Can't open USB tuner - check driver or Linux udev rules"); + throw new SourceException("Can't open USB tuner - reinstall driver? - " + LibUsb.errorName(status)); + } + + try + { + return LibUsb.getStringDescriptor(handle, descriptor.iSerialNumber()); + } + catch(RuntimeException e) + { + return null; + } + finally + { + LibUsb.close(handle); + } + } + + /** + * Operation that requires the device. + */ + private interface DeviceOperation + { + void execute(RtlSdrDevice device) throws RtlSdrException; + } + + /** + * Executes the operation under the controller lock so that the device can't be closed while it is in use. The + * operation is ignored when the device isn't open, which allows for calls that arrive during tuner shutdown. + * + * While the device is streaming, the RTL2832 stalls the first vendor OUT control transfer that follows an idle + * period of more than about 100-300 ms (LIBUSB_ERROR_PIPE). The librtlsdr bundled with jRTL 1.4.0.1 and later + * reads a register before those writes, which prevents the stall. Operations that still fail with a pipe error + * are retried as a backstop, since the librtlsdr control functions set the complete state on each call. + * + * @param operation to execute + * @throws SourceException if the operation fails + */ + private void execute(DeviceOperation operation) throws SourceException + { + getLock().lock(); + + try + { + if(mDevice != null) + { + for(int attempt = 1; ; attempt++) + { + try + { + operation.execute(mDevice); + return; + } + catch(RtlSdrException e) + { + if(e.code() != LIBUSB_ERROR_PIPE || attempt >= CONTROL_ATTEMPTS) + { + throw e; + } + } + } + } + } + catch(RtlSdrException | RuntimeException e) + { + throw new SourceException(e.getMessage(), e); + } + finally + { + getLock().unlock(); + } + } + + /** + * Receives samples on the librtlsdr thread and dispatches them to the buffer listeners. Exceptions are + * contained here since an exception reaching the library would stop the sample stream. + * @param buffer from the library, which is only valid until this method returns. + * @return true to continue streaming + */ + private boolean receive(SampleBuffer buffer) + { + try + { + //The buffer factory copies the samples from native memory + broadcast(mNativeBufferFactory.getBuffer(buffer.samples().asByteBuffer(), System.currentTimeMillis())); + } + catch(RuntimeException e) + { + mLog.error("Error dispatching RTL-2832 sample buffer", e); + } + + return true; + } + + /** + * Starts the sample stream if it's not already started. + */ + private void startStreaming() throws SourceException + { + if(!mStreaming) + { + execute(device -> device.startRx(this::receive, USB_TRANSFER_BUFFER_COUNT, USB_TRANSFER_BUFFER_SIZE)); + mStreaming = mDevice != null; + } + } + + /** + * Stops the sample stream if it's started. + */ + private void stopStreaming() throws SourceException + { + if(mStreaming) + { + mStreaming = false; + execute(RtlSdrDevice::stopRx); + } + } + + /** + * Adds the IQ buffer listener and automatically starts streaming, if not already started. + */ + @Override + public void addBufferListener(Listener listener) + { + getLock().lock(); + + try + { + if(mDevice != null) + { + super.addBufferListener(listener); + startStreaming(); + } + } + catch(SourceException se) + { + mLog.error("Error starting streaming on RTL-2832 tuner", se); + } + finally + { + getLock().unlock(); + } + } + + /** + * Removes the IQ buffer listener and stops streaming if there are no more listeners. + */ + @Override + public void removeBufferListener(Listener listener) + { + getLock().lock(); + + try + { + super.removeBufferListener(listener); + + if(!hasBufferListeners()) + { + stopStreaming(); + } + } + catch(SourceException se) + { + mLog.error("Error stopping streaming on RTL-2832 tuner", se); + } + finally + { + getLock().unlock(); + } } @Override @@ -136,128 +611,230 @@ public class RTL2832TunerController extends USBTunerController if(tunerConfig instanceof RTL2832TunerConfiguration rtl) { setSampleRate(rtl.getSampleRate()); - getEmbeddedTuner().apply(rtl); + setBiasT(rtl.isBiasT()); + setGain(rtl.isAGC(), rtl.getGain()); + + if(rtl instanceof E4KTunerConfiguration e4k) + { + setE4KIFGain(e4k.getIFGainDb()); + } } } /** - * Executes additional device startup actions after being commanded by the parent USB startup actions. - * @throws SourceException if there is an error/issue making this device is unusable + * Sets the tuned frequency + * @param frequency in Hertz + * @throws SourceException if there is an error */ @Override - protected void deviceStart() throws SourceException + public void setTunedFrequency(long frequency) throws SourceException { - //Perform dummy write to see if device needs reset - boolean resetRequired = false; + execute(device -> device.setCenterFrequency(frequency)); + } + @Override + public long getTunedFrequency() throws SourceException + { + return mFrequencyController.getTunedFrequency(); + } + + /** + * Sets the enabled state of the bias-t + * @param enabled true to turn-on the bias-t or false to turn-off the bias-t. + */ + public void setBiasT(boolean enabled) + { try { - writeRegister(Block.USB, Address.USB_SYSCTL, 0x09, 1); + execute(device -> device.setBiasTee(enabled)); + mBiasTEnabled = enabled; } - catch(LibUsbException lue) + catch(SourceException se) { - if(lue.getErrorCode() < 0) + mLog.error("Error setting RTL-2832 bias-T to " + (enabled ? "on" : "off"), se); + } + } + + /** + * Indicates if the bias-t is enabled. + * @return true if enabled. + */ + public boolean isBiasT() + { + return mBiasTEnabled; + } + + /** + * Gains supported by the embedded tuner, in tenths of a dB from lowest to highest. Available once the tuner has + * been started. + */ + public int[] getTunerGains() + { + return mTunerGains; + } + + /** + * Supported tuner gain that is closest to the requested gain. + * @param gain in tenths of a dB + * @return closest supported gain, or the requested gain if the tuner doesn't report its gains. + */ + public int getClosestTunerGain(int gain) + { + int closest = gain; + int distance = Integer.MAX_VALUE; + + for(int supported : mTunerGains) + { + if(Math.abs(supported - gain) < distance) { - resetRequired = true; - } - else - { - mLog.error("Error performing test write to RTL2832 device - " + LibUsb.errorName(lue.getErrorCode())); + closest = supported; + distance = Math.abs(supported - gain); } } - if(resetRequired) - { - int status = LibUsb.resetDevice(getDeviceHandle()); - mLog.info("Resetting device - status: " + LibUsb.errorName(status)); + return closest; + } - try + /** + * Sets the tuner gain mode and manual gain. + * @param agc true for the tuner's automatic gain control, false for manual gain + * @param gain in tenths of a dB, applied when AGC is off. The gain is rounded to the closest supported gain, + * since the librtlsdr E4000 driver rejects gains that aren't in its gain table. + * @throws SourceException if the gain can't be set + */ + public void setGain(boolean agc, int gain) throws SourceException + { + if(!hasGainControl()) + { + return; + } + + boolean tunerAgc = agc && hasTunerAGC(); + int supported = getClosestTunerGain(gain); + + execute(device -> { + device.setTunerGainMode(!tunerAgc); + + if(!tunerAgc) { - writeRegister(Block.USB, Address.USB_SYSCTL, 0x09, 1); + device.setTunerGain(supported); } - catch(LibUsbException lue) + }); + } + + /** + * Indicates if the embedded tuner supports manual gain control. The librtlsdr FC2580 driver doesn't, and the + * tuner always uses its internal AGC. + */ + public boolean hasGainControl() + { + return mTunerGains != null && mTunerGains.length > 1; + } + + /** + * Indicates if the embedded tuner supports switching to its automatic gain control. The librtlsdr FC0012 and + * FC2580 drivers ignore the gain mode, so the FC0012 always uses manual gain. + */ + public boolean hasTunerAGC() + { + return hasGainControl() && mTunerType != TunerType.FITIPOWER_FC0012; + } + + /** + * Sets the E4000 total IF gain, distributed across the six IF gain stages. Ignored for other tuner types. + * @param gain in dB + * @throws SourceException if the gain can't be set + */ + public void setE4KIFGain(int gain) throws SourceException + { + if(mTunerType == TunerType.ELONICS_E4000) + { + int[] stages = getE4KIFStageGains(Math.clamp(gain, E4K_IF_GAIN_MINIMUM, E4K_IF_GAIN_MAXIMUM)); + + execute(device -> { + for(int stage = 0; stage < stages.length; stage++) + { + device.setTunerIfGain(stage + 1, stages[stage] * 10); + } + }); + } + } + + /** + * Distributes a total E4000 IF gain across the six IF gain stages, choosing the combination closest to the + * requested gain and, among equally close combinations, the one with the most gain in the earliest stages for the + * best noise figure. + * @param gain total in dB + * @return gain for stages 1-6 in dB + */ + public static int[] getE4KIFStageGains(int gain) + { + int[] best = null; + int bestDistance = Integer.MAX_VALUE; + int[] indices = new int[E4K_IF_STAGE_GAINS.length]; + + while(true) + { + int[] candidate = new int[indices.length]; + int total = 0; + + for(int stage = 0; stage < indices.length; stage++) { - mLog.error("Couldnt' reset RTL2832 device - setting error"); - throw new SourceException("unable to reset USB device - " + LibUsb.errorName(status)); + candidate[stage] = E4K_IF_STAGE_GAINS[stage][indices[stage]]; + total += candidate[stage]; } - } + int distance = Math.abs(total - gain); - byte[] eeprom = null; - - try - { - /* Read the contents of the 256-byte EEPROM */ - eeprom = readEEPROM((short) 0, 256); - } - catch(Exception e) - { - mLog.error("error while reading the EEPROM device descriptor", e); - } - - try - { - mDescriptor = new Descriptor(eeprom); - - if(eeprom == null) + if(distance < bestDistance || (distance == bestDistance && Arrays.compare(candidate, best) > 0)) { - mLog.error("eeprom byte array was null - constructed empty descriptor object"); + best = candidate; + bestDistance = distance; } - } - catch(Exception e) - { - mLog.error("error while constructing device descriptor using descriptor byte array " + - (eeprom == null ? "[null]" : Arrays.toString(eeprom)), e); - } - initBaseband(); + //Advance to the next combination + int stage = indices.length - 1; - TunerType tunerType = identifyTunerType(); + while(stage >= 0 && ++indices[stage] == E4K_IF_STAGE_GAINS[stage].length) + { + indices[stage] = 0; + stage--; + } - if(tunerType == TunerType.UNKNOWN) - { - throw new SourceException("Unrecognized RTL-2832 embedded tuner type: " + tunerType); - } - - mEmbeddedTuner = TunerFactory.getRtlEmbeddedTuner(tunerType, new ControllerAdapter(this)); - - try - { - enableI2CRepeater(true); - getEmbeddedTuner().initTuner(); - enableI2CRepeater(false); - } - catch(UsbException ue) - { - throw new SourceException("Unable to initialize " + tunerType, ue); - } - - setMinimumFrequency(mEmbeddedTuner.getMinimumFrequencySupported()); - setMaximumFrequency(mEmbeddedTuner.getMaximumFrequencySupported()); - setMiddleUnusableHalfBandwidth(mEmbeddedTuner.getDcSpikeHalfBandwidth()); - setUsableBandwidthPercentage(mEmbeddedTuner.getUsableBandwidthPercent()); - - try - { - setSampleRate(DEFAULT_SAMPLE_RATE); - } - catch(Exception e) - { - throw new SourceException("RTL2832 Tuner Controller - couldn't set default sample rate", e); + if(stage < 0) + { + return best; + } } } @Override - protected void deviceStop() + public double getCurrentSampleRate() { + return mSampleRate.getRate(); + } + + /** + * Sets the sampling rate of the RTL2832. librtlsdr also sets the tuner's bandwidth for the sample rate. + * @param sampleRate to apply + * @throws SourceException if the sample rate can't be set + */ + public void setSampleRate(SampleRate sampleRate) throws SourceException + { + getLock().lock(); + try { - deinitBaseband(); + execute(device -> device.setSampleRate(sampleRate.getRate())); + mSampleRate = sampleRate; + mFrequencyController.setSampleRate(sampleRate.getRate()); + + //Updates native buffer factory so that sample buffers can be accurately timestamped + mNativeBufferFactory.setSamplesPerMillisecond(sampleRate.getRate() / 1000.0f); } - catch(Exception e) + finally { -// mLog.error("Error on device stop", e); - //No-op + getLock().unlock(); } } @@ -277,40 +854,6 @@ public class RTL2832TunerController extends USBTunerController return null; } - /** - * Sets the enabled state of the bias-t - * @param enabled true to turn-on the bias-t or false to turn-off the bias-t. - */ - public void setBiasT(boolean enabled) - { - setGPIOOutput((byte)0x01); - setGPIOBit((byte)0x01, enabled); - mBiasTEnabled = enabled; - } - - /** - * Indicates if the bias-t is enabled. - * @return true if enabled. - */ - public boolean isBiasT() - { - return mBiasTEnabled; - } - - private void setIFFrequency(int frequency) throws LibUsbException - { - long ifFrequency = ((long) TWO_TO_22_POWER * (long) frequency) / (long) mOscillatorFrequency * -1; - - /* Write byte 2 (high) */ - writeDemodRegister(Page.ONE, (short) 0x19, (short) (Long.rotateRight(ifFrequency, 16) & 0x3F), 1); - - /* Write byte 1 (middle) */ - writeDemodRegister(Page.ONE, (short) 0x1A, (short) (Long.rotateRight(ifFrequency, 8) & 0xFF), 1); - - /* Write byte 0 (low) */ - writeDemodRegister(Page.ONE, (short) 0x1B, (short) (ifFrequency & 0xFF), 1); - } - /** * Provides a unique identifier to use in distinctly identifying this tuner from among other tuners of the same * type, so that we can fetch a tuner configuration from the settings manager for this specific tuner. @@ -323,7 +866,7 @@ public class RTL2832TunerController extends USBTunerController { if(mDescriptor.hasSerial()) { - if(hasEmbeddedTuner()) + if(mTunerType != TunerType.UNKNOWN) { return TunerClass.RTL2832 + "/" + getTunerType().getLabel() + " " + mDescriptor.getSerial(); } @@ -339,885 +882,52 @@ public class RTL2832TunerController extends USBTunerController } else { - int serial = (0xFF & getDeviceDescriptor().iSerialNumber()); - return TunerClass.RTL2832 + " " + serial; + return TunerClass.RTL2832 + " " + mUsbSerialNumberIndex; } } - /** - * Sets the filters for the sample rate - * @param sampleRate to setup - * @throws SourceException if there is an error - */ - public void setSampleRateFilters(int sampleRate) throws SourceException - { - getEmbeddedTuner().setSampleRateFilters(sampleRate); - } - /** * Identifies the embedded tuner type */ + @Override public TunerType getTunerType() { - if(getEmbeddedTuner() != null) - { - return getEmbeddedTuner().getTunerType(); - } - - return TunerType.UNKNOWN; + return mTunerType; } /** - * Identifies the embedded tuner type - * @return tuner type - * @throws SourceException if there is an error - */ - private TunerType identifyTunerType() throws SourceException - { - TunerType tunerClass = TunerType.UNKNOWN; - - try - { - enableI2CRepeater(true); - - boolean controlI2CRepeater = false; - - /* Test for each tuner type until we find the correct one */ - if(isTuner(TunerTypeCheck.E4K, controlI2CRepeater)) - { - tunerClass = TunerType.ELONICS_E4000; - } - else if(isTuner(TunerTypeCheck.R820T, controlI2CRepeater)) - { - tunerClass = TunerType.RAFAELMICRO_R820T; - } - else if(isTuner(TunerTypeCheck.FC0013, controlI2CRepeater)) - { - tunerClass = TunerType.FITIPOWER_FC0013; - } - else if(isTuner(TunerTypeCheck.R828D, controlI2CRepeater)) - { - tunerClass = TunerType.RAFAELMICRO_R828D; - } - else if(isTuner(TunerTypeCheck.FC2580, controlI2CRepeater)) - { - tunerClass = TunerType.FCI_FC2580; - } - else if(isTuner(TunerTypeCheck.FC0012, controlI2CRepeater)) - { - tunerClass = TunerType.FITIPOWER_FC0012; - } - - enableI2CRepeater(false); - } - catch(Exception e) - { - mLog.error("error while determining tuner type", e); - } - - return tunerClass; - } - - @Override - protected void prepareStreaming() - { - resetUSBBuffer(); - } - - /** - * Resets the USB buffer - * @throws LibUsbException if there is an error - */ - public void resetUSBBuffer() throws LibUsbException - { - writeRegister(Block.USB, Address.USB_EPA_CTL, 0x1002, 2); - writeRegister(Block.USB, Address.USB_EPA_CTL, 0x0000, 2); - } - - /** - * Initializes the RF baseband system - * @throws LibUsbException if there is an error - */ - public void initBaseband() throws LibUsbException - { - /* Initialize USB */ - writeRegister(Block.USB, Address.USB_SYSCTL, 0x09, 1); - writeRegister(Block.USB, Address.USB_EPA_MAXPKT, 0x0002, 2); - writeRegister(Block.USB, Address.USB_EPA_CTL, 0x1002, 2); - - /* Power on demod */ - writeRegister(Block.SYS, Address.DEMOD_CTL_1, 0x22, 1); - writeRegister(Block.SYS, Address.DEMOD_CTL, 0xE8, 1); - - /* Reset demod */ - writeDemodRegister(Page.ONE, (short) 0x01, 0x14, 1); //Bit 3 = soft reset - writeDemodRegister(Page.ONE, (short) 0x01, 0x10, 1); - - /* Disable spectrum inversion and adjacent channel rejection */ - writeDemodRegister(Page.ONE, (short) 0x15, 0x00, 1); - writeDemodRegister(Page.ONE, (short) 0x16, 0x0000, 2); - - /* Clear DDC shift and IF frequency registers */ - writeDemodRegister(Page.ONE, (short) 0x16, 0x00, 1); - writeDemodRegister(Page.ONE, (short) 0x17, 0x00, 1); - writeDemodRegister(Page.ONE, (short) 0x18, 0x00, 1); - writeDemodRegister(Page.ONE, (short) 0x19, 0x00, 1); - writeDemodRegister(Page.ONE, (short) 0x1A, 0x00, 1); - writeDemodRegister(Page.ONE, (short) 0x1B, 0x00, 1); - - /* Set FIR coefficients */ - for(int x = 0; x < FIR_FILTER_COEFFICIENTS.length; x++) - { - writeDemodRegister(Page.ONE, (short) (0x1C + x), FIR_FILTER_COEFFICIENTS[x], 1); - } - - /* Enable SDR mode, disable DAGC (bit 5) */ - writeDemodRegister(Page.ZERO, (short) 0x19, 0x05, 1); - - /* Init FSM state-holding register */ - writeDemodRegister(Page.ONE, (short) 0x93, 0xF0, 1); - writeDemodRegister(Page.ONE, (short) 0x94, 0x0F, 1); - - /* Disable AGC (en_dagc, bit 0) (seems to have no effect) */ - writeDemodRegister(Page.ONE, (short) 0x11, 0x00, 1); - - /* Disable RF and IF AGC loop */ - writeDemodRegister(Page.ONE, (short) 0x04, 0x00, 1); - - /* Disable PID filter */ - writeDemodRegister(Page.ZERO, (short) 0x61, 0x60, 1); - - /* opt_adc_iq = 0, default ADC_I/ADC_Q datapath */ - writeDemodRegister(Page.ZERO, (short) 0x06, 0x80, 1); - - /* Enable Zero-if mode (en_bbin bit), - * DC cancellation (en_dc_est), - * IQ estimation/compensation (en_iq_comp, en_iq_est) */ - writeDemodRegister(Page.ONE, (short) 0xB1, 0x1B, 1); - - /* Disable 4.096 MHz clock output on pin TP_CK0 */ - writeDemodRegister(Page.ZERO, (short) 0x0D, 0x83, 1); - } - - /** - * Turns off the RF baseband system - * @throws IllegalArgumentException never - * @throws UsbDisconnectedException of the device is no longer accessible - */ - private void deinitBaseband() throws IllegalArgumentException, UsbDisconnectedException - { - writeRegister(Block.SYS, Address.DEMOD_CTL, 0x20, 1); - } - - /** - * Sets the General Purpose Input/Output (GPIO) register bit - * - * @param bitMask - bit mask with one for targeted register bits and zero for the non-targeted register bits - * @param enabled - true to set the bit and false to clear the bit - * @throws UsbDisconnectedException - if the tuner device is disconnected - * @throws UsbException - if there is a USB error while communicating with the device - */ - private void setGPIOBit(byte bitMask, boolean enabled) throws LibUsbException - { - //Get current register value - int value = readRegister(Block.SYS, Address.GPO, 1); - - //Update the masked bits - if(enabled) - { - value |= bitMask; - } - else - { - value &= ~bitMask; - } - - //Write the change back to the device - writeRegister(Block.SYS, Address.GPO, value, 1); - } - - /** - * Enables GPIO Output - * - * @param bitMask - mask containing one bit value in targeted bit field(s) - * @throws UsbDisconnectedException - * @throws UsbException - */ - private void setGPIOOutput(byte bitMask) throws LibUsbException - { - //Get current register value - int value = readRegister(Block.SYS, Address.GPD, 1); - - //Mask the value and rewrite it - writeRegister(Block.SYS, Address.GPO, value & ~bitMask, 1); - - //Get current register value - value = readRegister(Block.SYS, Address.GPOE, 1); - - //Mask the value and rewrite it - writeRegister(Block.SYS, Address.GPOE, value | bitMask, 1); - } - - /** - * Enables or disables the I2C repeater - * @param enabled to turn I2C on or off - * @throws LibUsbException if there is an error - */ - private void enableI2CRepeater(boolean enabled) throws LibUsbException - { - short address = 1; - int value = (enabled ? 0x18 : 0x10); - - boolean isEnabledAlready = isI2CRepeaterEnabled(); - if (isEnabledAlready != enabled) { - // mLog.info("trying to " + (enabled ? "enable" : "disable") + " i2c repeater, which is currently " + (isEnabledAlready ? "enabled" : "disabled")); - writeDemodRegister(Page.ONE, address, value, 1); - } - } - - /** - * Indicates if the I2C repeater is currently enabled or turned on. - */ - private boolean isI2CRepeaterEnabled() - { - return readDemodRegister(Page.ONE, (short) 0x1, 1) == 0x18; - } - - /** - * Reads the I2C register - * @param i2CAddress address of the register - * @param i2CRegister register - * @param controlI2CRepeater set to true to have this method turn repeater on/off for this invocation or false if - * the repeater has already been enabled. - * @return register value - * @throws LibUsbException if there is an error - */ - private int readI2CRegister(byte i2CAddress, byte i2CRegister, boolean controlI2CRepeater) throws LibUsbException - { - short address = (short) (i2CAddress & 0xFF); - ByteBuffer buffer = ByteBuffer.allocateDirect(1); - buffer.put(i2CRegister); - buffer.rewind(); - ByteBuffer data = ByteBuffer.allocateDirect(1); - - if(controlI2CRepeater) - { - enableI2CRepeater(true); - write(address, Block.I2C, buffer); - read(address, Block.I2C, data); - enableI2CRepeater(false); - } - else - { - write(address, Block.I2C, buffer); - read(address, Block.I2C, data); - } - - return (data.get() & 0xFF); - } - - /** - * Writes to the I2C register - * @param i2CAddress of the register - * @param i2CRegister register - * @param value to write - * @param controlI2CRepeater set to true to have this method turn repeater on/off for this invocation or false if - * the repeater has already been enabled. - * @throws LibUsbException if there is an error - */ - private void writeI2CRegister(byte i2CAddress, byte i2CRegister, byte value, boolean controlI2CRepeater) - throws LibUsbException - { - - short address = (short) (i2CAddress & 0xFF); - ByteBuffer buffer = ByteBuffer.allocateDirect(2); - buffer.put(i2CRegister); - buffer.put(value); - - buffer.rewind(); - - if(controlI2CRepeater) - { - enableI2CRepeater(true); - write(address, Block.I2C, buffer); - enableI2CRepeater(false); - } - else - { - write(address, Block.I2C, buffer); - } - } - - /** - * Writes to the demod register - * @param page of register - * @param address of resister - * @param value to write - * @param length of value in bytes - * @throws LibUsbException on error - */ - private void writeDemodRegister(Page page, short address, int value, int length) throws LibUsbException - { - ByteBuffer buffer = ByteBuffer.allocateDirect(length); - buffer.order(ByteOrder.BIG_ENDIAN); - - if(length == 1) - { - buffer.put((byte) (value & 0xFF)); - } - else if(length == 2) - { - buffer.putShort((short) (value & 0xFFFF)); - } - else - { - throw new IllegalArgumentException("Cannot write value greater than 16 bits to the register - length [" + - length + "]"); - } - - short index = (short) (0x10 | page.getPage()); - short newAddress = (short) (address << 8 | 0x20); - write(newAddress, index, buffer); - readDemodRegister(Page.TEN, (short) 1, length); - } - - /** - * Reads the demod register - * @param page of register - * @param address of register - * @param length of value to read in bytes - * @return value read - * @throws LibUsbException on error - */ - private int readDemodRegister(Page page, short address, int length) throws LibUsbException - { - short index = page.getPage(); - short newAddress = (short) ((address << 8) | 0x20); - - ByteBuffer buffer = ByteBuffer.allocateDirect(length); - - read(newAddress, index, buffer); - buffer.order(ByteOrder.LITTLE_ENDIAN); - - if(length == 2) - { - return (buffer.getShort() & 0xFFFF); - } - else - { - return (buffer.get() & 0xFF); - } - } - - /** - * Writes a value to the register - * @param block of register - * @param address of register - * @param value to write - * @param length of value in bytes - * @throws LibUsbException on error - */ - private void writeRegister(Block block, Address address, int value, int length) throws LibUsbException - { - ByteBuffer buffer = ByteBuffer.allocateDirect(length); - buffer.order(ByteOrder.BIG_ENDIAN); - - if(length == 1) - { - buffer.put((byte) (value & 0xFF)); - } - else if(length == 2) - { - buffer.putShort((short) value); - } - else - { - throw new IllegalArgumentException("Cannot write value greater than 16 bits to the register - length [" + - length + "]"); - } - - buffer.rewind(); - write(address.getAddress(), block, buffer); - } - - /** - * Reads a value from a register - * @param block of register - * @param address of register - * @param length of value to read - * @return value read - * @throws LibUsbException on error - */ - private int readRegister(Block block, Address address, int length) throws LibUsbException - { - ByteBuffer buffer = ByteBuffer.allocateDirect(2); - read(address.getAddress(), block, buffer); - buffer.order(ByteOrder.LITTLE_ENDIAN); - - if(length == 2) - { - return (buffer.getShort() & 0xFFFF); - } - else - { - return (buffer.get() & 0xFF); - } - } - - /** - * Writes a value - * @param address of register - * @param block of register - * @param buffer containing value to write - * @throws LibUsbException on error - */ - private void write(short address, Block block, ByteBuffer buffer) throws LibUsbException - { - write(address, block.getWriteIndex(), buffer); - } - - /** - * Performs a control type write - * @param value to write - * @param index to write value to - * @param buffer contents to write - * @throws LibUsbException if there is an error - */ - private void write(short value, short index, ByteBuffer buffer) throws LibUsbException - { - if(hasDeviceHandle()) - { - int transferred = LibUsb.controlTransfer(getDeviceHandle(), CONTROL_ENDPOINT_OUT, REQUEST_ZERO, value, - index, buffer, TIMEOUT_US); - - if(transferred < 0) - { - throw new LibUsbException("error writing byte buffer", transferred); - } - else if(transferred != buffer.capacity()) - { - throw new LibUsbException("transferred bytes [" + transferred + "] is not what was expected [" + - buffer.capacity() + "]", transferred); - } - } - else - { - throw new LibUsbException("device handle is null", LibUsb.ERROR_NO_DEVICE); - } - } - - /** - * Performs a control type read - * @param address to read - * @param index to read - * @param buffer to store read content - */ - private void read(short address, short index, ByteBuffer buffer) throws LibUsbException - { - if(isRunning()) - { - int transferred = LibUsb.controlTransfer(getDeviceHandle(), CONTROL_ENDPOINT_IN, REQUEST_ZERO, address, - index, buffer, TIMEOUT_US); - - if(transferred < 0) - { - throw new LibUsbException("read error", transferred); - } - else if(transferred != buffer.capacity()) - { - throw new LibUsbException("transferred bytes [" + transferred + "] is not what was expected [" + - buffer.capacity() + "]", transferred); - } - } - } - - /** - * Reads byte array from index at the address. - * - * @return big-endian byte array (needs to be swapped to be usable) - */ - private void read(short address, Block block, ByteBuffer buffer) throws LibUsbException - { - read(address, block.getReadIndex(), buffer); - } - - /** - * Tests if the specified tuner type is contained in the usb tuner device. - * - * @param type - tuner type to test for - * @param controlI2CRepeater - indicates if the method should control the I2C repeater independently - * @return - true if the device is the specified tuner type - */ - private boolean isTuner(TunerTypeCheck type, boolean controlI2CRepeater) - { - try - { - if(type == TunerTypeCheck.FC0012 || type == TunerTypeCheck.FC2580) - { - /* Initialize the GPIOs */ - setGPIOOutput((byte) 0x20); - - /* Reset tuner before probing */ - setGPIOBit((byte) 0x20, true); - setGPIOBit((byte) 0x20, false); - } - - int value = readI2CRegister(type.getI2CAddress(), type.getCheckAddress(), controlI2CRepeater); - - if(type == TunerTypeCheck.FC2580) - { - return ((value & 0x7F) == (type.getCheckValue() & 0xFF)); - } - else - { - return (value == (type.getCheckValue() & 0xFF)); - } - } - catch(LibUsbException e) - { - //Do nothing ... it's not the specified tuner - } - - return false; - } - - public double getCurrentSampleRate() - { - return mSampleRate.getRate(); - } - - public int getSampleRateFromTuner() throws SourceException - { - int returnRate = DEFAULT_SAMPLE_RATE.getRate(); - - getLock().lock(); - - try - { - int high = readDemodRegister(Page.ONE, (short) 0x9F, 2); - int low = readDemodRegister(Page.ONE, (short) 0xA1, 2); - int ratio = Integer.rotateLeft(high, 16) | low; - int rate = (mOscillatorFrequency * TWO_TO_22_POWER / ratio); - - SampleRate sampleRate = SampleRate.getClosest(rate); - - /* If we're not currently set to this rate, set it as the current rate */ - if(sampleRate.getRate() != rate) - { - setSampleRate(sampleRate); - returnRate = sampleRate.getRate(); - } - } - catch(Exception e) - { - throw new SourceException("RTL2832 Tuner Controller - cannot get current sample rate", e); - } - finally - { - getLock().unlock(); - } - - return returnRate; - } - - /** - * Sets the sampling rate of the RTL2832 - * @param sampleRate to apply - * @throws SourceException - */ - public void setSampleRate(SampleRate sampleRate) throws SourceException - { - getLock().lock(); - - try - { - /* Write high-order 16-bits of sample rate ratio to demod register */ - writeDemodRegister(Page.ONE, (short) 0x9F, sampleRate.getRatioHighBits(), 2); - - /* Write low-order 16-bits of sample rate ratio to demod register. - * Note: none of the defined rates have a low order ratio value, so we - * simply write a zero to the register */ - writeDemodRegister(Page.ONE, (short) 0xA1, 0, 2); - - /* Set sample rate correction to 0 */ - setSampleRateFrequencyCorrection(0); - - /* Reset the demod for the changes to take effect */ - writeDemodRegister(Page.ONE, (short) 0x01, 0x14, 1); - writeDemodRegister(Page.ONE, (short) 0x01, 0x10, 1); - - /* Apply any tuner specific sample rate filter settings */ - setSampleRateFilters(sampleRate.getRate()); - mSampleRate = sampleRate; - mFrequencyController.setSampleRate(sampleRate.getRate()); - - //Updates native buffer factory so that sample buffers can be accurately timestamped - getNativeBufferFactory().setSamplesPerMillisecond(sampleRate.getRate() / 1000.0f); - } - finally - { - getLock().unlock(); - } - } - - /** - * Determines the size of USB transfer buffers according to the sample rate - */ - private int getUSBTransferBufferSize(double sampleRate) - { - return USB_TRANSFER_BUFFER_SIZE; - } - - public void setSampleRateFrequencyCorrection(int ppm) throws SourceException - { - int offset = -ppm * TWO_TO_22_POWER / 1_000_000; - writeDemodRegister(Page.ONE, (short) 0x3F, (offset & 0xFF), 1); - writeDemodRegister(Page.ONE, (short) 0x3E, (Integer.rotateRight(offset, 8) & 0xFF), 1); - /* Test to retune controller to apply frequency correction */ - try - { - mFrequencyController.setFrequency(mFrequencyController.getFrequency()); - } - catch(Exception e) - { - throw new SourceException("Couldn't set sample rate frequency correction", e); - } - } - - public int getSampleRateFrequencyCorrection() throws UsbException - { - int high = readDemodRegister(Page.ONE, (short) 0x3E, 1); - int low = readDemodRegister(Page.ONE, (short) 0x3F, 1); - return (Integer.rotateLeft(high, 8) | low); - } - - /** - * Returns contents of the 256-byte EEPROM. The contents are as follows: - * - * 256-byte EEPROM (in hex): - * 00/01 - 2832 Signature - * 03/02 - 0BDA Vendor ID - * 05/04 - 2832 Product ID - * 06 - A5 (has serial id?) - * 07 - 16 (bit field - bit 0 = remote wakeup, bit 1 = IR enabled - * 08 - 02 or 12 - * 10/09 0310 ETX(0x03) plus label length (includes length and ETX bytes) - * 12/11 First UTF-16 character - * 14/13 Second UTF-16 character ... - * - * Label 1: vendor - * Label 2: product - * Label 3: serial - * Label 4,5 ... (user defined) - */ - public byte[] readEEPROM(short offset, int length) throws IllegalArgumentException - { - if(offset + length > 256) - { - throw new IllegalArgumentException("cannot read more than 256 bytes from EEPROM - requested read to byte [" + - (offset + length) + "]"); - } - - byte[] data = new byte[length]; - ByteBuffer buffer = ByteBuffer.allocateDirect(1); - - try - { - /* Tell the RTL-2832 to address the EEPROM */ - writeRegister(Block.I2C, Address.EEPROM, (byte) offset, 1); - } - catch(LibUsbException e) - { - mLog.error("usb error while attempting to set read address to EEPROM register, prior to reading the EEPROM " + - "device descriptor", e); - } - - for(int x = 0; x < length; x++) - { - try - { - read(EEPROM_ADDRESS, Block.I2C, buffer); - data[x] = buffer.get(); - buffer.rewind(); - } - catch(Exception e) - { - mLog.error("error while reading eeprom byte [" + x + "/" + length + "] aborting eeprom read and " + - "returning partially filled descriptor byte array", e); - x = length; - } - } - - return data; - } - - /** - * Writes a single byte to the 256-byte EEPROM using the specified offset. - * - * Note: introduce a 5 millisecond delay between each successive write to - * the EEPROM or subsequent writes may fail. - */ - public void writeEEPROMByte(byte offset, byte value) throws IllegalArgumentException, UsbDisconnectedException - { - if(offset < 0 || offset > 255) - { - throw new IllegalArgumentException("RTL2832 Tuner Controller - EEPROM offset must be within range of 0 - 255"); - } - - int offsetAndValue = Integer.rotateLeft((0xFF & offset), 8) | (0xFF & value); - writeRegister(Block.I2C, Address.EEPROM, offsetAndValue, 2); - } - - public enum Address - { - USB_SYSCTL(0x2000), - USB_CTRL(0x2010), - USB_STAT(0x2014), - USB_EPA_CFG(0x2144), - USB_EPA_CTL(0x2148), - USB_EPA_MAXPKT(0x2158), - USB_EPA_MAXPKT_2(0x215A), - USB_EPA_FIFO_CFG(0x2160), - DEMOD_CTL(0x3000), - GPO(0x3001), - GPI(0x3002), - GPOE(0x3003), - GPD(0x3004), - SYSINTE(0x3005), - SYSINTS(0x3006), - GP_CFG0(0x3007), - GP_CFG1(0x3008), - SYSINTE_1(0x3009), - SYSINTS_1(0x300A), - DEMOD_CTL_1(0x300B), - IR_SUSPEND(0x300C), - EEPROM(0xA0); - - private int mAddress; - - Address(int address) - { - mAddress = address; - } - - public short getAddress() - { - return (short) mAddress; - } - } - - public enum Page - { - ZERO(0x0), - ONE(0x1), - TEN(0xA); - - private int mPage; - - Page(int page) - { - mPage = page; - } - - public byte getPage() - { - return (byte) (mPage & 0xFF); - } - } - - public enum SampleMode - { - QUADRATURE, DIRECT; - } - - public enum Block - { - DEMOD(0), - USB(1), - SYS(2), - TUN(3), - ROM(4), - IR(5), - I2C(6); //I2C controller - - private int mValue; - - Block(int value) - { - mValue = value; - } - - public int getValue() - { - return mValue; - } - - /** - * Returns the value left shifted 8 bits - */ - public short getReadIndex() - { - return (short) Integer.rotateLeft(mValue, 8); - } - - public short getWriteIndex() - { - return (short) (getReadIndex() | 0x10); - } - } - - /** - * Sample rates supported by the RTL-2832. - * - * Formula to calculate the ratio value: - * - * ratio = ( ( crystal_frequency * 2^22 ) / sample_rate ) & ~3 - * - * Default crystal_frequency is 28,800,000 - * - * This produces a 32-bit value that has to be set in 2 x 16-bit registers. - * Place the high 16-bit value in ratioMSB and the low 16-bit value in - * ratioLSB. Use integer for these values to avoid sign-extension issues. - * - * Mask the value with 0xFFFF when setting the register. + * RTL2832 sample rates supported by librtlsdr: 225,001 to 300,000 and 900,001 to 3,200,000 Hz. */ public enum SampleRate { /* Note: sample rates below 1.0MHz are subject to aliasing */ - RATE_0_230MHZ(0x1F40, 230400, "0.230 MHz"), - RATE_0_240MHZ(0x1E00, 240000, "0.240 MHz"), - RATE_0_256MHZ(0x1C20, 256000, "0.256 MHz"), - RATE_0_288MHZ(0x1900, 288000, "0.288 MHz"), - RATE_0_300MHZ(0x1800, 300000, "0.300 MHz"), - RATE_0_960MHZ(0x0780, 960000, "0.960 MHz"), - RATE_1_024MHZ(0x0708, 1024000, "1.024 MHz"), - RATE_1_200MHZ(0x0600, 1200000, "1.200 MHz"), - RATE_1_440MHZ(0x0500, 1440000, "1.440 MHz"), - RATE_1_600MHZ(0x0480, 1600000, "1.600 MHz"), - RATE_1_800MHZ(0x0400, 1800000, "1.800 MHz"), - RATE_1_920MHZ(0x03C0, 1920000, "1.920 MHz"), - RATE_2_048MHZ(0x0384, 2048000, "2.048 MHz"), - RATE_2_304MHZ(0x0320, 2304000, "2.304 MHz"), - RATE_2_400MHZ(0x0300, 2400000, "2.400 MHz"), - RATE_2_560MHZ(0x02D0, 2560000, "2.560 MHz"), - RATE_2_880MHZ(0x0280, 2880000, "2.880 MHz"); + RATE_0_230MHZ(230400, "0.230 MHz"), + RATE_0_240MHZ(240000, "0.240 MHz"), + RATE_0_256MHZ(256000, "0.256 MHz"), + RATE_0_288MHZ(288000, "0.288 MHz"), + RATE_0_300MHZ(300000, "0.300 MHz"), + RATE_0_960MHZ(960000, "0.960 MHz"), + RATE_1_024MHZ(1024000, "1.024 MHz"), + RATE_1_200MHZ(1200000, "1.200 MHz"), + RATE_1_440MHZ(1440000, "1.440 MHz"), + RATE_1_600MHZ(1600000, "1.600 MHz"), + RATE_1_800MHZ(1800000, "1.800 MHz"), + RATE_1_920MHZ(1920000, "1.920 MHz"), + RATE_2_048MHZ(2048000, "2.048 MHz"), + RATE_2_304MHZ(2304000, "2.304 MHz"), + RATE_2_400MHZ(2400000, "2.400 MHz"), + RATE_2_560MHZ(2560000, "2.560 MHz"), + RATE_2_880MHZ(2880000, "2.880 MHz"); - private int mRatioHigh; private int mRate; private String mLabel; - SampleRate(int ratioHigh, int rate, String label) + SampleRate(int rate, String label) { - mRatioHigh = ratioHigh; mRate = rate; mLabel = label; } - public int getRatioHighBits() - { - return mRatioHigh; - } - public int getRate() { return mRate; @@ -1232,94 +942,37 @@ public class RTL2832TunerController extends USBTunerController { return mLabel; } - - /** - * Returns the sample rate that is equal to the argument or the next - * higher sample rate - * - * @param sampleRate - * @return - */ - public static SampleRate getClosest(int sampleRate) - { - for(SampleRate rate : values()) - { - if(rate.getRate() >= sampleRate) - { - return rate; - } - } - - return DEFAULT_SAMPLE_RATE; - } - } - - public enum TunerTypeCheck - { - E4K(0xC8, 0x02, 0x40), - FC0012(0xC6, 0x00, 0xA1), - FC0013(0xC6, 0x00, 0xA3), - FC2580(0xAC, 0x01, 0x56), - R820T(0x34, 0x00, 0x69), - R828D(0x74, 0x00, 0x69); - - private int mI2CAddress; - private int mCheckAddress; - private int mCheckValue; - - TunerTypeCheck(int i2c, int address, int value) - { - mI2CAddress = i2c; - mCheckAddress = address; - mCheckValue = value; - } - - public byte getI2CAddress() - { - return (byte) mI2CAddress; - } - - public byte getCheckAddress() - { - return (byte) mCheckAddress; - } - - public byte getCheckValue() - { - return (byte) mCheckValue; - } } /** - * RTL2832 EEPROM byte array descriptor parsing class + * RTL2832 EEPROM byte array descriptor parsing class. The 256-byte EEPROM contents are as follows (in hex): + * + * 00/01 - 2832 Signature + * 03/02 - 0BDA Vendor ID + * 05/04 - 2832 Product ID + * 06 - A5 (has serial id?) + * 07 - 16 (bit field - bit 0 = remote wakeup, bit 1 = IR enabled + * 08 - 02 or 12 + * 10/09 0310 ETX(0x03) plus label length (includes length and ETX bytes) + * 12/11 First UTF-16 character + * 14/13 Second UTF-16 character ... + * + * Label 1: vendor + * Label 2: product + * Label 3: serial + * Label 4,5 ... (user defined) */ - public class Descriptor + public static class Descriptor { - private byte[] mData; - private ArrayList mLabels = new ArrayList(); + private final byte[] mData; + private final List mLabels = new ArrayList<>(); public Descriptor(byte[] data) { - if(data != null) - { - mData = data; - } - else - { - data = new byte[256]; - } - + mData = Arrays.copyOf(data, EEPROM_SIZE); getLabels(); } - /** - * Indicates if this is a rtl-sdr.com V4 R828D dongle with notch filtering. - */ - public boolean isRtlSdrV4() - { - return "RTLSDRBlog".equals(getVendorLabel()) && "Blog V4".equals(getProductLabel()); - } - public boolean isValid() { return mData[0] != (byte)0x0 && mData[1] != (byte)0x0; @@ -1333,7 +986,7 @@ public class RTL2832TunerController extends USBTunerController public String getVendorLabel() { - return mLabels.get(0); + return getLabel(0); } public String getProductID() @@ -1344,7 +997,7 @@ public class RTL2832TunerController extends USBTunerController public String getProductLabel() { - return mLabels.get(1); + return getLabel(1); } public boolean hasSerial() @@ -1354,7 +1007,7 @@ public class RTL2832TunerController extends USBTunerController public String getSerial() { - return mLabels.get(2); + return getLabel(2); } public boolean remoteWakeupEnabled() @@ -1369,38 +1022,40 @@ public class RTL2832TunerController extends USBTunerController return (mData[7] & mask) == mask; } + private String getLabel(int index) + { + return index < mLabels.size() ? mLabels.get(index) : ""; + } + private void getLabels() { mLabels.clear(); int start = 0x09; - while(start < 256) + while(start < EEPROM_SIZE) { - start = getLabel(start); + start = parseLabel(start); } } - private int getLabel(int start) + private int parseLabel(int start) { /* Validate length and check second byte for ETX (0x03) */ if(start > 254 || mData[start + 1] != (byte) 0x03) { - return 256; + return EEPROM_SIZE; } /* Get label length, including the length and ETX bytes */ int length = 0xFF & mData[start]; - if(start + length > 255) + if(length < 2 || start + length > 255) { - return 256; + return EEPROM_SIZE; } - /* Get the label bytes */ - byte[] data = Arrays.copyOfRange(mData, start + 2, start + length); - /* Translate the bytes as UTF-16 Little Endian and store the label */ - String label = new String(data, Charset.forName("UTF-16LE")); - mLabels.add(label); + byte[] data = Arrays.copyOfRange(mData, start + 2, start + length); + mLabels.add(new String(data, StandardCharsets.UTF_16LE)); return start + length; } @@ -1408,38 +1063,19 @@ public class RTL2832TunerController extends USBTunerController { StringBuilder sb = new StringBuilder(); sb.append("RTL-2832 EEPROM Descriptor\n"); - - sb.append("Vendor: "); - sb.append(getVendorID()); - sb.append(" ["); - sb.append(getVendorLabel()); - sb.append("]\n"); - - sb.append("Product: "); - sb.append(getProductID()); - sb.append(" ["); - sb.append(getProductLabel()); - sb.append("]\n"); - + sb.append("Vendor: ").append(getVendorID()).append(" [").append(getVendorLabel()).append("]\n"); + sb.append("Product: ").append(getProductID()).append(" [").append(getProductLabel()).append("]\n"); sb.append("Serial: "); if(hasSerial()) { - sb.append("yes ["); - sb.append(getSerial()); - sb.append("]\n"); + sb.append("yes [").append(getSerial()).append("]\n"); } else { sb.append("no\n"); } - - sb.append("Remote Wakeup Enabled: "); - sb.append((remoteWakeupEnabled() ? "yes" : "no")); - sb.append("\n"); - - sb.append("IR Enabled: "); - sb.append((irEnabled() ? "yes" : "no")); - sb.append("\n"); + sb.append("Remote Wakeup Enabled: ").append(remoteWakeupEnabled() ? "yes" : "no").append("\n"); + sb.append("IR Enabled: ").append(irEnabled() ? "yes" : "no").append("\n"); if(mLabels.size() > 3) { @@ -1447,155 +1083,13 @@ public class RTL2832TunerController extends USBTunerController for(int x = 3; x < mLabels.size(); x++) { - sb.append(" ["); - sb.append(mLabels.get(x)); - sb.append("\n"); + sb.append(" [").append(mLabels.get(x)).append("]"); } + + sb.append("\n"); } return sb.toString(); } } - - /** - * Adapter that provides access to the private methods of this RTL2832 USB tuner device's USB and other interfaces. - * - * Note: most of the methods exposed by this tuner are private in the tuner controller class by design to prevent - * the user from incorrectly accessing them. This private adapter exposes these as public methods that the various - * RTL2832 tuner implementations need access to. - */ - public class ControllerAdapter - { - private RTL2832TunerController mController; - - /** - * Constructs an instance - * @param controller for this adapter - */ - public ControllerAdapter(RTL2832TunerController controller) - { - mController = controller; - } - - /** - * Indicates if the tuner controller is running and that the Device Handle is active. - */ - public boolean isRunning() - { - return mController.isRunning(); - } - - /** - * Indicates if this is a rtl-sdr.com V4 dongle with support for notch filtering. - * @return true if this is a V4 R828D tuner. - */ - public boolean isV4Dongle() - { - return mController.getDescriptor().isRtlSdrV4(); - } - - /** - * Device handle for the tuner controller. - * @return - */ - public DeviceHandle getDeviceHandle() - { - return mController.getDeviceHandle(); - } - - /** - * Thread lock for controlling access to configuration changes that require multiple USB control message - * sequences. - * - * @return lock. - */ - public ReentrantLock getLock() - { - return mController.getLock(); - } - - /** - * Enables the I2C repeater - */ - public void enableI2CRepeater() - { - mController.enableI2CRepeater(true); - } - - /** - * Disables the I2C repeater - */ - public void disableI2CRepeater() - { - mController.enableI2CRepeater(false); - } - - /** - * Indicates if the I2C repeater is enabled - */ - public boolean isI2CRepeaterEnabled() - { - return mController.isI2CRepeaterEnabled(); - } - - /** - * Writes to the Demod registers - * @param page value - * @param address value - * @param value to write - * @param length of the value in bytes - * @throws LibUsbException if there is an error - */ - public void writeDemodRegister(Page page, short address, int value, int length) throws LibUsbException - { - mController.writeDemodRegister(page, address, value, length); - } - - /** - * Writes to the I2C register - * @param i2CAddress address - * @param i2CRegister register - * @param value to write - * @param controlI2CRepeater true to turn on/off the I2C repeater, or false if it's already been turned on. - * @throws LibUsbException if there is an error - */ - public void writeI2CRegister(byte i2CAddress, byte i2CRegister, byte value, boolean controlI2CRepeater) - throws LibUsbException - { - mController.writeI2CRegister(i2CAddress, i2CRegister, value, controlI2CRepeater); - } - - /** - * Reads the I2C register - * @param i2CAddress address - * @param i2CRegister register - * @param controlI2CRepeater to control the I2C repeater - * @return value - * @throws LibUsbException if there is an error - */ - public int readI2CRegister(byte i2CAddress, byte i2CRegister, boolean controlI2CRepeater) throws LibUsbException - { - return mController.readI2CRegister(i2CAddress, i2CRegister, controlI2CRepeater); - } - - /** - * Reads a value - * @param address to read - * @param block at the address - * @param buffer to read the value into - * @throws LibUsbException - */ - public void read(short address, Block block, ByteBuffer buffer) throws LibUsbException - { - mController.read(address, block, buffer); - } - - /** - * Sets the intermediate frequency (IF) for the digitizer - */ - public void setIFFrequency(int frequency) - { - mController.setIFFrequency(frequency); - } - } -} \ No newline at end of file +} diff --git a/src/main/java/io/github/dsheirer/source/tuner/rtl/RTL2832TunerEditor.java b/src/main/java/io/github/dsheirer/source/tuner/rtl/RTL2832TunerEditor.java new file mode 100644 index 00000000..315f1334 --- /dev/null +++ b/src/main/java/io/github/dsheirer/source/tuner/rtl/RTL2832TunerEditor.java @@ -0,0 +1,508 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2026 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; + +import io.github.dsheirer.preference.UserPreferences; +import io.github.dsheirer.source.SourceException; +import io.github.dsheirer.source.tuner.TunerType; +import io.github.dsheirer.source.tuner.manager.DiscoveredTuner; +import io.github.dsheirer.source.tuner.manager.TunerManager; +import io.github.dsheirer.source.tuner.rtl.RTL2832TunerController.SampleRate; +import io.github.dsheirer.source.tuner.rtl.e4k.E4KTunerConfiguration; +import io.github.dsheirer.source.tuner.rtl.r8x.R8xTunerConfiguration; +import io.github.dsheirer.source.tuner.ui.TunerEditor; +import javax.swing.DefaultComboBoxModel; +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JSeparator; +import javax.swing.JToggleButton; +import javax.swing.SpinnerNumberModel; +import net.miginfocom.swing.MigLayout; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * RTL-2832 tuner editor for all supported embedded tuner types (E4000, FC0012, FC0013, FC2580, R820T and R828D). + * + * Gain options are the gains that librtlsdr reports for the embedded tuner, plus the tuner's automatic gain control. + * The E4000 additionally has a total IF gain that is distributed across its IF gain stages. + */ +public class RTL2832TunerEditor extends TunerEditor +{ + private static final Logger mLog = LoggerFactory.getLogger(RTL2832TunerEditor.class); + private static final long serialVersionUID = 1L; + private JButton mTunerInfoButton; + private JToggleButton mBiasTButton; + private JComboBox mSampleRateCombo; + private JToggleButton mAgcToggleButton; + private JComboBox mGainCombo; + private JLabel mIFGainLabel; + private JComboBox mIFGainCombo; + + /** + * Constructs an instance + * @param userPreferences for wide-band recordings + * @param tunerManager for saving configurations + * @param discoveredTuner to edit + */ + public RTL2832TunerEditor(UserPreferences userPreferences, TunerManager tunerManager, DiscoveredTuner discoveredTuner) + { + super(userPreferences, tunerManager, discoveredTuner); + init(); + tunerStatusUpdated(); + } + + @Override + public long getMinimumTunableFrequency() + { + if(hasTuner()) + { + return getTuner().getController().getMinimumFrequency(); + } + + return R8xTunerConfiguration.MINIMUM_TUNABLE_FREQUENCY_HZ; + } + + @Override + public long getMaximumTunableFrequency() + { + if(hasTuner()) + { + return getTuner().getController().getMaximumFrequency(); + } + + return R8xTunerConfiguration.MAXIMUM_TUNABLE_FREQUENCY_HZ; + } + + private String getLogPrefix() + { + return (hasTuner() ? getTuner().getController().getTunerType().getLabel() : "RTL-2832") + " Tuner Controller - "; + } + + /** + * Indicates if the tuner has an E4000 embedded tuner, which supports IF gain control. + */ + private boolean isE4K() + { + return hasTuner() && getTuner().getController().getTunerType() == TunerType.ELONICS_E4000; + } + + 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"); + add(new JLabel("Gain Control"), "wrap"); + + add(new JLabel("Gain:")); + add(getGainCombo()); + add(getAgcToggleButton(), "wrap"); + + add(getIFGainLabel()); + add(getIFGainCombo(), "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(); + + DefaultComboBoxModel gainModel = new DefaultComboBoxModel<>(); + + if(hasTuner()) + { + RTL2832TunerController controller = getTuner().getController(); + + if(controller.hasGainControl()) + { + for(int gain : controller.getTunerGains()) + { + gainModel.addElement(new Gain(gain)); + } + } + } + + getGainCombo().setModel(gainModel); + + boolean e4k = isE4K(); + getIFGainLabel().setVisible(e4k); + getIFGainCombo().setVisible(e4k); + + if(hasTuner() && hasConfiguration()) + { + RTL2832TunerConfiguration config = getConfiguration(); + getBiasTButton().setEnabled(true); + getBiasTButton().setSelected(config.isBiasT()); + getTunerInfoButton().setEnabled(true); + getSampleRateCombo().setEnabled(true); + getSampleRateCombo().setSelectedItem(config.getSampleRate()); + RTL2832TunerController controller = getTuner().getController(); + //The FC2580 always uses its internal AGC and the FC0012 doesn't support AGC + boolean agc = controller.hasGainControl() ? controller.hasTunerAGC() && config.isAGC() : true; + getAgcToggleButton().setEnabled(controller.hasTunerAGC()); + getAgcToggleButton().setSelected(agc); + getAgcToggleButton().setToolTipText(getAgcToolTip(controller)); + getGainCombo().setEnabled(!agc && gainModel.getSize() > 0); + getGainCombo().setSelectedItem(new Gain(getTuner().getController().getClosestTunerGain(config.getGain()))); + + if(e4k && config instanceof E4KTunerConfiguration e4kConfig) + { + getIFGainCombo().setEnabled(true); + getIFGainCombo().setSelectedItem(e4kConfig.getIFGainDb()); + } + } + else + { + getBiasTButton().setEnabled(false); + getBiasTButton().setSelected(false); + getTunerInfoButton().setEnabled(false); + getSampleRateCombo().setEnabled(false); + getAgcToggleButton().setEnabled(false); + getGainCombo().setEnabled(false); + getIFGainCombo().setEnabled(false); + } + + updateSampleRateToolTip(); + + setLoading(false); + } + + /** + * Bias-T toggle 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; + } + + /** + * Button that provides tuner information + */ + private JButton getTunerInfoButton() + { + if(mTunerInfoButton == null) + { + mTunerInfoButton = new JButton("Info"); + mTunerInfoButton.setEnabled(false); + mTunerInfoButton.addActionListener(e -> JOptionPane.showMessageDialog(RTL2832TunerEditor.this, + getTunerInfo(), "Tuner Info", JOptionPane.INFORMATION_MESSAGE)); + } + + return mTunerInfoButton; + } + + /** + * Applies the current AGC and gain settings to the tuner and saves the configuration. + */ + private void applyGain() + { + if(isLoading() || !hasTuner()) + { + return; + } + + boolean agc = getAgcToggleButton().isSelected(); + Gain gain = (Gain)getGainCombo().getSelectedItem(); + getGainCombo().setEnabled(!agc && getGainCombo().getItemCount() > 0); + + try + { + getTuner().getController().setGain(agc, gain != null ? gain.value() : getConfiguration().getGain()); + save(); + } + catch(SourceException se) + { + JOptionPane.showMessageDialog(RTL2832TunerEditor.this, getLogPrefix() + + "couldn't apply the gain setting - " + se.getLocalizedMessage()); + mLog.error(getLogPrefix() + "couldn't apply gain setting", se); + } + } + + /** + * Tool tip for the AGC button that explains when the tuner doesn't support switching AGC. + */ + private static String getAgcToolTip(RTL2832TunerController controller) + { + if(!controller.hasGainControl()) + { + return "The " + controller.getTunerType().getLabel() + " tuner always uses its automatic gain control (AGC)"; + } + else if(!controller.hasTunerAGC()) + { + return "The " + controller.getTunerType().getLabel() + " tuner doesn't support automatic gain control (AGC)"; + } + + return "Turn on the tuner's automatic gain control (AGC)"; + } + + private JToggleButton getAgcToggleButton() + { + if(mAgcToggleButton == null) + { + mAgcToggleButton = new JToggleButton("AGC"); + mAgcToggleButton.setEnabled(false); + mAgcToggleButton.setToolTipText("Turn on the tuner's automatic gain control (AGC)"); + mAgcToggleButton.addActionListener(e -> applyGain()); + } + + return mAgcToggleButton; + } + + private JComboBox getGainCombo() + { + if(mGainCombo == null) + { + mGainCombo = new JComboBox<>(); + mGainCombo.setEnabled(false); + mGainCombo.setToolTipText("Tuner gain. Turn off AGC to enable adjustment."); + mGainCombo.addActionListener(e -> applyGain()); + } + + return mGainCombo; + } + + private JLabel getIFGainLabel() + { + if(mIFGainLabel == null) + { + mIFGainLabel = new JLabel("IF Gain (dB):"); + } + + return mIFGainLabel; + } + + private JComboBox getIFGainCombo() + { + if(mIFGainCombo == null) + { + mIFGainCombo = new JComboBox<>(); + + for(int gain = RTL2832TunerController.E4K_IF_GAIN_MINIMUM; gain <= RTL2832TunerController.E4K_IF_GAIN_MAXIMUM; gain++) + { + mIFGainCombo.addItem(gain); + } + + mIFGainCombo.setEnabled(false); + mIFGainCombo.setToolTipText("E4000 IF gain, distributed across the six IF gain stages"); + mIFGainCombo.addActionListener(e -> + { + Integer gain = (Integer)mIFGainCombo.getSelectedItem(); + + if(!isLoading() && hasTuner() && gain != null) + { + try + { + getTuner().getController().setE4KIFGain(gain); + save(); + } + catch(SourceException se) + { + JOptionPane.showMessageDialog(RTL2832TunerEditor.this, getLogPrefix() + + "couldn't apply the IF gain setting - " + se.getLocalizedMessage()); + mLog.error(getLogPrefix() + "couldn't apply IF gain setting", se); + } + } + }); + } + + return mIFGainCombo; + } + + 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); + //Adjust the min/max values for the sample rate. + adjustForSampleRate(sampleRate.getRate()); + save(); + } + catch(SourceException se) + { + JOptionPane.showMessageDialog(RTL2832TunerEditor.this, + getLogPrefix() + "couldn't apply the sample rate setting [" + + sampleRate.getLabel() + "] " + se.getLocalizedMessage()); + + mLog.error(getLogPrefix() + "couldn't apply sample rate setting [" + sampleRate.getLabel() + + "]", se); + } + } + }); + } + + return mSampleRateCombo; + } + + /** + * 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 ").append(getTuner().getController().getTunerType().getLabel()) + .append(" Tuner

"); + + if(descriptor == null) + { + sb.append("No EEPROM Descriptor Available"); + } + else + { + sb.append("USB ID: ").append(descriptor.getVendorID()).append(":") + .append(descriptor.getProductID()).append("
"); + sb.append("Vendor: ").append(descriptor.getVendorLabel()).append("
"); + sb.append("Product: ").append(descriptor.getProductLabel()).append("
"); + sb.append("Serial: ").append(descriptor.getSerial()).append("
"); + sb.append("IR Enabled: ").append(descriptor.irEnabled()).append("
"); + sb.append("Remote Wake: ").append(descriptor.remoteWakeupEnabled()).append("
"); + } + + return sb.toString(); + } + + @Override + public void save() + { + if(hasConfiguration() && !isLoading()) + { + RTL2832TunerConfiguration config = getConfiguration(); + config.setBiasT(getTuner().getController().isBiasT()); + config.setFrequency(getFrequencyControl().getFrequency()); + config.setMinimumFrequency(getMinimumFrequencyTextField().getFrequency()); + config.setMaximumFrequency(getMaximumFrequencyTextField().getFrequency()); + double value = ((SpinnerNumberModel)getFrequencyCorrectionSpinner().getModel()).getNumber().doubleValue(); + config.setFrequencyCorrection(value); + config.setAutoPPMCorrectionEnabled(getAutoPPMCheckBox().isSelected()); + config.setSampleRate((SampleRate)getSampleRateCombo().getSelectedItem()); + config.setAGC(getAgcToggleButton().isSelected()); + + if(getGainCombo().getSelectedItem() instanceof Gain gain) + { + config.setGain(gain.value()); + } + + if(config instanceof E4KTunerConfiguration e4k && getIFGainCombo().getSelectedItem() instanceof Integer ifGain) + { + e4k.setIFGainDb(ifGain); + } + + saveConfiguration(); + } + } + + /** + * Tuner gain combo box entry. + * @param value in tenths of a dB + */ + private record Gain(int value) + { + @Override + public String toString() + { + return String.format("%.1f dB", value / 10.0); + } + } +} diff --git a/src/main/java/io/github/dsheirer/source/tuner/rtl/RTL2832UnknownTunerEditor.java b/src/main/java/io/github/dsheirer/source/tuner/rtl/RTL2832UnknownTunerEditor.java index 219958bc..c8c2c12b 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/rtl/RTL2832UnknownTunerEditor.java +++ b/src/main/java/io/github/dsheirer/source/tuner/rtl/RTL2832UnknownTunerEditor.java @@ -22,7 +22,7 @@ package io.github.dsheirer.source.tuner.rtl; import io.github.dsheirer.preference.UserPreferences; import io.github.dsheirer.source.tuner.manager.DiscoveredTuner; import io.github.dsheirer.source.tuner.manager.TunerManager; -import io.github.dsheirer.source.tuner.rtl.r8x.R8xEmbeddedTuner; +import io.github.dsheirer.source.tuner.rtl.r8x.R8xTunerConfiguration; import io.github.dsheirer.source.tuner.ui.TunerEditor; import net.miginfocom.swing.MigLayout; @@ -70,14 +70,14 @@ public class RTL2832UnknownTunerEditor extends TunerEditor - * **************************************************************************** - */ -package io.github.dsheirer.source.tuner.rtl.e4k; - -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.util.EnumSet; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.usb4java.LibUsb; -import org.usb4java.LibUsbException; - -import javax.usb.UsbException; - -/** - * Elonic E4000 Tuner - */ -public class E4KEmbeddedTuner extends EmbeddedTuner -{ - private final static Logger mLog = LoggerFactory.getLogger(E4KEmbeddedTuner.class); - - public static final long MINIMUM_TUNABLE_FREQUENCY_HZ = 52000000; - public static final long MAXIMUM_TUNABLE_FREQUENCY_HZ = 2200000000l; - public static final double USABLE_BANDWIDTH_PERCENT = 0.95; - public static final int DC_SPIKE_AVOID_BUFFER = 15000; - - /** - * The local oscillator is defined by whole (integer) units of the oscillator frequency and fractional units - * representing 1/65536th of the oscillator frequency, meaning we can only tune the local oscillator in units of - * 439.453125 hertz. - */ - public static final long E4K_PLL_Y = 65536l; /* 16-bit fractional register */ - public static final byte MASTER1_RESET = (byte) 0x01; - public static final byte MASTER1_NORM_STBY = (byte) 0x02; - public static final byte MASTER1_POR_DET = (byte) 0x04; - - public static final byte SYNTH1_PLL_LOCK = (byte) 0x01; - public static final byte SYNTH1_BAND_SHIF = (byte) 0x01; - public static final byte SYNTH7_3PHASE_EN = (byte) 0x08; - public static final byte SYNTH8_VCOCAL_UPD = (byte) 0x04; - - public static final byte FILT3_MASK = (byte) 0x20; - public static final byte FILT3_ENABLE = (byte) 0x00; - public static final byte FILT3_DISABLE = (byte) 0x20; - - public static final byte MIXER_FILTER_MASK = (byte) 0xF0; - public static final byte IF_CHANNEL_FILTER_MASK = (byte) 0x1F; - public static final byte IF_RC_FILTER_MASK = (byte) 0x0F; - - public static final byte AGC1_LIN_MODE = (byte) 0x10; - public static final byte AGC1_LNA_UPDATE = (byte) 0x20; - public static final byte AGC1_LNA_G_LOW = (byte) 0x40; - public static final byte AGC1_LNA_G_HIGH = (byte) 0x80; - public static final byte AGC1_MOD_MASK = (byte) 0xF; - - public static final byte GAIN1_MOD_MASK = (byte) 0xF; - public static final byte IF_GAIN_MODE_SWITCHING_MASK = (byte) 0x1; - - public static final byte AGC6_LNA_CAL_REQ = (byte) 0x10; - - public static final byte AGC7_MIXER_GAIN_MASK = (byte) 0x01; - public static final byte AGC7_MIX_GAIN_MANUAL = (byte) 0x00; - public static final byte AGC7_MIX_GAIN_AUTO = (byte) 0x01; - public static final byte ENH_GAIN_MOD_MASK = (byte) 0x07; - public static final byte MIXER_GAIN_MASK = (byte) 0x01; - public static final byte DC5_RANGE_DETECTOR_ENABLED_MASK = (byte) 0x04; //DC Offset Detector Enabled - public static final byte DC5_RANGE_DETECTOR_ENABLED = (byte) 0x04; //DC Offset Detector Enabled - - public E4KEmbeddedTuner(RTL2832TunerController.ControllerAdapter adapter) - { - super(adapter); - } - - @Override - public TunerType getTunerType() - { - return TunerType.ELONICS_E4000; - } - - @Override - public long getMinimumFrequencySupported() - { - return MINIMUM_TUNABLE_FREQUENCY_HZ; - } - - @Override - public long getMaximumFrequencySupported() - { - return MAXIMUM_TUNABLE_FREQUENCY_HZ; - } - - @Override - public int getDcSpikeHalfBandwidth() - { - return DC_SPIKE_AVOID_BUFFER; - } - - @Override - public double getUsableBandwidthPercent() - { - return USABLE_BANDWIDTH_PERCENT; - } - - @Override - public void setSamplingMode(RTL2832TunerController.SampleMode mode) throws LibUsbException - { - switch(mode) - { - case QUADRATURE: - /* Set intermediate frequency to 0 Hz */ - getAdapter().setIFFrequency(0); - - /* Enable I/Q ADC Input */ - getAdapter().writeDemodRegister(RTL2832TunerController.Page.ZERO, (short) 0x08, (short) 0xCD, 1); - - /* Enable zero-IF mode */ - getAdapter().writeDemodRegister(RTL2832TunerController.Page.ONE, (short) 0xB1, (short) 0x1B, 1); - - /* Set default i/q path */ - getAdapter().writeDemodRegister(RTL2832TunerController.Page.ZERO, (short) 0x06, (short) 0x80, 1); - break; - case DIRECT: - default: - throw new LibUsbException("QUADRATURE mode is the only mode currently supported", LibUsb.ERROR_NOT_SUPPORTED); - } - } - - - @Override - public void apply(TunerConfiguration config) throws SourceException - { - if(config instanceof E4KTunerConfiguration e4kConfig) - { - try - { - E4KGain masterGain = e4kConfig.getMasterGain(); - setGain(masterGain, true); - - if(masterGain == E4KGain.MANUAL) - { - E4KMixerGain mixerGain = e4kConfig.getMixerGain(); - setMixerGain(mixerGain, true); - - E4KLNAGain lnaGain = e4kConfig.getLNAGain(); - setLNAGain(lnaGain, true); - - IFGain ifGain = e4kConfig.getIFGain(); - setIFGain(ifGain, true); - } - } - catch(UsbException e) - { - throw new SourceException("Usb error while applying tuner config", e); - } - } - else - { - throw new IllegalArgumentException("Unrecognized tuner config [" + config.getClass() + "]"); - } - } - - /** - * Sets the IF filters (mixer, channel and RC) to the correct filter setting - * for the selected bandwidth/sample rate - */ - @Override - public void setSampleRateFilters(int bandwidth) throws SourceException - { - /* Determine repeater state so we can restore it when done */ - boolean i2CRepeaterEnabled = getAdapter().isI2CRepeaterEnabled(); - - if(!i2CRepeaterEnabled) - { - getAdapter().enableI2CRepeater(); - } - - boolean controlI2CRepeater = false; - - MixerFilter mixer = MixerFilter.getFilter(bandwidth); - setMixerFilter(mixer, controlI2CRepeater); - - ChannelFilter channel = ChannelFilter.getFilter(bandwidth); - setChannelFilter(channel, controlI2CRepeater); - - RCFilter rc = RCFilter.getFilter(bandwidth); - setRCFilter(rc, controlI2CRepeater); - - if(!i2CRepeaterEnabled) - { - getAdapter().disableI2CRepeater(); - } - } - - public long getTunedFrequency() throws SourceException - { - try - { - /* Determine repeater state so we can restore it when done */ - boolean i2CRepeaterEnabled = getAdapter().isI2CRepeaterEnabled(); - - if(!i2CRepeaterEnabled) - { - getAdapter().enableI2CRepeater(); - } - - boolean controlI2CRepeater = false; - byte z = (byte) readE4KRegister(Register.SYNTH3, controlI2CRepeater); - int xHigh = readE4KRegister(Register.SYNTH4, controlI2CRepeater); - int xLow = readE4KRegister(Register.SYNTH5, controlI2CRepeater); - int x = (Integer.rotateLeft(xHigh, 8)) | xLow; - int pllSetting = readE4KRegister(Register.SYNTH7, controlI2CRepeater); - PLL pll = PLL.fromSetting(pllSetting); - - /* Return the repeater to its previous state */ - if(!i2CRepeaterEnabled) - { - getAdapter().disableI2CRepeater(); - } - - return calculateActualFrequency(pll, z, x); - } - catch(LibUsbException e) - { - throw new SourceException("E4K tuner controller - couldn't get " + "tuned frequency", e); - } - } - - @Override - public synchronized void setTunedFrequency(long frequency) throws SourceException - { - /* Get the phase locked loop setting */ - PLL pll = PLL.fromFrequency(frequency); - - /* Z is an integer representing the number of scaled oscillator frequency - * increments the multiplied frequency. */ - byte z = (byte) (frequency / pll.getScaledOscillator()); - - /* remainder is just as it describes. It is what is left over after we - * carve out scaled oscillator frequency increments (z) from the desired - * frequency. */ - int remainder = (int) (frequency - ((z & 0xFF) * pll.getScaledOscillator())); - - /* X is a 16-bit representation of the remainder */ - int x = (int) ((double) remainder / (double) pll.getScaledOscillator() * E4K_PLL_Y); - - /* Calculate the exact (tunable) frequency and apply that to the tuner */ - long actualFrequency = calculateActualFrequency(pll, z, x); - - /** - * Hack: if we're trying to set the minimum frequency for the E4K, due - * to rounding errors, 52 mhz becomes 51.999993, so we need to adjust - * x to get 52.000003 mhz ... otherwise the PLL won't lock on the freq - */ - if(actualFrequency < getMinimumFrequencySupported()) - { - x++; - actualFrequency = calculateActualFrequency(pll, z, x); - } - - getAdapter().getLock().lock(); - - try - { - /* Apply the actual frequency */ - getAdapter().enableI2CRepeater(); - boolean controlI2CRepeater = false; - /* Write the PLL setting */ - writeE4KRegister(Register.SYNTH7, pll.getIndex(), controlI2CRepeater); - /* Write z (integral) value */ - writeE4KRegister(Register.SYNTH3, z, controlI2CRepeater); - /* Write the x (fractional) value high-order byte to synth4 register */ - writeE4KRegister(Register.SYNTH4, (byte) (x & 0xFF), controlI2CRepeater); - /* Write the x (fractional) value low-order byte to synth5 register */ - writeE4KRegister(Register.SYNTH5, (byte) ((Integer.rotateRight(x, 8)) & 0xFF), controlI2CRepeater); - /* Set the band for the new frequency */ - setBand(actualFrequency, controlI2CRepeater); - /* Set the filter */ - setRFFilter(actualFrequency, controlI2CRepeater); - /* Check for PLL lock */ - int lock = readE4KRegister(Register.SYNTH1, controlI2CRepeater); - - if(!((lock & 0x1) == 0x1)) - { - throw new SourceException("E4K tuner - couldn't achieve PLL lock for frequency [" + actualFrequency + - "] lock value [" + lock + "]"); - } - - getAdapter().disableI2CRepeater(); - } - catch(UsbException e) - { - throw new SourceException("E4K tuner controller - error tuning frequency [" + frequency + "]", e); - } - finally - { - getAdapter().getLock().unlock(); - } - } - - private long calculateActualFrequency(PLL pll, byte z, int x) - { - long whole = pll.getScaledOscillator() * (z & 0xFF); - - int fractional = (int) (pll.getScaledOscillator() * - ((double) x / (double) E4K_PLL_Y)); - - return whole + fractional; - } - - protected void initTuner() throws UsbException - { - getAdapter().enableI2CRepeater(); - - boolean i2CRepeaterControl = false; - - /* Perform dummy read */ - readE4KRegister(Register.DUMMY, i2CRepeaterControl); - /* Reset everything and clear POR indicator. NOTE: register value remains 0010 even after we write 0111 to it */ - writeE4KRegister(Register.MASTER1, (byte) (MASTER1_RESET | MASTER1_NORM_STBY | MASTER1_POR_DET), i2CRepeaterControl); - /* Configure clock input */ - writeE4KRegister(Register.CLK_INP, (byte) 0x00, i2CRepeaterControl); - /* Disable clock output */ - writeE4KRegister(Register.REF_CLK, (byte) 0x00, i2CRepeaterControl); - writeE4KRegister(Register.CLKOUT_PWDN, (byte) 0x96, i2CRepeaterControl); - //Make the magic happen ... - magicInit(i2CRepeaterControl); - - /* Initialize DC offset lookup tables */ - generateDCOffsetTables(i2CRepeaterControl); - - /* Enable time variant DC correction */ - writeE4KRegister(Register.DCTIME1, (byte) 0x01, i2CRepeaterControl); - writeE4KRegister(Register.DCTIME2, (byte) 0x01, i2CRepeaterControl); - - /* Set LNA Mode */ - writeE4KRegister(Register.AGC4, (byte) 0x10, i2CRepeaterControl); //High Threshold - writeE4KRegister(Register.AGC5, (byte) 0x04, i2CRepeaterControl); //Low Threshold - writeE4KRegister(Register.AGC6, (byte) 0x1A, i2CRepeaterControl); //LNA calibrate request + slowest loop rate - - //Temp - manual set lna mode to manual - writeMaskedE4KRegister(Register.AGC1, AGC1_MOD_MASK, AGCMode.SERIAL.getValue(), false); - //Temp - set mixer gain to manual - writeMaskedE4KRegister(Register.AGC7, AGC7_MIXER_GAIN_MASK, AGC7_MIX_GAIN_MANUAL, false); - - /* Use automatic gain as a default */ - setLNAGain(E4KLNAGain.AUTOMATIC, i2CRepeaterControl); - setMixerGain(E4KMixerGain.AUTOMATIC, i2CRepeaterControl); - setEnhanceGain(E4KEnhanceGain.GAIN_5, i2CRepeaterControl); //Setting of 5 is recommended in datasheet - - /* Set IF gain stages */ - setIFGain(IFGain.LINEARITY_8, i2CRepeaterControl); - - /* Set the narrowest filter we can possibly use */ - setMixerFilter(MixerFilter.BW_1M9, i2CRepeaterControl); - setRCFilter(RCFilter.BW_1M0, i2CRepeaterControl); - setChannelFilter(ChannelFilter.BW_2M15, i2CRepeaterControl); - setChannelFilterEnabled(true, i2CRepeaterControl); - - /* Disable DC detector */ - setDCRangeDetectorEnabled( false, i2CRepeaterControl ); - setDCOffsetLookupTablesEnabled(false, i2CRepeaterControl); - - /* Disable time variant DC correction */ - writeMaskedE4KRegister(Register.DC5, (byte) 0x03, (byte) 0, i2CRepeaterControl); - writeMaskedE4KRegister(Register.DCTIME1, (byte) 0x03, (byte) 0, i2CRepeaterControl); - writeMaskedE4KRegister(Register.DCTIME2, (byte) 0x03, (byte) 0, i2CRepeaterControl); - - getAdapter().disableI2CRepeater(); - } - - /** - * Requests a DC offset calibration for the tuner - */ - private void requestDcOffsetCalibration(boolean controlI2CRepeater) throws LibUsbException - { - if(controlI2CRepeater) - { - getAdapter().enableI2CRepeater(); - } - - writeMaskedE4KRegister(Register.DC1, (byte) 0x1, (byte) 0x1, false); - - if(controlI2CRepeater) - { - getAdapter().disableI2CRepeater(); - } - } - - /** - * Calibrates the DC offset lookup table values for tuner initialization - */ - private void generateDCOffsetTables(boolean controlI2CRepeater) throws UsbException - { - if(controlI2CRepeater) - { - getAdapter().enableI2CRepeater(); - } - - boolean i2CRepeaterControl = false; - - //Set all IF gain stages (1-6) to maximum values. During calibration we'll change IF stage 1 gain - setIFGain(IFGain.LINEARITY_60, i2CRepeaterControl); - - //Enable DC range detector - setDCRangeDetectorEnabled(true, i2CRepeaterControl); - - //Enable DC offset lookup tables - setDCOffsetLookupTablesEnabled(true, i2CRepeaterControl); - - //Step 0 - setIFStage1Gain(IFStage1Gain.GAIN_MINUS3, i2CRepeaterControl); - setMixerGain(E4KMixerGain.GAIN_4, i2CRepeaterControl); - requestDcOffsetCalibration(i2CRepeaterControl); - byte i = getDcOffsetI(i2CRepeaterControl); - byte q = getDcOffsetQ(i2CRepeaterControl); - writeE4KRegister(Register.ILUT0, i, i2CRepeaterControl); - writeE4KRegister(Register.QLUT0, q, i2CRepeaterControl); - - //Step 1 - setMixerGain(E4KMixerGain.GAIN_12, i2CRepeaterControl); - requestDcOffsetCalibration(i2CRepeaterControl); - i = getDcOffsetI(i2CRepeaterControl); - q = getDcOffsetQ(i2CRepeaterControl); - writeE4KRegister(Register.ILUT1, i, i2CRepeaterControl); - writeE4KRegister(Register.QLUT1, q, i2CRepeaterControl); - - //Step 2 - setIFStage1Gain(IFStage1Gain.GAIN_PLUS6, i2CRepeaterControl); - setMixerGain(E4KMixerGain.GAIN_4, i2CRepeaterControl); - requestDcOffsetCalibration(i2CRepeaterControl); - i = getDcOffsetI(i2CRepeaterControl); - q = getDcOffsetQ(i2CRepeaterControl); - writeE4KRegister(Register.ILUT2, i, i2CRepeaterControl); - writeE4KRegister(Register.QLUT2, q, i2CRepeaterControl); - - //Step 3 - setMixerGain(E4KMixerGain.GAIN_12, i2CRepeaterControl); - requestDcOffsetCalibration(i2CRepeaterControl); - i = getDcOffsetI(i2CRepeaterControl); - q = getDcOffsetQ(i2CRepeaterControl); - writeE4KRegister(Register.ILUT3, i, i2CRepeaterControl); - writeE4KRegister(Register.QLUT3, q, i2CRepeaterControl); - - if(controlI2CRepeater) - { - getAdapter().disableI2CRepeater(); - } - } - - private byte getDcOffsetI(boolean i2CRepeaterControl) - { - byte offset = (byte) (readE4KRegister(Register.DC2, i2CRepeaterControl) & 0x3F); - byte range = (byte) ((readE4KRegister(Register.DC4, i2CRepeaterControl) & 0x3) << 6); - return (byte) (range | offset); - } - - private byte getDcOffsetQ(boolean i2CRepeaterControl) - { - byte offset = (byte) (readE4KRegister(Register.DC3, i2CRepeaterControl) & 0x3F); - byte range = (byte) (readE4KRegister(Register.DC4, i2CRepeaterControl) << 2); - return (byte) (range | offset); - } - - /** - * Enables or disables the DC offset lookup tables. - * - * @param enabled - * @param controlI2CRepeater - * @throws LibUsbException - */ - public void setDCOffsetLookupTablesEnabled(boolean enabled, boolean controlI2CRepeater) throws LibUsbException - { - byte value = enabled ? (byte) 0x3 : (byte) 0x0; - writeMaskedE4KRegister(Register.DC5, value, (byte) 0x3, controlI2CRepeater); - } - - /** - * Enables or disables the DC range detector - */ - public void setDCRangeDetectorEnabled(boolean enabled, boolean controlI2CRepeater) throws LibUsbException - { - byte value = enabled ? DC5_RANGE_DETECTOR_ENABLED : (byte) 0x0; - writeMaskedE4KRegister(Register.DC5, DC5_RANGE_DETECTOR_ENABLED_MASK, value, controlI2CRepeater); - } - - /** - * Sets the LNA gain within the E4K Tuner - *

- * Note: requires I2C repeater enabled - * - * @param gain - * @throws UsbException - */ - public void setLNAGain(E4KLNAGain gain, boolean controlI2CRepeater) throws UsbException - { - getAdapter().getLock().lock(); - - try - { - if(controlI2CRepeater) - { - getAdapter().enableI2CRepeater(); - } - - if(gain == E4KLNAGain.AUTOMATIC) - { - writeMaskedE4KRegister(Register.AGC1, AGC1_MOD_MASK, AGCMode.IF_SERIAL_LNA_AUTON.getValue(), false); - } - else - { - writeMaskedE4KRegister(Register.AGC1, AGC1_MOD_MASK, AGCMode.SERIAL.getValue(), false); - writeMaskedE4KRegister(E4KLNAGain.getRegister(), E4KLNAGain.getMask(), gain.getValue(), false); - } - - if(controlI2CRepeater) - { - getAdapter().disableI2CRepeater(); - } - } - finally - { - getAdapter().getLock().unlock(); - } - } - - /** - * Reads LNA gain from E4K tuner - *

- * Note: requires I2C repeater enabled - */ - public E4KLNAGain getLNAGain(boolean controlI2CRepeater) throws UsbException - { - return E4KLNAGain.fromRegisterValue(readE4KRegister(E4KLNAGain.getRegister(), controlI2CRepeater)); - } - - /** - * Sets enhanced gain for E4K repeater. - *

- * Note: requires I2C repeater enabled - * - * @param gain - * @throws UsbException - */ - public void setEnhanceGain(E4KEnhanceGain gain, boolean controlI2CRepeater) throws UsbException - { - writeMaskedE4KRegister(E4KEnhanceGain.getRegister(), E4KEnhanceGain.getMask(), gain.getValue(), controlI2CRepeater); - } - - /** - * Gets the enhanced gain setting in the E4K tuner. - *

- * Note: requires I2C repeater - * - * @return - * @throws UsbException - */ - public E4KEnhanceGain getEnhanceGain(boolean controlI2CRepeater) throws UsbException - { - return E4KEnhanceGain.fromRegisterValue(readE4KRegister(E4KEnhanceGain.getRegister(), controlI2CRepeater)); - } - - /** - * Sets the mixer gain - * - * @param gain to apply - * @param controlI2CRepeater set to true to turn on/off repeater within this method or false if already enabled. - * @throws UsbException on error - */ - public void setMixerGain(E4KMixerGain gain, boolean controlI2CRepeater) throws UsbException - { - getAdapter().getLock().lock(); - - try - { - if(controlI2CRepeater) - { - getAdapter().enableI2CRepeater(); - } - - boolean repeaterControl = false; - - if(gain == E4KMixerGain.AUTOMATIC) - { - writeMaskedE4KRegister(Register.AGC7, AGC7_MIXER_GAIN_MASK, AGC7_MIX_GAIN_AUTO, repeaterControl); - } - else - { - writeMaskedE4KRegister(Register.AGC7, AGC7_MIXER_GAIN_MASK, AGC7_MIX_GAIN_MANUAL, repeaterControl); - /* Set the desired manual gain setting */ - writeMaskedE4KRegister(E4KMixerGain.getRegister(), E4KMixerGain.getMask(), gain.getValue(), repeaterControl); - } - - if(controlI2CRepeater) - { - getAdapter().disableI2CRepeater(); - } - } - finally - { - getAdapter().getLock().unlock(); - } - } - - public E4KMixerGain getMixerGain(boolean controlI2CRepeater) throws UsbException - { - byte autoOrManual = readMaskedE4KRegister(Register.AGC7, AGC7_MIXER_GAIN_MASK, controlI2CRepeater); - - if(autoOrManual == AGC7_MIX_GAIN_AUTO) - { - return E4KMixerGain.AUTOMATIC; - } - else - { - int register = readE4KRegister(E4KMixerGain.getRegister(), controlI2CRepeater); - return E4KMixerGain.fromRegisterValue(register); - } - } - - /** - * Sets IF Gains 1 - 6 using a set of linear gain values defined in the IFGain enumeration - * - * @param gain to apply - * @param controlI2CRepeater set false if I2C repeater is already enabled or true if the I2C repeater should - * be turned on and off within this method. - * @throws LibUsbException if the write operation doesn't complete successfully - */ - public void setIFGain(IFGain gain, boolean controlI2CRepeater) throws LibUsbException - { - getAdapter().getLock().lock(); - - try - { - byte linearityMode = gain.isLinearity() ? (byte) 0x00 : (byte) 0x10; - writeMaskedE4KRegister(Register.AGC1, gain.getLinearityModeMask(), linearityMode, controlI2CRepeater); - writeMaskedE4KRegister(Register.GAIN3, gain.getGain3Mask(), gain.getGain3(), controlI2CRepeater); - writeMaskedE4KRegister(Register.GAIN4, gain.getGain4Mask(), gain.getGain4(), controlI2CRepeater); - } - finally - { - getAdapter().getLock().unlock(); - } - } - - /** - * Sets the IF Stage 1 gain value. - */ - public void setIFStage1Gain(IFStage1Gain gain, boolean controlI2CRepeater) - { - if(controlI2CRepeater) - { - getAdapter().enableI2CRepeater(); - } - - writeMaskedE4KRegister(Register.GAIN3, gain.getValue(), gain.getMask(), false); - - if(controlI2CRepeater) - { - getAdapter().disableI2CRepeater(); - } - } - - public void setMixerFilter(MixerFilter filter, boolean controlI2CRepeater) throws LibUsbException - { - writeMaskedE4KRegister(MixerFilter.getRegister(), MixerFilter.getMask(), filter.getValue(), controlI2CRepeater); - } - - public MixerFilter getMixerFilter(boolean controlI2CRepeater) throws UsbException - { - int value = readE4KRegister(MixerFilter.getRegister(), controlI2CRepeater); - return MixerFilter.fromRegisterValue(value); - } - - public void setRCFilter(RCFilter filter, boolean controlI2CRepeater) throws LibUsbException - { - writeMaskedE4KRegister(RCFilter.getRegister(), RCFilter.getMask(), filter.getValue(), controlI2CRepeater); - } - - public RCFilter getRCFilter(boolean controlI2CRepeater) throws UsbException - { - int value = readE4KRegister(RCFilter.getRegister(), controlI2CRepeater); - return RCFilter.fromRegisterValue(value); - } - - public void setChannelFilter(ChannelFilter filter, boolean controlI2CRepeater) throws LibUsbException - { - writeMaskedE4KRegister(ChannelFilter.getRegister(), ChannelFilter.getMask(), filter.getValue(), controlI2CRepeater); - } - - public ChannelFilter getChannelFilter(boolean controlI2CRepeater) throws UsbException - { - int value = readE4KRegister(ChannelFilter.getRegister(), controlI2CRepeater); - return ChannelFilter.fromRegisterValue(value); - } - - public void setChannelFilterEnabled(boolean enabled, boolean controlI2CRepeater) throws UsbException - { - if(enabled) - { - writeMaskedE4KRegister(Register.FILT3, FILT3_MASK, FILT3_ENABLE, controlI2CRepeater); - } - else - { - writeMaskedE4KRegister(Register.FILT3, FILT3_MASK, FILT3_DISABLE, controlI2CRepeater); - } - } - - /** - * Enables or disables VCO auto-calibration. - */ - public void setVcoAutoCalibration(boolean enabled, boolean controlI2CRepeater) - { - byte value = enabled ? (byte) 0x01 : (byte) 0x00; - byte mask = (byte) 0x3; - writeMaskedE4KRegister(Register.SYNTH8, mask, value, controlI2CRepeater); - } - - public void setBand(long frequency, boolean controlI2CRepeater) throws UsbException - { - if(controlI2CRepeater) - { - getAdapter().enableI2CRepeater(); - } - - FrequencyBand frequencyBand = FrequencyBand.fromFrequency(frequency); - - /* Set the bias */ - switch(frequencyBand) - { - case UHF: - case VHF2: - case VHF3: - writeE4KRegister(Register.BIAS, (byte) 0x3, false); - break; - case L_BAND: - default: - writeE4KRegister(Register.BIAS, (byte) 0x0, false); - break; - - } - - /** - * Workaround - reset register (set value to 0) before writing to it, to - * avoid a gap around 325-350 MHz - */ - writeMaskedE4KRegister(Register.SYNTH1, FrequencyBand.getMask(), (byte) 0x0, false); - writeMaskedE4KRegister(Register.SYNTH1, FrequencyBand.getMask(), frequencyBand.getValue(), false); - - if(controlI2CRepeater) - { - getAdapter().disableI2CRepeater(); - } - } - - private void setRFFilter(long frequency, boolean controlI2CRepeater) throws UsbException - { - RFFilter filter = RFFilter.fromFrequency(frequency); - writeMaskedE4KRegister(Register.FILT1, RFFilter.getMask(), filter.getValue(), controlI2CRepeater); - } - - /** - * Sets the master gain combination setting. - * Note: does not respond to the MANUAL setting. Use the manual setting to configure gui components to expose the - * individual Mixer, LNA and Enhance gain settings, to allow the user to change those settings individually. - * - * @param gain - requested gain setting - * @throws UsbException if there are any errors - */ - public void setGain(E4KGain gain, boolean controlI2CRepeater) throws UsbException - { - getAdapter().getLock().lock(); - - try - { - if(gain != E4KGain.MANUAL) - { - if(controlI2CRepeater) - { - getAdapter().enableI2CRepeater(); - } - - boolean i2CRepeaterControl = false; - - setLNAGain(gain.getLNAGain(), i2CRepeaterControl); - setMixerGain(gain.getMixerGain(), i2CRepeaterControl); - - if(controlI2CRepeater) - { - getAdapter().disableI2CRepeater(); - } - } - } - finally - { - getAdapter().getLock().unlock(); - } - } - - /* - * Performs magic initialization ... and that's all we need to know, I guess - */ - private void magicInit(boolean controlI2CRepeater) throws UsbException - { - if(controlI2CRepeater) - { - getAdapter().enableI2CRepeater(); - } - - writeE4KRegister(Register.MAGIC_1, (byte) 0x01, false); - writeE4KRegister(Register.MAGIC_2, (byte) 0xFE, false); - writeE4KRegister(Register.MAGIC_3, (byte) 0x00, false); - writeE4KRegister(Register.MAGIC_4, (byte) 0x50, false); //Polarity A - writeE4KRegister(Register.MAGIC_5, (byte) 0x20, false); - writeE4KRegister(Register.MAGIC_6, (byte) 0x01, false); - writeE4KRegister(Register.MAGIC_7, (byte) 0x7F, false); - writeE4KRegister(Register.MAGIC_8, (byte) 0x07, false); - - if(controlI2CRepeater) - { - getAdapter().disableI2CRepeater(); - } - } - - private byte readMaskedE4KRegister(Register register, byte mask, boolean controlI2CRepeater) throws UsbException - { - int temp = readE4KRegister(register, controlI2CRepeater); - return (byte) (temp & mask); - } - - private void writeMaskedE4KRegister(Register register, byte mask, byte value, boolean controlI2CRepeater) throws LibUsbException - { - int temp = readE4KRegister(register, controlI2CRepeater); - - /* If the register is not set to the masked value, then change it */ - if((byte) (temp & mask) != value) - { - writeE4KRegister(register, (byte) ((temp & ~mask) | (value & mask)), controlI2CRepeater); - readE4KRegister(register, controlI2CRepeater); - } - } - - private int readE4KRegister(Register register, boolean controlI2CRepeater) throws LibUsbException - { - return getAdapter().readI2CRegister(Register.I2C_REGISTER.getValue(), register.getValue(), controlI2CRepeater); - } - - - private void writeE4KRegister(Register register, byte value, boolean controlI2CRepeater) throws LibUsbException - { - getAdapter().writeI2CRegister(Register.I2C_REGISTER.getValue(), register.getValue(), value, controlI2CRepeater); - } - - /** - * Frequency bands enumeration - */ - public enum FrequencyBand - { - VHF2(0), //64-108 MHz - VHF3(2), //170-240 MHz - UHF(4), //470-858 MHz - L_BAND(6); //1452-1680 MHz - - private int mValue; - - FrequencyBand(int value) - { - mValue = value; - } - - public static byte getMask() - { - return (byte) 0x06; - } - - public byte getValue() - { - return (byte) mValue; - } - - public String getLabel() - { - return toString(); - } - - public static FrequencyBand fromFrequency(long frequency) - { - if(frequency < 140000000) //140 MHz - { - return VHF2; - } - else if(frequency < 350000000) //350 MHz - { - return VHF3; - } - else if(frequency < 1135000000) //1135 MHz - { - return UHF; - } - else - { - return L_BAND; - } - } - } - - /** - * PLL enumeration for SYNTH7 (0x0D) Register to control the 'R' VCO output divider and 3-phase mixing. - *

- * Note: 3-phase mixing (1-bit) and R index (3-bits) are combined in the index value that is written to the register. - */ - public enum PLL - { - PLL_72M4(0x0F, 72400000, 48, 600000, true, "72.4 MHz"), - PLL_81M2(0x0E, 81200000, 40, 720000, true, "81.2 MHz"), - PLL_108M3(0x0D, 108300000, 32, 900000, true, "108.3 MHz"), - PLL_162M5(0x0C, 162500000, 24, 1200000, true, "162.5 MHz"), - PLL_216M6(0x0B, 216600000, 16, 1800000, true, "216.6 MHz"), - PLL_325M0(0x0A, 325000000, 12, 2400000, true, "325.0 MHz"), - PLL_350M0(0x09, 350000000, 8, 3600000, true, "350.0 MHz"), - PLL_432M0(0x03, 432000000, 8, 3600000, false, "432.0 MHz"), - PLL_667M0(0x02, 667000000, 6, 4800000, false, "667.0 MHz"), - PLL_1200M0(0x01, 1200000000, 4, 7200000, false, "1200.0 MHz"); - - private int mIndex; - private long mFrequency; - private int mMultiplier; - private int mScaledOscillator; - private boolean mRequires3PhaseMixing; - private String mLabel; - - PLL(int index, long frequency, int multiplier, int scaledOscillator, boolean requires3Phase, String label) - { - mIndex = index; - mFrequency = frequency; - mMultiplier = multiplier; - mScaledOscillator = scaledOscillator; - mRequires3PhaseMixing = requires3Phase; - mLabel = label; - } - - public byte getIndex() - { - return (byte) mIndex; - } - - public long getFrequency() - { - return mFrequency; - } - - public byte getMultiplier() - { - return (byte) mMultiplier; - } - - public int getScaledOscillator() - { - return mScaledOscillator; - } - - public boolean requires3PhaseMixing() - { - return mRequires3PhaseMixing; - } - - public String getLabel() - { - return mLabel; - } - - public String toString() - { - return mLabel; - } - - /** - * Returns the PLL setting with the closest frequency that is greater than the frequency argument - */ - public static PLL fromFrequency(long frequency) - { - for(PLL pll : values()) - { - if(frequency < pll.getFrequency()) - { - return pll; - } - } - - //Default - return PLL.PLL_72M4; - } - - public static PLL fromSetting(int setting) - { - int value = setting & 0xF; - - for(PLL pll : values()) - { - if(value == pll.getIndex()) - { - return pll; - } - } - - //Default - return PLL.PLL_72M4; - } - } - - /** - * RF filters - UHF Band. Each enum entry defines the center frequency for - * the filter and the applicable frequency minimums and maximums for the - * filter. - *

- * Note: max frequency overlaps with the next minimum frequency, so valid - * frequencies should be less than the maximum frequency defined for the - * filter. - */ - public enum RFFilter - { - NO_FILTER(0, 0, 0), - - //VHF-II Filters (0.0 - 140.0) Optimal (64-108) - LP268(0, MINIMUM_TUNABLE_FREQUENCY_HZ, 86_000_000), //Values 0 - 7 are all the same - LP299(8, 86_000_000, 140_000_000), //Values 8 - 15 are all the same - - //VHF-III Filters (140.0 - 350.0) Optimal (170-240) - LP509(0, 140_000_000, 245_000_000), //Values 0 - 7 are all the same - LP656(8, 245_000_000, 350_000_000), //Values 8 - 15 are all the same - - //UHF Filters (350.0 - 1135) Optimal (470-858) - BP360(0, 350000000, 370000000), - BP380(1, 370000000, 392500000), - BP405(2, 392500000, 417500000), - BP425(3, 417500000, 437500000), - BP450(4, 437500000, 462500000), - BP475(5, 462500000, 490000000), - BP505(6, 490000000, 522500000), - BP540(7, 522500000, 557500000), - BP575(8, 557500000, 595000000), - BP615(9, 595000000, 642500000), - BP670(10, 642500000, 695000000), - BP720(11, 695000000, 740000000), - BP760(12, 740000000, 800000000), - BP840(13, 800000000, 865000000), - BP890(14, 865000000, 930000000), - BP970(15, 930000000, 1135000000), - - //L Band Filters (1135.0 - 2147.0) Optimal (1452-1680) - BP1300(0, 1135000000, 1310000000), - BP1320(1, 1310000000, 1340000000), - BP1360(2, 1340000000, 1385000000), - BP1410(3, 1385000000, 1427500000), - BP1445(4, 1427500000, 1452500000), - BP1460(5, 1452500000, 1475000000), - BP1490(6, 1475000000, 1510000000), - BP1530(7, 1510000000, 1545000000), - BP1560(8, 1545000000, 1575000000), - BP1590(9, 1575000000, 1615000000), - BP1640(10, 1615000000, 1650000000), - BP1660(11, 1650000000, 1670000000), - BP1680(12, 1670000000, 1690000000), - BP1700(13, 1690000000, 1710000000), - BP1720(14, 1710000000, 1735000000), - BP1750(15, 1735000000, MAXIMUM_TUNABLE_FREQUENCY_HZ); - - private int mValue; - private long mMinFrequency; - private long mMaxFrequency; - - RFFilter(int value, long minFrequency, long maxFrequency) - { - mValue = value; - mMinFrequency = minFrequency; - mMaxFrequency = maxFrequency; - } - - public static Register getRegister() - { - return Register.FILT1; - } - - public static byte getMask() - { - return (byte) 0xF; - } - - public byte getValue() - { - return (byte) mValue; - } - - /** - * Minimum frequency for this filter. - */ - public long getMinimumFrequency() - { - return mMinFrequency; - } - - /** - * Maximum frequency for this filter is less than this value, but does - * not include this value. - */ - public long getMaximumFrequency() - { - return mMaxFrequency; - } - - /** - * Filter ranges by frequency band - */ - public static EnumSet VHFII_FILTERS = EnumSet.of(LP268, LP299); - public static EnumSet VHFIII_FILTERS = EnumSet.of(LP509, LP656); - public static EnumSet UHF_FILTERS = EnumSet.range(BP360, BP970); - public static EnumSet LBAND_FILTERS = EnumSet.of(BP1300, BP1750); - - /** - * Selects the appropriate filter for the frequency - * - * @param frequency - * @return selected filter - * @throws IllegalArgumentException if the frequency is outside the - * max value (2200 MHz) - */ - public static RFFilter fromFrequency(long frequency) - { - switch(FrequencyBand.fromFrequency(frequency)) - { - case VHF2: - for(RFFilter filter : VHFII_FILTERS) - { - if(filter.getMinimumFrequency() <= frequency && frequency < filter.getMaximumFrequency()) - { - return filter; - } - } - break; - case VHF3: - for(RFFilter filter : VHFIII_FILTERS) - { - if(filter.getMinimumFrequency() <= frequency && frequency < filter.getMaximumFrequency()) - { - return filter; - } - } - break; - case UHF: - for(RFFilter filter : UHF_FILTERS) - { - if(filter.getMinimumFrequency() <= frequency && frequency < filter.getMaximumFrequency()) - { - return filter; - } - } - break; - case L_BAND: - default: - for(RFFilter filter : LBAND_FILTERS) - { - if(filter.getMinimumFrequency() <= frequency && frequency < filter.getMaximumFrequency()) - { - return filter; - } - } - break; - } - - return NO_FILTER; - } - } - - public enum MixerFilter - { - BW_27M0(0x00, 27000000, 4800000, 28800000, "27.0 MHz"), - BW_4M6(0x80, 4600000, 4400000, 4800000, "4.6 MHz"), - BW_4M2(0x90, 4200000, 4000000, 4400000, "4.2 MHz"), - BW_3M8(0xA0, 3800000, 3600000, 4000000, "3.8 MHz"), - BW_3M4(0xB0, 3400000, 3200000, 3600000, "3.4 MHz"), - BW_3M0(0xC0, 3000000, 2850000, 3200000, "3.0 MHz"), - BW_2M7(0xD0, 2700000, 2500000, 2850000, "2.7 MHz"), - BW_2M3(0xE0, 2300000, 2100000, 2500000, "2.3 MHz"), - BW_1M9(0xF0, 1900000, 0, 2100000, "1.9 MHz"); - - private int mValue; - private int mBandwidth; - private int mMinBandwidth; - private int mMaxBandwidth; - private String mLabel; - - MixerFilter(int value, int bandwidth, int minimumBandwidth, int maximumBandwidth, String label) - { - mValue = value; - mBandwidth = bandwidth; - mMinBandwidth = minimumBandwidth; - mMaxBandwidth = maximumBandwidth; - mLabel = label; - } - - public static Register getRegister() - { - return Register.FILT2; - } - - public static byte getMask() - { - return MIXER_FILTER_MASK; - } - - public byte getValue() - { - return (byte) mValue; - } - - public int getBandwidth() - { - return mBandwidth; - } - - public int getMinimumBandwidth() - { - return mMinBandwidth; - } - - public int getMaximumBandwidth() - { - return mMaxBandwidth; - } - - public String getLabel() - { - return mLabel; - } - - /** - * Returns the correct filter to use for the bandwidth/sample rate - * - * @param bandwidth - current sample rate - * @return - correct filter to use for the bandwidth/sample rate - */ - public static MixerFilter getFilter(int bandwidth) - { - for(MixerFilter filter : values()) - { - if(filter.getMinimumBandwidth() <= bandwidth && bandwidth < filter.getMaximumBandwidth()) - { - return filter; - } - } - - /* default */ - return MixerFilter.BW_27M0; - } - - public static MixerFilter fromRegisterValue(int registerValue) - { - int value = registerValue & getMask(); - - for(MixerFilter filter : values()) - { - if(value == filter.getValue()) - { - return filter; - } - } - - throw new IllegalArgumentException("E4KTunerController - unrecognized mixer filter value [" + value + "]"); - } - } - - public enum RCFilter - { - BW_21M4(0x00, 21400000, 21200000, 28800000, "21.4 MHz"), - BW_21M0(0x01, 21000000, 19300000, 21200000, "21.0 MHz"), - BW_17M6(0x02, 17600000, 16150000, 19300000, "17.6 MHz"), - BW_14M7(0x03, 14700000, 13550000, 16150000, "14.7 MHz"), - BW_12M4(0x04, 12400000, 11500000, 13550000, "12.4 MHz"), - BW_10M6(0x05, 10600000, 9800000, 11500000, "10.6 MHz"), - BW_9M0(0x06, 9000000, 8350000, 9800000, "9.0 MHz"), - BW_7M7(0x07, 7700000, 7050000, 8350000, "7.7 MHz"), - BW_6M4(0x08, 6400000, 5805000, 7050000, "6.4 MHz"), - BW_5M3(0x09, 5300000, 4850000, 5805000, "5.3 MHz"), - BW_4M4(0x0A, 4400000, 3900000, 4850000, "4.4 MHz"), - BW_3M4(0x0B, 3400000, 3000000, 3900000, "3.4 MHz"), - BW_2M6(0x0C, 2600000, 2200000, 3000000, "2.6 MHz"), - BW_1M8(0x0D, 1800000, 1500000, 2200000, "1.8 MHz"), - BW_1M2(0x0E, 1200000, 1100000, 1500000, "1.2 MHz"), - BW_1M0(0x0F, 1000000, 0, 1100000, "1.0 MHz"); - - private int mValue; - private int mBandwidth; - private int mMinBandwidth; - private int mMaxBandwidth; - private String mLabel; - - RCFilter(int value, int bandwidth, int minimumBandwidth, int maximumBandwidth, String label) - { - mValue = value; - mBandwidth = bandwidth; - mMinBandwidth = minimumBandwidth; - mMaxBandwidth = maximumBandwidth; - mLabel = label; - } - - public static Register getRegister() - { - return Register.FILT2; - } - - public static byte getMask() - { - return IF_RC_FILTER_MASK; - } - - public byte getValue() - { - return (byte) mValue; - } - - public int getBandwidth() - { - return mBandwidth; - } - - public int getMinimumBandwidth() - { - return mMinBandwidth; - } - - public int getMaximumBandwidth() - { - return mMaxBandwidth; - } - - public String getLabel() - { - return mLabel; - } - - /** - * Returns the correct filter to use for the bandwidth/sample rate - * - * @param bandwidth - current sample rate - * @return - correct filter to use for the bandwidth/sample rate - */ - public static RCFilter getFilter(int bandwidth) - { - for(RCFilter filter : values()) - { - if(filter.getMinimumBandwidth() <= bandwidth && bandwidth < filter.getMaximumBandwidth()) - { - return filter; - } - } - - /* default */ - return RCFilter.BW_21M4; - } - - public static RCFilter fromRegisterValue(int registerValue) - { - int value = registerValue & getMask(); - - for(RCFilter filter : values()) - { - if(value == filter.getValue()) - { - return filter; - } - } - - throw new IllegalArgumentException("E4KTunerController - unrecognized rc filter value [" + value + "]"); - } - } - - public enum ChannelFilter - { - BW_5M50(0x00, 5500000, 5500000, 28800000, "5.50 MHz"), - BW_5M30(0x01, 5300000, 5300000, 5500000, "5.30 MHz"), - BW_5M00(0x02, 5000000, 5000000, 5300000, "5.00 MHz"), - BW_4M80(0x03, 4800000, 4800000, 5000000, "4.80 MHz"), - BW_4M60(0x04, 4600000, 4600000, 4800000, "4.60 MHz"), - BW_4M40(0x05, 4400000, 4400000, 4600000, "4.40 MHz"), - BW_4M30(0x06, 4300000, 4300000, 4400000, "4.30 MHz"), - BW_4M10(0x07, 4100000, 4100000, 4300000, "4.10 MHz"), - BW_3M90(0x08, 3900000, 3900000, 4100000, "3.90 MHz"), - BW_3M80(0x09, 3800000, 3800000, 3900000, "3.80 MHz"), - BW_3M70(0x0A, 3700000, 3700000, 3800000, "3.70 MHz"), - BW_3M60(0x0B, 3600000, 3600000, 3700000, "3.60 MHz"), - BW_3M40(0x0C, 3400000, 3400000, 3600000, "3.40 MHz"), - BW_3M30(0x0D, 3300000, 3300000, 3400000, "3.30 MHz"), - BW_3M20(0x0E, 3200000, 3200000, 3300000, "3.20 MHz"), - BW_3M10(0x0F, 3100000, 3100000, 3200000, "3.10 MHz"), - BW_3M00(0x10, 3000000, 3000000, 3100000, "3.00 MHz"), - BW_2M95(0x11, 2950000, 2950000, 3000000, "2.95 MHz"), - BW_2M90(0x12, 2900000, 2900000, 2950000, "2.90 MHz"), - BW_2M80(0x13, 2800000, 2800000, 2900000, "2.80 MHz"), - BW_2M75(0x14, 2750000, 2750000, 2800000, "2.75 MHz"), - BW_2M70(0x15, 2700000, 2700000, 2750000, "2.70 MHz"), - BW_2M60(0x16, 2600000, 2600000, 2700000, "2.60 MHz"), - BW_2M55(0x17, 2550000, 2550000, 2600000, "2.55 MHz"), - BW_2M50(0x18, 2500000, 2500000, 2550000, "2.50 MHz"), - BW_2M45(0x19, 2450000, 2450000, 2500000, "2.45 MHz"), - BW_2M40(0x1A, 2400000, 2400000, 2450000, "2.40 MHz"), - BW_2M30(0x1B, 2300000, 2300000, 2400000, "2.30 MHz"), - BW_2M28(0x1C, 2280000, 2280000, 2300000, "2.28 MHz"), - BW_2M24(0x1D, 2240000, 2240000, 2280000, "2.24 MHz"), - BW_2M20(0x1E, 2200000, 2200000, 2240000, "2.20 MHz"), - BW_2M15(0x1F, 2150000, 0, 2150000, "2.15 MHz"); - - private int mValue; - private int mBandwidth; - private int mMinBandwidth; - private int mMaxBandwidth; - private String mLabel; - - ChannelFilter(int value, int bandwidth, int minimumBandwidth, int maximumBandwidth, String label) - { - mValue = value; - mBandwidth = bandwidth; - mMinBandwidth = minimumBandwidth; - mMaxBandwidth = maximumBandwidth; - mLabel = label; - } - - public static Register getRegister() - { - return Register.FILT3; - } - - public static byte getMask() - { - return IF_CHANNEL_FILTER_MASK; - } - - public byte getValue() - { - return (byte) mValue; - } - - public int getBandwidth() - { - return mBandwidth; - } - - public int getMinimumBandwidth() - { - return mMinBandwidth; - } - - public int getMaximumBandwidth() - { - return mMaxBandwidth; - } - - public String getLabel() - { - return mLabel; - } - - /** - * Returns the correct filter to use for the bandwidth/sample rate - * - * @param bandwidth - current sample rate - * @return - correct filter to use for the bandwidth/sample rate - */ - public static ChannelFilter getFilter(int bandwidth) - { - for(ChannelFilter filter : values()) - { - if(filter.getMinimumBandwidth() <= bandwidth && bandwidth < filter.getMaximumBandwidth()) - { - return filter; - } - } - - /* default */ - return ChannelFilter.BW_5M50; - } - - public static ChannelFilter fromRegisterValue(int registerValue) - { - int value = registerValue & getMask(); - - for(ChannelFilter filter : values()) - { - if(value == filter.getValue()) - { - return filter; - } - } - - throw new IllegalArgumentException("E4KTunerController - unrecognized channel filter value [" + value + "]"); - } - } - - public enum E4KLNAGain - { - AUTOMATIC(-1, "Auto"), - GAIN_MINUS_50(0, "-5.0 db"), - GAIN_MINUS_25(1, "-2.5 db"), - GAIN_PLUS_0(4, "0.0 db"), - GAIN_PLUS_25(5, "2.5 db"), - GAIN_PLUS_50(6, "5.0 db"), - GAIN_PLUS_75(7, "7.5 db"), - GAIN_PLUS_100(8, "10.0 db"), - GAIN_PLUS_125(9, "12.5 db"), - GAIN_PLUS_150(10, "15.0 db"), - GAIN_PLUS_175(11, "17.5 db"), - GAIN_PLUS_200(12, "20.0 db"), - GAIN_PLUS_250(13, "25.0 db"), - GAIN_PLUS_300(14, "30.0 db"); - - private int mValue; - private String mLabel; - - E4KLNAGain(int value, String label) - { - mValue = value; - mLabel = label; - } - - public static Register getRegister() - { - return Register.GAIN1; - } - - public static byte getMask() - { - return GAIN1_MOD_MASK; - } - - public byte getValue() - { - return (byte) mValue; - } - - public String getLabel() - { - return mLabel; - } - - public String toString() - { - return mLabel; - } - - public static E4KLNAGain fromRegisterValue(int registerValue) - { - int value = registerValue & getMask(); - - for(E4KLNAGain setting : values()) - { - if(value == setting.getValue()) - { - return setting; - } - } - - throw new IllegalArgumentException("E4KTunerController - unrecognized LNA Gain value [" + value + "]"); - } - } - - /** - * Defines the default gain settings that are exposed to the user. - *

- * Manual is an override that keeps each of the settings intact and allows - * the user to change each of them individually - */ - public enum E4KGain - { - AUTOMATIC("Auto", E4KMixerGain.AUTOMATIC, E4KLNAGain.AUTOMATIC), - MANUAL("Manual", null, null), - MINUS_10("-1.0 db", E4KMixerGain.GAIN_4, E4KLNAGain.GAIN_MINUS_50), - PLUS_15("1.5 db", E4KMixerGain.GAIN_4, E4KLNAGain.GAIN_MINUS_25), - PLUS_40("4.0 db", E4KMixerGain.GAIN_4, E4KLNAGain.GAIN_PLUS_0), - PLUS_65("6.5 db", E4KMixerGain.GAIN_4, E4KLNAGain.GAIN_PLUS_25), - PLUS_90("9.0 db", E4KMixerGain.GAIN_4, E4KLNAGain.GAIN_PLUS_50), - PLUS_115("11.5 db", E4KMixerGain.GAIN_4, E4KLNAGain.GAIN_PLUS_75), - PLUS_140("14.0 db", E4KMixerGain.GAIN_4, E4KLNAGain.GAIN_PLUS_100), - PLUS_165("16.5 db", E4KMixerGain.GAIN_4, E4KLNAGain.GAIN_PLUS_125), - PLUS_190("19.0 db", E4KMixerGain.GAIN_4, E4KLNAGain.GAIN_PLUS_150), - PLUS_215("21.5 db", E4KMixerGain.GAIN_4, E4KLNAGain.GAIN_PLUS_175), - PLUS_240("24.0 db", E4KMixerGain.GAIN_4, E4KLNAGain.GAIN_PLUS_200), - PLUS_290("29.0 db", E4KMixerGain.GAIN_4, E4KLNAGain.GAIN_PLUS_250), - PLUS_340("34.0 db", E4KMixerGain.GAIN_4, E4KLNAGain.GAIN_PLUS_300), - PLUS_420("42.0 db", E4KMixerGain.GAIN_12, E4KLNAGain.GAIN_PLUS_300); - - private String mLabel; - private E4KMixerGain mMixerGain; - private E4KLNAGain mLNAGain; - - E4KGain(String label, E4KMixerGain mixerGain, E4KLNAGain lnaGain) - { - mLabel = label; - mMixerGain = mixerGain; - mLNAGain = lnaGain; - } - - public String getLabel() - { - return mLabel; - } - - public String toString() - { - return mLabel; - } - - public E4KMixerGain getMixerGain() - { - return mMixerGain; - } - - public E4KLNAGain getLNAGain() - { - return mLNAGain; - } - } - - public enum E4KEnhanceGain - { - AUTOMATIC(0, "Auto"), - GAIN_1(1, "10 db"), - GAIN_3(3, "30 db"), - GAIN_5(5, "50 db"), //Recommended - GAIN_7(7, "70 db"); - - private int mValue; - private String mLabel; - - E4KEnhanceGain(int value, String label) - { - mValue = value; - mLabel = label; - } - - public static Register getRegister() - { - return Register.AGC11; - } - - public static byte getMask() - { - return ENH_GAIN_MOD_MASK; - } - - public byte getValue() - { - return (byte) mValue; - } - - public String getLabel() - { - return mLabel; - } - - public String toString() - { - return mLabel; - } - - public static E4KEnhanceGain fromRegisterValue(int registerValue) - { - int value = registerValue & getMask(); - - for(E4KEnhanceGain setting : values()) - { - if(value == setting.getValue()) - { - return setting; - } - } - - throw new IllegalArgumentException("E4KTunerController - unrecognized Enhance Gain value [" + value + "]"); - } - } - - public enum DCGainCombination - { - LOOKUP_TABLE_0(Register.QLUT0, Register.ILUT0, E4KMixerGain.GAIN_4, IFStage1Gain.GAIN_MINUS3), - LOOKUP_TABLE_1(Register.QLUT1, Register.ILUT1, E4KMixerGain.GAIN_4, IFStage1Gain.GAIN_PLUS6), - LOOKUP_TABLE_2(Register.QLUT2, Register.ILUT2, E4KMixerGain.GAIN_12, IFStage1Gain.GAIN_MINUS3), - LOOKUP_TABLE_3(Register.QLUT3, Register.ILUT3, E4KMixerGain.GAIN_12, IFStage1Gain.GAIN_PLUS6); - - private Register mQRegister; - private Register mIRegister; - private E4KMixerGain mMixerGain; - private IFStage1Gain mIFStage1Gain; - - DCGainCombination(Register qRegister, Register iRegister, E4KMixerGain mixer, IFStage1Gain stage1) - { - mQRegister = qRegister; - mIRegister = iRegister; - mMixerGain = mixer; - mIFStage1Gain = stage1; - } - - public Register getQRegister() - { - return mQRegister; - } - - public Register getIRegister() - { - return mIRegister; - } - - public E4KMixerGain getMixerGain() - { - return mMixerGain; - } - - public IFStage1Gain getIFStage1Gain() - { - return mIFStage1Gain; - } - } - - - public enum E4KMixerGain - { - AUTOMATIC(-1, "Auto"), - GAIN_4(0, "4 db"), - GAIN_12(1, "12 db"); - - private int mValue; - private String mLabel; - - E4KMixerGain(int value, String label) - { - mValue = value; - mLabel = label; - } - - public static Register getRegister() - { - return Register.GAIN2; - } - - public static byte getMask() - { - return MIXER_GAIN_MASK; - } - - public byte getValue() - { - return (byte) mValue; - } - - public String getLabel() - { - return mLabel; - } - - public String toString() - { - return mLabel; - } - - public static E4KMixerGain fromRegisterValue(int registerValue) - { - int value = registerValue & getMask(); - - for(E4KMixerGain setting : values()) - { - if(value == setting.getValue()) - { - return setting; - } - } - - throw new IllegalArgumentException("E4KTunerController - unrecognized Mixer Gain value [" + value + "]"); - } - } - - /** - * Combines IF Gains 1 - 4 (register 0x16h GAIN3) and IF Gains 5 - 6 (register 0x17h GAIN4) into a contiguous - * range if IF gains, per data sheet table page 38, for both Linearity and Sensitivity modes. - */ - public enum IFGain - { - LINEARITY_6((byte) 0x00, (byte) 0x00, "6"), - LINEARITY_7((byte) 0x20, (byte) 0x00, "7"), - LINEARITY_8((byte) 0x40, (byte) 0x00, "8"), - LINEARITY_9((byte) 0x00, (byte) 0x08, "9"), - LINEARITY_10((byte) 0x20, (byte) 0x08, "10"), - LINEARITY_11((byte) 0x40, (byte) 0x08, "11"), - LINEARITY_12((byte) 0x00, (byte) 0x10, "12"), - LINEARITY_13((byte) 0x20, (byte) 0x10, "13"), - LINEARITY_14((byte) 0x40, (byte) 0x10, "14"), - LINEARITY_15((byte) 0x00, (byte) 0x18, "15"), - LINEARITY_16((byte) 0x20, (byte) 0x18, "16"), - LINEARITY_17((byte) 0x40, (byte) 0x18, "17"), - LINEARITY_18((byte) 0x00, (byte) 0x20, "18"), - LINEARITY_19((byte) 0x20, (byte) 0x20, "19"), - LINEARITY_20((byte) 0x40, (byte) 0x20, "20"), - LINEARITY_21((byte) 0x00, (byte) 0x21, "21"), - LINEARITY_22((byte) 0x20, (byte) 0x21, "22"), - LINEARITY_23((byte) 0x40, (byte) 0x21, "23"), - LINEARITY_24((byte) 0x00, (byte) 0x22, "24"), - LINEARITY_25((byte) 0x20, (byte) 0x22, "25"), - LINEARITY_26((byte) 0x40, (byte) 0x22, "26"), - LINEARITY_27((byte) 0x00, (byte) 0x23, "27"), - LINEARITY_28((byte) 0x20, (byte) 0x23, "28"), - LINEARITY_29((byte) 0x40, (byte) 0x23, "29"), - LINEARITY_30((byte) 0x00, (byte) 0x24, "30"), - LINEARITY_31((byte) 0x20, (byte) 0x24, "31"), - LINEARITY_32((byte) 0x40, (byte) 0x24, "32"), - LINEARITY_33((byte) 0x08, (byte) 0x24, "33"), - LINEARITY_34((byte) 0x28, (byte) 0x24, "34"), - LINEARITY_35((byte) 0x48, (byte) 0x24, "35"), - LINEARITY_36((byte) 0x10, (byte) 0x24, "36"), - LINEARITY_37((byte) 0x30, (byte) 0x24, "37"), - LINEARITY_38((byte) 0x50, (byte) 0x24, "38"), - LINEARITY_39((byte) 0x18, (byte) 0x24, "39"), - LINEARITY_40((byte) 0x38, (byte) 0x24, "40"), - LINEARITY_41((byte) 0x58, (byte) 0x24, "41"), - LINEARITY_42((byte) 0x1A, (byte) 0x24, "42"), - LINEARITY_43((byte) 0x3A, (byte) 0x24, "43"), - LINEARITY_44((byte) 0x5A, (byte) 0x24, "44"), - LINEARITY_45((byte) 0x1C, (byte) 0x24, "45"), - LINEARITY_46((byte) 0x3C, (byte) 0x24, "46"), - LINEARITY_47((byte) 0x5C, (byte) 0x24, "47"), - LINEARITY_48((byte) 0x19, (byte) 0x24, "48"), - LINEARITY_49((byte) 0x39, (byte) 0x24, "49"), - LINEARITY_50((byte) 0x59, (byte) 0x24, "50"), - LINEARITY_51((byte) 0x1B, (byte) 0x24, "51"), - LINEARITY_52((byte) 0x3B, (byte) 0x24, "52"), - LINEARITY_53((byte) 0x5B, (byte) 0x24, "53"), - LINEARITY_54((byte) 0x1D, (byte) 0x24, "54"), - LINEARITY_55((byte) 0x3D, (byte) 0x24, "55"), - LINEARITY_56((byte) 0x5D, (byte) 0x24, "56"), - LINEARITY_57((byte) 0x1F, (byte) 0x24, "57"), - LINEARITY_58((byte) 0x3F, (byte) 0x24, "58"), - LINEARITY_59((byte) 0x5F, (byte) 0x24, "59"), - LINEARITY_60((byte) 0x7F, (byte) 0x24, "60"), - - SENSITIVITY_6((byte) 0x00, (byte) 0x00, "6"), - SENSITIVITY_7((byte) 0x20, (byte) 0x00, "7"), - SENSITIVITY_8((byte) 0x40, (byte) 0x00, "8"), - SENSITIVITY_9((byte) 0x02, (byte) 0x00, "9"), - SENSITIVITY_10((byte) 0x22, (byte) 0x00, "10"), - SENSITIVITY_11((byte) 0x44, (byte) 0x00, "11"), - SENSITIVITY_12((byte) 0x04, (byte) 0x00, "12"), - SENSITIVITY_13((byte) 0x24, (byte) 0x00, "13"), - SENSITIVITY_14((byte) 0x44, (byte) 0x00, "14"), - SENSITIVITY_15((byte) 0x01, (byte) 0x00, "15"), - SENSITIVITY_16((byte) 0x21, (byte) 0x00, "16"), - SENSITIVITY_17((byte) 0x41, (byte) 0x00, "17"), - SENSITIVITY_18((byte) 0x03, (byte) 0x00, "18"), - SENSITIVITY_19((byte) 0x23, (byte) 0x00, "19"), - SENSITIVITY_20((byte) 0x43, (byte) 0x00, "20"), - SENSITIVITY_21((byte) 0x05, (byte) 0x00, "21"), - SENSITIVITY_22((byte) 0x25, (byte) 0x00, "22"), - SENSITIVITY_23((byte) 0x45, (byte) 0x00, "23"), - SENSITIVITY_24((byte) 0x07, (byte) 0x00, "24"), - SENSITIVITY_25((byte) 0x27, (byte) 0x00, "25"), - SENSITIVITY_26((byte) 0x47, (byte) 0x00, "26"), - SENSITIVITY_27((byte) 0x0F, (byte) 0x00, "27"), - SENSITIVITY_28((byte) 0x2F, (byte) 0x00, "28"), - SENSITIVITY_29((byte) 0x4F, (byte) 0x00, "29"), - SENSITIVITY_30((byte) 0x17, (byte) 0x00, "30"), - SENSITIVITY_31((byte) 0x37, (byte) 0x00, "31"), - SENSITIVITY_32((byte) 0x57, (byte) 0x00, "32"), - SENSITIVITY_33((byte) 0x1F, (byte) 0x00, "33"), - SENSITIVITY_34((byte) 0x3F, (byte) 0x00, "34"), - SENSITIVITY_35((byte) 0x5F, (byte) 0x00, "35"), - SENSITIVITY_36((byte) 0x1F, (byte) 0x01, "36"), - SENSITIVITY_37((byte) 0x3F, (byte) 0x01, "37"), - SENSITIVITY_38((byte) 0x5F, (byte) 0x01, "38"), - SENSITIVITY_39((byte) 0x1F, (byte) 0x02, "39"), - SENSITIVITY_40((byte) 0x3F, (byte) 0x02, "40"), - SENSITIVITY_41((byte) 0x5F, (byte) 0x02, "41"), - SENSITIVITY_42((byte) 0x1F, (byte) 0x03, "42"), - SENSITIVITY_43((byte) 0x3F, (byte) 0x03, "43"), - SENSITIVITY_44((byte) 0x5F, (byte) 0x03, "44"), - SENSITIVITY_45((byte) 0x1F, (byte) 0x04, "45"), - SENSITIVITY_46((byte) 0x3F, (byte) 0x04, "46"), - SENSITIVITY_47((byte) 0x5F, (byte) 0x04, "47"), - SENSITIVITY_48((byte) 0x1F, (byte) 0x0C, "48"), - SENSITIVITY_49((byte) 0x3F, (byte) 0x0C, "49"), - SENSITIVITY_50((byte) 0x5F, (byte) 0x0C, "50"), - SENSITIVITY_51((byte) 0x1F, (byte) 0x14, "51"), - SENSITIVITY_52((byte) 0x3F, (byte) 0x14, "52"), - SENSITIVITY_53((byte) 0x5F, (byte) 0x14, "53"), - SENSITIVITY_54((byte) 0x1F, (byte) 0x1C, "54"), - SENSITIVITY_55((byte) 0x3F, (byte) 0x1C, "55"), - SENSITIVITY_56((byte) 0x5F, (byte) 0x1C, "56"), - SENSITIVITY_57((byte) 0x1F, (byte) 0x24, "57"), - SENSITIVITY_58((byte) 0x3F, (byte) 0x24, "58"), - SENSITIVITY_59((byte) 0x5F, (byte) 0x24, "59"), - SENSITIVITY_60((byte) 0x7F, (byte) 0x24, "60"); - - private byte mGain3; - private byte mGain4; - private String mLabel; - - IFGain(byte gain3, byte gain4, String label) - { - mGain3 = gain3; - mGain4 = gain4; - mLabel = label; - } - - /** - * Value to apply to AGC3 register - */ - public byte getGain3() - { - return mGain3; - } - - /** - * Value to apply to AGC4 register - */ - public byte getGain4() - { - return mGain4; - } - - /** - * Register mask for AGC3 - */ - public byte getGain3Mask() - { - return (byte) 0x7F; - } - - /** - * Register mask for AGC4 - */ - public byte getGain4Mask() - { - return (byte) 0x3F; - } - - /** - * Linearity mode mask for AGC1 (0x1A) register - */ - public byte getLinearityModeMask() - { - return (byte) 0x10; - } - - /** - * Set of linearity gains - */ - public static EnumSet LINEARITY_GAINS = EnumSet.range(LINEARITY_6, LINEARITY_60); - - /** - * Indicates if this is a linearity or sensitivity mode - * - * @return true if linearity or false if sensitivity mode - */ - public boolean isLinearity() - { - return LINEARITY_GAINS.contains(this); - } - - @Override - public String toString() - { - if(isLinearity()) - { - return "Linearity " + mLabel + " dB"; - } - else - { - return "Sensitivity " + mLabel + " dB"; - } - } - } - - public enum IFStage1Gain - { - GAIN_MINUS3(0x0, "-3 db"), - GAIN_PLUS6(0x1, "6 db"); - - private int mValue; - private String mLabel; - - IFStage1Gain(int value, String label) - { - mValue = value; - mLabel = label; - } - - public static Register getRegister() - { - return Register.GAIN3; - } - - public byte getMask() - { - return (byte) 0x1; - } - - public byte getValue() - { - return (byte) mValue; - } - - public String getLabel() - { - return mLabel; - } - - public String toString() - { - return mLabel; - } - } - - public enum IFStage2Gain - { - GAIN_PLUS0(0x0, "0 db"), - GAIN_PLUS3(0x2, "3 db"), - GAIN_PLUS6(0x4, "6 db"), - GAIN_PLUS9(0x6, "9 db"); - - private int mValue; - private String mLabel; - - IFStage2Gain(int value, String label) - { - mValue = value; - mLabel = label; - } - - public static Register getRegister() - { - return Register.GAIN3; - } - - public static byte getMask() - { - return (byte) 0x6; - } - - public byte getValue() - { - return (byte) mValue; - } - - public String getLabel() - { - return mLabel; - } - - public String toString() - { - return mLabel; - } - - public static IFStage2Gain fromRegisterValue(int registerValue) - { - int value = registerValue & getMask(); - - for(IFStage2Gain setting : values()) - { - if(value == setting.getValue()) - { - return setting; - } - } - - throw new IllegalArgumentException("E4KTunerController - unrecognized IF Gain Stage 2 value [" + value + "]"); - } - } - - public enum IFStage3Gain - { - GAIN_PLUS0(0x00, "0 db"), - GAIN_PLUS3(0x08, "3 db"), - GAIN_PLUS6(0x10, "6 db"), - GAIN_PLUS9(0x30, "9 db"); - - private int mValue; - private String mLabel; - - IFStage3Gain(int value, String label) - { - mValue = value; - mLabel = label; - } - - public static Register getRegister() - { - return Register.GAIN3; - } - - public static byte getMask() - { - return (byte) 0x18; - } - - public byte getValue() - { - return (byte) mValue; - } - - public String getLabel() - { - return mLabel; - } - - public String toString() - { - return mLabel; - } - - public static IFStage3Gain fromRegisterValue(int registerValue) - { - int value = registerValue & getMask(); - - for(IFStage3Gain setting : values()) - { - if(value == setting.getValue()) - { - return setting; - } - } - - throw new IllegalArgumentException("E4KTunerController - unrecognized IF Gain Stage 3 value [" + value + "]"); - } - } - - public enum IFStage4Gain - { - GAIN_PLUS0(0x00, "0 db"), - GAIN_PLUS1(0x20, "1 db"), - GAIN_PLUS2A(0x40, "2 db"), - GAIN_PLUS2B(0x60, "2 db"); - - private int mValue; - private String mLabel; - - IFStage4Gain(int value, String label) - { - mValue = value; - mLabel = label; - } - - public static Register getRegister() - { - return Register.GAIN3; - } - - public static byte getMask() - { - return (byte) 0x60; - } - - public byte getValue() - { - return (byte) mValue; - } - - public String getLabel() - { - return mLabel; - } - - public String toString() - { - return mLabel; - } - - public static IFStage4Gain fromRegisterValue(int registerValue) - { - int value = registerValue & getMask(); - - for(IFStage4Gain setting : values()) - { - if(value == setting.getValue()) - { - return setting; - } - } - - throw new IllegalArgumentException("E4KTunerController - unrecognized IF Gain Stage 4 value [" + value + "]"); - } - } - - public enum IFStage5Gain - { - GAIN_PLUS3(0x0, "3 db"), - GAIN_PLUS6(0x1, "6 db"), - GAIN_PLUS9(0x2, "9 db"), - GAIN_PLUS12(0x3, "12 db"), - GAIN_PLUS15A(0x4, "15 db"), - GAIN_PLUS15B(0x5, "15 db"), - GAIN_PLUS15C(0x6, "15 db"), - GAIN_PLUS15D(0x7, "15 db"); - - private int mValue; - private String mLabel; - - IFStage5Gain(int value, String label) - { - mValue = value; - mLabel = label; - } - - public static Register getRegister() - { - return Register.GAIN4; - } - - public static byte getMask() - { - return (byte) 0x7; - } - - public byte getValue() - { - return (byte) mValue; - } - - public String getLabel() - { - return mLabel; - } - - public String toString() - { - return mLabel; - } - - public static IFStage5Gain fromRegisterValue(int registerValue) - { - int value = registerValue & getMask(); - - for(IFStage5Gain setting : values()) - { - if(value == setting.getValue()) - { - return setting; - } - } - - throw new IllegalArgumentException("E4KTunerController - unrecognized IF Gain Stage 5 value [" + value + "]"); - } - - } - - public enum IFStage6Gain - { - GAIN_PLUS3(0x00, "3 db"), - GAIN_PLUS6(0x08, "6 db"), - GAIN_PLUS9(0x10, "9 db"), - GAIN_PLUS12(0x18, "12 db"), - GAIN_PLUS15A(0x20, "15 db"), - GAIN_PLUS15B(0x28, "15 db"), - GAIN_PLUS15C(0x30, "15 db"), - GAIN_PLUS15D(0x38, "15 db"); - - private int mValue; - private String mLabel; - - IFStage6Gain(int value, String label) - { - mValue = value; - mLabel = label; - } - - public static Register getRegister() - { - return Register.GAIN4; - } - - public static byte getMask() - { - return (byte) 0x38; - } - - public byte getValue() - { - return (byte) mValue; - } - - public String getLabel() - { - return mLabel; - } - - public String toString() - { - return mLabel; - } - - public static IFStage6Gain fromRegisterValue(int registerValue) - { - int value = registerValue & getMask(); - - for(IFStage6Gain setting : values()) - { - if(value == setting.getValue()) - { - return setting; - } - } - - throw new IllegalArgumentException("E4KTunerController - unrecognized IF Gain Stage 6 value [" + value + "]"); - } - } - - public enum IFFilter - { - MIX("Mix"), - CHAN("Channel"), - RC("RC"); - - private String mLabel; - - IFFilter(String label) - { - mLabel = label; - } - - public String getLabel() - { - return mLabel; - } - } - - public enum AGCMode - { - SERIAL((byte) 0x0), - IF_PWM_LNA_SERIAL((byte) 0x1), - IF_PWM_LNA_AUTONL((byte) 0x2), - IF_PWM_LNA_SUPERV((byte) 0x3), - IF_SERIAL_LNA_PWM((byte) 0x4), - IF_PWM_LNA_PWM((byte) 0x5), - IF_DIG_LNA_SERIAL((byte) 0x6), - IF_DIG_LNA_AUTON((byte) 0x7), - IF_DIG_LNA_SUPERV((byte) 0x8), - IF_SERIAL_LNA_AUTON((byte) 0x9), - IF_SERIAL_LNA_SUPERV((byte) 0xA); - - private byte mValue; - - AGCMode(byte value) - { - mValue = value; - } - - public byte getValue() - { - return mValue; - } - } - - public enum Register - { - DUMMY(0x00), - MASTER1(0x00), - MASTER2(0x01), - MASTER3(0x02), - MASTER4(0x03), - MASTER5(0x04), - CLK_INP(0x05), - REF_CLK(0x06), - SYNTH1(0x07), - SYNTH2(0x08), - SYNTH3(0x09), - SYNTH4(0x0A), - SYNTH5(0x0B), - SYNTH6(0x0C), - SYNTH7(0x0D), - SYNTH8(0x0E), - SYNTH9(0x0F), - FILT1(0x10), - FILT2(0x11), - FILT3(0x12), - GAIN1(0x14), - GAIN2(0x15), - GAIN3(0x16), - GAIN4(0x17), - AGC1(0x1A), - AGC2(0x1B), - AGC3(0x1C), - AGC4(0x1D), - AGC5(0x1E), - AGC6(0x1F), - AGC7(0x20), - AGC8(0x21), - AGC11(0x24), - AGC12(0x25), - DC1(0x29), - DC2(0x2A), - DC3(0x2B), - DC4(0x2C), - DC5(0x2D), - DC6(0x2E), - DC7(0x2F), - DC8(0x30), - QLUT0(0x50), - QLUT1(0x51), - QLUT2(0x52), - QLUT3(0x53), - ILUT0(0x60), - ILUT1(0x61), - ILUT2(0x62), - ILUT3(0x63), - DCTIME1(0x70), - DCTIME2(0x71), - DCTIME3(0x72), - DCTIME4(0x73), - PWM1(0x74), - PWM2(0x75), - PWM3(0x76), - PWM4(0x77), - BIAS(0x78), - CLKOUT_PWDN(0x7A), - CHFILT_CALIB(0x7B), - I2C_REG_ADDR(0x7D), - MAGIC_1(0x7E), - MAGIC_2(0x7F), - MAGIC_3(0x82), - MAGIC_4(0x86), - MAGIC_5(0x87), - MAGIC_6(0x88), - MAGIC_7(0x9F), - MAGIC_8(0xA0), - I2C_REGISTER(0xC8); - - private int mValue; - - Register(int value) - { - mValue = value; - } - - public byte getValue() - { - return (byte) mValue; - } - } -} \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/source/tuner/rtl/e4k/E4KTunerConfiguration.java b/src/main/java/io/github/dsheirer/source/tuner/rtl/e4k/E4KTunerConfiguration.java index 0df9dedb..2f766ddc 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/rtl/e4k/E4KTunerConfiguration.java +++ b/src/main/java/io/github/dsheirer/source/tuner/rtl/e4k/E4KTunerConfiguration.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2024 Dennis Sheirer + * Copyright (C) 2014-2026 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 @@ -19,30 +19,53 @@ package io.github.dsheirer.source.tuner.rtl.e4k; import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonSetter; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; import io.github.dsheirer.source.tuner.TunerType; import io.github.dsheirer.source.tuner.rtl.RTL2832TunerConfiguration; -import io.github.dsheirer.source.tuner.rtl.e4k.E4KEmbeddedTuner.E4KGain; -import io.github.dsheirer.source.tuner.rtl.e4k.E4KEmbeddedTuner.E4KLNAGain; -import io.github.dsheirer.source.tuner.rtl.e4k.E4KEmbeddedTuner.E4KMixerGain; -import io.github.dsheirer.source.tuner.rtl.e4k.E4KEmbeddedTuner.IFGain; +import io.github.dsheirer.source.tuner.rtl.RTL2832TunerController; /** * RTL-2832 with embedded E4000 tuner configuration */ public class E4KTunerConfiguration extends RTL2832TunerConfiguration { - private E4KGain mMasterGain = E4KGain.MANUAL; - private E4KMixerGain mMixerGain = E4KMixerGain.GAIN_4; - private E4KLNAGain mLNAGain = E4KLNAGain.GAIN_PLUS_200; - private IFGain mIFGain = IFGain.LINEARITY_8; + public static final long MINIMUM_TUNABLE_FREQUENCY_HZ = 52_000_000; + public static final long MAXIMUM_TUNABLE_FREQUENCY_HZ = 2_200_000_000l; + public static final double USABLE_BANDWIDTH_PERCENT = 0.95; + public static final int DC_SPIKE_AVOID_BUFFER = 15000; + /** + * Default gain in tenths of a dB + */ + public static final int DEFAULT_GAIN = 240; + /** + * Default total IF gain in dB + */ + public static final int DEFAULT_IF_GAIN = 24; + + private int mIFGain = DEFAULT_IF_GAIN; + + //Legacy gain settings, used to convert configurations from the previous E4000 driver's master, LNA and mixer gains + private boolean mLegacyManualGain = false; + private Integer mLegacyLNAGain; + private Integer mLegacyMixerGain; /** - * Default constructor for JAXB + * Default constructor for Jackson */ public E4KTunerConfiguration() { - super(E4KEmbeddedTuner.MINIMUM_TUNABLE_FREQUENCY_HZ, E4KEmbeddedTuner.MAXIMUM_TUNABLE_FREQUENCY_HZ); + super(MINIMUM_TUNABLE_FREQUENCY_HZ, MAXIMUM_TUNABLE_FREQUENCY_HZ, DEFAULT_GAIN); + } + + /** + * Constructs an instance + * + * @param uniqueID for the tuner + */ + public E4KTunerConfiguration(String uniqueID) + { + super(uniqueID, DEFAULT_GAIN); } @JsonIgnore @@ -53,56 +76,95 @@ public class E4KTunerConfiguration extends RTL2832TunerConfiguration } /** - * Constructs an instance - * - * @param uniqueID for the tuner + * Total IF gain, distributed across the six IF gain stages. + * @return gain in dB */ - public E4KTunerConfiguration(String uniqueID) - { - super(uniqueID); - } - - @JacksonXmlProperty(isAttribute = true, localName = "master_gain") - public E4KGain getMasterGain() - { - return mMasterGain; - } - - public void setMasterGain(E4KGain gain) - { - mMasterGain = gain; - } - - @JacksonXmlProperty(isAttribute = true, localName = "mixer_gain") - public E4KMixerGain getMixerGain() - { - return mMixerGain; - } - - public void setMixerGain(E4KMixerGain mixerGain) - { - mMixerGain = mixerGain; - } - - @JacksonXmlProperty(isAttribute = true, localName = "lna_gain") - public E4KLNAGain getLNAGain() - { - return mLNAGain; - } - - public void setLNAGain(E4KLNAGain lnaGain) - { - mLNAGain = lnaGain; - } - - @JacksonXmlProperty(isAttribute = true, localName = "if_gain") - public IFGain getIFGain() + @JacksonXmlProperty(isAttribute = true, localName = "if_gain_db") + public int getIFGainDb() { return mIFGain; } - public void setIFGain(IFGain ifGain) + /** + * Sets the total IF gain + * @param gain in dB, in the range E4K_IF_GAIN_MINIMUM to E4K_IF_GAIN_MAXIMUM + */ + public void setIFGainDb(int gain) { - mIFGain = ifGain; + mIFGain = Math.clamp(gain, RTL2832TunerController.E4K_IF_GAIN_MINIMUM, + RTL2832TunerController.E4K_IF_GAIN_MAXIMUM); + } + + /** + * Converts the legacy IF gain setting, such as LINEARITY_8 or SENSITIVITY_24, where the value is in dB. + */ + @JsonSetter("ifgain") + public void setLegacyIFGain(String ifGain) + { + Integer gain = parseLegacyGain(ifGain); + + if(gain != null) + { + setIFGainDb(gain); + } + } + + /** + * Converts the legacy master gain setting: AUTOMATIC, MANUAL (using the LNA and mixer gains) or a gain value + * such as PLUS_240 or MINUS_10 in tenths of a dB. + */ + @JsonSetter("masterGain") + public void setLegacyMasterGain(String masterGain) + { + if("AUTOMATIC".equals(masterGain)) + { + setAGC(true); + } + else if("MANUAL".equals(masterGain)) + { + mLegacyManualGain = true; + updateLegacyManualGain(); + } + else + { + Integer gain = parseLegacyGain(masterGain); + + if(gain != null) + { + setGain(gain); + } + } + } + + /** + * Converts the legacy LNA gain setting, such as GAIN_PLUS_200 or GAIN_MINUS_50 in tenths of a dB. + */ + @JsonSetter("lnagain") + public void setLegacyLNAGain(String lnaGain) + { + mLegacyLNAGain = parseLegacyGain(lnaGain); + updateLegacyManualGain(); + } + + /** + * Converts the legacy mixer gain setting, GAIN_4 or GAIN_12 in dB. + */ + @JsonSetter("mixerGain") + public void setLegacyMixerGain(String mixerGain) + { + Integer gain = parseLegacyGain(mixerGain); + mLegacyMixerGain = gain != null ? gain * 10 : null; + updateLegacyManualGain(); + } + + /** + * Sets the gain to the sum of the legacy LNA and mixer gains when the legacy master gain is MANUAL. + */ + private void updateLegacyManualGain() + { + if(mLegacyManualGain && mLegacyLNAGain != null && mLegacyMixerGain != null) + { + setGain(mLegacyLNAGain + mLegacyMixerGain); + } } } diff --git a/src/main/java/io/github/dsheirer/source/tuner/rtl/e4k/E4KTunerEditor.java b/src/main/java/io/github/dsheirer/source/tuner/rtl/e4k/E4KTunerEditor.java deleted file mode 100644 index 35fdb9db..00000000 --- a/src/main/java/io/github/dsheirer/source/tuner/rtl/e4k/E4KTunerEditor.java +++ /dev/null @@ -1,516 +0,0 @@ -/* - * ***************************************************************************** - * Copyright (C) 2014-2024 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.e4k; - -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.rtl.e4k.E4KEmbeddedTuner.E4KGain; -import io.github.dsheirer.source.tuner.rtl.e4k.E4KEmbeddedTuner.E4KLNAGain; -import io.github.dsheirer.source.tuner.rtl.e4k.E4KEmbeddedTuner.E4KMixerGain; -import io.github.dsheirer.source.tuner.ui.TunerEditor; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -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.JSeparator; -import javax.swing.JToggleButton; -import javax.swing.SpinnerNumberModel; -import javax.usb.UsbException; - -/** - * E4000 tuner editor - */ -public class E4KTunerEditor extends TunerEditor -{ - private final static Logger mLog = LoggerFactory.getLogger(E4KTunerEditor.class); - private static final long serialVersionUID = 1L; - private JButton mTunerInfoButton; - private JToggleButton mBiasTButton; - private JComboBox mSampleRateCombo; - private JComboBox mMasterGainCombo; - private JComboBox mMixerGainCombo; - private JComboBox mLNAGainCombo; - private JComboBox mIfGainCombo; - - /** - * Constructs an instance - * @param userPreferences for wide-band recordings - * @param tunerManager to save configuration - * @param discoveredTuner to control - */ - public E4KTunerEditor(UserPreferences userPreferences, TunerManager tunerManager, DiscoveredTuner discoveredTuner) - { - super(userPreferences, tunerManager, discoveredTuner); - init(); - tunerStatusUpdated(); - } - - @Override - public long getMinimumTunableFrequency() - { - return E4KEmbeddedTuner.MINIMUM_TUNABLE_FREQUENCY_HZ; - } - - @Override - public long getMaximumTunableFrequency() - { - return E4KEmbeddedTuner.MAXIMUM_TUNABLE_FREQUENCY_HZ; - } - - /** - * Access to the E4000 embedded tuner - * @return E4000 tuner if there is a tuner, or null otherwise - */ - public E4KEmbeddedTuner getEmbeddedTuner() - { - if(hasTuner()) - { - return (E4KEmbeddedTuner)getTuner().getController().getEmbeddedTuner(); - } - - return null; - } - - 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(), "span 2"); - - add(new JLabel("Sample Rate:")); - add(getSampleRateCombo(), "wrap"); - - add(new JSeparator(), "span,growx,push"); - add(new JLabel("Mixer/LNA Gain Control"), "wrap"); - - add(new JLabel("Master:")); - add(getMasterGainCombo(), "wrap"); - - add(new JLabel("Mixer:")); - add(getMixerGainCombo(), "wrap"); - - add(new JLabel("LNA:")); - add(getLNAGainCombo(), "wrap"); - - add(new JSeparator(), "span,growx,push"); - - add(new JLabel("IF Gain:")); - add(getIfGainCombo(), "wrap"); - } - - @Override - protected void tunerStatusUpdated() - { - setLoading(true); - - if(hasTuner()) - { - getTunerIdLabel().setText(getTuner().getPreferredName()); - } - else - { - getTunerIdLabel().setText(getDiscoveredTuner().getId()); - } - getTunerStatusLabel().setText(getDiscoveredTuner().getTunerStatus().toString()); - getButtonPanel().updateControls(); - getFrequencyPanel().updateControls(); - - if(hasTuner()) - { - getBiasTButton().setEnabled(true); - getBiasTButton().setSelected(getConfiguration().isBiasT()); - getTunerInfoButton().setEnabled(true); - getSampleRateCombo().setEnabled(true); - getSampleRateCombo().setSelectedItem(getConfiguration().getSampleRate()); - getMasterGainCombo().setEnabled(true); - getIfGainCombo().setEnabled(true); - getIfGainCombo().setSelectedItem(getConfiguration().getIFGain()); - - E4KGain gain = getConfiguration().getMasterGain(); - getMasterGainCombo().setEnabled(true); - getMasterGainCombo().setSelectedItem(gain); - - if(gain == E4KGain.MANUAL) - { - getMixerGainCombo().setSelectedItem(getConfiguration().getMixerGain()); - getMixerGainCombo().setEnabled(true); - - getLNAGainCombo().setSelectedItem(getConfiguration().getLNAGain()); - getLNAGainCombo().setEnabled(true); - } - else - { - getMixerGainCombo().setEnabled(false); - getMixerGainCombo().setSelectedItem(gain.getMixerGain()); - - getLNAGainCombo().setEnabled(false); - getLNAGainCombo().setSelectedItem(gain.getLNAGain()); - } - } - else - { - getBiasTButton().setEnabled(false); - getBiasTButton().setSelected(false); - getTunerInfoButton().setEnabled(false); - getSampleRateCombo().setEnabled(false); - getMasterGainCombo().setEnabled(false); - getMixerGainCombo().setEnabled(false); - getLNAGainCombo().setEnabled(false); - getIfGainCombo().setEnabled(false); - } - - updateSampleRateToolTip(); - - setLoading(false); - } - - /** - * Bias-T toggle button - * @return - */ - 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; - } - - private JComboBox getIfGainCombo() - { - if(mIfGainCombo == null) - { - mIfGainCombo = new JComboBox<>(E4KEmbeddedTuner.IFGain.values()); - mIfGainCombo.addActionListener(new ActionListener() - { - @Override - public void actionPerformed(ActionEvent e) - { - if(!isLoading()) - { - try - { - E4KEmbeddedTuner.IFGain selected = (E4KEmbeddedTuner.IFGain) getIfGainCombo().getSelectedItem(); - getEmbeddedTuner().setIFGain(selected, true); - save(); - } - catch(LibUsbException lue) - { - JOptionPane.showMessageDialog(E4KTunerEditor.this, "E4000 Tuner Controller - " - + "couldn't apply the IF setting - " + lue.getLocalizedMessage()); - mLog.error("E4000 Tuner Controller - couldn't apply IF gain setting", e); - } - } - } - }); - mIfGainCombo.setToolTipText("Linear IF Gain"); - mIfGainCombo.setEnabled(true); - } - - return mIfGainCombo; - } - - private JComboBox getLNAGainCombo() - { - if(mLNAGainCombo == null) - { - mLNAGainCombo = new JComboBox<>(E4KLNAGain.values()); - mLNAGainCombo.addActionListener(arg0 -> - { - if(!isLoading()) - { - try - { - E4KLNAGain lnaGain = (E4KLNAGain) mLNAGainCombo.getSelectedItem(); - getEmbeddedTuner().setLNAGain(lnaGain, true); - save(); - } - catch(UsbException e) - { - JOptionPane.showMessageDialog(E4KTunerEditor.this, "E4000 Tuner Controller - " - + "couldn't apply the LNA gain setting - " + e.getLocalizedMessage()); - mLog.error("E4000 Tuner Controller - couldn't apply LNA gain setting - ", e); - } - } - }); - mLNAGainCombo.setToolTipText("LNA Gain. Set master gain to MANUAL to enable adjustment"); - mLNAGainCombo.setEnabled(false); - } - - return mLNAGainCombo; - } - - private JComboBox getMixerGainCombo() - { - if(mMixerGainCombo == null) - { - mMixerGainCombo = new JComboBox<>(E4KMixerGain.values()); - mMixerGainCombo.addActionListener(arg0 -> - { - if(!isLoading()) - { - try - { - E4KMixerGain mixerGain = (E4KMixerGain) mMixerGainCombo.getSelectedItem(); - getEmbeddedTuner().setMixerGain(mixerGain, true); - save(); - } - catch(UsbException e) - { - JOptionPane.showMessageDialog(E4KTunerEditor.this, "E4000 Tuner Controller - " - + "couldn't apply the mixer gain setting - " + e.getLocalizedMessage()); - mLog.error("E4000 Tuner Controller - couldn't apply mixer gain setting", e); - } - } - }); - mMixerGainCombo.setToolTipText("Mixer Gain. Set master gain to MASTER to enable adjustment"); - mMixerGainCombo.setEnabled(false); - } - - return mMixerGainCombo; - } - - private JComboBox getMasterGainCombo() - { - if(mMasterGainCombo == null) - { - mMasterGainCombo = new JComboBox<>(E4KGain.values()); - mMasterGainCombo.setEnabled(false); - mMasterGainCombo.addActionListener(arg0 -> - { - if(!isLoading()) - { - try - { - E4KGain gain = (E4KGain) mMasterGainCombo.getSelectedItem(); - getEmbeddedTuner().setGain((E4KGain) mMasterGainCombo.getSelectedItem(), true); - if(gain == E4KGain.MANUAL) - { - getMixerGainCombo().setSelectedItem(getEmbeddedTuner().getMixerGain(true)); - getMixerGainCombo().setEnabled(true); - getLNAGainCombo().setSelectedItem(getEmbeddedTuner().getLNAGain(true)); - getLNAGainCombo().setEnabled(true); - } - else - { - getMixerGainCombo().setEnabled(false); - getMixerGainCombo().setSelectedItem(gain.getMixerGain()); - getLNAGainCombo().setEnabled(false); - getLNAGainCombo().setSelectedItem(gain.getLNAGain()); - } - - save(); - } - catch(UsbException e) - { - JOptionPane.showMessageDialog(E4KTunerEditor.this, "E4000 Tuner Controller - " - + "couldn't apply the gain setting - " + e.getLocalizedMessage()); - mLog.error("E4000 Tuner Controller - couldn't apply gain setting", e); - } - } - }); - - mMasterGainCombo.setToolTipText("Select AUTOMATIC for auto gain, MANUAL to enable
" + - "independent control of Mixer, LNA and Enhance gain
settings, or one of the " + - "individual gain settings for
semi-manual gain control"); - } - - return mMasterGainCombo; - } - - 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); - //Adjust the min/max values for the sample rate. - adjustForSampleRate(sampleRate.getRate()); - save(); - } - catch(SourceException | LibUsbException eSampleRate) - { - JOptionPane.showMessageDialog(E4KTunerEditor.this, - "E4000 Tuner Controller - couldn't apply the sample rate setting [" + - sampleRate.getLabel() + "] " + eSampleRate.getLocalizedMessage()); - - mLog.error("E4000 Tuner Controller - couldn't apply sample rate setting [" + - sampleRate.getLabel() + "]", eSampleRate); - } - } - }); - } - - return mSampleRateCombo; - } - - private JButton getTunerInfoButton() - { - if(mTunerInfoButton == null) - { - mTunerInfoButton = new JButton("Tuner Info"); - mTunerInfoButton.setEnabled(false); - mTunerInfoButton.addActionListener(e -> JOptionPane.showMessageDialog(E4KTunerEditor.this, - getTunerInfo(), "Tuner Info", JOptionPane.INFORMATION_MESSAGE)); - } - - return mTunerInfoButton; - } - - /** - * Updates the sample rate tooltip according to the tuner controller's lock state. - */ - /** - * 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 E4000 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()) - { - E4KTunerConfiguration config = getConfiguration(); - config.setFrequency(getFrequencyControl().getFrequency()); - getConfiguration().setMinimumFrequency(getMinimumFrequencyTextField().getFrequency()); - getConfiguration().setMaximumFrequency(getMaximumFrequencyTextField().getFrequency()); - double value = ((SpinnerNumberModel)getFrequencyCorrectionSpinner().getModel()).getNumber().doubleValue(); - config.setFrequencyCorrection(value); - config.setAutoPPMCorrectionEnabled(getAutoPPMCheckBox().isSelected()); - config.setBiasT(getTuner().getController().isBiasT()); - config.setSampleRate((SampleRate)getSampleRateCombo().getSelectedItem()); - config.setMasterGain((E4KGain)getMasterGainCombo().getSelectedItem()); - config.setMixerGain((E4KMixerGain)getMixerGainCombo().getSelectedItem()); - config.setLNAGain((E4KLNAGain)getLNAGainCombo().getSelectedItem()); - config.setIFGain((E4KEmbeddedTuner.IFGain)getIfGainCombo().getSelectedItem()); - saveConfiguration(); - } - } -} \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/source/tuner/rtl/fc0012/FC0012TunerConfiguration.java b/src/main/java/io/github/dsheirer/source/tuner/rtl/fc0012/FC0012TunerConfiguration.java new file mode 100644 index 00000000..60498bd3 --- /dev/null +++ b/src/main/java/io/github/dsheirer/source/tuner/rtl/fc0012/FC0012TunerConfiguration.java @@ -0,0 +1,63 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2026 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.fc0012; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import io.github.dsheirer.source.tuner.TunerType; +import io.github.dsheirer.source.tuner.rtl.RTL2832TunerConfiguration; + +/** + * RTL-2832 with embedded FC0012 tuner configuration. The librtlsdr FC0012 driver supports manual LNA gain only. + */ +public class FC0012TunerConfiguration extends RTL2832TunerConfiguration +{ + public static final long MINIMUM_TUNABLE_FREQUENCY_HZ = 22_000_000; + public static final long MAXIMUM_TUNABLE_FREQUENCY_HZ = 948_600_000; + public static final double USABLE_BANDWIDTH_PERCENT = 0.95; + public static final int DC_SPIKE_AVOID_BUFFER = 15000; + /** + * Default gain in tenths of a dB + */ + public static final int DEFAULT_GAIN = 179; + + /** + * Default constructor for Jackson + */ + public FC0012TunerConfiguration() + { + super(MINIMUM_TUNABLE_FREQUENCY_HZ, MAXIMUM_TUNABLE_FREQUENCY_HZ, DEFAULT_GAIN); + } + + /** + * Constructs an instance + * + * @param uniqueID for the tuner + */ + public FC0012TunerConfiguration(String uniqueID) + { + super(uniqueID, DEFAULT_GAIN); + } + + @JsonIgnore + @Override + public TunerType getTunerType() + { + return TunerType.FITIPOWER_FC0012; + } +} 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 deleted file mode 100644 index fcc2abfc..00000000 --- a/src/main/java/io/github/dsheirer/source/tuner/rtl/fc0013/FC0013EmbeddedTuner.java +++ /dev/null @@ -1,842 +0,0 @@ -/* - * ***************************************************************************** - * Copyright (C) 2014-2024 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"); - public static final long MINIMUM_TUNABLE_FREQUENCY_HZ = 13_500_000; - public static final long MAXIMUM_TUNABLE_FREQUENCY_HZ = 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_TUNABLE_FREQUENCY_HZ; - - /** - * 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_TUNABLE_FREQUENCY_HZ; - } - - @Override - public long getMaximumFrequencySupported() - { - return MAXIMUM_TUNABLE_FREQUENCY_HZ; - } - - @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 index c30eabfa..1a5067f0 100644 --- 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 @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2024 Dennis Sheirer + * Copyright (C) 2014-2026 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 @@ -19,7 +19,7 @@ package io.github.dsheirer.source.tuner.rtl.fc0013; import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.annotation.JsonSetter; import io.github.dsheirer.source.tuner.TunerType; import io.github.dsheirer.source.tuner.rtl.RTL2832TunerConfiguration; @@ -28,15 +28,38 @@ import io.github.dsheirer.source.tuner.rtl.RTL2832TunerConfiguration; */ public class FC0013TunerConfiguration extends RTL2832TunerConfiguration { - private FC0013EmbeddedTuner.LNAGain mLnaGain = FC0013EmbeddedTuner.LNAGain.G10; - private boolean mAgc = false; + public static final long MINIMUM_TUNABLE_FREQUENCY_HZ = 13_500_000; + public static final long MAXIMUM_TUNABLE_FREQUENCY_HZ = 1_907_999_890l; + public static final double USABLE_BANDWIDTH_PERCENT = 0.95; + public static final int DC_SPIKE_AVOID_BUFFER = 15000; + /** + * Gains in tenths of a dB for the legacy LNA gain settings G00 to G23, from the librtlsdr FC0013 LNA gain table. + * The legacy settings used the same 24 register values in the same order. Two of them (G03 and G04) are both + * listed as -6.3 dB, so librtlsdr reports only 23 distinct gains. + */ + private static final int[] LEGACY_GAINS = {-99, -73, -65, -63, -63, -60, -58, -54, 58, 61, 63, 65, 67, 68, 70, 71, + 179, 181, 182, 184, 186, 188, 191, 197}; + /** + * Default gain in tenths of a dB + */ + public static final int DEFAULT_GAIN = 67; /** - * Default constructor for JAXB + * Default constructor for Jackson */ public FC0013TunerConfiguration() { - super(FC0013EmbeddedTuner.MINIMUM_TUNABLE_FREQUENCY_HZ, FC0013EmbeddedTuner.MAXIMUM_TUNABLE_FREQUENCY_HZ); + super(MINIMUM_TUNABLE_FREQUENCY_HZ, MAXIMUM_TUNABLE_FREQUENCY_HZ, DEFAULT_GAIN); + } + + /** + * Constructs an instance + * + * @param uniqueID for the tuner + */ + public FC0013TunerConfiguration(String uniqueID) + { + super(uniqueID, DEFAULT_GAIN); } @JsonIgnore @@ -47,50 +70,15 @@ public class FC0013TunerConfiguration extends RTL2832TunerConfiguration } /** - * Constructs an instance - * - * @param uniqueID for the tuner + * Converts the legacy LNA gain setting, G00 (lowest) to G23 (highest), to the librtlsdr gain for the same + * LNA register value. */ - public FC0013TunerConfiguration(String uniqueID) + @JsonSetter("lnaGain") + public void setLegacyLNAGain(String lnaGain) { - 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; + if(lnaGain != null && lnaGain.matches("G\\d\\d")) + { + setGain(LEGACY_GAINS[Math.min(Integer.parseInt(lnaGain.substring(1)), LEGACY_GAINS.length - 1)]); + } } } 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 deleted file mode 100644 index 1dd0f488..00000000 --- a/src/main/java/io/github/dsheirer/source/tuner/rtl/fc0013/FC0013TunerEditor.java +++ /dev/null @@ -1,420 +0,0 @@ -/* - * ***************************************************************************** - * Copyright (C) 2014-2024 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(); - } - - @Override - public long getMinimumTunableFrequency() - { - return FC0013EmbeddedTuner.MINIMUM_TUNABLE_FREQUENCY_HZ; - } - - @Override - public long getMaximumTunableFrequency() - { - return FC0013EmbeddedTuner.MAXIMUM_TUNABLE_FREQUENCY_HZ; - } - - /** - * 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); - //Adjust the min/max values for the sample rate. - adjustForSampleRate(sampleRate.getRate()); - 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()); - getConfiguration().setMinimumFrequency(getMinimumFrequencyTextField().getFrequency()); - getConfiguration().setMaximumFrequency(getMaximumFrequencyTextField().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/fc2580/FC2580TunerConfiguration.java b/src/main/java/io/github/dsheirer/source/tuner/rtl/fc2580/FC2580TunerConfiguration.java new file mode 100644 index 00000000..8ade79de --- /dev/null +++ b/src/main/java/io/github/dsheirer/source/tuner/rtl/fc2580/FC2580TunerConfiguration.java @@ -0,0 +1,64 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2026 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.fc2580; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import io.github.dsheirer.source.tuner.TunerType; +import io.github.dsheirer.source.tuner.rtl.RTL2832TunerConfiguration; + +/** + * RTL-2832 with embedded FC2580 tuner configuration. The librtlsdr FC2580 driver doesn't support gain control and the + * tuner always uses its internal AGC. The tuner covers 146-308 MHz and 438-924 MHz. + */ +public class FC2580TunerConfiguration extends RTL2832TunerConfiguration +{ + public static final long MINIMUM_TUNABLE_FREQUENCY_HZ = 146_000_000; + public static final long MAXIMUM_TUNABLE_FREQUENCY_HZ = 924_000_000; + public static final double USABLE_BANDWIDTH_PERCENT = 0.95; + public static final int DC_SPIKE_AVOID_BUFFER = 15000; + /** + * Default gain in tenths of a dB. Not used, since the tuner doesn't support gain control. + */ + public static final int DEFAULT_GAIN = 0; + + /** + * Default constructor for Jackson + */ + public FC2580TunerConfiguration() + { + super(MINIMUM_TUNABLE_FREQUENCY_HZ, MAXIMUM_TUNABLE_FREQUENCY_HZ, DEFAULT_GAIN); + } + + /** + * Constructs an instance + * + * @param uniqueID for the tuner + */ + public FC2580TunerConfiguration(String uniqueID) + { + super(uniqueID, DEFAULT_GAIN); + } + + @JsonIgnore + @Override + public TunerType getTunerType() + { + return TunerType.FCI_FC2580; + } +} 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 deleted file mode 100644 index ccdd0b0b..00000000 --- a/src/main/java/io/github/dsheirer/source/tuner/rtl/r8x/R8xEmbeddedTuner.java +++ /dev/null @@ -1,1156 +0,0 @@ -/* - * ***************************************************************************** - * Copyright (C) 2014-2024 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.r8x; - -import io.github.dsheirer.source.SourceException; -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.nio.ByteBuffer; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.usb4java.LibUsbException; - -import javax.usb.UsbException; - -/** - * Abstract Rafael Micro R8XXX Embedded Tuner base class implementation - */ -public abstract class R8xEmbeddedTuner extends EmbeddedTuner -{ - public static final byte[] BIT_REV_LOOKUP_TABLE = {(byte) 0x0, (byte) 0x8, (byte) 0x4, (byte) 0xC, (byte) 0x2, - (byte) 0xA, (byte) 0x6, (byte) 0xE, (byte) 0x1, (byte) 0x9, (byte) 0x5, (byte) 0xD, (byte) 0x3, (byte) 0xB, - (byte) 0x7, (byte) 0xF}; - public static final long MINIMUM_TUNABLE_FREQUENCY_HZ = 3180000; - public static final long MAXIMUM_TUNABLE_FREQUENCY_HZ = 1782030000; - private static final double USABLE_BANDWIDTH_PERCENT = 0.98; - private static final int DC_SPIKE_AVOID_BUFFER = 5000; - private static final byte VERSION = (byte) 49; - protected static final int IF_FREQUENCY = 3570000; - private static final Logger mLog = LoggerFactory.getLogger(R8xEmbeddedTuner.class); - private int mVcoPowerRef = 1; - - /** - * Shadow register is used to keep a cached (in-memory) copy of all registers, so that we don't have to read a - * full byte from a register in order to apply a masked value and then re-write the full byte. With the shadow - * register, we can apply the masked value to the cached value, and then just write the masked byte, skipping the - * need to read the byte first and avoid writing the byte value if the value is unchanged. - */ - private byte[] mShadowRegister = {(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x83, - (byte) 0x32, (byte) 0x75, (byte) 0xC0, (byte) 0x40, (byte) 0xD6, (byte) 0x6C, (byte) 0xF5, (byte) 0x63, - (byte) 0x75, (byte) 0x68, (byte) 0x6C, (byte) 0x83, (byte) 0x80, (byte) 0x00, (byte) 0x0F, (byte) 0x00, - (byte) 0xC0, (byte) 0x30, (byte) 0x48, (byte) 0xCC, (byte) 0x60, (byte) 0x00, (byte) 0x54, (byte) 0xAE, - (byte) 0x4A, (byte) 0xC0}; - - /** - * Constructs an instance - * @param adapter to control the RTL2832 interface. - * @param vcoPowerRef power reference. - */ - public R8xEmbeddedTuner(RTL2832TunerController.ControllerAdapter adapter, int vcoPowerRef) - { - super(adapter); - mVcoPowerRef = vcoPowerRef; - } - - /** - * I2C Write Address for the embedded tuner - */ - public abstract byte getI2CWriteAddress(); - - /** - * I2C Read Address for the embedded tuner - */ - public abstract byte getI2CReadAddress(); - - /** - * Applies the tuner configuration values to this embedded tuner - * @param tunerConfig containing settings to apply - * @throws SourceException if there is an error - */ - @Override - public void apply(TunerConfiguration tunerConfig) throws SourceException - { - if(tunerConfig instanceof R8xTunerConfiguration config) - { - try - { - MasterGain masterGain = config.getMasterGain(); - setGain(masterGain, true); - - if(masterGain == MasterGain.MANUAL) - { - setLNAGain(config.getLNAGain(), true); - setMixerGain(config.getMixerGain(), true); - setVGAGain(config.getVGAGain(), true); - } - } - catch(UsbException e) - { - throw new SourceException("R8xxxTunerController - usb error while applying tuner config", e); - } - } - } - - /** - * Writes the byte value to the specified register, optionally controlling the I2C repeater as needed. - */ - public synchronized void writeRegister(Register register, byte value, boolean controlI2C) throws UsbException - { - byte current = mShadowRegister[register.getRegister()]; - - if(register.isMasked()) - { - value = (byte) ((current & ~register.getMask()) | (value & register.getMask())); - } - - if(value != current) - { - getAdapter().writeI2CRegister(getI2CWriteAddress(), (byte) register.getRegister(), value, controlI2C); - mShadowRegister[register.getRegister()] = value; - } - } - - /** - * Reads the specified register, optionally controlling the I2C repeater - */ - public int readRegister(Register register, boolean controlI2C) throws UsbException - { - return getAdapter().readI2CRegister(getI2CReadAddress(), (byte) register.getRegister(), controlI2C); - } - - /** - * Assumes value is a byte value and reverses the bits in the byte. - */ - private static int bitReverse(int value) - { - return BIT_REV_LOOKUP_TABLE[value & 0x0F] << 4 | BIT_REV_LOOKUP_TABLE[(value & 0xF0) >> 4]; - } - - @Override - public long getMinimumFrequencySupported() - { - return MINIMUM_TUNABLE_FREQUENCY_HZ; - } - - @Override - public long getMaximumFrequencySupported() - { - return MAXIMUM_TUNABLE_FREQUENCY_HZ; - } - - @Override - public int getDcSpikeHalfBandwidth() - { - return DC_SPIKE_AVOID_BUFFER; - } - - @Override - public double getUsableBandwidthPercent() - { - return USABLE_BANDWIDTH_PERCENT; - } - - @Override - public void setSampleRateFilters(int sampleRate) throws SourceException - { - //No-op - } - - /** - * Overrides the same method from the RTL2832 tuner controller to apply settings specific to the R8xxx tuner. - */ - public void setSamplingMode(RTL2832TunerController.SampleMode mode) throws LibUsbException - { - if(mode == RTL2832TunerController.SampleMode.QUADRATURE) - { - /* Set intermediate frequency to R820T IF frequency */ - getAdapter().setIFFrequency(IF_FREQUENCY); - - /* Enable spectrum inversion */ - getAdapter().writeDemodRegister(RTL2832TunerController.Page.ONE, (short) 0x15, (short) 0x01, 1); - - /* Set default i/q path */ - getAdapter().writeDemodRegister(RTL2832TunerController.Page.ZERO, (short) 0x06, (short) 0x80, 1); - } - } - - /** - * Initializes the tuner section. - */ - protected void initTuner() throws UsbException - { - /* Disable zero IF mode */ - getAdapter().writeDemodRegister(RTL2832TunerController.Page.ONE, (short) 0xB1, (short) 0x1A, 1); - - /* Only enable in-phase ADC input */ - getAdapter().writeDemodRegister(RTL2832TunerController.Page.ZERO, (short) 0x08, (short) 0x4D, 1); - - /* Set intermediate frequency to IF frequency (3.57 MHz) */ - getAdapter().setIFFrequency(IF_FREQUENCY); - - /* Enable spectrum inversion */ - getAdapter().writeDemodRegister(RTL2832TunerController.Page.ONE, (short) 0x15, (short) 0x01, 1); - initializeRegisters(false); - setTVStandard(false); - systemFrequencySelect(0, false); - } - - /** - * Sets the tuner's Phase-Locked-Loop (PLL) oscillator used for frequency (tuning) control - * - * @param frequency - desired center frequency - * @param controlI2C - control the I2C repeater locally - * @throws UsbException - if unable to set any of the R8xxx registers - */ - protected void setPLL(long frequency, boolean controlI2C) throws UsbException - { - /* Set reference divider to 0 */ - writeRegister(Register.REFERENCE_DIVIDER_2, (byte) 0x00, controlI2C); - /* Set PLL autotune to 128kHz */ - writeRegister(Register.PLL_AUTOTUNE, (byte) 0x00, controlI2C); - /* Set VCO current to 100 */ - writeRegister(Register.VCO_CURRENT, (byte) 0x80, controlI2C); - /* Set the frequency divider - adjust for vco_fine_tune status */ - FrequencyDivider divider = FrequencyDivider.fromFrequency(frequency); - int statusRegister4 = getStatusRegister(4, controlI2C); - int vco_fine_tune = (statusRegister4 & 0x30) >> 4; - int div_num = divider.getDividerNumber(vco_fine_tune, mVcoPowerRef); - writeRegister(Register.DIVIDER, (byte) (div_num << 5), controlI2C); - /* Get the integral number for this divider and frequency */ - Integral integral = divider.getIntegral(frequency); - writeRegister(Register.PLL, integral.getRegisterValue(), controlI2C); - /* Calculate the sigma-delta modulator fractional setting. If it's non-zero, power up the sdm and apply the - fractional setting, otherwise turn it off */ - int sdm = divider.getSDM(integral, frequency); - if(sdm != 0) - { - writeRegister(Register.SIGMA_DELTA_MODULATOR_POWER, (byte) 0x00, controlI2C); - writeRegister(Register.SIGMA_DELTA_MODULATOR_MSB, (byte) ((sdm >> 8) & 0xFF), controlI2C); - writeRegister(Register.SIGMA_DELTA_MODULATOR_LSB, (byte) (sdm & 0xFF), controlI2C); - } - else - { - writeRegister(Register.SIGMA_DELTA_MODULATOR_POWER, (byte) 0x08, controlI2C); - } - - /* Check to see if the PLL locked with these divider, integral and sdm - * settings */ - if(!isPLLLocked(controlI2C)) - { - mLog.info("PLL is not locked. Increasing VCO current"); - /* Increase VCO current */ - writeRegister(Register.VCO_CURRENT, (byte) 0x60, controlI2C); - - if(!isPLLLocked(controlI2C)) - { - throw new UsbException("R8xxx Tuner Controller - couldn't achieve PLL lock on frequency [" + frequency + "]"); - } - } - /* set pll autotune to 8kHz */ - writeRegister(Register.PLL_AUTOTUNE_VARIANT, (byte) 0x08, controlI2C); - } - - /** - * Sets the system IF frequency - */ - private void systemFrequencySelect(long frequency, boolean controlI2C) throws UsbException - { - /* LNA top? */ - writeRegister(Register.LNA_TOP2, (byte) 0xE5, controlI2C); - - byte mixer_top; - byte cp_cur; - byte div_buf_cur; - - if(frequency == 506000000 || frequency == 666000000 || frequency == 818000000) - { - mixer_top = (byte) 0x14; - cp_cur = (byte) 0x28; - div_buf_cur = (byte) 0x20; - } - else - { - mixer_top = (byte) 0x24; - cp_cur = (byte) 0x38; - div_buf_cur = (byte) 0x30; - } - - writeRegister(Register.MIXER_TOP, mixer_top, controlI2C); - writeRegister(Register.LNA_VTH_L, (byte) 0x53, controlI2C); - writeRegister(Register.MIXER_VTH_L, (byte) 0x75, controlI2C); - /* Air-In only for Astrometa */ - writeRegister(Register.INPUT_SELECTOR_AIR_AND_CABLE_1, (byte) 0x00, controlI2C); - writeRegister(Register.INPUT_SELECTOR_CABLE_2, (byte) 0x00, controlI2C); - writeRegister(Register.CP_CUR, cp_cur, controlI2C); - writeRegister(Register.DIVIDER_BUFFER_CURRENT, div_buf_cur, controlI2C); - writeRegister(Register.FILTER_CURRENT, (byte) 0x40, controlI2C); - /* if( type != TUNER_ANALOG_TV ) ... */ - writeRegister(Register.LNA_TOP, (byte) 0x00, controlI2C); - writeRegister(Register.MIXER_TOP2, (byte) 0x00, controlI2C); - writeRegister(Register.PRE_DETECT, (byte) 0x00, controlI2C); - writeRegister(Register.AGC_CLOCK, (byte) 0x30, controlI2C); - writeRegister(Register.LNA_TOP, (byte) 0x18, controlI2C); - writeRegister(Register.MIXER_TOP2, mixer_top, controlI2C); - /* LNA discharge current */ - writeRegister(Register.LNA_DISCHARGE_CURRENT, (byte) 0x14, controlI2C); - /* AGC clock 1 khz, external det1 cap 1u */ - writeRegister(Register.AGC_CLOCK, (byte) 0x20, controlI2C); - } - - /** - * Partially implements the r82xx_set_tv_standard() method from librtlsdr. - * Sets standard to digital tv to support sdr operations only. - */ - protected void setTVStandard(boolean controlI2C) throws UsbException - { - /* Init Flag & Xtal check Result */ - writeRegister(Register.XTAL_CHECK, (byte) 0x00, controlI2C); - - /* Set version */ - writeRegister(Register.VERSION, VERSION, controlI2C); - - /* LT Gain Test */ - writeRegister(Register.LNA_TOP, (byte) 0x00, controlI2C); - - int calibrationCode = 0; - - for(int x = 0; x < 2; x++) - { - /* Set filter cap */ - writeRegister(Register.FILTER_CAPACITOR, (byte) 0x6B, controlI2C); - - /* Set calibration clock on */ - writeRegister(Register.CALIBRATION_CLOCK, (byte) 0x04, controlI2C); - - /* XTAL capacitor 0pF for PLL */ - writeRegister(Register.PLL_XTAL_CAPACITOR, (byte) 0x00, controlI2C); - - setPLL(56000 * 1000, controlI2C); - - /* Start trigger */ - writeRegister(Register.CALIBRATION_TRIGGER, (byte) 0x10, controlI2C); - - /* Stop trigger */ - writeRegister(Register.CALIBRATION_TRIGGER, (byte) 0x00, controlI2C); - - /* Set calibration clock off */ - writeRegister(Register.CALIBRATION_CLOCK, (byte) 0x00, controlI2C); - - calibrationCode = getCalibrationCode(controlI2C); - - if(!calibrationSuccessful(calibrationCode)) - { - mLog.error("Calibration NOT successful - code: " + calibrationCode); - } - } - - if(calibrationCode == 0x0F) - { - calibrationCode = 0; - } - - /* Write calibration code */ - byte filt_q = 0x10; - - writeRegister(Register.FILTER_CALIBRATION_CODE, (byte) (calibrationCode | filt_q), controlI2C); - - /* Set BW, Filter gain & HP Corner */ - writeRegister(Register.BANDWIDTH_FILTER_GAIN_HIGHPASS_FILTER_CORNER, (byte) 0x6B, controlI2C); - - /* Set Image_R */ - writeRegister(Register.IMAGE_REVERSE, (byte) 0x00, controlI2C); - - /* Set filter_3db, V6MHz */ - writeRegister(Register.FILTER_GAIN, (byte) 0x10, controlI2C); - - /* Channel filter extension */ - writeRegister(Register.CHANNEL_FILTER_EXTENSION, (byte) 0x60, controlI2C); - - /* Loop through */ - writeRegister(Register.LOOP_THROUGH, (byte) 0x00, controlI2C); - - /* Loop through attenuation */ - writeRegister(Register.LOOP_THROUGH_ATTENUATION, (byte) 0x00, controlI2C); - - /* Filter extension widest */ - writeRegister(Register.FILTER_EXTENSION_WIDEST, (byte) 0x00, controlI2C); - - /* RF poly filter current */ - writeRegister(Register.RF_POLY_FILTER_CURRENT, (byte) 0x60, controlI2C); - } - - /** - * Indicates if a calibration request was successful. - */ - private boolean calibrationSuccessful(int calibrationCode) - { - return calibrationCode != 0 && calibrationCode != 0x0F; - } - - /** - * Returns the calibration code resulting from a calibration request. - */ - private int getCalibrationCode(boolean controlI2C) throws UsbException - { - return getStatusRegister(4, controlI2C) & 0x0F; - } - - /** - * Indicates if the Phase Locked Loop (PLL) oscillator is locked following - * a change in the tuned center frequency. Checks status register 2 to see - * if the PLL locked indicator bit is set. - */ - protected boolean isPLLLocked(boolean controlI2C) throws UsbException - { - int register = getStatusRegister(2, controlI2C); - return (register & 0x40) == 0x40; - } - - /** - * Sets the multiplexer for the desired center frequency. - */ - protected void setMux(long frequency, boolean controlI2C) throws UsbException - { - FrequencyRange range = FrequencyRange.getRangeForFrequency(frequency); - - /* Set open drain */ - writeRegister(Register.DRAIN, range.getOpenDrain(), controlI2C); - - /* RF_MUX, Polymux */ - writeRegister(Register.RF_POLY_MUX, range.getRFMuxPolyMux(), controlI2C); - - /* TF Band */ - writeRegister(Register.TF_BAND, range.getTFC(), controlI2C); - - /* XTAL CAP & Drive */ - writeRegister(Register.PLL_XTAL_CAPACITOR_AND_DRIVE, range.getXTALHighCap0P(), controlI2C); - - /* Register 8 - what is it? */ - writeRegister(Register.UNKNOWN_REGISTER_8, (byte) 0x00, controlI2C); - - /* Register 9 - what is it? */ - writeRegister(Register.UNKNOWN_REGISTER_9, (byte) 0x00, controlI2C); - } - - - /** - * Writes initial starting value of registers 0x05 through 0x1F using the - * default value initialized in the shadow register array. This method only - * needs to be called once, upon initialization. - * - * @throws UsbException - */ - protected void initializeRegisters(boolean controlI2C) throws UsbException - { - for(int x = 5; x < mShadowRegister.length; x++) - { - getAdapter().writeI2CRegister(getI2CWriteAddress(), (byte) x, mShadowRegister[x], controlI2C); - } - } - - /** - * Returns the contents of status registers 0 through 4 - */ - protected int getStatusRegister(int register, boolean controlI2C) throws UsbException - { - ByteBuffer buffer = ByteBuffer.allocateDirect(5); - getAdapter().read(getI2CWriteAddress(), RTL2832TunerController.Block.I2C, buffer); - return bitReverse(buffer.get(register) & 0xFF); - } - - /** - * Sets master gain by applying gain component values to LNA, Mixer and - * VGA gain registers. - */ - public void setGain(MasterGain masterGain, boolean controlI2C) throws UsbException - { - setLNAGain(masterGain.getLNAGain(), controlI2C); - setMixerGain(masterGain.getMixerGain(), controlI2C); - setVGAGain(masterGain.getVGAGain(), controlI2C); - } - - /** - * Sets LNA gain - */ - public void setLNAGain(LNAGain gain, boolean controlI2C) throws UsbException - { - getAdapter().getLock().lock(); - - try - { - writeRegister(Register.LNA_GAIN, gain.getSetting(), controlI2C); - } - finally - { - getAdapter().getLock().unlock(); - } - } - - /** - * Sets Mixer gain - */ - public void setMixerGain(MixerGain gain, boolean controlI2C) throws UsbException - { - getAdapter().getLock().lock(); - - try - { - writeRegister(Register.MIXER_GAIN, gain.getSetting(), controlI2C); - } - finally - { - getAdapter().getLock().unlock(); - } - } - - /** - * Sets VGA gain - */ - public void setVGAGain(VGAGain gain, boolean controlI2C) throws UsbException - { - getAdapter().getLock().lock(); - - try - { - writeRegister(Register.VGA_GAIN, gain.getSetting(), controlI2C); - } - finally - { - getAdapter().getLock().unlock(); - } - } - - /** - * VGA gain settings - */ - public enum VGAGain - { - GAIN_0("0", 0x00), - GAIN_26("26", 0x01), - GAIN_52("52", 0x02), - GAIN_82("82", 0x03), - GAIN_124("124", 0x04), - GAIN_159("159", 0x05), - GAIN_183("183", 0x06), - GAIN_196("196", 0x07), - GAIN_210("210", 0x08), - GAIN_242("242", 0x09), - GAIN_278("278", 0x0A), - GAIN_312("312", 0x0B), - GAIN_347("347", 0x0C), - GAIN_384("384", 0x0D), - GAIN_419("419", 0x0E), - GAIN_455("455", 0x0F); - - private String mLabel; - private int mSetting; - - VGAGain(String label, int setting) - { - mLabel = label; - mSetting = setting; - } - - public String toString() - { - return mLabel; - } - - public byte getSetting() - { - return (byte) mSetting; - } - } - - /** - * Low Noise Amplifier gain settings - */ - public enum LNAGain - { - AUTOMATIC("Automatic", 0x00), - GAIN_0("0", 0x10), - GAIN_9("9", 0x11), - GAIN_21("21", 0x12), - GAIN_61("61", 0x13), - GAIN_99("99", 0x14), - GAIN_112("112", 0x15), - GAIN_143("143", 0x16), - GAIN_165("165", 0x17), - GAIN_191("191", 0x18), - GAIN_222("222", 0x19), - GAIN_248("248", 0x1A), - GAIN_262("262", 0x1B), - GAIN_281("281", 0x1C), - GAIN_286("286", 0x1D), - GAIN_321("321", 0x1E), - GAIN_334("334", 0x1F); - - private String mLabel; - private int mSetting; - - LNAGain(String label, int setting) - { - mLabel = label; - mSetting = setting; - } - - public String toString() - { - return mLabel; - } - - public byte getSetting() - { - return (byte) mSetting; - } - } - - /** - * Mixer gain settings - *

- * Note: gain labels were changed from measured dB values to simple 1-16 value labels to conform - * with the ICD listing the values from 0-15 as minimum to maximum. - */ - public enum MixerGain - { - AUTOMATIC("Automatic", 0x10), - GAIN_0("1", 0x00), - GAIN_5("2", 0x01), - GAIN_15("3", 0x02), - GAIN_25("4", 0x03), - GAIN_44("5", 0x04), - GAIN_53("6", 0x05), - GAIN_63("7", 0x06), - GAIN_88("8", 0x07), - GAIN_105("9", 0x08), - GAIN_115("10", 0x09), - GAIN_123("11", 0x0A), - GAIN_139("12", 0x0B), - GAIN_152("13", 0x0C), - GAIN_158("14", 0x0D), - GAIN_161("15", 0x0E), - GAIN_153("16", 0x0F); - - private String mLabel; - private int mSetting; - - MixerGain(String label, int setting) - { - mLabel = label; - mSetting = setting; - } - - public String toString() - { - return mLabel; - } - - public byte getSetting() - { - return (byte) mSetting; - } - } - - /** - * Master gain settings - */ - public enum MasterGain - { - AUTOMATIC("Automatic", VGAGain.GAIN_312, LNAGain.AUTOMATIC, MixerGain.AUTOMATIC), - MANUAL("Manual", VGAGain.GAIN_210, LNAGain.GAIN_248, MixerGain.GAIN_123), - GAIN_0("0", VGAGain.GAIN_210, LNAGain.GAIN_0, MixerGain.GAIN_0), - GAIN_9("9", VGAGain.GAIN_210, LNAGain.GAIN_9, MixerGain.GAIN_0), - GAIN_14("14", VGAGain.GAIN_210, LNAGain.GAIN_9, MixerGain.GAIN_5), - GAIN_26("26", VGAGain.GAIN_210, LNAGain.GAIN_21, MixerGain.GAIN_5), - GAIN_36("36", VGAGain.GAIN_210, LNAGain.GAIN_21, MixerGain.GAIN_15), - GAIN_76("76", VGAGain.GAIN_210, LNAGain.GAIN_61, MixerGain.GAIN_15), - GAIN_86("86", VGAGain.GAIN_210, LNAGain.GAIN_61, MixerGain.GAIN_25), - GAIN_124("124", VGAGain.GAIN_210, LNAGain.GAIN_99, MixerGain.GAIN_25), - GAIN_143("143", VGAGain.GAIN_210, LNAGain.GAIN_99, MixerGain.GAIN_44), - GAIN_156("156", VGAGain.GAIN_210, LNAGain.GAIN_112, MixerGain.GAIN_44), - GAIN_165("165", VGAGain.GAIN_210, LNAGain.GAIN_112, MixerGain.GAIN_53), - GAIN_196("196", VGAGain.GAIN_210, LNAGain.GAIN_143, MixerGain.GAIN_53), - GAIN_208("208", VGAGain.GAIN_210, LNAGain.GAIN_143, MixerGain.GAIN_63), - GAIN_228("228", VGAGain.GAIN_210, LNAGain.GAIN_165, MixerGain.GAIN_63), - GAIN_253("253", VGAGain.GAIN_210, LNAGain.GAIN_165, MixerGain.GAIN_88), - GAIN_279("279", VGAGain.GAIN_210, LNAGain.GAIN_191, MixerGain.GAIN_88), - GAIN_296("296", VGAGain.GAIN_210, LNAGain.GAIN_191, MixerGain.GAIN_105), - GAIN_327("327", VGAGain.GAIN_210, LNAGain.GAIN_222, MixerGain.GAIN_105), - GAIN_337("337", VGAGain.GAIN_210, LNAGain.GAIN_222, MixerGain.GAIN_115), - GAIN_363("363", VGAGain.GAIN_210, LNAGain.GAIN_248, MixerGain.GAIN_115), - GAIN_371("371", VGAGain.GAIN_210, LNAGain.GAIN_248, MixerGain.GAIN_123), - GAIN_385("385", VGAGain.GAIN_210, LNAGain.GAIN_262, MixerGain.GAIN_123), - GAIN_401("401", VGAGain.GAIN_210, LNAGain.GAIN_262, MixerGain.GAIN_139), - GAIN_420("420", VGAGain.GAIN_210, LNAGain.GAIN_281, MixerGain.GAIN_139), - GAIN_433("433", VGAGain.GAIN_210, LNAGain.GAIN_281, MixerGain.GAIN_152), - GAIN_438("438", VGAGain.GAIN_210, LNAGain.GAIN_286, MixerGain.GAIN_152), - GAIN_444("444", VGAGain.GAIN_210, LNAGain.GAIN_286, MixerGain.GAIN_158), - GAIN_479("479", VGAGain.GAIN_210, LNAGain.GAIN_321, MixerGain.GAIN_158), - GAIN_482("482", VGAGain.GAIN_210, LNAGain.GAIN_321, MixerGain.GAIN_161), - GAIN_495("495", VGAGain.GAIN_210, LNAGain.GAIN_334, MixerGain.GAIN_161); - - private String mLabel; - private VGAGain mVGAGain; - private LNAGain mLNAGain; - private MixerGain mMixerGain; - - MasterGain(String label, VGAGain vga, LNAGain lna, MixerGain mixer) - { - mLabel = label; - mVGAGain = vga; - mLNAGain = lna; - mMixerGain = mixer; - } - - public String toString() - { - return mLabel; - } - - public VGAGain getVGAGain() - { - return mVGAGain; - } - - public LNAGain getLNAGain() - { - return mLNAGain; - } - - public MixerGain getMixerGain() - { - return mMixerGain; - } - } - - /** - * R8xxx tuner registers and register mask values - */ - public enum Register - { - LNA_GAIN(0x05, 0x1F), - INPUT_SELECTOR_AIR(0x05, 0x20), - INPUT_SELECTOR_CABLE_1(0x05, 0x40), - INPUT_SELECTOR_AIR_AND_CABLE_1(0x05, 0x60), - LOOP_THROUGH(0x05, 0x80), - INPUT_SELECTOR_CABLE_2(0x06, 0x08), - FILTER_GAIN(0x06, 0x30), - PRE_DETECT(0x06, 0x40), - MIXER_GAIN(0x07, 0x1F), - IMAGE_REVERSE(0x07, 0x80), - UNKNOWN_REGISTER_8(0x08, 0x3F), - UNKNOWN_REGISTER_9(0x09, 0x3F), - FILTER_CALIBRATION_CODE(0x0A, 0x1F), - FILTER_CURRENT(0x0A, 0x60), - CALIBRATION_TRIGGER(0x0B, 0x10), - FILTER_CAPACITOR(0x0B, 0x60), - BANDWIDTH_FILTER_GAIN_HIGHPASS_FILTER_CORNER(0x0B, 0xEF), - XTAL_CHECK(0x0C, 0x0F), - VGA_GAIN(0x0C, 0x9F), - LNA_VTH_L(0x0D, 0x0), - MIXER_VTH_L(0x0E, 0x0), - CALIBRATION_CLOCK(0x0F, 0x04), - FILTER_EXTENSION_WIDEST(0x0F, 0x80), - PLL_XTAL_CAPACITOR(0x10, 0x03), - UNKNOWN_REGISTER_10(0x10, 0x04), - PLL_XTAL_CAPACITOR_AND_DRIVE(0x10, 0x0B), - REFERENCE_DIVIDER_2(0x10, 0x10), - CAPACITOR_SELECTOR(0x10, 0x1B), - DIVIDER(0x10, 0xE0), - CP_CUR(0x11, 0x38), - SIGMA_DELTA_MODULATOR_POWER(0x12, 0x08), - VCO_CURRENT(0x12, 0xE0), - VERSION(0x13, 0x3F), - PLL(0x14, 0x0), - SIGMA_DELTA_MODULATOR_LSB(0x15, 0x0), - SIGMA_DELTA_MODULATOR_MSB(0x16, 0x0), - DRAIN(0x17, 0x08), - DIVIDER_BUFFER_CURRENT(0x17, 0x30), - RF_POLY_FILTER_CURRENT(0x19, 0x60), - PLL_AUTOTUNE(0x1A, 0x0C), - PLL_AUTOTUNE_VARIANT(0x1A, 0x08), - AGC_CLOCK(0x1A, 0x30), - RF_POLY_MUX(0x1A, 0xC3), - TF_BAND(0x1B, 0x0), - MIXER_TOP(0x1C, 0xF8), - MIXER_TOP2(0X1C, 0x04), - LNA_TOP(0x1D, 0x38), - LNA_TOP2(0x1D, 0xC7), - CHANNEL_FILTER_EXTENSION(0x1E, 0x60), - LNA_DISCHARGE_CURRENT(0x1E, 0x1F), - LOOP_THROUGH_ATTENUATION(0x1F, 0x80); - - private int mRegister; - private int mMask; - - Register(int register, int mask) - { - mRegister = register; - mMask = mask; - } - - public int getRegister() - { - return mRegister; - } - - public byte getMask() - { - return (byte) mMask; - } - - public boolean isMasked() - { - return mMask != 0; - } - } - - public enum FrequencyRange - { - RANGE_024(24000000, 49999999, 0x08, 0x02, 0xDF, 0x02, 0x01), - RANGE_050(50000000, 54999999, 0x08, 0x02, 0xBE, 0x02, 0x01), - RANGE_055(55000000, 59999999, 0x08, 0x02, 0x8B, 0x02, 0x01), - RANGE_060(60000000, 64999999, 0x08, 0x02, 0x7B, 0x02, 0x01), - RANGE_065(65000000, 69999999, 0x08, 0x02, 0x69, 0x02, 0x01), - RANGE_070(70000000, 74999999, 0x08, 0x02, 0x58, 0x02, 0x01), - RANGE_075(75000000, 79999999, 0x00, 0x02, 0x44, 0x02, 0x01), - RANGE_080(80000000, 89999999, 0x00, 0x02, 0x44, 0x02, 0x01), - RANGE_090(90000000, 99999999, 0x00, 0x02, 0x34, 0x01, 0x01), - RANGE_100(100000000, 109999999, 0x00, 0x02, 0x34, 0x01, 0x01), - RANGE_110(110000000, 119999999, 0x00, 0x02, 0x24, 0x01, 0x01), - RANGE_120(120000000, 139999999, 0x00, 0x02, 0x24, 0x01, 0x01), - RANGE_140(140000000, 179999999, 0x00, 0x02, 0x14, 0x01, 0x01), - RANGE_180(180000000, 219999999, 0x00, 0x02, 0x13, 0x00, 0x00), - RANGE_220(220000000, 249999999, 0x00, 0x02, 0x13, 0x00, 0x00), - RANGE_250(250000000, 279999999, 0x00, 0x02, 0x11, 0x00, 0x00), - RANGE_280(280000000, 309999999, 0x00, 0x02, 0x00, 0x00, 0x00), - RANGE_310(310000000, 449999999, 0x00, 0x41, 0x00, 0x00, 0x00), - RANGE_450(450000000, 587999999, 0x00, 0x41, 0x00, 0x00, 0x00), - RANGE_588(588000000, 649999999, 0x00, 0x40, 0x00, 0x00, 0x00), - RANGE_650(650000000, 1766000000, 0x00, 0x40, 0x00, 0x00, 0x00), - RANGE_UNK(0, 0, 0, 0, 0, 0, 0); - - private long mMinFrequency; - private long mMaxFrequency; - private int mOpenDrain; - private int mRFMux_PolyMux; - private int mTF_c; - private int mXtalCap20p; - private int mXtalCap10p; - - FrequencyRange(long minFrequency, long maxFrequency, int openDrain, int rfMuxPloy, int tf_c, int xtalCap20p, - int xtalCap10p) - { - mMinFrequency = minFrequency; - mMaxFrequency = maxFrequency; - mOpenDrain = openDrain; - mRFMux_PolyMux = rfMuxPloy; - mTF_c = tf_c; - mXtalCap20p = xtalCap20p; - mXtalCap10p = xtalCap10p; - } - - /** - * Indicates if the frequency is contained by the frequency range of this band - */ - public boolean contains(long frequency) - { - return mMinFrequency <= frequency && frequency <= mMaxFrequency; - } - - /** - * Finds the correct frequency range that contains the frequency - * - * @param frequency to lookup - * @return frequency range - */ - public static FrequencyRange getRangeForFrequency(long frequency) - { - for(FrequencyRange range : values()) - { - if(range.contains(frequency)) - { - return range; - } - } - - return RANGE_UNK; - } - - public long getMinFrequency() - { - return mMinFrequency; - } - - public long getMaxFrequency() - { - return mMaxFrequency; - } - - public byte getOpenDrain() - { - return (byte) mOpenDrain; - } - - public byte getRFMuxPolyMux() - { - return (byte) mRFMux_PolyMux; - } - - public byte getTFC() - { - return (byte) mTF_c; - } - - public byte getXTALCap20P() - { - return (byte) mXtalCap20p; - } - - public byte getXTALCap10P() - { - return (byte) mXtalCap10p; - } - - public byte getXTALLowCap0P() - { - return (byte) 0x08; - } - - public byte getXTALHighCap0P() - { - return (byte) 0x00; - } - } - - /** - * Frequency Divider Ranges - *

- * Actual Tuned Frequency Ranges (after subtracting IF = 3.57 MHz ) - *

- * Divider 0: 860.43 to 1782.03 MHz - * Divider 1: 428.43 to 889.23 MHz - * Divider 2: 212.43 to 457.23 MHz - * Divider 3: 104.43 to 219.63 MHz - * Divider 4: 50.43 to 108.03 MHz - * Divider 5: 23.43 to 52.23 MHz - * Divider 6: 9.93 to 24.33 MHz - * Divider 7: 3.18 to 10.38 MHz - */ - public enum FrequencyDivider - { - DIVIDER_0(0, 2, 864000000, 1785600000, 0x00, 28800000), - DIVIDER_1(1, 4, 432000000, 892800000, 0x20, 14400000), - DIVIDER_2(2, 8, 216000000, 460800000, 0x40, 7200000), - DIVIDER_3(3, 16, 108000000, 223200000, 0x60, 3600000), - DIVIDER_4(4, 32, 54000000, 111600000, 0x80, 1800000), - DIVIDER_5(5, 64, 27000000, 55800000, 0xA0, 900000), - DIVIDER_6(6, 128, 13500000, 27900000, 0xC0, 450000), - DIVIDER_7(7, 256, 6750000, 13950000, 0xE0, 225000); - - private int mDividerNumber; - private int mMixerDivider; - private long mMinimumFrequency; - private long mMaximumFrequency; - private int mRegisterSetting; - private int mIntegralValue; - - FrequencyDivider(int dividerNumber, int mixerDivider, long minimumFrequency, long maximumFrequency, - int registerSetting, int integralValue) - { - mDividerNumber = dividerNumber; - mMixerDivider = mixerDivider; - mMinimumFrequency = minimumFrequency; - mMaximumFrequency = maximumFrequency; - mRegisterSetting = registerSetting; - mIntegralValue = integralValue; - } - - public int getDividerNumber(int vcoFineTune, int vcoPowerRef) - { - if(vcoFineTune == vcoPowerRef) - { - return mDividerNumber; - } - else if(vcoFineTune < vcoPowerRef) - { - return mDividerNumber - 1; - } - else if(vcoFineTune > vcoPowerRef) - { - return mDividerNumber + 1; - } - - return mDividerNumber; - } - - public int getMixerDivider() - { - return mMixerDivider; - } - - public long getMinimumFrequency() - { - return mMinimumFrequency; - } - - public long getMaximumFrequency() - { - return mMaximumFrequency; - } - - public byte getDividerRegisterSetting() - { - return (byte) mRegisterSetting; - } - - public boolean contains(long frequency) - { - return mMinimumFrequency <= frequency && frequency <= mMaximumFrequency; - } - - /** - * Returns the correct frequency divider for the specified frequency, - * or returns divider #5, if the frequency is outside of the specified - * frequency ranges. - * - * @param frequency - desired frequency - * @return - FrequencyDivider to use for the specified frequency - */ - public static FrequencyDivider fromFrequency(long frequency) - { - for(FrequencyDivider divider : R8xEmbeddedTuner.FrequencyDivider.values()) - { - if(divider.contains(frequency)) - { - return divider; - } - } - - return R8xEmbeddedTuner.FrequencyDivider.DIVIDER_5; - } - - /** - * Returns the integral to use for this frequency and divider - */ - public Integral getIntegral(long frequency) - { - if(contains(frequency)) - { - int delta = (int) (frequency - mMinimumFrequency); - - int integral = (int) ((double) delta / (double) mIntegralValue); - - return R8xEmbeddedTuner.Integral.fromValue(integral); - } - - throw new IllegalArgumentException("PLL frequency [" + frequency + "] is not valid for this frequency " + - "divider " + this); - } - - /** - * Calculates the 16-bit value of the sigma-delta modulator setting which represents the fractional portion of - * the requested frequency that is left over after subtracting the divider minimum frequency and the integral - * frequency units. That residual value is divided by the integral unit value to derive a 16-bit fractional - * value, returned as an integer - */ - public int getSDM(Integral integral, long frequency) - { - if(contains(frequency)) - { - int delta = (int) (frequency - mMinimumFrequency - (integral.getNumber() * mIntegralValue)); - double fractional = (double) delta / (double) mIntegralValue; - //Left shift the double value 16 bits and truncate to an integer - return (int) (fractional * 0x10000) & 0xFFFF; - } - - return 0; - } - } - - /** - * PLL Integral values. Each value represents one unit of the divided crystal frequency. - */ - public enum Integral - { - I00(0, 0x44), - I01(1, 0x84), - I02(2, 0xC4), - I03(3, 0x05), - I04(4, 0x45), - I05(5, 0x85), - I06(6, 0xC5), - I07(7, 0x06), - I08(8, 0x46), - I09(9, 0x86), - I10(10, 0xC6), - I11(11, 0x07), - I12(12, 0x47), - I13(13, 0x87), - I14(14, 0xC7), - I15(15, 0x08), - I16(16, 0x48), - I17(17, 0x88), - I18(18, 0xC8), - I19(19, 0x09), - I20(20, 0x49), - I21(21, 0x89), - I22(22, 0xC9), - I23(23, 0x0A), - I24(24, 0x4A), - I25(25, 0x8A), - I26(26, 0xCA), - I27(27, 0x0B), - I28(28, 0x4B), - I29(29, 0x8B), - I30(30, 0xCB), - I31(31, 0x0C); - - private int mNumber; - private int mRegister; - - Integral(int number, int register) - { - mNumber = number; - mRegister = register; - } - - public int getNumber() - { - return mNumber; - } - - public byte getRegisterValue() - { - return (byte) mRegister; - } - - public static Integral fromValue(int value) - { - if(0 <= value && value <= 31) - { - return R8xEmbeddedTuner.Integral.values()[value]; - } - - throw new IllegalArgumentException("PLL integral value [" + value + "] must be in the range 0 - 31"); - } - } -} diff --git a/src/main/java/io/github/dsheirer/source/tuner/rtl/r8x/R8xTunerConfiguration.java b/src/main/java/io/github/dsheirer/source/tuner/rtl/r8x/R8xTunerConfiguration.java index a81ae865..9d407838 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/rtl/r8x/R8xTunerConfiguration.java +++ b/src/main/java/io/github/dsheirer/source/tuner/rtl/r8x/R8xTunerConfiguration.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2024 Dennis Sheirer + * Copyright (C) 2014-2026 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 @@ -18,25 +18,34 @@ */ package io.github.dsheirer.source.tuner.rtl.r8x; -import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.annotation.JsonSetter; import io.github.dsheirer.source.tuner.rtl.RTL2832TunerConfiguration; /** - * RTL-2832 with embedded R8xxx tuner configuration + * RTL-2832 with embedded R820T or R828D tuner configuration */ public abstract class R8xTunerConfiguration extends RTL2832TunerConfiguration { - private R8xEmbeddedTuner.MasterGain mMasterMasterGain = R8xEmbeddedTuner.MasterGain.GAIN_327; - private R8xEmbeddedTuner.MixerGain mMixerGain = R8xEmbeddedTuner.MixerGain.GAIN_105; - private R8xEmbeddedTuner.LNAGain mLNAGain = R8xEmbeddedTuner.LNAGain.GAIN_222; - private R8xEmbeddedTuner.VGAGain mVGAGain = R8xEmbeddedTuner.VGAGain.GAIN_210; + public static final long MINIMUM_TUNABLE_FREQUENCY_HZ = 3_180_000; + public static final long MAXIMUM_TUNABLE_FREQUENCY_HZ = 1_782_030_000; + public static final double USABLE_BANDWIDTH_PERCENT = 0.98; + public static final int DC_SPIKE_AVOID_BUFFER = 5000; + /** + * Default gain in tenths of a dB + */ + public static final int DEFAULT_GAIN = 328; + + //Legacy gain settings, used to convert configurations from the previous R8x driver's master, LNA and mixer gains + private boolean mLegacyManualGain = false; + private Integer mLegacyLNAGain; + private Integer mLegacyMixerGain; /** - * Default constructor for JAXB + * Default constructor for Jackson */ public R8xTunerConfiguration() { - super(R8xEmbeddedTuner.MINIMUM_TUNABLE_FREQUENCY_HZ, R8xEmbeddedTuner.MAXIMUM_TUNABLE_FREQUENCY_HZ); + super(MINIMUM_TUNABLE_FREQUENCY_HZ, MAXIMUM_TUNABLE_FREQUENCY_HZ, DEFAULT_GAIN); } /** @@ -46,50 +55,64 @@ public abstract class R8xTunerConfiguration extends RTL2832TunerConfiguration */ public R8xTunerConfiguration(String uniqueID) { - super(uniqueID); + super(uniqueID, DEFAULT_GAIN); } - @JacksonXmlProperty(isAttribute = true, localName = "master_gain") - public R8xEmbeddedTuner.MasterGain getMasterGain() + /** + * Converts the legacy master gain setting: AUTOMATIC, MANUAL (using the LNA and mixer gains) or a gain value + * such as GAIN_327 in tenths of a dB. + */ + @JsonSetter("masterGain") + public void setLegacyMasterGain(String masterGain) { - return mMasterMasterGain; + if("AUTOMATIC".equals(masterGain)) + { + setAGC(true); + } + else if("MANUAL".equals(masterGain)) + { + mLegacyManualGain = true; + updateLegacyManualGain(); + } + else + { + Integer gain = parseLegacyGain(masterGain); + + if(gain != null) + { + setGain(gain); + } + } } - public void setMasterGain(R8xEmbeddedTuner.MasterGain masterGain) + /** + * Converts the legacy LNA gain setting, such as GAIN_222 in tenths of a dB. + */ + @JsonSetter("lnagain") + public void setLegacyLNAGain(String lnaGain) { - mMasterMasterGain = masterGain; + mLegacyLNAGain = parseLegacyGain(lnaGain); + updateLegacyManualGain(); } - @JacksonXmlProperty(isAttribute = true, localName = "mixer_gain") - public R8xEmbeddedTuner.MixerGain getMixerGain() + /** + * Converts the legacy mixer gain setting, such as GAIN_105 in tenths of a dB. + */ + @JsonSetter("mixerGain") + public void setLegacyMixerGain(String mixerGain) { - return mMixerGain; + mLegacyMixerGain = parseLegacyGain(mixerGain); + updateLegacyManualGain(); } - public void setMixerGain(R8xEmbeddedTuner.MixerGain mixerGain) + /** + * Sets the gain to the sum of the legacy LNA and mixer gains when the legacy master gain is MANUAL. + */ + private void updateLegacyManualGain() { - mMixerGain = mixerGain; - } - - @JacksonXmlProperty(isAttribute = true, localName = "lna_gain") - public R8xEmbeddedTuner.LNAGain getLNAGain() - { - return mLNAGain; - } - - public void setLNAGain(R8xEmbeddedTuner.LNAGain lnaGain) - { - mLNAGain = lnaGain; - } - - @JacksonXmlProperty(isAttribute = true, localName = "vga_gain") - public R8xEmbeddedTuner.VGAGain getVGAGain() - { - return mVGAGain; - } - - public void setVGAGain(R8xEmbeddedTuner.VGAGain vgaGain) - { - mVGAGain = vgaGain; + if(mLegacyManualGain && mLegacyLNAGain != null && mLegacyMixerGain != null) + { + setGain(mLegacyLNAGain + mLegacyMixerGain); + } } } diff --git a/src/main/java/io/github/dsheirer/source/tuner/rtl/r8x/R8xTunerEditor.java b/src/main/java/io/github/dsheirer/source/tuner/rtl/r8x/R8xTunerEditor.java deleted file mode 100644 index db3713fc..00000000 --- a/src/main/java/io/github/dsheirer/source/tuner/rtl/r8x/R8xTunerEditor.java +++ /dev/null @@ -1,556 +0,0 @@ -/* - * ***************************************************************************** - * Copyright (C) 2014-2024 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.r8x; - -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.JSeparator; -import javax.swing.JToggleButton; -import javax.swing.SpinnerNumberModel; -import javax.usb.UsbException; - -/** - * R8xxx Tuner Editor - */ -public class R8xTunerEditor extends TunerEditor -{ - private final static Logger mLog = LoggerFactory.getLogger(R8xTunerEditor.class); - private static final long serialVersionUID = 1L; - private static final R8xEmbeddedTuner.MasterGain DEFAULT_GAIN = R8xEmbeddedTuner.MasterGain.GAIN_279; - private JButton mTunerInfoButton; - private JToggleButton mBiasTButton; - private JComboBox mSampleRateCombo; - private JComboBox mMasterGainCombo; - private JComboBox mMixerGainCombo; - private JComboBox mLNAGainCombo; - private JComboBox mVGAGainCombo; - - /** - * Constructs an instance - * @param userPreferences for wide-band recordings - * @param tunerManager for saving configurations - * @param discoveredTuner to edit - */ - public R8xTunerEditor(UserPreferences userPreferences, TunerManager tunerManager, DiscoveredTuner discoveredTuner) - { - super(userPreferences, tunerManager, discoveredTuner); - init(); - tunerStatusUpdated(); - } - - @Override - public long getMinimumTunableFrequency() - { - return R8xEmbeddedTuner.MINIMUM_TUNABLE_FREQUENCY_HZ; - } - - @Override - public long getMaximumTunableFrequency() - { - return R8xEmbeddedTuner.MAXIMUM_TUNABLE_FREQUENCY_HZ; - } - - /** - * Access the R8xxx embedded tuner - * @return R8xxx tuner if there is a tuner, or null otherwise - */ - private R8xEmbeddedTuner getEmbeddedTuner() - { - if(hasTuner()) - { - return (R8xEmbeddedTuner) 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"); - add(new JLabel("Gain Control"), "wrap"); - - add(new JLabel("Master:")); - add(getMasterGainCombo(), "wrap"); - - add(new JLabel("Mixer:")); - add(getMixerGainCombo(), "wrap"); - - add(new JLabel("LNA:")); - add(getLNAGainCombo(), "wrap"); - - add(new JLabel("VGA:")); - add(getVGAGainCombo(), "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()); - getMasterGainCombo().setEnabled(true); - R8xEmbeddedTuner.MasterGain gain = getConfiguration().getMasterGain(); - getMasterGainCombo().setEnabled(true); - getMasterGainCombo().setSelectedItem(gain); - - if(gain == R8xEmbeddedTuner.MasterGain.MANUAL) - { - getMixerGainCombo().setSelectedItem(getConfiguration().getMixerGain()); - getMixerGainCombo().setEnabled(true); - - getLNAGainCombo().setSelectedItem(getConfiguration().getLNAGain()); - getLNAGainCombo().setEnabled(true); - - getVGAGainCombo().setSelectedItem(getConfiguration().getVGAGain()); - getVGAGainCombo().setEnabled(true); - } - else - { - getMixerGainCombo().setEnabled(false); - getMixerGainCombo().setSelectedItem(gain.getMixerGain()); - - getLNAGainCombo().setEnabled(false); - getLNAGainCombo().setSelectedItem(gain.getLNAGain()); - - getVGAGainCombo().setEnabled(false); - getVGAGainCombo().setSelectedItem(gain.getVGAGain()); - } - } - else - { - getBiasTButton().setEnabled(false); - getBiasTButton().setSelected(false); - getTunerInfoButton().setEnabled(false); - getSampleRateCombo().setEnabled(false); - getMasterGainCombo().setEnabled(false); - getMixerGainCombo().setEnabled(false); - getLNAGainCombo().setEnabled(false); - getVGAGainCombo().setEnabled(false); - } - - updateSampleRateToolTip(); - - setLoading(false); - } - - /** - * Bias-T toggle button - * @return - */ - 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(R8xTunerEditor.this, - getTunerInfo(), "Tuner Info", JOptionPane.INFORMATION_MESSAGE)); - } - - return mTunerInfoButton; - } - - private JComboBox getVGAGainCombo() - { - if(mVGAGainCombo == null) - { - mVGAGainCombo = new JComboBox<>(R8xEmbeddedTuner.VGAGain.values()); - mVGAGainCombo.setEnabled(false); - mVGAGainCombo.addActionListener(arg0 -> - { - try - { - R8xEmbeddedTuner.VGAGain vgaGain = (R8xEmbeddedTuner.VGAGain) mVGAGainCombo.getSelectedItem(); - - if(vgaGain == null) - { - vgaGain = DEFAULT_GAIN.getVGAGain(); - } - - if(mVGAGainCombo.isEnabled()) - { - getEmbeddedTuner().setVGAGain(vgaGain, true); - } - - save(); - } - catch(UsbException e) - { - JOptionPane.showMessageDialog(R8xTunerEditor.this, getLogPrefix() + - "couldn't apply the VGA gain setting - " + e.getLocalizedMessage()); - mLog.error(getLogPrefix() + "couldn't apply VGA gain setting", e); - } - }); - mVGAGainCombo.setToolTipText("VGA Gain. Set master gain to MANUAL to enable adjustment"); - } - - return mVGAGainCombo; - } - - private JComboBox getLNAGainCombo() - { - if(mLNAGainCombo == null) - { - mLNAGainCombo = new JComboBox<>(R8xEmbeddedTuner.LNAGain.values()); - mLNAGainCombo.setEnabled(false); - mLNAGainCombo.addActionListener(arg0 -> - { - try - { - R8xEmbeddedTuner.LNAGain lnaGain = (R8xEmbeddedTuner.LNAGain) mLNAGainCombo.getSelectedItem(); - - if(lnaGain == null) - { - lnaGain = DEFAULT_GAIN.getLNAGain(); - } - - if(mLNAGainCombo.isEnabled()) - { - getEmbeddedTuner().setLNAGain(lnaGain, true); - } - - save(); - } - catch(UsbException e) - { - JOptionPane.showMessageDialog(R8xTunerEditor.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); - //Adjust the min/max values for the sample rate. - adjustForSampleRate(sampleRate.getRate()); - save(); - } - catch(SourceException | LibUsbException eSampleRate) - { - JOptionPane.showMessageDialog(R8xTunerEditor.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 JComboBox getMixerGainCombo() - { - if(mMixerGainCombo == null) - { - mMixerGainCombo = new JComboBox<>(R8xEmbeddedTuner.MixerGain.values()); - mMixerGainCombo.setEnabled(false); - mMixerGainCombo.addActionListener(arg0 -> - { - if(!isLoading()) - { - try - { - R8xEmbeddedTuner.MixerGain mixerGain = (R8xEmbeddedTuner.MixerGain) mMixerGainCombo.getSelectedItem(); - - if(mixerGain == null) - { - mixerGain = DEFAULT_GAIN.getMixerGain(); - } - - if(mMixerGainCombo.isEnabled()) - { - getEmbeddedTuner().setMixerGain(mixerGain, true); - } - - save(); - } - catch(UsbException e) - { - JOptionPane.showMessageDialog(R8xTunerEditor.this, getLogPrefix() + - "couldn't apply the mixer gain setting - " + e.getLocalizedMessage()); - - mLog.error(getLogPrefix() + "couldn't apply mixer gain setting - ", e); - } - } - }); - mMixerGainCombo.setToolTipText("Mixer Gain. Set master gain to MANUAL to enable adjustment"); - } - - return mMixerGainCombo; - } - - private JComboBox getMasterGainCombo() - { - if(mMasterGainCombo == null) - { - mMasterGainCombo = new JComboBox<>(R8xEmbeddedTuner.MasterGain.values()); - mMasterGainCombo.setEnabled(false); - mMasterGainCombo.addActionListener(arg0 -> - { - if(!isLoading()) - { - try - { - R8xEmbeddedTuner.MasterGain gain = (R8xEmbeddedTuner.MasterGain)getMasterGainCombo().getSelectedItem(); - getEmbeddedTuner().setGain((R8xEmbeddedTuner.MasterGain)getMasterGainCombo().getSelectedItem(), true); - - if(gain == R8xEmbeddedTuner.MasterGain.MANUAL) - { - getMixerGainCombo().setSelectedItem(gain.getMixerGain()); - getMixerGainCombo().setEnabled(true); - - getLNAGainCombo().setSelectedItem(gain.getLNAGain()); - getLNAGainCombo().setEnabled(true); - - getVGAGainCombo().setSelectedItem(gain.getVGAGain()); - getVGAGainCombo().setEnabled(true); - } - else - { - getMixerGainCombo().setEnabled(false); - getMixerGainCombo().setSelectedItem(gain.getMixerGain()); - - getLNAGainCombo().setEnabled(false); - getLNAGainCombo().setSelectedItem(gain.getLNAGain()); - - getVGAGainCombo().setEnabled(false); - getVGAGainCombo().setSelectedItem(gain.getVGAGain()); - } - - save(); - } - catch(UsbException e) - { - JOptionPane.showMessageDialog(R8xTunerEditor.this, getLogPrefix() + - "couldn't apply the gain setting - " + e.getLocalizedMessage()); - mLog.error(getLogPrefix() + "couldn't apply gain setting - ", e); - } - } - }); - mMasterGainCombo.setToolTipText("Select AUTOMATIC for auto gain, MANUAL to enable
" + - "independent control of Mixer, LNA and Enhance gain
settings, or one of the " + - "individual gain settings for
semi-manual gain control"); - } - - return mMasterGainCombo; - } - - /** - * 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()) - { - R8xTunerConfiguration config = getConfiguration(); - config.setBiasT(getTuner().getController().isBiasT()); - config.setFrequency(getFrequencyControl().getFrequency()); - getConfiguration().setMinimumFrequency(getMinimumFrequencyTextField().getFrequency()); - getConfiguration().setMaximumFrequency(getMaximumFrequencyTextField().getFrequency()); - double value = ((SpinnerNumberModel)getFrequencyCorrectionSpinner().getModel()).getNumber().doubleValue(); - config.setFrequencyCorrection(value); - config.setAutoPPMCorrectionEnabled(getAutoPPMCheckBox().isSelected()); - - config.setSampleRate((SampleRate)getSampleRateCombo().getSelectedItem()); - R8xEmbeddedTuner.MasterGain gain = (R8xEmbeddedTuner.MasterGain)getMasterGainCombo().getSelectedItem(); - config.setMasterGain(gain); - R8xEmbeddedTuner.MixerGain mixerGain = (R8xEmbeddedTuner.MixerGain)getMixerGainCombo().getSelectedItem(); - config.setMixerGain(mixerGain); - R8xEmbeddedTuner.LNAGain lnaGain = (R8xEmbeddedTuner.LNAGain)getLNAGainCombo().getSelectedItem(); - config.setLNAGain(lnaGain); - R8xEmbeddedTuner.VGAGain vgaGain = (R8xEmbeddedTuner.VGAGain)getVGAGainCombo().getSelectedItem(); - config.setVGAGain(vgaGain); - saveConfiguration(); - } - } -} \ No newline at end of file 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 deleted file mode 100644 index c340210b..00000000 --- a/src/main/java/io/github/dsheirer/source/tuner/rtl/r8x/r820t/R820TEmbeddedTuner.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * ***************************************************************************** - * 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.r8x.r820t; - -import io.github.dsheirer.source.SourceException; -import io.github.dsheirer.source.tuner.TunerType; -import io.github.dsheirer.source.tuner.rtl.RTL2832TunerController; -import io.github.dsheirer.source.tuner.rtl.r8x.R8xEmbeddedTuner; - -import javax.usb.UsbException; - -/** - * Rafael Micro R820T and R820T2 Embedded Tuner implementation - */ -public class R820TEmbeddedTuner extends R8xEmbeddedTuner -{ - 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; - - /** - * Constructs an instance - * @param adapter for accessing RTL2832USBController interfaces - */ - public R820TEmbeddedTuner(RTL2832TunerController.ControllerAdapter adapter) - { - super(adapter, VCO_POWER_REF); - } - - @Override - public TunerType getTunerType() - { - return TunerType.RAFAELMICRO_R820T; - } - - @Override - public byte getI2CWriteAddress() - { - return I2C_WRITE_ADDRESS; - } - - @Override - public byte getI2CReadAddress() - { - return I2C_READ_ADDRESS; - } - - /** - * Sets the center frequency. Setting the frequency is a two-part process - * of setting the multiplexer and then setting the Oscillator (PLL). - */ - @Override - public synchronized void setTunedFrequency(long frequency) throws SourceException - { - getAdapter().getLock().lock(); - - try - { - getAdapter().enableI2CRepeater(); - boolean controlI2C = false; - long offsetFrequency = frequency + IF_FREQUENCY; - setMux(offsetFrequency, controlI2C); - setPLL(offsetFrequency, controlI2C); - getAdapter().disableI2CRepeater(); - } - catch(UsbException e) - { - throw new SourceException("R820TTunerController - exception while setting frequency [" + frequency + "] - " + - e.getLocalizedMessage()); - } - finally - { - getAdapter().getLock().unlock(); - } - } -} \ No newline at end of file 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 deleted file mode 100644 index 33940db4..00000000 --- a/src/main/java/io/github/dsheirer/source/tuner/rtl/r8x/r828d/R828DEmbeddedTuner.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * ***************************************************************************** - * 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.r8x.r828d; - -import io.github.dsheirer.source.SourceException; -import io.github.dsheirer.source.tuner.TunerType; -import io.github.dsheirer.source.tuner.rtl.RTL2832TunerController; -import io.github.dsheirer.source.tuner.rtl.r8x.R8xEmbeddedTuner; - -import javax.usb.UsbException; - -/** - * Rafael Micro R828D Embedded Tuner implementation - */ -public class R828DEmbeddedTuner extends R8xEmbeddedTuner -{ - 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. - */ - private final boolean mIsV4Dongle; - - /** - * Constructs an instance - * @param adapter for accessing RTL2832USBController interfaces - */ - public R828DEmbeddedTuner(RTL2832TunerController.ControllerAdapter adapter) - { - super(adapter, VCO_POWER_REF); - - //Set flag to indicate if this is a V4 dongle that supports notch filtering. - mIsV4Dongle = adapter.isV4Dongle(); - } - - @Override - public TunerType getTunerType() - { - return TunerType.RAFAELMICRO_R828D; - } - - @Override - public byte getI2CWriteAddress() - { - return I2C_WRITE_ADDRESS; - } - - @Override - public byte getI2CReadAddress() - { - return I2C_READ_ADDRESS; - } - - /** - * Sets the center frequency. Setting the frequency is a two-part process - * of setting the multiplexer and then setting the Oscillator (PLL). - */ - @Override - public synchronized void setTunedFrequency(long frequency) throws SourceException - { - getAdapter().getLock().lock(); - - try - { - getAdapter().enableI2CRepeater(); - boolean controlI2C = false; - long offsetFrequency = frequency + IF_FREQUENCY; - setMux(offsetFrequency, controlI2C); - setPLL(offsetFrequency, controlI2C); - - //Select the RF input - if(mIsV4Dongle) - { - //Disable notch filtering if frequency falls into a notched band, otherwise enable it. - boolean isNotchFilterFrequency = (frequency < 2_200_000) || - (85_000_000 < frequency && frequency < 112_000_000) || - (172_000_000 < frequency && frequency < 242_000_000); - writeRegister(Register.DRAIN, isNotchFilterFrequency ? (byte)0x00 : (byte)0x08, controlI2C); - - if(frequency <= 28_800_000) //Use cable 2 input - { - writeRegister(Register.INPUT_SELECTOR_AIR, (byte)0x20, controlI2C); //disabled - writeRegister(Register.INPUT_SELECTOR_CABLE_1, (byte)0x00, controlI2C); //disabled - writeRegister(Register.INPUT_SELECTOR_CABLE_2, (byte)0x08, controlI2C); //enabled - } - else if(frequency < 250_000_000) - { - writeRegister(Register.INPUT_SELECTOR_AIR, (byte)0x20, controlI2C); //disabled - writeRegister(Register.INPUT_SELECTOR_CABLE_1, (byte)0x40, controlI2C); //enabled - writeRegister(Register.INPUT_SELECTOR_CABLE_2, (byte)0x00, controlI2C); //disabled - } - else - { - writeRegister(Register.INPUT_SELECTOR_AIR, (byte)0x00, controlI2C); //enabled - writeRegister(Register.INPUT_SELECTOR_CABLE_1, (byte)0x00, controlI2C); //disabled - writeRegister(Register.INPUT_SELECTOR_CABLE_2, (byte)0x00, controlI2C); //disabled - } - } - else - { - byte air_cable_in = (frequency > 345_000_000) ? (byte)0x00 : (byte)0x60; - writeRegister(Register.INPUT_SELECTOR_AIR_AND_CABLE_1, air_cable_in, controlI2C); - } - - getAdapter().disableI2CRepeater(); - } - catch(UsbException e) - { - throw new SourceException("R828DTunerController - exception while setting frequency [" + frequency + "] - " + - e.getLocalizedMessage()); - } - finally - { - getAdapter().getLock().unlock(); - } - } -} \ No newline at end of file diff --git a/src/main/resources/rtl-sdr.rules b/src/main/resources/rtl-sdr.rules index 44dbf0c3..8ed43027 100644 --- a/src/main/resources/rtl-sdr.rules +++ b/src/main/resources/rtl-sdr.rules @@ -90,3 +90,47 @@ SUBSYSTEMS=="usb", ATTRS{idVendor}=="1b80", ATTRS{idProduct}=="d395", MODE:="066 # SVEON STV20 DVB-T USB & FM (FC0012) SUBSYSTEMS=="usb", ATTRS{idVendor}=="1b80", ATTRS{idProduct}=="d39d", MODE:="0666" +# DigitalNow Quad DVB-T PCI-E card (4x FC0012?) +SUBSYSTEMS=="usb", ATTRS{idVendor}=="0413", ATTRS{idProduct}=="6680", MODE:="0666" + +# Leadtek WinFast DTV Dongle mini D (FC0012) +SUBSYSTEMS=="usb", ATTRS{idVendor}=="0413", ATTRS{idProduct}=="6f0f", MODE:="0666" + +# Terratec NOXON DAB Stick - Radio Energy (FC0013) +SUBSYSTEMS=="usb", ATTRS{idVendor}=="0ccd", ATTRS{idProduct}=="00b5", MODE:="0666" + +# Terratec BR DAB Stick (FC0013) +SUBSYSTEMS=="usb", ATTRS{idVendor}=="0ccd", ATTRS{idProduct}=="00b8", MODE:="0666" + +# Terratec WDR DAB Stick (FC0013) +SUBSYSTEMS=="usb", ATTRS{idVendor}=="0ccd", ATTRS{idProduct}=="00b9", MODE:="0666" + +# Terratec MuellerVerlag DAB Stick (FC0013) +SUBSYSTEMS=="usb", ATTRS{idVendor}=="0ccd", ATTRS{idProduct}=="00c0", MODE:="0666" + +# PixelView PV-DT235U(RN) (FC0012) +SUBSYSTEMS=="usb", ATTRS{idVendor}=="1554", ATTRS{idProduct}=="5020", MODE:="0666" + +# Astrometa DVB-T/DVB-T2 (R828D) +SUBSYSTEMS=="usb", ATTRS{idVendor}=="15f4", ATTRS{idProduct}=="0131", MODE:="0666" + +# HanfTek DAB+FM+DVB-T +SUBSYSTEMS=="usb", ATTRS{idVendor}=="15f4", ATTRS{idProduct}=="0133", MODE:="0666" + +# KWorld KW-UB450-T USB DVB-T Pico TV (TUA9001) +SUBSYSTEMS=="usb", ATTRS{idVendor}=="1b80", ATTRS{idProduct}=="d397", MODE:="0666" + +# ASUS U3100MINI_PLUS_V2 (FC0013) +SUBSYSTEMS=="usb", ATTRS{idVendor}=="1b80", ATTRS{idProduct}=="d3a8", MODE:="0666" + +# SVEON STV27 DVB-T USB & FM (FC0013) +SUBSYSTEMS=="usb", ATTRS{idVendor}=="1b80", ATTRS{idProduct}=="d3af", MODE:="0666" + +# SVEON STV21 DVB-T USB & FM +SUBSYSTEMS=="usb", ATTRS{idVendor}=="1b80", ATTRS{idProduct}=="d3b0", MODE:="0666" + +# MSI DigiVox Micro HD (FC2580) +SUBSYSTEMS=="usb", ATTRS{idVendor}=="1d19", ATTRS{idProduct}=="1104", MODE:="0666" + +# Sweex DVB-T USB (FC0012) +SUBSYSTEMS=="usb", ATTRS{idVendor}=="1f4d", ATTRS{idProduct}=="a803", MODE:="0666" diff --git a/src/test/java/io/github/dsheirer/source/tuner/rtl/RTL2832TunerConfigurationTest.java b/src/test/java/io/github/dsheirer/source/tuner/rtl/RTL2832TunerConfigurationTest.java new file mode 100644 index 00000000..e020aa20 --- /dev/null +++ b/src/test/java/io/github/dsheirer/source/tuner/rtl/RTL2832TunerConfigurationTest.java @@ -0,0 +1,161 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2026 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; + +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.github.dsheirer.source.tuner.TunerFactory; +import io.github.dsheirer.source.tuner.TunerType; +import io.github.dsheirer.source.tuner.configuration.TunerConfiguration; +import io.github.dsheirer.source.tuner.rtl.e4k.E4KTunerConfiguration; +import io.github.dsheirer.source.tuner.rtl.fc0012.FC0012TunerConfiguration; +import io.github.dsheirer.source.tuner.rtl.fc0013.FC0013TunerConfiguration; +import io.github.dsheirer.source.tuner.rtl.fc2580.FC2580TunerConfiguration; +import io.github.dsheirer.source.tuner.rtl.r8x.r820t.R820TTunerConfiguration; +import java.util.Arrays; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Tests conversion of tuner configurations saved by the previous RTL-2832 driver and the E4000 IF gain distribution. + */ +public class RTL2832TunerConfigurationTest +{ + //Same settings as TunerConfigurationManager + private final ObjectMapper mObjectMapper = new ObjectMapper() + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + + private RTL2832TunerConfiguration read(String json) throws Exception + { + return (RTL2832TunerConfiguration)mObjectMapper.readValue(json, TunerConfiguration.class); + } + + @Test + public void legacyR8xMasterGain() throws Exception + { + RTL2832TunerConfiguration config = read("{\"type\":\"r820TTunerConfiguration\",\"masterGain\":\"GAIN_296\"," + + "\"mixerGain\":\"GAIN_105\",\"lnagain\":\"GAIN_222\",\"vgagain\":\"GAIN_210\",\"sampleRate\":\"RATE_2_048MHZ\"," + + "\"biasT\":true,\"uniqueID\":\"RTL2832/R820T 00000001\",\"frequency\":101100000}"); + assertInstanceOf(R820TTunerConfiguration.class, config); + assertEquals(296, config.getGain()); + assertFalse(config.isAGC()); + assertTrue(config.isBiasT()); + assertEquals(RTL2832TunerController.SampleRate.RATE_2_048MHZ, config.getSampleRate()); + } + + @Test + public void legacyR8xManualAndAutomaticGain() throws Exception + { + //Stage gains before the master gain, to check that property order doesn't matter + RTL2832TunerConfiguration manual = read("{\"type\":\"r828DTunerConfiguration\",\"lnagain\":\"GAIN_222\"," + + "\"mixerGain\":\"GAIN_105\",\"masterGain\":\"MANUAL\",\"uniqueID\":\"x\"}"); + assertEquals(327, manual.getGain()); + assertFalse(manual.isAGC()); + + RTL2832TunerConfiguration automatic = read("{\"type\":\"r820TTunerConfiguration\",\"masterGain\":\"AUTOMATIC\"," + + "\"uniqueID\":\"x\"}"); + assertTrue(automatic.isAGC()); + } + + @Test + public void legacyE4K() throws Exception + { + RTL2832TunerConfiguration manual = read("{\"type\":\"e4KTunerConfiguration\",\"masterGain\":\"MANUAL\"," + + "\"mixerGain\":\"GAIN_12\",\"lnagain\":\"GAIN_PLUS_200\",\"ifgain\":\"SENSITIVITY_30\",\"uniqueID\":\"x\"}"); + assertEquals(320, manual.getGain()); + assertEquals(30, ((E4KTunerConfiguration)manual).getIFGainDb()); + + //Legacy IF gains above the saturation limit are capped + TunerConfiguration high = read("{\"type\":\"e4KTunerConfiguration\",\"ifgain\":\"LINEARITY_60\",\"uniqueID\":\"x\"}"); + assertEquals(RTL2832TunerController.E4K_IF_GAIN_MAXIMUM, ((E4KTunerConfiguration)high).getIFGainDb()); + + RTL2832TunerConfiguration minus = read("{\"type\":\"e4KTunerConfiguration\",\"masterGain\":\"MINUS_10\"," + + "\"uniqueID\":\"x\"}"); + assertEquals(-10, minus.getGain()); + } + + @Test + public void legacyFC0013() throws Exception + { + RTL2832TunerConfiguration config = read("{\"type\":\"fc0013TunerConfiguration\",\"lnaGain\":\"G23\"," + + "\"agc\":true,\"uniqueID\":\"x\"}"); + assertInstanceOf(FC0013TunerConfiguration.class, config); + assertEquals(197, config.getGain()); + assertTrue(config.isAGC()); + + //Legacy settings map by LNA register value: G03 and G04 are both -6.3 dB + int[] expected = {-99, -73, -65, -63, -63, -60, -58, -54, 58}; + + for(int x = 0; x < expected.length; x++) + { + RTL2832TunerConfiguration legacy = read("{\"type\":\"fc0013TunerConfiguration\",\"lnaGain\":\"G0" + x + + "\",\"uniqueID\":\"x\"}"); + assertEquals(expected[x], legacy.getGain(), "G0" + x); + } + } + + @Test + public void roundTrip() throws Exception + { + E4KTunerConfiguration config = new E4KTunerConfiguration("RTL2832/E4000 1"); + config.setGain(340); + config.setAGC(true); + config.setIFGainDb(41); + String json = mObjectMapper.writeValueAsString(config); + E4KTunerConfiguration copy = (E4KTunerConfiguration)read(json); + assertEquals(340, copy.getGain()); + assertTrue(copy.isAGC()); + assertEquals(41, copy.getIFGainDb()); + } + + @Test + public void e4kIFStageGains() + { + for(int gain = RTL2832TunerController.E4K_IF_GAIN_MINIMUM; gain <= RTL2832TunerController.E4K_IF_GAIN_MAXIMUM; gain++) + { + int[] stages = RTL2832TunerController.getE4KIFStageGains(gain); + assertEquals(gain, Arrays.stream(stages).sum(), "Total IF gain " + gain); + } + + assertArrayEquals(new int[]{-3, 0, 0, 0, 3, 3}, RTL2832TunerController.getE4KIFStageGains(0)); + assertArrayEquals(new int[]{6, 9, 9, 2, 15, 15}, RTL2832TunerController.getE4KIFStageGains(99)); + } + + @Test + public void fc0012AndFC2580RoundTrip() throws Exception + { + TunerConfiguration fc0012 = TunerFactory.getTunerConfiguration(TunerType.FITIPOWER_FC0012, "RTL-2832/FC0012 1"); + assertInstanceOf(FC0012TunerConfiguration.class, fc0012); + assertEquals(FC0012TunerConfiguration.DEFAULT_GAIN, ((RTL2832TunerConfiguration)fc0012).getGain()); + ((RTL2832TunerConfiguration)fc0012).setGain(71); + RTL2832TunerConfiguration fc0012Copy = read(mObjectMapper.writeValueAsString(fc0012)); + assertInstanceOf(FC0012TunerConfiguration.class, fc0012Copy); + assertEquals(71, fc0012Copy.getGain()); + + TunerConfiguration fc2580 = TunerFactory.getTunerConfiguration(TunerType.FCI_FC2580, "RTL-2832/FC2580 1"); + assertInstanceOf(FC2580TunerConfiguration.class, fc2580); + RTL2832TunerConfiguration fc2580Copy = read(mObjectMapper.writeValueAsString(fc2580)); + assertInstanceOf(FC2580TunerConfiguration.class, fc2580Copy); + } +}