mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 08:37:25 -05:00
1536 lines
46 KiB
Java
1536 lines
46 KiB
Java
/*******************************************************************************
|
|
* SDR Trunk
|
|
* Copyright (C) 2014-2017 Dennis Sheirer
|
|
*
|
|
* Java version based on librtlsdr
|
|
* Copyright (C) 2012-2013 by Steve Markgraf <steve@steve-m.de>
|
|
* Copyright (C) 2012 by Dimitri Stolnikov <horiz0n@gmx.net>
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
******************************************************************************/
|
|
package source.tuner.rtl;
|
|
|
|
import org.slf4j.Logger;
|
|
import org.slf4j.LoggerFactory;
|
|
import org.usb4java.Device;
|
|
import org.usb4java.DeviceDescriptor;
|
|
import org.usb4java.DeviceHandle;
|
|
import org.usb4java.LibUsb;
|
|
import org.usb4java.LibUsbException;
|
|
import sample.Broadcaster;
|
|
import sample.Listener;
|
|
import sample.adapter.ByteSampleAdapter;
|
|
import sample.adapter.ISampleAdapter;
|
|
import sample.complex.ComplexBuffer;
|
|
import source.SourceException;
|
|
import source.tuner.TunerController;
|
|
import source.tuner.TunerManager;
|
|
import source.tuner.TunerType;
|
|
import source.tuner.usb.USBTransferProcessor;
|
|
|
|
import javax.usb.UsbDisconnectedException;
|
|
import javax.usb.UsbException;
|
|
import java.nio.ByteBuffer;
|
|
import java.nio.ByteOrder;
|
|
import java.nio.charset.Charset;
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
|
|
public abstract class RTL2832TunerController extends TunerController
|
|
{
|
|
private final static Logger mLog = LoggerFactory.getLogger(RTL2832TunerController.class);
|
|
|
|
public final static int TWO_TO_22_POWER = 4194304;
|
|
|
|
public final static int USB_TRANSFER_BUFFER_SIZE = 131072;
|
|
public final static byte USB_INTERFACE = (byte) 0x0;
|
|
public final static byte CONTROL_ENDPOINT_IN = (byte) (LibUsb.ENDPOINT_IN | LibUsb.REQUEST_TYPE_VENDOR);
|
|
public final static byte CONTROL_ENDPOINT_OUT = (byte) (LibUsb.ENDPOINT_OUT | LibUsb.REQUEST_TYPE_VENDOR);
|
|
|
|
public final static long TIMEOUT_US = 1000000l; //uSeconds
|
|
public final static byte REQUEST_ZERO = (byte) 0;
|
|
public final static byte EEPROM_ADDRESS = (byte) 0xA0;
|
|
|
|
public final static byte[] sFIR_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;
|
|
|
|
protected Device mDevice;
|
|
protected DeviceDescriptor mDeviceDescriptor;
|
|
protected DeviceHandle mDeviceHandle;
|
|
|
|
private SampleRate mSampleRate = DEFAULT_SAMPLE_RATE;
|
|
|
|
protected ByteSampleAdapter mSampleAdapter = new ByteSampleAdapter();
|
|
protected int mOscillatorFrequency = 28800000; //28.8 MHz
|
|
protected USBTransferProcessor mUSBTransferProcessor;
|
|
protected Descriptor mDescriptor;
|
|
|
|
/**
|
|
* Abstract tuner controller device. Use the static getTunerClass() method
|
|
* to determine the tuner type, and construct the corresponding child
|
|
* tuner controller class for that tuner type.
|
|
*/
|
|
public RTL2832TunerController(Device device, DeviceDescriptor deviceDescriptor, long minTunableFrequency,
|
|
long maxTunableFrequency, int centerUnusableBandwidth,
|
|
double usableBandwidthPercentage) throws SourceException
|
|
{
|
|
super(minTunableFrequency,
|
|
maxTunableFrequency,
|
|
centerUnusableBandwidth,
|
|
usableBandwidthPercentage);
|
|
|
|
mDevice = device;
|
|
mDeviceDescriptor = deviceDescriptor;
|
|
}
|
|
|
|
|
|
public void init() throws SourceException
|
|
{
|
|
mDeviceHandle = new DeviceHandle();
|
|
|
|
int result = LibUsb.open(mDevice, mDeviceHandle);
|
|
|
|
if(result != LibUsb.SUCCESS)
|
|
{
|
|
mDeviceHandle = null;
|
|
|
|
throw new SourceException("libusb couldn't open RTL2832 usb "
|
|
+ "device [" + LibUsb.errorName(result) + "]");
|
|
}
|
|
|
|
claimInterface(mDeviceHandle);
|
|
|
|
try
|
|
{
|
|
setSampleRate(DEFAULT_SAMPLE_RATE);
|
|
}
|
|
catch(Exception e)
|
|
{
|
|
throw new SourceException("RTL2832 Tuner Controller - couldn't "
|
|
+ "set default sample rate", e);
|
|
}
|
|
|
|
byte[] eeprom = null;
|
|
|
|
try
|
|
{
|
|
/* Read the contents of the 256-byte EEPROM */
|
|
eeprom = readEEPROM(mDeviceHandle, (short) 0, 256);
|
|
}
|
|
catch(Exception e)
|
|
{
|
|
mLog.error("error while reading the EEPROM device descriptor", e);
|
|
}
|
|
|
|
try
|
|
{
|
|
mDescriptor = new Descriptor(eeprom);
|
|
|
|
if(eeprom == null)
|
|
{
|
|
mLog.error("eeprom byte array was null - constructed "
|
|
+ "empty descriptor object");
|
|
}
|
|
}
|
|
catch(Exception e)
|
|
{
|
|
mLog.error("error while constructing device descriptor using "
|
|
+ "descriptor byte array " +
|
|
(eeprom == null ? "[null]" : Arrays.toString(eeprom)), e);
|
|
}
|
|
|
|
String deviceName = getTunerType().getLabel() + " " + getUniqueID();
|
|
|
|
mUSBTransferProcessor = new RTL2832USBTransferProcessor(deviceName, mDeviceHandle, mSampleAdapter,
|
|
USB_TRANSFER_BUFFER_SIZE);
|
|
}
|
|
|
|
/**
|
|
* Claims the USB interface. Attempts to detach the active kernel driver
|
|
* if one is currently attached.
|
|
*/
|
|
public static void claimInterface(DeviceHandle handle) throws SourceException
|
|
{
|
|
if(handle != null)
|
|
{
|
|
int result = LibUsb.kernelDriverActive(handle, USB_INTERFACE);
|
|
|
|
if(result == 1)
|
|
{
|
|
result = LibUsb.detachKernelDriver(handle, USB_INTERFACE);
|
|
|
|
if(result != LibUsb.SUCCESS)
|
|
{
|
|
mLog.error("failed attempt to detach kernel driver [" +
|
|
LibUsb.errorName(result) + "]");
|
|
|
|
throw new SourceException("couldn't detach kernel driver "
|
|
+ "from device");
|
|
}
|
|
}
|
|
|
|
result = LibUsb.claimInterface(handle, USB_INTERFACE);
|
|
|
|
if(result != LibUsb.SUCCESS)
|
|
{
|
|
throw new SourceException("couldn't claim usb interface [" +
|
|
LibUsb.errorName(result) + "]");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
throw new SourceException("couldn't claim usb interface - no "
|
|
+ "device handle");
|
|
}
|
|
}
|
|
|
|
public static void releaseInterface(DeviceHandle handle)
|
|
throws SourceException
|
|
{
|
|
int result = LibUsb.releaseInterface(handle, USB_INTERFACE);
|
|
|
|
if(result != LibUsb.SUCCESS)
|
|
{
|
|
throw new SourceException("couldn't release interface [" +
|
|
LibUsb.errorName(result) + "]");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Descriptor contains all identifiers and labels parsed from the EEPROM.
|
|
*
|
|
* May return null if unable to get 256 byte eeprom descriptor from tuner
|
|
* or if the descriptor doesn't begin with byte values of 0x28 and 0x32
|
|
* meaning it is a valid (and can be parsed) RTL2832 descriptor
|
|
*/
|
|
public Descriptor getDescriptor()
|
|
{
|
|
if(mDescriptor != null && mDescriptor.isValid())
|
|
{
|
|
return mDescriptor;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public void setSamplingMode(SampleMode mode) throws LibUsbException
|
|
{
|
|
switch(mode)
|
|
{
|
|
case QUADRATURE:
|
|
/* Set intermediate frequency to 0 Hz */
|
|
setIFFrequency(0);
|
|
|
|
/* Enable I/Q ADC Input */
|
|
writeDemodRegister(mDeviceHandle,
|
|
Page.ZERO,
|
|
(short) 0x08,
|
|
(short) 0xCD,
|
|
1);
|
|
|
|
/* Enable zero-IF mode */
|
|
writeDemodRegister(mDeviceHandle,
|
|
Page.ONE,
|
|
(short) 0xB1,
|
|
(short) 0x1B,
|
|
1);
|
|
|
|
/* Set default i/q path */
|
|
writeDemodRegister(mDeviceHandle,
|
|
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);
|
|
}
|
|
}
|
|
|
|
public void setIFFrequency(int frequency) throws LibUsbException
|
|
{
|
|
long ifFrequency = ((long) TWO_TO_22_POWER * (long) frequency) /
|
|
(long) mOscillatorFrequency * -1;
|
|
|
|
/* Write byte 2 (high) */
|
|
writeDemodRegister(mDeviceHandle,
|
|
Page.ONE,
|
|
(short) 0x19,
|
|
(short) (Long.rotateRight(ifFrequency, 16) & 0x3F),
|
|
1);
|
|
|
|
/* Write byte 1 (middle) */
|
|
writeDemodRegister(mDeviceHandle,
|
|
Page.ONE,
|
|
(short) 0x1A,
|
|
(short) (Long.rotateRight(ifFrequency, 8) & 0xFF),
|
|
1);
|
|
|
|
/* Write byte 0 (low) */
|
|
writeDemodRegister(mDeviceHandle,
|
|
Page.ONE,
|
|
(short) 0x1B,
|
|
(short) (ifFrequency & 0xFF),
|
|
1);
|
|
}
|
|
|
|
public abstract void initTuner(boolean controlI2CRepeater)
|
|
throws UsbException;
|
|
|
|
/**
|
|
* 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.
|
|
*
|
|
* @return serial number of the device
|
|
*/
|
|
public String getUniqueID()
|
|
{
|
|
if(mDescriptor != null && mDescriptor.hasSerial())
|
|
{
|
|
return mDescriptor.getSerial();
|
|
}
|
|
else
|
|
{
|
|
int serial =
|
|
(0xFF & mDeviceDescriptor.iSerialNumber());
|
|
|
|
return "SER#" + serial;
|
|
}
|
|
}
|
|
|
|
public abstract void setSampleRateFilters(int sampleRate)
|
|
throws SourceException;
|
|
|
|
public abstract TunerType getTunerType();
|
|
|
|
public static TunerType identifyTunerType(Device device)
|
|
throws SourceException
|
|
{
|
|
DeviceHandle handle = new DeviceHandle();
|
|
|
|
int reason = LibUsb.open(device, handle);
|
|
|
|
if(reason != LibUsb.SUCCESS)
|
|
{
|
|
throw new SourceException("couldn't open device - check permissions"
|
|
+ " Linux: udev.rule? , or Windows: reinstall Zadig? [" + LibUsb.errorName(reason) + "]");
|
|
}
|
|
|
|
TunerType tunerClass = TunerType.UNKNOWN;
|
|
|
|
try
|
|
{
|
|
claimInterface(handle);
|
|
|
|
/* Perform a dummy write to see if the device needs reset */
|
|
boolean resetRequired = false;
|
|
|
|
try
|
|
{
|
|
writeRegister(handle,
|
|
Block.USB,
|
|
Address.USB_SYSCTL.getAddress(),
|
|
0x09,
|
|
1);
|
|
}
|
|
catch(LibUsbException e)
|
|
{
|
|
|
|
if(e.getErrorCode() < 0)
|
|
{
|
|
mLog.error("error performing dummy write - attempting "
|
|
+ "device reset", e);
|
|
|
|
resetRequired = true;
|
|
}
|
|
else
|
|
{
|
|
throw new SourceException("error performing dummy write "
|
|
+ "to device [" + LibUsb.errorName(
|
|
e.getErrorCode()) + "]", e);
|
|
}
|
|
}
|
|
|
|
if(resetRequired)
|
|
{
|
|
reason = LibUsb.resetDevice(handle);
|
|
|
|
try
|
|
{
|
|
writeRegister(handle,
|
|
Block.USB,
|
|
Address.USB_SYSCTL.getAddress(),
|
|
0x09,
|
|
1);
|
|
}
|
|
catch(LibUsbException e2)
|
|
{
|
|
mLog.error("device reset attempted, but lost device handle. "
|
|
+ "Try restarting the application to use this device");
|
|
|
|
throw new SourceException("couldn't reset device");
|
|
}
|
|
}
|
|
|
|
/* Initialize the baseband */
|
|
initBaseband(handle);
|
|
|
|
enableI2CRepeater(handle, true);
|
|
|
|
boolean controlI2CRepeater = false;
|
|
|
|
/* Test for each tuner type until we find the correct one */
|
|
if(isTuner(TunerTypeCheck.E4K, handle, controlI2CRepeater))
|
|
{
|
|
tunerClass = TunerType.ELONICS_E4000;
|
|
}
|
|
else if(isTuner(TunerTypeCheck.FC0013, handle, controlI2CRepeater))
|
|
{
|
|
tunerClass = TunerType.FITIPOWER_FC0013;
|
|
}
|
|
else if(isTuner(TunerTypeCheck.R820T, handle, controlI2CRepeater))
|
|
{
|
|
tunerClass = TunerType.RAFAELMICRO_R820T;
|
|
}
|
|
else if(isTuner(TunerTypeCheck.R828D, handle, controlI2CRepeater))
|
|
{
|
|
tunerClass = TunerType.RAFAELMICRO_R828D;
|
|
}
|
|
else if(isTuner(TunerTypeCheck.FC2580, handle, controlI2CRepeater))
|
|
{
|
|
tunerClass = TunerType.FCI_FC2580;
|
|
}
|
|
else if(isTuner(TunerTypeCheck.FC0012, handle, controlI2CRepeater))
|
|
{
|
|
tunerClass = TunerType.FITIPOWER_FC0012;
|
|
}
|
|
|
|
enableI2CRepeater(handle, false);
|
|
|
|
releaseInterface(handle);
|
|
|
|
LibUsb.close(handle);
|
|
}
|
|
catch(Exception e)
|
|
{
|
|
mLog.error("error while determining tuner type", e);
|
|
}
|
|
|
|
return tunerClass;
|
|
}
|
|
|
|
/**
|
|
* Releases the USB interface
|
|
*/
|
|
public void release()
|
|
{
|
|
try
|
|
{
|
|
if(mUSBTransferProcessor != null)
|
|
{
|
|
mUSBTransferProcessor.removeAllListeners();
|
|
TunerManager.LIBUSB_TRANSFER_PROCESSOR.unregisterTransferProcessor(mUSBTransferProcessor);
|
|
}
|
|
|
|
LibUsb.releaseInterface(mDeviceHandle, USB_INTERFACE);
|
|
}
|
|
catch(Exception e)
|
|
{
|
|
mLog.error("attempt to release USB interface failed", e);
|
|
}
|
|
}
|
|
|
|
public void resetUSBBuffer() throws LibUsbException
|
|
{
|
|
writeRegister(mDeviceHandle, Block.USB, Address.USB_EPA_CTL.getAddress(), 0x1002, 2);
|
|
writeRegister(mDeviceHandle, Block.USB, Address.USB_EPA_CTL.getAddress(), 0x0000, 2);
|
|
}
|
|
|
|
public static void initBaseband(DeviceHandle handle) throws LibUsbException
|
|
{
|
|
/* Initialize USB */
|
|
writeRegister(handle, Block.USB, Address.USB_SYSCTL.getAddress(), 0x09, 1);
|
|
writeRegister(handle, Block.USB, Address.USB_EPA_MAXPKT.getAddress(), 0x0002, 2);
|
|
writeRegister(handle, Block.USB, Address.USB_EPA_CTL.getAddress(), 0x1002, 2);
|
|
|
|
/* Power on demod */
|
|
writeRegister(handle, Block.SYS, Address.DEMOD_CTL_1.getAddress(), 0x22, 1);
|
|
writeRegister(handle, Block.SYS, Address.DEMOD_CTL.getAddress(), 0xE8, 1);
|
|
|
|
/* Reset demod */
|
|
writeDemodRegister(handle, Page.ONE, (short) 0x01, 0x14, 1); //Bit 3 = soft reset
|
|
writeDemodRegister(handle, Page.ONE, (short) 0x01, 0x10, 1);
|
|
|
|
/* Disable spectrum inversion and adjacent channel rejection */
|
|
writeDemodRegister(handle, Page.ONE, (short) 0x15, 0x00, 1);
|
|
writeDemodRegister(handle, Page.ONE, (short) 0x16, 0x0000, 2);
|
|
|
|
/* Clear DDC shift and IF frequency registers */
|
|
writeDemodRegister(handle, Page.ONE, (short) 0x16, 0x00, 1);
|
|
writeDemodRegister(handle, Page.ONE, (short) 0x17, 0x00, 1);
|
|
writeDemodRegister(handle, Page.ONE, (short) 0x18, 0x00, 1);
|
|
writeDemodRegister(handle, Page.ONE, (short) 0x19, 0x00, 1);
|
|
writeDemodRegister(handle, Page.ONE, (short) 0x1A, 0x00, 1);
|
|
writeDemodRegister(handle, Page.ONE, (short) 0x1B, 0x00, 1);
|
|
|
|
/* Set FIR coefficients */
|
|
for(int x = 0; x < sFIR_COEFFICIENTS.length; x++)
|
|
{
|
|
writeDemodRegister(handle,
|
|
Page.ONE,
|
|
(short) (0x1C + x),
|
|
sFIR_COEFFICIENTS[x],
|
|
1);
|
|
}
|
|
|
|
/* Enable SDR mode, disable DAGC (bit 5) */
|
|
writeDemodRegister(handle, Page.ZERO, (short) 0x19, 0x05, 1);
|
|
|
|
/* Init FSM state-holding register */
|
|
writeDemodRegister(handle, Page.ONE, (short) 0x93, 0xF0, 1);
|
|
writeDemodRegister(handle, Page.ONE, (short) 0x94, 0x0F, 1);
|
|
|
|
/* Disable AGC (en_dagc, bit 0) (seems to have no effect) */
|
|
writeDemodRegister(handle, Page.ONE, (short) 0x11, 0x00, 1);
|
|
|
|
/* Disable RF and IF AGC loop */
|
|
writeDemodRegister(handle, Page.ONE, (short) 0x04, 0x00, 1);
|
|
|
|
/* Disable PID filter */
|
|
writeDemodRegister(handle, Page.ZERO, (short) 0x61, 0x60, 1);
|
|
|
|
/* opt_adc_iq = 0, default ADC_I/ADC_Q datapath */
|
|
writeDemodRegister(handle, 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(handle, Page.ONE, (short) 0xB1, 0x1B, 1);
|
|
|
|
/* Disable 4.096 MHz clock output on pin TP_CK0 */
|
|
writeDemodRegister(handle, Page.ZERO, (short) 0x0D, 0x83, 1);
|
|
}
|
|
|
|
protected void deinitBaseband(DeviceHandle handle)
|
|
throws IllegalArgumentException, UsbDisconnectedException, UsbException
|
|
{
|
|
writeRegister(handle, Block.SYS, Address.DEMOD_CTL.getAddress(), 0x20, 1);
|
|
}
|
|
|
|
/**
|
|
* Sets the General Purpose Input/Output (GPIO) register bit
|
|
*
|
|
* @param handle - USB tuner device
|
|
* @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
|
|
*/
|
|
protected static void setGPIOBit(DeviceHandle handle,
|
|
byte bitMask,
|
|
boolean enabled) throws LibUsbException
|
|
{
|
|
//Get current register value
|
|
int value = readRegister(handle, Block.SYS, Address.GPO.getAddress(), 1);
|
|
|
|
//Update the masked bits
|
|
if(enabled)
|
|
{
|
|
value |= bitMask;
|
|
}
|
|
else
|
|
{
|
|
value &= ~bitMask;
|
|
}
|
|
|
|
//Write the change back to the device
|
|
writeRegister(handle, Block.SYS, Address.GPO.getAddress(), value, 1);
|
|
}
|
|
|
|
/**
|
|
* Enables GPIO Output
|
|
*
|
|
* @param handle - usb tuner device
|
|
* @param bitMask - mask containing one bit value in targeted bit field(s)
|
|
* @throws UsbDisconnectedException
|
|
* @throws UsbException
|
|
*/
|
|
protected static void setGPIOOutput(DeviceHandle handle, byte bitMask)
|
|
throws LibUsbException
|
|
{
|
|
//Get current register value
|
|
int value = readRegister(handle, Block.SYS, Address.GPD.getAddress(), 1);
|
|
|
|
//Mask the value and rewrite it
|
|
writeRegister(handle, Block.SYS, Address.GPO.getAddress(),
|
|
value & ~bitMask, 1);
|
|
|
|
//Get current register value
|
|
value = readRegister(handle, Block.SYS, Address.GPOE.getAddress(), 1);
|
|
|
|
//Mask the value and rewrite it
|
|
writeRegister(handle, Block.SYS, Address.GPOE.getAddress(),
|
|
value | bitMask, 1);
|
|
}
|
|
|
|
protected static void enableI2CRepeater(DeviceHandle handle,
|
|
boolean enabled)
|
|
throws LibUsbException
|
|
{
|
|
Page page = Page.ONE;
|
|
short address = 1;
|
|
int value;
|
|
|
|
if(enabled)
|
|
{
|
|
value = 0x18; //ON
|
|
}
|
|
else
|
|
{
|
|
value = 0x10; //OFF
|
|
}
|
|
|
|
writeDemodRegister(handle, page, address, value, 1);
|
|
}
|
|
|
|
protected boolean isI2CRepeaterEnabled() throws SourceException
|
|
{
|
|
int register = readDemodRegister(mDeviceHandle, Page.ONE, (short) 0x1, 1);
|
|
|
|
return register == 0x18;
|
|
}
|
|
|
|
protected static int readI2CRegister(DeviceHandle handle,
|
|
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(handle, true);
|
|
|
|
write(handle, address, Block.I2C, buffer);
|
|
read(handle, address, Block.I2C, data);
|
|
|
|
enableI2CRepeater(handle, false);
|
|
}
|
|
else
|
|
{
|
|
write(handle, address, Block.I2C, buffer);
|
|
read(handle, address, Block.I2C, data);
|
|
}
|
|
|
|
return (int) (data.get() & 0xFF);
|
|
}
|
|
|
|
protected void writeI2CRegister(DeviceHandle handle,
|
|
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(handle, true);
|
|
write(handle, address, Block.I2C, buffer);
|
|
enableI2CRepeater(handle, false);
|
|
}
|
|
else
|
|
{
|
|
write(handle, address, Block.I2C, buffer);
|
|
}
|
|
}
|
|
|
|
protected static void writeDemodRegister(DeviceHandle handle,
|
|
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(handle, newAddress, index, buffer);
|
|
|
|
readDemodRegister(handle, Page.TEN, (short) 1, length);
|
|
}
|
|
|
|
protected static int readDemodRegister(DeviceHandle handle,
|
|
Page page,
|
|
short address,
|
|
int length) throws LibUsbException
|
|
{
|
|
short index = page.getPage();
|
|
short newAddress = (short) ((address << 8) | 0x20);
|
|
|
|
ByteBuffer buffer = ByteBuffer.allocateDirect(length);
|
|
|
|
read(handle, newAddress, index, buffer);
|
|
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
|
|
|
if(length == 2)
|
|
{
|
|
return (int) (buffer.getShort() & 0xFFFF);
|
|
}
|
|
else
|
|
{
|
|
return (int) (buffer.get() & 0xFF);
|
|
}
|
|
}
|
|
|
|
protected static void writeRegister(DeviceHandle handle,
|
|
Block block,
|
|
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);
|
|
}
|
|
else
|
|
{
|
|
throw new IllegalArgumentException("Cannot write value greater "
|
|
+ "than 16 bits to the register - length [" + length + "]");
|
|
}
|
|
|
|
buffer.rewind();
|
|
|
|
write(handle, address, block, buffer);
|
|
}
|
|
|
|
protected static int readRegister(DeviceHandle handle,
|
|
Block block,
|
|
short address,
|
|
int length) throws LibUsbException
|
|
{
|
|
ByteBuffer buffer = ByteBuffer.allocateDirect(2);
|
|
|
|
read(handle, address, block, buffer);
|
|
|
|
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
|
|
|
if(length == 2)
|
|
{
|
|
return (int) (buffer.getShort() & 0xFFFF);
|
|
}
|
|
else
|
|
{
|
|
return (int) (buffer.get() & 0xFF);
|
|
}
|
|
}
|
|
|
|
/**
|
|
*/
|
|
protected static void write(DeviceHandle handle,
|
|
short address,
|
|
Block block,
|
|
ByteBuffer buffer) throws LibUsbException
|
|
{
|
|
write(handle, address, block.getWriteIndex(), buffer);
|
|
}
|
|
|
|
protected static void write(DeviceHandle handle,
|
|
short value,
|
|
short index,
|
|
ByteBuffer buffer) throws LibUsbException
|
|
{
|
|
if(handle != null)
|
|
{
|
|
int transferred = LibUsb.controlTransfer(handle,
|
|
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
|
|
*/
|
|
protected static void read(DeviceHandle handle,
|
|
short address,
|
|
short index,
|
|
ByteBuffer buffer) throws LibUsbException
|
|
{
|
|
if(handle != null)
|
|
{
|
|
int transferred = LibUsb.controlTransfer(handle,
|
|
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);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
throw new LibUsbException("device handle is null",
|
|
LibUsb.ERROR_NO_DEVICE);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Reads byte array from index at the address.
|
|
*
|
|
* @return big-endian byte array (needs to be swapped to be usable)
|
|
*/
|
|
protected static void read(DeviceHandle handle,
|
|
short address,
|
|
Block block,
|
|
ByteBuffer buffer) throws LibUsbException
|
|
{
|
|
read(handle, 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 handle - handle to the usb tuner device
|
|
* @param controlI2CRepeater - indicates if the method should control the
|
|
* I2C repeater independently
|
|
* @return - true if the device is the specified tuner type
|
|
*/
|
|
protected static boolean isTuner(TunerTypeCheck type,
|
|
DeviceHandle handle,
|
|
boolean controlI2CRepeater)
|
|
{
|
|
try
|
|
{
|
|
if(type == TunerTypeCheck.FC0012 ||
|
|
type == TunerTypeCheck.FC2580)
|
|
{
|
|
/* Initialize the GPIOs */
|
|
setGPIOOutput(handle, (byte) 0x20);
|
|
|
|
/* Reset tuner before probing */
|
|
setGPIOBit(handle, (byte) 0x20, true);
|
|
setGPIOBit(handle, (byte) 0x20, false);
|
|
}
|
|
|
|
int value = readI2CRegister(handle, type.getI2CAddress(),
|
|
type.getCheckAddress(), controlI2CRepeater);
|
|
|
|
if(type == TunerTypeCheck.FC2580)
|
|
{
|
|
return ((value & 0x7F) == type.getCheckValue());
|
|
}
|
|
else
|
|
{
|
|
return (value == type.getCheckValue());
|
|
}
|
|
}
|
|
catch(LibUsbException e)
|
|
{
|
|
//Do nothing ... it's not the specified tuner
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public int getCurrentSampleRate() throws SourceException
|
|
{
|
|
return mSampleRate.getRate();
|
|
}
|
|
|
|
public int getSampleRateFromTuner() throws SourceException
|
|
{
|
|
try
|
|
{
|
|
int high = readDemodRegister(mDeviceHandle, Page.ONE, (short) 0x9F, 2);
|
|
int low = readDemodRegister(mDeviceHandle, Page.ONE, (short) 0xA1, 2);
|
|
|
|
int ratio = Integer.rotateLeft(high, 16) | low;
|
|
|
|
int rate = (int) (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);
|
|
|
|
return sampleRate.getRate();
|
|
}
|
|
}
|
|
catch(Exception e)
|
|
{
|
|
throw new SourceException("RTL2832 Tuner Controller - cannot get "
|
|
+ "current sample rate", e);
|
|
}
|
|
|
|
return DEFAULT_SAMPLE_RATE.getRate();
|
|
}
|
|
|
|
public void setSampleRate(SampleRate sampleRate) throws SourceException
|
|
{
|
|
/* Write high-order 16-bits of sample rate ratio to demod register */
|
|
writeDemodRegister(mDeviceHandle, 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(mDeviceHandle, Page.ONE, (short) 0xA1, 0, 2);
|
|
|
|
/* Set sample rate correction to 0 */
|
|
setSampleRateFrequencyCorrection(0);
|
|
|
|
/* Reset the demod for the changes to take effect */
|
|
writeDemodRegister(mDeviceHandle, Page.ONE, (short) 0x01, 0x14, 1);
|
|
writeDemodRegister(mDeviceHandle, Page.ONE, (short) 0x01, 0x10, 1);
|
|
|
|
/* Apply any tuner specific sample rate filter settings */
|
|
setSampleRateFilters(sampleRate.getRate());
|
|
|
|
mSampleRate = sampleRate;
|
|
|
|
mFrequencyController.setSampleRate(sampleRate.getRate());
|
|
}
|
|
|
|
public void setSampleRateFrequencyCorrection(int ppm) throws SourceException
|
|
{
|
|
int offset = -ppm * TWO_TO_22_POWER / 1000000;
|
|
|
|
writeDemodRegister(mDeviceHandle,
|
|
Page.ONE,
|
|
(short) 0x3F,
|
|
(offset & 0xFF),
|
|
1);
|
|
writeDemodRegister(mDeviceHandle,
|
|
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(mDeviceHandle, Page.ONE, (short) 0x3E, 1);
|
|
int low = readDemodRegister(mDeviceHandle, 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(DeviceHandle handle, short offset, int length)
|
|
throws IllegalArgumentException
|
|
{
|
|
if(offset + length > 256)
|
|
{
|
|
throw new IllegalArgumentException("cannot read more than 256 "
|
|
+ "bytes from EEPROM - requested to 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(handle, Block.I2C, EEPROM_ADDRESS, (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(handle, 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(DeviceHandle handle, byte offset, byte value)
|
|
throws IllegalArgumentException, UsbDisconnectedException, UsbException
|
|
{
|
|
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(handle, Block.I2C, EEPROM_ADDRESS, 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);
|
|
|
|
private int mAddress;
|
|
|
|
private Address(int address)
|
|
{
|
|
mAddress = address;
|
|
}
|
|
|
|
public short getAddress()
|
|
{
|
|
return (short) mAddress;
|
|
}
|
|
}
|
|
|
|
public enum Page
|
|
{
|
|
ZERO(0x0),
|
|
ONE(0x1),
|
|
TEN(0xA);
|
|
|
|
private int mPage;
|
|
|
|
private 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;
|
|
|
|
private 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.
|
|
*/
|
|
public enum SampleRate
|
|
{
|
|
/* Note: sample rates below 1.0MHz are subject to aliasing */
|
|
RATE_0_240MHZ(0x1E00, 240000, "0.240 MHz"),
|
|
RATE_0_288MHZ(0x1900, 288000, "0.288 MHz"),
|
|
RATE_0_960MHZ(0x0780, 960000, "0.960 MHz"),
|
|
RATE_1_200MHZ(0x0600, 1200000, "1.200 MHz"),
|
|
RATE_1_440MHZ(0x0500, 1440000, "1.440 MHz"),
|
|
RATE_1_920MHZ(0x03C0, 2016000, "2.016 MHz"),
|
|
RATE_2_304MHZ(0x0320, 2208000, "2.208 MHz"),
|
|
RATE_2_400MHZ(0x0300, 2400000, "2.400 MHz"),
|
|
RATE_2_880MHZ(0x0280, 2880000, "2.880 MHz");
|
|
|
|
private int mRatioHigh;
|
|
private int mRate;
|
|
private String mLabel;
|
|
|
|
private SampleRate(int ratioHigh,
|
|
int rate,
|
|
String label)
|
|
{
|
|
mRatioHigh = ratioHigh;
|
|
mRate = rate;
|
|
mLabel = label;
|
|
}
|
|
|
|
public int getRatioHighBits()
|
|
{
|
|
return mRatioHigh;
|
|
}
|
|
|
|
public int getRate()
|
|
{
|
|
return mRate;
|
|
}
|
|
|
|
public String getLabel()
|
|
{
|
|
return mLabel;
|
|
}
|
|
|
|
public String toString()
|
|
{
|
|
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;
|
|
|
|
private 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
|
|
*/
|
|
public class Descriptor
|
|
{
|
|
private byte[] mData;
|
|
private ArrayList<String> mLabels = new ArrayList<String>();
|
|
|
|
public Descriptor(byte[] data)
|
|
{
|
|
if(data != null)
|
|
{
|
|
mData = data;
|
|
}
|
|
else
|
|
{
|
|
data = new byte[256];
|
|
}
|
|
|
|
getLabels();
|
|
}
|
|
|
|
public boolean isValid()
|
|
{
|
|
return mData[0] == (byte) 0x28 &&
|
|
mData[1] == (byte) 0x32;
|
|
}
|
|
|
|
public String getVendorID()
|
|
{
|
|
int id = Integer.rotateLeft((0xFF & mData[3]), 8) |
|
|
(0xFF & mData[2]);
|
|
|
|
return String.format("%04X", id);
|
|
}
|
|
|
|
public String getVendorLabel()
|
|
{
|
|
return mLabels.get(0);
|
|
}
|
|
|
|
public String getProductID()
|
|
{
|
|
int id = Integer.rotateLeft((0xFF & mData[5]), 8) |
|
|
(0xFF & mData[4]);
|
|
|
|
return String.format("%04X", id);
|
|
}
|
|
|
|
public String getProductLabel()
|
|
{
|
|
return mLabels.get(1);
|
|
}
|
|
|
|
public boolean hasSerial()
|
|
{
|
|
return mData[6] == (byte) 0xA5;
|
|
}
|
|
|
|
public String getSerial()
|
|
{
|
|
return mLabels.get(2);
|
|
}
|
|
|
|
public boolean remoteWakeupEnabled()
|
|
{
|
|
byte mask = (byte) 0x01;
|
|
|
|
return (mData[7] & mask) == mask;
|
|
}
|
|
|
|
public boolean irEnabled()
|
|
{
|
|
byte mask = (byte) 0x02;
|
|
|
|
return (mData[7] & mask) == mask;
|
|
}
|
|
|
|
private void getLabels()
|
|
{
|
|
mLabels.clear();
|
|
|
|
int start = 0x09;
|
|
|
|
while(start < 256)
|
|
{
|
|
start = getLabel(start);
|
|
}
|
|
}
|
|
|
|
private int getLabel(int start)
|
|
{
|
|
/* Validate length and check second byte for ETX (0x03) */
|
|
if(start > 254 || mData[start + 1] != (byte) 0x03)
|
|
{
|
|
return 256;
|
|
}
|
|
|
|
/* Get label length, including the length and ETX bytes */
|
|
int length = 0xFF & mData[start];
|
|
|
|
if(start + length > 255)
|
|
{
|
|
return 256;
|
|
}
|
|
|
|
/* 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);
|
|
|
|
return start + length;
|
|
}
|
|
|
|
public String toString()
|
|
{
|
|
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("Serial: ");
|
|
if(hasSerial())
|
|
{
|
|
sb.append("yes [");
|
|
sb.append(getSerial());
|
|
sb.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");
|
|
|
|
if(mLabels.size() > 3)
|
|
{
|
|
sb.append("Additional Labels: ");
|
|
|
|
for(int x = 3; x < mLabels.size(); x++)
|
|
{
|
|
sb.append(" [");
|
|
sb.append(mLabels.get(x));
|
|
sb.append("\n");
|
|
}
|
|
}
|
|
|
|
return sb.toString();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Adds a sample listener. If the USB transfer processor is not currently running, it will auto-start
|
|
*/
|
|
public void addListener(Listener<ComplexBuffer> listener)
|
|
{
|
|
mUSBTransferProcessor.addListener(listener);
|
|
}
|
|
|
|
/**
|
|
* Removes the sample listener. If this is the last registered listener, the USB transfer processor will
|
|
* halt buffer processing
|
|
*/
|
|
public void removeListener(Listener<ComplexBuffer> listener)
|
|
{
|
|
mUSBTransferProcessor.removeListener(listener);
|
|
}
|
|
|
|
/**
|
|
* RTL-2832 USB transfer processor. Extends USB transfer processor and allows resetting the device USB
|
|
* buffer prior to starting streaming.
|
|
*/
|
|
public class RTL2832USBTransferProcessor extends USBTransferProcessor
|
|
{
|
|
/**
|
|
* Manages stream of USB transfer buffers and converts buffers to complex buffer samples for distribution to
|
|
* any registered listeners.
|
|
*
|
|
* @param deviceName to use when logging information or errors
|
|
* @param deviceHandle to the USB bulk transfer device
|
|
* @param sampleAdapter specific to the tuner's byte buffer format for converting to floating point I/Q samples
|
|
* @param bufferSize in bytes. Should be a multiple of two: 65536, 131072 or 262144.
|
|
*/
|
|
public RTL2832USBTransferProcessor(String deviceName, DeviceHandle deviceHandle, ISampleAdapter sampleAdapter, int bufferSize)
|
|
{
|
|
super(deviceName, deviceHandle, sampleAdapter, bufferSize);
|
|
}
|
|
|
|
@Override
|
|
protected void prepareDeviceStart()
|
|
{
|
|
resetUSBBuffer();
|
|
}
|
|
}
|
|
}
|