/*******************************************************************************
* 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();
}
}
}