diff --git a/CMakeLists.txt b/CMakeLists.txt index 0597600..8053e55 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,6 +22,7 @@ # Project setup ######################################################################## cmake_minimum_required(VERSION 2.6) +set(CMAKE_LEGACY_CYGWIN_WIN32 0) project(rtlsdr C) #select the release build type by default to get optimization flags diff --git a/configure.ac b/configure.ac index b60ca81..0d3874b 100644 --- a/configure.ac +++ b/configure.ac @@ -46,7 +46,7 @@ AC_CHECK_LIB(m, sqrt, [LIBS="$LIBS -lm"]) dnl libmath (for rtl_power) AC_CHECK_LIB(m, atan2, [LIBS="$LIBS -lm"]) -dnl librealtime (for rtl_test) +dnl librealtime (for rtl_test, rtl_fm) AC_CHECK_LIB(rt, clock_gettime, [LIBS="$LIBS -lrt"]) # The following test is taken from WebKit's webkit.m4 diff --git a/include/rtl-sdr.h b/include/rtl-sdr.h index 489e117..656c55b 100644 --- a/include/rtl-sdr.h +++ b/include/rtl-sdr.h @@ -144,6 +144,10 @@ RTLSDR_API int rtlsdr_read_eeprom(rtlsdr_dev_t *dev, uint8_t *data, RTLSDR_API int rtlsdr_set_center_freq(rtlsdr_dev_t *dev, uint32_t freq); +RTLSDR_API int rtlsdr_set_if_freq(rtlsdr_dev_t *dev, uint32_t freq); + +RTLSDR_API int rtlsdr_set_if_bandwidth(rtlsdr_dev_t *dev, int bw); + /*! * Get actual frequency the device is tuned to. * @@ -322,6 +326,17 @@ RTLSDR_API int rtlsdr_set_offset_tuning(rtlsdr_dev_t *dev, int on); */ RTLSDR_API int rtlsdr_get_offset_tuning(rtlsdr_dev_t *dev); +/*! + * Enable or disable frequency dithering for r820t tuners. + * Must be performed before freq_set(). + * Fails for other tuners. + * + * \param dev the device handle given by rtlsdr_open() + * \param on 0 means disabled, 1 enabled + * \return 0 on success + */ +RTLSDR_API int rtlsdr_set_dithering(rtlsdr_dev_t *dev, int dither); + /* streaming functions */ RTLSDR_API int rtlsdr_reset_buffer(rtlsdr_dev_t *dev); diff --git a/include/tuner_r82xx.h b/include/tuner_r82xx.h index fd81e1d..77a87e3 100644 --- a/include/tuner_r82xx.h +++ b/include/tuner_r82xx.h @@ -32,7 +32,8 @@ #define R82XX_CHECK_ADDR 0x00 #define R82XX_CHECK_VAL 0x69 -#define R82XX_IF_FREQ 3570000 +#define R82XX_DEFAULT_IF_FREQ 6000000 +#define R82XX_DEFAULT_IF_BW 2000000 #define REG_SHADOW_START 5 #define NUM_REGS 30 @@ -82,8 +83,10 @@ struct r82xx_priv { uint32_t int_freq; uint8_t fil_cal_code; uint8_t input; - int has_lock; int init_done; + int disable_dither; + int reg_cache; + int reg_batch, reg_low, reg_high; /* Store current mode */ uint32_t delsys; @@ -115,5 +118,9 @@ int r82xx_standby(struct r82xx_priv *priv); int r82xx_init(struct r82xx_priv *priv); int r82xx_set_freq(struct r82xx_priv *priv, uint32_t freq); int r82xx_set_gain(struct r82xx_priv *priv, int set_manual_gain, int gain); +int r82xx_set_nomod(struct r82xx_priv *priv); +int r82xx_set_dither(struct r82xx_priv *priv, int dither); +int r82xx_set_bw(struct r82xx_priv *priv, uint32_t bw); +int r82xx_set_if_freq(struct r82xx_priv *priv, uint32_t freq); #endif diff --git a/librtlsdr.pc.in b/librtlsdr.pc.in index 5e55049..76b4190 100644 --- a/librtlsdr.pc.in +++ b/librtlsdr.pc.in @@ -7,5 +7,6 @@ Name: RTL-SDR Library Description: C Utility Library Version: @VERSION@ Cflags: -I${includedir}/ @RTLSDR_PC_CFLAGS@ -Libs: -L${libdir} -lrtlsdr -lusb-1.0 +Libs: -L${libdir} -lrtlsdr Libs.private: @RTLSDR_PC_LIBS@ +Requires: libusb-1.0 diff --git a/rtlsdr.conf b/rtlsdr.conf new file mode 100644 index 0000000..4dda64c --- /dev/null +++ b/rtlsdr.conf @@ -0,0 +1,2 @@ +# disable DVB drivers +blacklist dvb_usb_rtl28xxu diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 77e1dc4..6647d5c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -28,11 +28,9 @@ add_library(rtlsdr_shared SHARED tuner_fc2580.c tuner_r82xx.c ) - target_link_libraries(rtlsdr_shared ${LIBUSB_LIBRARIES} ) - set_target_properties(rtlsdr_shared PROPERTIES DEFINE_SYMBOL "rtlsdr_EXPORTS") set_target_properties(rtlsdr_shared PROPERTIES OUTPUT_NAME rtlsdr) set_target_properties(rtlsdr_shared PROPERTIES SOVERSION ${MAJOR_VERSION}) @@ -46,25 +44,17 @@ add_library(rtlsdr_static STATIC tuner_fc2580.c tuner_r82xx.c ) +target_link_libraries(rtlsdr_static + ${LIBUSB_LIBRARIES} +) +set_property(TARGET rtlsdr_static APPEND PROPERTY COMPILE_DEFINITIONS "rtlsdr_STATIC" ) add_library(convenience_static STATIC convenience/convenience.c ) - -if(WIN32) -add_library(libgetopt_static STATIC - getopt/getopt.c -) target_link_libraries(convenience_static rtlsdr_shared ) -endif() - -target_link_libraries(rtlsdr_static - ${LIBUSB_LIBRARIES} -) - -set_property(TARGET rtlsdr_static APPEND PROPERTY COMPILE_DEFINITIONS "rtlsdr_STATIC" ) if(NOT WIN32) # Force same library filename for static and shared variants of the library @@ -83,46 +73,50 @@ add_executable(rtl_adsb rtl_adsb.c) add_executable(rtl_power rtl_power.c) set(INSTALL_TARGETS rtlsdr_shared rtlsdr_static rtl_sdr rtl_tcp rtl_test rtl_fm rtl_eeprom rtl_adsb rtl_power) -target_link_libraries(rtl_sdr rtlsdr_shared convenience_static +target_link_libraries(rtl_sdr convenience_static rtlsdr_shared ${LIBUSB_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} ) -target_link_libraries(rtl_tcp rtlsdr_shared convenience_static +target_link_libraries(rtl_tcp convenience_static rtlsdr_shared ${LIBUSB_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} ) -target_link_libraries(rtl_test rtlsdr_shared convenience_static +target_link_libraries(rtl_test convenience_static rtlsdr_shared ${LIBUSB_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} ) -target_link_libraries(rtl_fm rtlsdr_shared convenience_static +target_link_libraries(rtl_fm convenience_static rtlsdr_shared ${LIBUSB_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} ) -target_link_libraries(rtl_eeprom rtlsdr_shared convenience_static +target_link_libraries(rtl_eeprom convenience_static rtlsdr_shared ${LIBUSB_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} ) -target_link_libraries(rtl_adsb rtlsdr_shared convenience_static +target_link_libraries(rtl_adsb convenience_static rtlsdr_shared ${LIBUSB_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} ) -target_link_libraries(rtl_power rtlsdr_shared convenience_static +target_link_libraries(rtl_power convenience_static rtlsdr_shared ${LIBUSB_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} ) if(UNIX) -target_link_libraries(rtl_fm m) target_link_libraries(rtl_adsb m) target_link_libraries(rtl_power m) if(APPLE) target_link_libraries(rtl_test m) + target_link_libraries(rtl_fm m) else() target_link_libraries(rtl_test m rt) + target_link_libraries(rtl_fm m rt) endif() endif() if(WIN32) +add_library(libgetopt_static STATIC + getopt/getopt.c +) target_link_libraries(rtl_sdr libgetopt_static) target_link_libraries(rtl_tcp ws2_32 libgetopt_static) target_link_libraries(rtl_test libgetopt_static) @@ -130,13 +124,6 @@ target_link_libraries(rtl_fm libgetopt_static) target_link_libraries(rtl_eeprom libgetopt_static) target_link_libraries(rtl_adsb libgetopt_static) target_link_libraries(rtl_power libgetopt_static) -set_property(TARGET rtl_sdr APPEND PROPERTY COMPILE_DEFINITIONS "rtlsdr_STATIC" ) -set_property(TARGET rtl_tcp APPEND PROPERTY COMPILE_DEFINITIONS "rtlsdr_STATIC" ) -set_property(TARGET rtl_test APPEND PROPERTY COMPILE_DEFINITIONS "rtlsdr_STATIC" ) -set_property(TARGET rtl_fm APPEND PROPERTY COMPILE_DEFINITIONS "rtlsdr_STATIC" ) -set_property(TARGET rtl_eeprom APPEND PROPERTY COMPILE_DEFINITIONS "rtlsdr_STATIC" ) -set_property(TARGET rtl_adsb APPEND PROPERTY COMPILE_DEFINITIONS "rtlsdr_STATIC" ) -set_property(TARGET rtl_power APPEND PROPERTY COMPILE_DEFINITIONS "rtlsdr_STATIC" ) endif() ######################################################################## # Install built library files & utilities diff --git a/src/convenience/convenience.c b/src/convenience/convenience.c index 517dc4e..cc8a8bf 100644 --- a/src/convenience/convenience.c +++ b/src/convenience/convenience.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 by Kyle Keen + * Copyright (C) 2013-2014 by Kyle Keen * * 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 @@ -174,6 +174,8 @@ int verbose_direct_sampling(rtlsdr_dev_t *dev, int on) fprintf(stderr, "Enabled direct sampling mode, input 1/I.\n");} if (on == 2) { fprintf(stderr, "Enabled direct sampling mode, input 2/Q.\n");} + if (on == 3) { + fprintf(stderr, "Enabled no-mod direct sampling mode.\n");} return r; } @@ -232,6 +234,34 @@ int verbose_ppm_set(rtlsdr_dev_t *dev, int ppm_error) return r; } +int verbose_ppm_eeprom(rtlsdr_dev_t *dev, int *ppm_error) +{ + #define start_char ' ' + #define stop_char 'p' + int i, r, len, status = -1; + char vendor[256], product[256], serial[256]; + r = rtlsdr_get_usb_strings(dev, vendor, product, serial); + if (r) { + return r; + } + len = strlen(serial); + if (len <= 3) { + return -1;} + if (serial[len-1] != stop_char) { + return -1;} + serial[len-1] = '\0'; + for (i=len-3; i>=0; i--) { + if (serial[i] != start_char) { + continue;} + fprintf(stderr, "PPM calibration found in eeprom.\n"); + status = 0; + *ppm_error = atoi(serial + i + 1); + break; + } + serial[len-1] = stop_char; + return status; +} + int verbose_reset_buffer(rtlsdr_dev_t *dev) { int r; @@ -245,7 +275,7 @@ int verbose_device_search(char *s) { int i, device_count, device, offset; char *s2; - char vendor[256], product[256], serial[256]; + char vendor[256] = {0}, product[256] = {0}, serial[256] = {0}; device_count = rtlsdr_get_device_count(); if (!device_count) { fprintf(stderr, "No supported devices found.\n"); @@ -253,8 +283,11 @@ int verbose_device_search(char *s) } fprintf(stderr, "Found %d device(s):\n", device_count); for (i = 0; i < device_count; i++) { - rtlsdr_get_device_usb_strings(i, vendor, product, serial); - fprintf(stderr, " %d: %s, %s, SN: %s\n", i, vendor, product, serial); + if (rtlsdr_get_device_usb_strings(i, vendor, product, serial) == 0) { + fprintf(stderr, " %d: %s, %s, SN: %s\n", i, vendor, product, serial); + } else { + fprintf(stderr, " %d: %s\n", i, "Failed to query data"); + } } fprintf(stderr, "\n"); /* does string look like raw id number */ diff --git a/src/convenience/convenience.h b/src/convenience/convenience.h index 1faa2af..2daac78 100644 --- a/src/convenience/convenience.h +++ b/src/convenience/convenience.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 by Kyle Keen + * Copyright (C) 2013-2014 by Kyle Keen * * 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 @@ -122,6 +122,15 @@ int verbose_gain_set(rtlsdr_dev_t *dev, int gain); int verbose_ppm_set(rtlsdr_dev_t *dev, int ppm_error); +/*! + * Attempts to extract a correction value from eeprom and store it to an int. + * + * \param dev the device handle given by rtlsdr_open() + * \param ppm_error correction value in parts per million (ppm) + * \return 0 on success + */ +int verbose_ppm_eeprom(rtlsdr_dev_t *dev, int *ppm_error); + /*! * Reset buffer * diff --git a/src/librtlsdr.c b/src/librtlsdr.c index 9a3ebcd..622f985 100644 --- a/src/librtlsdr.c +++ b/src/librtlsdr.c @@ -64,6 +64,7 @@ typedef struct rtlsdr_tuner_iface { int (*set_gain)(void *, int gain /* tenth dB */); int (*set_if_gain)(void *, int stage, int gain /* tenth dB */); int (*set_gain_mode)(void *, int manual); + int (*set_if_freq)(void *, uint32_t freq /* Hz */); } rtlsdr_tuner_iface_t; enum rtlsdr_async_status { @@ -123,6 +124,8 @@ struct rtlsdr_dev { int dev_lost; int driver_active; unsigned int xfer_errors; + int tuner_initialized; + int i2c_repeater_on; }; void rtlsdr_set_gpio_bit(rtlsdr_dev_t *dev, uint8_t gpio, int val); @@ -238,7 +241,17 @@ int r820t_set_freq(void *dev, uint32_t freq) { rtlsdr_dev_t* devt = (rtlsdr_dev_t*)dev; return r82xx_set_freq(&devt->r82xx_p, freq); } -int r820t_set_bw(void *dev, int bw) { return 0; } + +int r820t_set_bw(void *dev, int bw) { + rtlsdr_dev_t* devt = (rtlsdr_dev_t*)dev; + return r82xx_set_bw(&devt->r82xx_p, bw); +} + +int r820t_set_if_freq(void *dev, uint32_t freq) { + rtlsdr_dev_t* devt = (rtlsdr_dev_t*)dev; + return r82xx_set_if_freq(&devt->r82xx_p, freq); +} + int r820t_set_gain(void *dev, int gain) { rtlsdr_dev_t* devt = (rtlsdr_dev_t*)dev; return r82xx_set_gain(&devt->r82xx_p, 1, gain); @@ -251,37 +264,37 @@ int r820t_set_gain_mode(void *dev, int manual) { /* definition order must match enum rtlsdr_tuner */ static rtlsdr_tuner_iface_t tuners[] = { { - NULL, NULL, NULL, NULL, NULL, NULL, NULL /* dummy for unknown tuners */ + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL /* dummy for unknown tuners */ }, { e4000_init, e4000_exit, e4000_set_freq, e4000_set_bw, e4000_set_gain, e4000_set_if_gain, - e4000_set_gain_mode + e4000_set_gain_mode, NULL }, { _fc0012_init, fc0012_exit, fc0012_set_freq, fc0012_set_bw, _fc0012_set_gain, NULL, - fc0012_set_gain_mode + fc0012_set_gain_mode, NULL }, { _fc0013_init, fc0013_exit, fc0013_set_freq, fc0013_set_bw, _fc0013_set_gain, NULL, - fc0013_set_gain_mode + fc0013_set_gain_mode, NULL }, { fc2580_init, fc2580_exit, _fc2580_set_freq, fc2580_set_bw, fc2580_set_gain, NULL, - fc2580_set_gain_mode + fc2580_set_gain_mode, NULL }, { r820t_init, r820t_exit, r820t_set_freq, r820t_set_bw, r820t_set_gain, NULL, - r820t_set_gain_mode + r820t_set_gain_mode, r820t_set_if_freq }, { r820t_init, r820t_exit, r820t_set_freq, r820t_set_bw, r820t_set_gain, NULL, - r820t_set_gain_mode + r820t_set_gain_mode, r820t_set_if_freq }, }; @@ -561,6 +574,10 @@ void rtlsdr_set_gpio_output(rtlsdr_dev_t *dev, uint8_t gpio) void rtlsdr_set_i2c_repeater(rtlsdr_dev_t *dev, int on) { + if (on == dev->i2c_repeater_on) + return; + on = !!on; /* values +2 to force on */ + dev->i2c_repeater_on = on; rtlsdr_demod_write_reg(dev, 1, 0x01, on ? 0x18 : 0x10, 1); } @@ -658,10 +675,13 @@ int rtlsdr_deinit_baseband(rtlsdr_dev_t *dev) if (!dev) return -1; + rtlsdr_set_i2c_repeater(dev, 0); + if (dev->tuner && dev->tuner->exit) { rtlsdr_set_i2c_repeater(dev, 1); r = dev->tuner->exit(dev); /* deinitialize tuner */ rtlsdr_set_i2c_repeater(dev, 0); + dev->tuner_initialized = 0; } /* poweroff demodulator and ADCs */ @@ -679,12 +699,13 @@ int rtlsdr_set_if_freq(rtlsdr_dev_t *dev, uint32_t freq) if (!dev) return -1; + rtlsdr_set_i2c_repeater(dev, 0); /* read corrected clock value */ if (rtlsdr_get_xtal_freq(dev, &rtl_xtal, NULL)) return -2; - if_freq = ((freq * TWO_POW(22)) / rtl_xtal) * (-1); + if_freq = (int32_t)(((freq * TWO_POW(22)) / rtl_xtal) * -1); tmp = (if_freq >> 16) & 0x3f; r = rtlsdr_demod_write_reg(dev, 1, 0x19, tmp, 1); @@ -693,6 +714,13 @@ int rtlsdr_set_if_freq(rtlsdr_dev_t *dev, uint32_t freq) tmp = if_freq & 0xff; r |= rtlsdr_demod_write_reg(dev, 1, 0x1b, tmp, 1); + /* Tell the R820T driver which IF frequency we are currently using + * so that it can choose the optimal IF filter settings. + * Works for normal tuning as well as no-mod direct sampling! */ + if(dev->tuner_initialized && dev->tuner && dev->tuner->set_if_freq) { + rtlsdr_set_i2c_repeater(dev, 1); + dev->tuner->set_if_freq(dev, freq); + } return r; } @@ -700,7 +728,8 @@ int rtlsdr_set_sample_freq_correction(rtlsdr_dev_t *dev, int ppm) { int r = 0; uint8_t tmp; - int16_t offs = ppm * (-1) * TWO_POW(24) / 1000000; + int16_t offs = (int16_t)(ppm * -1 * TWO_POW(24) / 1000000); + rtlsdr_set_i2c_repeater(dev, 0); tmp = offs & 0xff; r |= rtlsdr_demod_write_reg(dev, 1, 0x3f, tmp, 1); @@ -713,6 +742,7 @@ int rtlsdr_set_sample_freq_correction(rtlsdr_dev_t *dev, int ppm) int rtlsdr_set_xtal_freq(rtlsdr_dev_t *dev, uint32_t rtl_freq, uint32_t tuner_freq) { int r = 0; + rtlsdr_set_i2c_repeater(dev, 0); if (!dev) return -1; @@ -810,6 +840,7 @@ int rtlsdr_write_eeprom(rtlsdr_dev_t *dev, uint8_t *data, uint8_t offset, uint16 int r = 0; int i; uint8_t cmd[2]; + rtlsdr_set_i2c_repeater(dev, 0); if (!dev) return -1; @@ -847,6 +878,7 @@ int rtlsdr_read_eeprom(rtlsdr_dev_t *dev, uint8_t *data, uint8_t offset, uint16_ { int r = 0; int i; + rtlsdr_set_i2c_repeater(dev, 0); if (!dev) return -1; @@ -876,11 +908,11 @@ int rtlsdr_set_center_freq(rtlsdr_dev_t *dev, uint32_t freq) return -1; if (dev->direct_sampling) { + rtlsdr_set_i2c_repeater(dev, 0); r = rtlsdr_set_if_freq(dev, freq); } else if (dev->tuner && dev->tuner->set_freq) { rtlsdr_set_i2c_repeater(dev, 1); r = dev->tuner->set_freq(dev, freq - dev->offs_freq); - rtlsdr_set_i2c_repeater(dev, 0); } if (!r) @@ -902,6 +934,7 @@ uint32_t rtlsdr_get_center_freq(rtlsdr_dev_t *dev) int rtlsdr_set_freq_correction(rtlsdr_dev_t *dev, int ppm) { int r = 0; + rtlsdr_set_i2c_repeater(dev, 0); if (!dev) return -1; @@ -1004,7 +1037,6 @@ int rtlsdr_set_tuner_gain(rtlsdr_dev_t *dev, int gain) if (dev->tuner->set_gain) { rtlsdr_set_i2c_repeater(dev, 1); r = dev->tuner->set_gain((void *)dev, gain); - rtlsdr_set_i2c_repeater(dev, 0); } if (!r) @@ -1033,7 +1065,6 @@ int rtlsdr_set_tuner_if_gain(rtlsdr_dev_t *dev, int stage, int gain) if (dev->tuner->set_if_gain) { rtlsdr_set_i2c_repeater(dev, 1); r = dev->tuner->set_if_gain(dev, stage, gain); - rtlsdr_set_i2c_repeater(dev, 0); } return r; @@ -1049,7 +1080,6 @@ int rtlsdr_set_tuner_gain_mode(rtlsdr_dev_t *dev, int mode) if (dev->tuner->set_gain_mode) { rtlsdr_set_i2c_repeater(dev, 1); r = dev->tuner->set_gain_mode((void *)dev, mode); - rtlsdr_set_i2c_repeater(dev, 0); } return r; @@ -1064,6 +1094,7 @@ int rtlsdr_set_sample_rate(rtlsdr_dev_t *dev, uint32_t samp_rate) if (!dev) return -1; + rtlsdr_set_i2c_repeater(dev, 0); /* check if the rate is supported by the resampler */ if ((samp_rate <= 225000) || (samp_rate > 3200000) || @@ -1072,7 +1103,7 @@ int rtlsdr_set_sample_rate(rtlsdr_dev_t *dev, uint32_t samp_rate) return -EINVAL; } - rsamp_ratio = (dev->rtl_xtal * TWO_POW(22)) / samp_rate; + rsamp_ratio = (uint32_t)((dev->rtl_xtal * TWO_POW(22)) / samp_rate); rsamp_ratio &= 0x0ffffffc; real_rsamp_ratio = rsamp_ratio | ((rsamp_ratio & 0x08000000) << 1); @@ -1119,6 +1150,7 @@ int rtlsdr_set_testmode(rtlsdr_dev_t *dev, int on) { if (!dev) return -1; + rtlsdr_set_i2c_repeater(dev, 0); return rtlsdr_demod_write_reg(dev, 0, 0x19, on ? 0x03 : 0x05, 1); } @@ -1127,6 +1159,7 @@ int rtlsdr_set_agc_mode(rtlsdr_dev_t *dev, int on) { if (!dev) return -1; + rtlsdr_set_i2c_repeater(dev, 0); return rtlsdr_demod_write_reg(dev, 0, 0x19, on ? 0x25 : 0x05, 1); } @@ -1138,13 +1171,40 @@ int rtlsdr_set_direct_sampling(rtlsdr_dev_t *dev, int on) if (!dev) return -1; - if (on) { + /* set up normal direct sampling */ + if (on == 1 || on == 2) { if (dev->tuner && dev->tuner->exit) { rtlsdr_set_i2c_repeater(dev, 1); r = dev->tuner->exit(dev); rtlsdr_set_i2c_repeater(dev, 0); + dev->tuner_initialized = 0; } + } + /* set up no-mod direct sampling */ + if (on == 3 && dev->tuner) { + if (dev->tuner_type == RTLSDR_TUNER_E4000) { + fprintf(stderr, "Tuning E4000 to 3708 MHz\n"); + rtlsdr_set_i2c_repeater(dev, 1); + dev->tuner->init(dev); + dev->tuner_initialized = 1; + dev->tuner->set_freq(dev, 3708000000u); + e4000_set_bw(dev, 15000000); + rtlsdr_set_i2c_repeater(dev, 0); + } + if (dev->tuner_type == RTLSDR_TUNER_R820T) { + rtlsdr_dev_t* devt = (rtlsdr_dev_t*)dev; + rtlsdr_set_i2c_repeater(dev, 1); + dev->tuner->init(dev); + dev->tuner_initialized = 1; + r82xx_set_nomod(&devt->r82xx_p); + rtlsdr_set_i2c_repeater(dev, 0); + } + } + rtlsdr_set_i2c_repeater(dev, 0); + + /* common to all direct modes */ + if (on) { /* disable Zero-IF mode */ r |= rtlsdr_demod_write_reg(dev, 1, 0xb1, 0x1a, 1); @@ -1155,20 +1215,24 @@ int rtlsdr_set_direct_sampling(rtlsdr_dev_t *dev, int on) r |= rtlsdr_demod_write_reg(dev, 0, 0x08, 0x4d, 1); /* swap I and Q ADC, this allows to select between two inputs */ - r |= rtlsdr_demod_write_reg(dev, 0, 0x06, (on > 1) ? 0x90 : 0x80, 1); + r |= rtlsdr_demod_write_reg(dev, 0, 0x06, (on == 2) ? 0x90 : 0x80, 1); fprintf(stderr, "Enabled direct sampling mode, input %i\n", on); dev->direct_sampling = on; - } else { + } + + /* disable direct sampling */ + if (!on) { if (dev->tuner && dev->tuner->init) { rtlsdr_set_i2c_repeater(dev, 1); r |= dev->tuner->init(dev); rtlsdr_set_i2c_repeater(dev, 0); + dev->tuner_initialized = 1; } if ((dev->tuner_type == RTLSDR_TUNER_R820T) || (dev->tuner_type == RTLSDR_TUNER_R828D)) { - r |= rtlsdr_set_if_freq(dev, R82XX_IF_FREQ); + r |= rtlsdr_set_if_freq(dev, R82XX_DEFAULT_IF_FREQ); /* enable spectrum inversion */ r |= rtlsdr_demod_write_reg(dev, 1, 0x15, 0x01, 1); @@ -1205,6 +1269,7 @@ int rtlsdr_get_direct_sampling(rtlsdr_dev_t *dev) int rtlsdr_set_offset_tuning(rtlsdr_dev_t *dev, int on) { int r = 0; + rtlsdr_set_i2c_repeater(dev, 0); if (!dev) return -1; @@ -1240,6 +1305,14 @@ int rtlsdr_get_offset_tuning(rtlsdr_dev_t *dev) return (dev->offs_freq) ? 1 : 0; } +int rtlsdr_set_dithering(rtlsdr_dev_t *dev, int dither) +{ + if (dev->tuner_type == RTLSDR_TUNER_R820T) { + return r82xx_set_dither(&dev->r82xx_p, dither); + } + return 1; +} + static rtlsdr_dongle_t *find_known_device(uint16_t vid, uint16_t pid) { unsigned int i; @@ -1551,7 +1624,7 @@ int rtlsdr_open(rtlsdr_dev_t **out_dev, uint32_t index) /* the R82XX use 3.57 MHz IF for the DVB-T 6 MHz mode, and * 4.57 MHz for the 8 MHz mode */ - rtlsdr_set_if_freq(dev, R82XX_IF_FREQ); + rtlsdr_set_if_freq(dev, R82XX_DEFAULT_IF_FREQ); /* enable spectrum inversion */ rtlsdr_demod_write_reg(dev, 1, 0x15, 0x01, 1); @@ -1564,8 +1637,11 @@ int rtlsdr_open(rtlsdr_dev_t **out_dev, uint32_t index) break; } - if (dev->tuner->init) + if (dev->tuner->init) { + rtlsdr_set_i2c_repeater(dev, 1); r = dev->tuner->init(dev); + dev->tuner_initialized = 1; + } rtlsdr_set_i2c_repeater(dev, 0); @@ -1587,6 +1663,7 @@ int rtlsdr_close(rtlsdr_dev_t *dev) { if (!dev) return -1; + rtlsdr_set_i2c_repeater(dev, 0); if(!dev->dev_lost) { /* block until all async operations have been completed (if any) */ @@ -1625,6 +1702,7 @@ int rtlsdr_reset_buffer(rtlsdr_dev_t *dev) { if (!dev) return -1; + rtlsdr_set_i2c_repeater(dev, 0); rtlsdr_write_reg(dev, USBB, USB_EPA_CTL, 0x1002, 2); rtlsdr_write_reg(dev, USBB, USB_EPA_CTL, 0x0000, 2); @@ -1775,7 +1853,7 @@ int rtlsdr_read_async(rtlsdr_dev_t *dev, rtlsdr_read_async_cb_t cb, void *ctx, r = libusb_submit_transfer(dev->xfer[i]); if (r < 0) { - fprintf(stderr, "Failed to submit transfer %i!\n", i); + fprintf(stderr, "Failed to submit transfer %u!\n", i); dev->async_status = RTLSDR_CANCELING; break; } @@ -1872,16 +1950,32 @@ uint32_t rtlsdr_get_tuner_clock(void *dev) int rtlsdr_i2c_write_fn(void *dev, uint8_t addr, uint8_t *buf, int len) { - if (dev) - return rtlsdr_i2c_write(((rtlsdr_dev_t *)dev), addr, buf, len); - + int r; + int retries = 2; + if (!dev) + return -1; + do { + r = rtlsdr_i2c_write(((rtlsdr_dev_t *)dev), addr, buf, len); + if (r >= 0) + return r; + rtlsdr_set_i2c_repeater(dev, 2); + retries--; + } while (retries > 0); return -1; } int rtlsdr_i2c_read_fn(void *dev, uint8_t addr, uint8_t *buf, int len) { - if (dev) - return rtlsdr_i2c_read(((rtlsdr_dev_t *)dev), addr, buf, len); - + int r; + int retries = 2; + if (!dev) + return -1; + do { + r = rtlsdr_i2c_read(((rtlsdr_dev_t *)dev), addr, buf, len); + if (r >= 0) + return r; + rtlsdr_set_i2c_repeater(dev, 2); + retries--; + } while (retries > 0); return -1; } diff --git a/src/rtl_adsb.c b/src/rtl_adsb.c index 7b10306..7092802 100644 --- a/src/rtl_adsb.c +++ b/src/rtl_adsb.c @@ -43,8 +43,7 @@ #include "rtl-sdr.h" #include "convenience/convenience.h" -#ifdef _WIN32 -#define sleep Sleep +#if defined(_MSC_VER) && _MSC_VER < 1800 #define round(x) (x > 0.0 ? floor(x + 0.5): ceil(x - 0.5)) #endif diff --git a/src/rtl_fm.c b/src/rtl_fm.c index 0f7ac38..6c11a76 100644 --- a/src/rtl_fm.c +++ b/src/rtl_fm.c @@ -30,18 +30,13 @@ * * todo: * sanity checks - * scale squelch to other input parameters - * test all the demodulations - * pad output on hop * frequency ranges could be stored better - * scaled AM demod amplification * auto-hop after time limit * peak detector to tune onto stronger signals * fifo for active hop frequency * clips * noise squelch * merge stereo patch - * merge soft agc patch * merge udp patch * testmode to detect overruns * watchdog to reset bad dongle @@ -54,6 +49,12 @@ #include #include +#ifdef __APPLE__ +#include +#else +#include +#endif + #ifndef _WIN32 #include #else @@ -62,7 +63,7 @@ #include #include "getopt/getopt.h" #define usleep(x) Sleep(x/1000) -#ifdef _MSC_VER +#if defined(_MSC_VER) && _MSC_VER < 1800 #define round(x) (x > 0.0 ? floor(x + 0.5): ceil(x - 0.5)) #endif #define _USE_MATH_DEFINES @@ -81,17 +82,34 @@ #define MAXIMUM_BUF_LENGTH (MAXIMUM_OVERSAMPLE * DEFAULT_BUF_LENGTH) #define AUTO_GAIN -100 #define BUFFER_DUMP 4096 +#define MAXIMUM_RATE 2400000 #define FREQUENCIES_LIMIT 1000 +#define PI_INT (1<<14) +#define ONE_INT (1<<14) + static volatile int do_exit = 0; static int lcm_post[17] = {1,1,1,3,1,5,3,7,1,9,5,11,3,13,7,15,1}; static int ACTUAL_BUF_LENGTH; +static uint32_t MINIMUM_RATE = 1000000; static int *atan_lut = NULL; static int atan_lut_size = 131072; /* 512 KB */ static int atan_lut_coef = 8; +// rewrite as dynamic and thread-safe for multi demod/dongle +#define SHARED_SIZE 6 +int16_t shared_samples[SHARED_SIZE][MAXIMUM_BUF_LENGTH]; +int ss_busy[SHARED_SIZE] = {0}; + +enum agc_mode_t +{ + agc_off = 0, + agc_normal, + agc_aggressive +}; + struct dongle_state { int exit_flag; @@ -101,27 +119,45 @@ struct dongle_state uint32_t freq; uint32_t rate; int gain; - uint16_t buf16[MAXIMUM_BUF_LENGTH]; + int16_t *buf16; uint32_t buf_len; int ppm_error; int offset_tuning; int direct_sampling; int mute; - struct demod_state *demod_target; + int pre_rotate; + struct demod_state *targets[2]; +}; + +struct agc_state +{ + int32_t gain_num; + int32_t gain_den; + int32_t gain_max; + int peak_target; + int attack_step; + int decay_step; + int error; +}; + +struct translate_state +{ + double angle; /* radians */ + int16_t *sincos; /* pairs */ + int len; + int i; }; struct demod_state { int exit_flag; pthread_t thread; - int16_t lowpassed[MAXIMUM_BUF_LENGTH]; + int16_t *lowpassed; int lp_len; int16_t lp_i_hist[10][6]; int16_t lp_q_hist[10][6]; - int16_t result[MAXIMUM_BUF_LENGTH]; int16_t droop_i_hist[9]; int16_t droop_q_hist[9]; - int result_len; int rate_in; int rate_out; int rate_out2; @@ -139,11 +175,26 @@ struct demod_state int now_lpr; int prev_lpr_index; int dc_block, dc_avg; + int rotate_enable; + struct translate_state rotate; + enum agc_mode_t agc_mode; + struct agc_state *agc; void (*mode_demod)(struct demod_state*); pthread_rwlock_t rw; pthread_cond_t ready; pthread_mutex_t ready_m; - struct output_state *output_target; + struct buffer_bucket *output_target; +}; + +struct buffer_bucket +{ + int16_t *buf; + int len; + pthread_rwlock_t rw; + pthread_cond_t ready; + pthread_mutex_t ready_m; + pthread_mutex_t trycond_m; + int trycond; }; struct output_state @@ -152,12 +203,11 @@ struct output_state pthread_t thread; FILE *file; char *filename; - int16_t result[MAXIMUM_BUF_LENGTH]; - int result_len; + struct buffer_bucket results[2]; int rate; - pthread_rwlock_t rw; - pthread_cond_t ready; - pthread_mutex_t ready_m; + int wav_format; + int padded; + int lrmix; }; struct controller_state @@ -176,6 +226,7 @@ struct controller_state // multiple of these, eventually struct dongle_state dongle; struct demod_state demod; +struct demod_state demod2; struct output_state output; struct controller_state controller; @@ -201,10 +252,17 @@ void usage(void) "\t[-E enable_option (default: none)]\n" "\t use multiple -E to enable multiple options\n" "\t edge: enable lower edge tuning\n" - "\t dc: enable dc blocking filter\n" + "\t no-dc: disable dc blocking filter\n" "\t deemp: enable de-emphasis filter\n" + "\t swagc: enable software agc (only for AM modes)\n" + "\t swagc-aggressive: enable aggressive software agc (only for AM modes)\n" "\t direct: enable direct sampling\n" + "\t no-mod: enable no-mod direct sampling\n" "\t offset: enable offset tuning\n" + "\t wav: generate WAV header\n" + "\t pad: pad output gaps with zeros\n" + "\t lrmix: one channel goes to left audio, one to right (broken)\n" + "\t remember to enable stereo (-c 2) in sox\n" "\tfilename ('-' means stdout)\n" "\t omitting the filename also uses stdout\n\n" "Experimental options:\n" @@ -214,7 +272,7 @@ void usage(void) "\t[-F fir_size (default: off)]\n" "\t enables low-leakage downsample filter\n" "\t size can be 0 or 9. 0 has bad roll off\n" - "\t[-A std/fast/lut choose atan math (default: std)]\n" + "\t[-A std/fast/lut/ale choose atan math (default: std)]\n" //"\t[-C clip_path (default: off)\n" //"\t (create time stamped raw clips, requires squelch)\n" //"\t (path must have '\%s' and will expand to date_time_freq)\n" @@ -225,6 +283,7 @@ void usage(void) "\trtl_fm ... | play -t raw -r 24k -es -b 16 -c 1 -V1 -\n" "\t | aplay -r 24k -f S16_LE -t raw -c 1\n" "\t -M wbfm | play -r 32k ... \n" + "\t -E wav | play -t wav - \n" "\t -s 22050 | multimon -t raw /dev/stdin\n\n"); exit(1); } @@ -271,7 +330,7 @@ int cic_9_tables[][10] = { {9, -199, -362, 5303, -25505, 77489, -25505, 5303, -362, -199}, }; -#ifdef _MSC_VER +#if defined(_MSC_VER) && _MSC_VER < 1800 double log2(double n) { return log(n) / log(2.0); @@ -299,6 +358,78 @@ void rotate_90(unsigned char *buf, uint32_t len) } } +int translate_init(struct translate_state *ts) +/* two pass: first to find optimal length, second to alloc/fill */ +{ + int max_length = 100000; + int i, s, c, best_i; + double a, a2, err, best_360; + if (fabs(ts->angle) < 2*M_PI/max_length) { + fprintf(stderr, "angle too small or array too short\n"); + return 1; + } + ts->i = 0; + ts->sincos = NULL; + if (ts->len) { + max_length = ts->len; + ts->sincos = malloc(max_length * sizeof(int16_t)); + } + a = 0.0; + err = 0.0; + best_i = 0; + best_360 = 4.0; + for (i=0; i < max_length; i+=2) { + s = (int)round(sin(a + err) * (1<<14)); + c = (int)round(cos(a + err) * (1<<14)); + a2 = atan2(s, c); + err = fmod(a, 2*M_PI) - a2; + a += ts->angle; + while (a > M_PI) { + a -= 2*M_PI;} + while (a < -M_PI) { + a += 2*M_PI;} + if (i != 0 && fabs(a) < best_360) { + best_i = i; + best_360 = fabs(a); + } + if (i != 0 && fabs(a) < 0.0000001) { + break;} + if (ts->sincos) { + ts->sincos[i] = s; + ts->sincos[i+1] = c; + //fprintf(stderr, "%i %i %i\n", i, s, c); + } + } + if (ts->sincos) { + return 0; + } + ts->len = best_i + 2; + return translate_init(ts); +} + +void translate(struct demod_state *d) +{ + int i, len, sc_i, sc_len; + int32_t tmp, ar, aj, br, bj; + int16_t *buf = d->lowpassed; + int16_t *sincos = d->rotate.sincos; + len = d->lp_len; + sc_i = d->rotate.i; + sc_len = d->rotate.len; + for (i=0; i> 14); + tmp = aj*br + ar*bj; + buf[i+1] = (int16_t)(tmp >> 14); + } + d->rotate.i = sc_i; +} + void low_pass(struct demod_state *d) /* simple square window FIR */ { @@ -341,22 +472,23 @@ void low_pass_real(struct demod_state *s) /* simple square window FIR */ // add support for upsampling? { + int16_t *lp = s->lowpassed; int i=0, i2=0; int fast = (int)s->rate_out; int slow = s->rate_out2; - while (i < s->result_len) { - s->now_lpr += s->result[i]; + while (i < s->lp_len) { + s->now_lpr += lp[i]; i++; s->prev_lpr_index += slow; if (s->prev_lpr_index < fast) { continue; } - s->result[i2] = (int16_t)(s->now_lpr / (fast/slow)); + lp[i2] = (int16_t)(s->now_lpr / (fast/slow)); s->prev_lpr_index -= fast; s->now_lpr = 0; i2 += 1; } - s->result_len = i2; + s->lp_len = i2; } void fifth_order(int16_t *data, int length, int16_t *hist) @@ -430,7 +562,7 @@ int polar_discriminant(int ar, int aj, int br, int bj) double angle; multiply(ar, aj, br, -bj, &cr, &cj); angle = atan2((double)cj, (double)cr); - return (int)(angle / 3.14159 * (1<<14)); + return (int)(angle / M_PI * (1<<14)); } int fast_atan2(int y, int x) @@ -470,7 +602,7 @@ int atan_lut_init(void) atan_lut = malloc(atan_lut_size * sizeof(int)); for (i = 0; i < atan_lut_size; i++) { - atan_lut[i] = (int) (atan((double) i / (1<lowpassed; - pcm = polar_discriminant(lp[0], lp[1], - fm->pre_r, fm->pre_j); - fm->result[0] = (int16_t)pcm; - for (i = 2; i < (fm->lp_len-1); i += 2) { + int16_t pr = fm->pre_r; + int16_t pj = fm->pre_j; + for (i = 0; i < (fm->lp_len-1); i += 2) { switch (fm->custom_atan) { case 0: - pcm = polar_discriminant(lp[i], lp[i+1], - lp[i-2], lp[i-1]); + pcm = polar_discriminant(lp[i], lp[i+1], pr, pj); break; case 1: - pcm = polar_disc_fast(lp[i], lp[i+1], - lp[i-2], lp[i-1]); + pcm = polar_disc_fast(lp[i], lp[i+1], pr, pj); break; case 2: - pcm = polar_disc_lut(lp[i], lp[i+1], - lp[i-2], lp[i-1]); + pcm = polar_disc_lut(lp[i], lp[i+1], pr, pj); + break; + case 3: + pcm = esbensen(lp[i], lp[i+1], pr, pj); break; } - fm->result[i/2] = (int16_t)pcm; + pr = lp[i]; + pj = lp[i+1]; + fm->lowpassed[i/2] = (int16_t)pcm; } - fm->pre_r = lp[fm->lp_len - 2]; - fm->pre_j = lp[fm->lp_len - 1]; - fm->result_len = fm->lp_len/2; + fm->pre_r = pr; + fm->pre_j = pj; + fm->lp_len = fm->lp_len / 2; } void am_demod(struct demod_state *fm) // todo, fix this extreme laziness { - int i, pcm; + int32_t i, pcm; int16_t *lp = fm->lowpassed; - int16_t *r = fm->result; for (i = 0; i < fm->lp_len; i += 2) { // hypot uses floats but won't overflow //r[i/2] = (int16_t)hypot(lp[i], lp[i+1]); pcm = lp[i] * lp[i]; pcm += lp[i+1] * lp[i+1]; - r[i/2] = (int16_t)sqrt(pcm) * fm->output_scale; + lp[i/2] = (int16_t)sqrt(pcm) * fm->output_scale; } - fm->result_len = fm->lp_len/2; - // lowpass? (3khz) highpass? (dc) + fm->lp_len = fm->lp_len / 2; + // lowpass? (3khz) } void usb_demod(struct demod_state *fm) { int i, pcm; int16_t *lp = fm->lowpassed; - int16_t *r = fm->result; for (i = 0; i < fm->lp_len; i += 2) { pcm = lp[i] + lp[i+1]; - r[i/2] = (int16_t)pcm * fm->output_scale; + lp[i/2] = (int16_t)pcm * fm->output_scale; } - fm->result_len = fm->lp_len/2; + fm->lp_len = fm->lp_len / 2; } void lsb_demod(struct demod_state *fm) { int i, pcm; int16_t *lp = fm->lowpassed; - int16_t *r = fm->result; for (i = 0; i < fm->lp_len; i += 2) { pcm = lp[i] - lp[i+1]; - r[i/2] = (int16_t)pcm * fm->output_scale; + lp[i/2] = (int16_t)pcm * fm->output_scale; } - fm->result_len = fm->lp_len/2; + fm->lp_len = fm->lp_len / 2; } void raw_demod(struct demod_state *fm) { - int i; - for (i = 0; i < fm->lp_len; i++) { - fm->result[i] = (int16_t)fm->lowpassed[i]; - } - fm->result_len = fm->lp_len; + return; } void deemph_filter(struct demod_state *fm) { - static int avg; // cheating... + static int avg; // cheating, not threadsafe int i, d; + int16_t *lp = fm->lowpassed; // de-emph IIR // avg = avg * (1 - alpha) + sample * alpha; - for (i = 0; i < fm->result_len; i++) { - d = fm->result[i] - avg; + for (i = 0; i < fm->lp_len; i++) { + d = lp[i] - avg; if (d > 0) { avg += (d + fm->deemph_a/2) / fm->deemph_a; } else { avg += (d - fm->deemph_a/2) / fm->deemph_a; } - fm->result[i] = (int16_t)avg; + lp[i] = (int16_t)avg; } } @@ -614,13 +759,14 @@ void dc_block_filter(struct demod_state *fm) { int i, avg; int64_t sum = 0; - for (i=0; i < fm->result_len; i++) { - sum += fm->result[i]; + int16_t *lp = fm->lowpassed; + for (i=0; i < fm->lp_len; i++) { + sum += lp[i]; } - avg = sum / fm->result_len; + avg = (int)(sum / fm->lp_len); avg = (avg + fm->dc_avg * 9) / 10; - for (i=0; i < fm->result_len; i++) { - fm->result[i] -= avg; + for (i=0; i < fm->lp_len; i++) { + lp[i] -= avg; } fm->dc_avg = avg; } @@ -662,75 +808,80 @@ int rms(int16_t *samples, int len, int step) return (int)sqrt((p-err) / len); } -void arbitrary_upsample(int16_t *buf1, int16_t *buf2, int len1, int len2) -/* linear interpolation, len1 < len2 */ +int squelch_to_rms(int db, struct dongle_state *dongle, struct demod_state *demod) +/* 0 dB = 1 rms at 50dB gain and 1024 downsample */ { - int i = 1; - int j = 0; - int tick = 0; - double frac; // use integers... - while (j < len2) { - frac = (double)tick / (double)len2; - buf2[j] = (int16_t)(buf1[i-1]*(1-frac) + buf1[i]*frac); - j++; - tick += len1; - if (tick > len2) { - tick -= len2; - i++; - } - if (i >= len1) { - i = len1 - 1; - tick = len2; - } + double linear, gain, downsample; + if (db == 0) { + return 0;} + linear = pow(10.0, (double)db/20.0); + gain = 50.0; + if (dongle->gain != AUTO_GAIN) { + gain = (double)(dongle->gain) / 10.0; } + gain = 50.0 - gain; + gain = pow(10.0, gain/20.0); + downsample = 1024.0 / (double)demod->downsample; + linear = linear / gain; + linear = linear / downsample; + return (int)linear + 1; } -void arbitrary_downsample(int16_t *buf1, int16_t *buf2, int len1, int len2) -/* fractional boxcar lowpass, len1 > len2 */ +void software_agc(struct demod_state *d) { - int i = 1; - int j = 0; - int tick = 0; - double remainder = 0; - double frac; // use integers... - buf2[0] = 0; - while (j < len2) { - frac = 1.0; - if ((tick + len2) > len1) { - frac = (double)(len1 - tick) / (double)len2;} - buf2[j] += (int16_t)((double)buf1[i] * frac + remainder); - remainder = (double)buf1[i] * (1.0-frac); - tick += len2; - i++; - if (tick > len1) { - j++; - buf2[j] = 0; - tick -= len1; + int i = 0; + int peaked = 0; + int32_t output = 0; + int abs_output = 0; + struct agc_state *agc = d->agc; + int16_t *lp = d->lowpassed; + int attack_step = agc->attack_step; + int aggressive = agc_aggressive == d->agc_mode; + float peak_factor = 1.0; + + for (i=0; i < d->lp_len; i++) { + output = (int32_t)lp[i] * agc->gain_num + agc->error; + agc->error = output % agc->gain_den; + output /= agc->gain_den; + abs_output = abs(output); + peaked = abs_output > agc->peak_target; + + if (peaked && aggressive && attack_step <= 1) { + peak_factor = fmin(5.0, (float) abs_output / agc->peak_target); + attack_step = (int) (pow(agc->attack_step - peak_factor, peak_factor) * (176 + 3 * peak_factor)); } - if (i >= len1) { - i = len1 - 1; - tick = len1; + + if (peaked) { + agc->gain_num -= attack_step; + if (aggressive) { + attack_step = (int) (attack_step / 1.2); + } + } else { + agc->gain_num += agc->decay_step; } - } - for (j=0; jgain_num < agc->gain_den) { + agc->gain_num = agc->gain_den;} + if (agc->gain_num > agc->gain_max) { + agc->gain_num = agc->gain_max;} + + if (output >= (1<<15)) { + output = (1<<15) - 1;} + if (output < -(1<<15)) { + output = -(1<<15) + 1;} + + lp[i] = (int16_t)output; } } void full_demod(struct demod_state *d) { int i, ds_p; + int do_squelch = 0; int sr = 0; + if(d->rotate_enable) { + translate(d); + } ds_p = d->downsample_passes; if (ds_p) { for (i=0; i < ds_p; i++) { @@ -752,55 +903,100 @@ void full_demod(struct demod_state *d) if (d->squelch_level) { sr = rms(d->lowpassed, d->lp_len, 1); if (sr < d->squelch_level) { - d->squelch_hits++; - for (i=0; ilp_len; i++) { - d->lowpassed[i] = 0; - } - } else { - d->squelch_hits = 0;} + do_squelch = 1;} } - d->mode_demod(d); /* lowpassed -> result */ - if (d->mode_demod == &raw_demod) { - return; + if (do_squelch) { + d->squelch_hits++; + for (i=0; ilp_len; i++) { + d->lowpassed[i] = 0; + } + } else { + d->squelch_hits = 0; + } + if (d->squelch_level && d->squelch_hits > d->conseq_squelch) { + d->agc->gain_num = d->agc->gain_den; } + d->mode_demod(d); /* lowpassed -> lowpassed */ + if (d->mode_demod == &raw_demod) { + return;} + if (d->dc_block) { + dc_block_filter(d);} + if (d->agc_mode != agc_off) { + software_agc(d);} /* todo, fm noise squelch */ // use nicer filter here too? if (d->post_downsample > 1) { - d->result_len = low_pass_simple(d->result, d->result_len, d->post_downsample);} + d->lp_len = low_pass_simple(d->lowpassed, d->lp_len, d->post_downsample);} if (d->deemph) { deemph_filter(d);} - if (d->dc_block) { - dc_block_filter(d);} if (d->rate_out2 > 0) { low_pass_real(d); - //arbitrary_resample(d->result, d->result, d->result_len, d->result_len * d->rate_out2 / d->rate_out); + //arbitrary_resample(d->lowpassed, d->lowpassed, d->lp_len, d->lp_len * d->rate_out2 / d->rate_out); + } +} + +int16_t* mark_shared_buffer(void) +{ + int i = 0; + for (i=0; idemod_target; + struct demod_state *d; + struct demod_state *d2; if (do_exit) { return;} - if (!ctx) { + if (!s) { return;} + d = s->targets[0]; + d2 = s->targets[1]; if (s->mute) { for (i=0; imute; i++) { buf[i] = 127;} s->mute = 0; } - if (!s->offset_tuning) { + if (s->pre_rotate) { rotate_90(buf, len);} for (i=0; i<(int)len; i++) { s->buf16[i] = (int16_t)buf[i] - 127;} + if (d2 != NULL) { + pthread_rwlock_wrlock(&d2->rw); + d2->lowpassed = mark_shared_buffer(); + memcpy(d2->lowpassed, s->buf16, 2*len); + d2->lp_len = len; + pthread_rwlock_unlock(&d2->rw); + safe_cond_signal(&d2->ready, &d2->ready_m); + } pthread_rwlock_wrlock(&d->rw); - memcpy(d->lowpassed, s->buf16, 2*len); + d->lowpassed = s->buf16; d->lp_len = len; pthread_rwlock_unlock(&d->rw); safe_cond_signal(&d->ready, &d->ready_m); + s->buf16 = mark_shared_buffer(); } static void *dongle_thread_fn(void *arg) @@ -813,7 +1009,7 @@ static void *dongle_thread_fn(void *arg) static void *demod_thread_fn(void *arg) { struct demod_state *d = arg; - struct output_state *o = d->output_target; + struct buffer_bucket *o = d->output_target; while (!do_exit) { safe_cond_wait(&d->ready, &d->ready_m); pthread_rwlock_wrlock(&d->rw); @@ -822,29 +1018,120 @@ static void *demod_thread_fn(void *arg) if (d->exit_flag) { do_exit = 1; } - if (d->squelch_level && d->squelch_hits > d->conseq_squelch) { + pthread_rwlock_wrlock(&o->rw); + o->buf = d->lowpassed; + o->len = d->lp_len; + pthread_rwlock_unlock(&o->rw); + if (controller.freq_len > 1 && d->squelch_level && \ + d->squelch_hits > d->conseq_squelch) { + unmark_shared_buffer(d->lowpassed); d->squelch_hits = d->conseq_squelch + 1; /* hair trigger */ safe_cond_signal(&controller.hop, &controller.hop_m); continue; } - pthread_rwlock_wrlock(&o->rw); - memcpy(o->result, d->result, 2*d->result_len); - o->result_len = d->result_len; - pthread_rwlock_unlock(&o->rw); safe_cond_signal(&o->ready, &o->ready_m); + pthread_mutex_lock(&o->trycond_m); + o->trycond = 0; + pthread_mutex_unlock(&o->trycond_m); } return 0; } +#ifndef _WIN32 +static int get_nanotime(struct timespec *ts) +{ + int rv = ENOSYS; +#ifdef __unix__ + rv = clock_gettime(CLOCK_MONOTONIC, ts); +#elif __APPLE__ + struct timeval tv; + + rv = gettimeofday(&tv, NULL); + ts->tv_sec = tv.tv_sec; + ts->tv_nsec = tv.tv_usec * 1000L; +#endif + return rv; +} +#endif + static void *output_thread_fn(void *arg) { + int j, r = 0; struct output_state *s = arg; + struct buffer_bucket *b0 = &s->results[0]; + struct buffer_bucket *b1 = &s->results[1]; + int64_t i, duration, samples = 0LL, samples_now; +#ifdef _WIN32 + LARGE_INTEGER perfFrequency; + LARGE_INTEGER start_time; + LARGE_INTEGER now_time; + + QueryPerformanceFrequency(&perfFrequency); + QueryPerformanceCounter(&start_time); +#else + struct timespec start_time; + struct timespec now_time; + + get_nanotime(&start_time); +#endif while (!do_exit) { - // use timedwait and pad out under runs - safe_cond_wait(&s->ready, &s->ready_m); - pthread_rwlock_rdlock(&s->rw); - fwrite(s->result, 2, s->result_len, s->file); - pthread_rwlock_unlock(&s->rw); + if (s->lrmix) { + safe_cond_wait(&b0->ready, &b0->ready_m); + pthread_rwlock_rdlock(&b0->rw); + safe_cond_wait(&b1->ready, &b1->ready_m); + pthread_rwlock_rdlock(&b1->rw); + for(j=0; j < b0->len; j++) { + fwrite(b0->buf+j, 2, 1, s->file); + fwrite(b1->buf+j, 2, 1, s->file); + } + unmark_shared_buffer(b1->buf); + pthread_rwlock_unlock(&b1->rw); + unmark_shared_buffer(b0->buf); + pthread_rwlock_unlock(&b0->rw); + continue; + } + if (!s->padded) { + safe_cond_wait(&b0->ready, &b0->ready_m); + pthread_rwlock_rdlock(&b0->rw); + fwrite(b0->buf, 2, b0->len, s->file); + unmark_shared_buffer(b0->buf); + pthread_rwlock_unlock(&b0->rw); + continue; + } + + /* padding requires output at constant rate */ + /* pthread_cond_timedwait is terrible, roll our own trycond */ + usleep(2000); + pthread_mutex_lock(&b0->trycond_m); + r = b0->trycond; + b0->trycond = 1; + pthread_mutex_unlock(&b0->trycond_m); + if (r == 0) { + pthread_rwlock_rdlock(&b0->rw); + fwrite(b0->buf, 2, b0->len, s->file); + unmark_shared_buffer(b0->buf); + samples += (int64_t)b0->len; + pthread_rwlock_unlock(&b0->rw); + continue; + } +#ifdef _WIN32 + QueryPerformanceCounter(&now_time); + duration = now_time.QuadPart - start_time.QuadPart; + samples_now = (duration * s->rate) / perfFrequency.QuadPart; +#else + get_nanotime(&now_time); + duration = now_time.tv_sec - start_time.tv_sec; + duration *= 1000000000L; + duration += (now_time.tv_nsec - start_time.tv_nsec); + samples_now = (duration * s->rate) / 1000000000UL; +#endif + if (samples_now < samples) { + continue;} + for (i=samples; ifile); + fputc(0, s->file); + } + samples = samples_now; } return 0; } @@ -857,14 +1144,14 @@ static void optimal_settings(int freq, int rate) struct dongle_state *d = &dongle; struct demod_state *dm = &demod; struct controller_state *cs = &controller; - dm->downsample = (1000000 / dm->rate_in) + 1; + dm->downsample = (MINIMUM_RATE / dm->rate_in) + 1; if (dm->downsample_passes) { dm->downsample_passes = (int)log2(dm->downsample) + 1; dm->downsample = 1 << dm->downsample_passes; } capture_freq = freq; capture_rate = dm->downsample * dm->rate_in; - if (!d->offset_tuning) { + if (d->pre_rotate) { capture_freq = freq + capture_rate/4;} capture_freq += cs->edge * dm->rate_in / 2; dm->output_scale = (1<<15) / (128 * dm->downsample); @@ -874,6 +1161,75 @@ static void optimal_settings(int freq, int rate) dm->output_scale = 1;} d->freq = (uint32_t)capture_freq; d->rate = (uint32_t)capture_rate; + //d->pre_rotate = 0; + //demod.rotate_enable = 1; + //demod.rotate.angle = -0.25 * 2 * M_PI; + //translate_init(&demod.rotate); +} + +void clone_demod(struct demod_state *d2, struct demod_state *d1) +/* copy from d1 to d2 */ +{ + d2->rate_in = d1->rate_in; + d2->rate_out = d1->rate_out; + d2->rate_out2 = d1->rate_out2; + d2->downsample = d1->downsample; + d2->downsample_passes = d1->downsample_passes; + d2->post_downsample = d1->post_downsample; + d2->output_scale = d1->output_scale; + d2->squelch_level = d1->squelch_level; + d2->conseq_squelch = d1->conseq_squelch; + d2->squelch_hits = d1->squelch_hits; + d2->terminate_on_squelch = d1->terminate_on_squelch; + d2->comp_fir_size = d1->comp_fir_size; + d2->custom_atan = d1->custom_atan; + d2->deemph = d1->deemph; + d2->deemph_a = d1->deemph_a; + d2->dc_block = d1->dc_block; + d2->rotate_enable = d1->rotate_enable; + d2->agc_mode = d1->agc_mode; + d2->mode_demod = d1->mode_demod; +} + +void optimal_lrmix(void) +{ + double angle1, angle2; + uint32_t freq, freq1, freq2, bw, dongle_bw, mr; + if (controller.freq_len != 2) { + fprintf(stderr, "error: lrmix requires two frequencies\n"); + do_exit = 1; + exit(1); + } + if (output.padded) { + fprintf(stderr, "warning: lrmix does not support padding\n"); + } + freq1 = controller.freqs[0]; + freq2 = controller.freqs[1]; + bw = demod.rate_out; + freq = freq1 / 2 + freq2 / 2 + bw; + mr = (uint32_t)abs((int64_t)freq1 - (int64_t)freq2) + bw; + if (mr > MINIMUM_RATE) { + MINIMUM_RATE = mr;} + dongle.pre_rotate = 0; + optimal_settings(freq, bw); + output.padded = 0; + clone_demod(&demod2, &demod); + //demod2 = demod; + demod2.output_target = &output.results[1]; + dongle.targets[1] = &demod2; + dongle_bw = dongle.rate; + if (dongle_bw > MAXIMUM_RATE) { + fprintf(stderr, "error: unable to find optimal settings\n"); + do_exit = 1; + exit(1); + } + angle1 = ((double)freq1 - (double)freq) / (double)dongle_bw; + demod.rotate.angle = angle1 * 2 * M_PI; + angle2 = ((double)freq2 - (double)freq) / (double)dongle_bw; + demod2.rotate.angle = angle2 * 2 * M_PI; + translate_init(&demod.rotate); + translate_init(&demod2.rotate); + //fprintf(stderr, "a1 %f, a2 %f\n", angle1, angle2); } static void *controller_thread_fn(void *arg) @@ -890,11 +1246,17 @@ static void *controller_thread_fn(void *arg) /* set up primary channel */ optimal_settings(s->freqs[0], demod.rate_in); + demod.squelch_level = squelch_to_rms(demod.squelch_level, &dongle, &demod); if (dongle.direct_sampling) { - verbose_direct_sampling(dongle.dev, 1);} + verbose_direct_sampling(dongle.dev, dongle.direct_sampling);} if (dongle.offset_tuning) { verbose_offset_tuning(dongle.dev);} + /* set up lrmix */ + if (output.lrmix) { + optimal_lrmix(); + } + /* Set the frequency */ verbose_set_frequency(dongle.dev, dongle.freq); fprintf(stderr, "Oversampling input by: %ix.\n", demod.downsample); @@ -910,6 +1272,8 @@ static void *controller_thread_fn(void *arg) safe_cond_wait(&s->hop, &s->hop_m); if (s->freq_len <= 1) { continue;} + if (output.lrmix) { + continue;} /* hacky hopping */ s->freq_now = (s->freq_now + 1) % s->freq_len; optimal_settings(s->freqs[s->freq_now], demod.rate_in); @@ -946,7 +1310,10 @@ void dongle_init(struct dongle_state *s) s->mute = 0; s->direct_sampling = 0; s->offset_tuning = 0; - s->demod_target = &demod; + s->pre_rotate = 1; + s->targets[0] = &demod; + s->targets[1] = NULL; + s->buf16 = mark_shared_buffer(); } void demod_init(struct demod_state *s) @@ -963,18 +1330,23 @@ void demod_init(struct demod_state *s) s->post_downsample = 1; // once this works, default = 4 s->custom_atan = 0; s->deemph = 0; + s->agc_mode = agc_off; + s->rotate_enable = 0; + s->rotate.len = 0; + s->rotate.sincos = NULL; s->rate_out2 = -1; // flag for disabled s->mode_demod = &fm_demod; s->pre_j = s->pre_r = s->now_r = s->now_j = 0; s->prev_lpr_index = 0; s->deemph_a = 0; s->now_lpr = 0; - s->dc_block = 0; + s->dc_block = 1; s->dc_avg = 0; pthread_rwlock_init(&s->rw, NULL); pthread_cond_init(&s->ready, NULL); pthread_mutex_init(&s->ready_m, NULL); - s->output_target = &output; + s->output_target = &output.results[0]; + s->lowpassed = NULL; } void demod_cleanup(struct demod_state *s) @@ -986,17 +1358,27 @@ void demod_cleanup(struct demod_state *s) void output_init(struct output_state *s) { - s->rate = DEFAULT_SAMPLE_RATE; - pthread_rwlock_init(&s->rw, NULL); - pthread_cond_init(&s->ready, NULL); - pthread_mutex_init(&s->ready_m, NULL); + int i; + //s->rate = DEFAULT_SAMPLE_RATE; + for (i=0; i<2; i++) { + pthread_rwlock_init(&s->results[i].rw, NULL); + pthread_cond_init(&s->results[i].ready, NULL); + pthread_mutex_init(&s->results[i].ready_m, NULL); + pthread_mutex_init(&s->results[i].trycond_m, NULL); + s->results[i].trycond = 1; + s->results[i].buf = NULL; + } } void output_cleanup(struct output_state *s) { - pthread_rwlock_destroy(&s->rw); - pthread_cond_destroy(&s->ready); - pthread_mutex_destroy(&s->ready_m); + int i; + for (i=0; i<2; i++) { + pthread_rwlock_destroy(&s->results[i].rw); + pthread_cond_destroy(&s->results[i].ready); + pthread_mutex_destroy(&s->results[i].ready_m); + pthread_mutex_destroy(&s->results[i].trycond_m); + } } void controller_init(struct controller_state *s) @@ -1027,11 +1409,71 @@ void sanity_checks(void) exit(1); } - if (controller.freq_len > 1 && demod.squelch_level == 0) { + if (!output.lrmix && controller.freq_len > 1 && demod.squelch_level == 0) { fprintf(stderr, "Please specify a squelch level. Required for scanning multiple frequencies.\n"); exit(1); } + if (demod.mode_demod == &raw_demod && output.lrmix) { + fprintf(stderr, "'raw' is not a supported demodulator for lrmix\n"); + exit(1); + } + +} + +int agc_init(struct demod_state *s) +{ + struct agc_state *agc; + + agc = malloc(sizeof(struct agc_state)); + s->agc = agc; + + agc->gain_den = 1<<15; + agc->peak_target = 1<<14; + agc->gain_max = 256 * agc->gain_den; + agc->gain_num = agc->gain_den; + agc->decay_step = 1; + agc->attack_step = 2; + if (s->agc_mode == agc_aggressive) { + agc->decay_step = agc->decay_step * 4; + agc->attack_step = agc->attack_step * 5; + } + + return 0; +} + +int generate_header(struct demod_state *d, struct output_state *o) +{ + int i, s_rate, b_rate; + char *channels = "\1\0"; + char *align = "\2\0"; + uint8_t samp_rate[4] = {0, 0, 0, 0}; + uint8_t byte_rate[4] = {0, 0, 0, 0}; + s_rate = o->rate; + b_rate = o->rate * 2; + if (d->mode_demod == &raw_demod || o->lrmix) { + channels = "\2\0"; + align = "\4\0"; + b_rate *= 2; + } + for (i=0; i<4; i++) { + samp_rate[i] = (uint8_t)((s_rate >> (8*i)) & 0xFF); + byte_rate[i] = (uint8_t)((b_rate >> (8*i)) & 0xFF); + } + fwrite("RIFF", 1, 4, o->file); + fwrite("\xFF\xFF\xFF\xFF", 1, 4, o->file); /* size */ + fwrite("WAVE", 1, 4, o->file); + fwrite("fmt ", 1, 4, o->file); + fwrite("\x10\0\0\0", 1, 4, o->file); /* size */ + fwrite("\1\0", 1, 2, o->file); /* pcm */ + fwrite(channels, 1, 2, o->file); + fwrite(samp_rate, 1, 4, o->file); + fwrite(byte_rate, 1, 4, o->file); + fwrite(align, 1, 2, o->file); + fwrite("\x10\0", 1, 2, o->file); /* bits per channel */ + fwrite("data", 1, 4, o->file); + fwrite("\xFF\xFF\xFF\xFF", 1, 4, o->file); /* size */ + return 0; } int main(int argc, char **argv) @@ -1042,8 +1484,10 @@ int main(int argc, char **argv) int r, opt; int dev_given = 0; int custom_ppm = 0; + dongle_init(&dongle); demod_init(&demod); + demod_init(&demod2); output_init(&output); controller_init(&controller); @@ -1098,14 +1542,27 @@ int main(int argc, char **argv) case 'E': if (strcmp("edge", optarg) == 0) { controller.edge = 1;} - if (strcmp("dc", optarg) == 0) { - demod.dc_block = 1;} + if (strcmp("no-dc", optarg) == 0) { + demod.dc_block = 0;} if (strcmp("deemp", optarg) == 0) { demod.deemph = 1;} + if (strcmp("swagc", optarg) == 0) { + demod.agc_mode = agc_normal;} + if (strcmp("swagc-aggressive", optarg) == 0) { + demod.agc_mode = agc_aggressive;} if (strcmp("direct", optarg) == 0) { dongle.direct_sampling = 1;} + if (strcmp("no-mod", optarg) == 0) { + dongle.direct_sampling = 3;} if (strcmp("offset", optarg) == 0) { - dongle.offset_tuning = 1;} + dongle.offset_tuning = 1; + dongle.pre_rotate = 0;} + if (strcmp("wav", optarg) == 0) { + output.wav_format = 1;} + if (strcmp("pad", optarg) == 0) { + output.padded = 1;} + if (strcmp("lrmix", optarg) == 0) { + output.lrmix = 1;} break; case 'F': demod.downsample_passes = 1; /* truthy placeholder */ @@ -1119,6 +1576,8 @@ int main(int argc, char **argv) if (strcmp("lut", optarg) == 0) { atan_lut_init(); demod.custom_atan = 2;} + if (strcmp("ale", optarg) == 0) { + demod.custom_atan = 3;} break; case 'M': if (strcmp("fm", optarg) == 0) { @@ -1137,6 +1596,7 @@ int main(int argc, char **argv) demod.rate_in = 170000; demod.rate_out = 170000; demod.rate_out2 = 32000; + output.rate = 32000; demod.custom_atan = 1; //demod.post_downsample = 4; demod.deemph = 1; @@ -1149,6 +1609,8 @@ int main(int argc, char **argv) } } + agc_init(&demod); + /* quadruple sample_rate to limit to Δθ to ±π/2 */ demod.rate_in *= demod.post_downsample; @@ -1189,8 +1651,10 @@ int main(int argc, char **argv) sigaction(SIGTERM, &sigact, NULL); sigaction(SIGQUIT, &sigact, NULL); sigaction(SIGPIPE, &sigact, NULL); + signal(SIGPIPE, SIG_IGN); #else SetConsoleCtrlHandler( (PHANDLER_ROUTINE) sighandler, TRUE ); + output.padded = 0; #endif if (demod.deemph) { @@ -1205,6 +1669,9 @@ int main(int argc, char **argv) verbose_gain_set(dongle.dev, dongle.gain); } + if (!custom_ppm) { + verbose_ppm_eeprom(dongle.dev, &(dongle.ppm_error)); + } verbose_ppm_set(dongle.dev, dongle.ppm_error); if (strcmp(output.filename, "-") == 0) { /* Write samples to stdout */ @@ -1220,6 +1687,10 @@ int main(int argc, char **argv) } } + if (output.wav_format) { + generate_header(&demod, &output); + } + //r = rtlsdr_set_testmode(dongle.dev, 1); /* Reset endpoint before we start reading from it (mandatory) */ @@ -1229,6 +1700,9 @@ int main(int argc, char **argv) usleep(100000); pthread_create(&output.thread, NULL, output_thread_fn, (void *)(&output)); pthread_create(&demod.thread, NULL, demod_thread_fn, (void *)(&demod)); + if (output.lrmix) { + pthread_create(&demod2.thread, NULL, demod_thread_fn, (void *)(&demod2)); + } pthread_create(&dongle.thread, NULL, dongle_thread_fn, (void *)(&dongle)); while (!do_exit) { @@ -1244,7 +1718,12 @@ int main(int argc, char **argv) pthread_join(dongle.thread, NULL); safe_cond_signal(&demod.ready, &demod.ready_m); pthread_join(demod.thread, NULL); - safe_cond_signal(&output.ready, &output.ready_m); + if (output.lrmix) { + safe_cond_signal(&demod2.ready, &demod2.ready_m); + pthread_join(demod2.thread, NULL); + } + safe_cond_signal(&output.results[0].ready, &output.results[0].ready_m); + safe_cond_signal(&output.results[1].ready, &output.results[1].ready_m); pthread_join(output.thread, NULL); safe_cond_signal(&controller.hop, &controller.hop_m); pthread_join(controller.thread, NULL); diff --git a/src/rtl_power.c b/src/rtl_power.c index 7eb1d06..217b745 100644 --- a/src/rtl_power.c +++ b/src/rtl_power.c @@ -53,7 +53,7 @@ #include #include "getopt/getopt.h" #define usleep(x) Sleep(x/1000) -#ifdef _MSC_VER +#if defined(_MSC_VER) && _MSC_VER < 1800 #define round(x) (x > 0.0 ? floor(x + 0.5): ceil(x - 0.5)) #endif #define _USE_MATH_DEFINES @@ -67,36 +67,50 @@ #include "convenience/convenience.h" #define MAX(x, y) (((x) > (y)) ? (x) : (y)) +#define MIN(x, y) (((x) < (y)) ? (x) : (y)) #define DEFAULT_BUF_LENGTH (1 * 16384) #define AUTO_GAIN -100 #define BUFFER_DUMP (1<<12) -#define MAXIMUM_RATE 2800000 +#define MAXIMUM_RATE 3200000 #define MINIMUM_RATE 1000000 +#define DEFAULT_TARGET 2400000 + +#define MAXIMUM_FFT 32 static volatile int do_exit = 0; static rtlsdr_dev_t *dev = NULL; FILE *file; -int16_t* Sinewave; -double* power_table; -int N_WAVE, LOG2_N_WAVE; -int next_power; -int16_t *fft_buf; -int *window_coefs; +struct sine_table +{ + int16_t* Sinewave; + int N_WAVE; + int LOG2_N_WAVE; +}; + +struct sine_table s_tables[MAXIMUM_FFT]; struct tuning_state /* one per tuning range */ { int freq; int rate; + int gain; int bin_e; - long *avg; /* length == 2^bin_e */ + int16_t *fft_buf; + int64_t *avg; /* length == 2^bin_e */ int samples; int downsample; int downsample_passes; /* for the recursive filter */ + int comp_fir_size; + int peak_hold; /* 1 = peak, 0 = off, -1 = trough */ + int linear; + int bin_spec; double crop; + int crop_i1, crop_i2; + int freq_low, freq_high; //pthread_rwlock_t avg_lock; //pthread_mutex_t avg_mutex; /* having the iq buffer here is wasteful, but will avoid contention */ @@ -105,27 +119,50 @@ struct tuning_state //int *comp_fir; //pthread_rwlock_t buf_lock; //pthread_mutex_t buf_mutex; + int *window_coefs; + struct sine_table *sine; /* points to an element of s_tables */ +}; + +struct channel_solve +/* details required to find optimal tuning */ +{ + int upper, lower, bin_spec; + int hops, bw_wanted, bw_needed; + int bin_e, downsample, downsample_passes; + double crop, crop_tmp; +}; + +enum time_modes { VERBOSE_TIME, EPOCH_TIME }; + +struct misc_settings +{ + int boxcar; + int comp_fir_size; + int peak_hold; + int linear; + int target_rate; + double crop; + int gain; + double (*window_fn)(int, int); + int smoothing; + enum time_modes time_mode; }; /* 3000 is enough for 3GHz b/w worst case */ -#define MAX_TUNES 3000 +#define MAX_TUNES 4000 struct tuning_state tunes[MAX_TUNES]; int tune_count = 0; -int boxcar = 1; -int comp_fir_size = 0; -int peak_hold = 0; - void usage(void) { fprintf(stderr, "rtl_power, a simple FFT logger for RTL2832 based DVB-T receivers\n\n" - "Use:\trtl_power -f freq_range [-options] [filename]\n" + "Use:\trtl_power -f freq_range [-options] [-f freq2 -opts2] [filename]\n" "\t-f lower:upper:bin_size [Hz]\n" - "\t (bin size is a maximum, smaller more convenient bins\n" - "\t will be used. valid range 1Hz - 2.8MHz)\n" + "\t valid range for bin_size is 1Hz - 2.8MHz\n" + "\t multiple frequency ranges are supported\n" "\t[-i integration_interval (default: 10 seconds)]\n" - "\t (buggy if a full sweep takes longer than the interval)\n" + "\t buggy if a full sweep takes longer than the interval\n" "\t[-1 enables single-shot mode (default: off)]\n" "\t[-e exit_timer (default: off/0)]\n" //"\t[-s avg/iir smoothing (default: avg)]\n" @@ -134,37 +171,46 @@ void usage(void) "\t[-g tuner_gain (default: automatic)]\n" "\t[-p ppm_error (default: 0)]\n" "\tfilename (a '-' dumps samples to stdout)\n" - "\t (omitting the filename also uses stdout)\n" + "\t omitting the filename also uses stdout\n" "\n" "Experimental options:\n" "\t[-w window (default: rectangle)]\n" - "\t (hamming, blackman, blackman-harris, hann-poisson, bartlett, youssef)\n" + "\t hamming, blackman, blackman-harris, hann-poisson, bartlett, youssef\n" // kaiser - "\t[-c crop_percent (default: 0%%, recommended: 20%%-50%%)]\n" - "\t (discards data at the edges, 100%% discards everything)\n" - "\t (has no effect for bins larger than 1MHz)\n" + "\t[-c crop_percent (default: 0%% suggested: 20%%)]\n" + "\t discards data at the edges, 100%% discards everything\n" + "\t has no effect for bins larger than 1MHz\n" + "\t this value is a minimum crop size, more may be discarded\n" "\t[-F fir_size (default: disabled)]\n" - "\t (enables low-leakage downsample filter,\n" + "\t enables low-leakage downsample filter,\n" "\t fir_size can be 0 or 9. 0 has bad roll off,\n" - "\t try with '-c 50%%')\n" - "\t[-P enables peak hold (default: off)]\n" - "\t[-D enable direct sampling (default: off)]\n" + "\t try -F 0 with '-c 50%%' to hide the roll off\n" + "\t[-r max_sample_rate (default: 2.4M)]\n" + "\t possible values are 2M to 3.2M\n" + "\t[-E enables epoch timestamps (default: off/verbose)]\n" + "\t[-P enables peak hold (default: off/averaging)]\n" + "\t[-T enables trough hold (default: off/averaging)]\n" + "\t[-L enable linear output (default: off/dB)]\n" + "\t[-D direct_sampling_mode, 0 (default/off), 1 (I), 2 (Q), 3 (no-mod)]\n" "\t[-O enable offset tuning (default: off)]\n" "\n" "CSV FFT output columns:\n" "\tdate, time, Hz low, Hz high, Hz step, samples, dbm, dbm, ...\n\n" "Examples:\n" "\trtl_power -f 88M:108M:125k fm_stations.csv\n" - "\t (creates 160 bins across the FM band,\n" - "\t individual stations should be visible)\n" + "\t creates 160 bins across the FM band,\n" + "\t individual stations should be visible\n" "\trtl_power -f 100M:1G:1M -i 5m -1 survey.csv\n" - "\t (a five minute low res scan of nearly everything)\n" + "\t a five minute low res scan of nearly everything\n" "\trtl_power -f ... -i 15m -1 log.csv\n" - "\t (integrate for 15 minutes and exit afterwards)\n" + "\t integrate for 15 minutes and exit afterwards\n" "\trtl_power -f ... -e 1h | gzip > log.csv.gz\n" - "\t (collect data for one hour and compress it on the fly)\n\n" + "\t collect data for one hour and compress it on the fly\n\n" + "\tIf you have issues writing +2GB logs on a 32bit platform\n" + "\tuse redirection (rtl_power ... > filename.csv) instead\n\n" "Convert CSV to a waterfall graphic with:\n" - "\t http://kmkeen.com/tmp/heatmap.py.txt \n"); + " https://github.com/keenerd/rtl-sdr-misc/blob/master/heatmap/heatmap.py \n" + "More examples at http://kmkeen.com/rtl-power/\n"); exit(1); } @@ -220,7 +266,7 @@ int cic_9_tables[][10] = { {9, -199, -362, 5303, -25505, 77489, -25505, 5303, -362, -199}, }; -#ifdef _MSC_VER +#if defined(_MSC_VER) && _MSC_VER < 1800 double log2(double n) { return log(n) / log(2.0); @@ -237,15 +283,34 @@ void sine_table(int size) { int i; double d; - LOG2_N_WAVE = size; - N_WAVE = 1 << LOG2_N_WAVE; - Sinewave = malloc(sizeof(int16_t) * N_WAVE*3/4); - power_table = malloc(sizeof(double) * N_WAVE); - for (i=0; i (MAXIMUM_FFT-1)) { + fprintf(stderr, "Maximum FFT is 2^%i\n", MAXIMUM_FFT-1); + exit(1); + } + sine = &s_tables[size]; + if (sine->LOG2_N_WAVE == size) { + return;} + sine->LOG2_N_WAVE = size; + sine->N_WAVE = 1 << sine->LOG2_N_WAVE; + sine->Sinewave = malloc(sizeof(int16_t) * sine->N_WAVE*3/4); + for (i=0; iN_WAVE*3/4; i++) { - d = (double)i * 2.0 * M_PI / N_WAVE; - Sinewave[i] = (int)round(32767*sin(d)); - //printf("%i\n", Sinewave[i]); + d = (double)i * 2.0 * M_PI / sine->N_WAVE; + sine->Sinewave[i] = (int)round(32767*sin(d)); + //printf("%i\n", sine->Sinewave[i]); + } +} + +void generate_sine_tables(void) +{ + struct tuning_state *ts; + int i; + for (i=0; i < tune_count; i++) { + ts = &tunes[i]; + sine_table(ts->bin_e); + ts->sine = &s_tables[ts->bin_e]; + ts->fft_buf = malloc(ts->buf_len * sizeof(int16_t)); } } @@ -257,13 +322,13 @@ inline int16_t FIX_MPY(int16_t a, int16_t b) return (c >> 1) + b; } -int fix_fft(int16_t iq[], int m) +int fix_fft(int16_t iq[], int m, struct sine_table *sine) /* interleaved iq[], 0 <= n < 2**m, changes in place */ { int mr, nn, i, j, l, k, istep, n, shift; int16_t qr, qi, tr, ti, wr, wi; n = 1 << m; - if (n > N_WAVE) + if (n > sine->N_WAVE) {return -1;} mr = 0; nn = n - 1; @@ -285,14 +350,14 @@ int fix_fft(int16_t iq[], int m) iq[2*mr+1] = ti; } l = 1; - k = LOG2_N_WAVE-1; + k = sine->LOG2_N_WAVE-1; while (l < n) { shift = 1; istep = l << 1; for (m=0; mSinewave[j+sine->N_WAVE/4]; + wi = -sine->Sinewave[j]; if (shift) { wr >>= 1; wi >>= 1;} for (i=m; ibuf8; int buf_len = ts->buf_len; - long p, t; + int64_t p, t; double dc, err; p = t = 0L; for (i=0; ipeak_hold == 0) { ts->avg[0] += p; - } else { + } else if (ts->peak_hold == 1) { ts->avg[0] = MAX(ts->avg[0], p); + } else if (ts->peak_hold == -1) { + ts->avg[0] = MIN(ts->avg[0], p); } ts->samples += 1; } -void frequency_range(char *arg, double crop) -/* flesh out the tunes[] for scanning */ -// do we want the fewest ranges (easy) or the fewest bins (harder)? +/* todo, add errors to parse_freq, solve_foo */ + +int parse_frequency(char *arg, struct channel_solve *c) { char *start, *stop, *step; - int i, j, upper, lower, max_size, bw_seen, bw_used, bin_e, buf_len; - int downsample, downsample_passes; - double bin_size; - struct tuning_state *ts; /* hacky string parsing */ start = arg; stop = strchr(start, ':') + 1; stop[-1] = '\0'; step = strchr(stop, ':') + 1; step[-1] = '\0'; - lower = (int)atofs(start); - upper = (int)atofs(stop); - max_size = (int)atofs(step); + c->lower = (int)atofs(start); + c->upper = (int)atofs(stop); + c->bin_spec = (int)atofs(step); stop[-1] = ':'; step[-1] = ':'; - downsample = 1; - downsample_passes = 0; - /* evenly sized ranges, as close to MAXIMUM_RATE as possible */ - // todo, replace loop with algebra - for (i=1; i<1500; i++) { - bw_seen = (upper - lower) / i; - bw_used = (int)((double)(bw_seen) / (1.0 - crop)); - if (bw_used > MAXIMUM_RATE) { + return 0; +} + +int solve_giant_bins(struct channel_solve *c) +{ + c->bw_wanted = c->bin_spec; + c->bw_needed = c->bin_spec; + c->hops = (c->upper - c->lower) / c->bin_spec; + c->bin_e = 0; + c->crop_tmp = 0; + return 0; +} + +int solve_downsample(struct channel_solve *c, int target_rate, int boxcar) +{ + int scan_size, bins_wanted, bins_needed, ds_next, bw; + + scan_size = c->upper - c->lower; + c->hops = 1; + c->bw_wanted = scan_size; + + bins_wanted = (int)ceil((double)scan_size / (double)c->bin_spec); + c->bin_e = (int)ceil(log2(bins_wanted)); + while (1) { + bins_needed = 1 << c->bin_e; + c->crop_tmp = (double)(bins_needed - bins_wanted) / (double)bins_needed; + if (c->crop_tmp >= c->crop) { + break;} + c->bin_e++; + } + + c->downsample = 1; + c->downsample_passes = 0; + while (1) { + bw = (int)((double)scan_size / (1.0 - c->crop_tmp)); + c->bw_needed = bw * c->downsample; + + if (boxcar) { + ds_next = c->downsample + 1; + } else { + ds_next = c->downsample * 2; + } + if ((bw * ds_next) > target_rate) { + break;} + + c->downsample = ds_next; + if (!boxcar) { + c->downsample_passes++;} + } + + return 0; +} + +int solve_single(struct channel_solve *c, int target_rate) +{ + int i, scan_size, bins_all, bins_crop, bin_e, bins_2, bw_needed; + scan_size = c->upper - c->lower; + bins_all = scan_size / c->bin_spec; + bins_crop = (int)ceil((double)bins_all * (1.0 + c->crop)); + bin_e = (int)ceil(log2(bins_crop)); + bins_2 = 1 << bin_e; + bw_needed = bins_2 * c->bin_spec; + + if (bw_needed > target_rate) { + /* actually multi-hop */ + return 1;} + + c->bw_wanted = scan_size; + c->bw_needed = bw_needed; + c->hops = 1; + c->bin_e = bin_e; + /* crop will always be bigger than specified crop */ + c->crop_tmp = (double)(bins_2 - bins_all) / (double)bins_2; + return 0; +} + +int solve_hopping(struct channel_solve *c, int target_rate) +{ + int i, scan_size, bins_all, bins_sub, bins_crop, bins_2, min_hops; + scan_size = c->upper - c->lower; + min_hops = scan_size / MAXIMUM_RATE - 1; + if (min_hops < 1) { + min_hops = 1;} + /* evenly sized ranges, as close to target_rate as possible */ + for (i=min_hops; ibw_wanted = scan_size / i; + bins_all = scan_size / c->bin_spec; + bins_sub = (int)ceil((double)bins_all / (double)i); + bins_crop = (int)ceil((double)bins_sub * (1.0 + c->crop)); + c->bin_e = (int)ceil(log2(bins_crop)); + bins_2 = 1 << c->bin_e; + c->bw_needed = bins_2 * c->bin_spec; + c->crop_tmp = (double)(bins_2 - bins_sub) / (double)bins_2; + if (c->bw_needed > target_rate) { + continue;} + if (c->crop_tmp < c->crop) { continue;} - tune_count = i; + c->hops = i; break; } - /* unless small bandwidth */ - if (bw_used < MINIMUM_RATE) { - tune_count = 1; - downsample = MAXIMUM_RATE / bw_used; - bw_used = bw_used * downsample; - } - if (!boxcar && downsample > 1) { - downsample_passes = (int)log2(downsample); - downsample = 1 << downsample_passes; - bw_used = (int)((double)(bw_seen * downsample) / (1.0 - crop)); - } - /* number of bins is power-of-two, bin size is under limit */ - // todo, replace loop with log2 - for (i=1; i<=21; i++) { - bin_e = i; - bin_size = (double)bw_used / (double)((1<crop; + + if (ms->target_rate < 2 * MINIMUM_RATE) { + ms->target_rate = 2 * MINIMUM_RATE; + } + if (ms->target_rate > MAXIMUM_RATE) { + ms->target_rate = MAXIMUM_RATE; + } + if ((ms->crop < 0.0) || (ms->crop > 1.0)) { + fprintf(stderr, "Crop value outside of 0 to 1.\n"); + exit(1); } - /* unless giant bins */ - if (max_size >= MINIMUM_RATE) { - bw_seen = max_size; - bw_used = max_size; - tune_count = (upper - lower) / bw_seen; - bin_e = 0; - crop = 0; + + r = -1; + if (c.bin_spec >= MINIMUM_RATE) { + fprintf(stderr, "Mode: rms power\n"); + solve_giant_bins(&c); + } else if ((c.upper - c.lower) < MINIMUM_RATE) { + fprintf(stderr, "Mode: downsampling\n"); + solve_downsample(&c, ms->target_rate, ms->boxcar); + } else if ((c.upper - c.lower) < MAXIMUM_RATE) { + r = solve_single(&c, ms->target_rate); + } else { + fprintf(stderr, "Mode: hopping\n"); + solve_hopping(&c, ms->target_rate); + } + + if (r == 0) { + fprintf(stderr, "Mode: single\n"); + } else if (r == 1) { + fprintf(stderr, "Mode: hopping\n"); + solve_hopping(&c, ms->target_rate); } - if (tune_count > MAX_TUNES) { + c.crop = c.crop_tmp; + + if ((tune_count+c.hops) > MAX_TUNES) { fprintf(stderr, "Error: bandwidth too wide.\n"); exit(1); } - buf_len = 2 * (1<freq = lower + i*bw_seen + bw_seen/2; - ts->rate = bw_used; - ts->bin_e = bin_e; + logged_bins = 0; + lower_edge = c.lower; + planned_bins = (c.upper - c.lower) / c.bin_spec; + for (i=0; i < c.hops; i++) { + ts = &tunes[tune_count + i]; + /* copy common values */ + ts->rate = c.bw_needed; + ts->gain = ms->gain; + ts->bin_e = c.bin_e; ts->samples = 0; - ts->crop = crop; - ts->downsample = downsample; - ts->downsample_passes = downsample_passes; - ts->avg = (long*)malloc((1<bin_spec = c.bin_spec; + ts->crop = c.crop; + ts->downsample = c.downsample; + ts->downsample_passes = c.downsample_passes; + ts->comp_fir_size = ms->comp_fir_size; + ts->peak_hold = ms->peak_hold; + ts->linear = ms->linear; + ts->avg = (int64_t*)malloc((1<avg) { fprintf(stderr, "Error: malloc.\n"); exit(1); } - for (j=0; j<(1<avg[j] = 0L; + for (j=0; j<(1<peak_hold == -1) { + ts->avg[j] = 1e6; + } else { + ts->avg[j] = 0L;} } ts->buf8 = (uint8_t*)malloc(buf_len * sizeof(uint8_t)); if (!ts->buf8) { @@ -515,23 +700,55 @@ void frequency_range(char *arg, double crop) exit(1); } ts->buf_len = buf_len; + length = 1 << c.bin_e; + ts->window_coefs = malloc(length * sizeof(int)); + for (j=0; jwindow_coefs[j] = (int)(256*ms->window_fn(j, length)); + } + /* calculate unique values */ + ts->freq_low = lower_edge; + hop_bins = c.bw_wanted / c.bin_spec; + actual_bw = hop_bins * c.bin_spec; + ts->freq_high = lower_edge + actual_bw; + upper_perfect = c.lower + (i+1) * c.bw_wanted; + if (ts->freq_high + c.bin_spec <= upper_perfect) { + hop_bins += 1; + actual_bw = hop_bins * c.bin_spec; + ts->freq_high = lower_edge + actual_bw; + } + remainder = planned_bins - logged_bins - hop_bins; + if (i == c.hops-1 && remainder > 0) { + hop_bins += remainder; + actual_bw = hop_bins * c.bin_spec; + ts->freq_high = lower_edge + actual_bw; + } + logged_bins += hop_bins; + ts->crop_i1 = (length - hop_bins) / 2; + ts->crop_i2 = ts->crop_i1 + hop_bins - 1; + ts->freq = (lower_edge - ts->crop_i1 * c.bin_spec) + c.bw_needed/(2*c.downsample); + /* prep for next hop */ + lower_edge = ts->freq_high; } + tune_count += c.hops; /* report */ - fprintf(stderr, "Number of frequency hops: %i\n", tune_count); - fprintf(stderr, "Dongle bandwidth: %iHz\n", bw_used); - fprintf(stderr, "Downsampling by: %ix\n", downsample); - fprintf(stderr, "Cropping by: %0.2f%%\n", crop*100); - fprintf(stderr, "Total FFT bins: %i\n", tune_count * (1<= 2) {return;} ts = &tunes[i]; - f = (int)rtlsdr_get_center_freq(dev); - if (f != ts->freq) { - retune(dev, ts->freq);} + fft_buf = ts->fft_buf; + regain(dev, ts->gain); + rerate(dev, ts->rate); + retune(dev, ts->freq); rtlsdr_read_sync(dev, ts->buf8, buf_len, &n_read); if (n_read != buf_len) { fprintf(stderr, "Error: dropped samples.\n");} @@ -657,7 +903,16 @@ void scanner(void) } ds = ts->downsample; ds_p = ts->downsample_passes; - if (boxcar && ds > 1) { + if (ds_p) { /* recursive */ + for (j=0; j < ds_p; j++) { + downsample_iq(fft_buf, buf_len >> j); + } + /* droop compensation */ + if (ts->comp_fir_size == 9 && ds_p <= CIC_TABLE_MAX) { + generic_fir(fft_buf, buf_len >> j, cic_9_tables[ds_p]); + generic_fir(fft_buf+1, (buf_len >> j)-1, cic_9_tables[ds_p]); + } + } else if (ds > 1) { /* boxcar */ j=2, j2=0; while (j < buf_len) { fft_buf[j2] += fft_buf[j]; @@ -668,15 +923,6 @@ void scanner(void) if (j % (ds*2) == 0) { j2 += 2;} } - } else if (ds_p) { /* recursive */ - for (j=0; j < ds_p; j++) { - downsample_iq(fft_buf, buf_len >> j); - } - /* droop compensation */ - if (comp_fir_size == 9 && ds_p <= CIC_TABLE_MAX) { - generic_fir(fft_buf, buf_len >> j, cic_9_tables[ds_p]); - generic_fir(fft_buf+1, (buf_len >> j)-1, cic_9_tables[ds_p]); - } } remove_dc(fft_buf, buf_len / ds); remove_dc(fft_buf+1, (buf_len / ds) - 1); @@ -685,23 +931,27 @@ void scanner(void) // todo, let rect skip this for (j=0; jwindow_coefs[j]); //w /= (int32_t)(ds); fft_buf[offset+j*2] = (int16_t)w; w = (int32_t)fft_buf[offset+j*2+1]; - w *= (int32_t)(window_coefs[j]); + w *= (int32_t)(ts->window_coefs[j]); //w /= (int32_t)(ds); fft_buf[offset+j*2+1] = (int16_t)w; } - fix_fft(fft_buf+offset, bin_e); - if (!peak_hold) { + fix_fft(fft_buf+offset, bin_e, ts->sine); + if (ts->peak_hold == 0) { for (j=0; javg[j] += real_conj(fft_buf[offset+j*2], fft_buf[offset+j*2+1]); } - } else { + } else if (ts->peak_hold == 1){ for (j=0; javg[j] = MAX(real_conj(fft_buf[offset+j*2], fft_buf[offset+j*2+1]), ts->avg[j]); } + } else if (ts->peak_hold == -1){ + for (j=0; javg[j] = MIN(real_conj(fft_buf[offset+j*2], fft_buf[offset+j*2+1]), ts->avg[j]); + } } ts->samples += ds; } @@ -710,11 +960,11 @@ void scanner(void) void csv_dbm(struct tuning_state *ts) { - int i, len, ds, i1, i2, bw2, bin_count; - long tmp; + int i, len; + int64_t tmp; double dbm; + char *sep = ", "; len = 1 << ts->bin_e; - ds = ts->downsample; /* fix FFT stuff quirks */ if (ts->bin_e > 0) { /* nuke DC component (not effective for all windows) */ @@ -727,75 +977,95 @@ void csv_dbm(struct tuning_state *ts) } } /* Hz low, Hz high, Hz step, samples, dbm, dbm, ... */ - bin_count = (int)((double)len * (1.0 - ts->crop)); - bw2 = (int)(((double)ts->rate * (double)bin_count) / (len * 2 * ds)); - fprintf(file, "%i, %i, %.2f, %i, ", ts->freq - bw2, ts->freq + bw2, - (double)ts->rate / (double)(len*ds), ts->samples); + fprintf(file, "%i, %i, %i, %i, ", ts->freq_low, ts->freq_high, + ts->bin_spec, ts->samples); // something seems off with the dbm math - i1 = 0 + (int)((double)len * ts->crop * 0.5); - i2 = (len-1) - (int)((double)len * ts->crop * 0.5); - for (i=i1; i<=i2; i++) { + for (i=ts->crop_i1; i<=ts->crop_i2; i++) { + if (i == ts->crop_i2) { + sep = "\n"; + } dbm = (double)ts->avg[i]; dbm /= (double)ts->rate; - dbm /= (double)ts->samples; - dbm = 10 * log10(dbm); - fprintf(file, "%.2f, ", dbm); - } - dbm = (double)ts->avg[i2] / ((double)ts->rate * (double)ts->samples); - if (ts->bin_e == 0) { - dbm = ((double)ts->avg[0] / \ - ((double)ts->rate * (double)ts->samples));} - dbm = 10 * log10(dbm); - fprintf(file, "%.2f\n", dbm); + if (ts->peak_hold == 0) { + dbm /= (double)ts->samples; + } + if (ts->linear) { + fprintf(file, "%.5g%s", dbm, sep); + } else { + dbm = 10 * log10(dbm); + fprintf(file, "%.2f%s", dbm, sep); + } + } for (i=0; iavg[i] = 0L; + if (ts->peak_hold == -1) { + ts->avg[i] = 1e6; + } else { + ts->avg[i] = 0L;} } ts->samples = 0; } +void init_misc(struct misc_settings *ms) +{ + ms->target_rate = DEFAULT_TARGET; + ms->boxcar = 1; + ms->comp_fir_size = 0; + ms->crop = 0.0; + ms->gain = AUTO_GAIN; + ms->window_fn = rectangle; + ms->smoothing = 0; + ms->peak_hold = 0; + ms->linear = 0; + ms->time_mode = VERBOSE_TIME; +} + int main(int argc, char **argv) { #ifndef _WIN32 struct sigaction sigact; #endif char *filename = NULL; - int i, length, r, opt, wb_mode = 0; + int i, r, opt; int f_set = 0; - int gain = AUTO_GAIN; // tenths of a dB int dev_index = 0; + char dev_label[255]; int dev_given = 0; int ppm_error = 0; + int custom_ppm = 0; int interval = 10; int fft_threads = 1; - int smoothing = 0; int single = 0; int direct_sampling = 0; int offset_tuning = 0; - double crop = 0.0; char *freq_optarg; time_t next_tick; time_t time_now; time_t exit_time = 0; char t_str[50]; struct tm *cal_time; - double (*window_fn)(int, int) = rectangle; + struct misc_settings ms; freq_optarg = ""; + init_misc(&ms); + strcpy(dev_label, "DEFAULT"); - while ((opt = getopt(argc, argv, "f:i:s:t:d:g:p:e:w:c:F:1PDOh")) != -1) { + while ((opt = getopt(argc, argv, "f:i:s:r:t:d:g:p:e:w:c:F:1EPTLD:Oh")) != -1) { switch (opt) { case 'f': // lower:upper:bin_size + if (f_set) { + frequency_range(freq_optarg, &ms);} freq_optarg = strdup(optarg); f_set = 1; break; case 'd': dev_index = verbose_device_search(optarg); + strncpy(dev_label, optarg, 255); dev_given = 1; break; case 'g': - gain = (int)(atof(optarg) * 10); + ms.gain = (int)(atof(optarg) * 10); break; case 'c': - crop = atofp(optarg); + ms.crop = atofp(optarg); break; case 'i': interval = (int)round(atoft(optarg)); @@ -805,49 +1075,62 @@ int main(int argc, char **argv) break; case 's': if (strcmp("avg", optarg) == 0) { - smoothing = 0;} + ms.smoothing = 0;} if (strcmp("iir", optarg) == 0) { - smoothing = 1;} + ms.smoothing = 1;} break; case 'w': if (strcmp("rectangle", optarg) == 0) { - window_fn = rectangle;} + ms.window_fn = rectangle;} if (strcmp("hamming", optarg) == 0) { - window_fn = hamming;} + ms.window_fn = hamming;} if (strcmp("blackman", optarg) == 0) { - window_fn = blackman;} + ms.window_fn = blackman;} if (strcmp("blackman-harris", optarg) == 0) { - window_fn = blackman_harris;} + ms.window_fn = blackman_harris;} if (strcmp("hann-poisson", optarg) == 0) { - window_fn = hann_poisson;} + ms.window_fn = hann_poisson;} if (strcmp("youssef", optarg) == 0) { - window_fn = youssef;} + ms.window_fn = youssef;} if (strcmp("kaiser", optarg) == 0) { - window_fn = kaiser;} + ms.window_fn = kaiser;} if (strcmp("bartlett", optarg) == 0) { - window_fn = bartlett;} + ms.window_fn = bartlett;} break; case 't': fft_threads = atoi(optarg); break; case 'p': ppm_error = atoi(optarg); + custom_ppm = 1; + break; + case 'r': + ms.target_rate = (int)atofs(optarg); break; case '1': single = 1; break; + case 'E': + ms.time_mode = EPOCH_TIME; + break; case 'P': - peak_hold = 1; + ms.peak_hold = 1; + break; + case 'T': + ms.peak_hold = -1; + break; + case 'L': + ms.linear = 1; break; case 'D': - direct_sampling = 1; + direct_sampling = atoi(optarg); break; case 'O': offset_tuning = 1; break; case 'F': - boxcar = 0; - comp_fir_size = atoi(optarg); + ms.boxcar = 0; + ms.comp_fir_size = atoi(optarg); break; case 'h': default: @@ -861,12 +1144,7 @@ int main(int argc, char **argv) exit(1); } - if ((crop < 0.0) || (crop > 1.0)) { - fprintf(stderr, "Crop value outside of 0 to 1.\n"); - exit(1); - } - - frequency_range(freq_optarg, crop); + frequency_range(freq_optarg, &ms); if (tune_count == 0) { usage();} @@ -903,12 +1181,13 @@ int main(int argc, char **argv) sigaction(SIGTERM, &sigact, NULL); sigaction(SIGQUIT, &sigact, NULL); sigaction(SIGPIPE, &sigact, NULL); + signal(SIGPIPE, SIG_IGN); #else SetConsoleCtrlHandler( (PHANDLER_ROUTINE) sighandler, TRUE ); #endif if (direct_sampling) { - verbose_direct_sampling(dev, 1); + verbose_direct_sampling(dev, direct_sampling); } if (offset_tuning) { @@ -916,13 +1195,21 @@ int main(int argc, char **argv) } /* Set the tuner gain */ - if (gain == AUTO_GAIN) { + for (i=0; itv_sec = (long)(tmp / 1000000UL); tv->tv_usec = (long)(tmp % 1000000UL); } @@ -303,23 +299,23 @@ static void *command_worker(void *arg) } switch(cmd.cmd) { case 0x01: - printf("set freq %d\n", ntohl(cmd.param)); + printf("set freq %u\n", ntohl(cmd.param)); rtlsdr_set_center_freq(dev,ntohl(cmd.param)); break; case 0x02: - printf("set sample rate %d\n", ntohl(cmd.param)); + printf("set sample rate %u\n", ntohl(cmd.param)); rtlsdr_set_sample_rate(dev, ntohl(cmd.param)); break; case 0x03: - printf("set gain mode %d\n", ntohl(cmd.param)); + printf("set gain mode %u\n", ntohl(cmd.param)); rtlsdr_set_tuner_gain_mode(dev, ntohl(cmd.param)); break; case 0x04: - printf("set gain %d\n", ntohl(cmd.param)); + printf("set gain %u\n", ntohl(cmd.param)); rtlsdr_set_tuner_gain(dev, ntohl(cmd.param)); break; case 0x05: - printf("set freq correction %d\n", ntohl(cmd.param)); + printf("set freq correction %u\n", ntohl(cmd.param)); rtlsdr_set_freq_correction(dev, ntohl(cmd.param)); break; case 0x06: @@ -328,31 +324,31 @@ static void *command_worker(void *arg) rtlsdr_set_tuner_if_gain(dev, tmp >> 16, (short)(tmp & 0xffff)); break; case 0x07: - printf("set test mode %d\n", ntohl(cmd.param)); + printf("set test mode %u\n", ntohl(cmd.param)); rtlsdr_set_testmode(dev, ntohl(cmd.param)); break; case 0x08: - printf("set agc mode %d\n", ntohl(cmd.param)); + printf("set agc mode %u\n", ntohl(cmd.param)); rtlsdr_set_agc_mode(dev, ntohl(cmd.param)); break; case 0x09: - printf("set direct sampling %d\n", ntohl(cmd.param)); + printf("set direct sampling %u\n", ntohl(cmd.param)); rtlsdr_set_direct_sampling(dev, ntohl(cmd.param)); break; case 0x0a: - printf("set offset tuning %d\n", ntohl(cmd.param)); + printf("set offset tuning %u\n", ntohl(cmd.param)); rtlsdr_set_offset_tuning(dev, ntohl(cmd.param)); break; case 0x0b: - printf("set rtl xtal %d\n", ntohl(cmd.param)); + printf("set rtl xtal %u\n", ntohl(cmd.param)); rtlsdr_set_xtal_freq(dev, ntohl(cmd.param), 0); break; case 0x0c: - printf("set tuner xtal %d\n", ntohl(cmd.param)); + printf("set tuner xtal %u\n", ntohl(cmd.param)); rtlsdr_set_xtal_freq(dev, 0, ntohl(cmd.param)); break; case 0x0d: - printf("set tuner gain by index %d\n", ntohl(cmd.param)); + printf("set tuner gain by index %u\n", ntohl(cmd.param)); set_gain_by_index(dev, ntohl(cmd.param)); break; default: @@ -374,6 +370,7 @@ int main(int argc, char **argv) int dev_given = 0; int gain = 0; int ppm_error = 0; + int custom_ppm = 0; struct llist *curelem,*prev; pthread_attr_t attr; void *status; @@ -420,6 +417,7 @@ int main(int argc, char **argv) break; case 'P': ppm_error = atoi(optarg); + custom_ppm = 1; break; default: usage(); @@ -458,6 +456,9 @@ int main(int argc, char **argv) #endif /* Set the tuner error */ + if (!custom_ppm) { + verbose_ppm_eeprom(dev, &ppm_error); + } verbose_ppm_set(dev, ppm_error); /* Set the sample rate */ diff --git a/src/rtl_test.c b/src/rtl_test.c index 9a6cfda..1f9dda1 100644 --- a/src/rtl_test.c +++ b/src/rtl_test.c @@ -53,6 +53,23 @@ #define PPM_DURATION 10 #define PPM_DUMP_TIME 5 +#define SCAN_LIMIT 2500000000 + +struct time_generic +/* holds all the platform specific values */ +{ +#ifndef _WIN32 + time_t tv_sec; + long tv_nsec; +#else + long tv_sec; + long tv_nsec; + int init; + LARGE_INTEGER frequency; + LARGE_INTEGER ticks; +#endif +}; + static enum { NO_BENCHMARK, TUNER_BENCHMARK, @@ -76,10 +93,8 @@ void usage(void) "Usage:\n" "\t[-s samplerate (default: 2048000 Hz)]\n" "\t[-d device_index (default: 0)]\n" - "\t[-t enable Elonics E4000 tuner benchmark]\n" -#ifndef _WIN32 + "\t[-t enable tuner range benchmark]\n" "\t[-p[seconds] enable PPM error measurement (default: 10 seconds)]\n" -#endif "\t[-b output_block_size (default: 16 * 16384)]\n" "\t[-S force sync output (default: async)]\n"); exit(1); @@ -134,12 +149,15 @@ static void underrun_test(unsigned char *buf, uint32_t len, int mute) } #ifndef _WIN32 -static int ppm_gettime(struct timespec *ts) +static int ppm_gettime(struct time_generic *tg) { int rv = ENOSYS; + struct timespec ts; #ifdef __unix__ - rv = clock_gettime(CLOCK_MONOTONIC, ts); + rv = clock_gettime(CLOCK_MONOTONIC, &ts); + tg->tv_sec = ts.tv_sec; + tg->tv_nsec = ts.tv_nsec; #elif __APPLE__ struct timeval tv; @@ -149,6 +167,24 @@ static int ppm_gettime(struct timespec *ts) #endif return rv; } +#endif + +#ifdef _WIN32 +static int ppm_gettime(struct time_generic *tg) +{ + int rv; + int64_t frac; + if (!tg->init) { + QueryPerformanceFrequency(&tg->frequency); + tg->init = 1; + } + rv = QueryPerformanceCounter(&tg->ticks); + tg->tv_sec = tg->ticks.QuadPart / tg->frequency.QuadPart; + frac = (int64_t)(tg->ticks.QuadPart - (tg->tv_sec * tg->frequency.QuadPart)); + tg->tv_nsec = (long)(frac * 1000000000L / (int64_t)tg->frequency.QuadPart); + return !rv; +} +#endif static int ppm_report(uint64_t nsamples, uint64_t interval) { @@ -165,8 +201,9 @@ static void ppm_test(uint32_t len) static uint64_t interval = 0; static uint64_t nsamples_total = 0; static uint64_t interval_total = 0; - struct timespec ppm_now; - static struct timespec ppm_recent; + static struct time_generic ppm_now; + static struct time_generic ppm_recent; + static enum { PPM_INIT_NO, PPM_INIT_DUMP, @@ -174,6 +211,7 @@ static void ppm_test(uint32_t len) } ppm_init = PPM_INIT_NO; ppm_gettime(&ppm_now); + if (ppm_init != PPM_INIT_RUN) { /* * Kyle Keen wrote: @@ -189,8 +227,7 @@ static void ppm_test(uint32_t len) } if (ppm_init == PPM_INIT_DUMP && ppm_recent.tv_sec < ppm_now.tv_sec) return; - ppm_recent.tv_sec = ppm_now.tv_sec; - ppm_recent.tv_nsec = ppm_now.tv_nsec; + ppm_recent = ppm_now; ppm_init = PPM_INIT_RUN; return; } @@ -200,71 +237,110 @@ static void ppm_test(uint32_t len) return; interval *= 1000000000UL; interval += (int64_t)(ppm_now.tv_nsec - ppm_recent.tv_nsec); + nsamples_total += nsamples; interval_total += interval; printf("real sample rate: %i current PPM: %i cumulative PPM: %i\n", (int)((1000000000UL * nsamples) / interval), ppm_report(nsamples, interval), ppm_report(nsamples_total, interval_total)); - ppm_recent.tv_sec = ppm_now.tv_sec; - ppm_recent.tv_nsec = ppm_now.tv_nsec; + ppm_recent = ppm_now; nsamples = 0; } -#endif static void rtlsdr_callback(unsigned char *buf, uint32_t len, void *ctx) { underrun_test(buf, len, 0); -#ifndef _WIN32 if (test_mode == PPM_BENCHMARK) ppm_test(len); -#endif } -void e4k_benchmark(void) -{ - uint32_t freq, gap_start = 0, gap_end = 0; - uint32_t range_start = 0, range_end = 0; +/* smallest band or band gap that tuner_benchmark() will notice */ +static uint32_t max_step(uint32_t freq) { + if (freq < 1e6) + return 1e4; + if (freq > 1e8) + return 1e6; + return freq / 1e2; +} - fprintf(stderr, "Benchmarking E4000 PLL...\n"); +/* precision with which tuner_benchmark() will measure the edges of bands */ +static uint32_t min_step(uint32_t freq) { + return 100; +} - /* find tuner range start */ - for (freq = MHZ(70); freq > MHZ(1); freq -= MHZ(1)) { - if (rtlsdr_set_center_freq(dev, freq) < 0) { - range_start = freq; - break; - } +int confirm_pll_lock(uint32_t f) +{ + int i; + for (i=0; i<20; i++) { + if (rtlsdr_set_center_freq(dev, f) >= 0) + return 1; } + return 0; +} - /* find tuner range end */ - for (freq = MHZ(2000); freq < MHZ(2300UL); freq += MHZ(1)) { - if (rtlsdr_set_center_freq(dev, freq) < 0) { - range_end = freq; +/* returns last frequency before achieving status of 'lock' */ +uint32_t coarse_search(uint32_t start, int lock) +{ + uint32_t f = start, f2; + int status; + while (f < SCAN_LIMIT) { + if (do_exit) break; - } + f2 = f + max_step(f); + status = rtlsdr_set_center_freq(dev, f2) >= 0; + if (!lock && !status) + status = confirm_pll_lock(f2); + if (status == lock) + return f; + f = f2; } + return SCAN_LIMIT + 1; +} - /* find start of L-band gap */ - for (freq = MHZ(1000); freq < MHZ(1300); freq += MHZ(1)) { - if (rtlsdr_set_center_freq(dev, freq) < 0) { - gap_start = freq; +/* returns frequency of a transition + * must have one transition between start and start+step */ +uint32_t fine_search(uint32_t start, uint32_t step) +{ + int low_status, mid_status, high_status; + uint32_t f, stop; + stop = start + step; + f = start + step / 2; + low_status = rtlsdr_set_center_freq(dev, start) >= 0; + high_status = rtlsdr_set_center_freq(dev, stop) >= 0; + if (low_status == high_status) + return start; + while (step > min_step(start)) { + if (do_exit) break; - } + mid_status = rtlsdr_set_center_freq(dev, f) >= 0; + if (low_status == mid_status) + start = f; + else + stop = f; + step = stop - start; + f = start + step / 2; } + return f; +} - /* find end of L-band gap */ - for (freq = MHZ(1300); freq > MHZ(1000); freq -= MHZ(1)) { - if (rtlsdr_set_center_freq(dev, freq) < 0) { - gap_end = freq; +void tuner_benchmark(void) +{ + uint32_t f = 0, low_bound, high_bound; + fprintf(stderr, "Testing tuner range. This may take a couple of minutes...\n"); + while (!do_exit) { + /* find start of a band */ + f = coarse_search(f, 1); + low_bound = fine_search(f, max_step(f)); + f += max_step(f); + /* find stop of a band */ + f = coarse_search(f, 0); + high_bound = fine_search(f, max_step(f)); + f += max_step(f); + if (f > SCAN_LIMIT) break; - } + fprintf(stderr, "Band: %u - %u Hz\n", low_bound, high_bound); } - - fprintf(stderr, "E4K range: %i to %i MHz\n", - range_start/MHZ(1) + 1, range_end/MHZ(1) - 1); - - fprintf(stderr, "E4K L-band gap: %i to %i MHz\n", - gap_start/MHZ(1), gap_end/MHZ(1)); } int main(int argc, char **argv) @@ -360,11 +436,7 @@ int main(int argc, char **argv) verbose_set_sample_rate(dev, samp_rate); if (test_mode == TUNER_BENCHMARK) { - if (rtlsdr_get_tuner_type(dev) == RTLSDR_TUNER_E4000) - e4k_benchmark(); - else - fprintf(stderr, "No E4000 tuner found, aborting.\n"); - + tuner_benchmark(); goto exit; } @@ -375,7 +447,7 @@ int main(int argc, char **argv) verbose_reset_buffer(dev); if ((test_mode == PPM_BENCHMARK) && !sync_mode) { - fprintf(stderr, "Reporting PPM error measurement every %i seconds...\n", ppm_duration); + fprintf(stderr, "Reporting PPM error measurement every %u seconds...\n", ppm_duration); fprintf(stderr, "Press ^C after a few minutes.\n"); } diff --git a/src/tuner_e4k.c b/src/tuner_e4k.c index c2ec044..0f2d265 100644 --- a/src/tuner_e4k.c +++ b/src/tuner_e4k.c @@ -443,7 +443,7 @@ static uint32_t compute_flo(uint32_t f_osc, uint8_t z, uint16_t x, uint8_t r) if (fvco == 0) return -EINVAL; - return fvco / r; + return (uint32_t)(fvco / r); } static int e4k_band_set(struct e4k_state *e4k, enum e4k_band band) @@ -483,10 +483,10 @@ uint32_t e4k_compute_pll_params(struct e4k_pll_params *oscp, uint32_t fosc, uint uint32_t i; uint8_t r = 2; uint64_t intended_fvco, remainder; - uint64_t z = 0; + uint8_t z = 0; uint32_t x; - int flo; - int three_phase_mixing = 0; + uint32_t flo; + uint8_t three_phase_mixing = 0; oscp->r_idx = 0; if (!is_fosc_valid(fosc)) @@ -507,13 +507,13 @@ uint32_t e4k_compute_pll_params(struct e4k_pll_params *oscp, uint32_t fosc, uint intended_fvco = (uint64_t)intended_flo * r; /* compute integral component of multiplier */ - z = intended_fvco / fosc; + z = (uint8_t)(intended_fvco / fosc); /* compute fractional part. this will not overflow, * as fosc(max) = 30MHz and z(max) = 255 */ remainder = intended_fvco - (fosc * z); /* remainder(max) = 30MHz, E4K_PLL_Y = 65536 -> 64bit! */ - x = (remainder * E4K_PLL_Y) / fosc; + x = (uint32_t)((remainder * E4K_PLL_Y) / fosc); /* x(max) as result of this computation is 65536 */ flo = compute_flo(fosc, z, x, r); diff --git a/src/tuner_r82xx.c b/src/tuner_r82xx.c index e03e034..5c5c603 100644 --- a/src/tuner_r82xx.c +++ b/src/tuner_r82xx.c @@ -286,11 +286,6 @@ static int r82xx_write(struct r82xx_priv *priv, uint8_t reg, const uint8_t *val, return 0; } -static int r82xx_write_reg(struct r82xx_priv *priv, uint8_t reg, uint8_t val) -{ - return r82xx_write(priv, reg, &val, 1); -} - static int r82xx_read_cache_reg(struct r82xx_priv *priv, int reg) { reg -= REG_SHADOW_START; @@ -301,6 +296,21 @@ static int r82xx_read_cache_reg(struct r82xx_priv *priv, int reg) return -1; } +static int r82xx_write_reg(struct r82xx_priv *priv, uint8_t reg, uint8_t val) +{ + if (priv->reg_cache && r82xx_read_cache_reg(priv, reg) == val) + return 0; + if (priv->reg_batch) { + shadow_store(priv, reg, &val, 1); + if (reg < priv->reg_low) + priv->reg_low = reg; + if (reg > priv->reg_high) + priv->reg_high = reg; + return 0; + } + return r82xx_write(priv, reg, &val, 1); +} + static int r82xx_write_reg_mask(struct r82xx_priv *priv, uint8_t reg, uint8_t val, uint8_t bit_mask) { @@ -311,7 +321,34 @@ static int r82xx_write_reg_mask(struct r82xx_priv *priv, uint8_t reg, uint8_t va val = (rc & ~bit_mask) | (val & bit_mask); - return r82xx_write(priv, reg, &val, 1); + return r82xx_write_reg(priv, reg, val); +} + +static int r82xx_write_batch_init(struct r82xx_priv *priv) +{ + priv->reg_batch = 0; + if (priv->reg_cache) { + priv->reg_batch = 1; + priv->reg_low = NUM_REGS; + priv->reg_high = 0; + } + return 0; +} + +static int r82xx_write_batch_sync(struct r82xx_priv *priv) +{ + int rc, offset, len; + if (!priv->reg_cache) + return -1; + if (!priv->reg_batch) + return -1; + priv->reg_batch = 0; + if (priv->reg_low > priv->reg_high) + return 0; /* No registers were changed */ + offset = priv->reg_low - REG_SHADOW_START; + len = priv->reg_high - priv->reg_low + 1; + rc = r82xx_write(priv, priv->reg_low, priv->regs+offset, len); + return rc; } static uint8_t r82xx_bitrev(uint8_t byte) @@ -419,12 +456,11 @@ static int r82xx_set_pll(struct r82xx_priv *priv, uint32_t freq) int rc, i; unsigned sleep_time = 10000; uint64_t vco_freq; - uint32_t vco_fra; /* VCO contribution by SDM (kHz) */ - uint32_t vco_min = 1770000; - uint32_t vco_max = vco_min * 2; - uint32_t freq_khz, pll_ref, pll_ref_khz; - uint16_t n_sdm = 2; - uint16_t sdm = 0; + uint64_t vco_div; + uint32_t vco_min = 1770000; /* kHz */ + uint32_t vco_max = vco_min * 2; /* kHz */ + uint32_t freq_khz, pll_ref; + uint32_t sdm = 0; uint8_t mix_div = 2; uint8_t div_buf = 0; uint8_t div_num = 0; @@ -433,10 +469,11 @@ static int r82xx_set_pll(struct r82xx_priv *priv, uint32_t freq) uint8_t ni, si, nint, vco_fine_tune, val; uint8_t data[5]; + r82xx_write_batch_init(priv); + /* Frequency in kHz */ freq_khz = (freq + 500) / 1000; pll_ref = priv->cfg->xtal; - pll_ref_khz = (priv->cfg->xtal + 500) / 1000; rc = r82xx_write_reg_mask(priv, 0x10, refdiv2, 0x10); if (rc < 0) @@ -453,6 +490,8 @@ static int r82xx_set_pll(struct r82xx_priv *priv, uint32_t freq) return rc; /* Calculate divider */ + if(freq_khz < vco_min/64) vco_min /= 2; + if(freq_khz >= vco_max/2) vco_max *= 2; while (mix_div <= 64) { if (((freq_khz * mix_div) >= vco_min) && ((freq_khz * mix_div) < vco_max)) { @@ -466,14 +505,21 @@ static int r82xx_set_pll(struct r82xx_priv *priv, uint32_t freq) mix_div = mix_div << 1; } - rc = r82xx_read(priv, 0x00, data, sizeof(data)); - if (rc < 0) - return rc; + if (mix_div > 64) { + fprintf(stderr, "[R82XX] No valid PLL values for %u Hz!\n", freq); + return -1; + } if (priv->cfg->rafael_chip == CHIP_R828D) vco_power_ref = 1; + /* + rc = r82xx_read(priv, 0x00, data, sizeof(data)); + if (rc < 0) + return rc; vco_fine_tune = (data[4] & 0x30) >> 4; + */ + vco_fine_tune = 2; if (vco_fine_tune > vco_power_ref) div_num = div_num - 1; @@ -485,10 +531,25 @@ static int r82xx_set_pll(struct r82xx_priv *priv, uint32_t freq) return rc; vco_freq = (uint64_t)freq * (uint64_t)mix_div; - nint = vco_freq / (2 * pll_ref); - vco_fra = (vco_freq - 2 * pll_ref * nint) / 1000; - if (nint > ((128 / vco_power_ref) - 1)) { + /* + * We want to approximate: + * vco_freq / (2 * pll_ref) + * in the form: + * nint + sdm/65536 + * where nint,sdm are integers and 0 < nint, 0 <= sdm < 65536 + * Scaling to fixed point and rounding: + * vco_div = 65536*(nint + sdm/65536) = int( 0.5 + 65536 * vco_freq / (2 * pll_ref) ) + * vco_div = 65536*nint + sdm = int( (pll_ref + 65536 * vco_freq) / (2 * pll_ref) ) + */ + + vco_div = (pll_ref + 65536 * vco_freq) / (2 * pll_ref); + nint = (uint32_t) (vco_div / 65536); + sdm = (uint32_t) (vco_div % 65536); + + if (nint < 13 || + (priv->cfg->rafael_chip == CHIP_R828D && nint > 127) || + (priv->cfg->rafael_chip != CHIP_R828D && nint > 76)) { fprintf(stderr, "[R82XX] No valid PLL values for %u Hz!\n", freq); return -1; } @@ -501,25 +562,19 @@ static int r82xx_set_pll(struct r82xx_priv *priv, uint32_t freq) return rc; /* pw_sdm */ - if (!vco_fra) + if (sdm == 0) val = 0x08; else val = 0x00; - rc = r82xx_write_reg_mask(priv, 0x12, val, 0x08); + if (priv->disable_dither) + val |= 0x10; + + rc = r82xx_write_reg_mask(priv, 0x12, val, 0x18); if (rc < 0) return rc; - /* sdm calculator */ - while (vco_fra > 1) { - if (vco_fra > (2 * pll_ref_khz / n_sdm)) { - sdm = sdm + 32768 / (n_sdm / 2); - vco_fra = vco_fra - 2 * pll_ref_khz / n_sdm; - if (n_sdm >= 0x8000) - break; - } - n_sdm <<= 1; - } + //fprintf(stderr, "LO: %u kHz, MixDiv: %u, PLLDiv: %u, VCO %u kHz, SDM: %u \n", (uint32_t)(freq/1000), mix_div, nint, (uint32_t)(vco_freq/1000), sdm); rc = r82xx_write_reg(priv, 0x16, sdm >> 8); if (rc < 0) @@ -528,32 +583,38 @@ static int r82xx_set_pll(struct r82xx_priv *priv, uint32_t freq) if (rc < 0) return rc; + if (priv->reg_batch) { + rc = r82xx_write_batch_sync(priv); + if (rc < 0) { + fprintf(stderr, "[R82XX] Batch error in PLL for %u Hz!\n", freq); + return rc; + } + } + for (i = 0; i < 2; i++) { // usleep_range(sleep_time, sleep_time + 1000); /* Check if PLL has locked */ + data[2] = 0; rc = r82xx_read(priv, 0x00, data, 3); if (rc < 0) return rc; if (data[2] & 0x40) break; + if (i > 0) + break; - if (!i) { - /* Didn't lock. Increase VCO current */ - rc = r82xx_write_reg_mask(priv, 0x12, 0x60, 0xe0); - if (rc < 0) - return rc; - } + /* Didn't lock. Increase VCO current */ + rc = r82xx_write_reg_mask(priv, 0x12, 0x60, 0xe0); + if (rc < 0) + return rc; } if (!(data[2] & 0x40)) { fprintf(stderr, "[R82XX] PLL not locked!\n"); - priv->has_lock = 0; - return 0; + return -1; } - priv->has_lock = 1; - /* set pll autotune = 8kHz */ rc = r82xx_write_reg_mask(priv, 0x1a, 0x08, 0x08); @@ -757,91 +818,28 @@ static int r82xx_sysfreq_sel(struct r82xx_priv *priv, uint32_t freq, return 0; } -static int r82xx_set_tv_standard(struct r82xx_priv *priv, +static int r82xx_init_tv_standard(struct r82xx_priv *priv, unsigned bw, enum r82xx_tuner_type type, uint32_t delsys) { - int rc, i; + /* everything that was previously done in r82xx_set_tv_standard + * and doesn't need to be changed when filter settings change */ + int rc; uint32_t if_khz, filt_cal_lo; - uint8_t data[5]; - uint8_t filt_gain, img_r, filt_q, hp_cor, ext_enable, loop_through; + uint8_t filt_gain, img_r, ext_enable, loop_through; uint8_t lt_att, flt_ext_widest, polyfil_cur; - int need_calibration; - - if (delsys == SYS_ISDBT) { - if_khz = 4063; - filt_cal_lo = 59000; - filt_gain = 0x10; /* +3db, 6mhz on */ - img_r = 0x00; /* image negative */ - filt_q = 0x10; /* r10[4]:low q(1'b1) */ - hp_cor = 0x6a; /* 1.7m disable, +2cap, 1.25mhz */ - ext_enable = 0x40; /* r30[6], ext enable; r30[5]:0 ext at lna max */ - loop_through = 0x00; /* r5[7], lt on */ - lt_att = 0x00; /* r31[7], lt att enable */ - flt_ext_widest = 0x00; /* r15[7]: flt_ext_wide off */ - polyfil_cur = 0x60; /* r25[6:5]:min */ - } else { - if (bw <= 6) { - if_khz = 3570; - filt_cal_lo = 56000; /* 52000->56000 */ - filt_gain = 0x10; /* +3db, 6mhz on */ - img_r = 0x00; /* image negative */ - filt_q = 0x10; /* r10[4]:low q(1'b1) */ - hp_cor = 0x6b; /* 1.7m disable, +2cap, 1.0mhz */ - ext_enable = 0x60; /* r30[6]=1 ext enable; r30[5]:1 ext at lna max-1 */ - loop_through = 0x00; /* r5[7], lt on */ - lt_att = 0x00; /* r31[7], lt att enable */ - flt_ext_widest = 0x00; /* r15[7]: flt_ext_wide off */ - polyfil_cur = 0x60; /* r25[6:5]:min */ - } else if (bw == 7) { -#if 0 - /* - * There are two 7 MHz tables defined on the original - * driver, but just the second one seems to be visible - * by rtl2832. Keep this one here commented, as it - * might be needed in the future - */ - - if_khz = 4070; - filt_cal_lo = 60000; - filt_gain = 0x10; /* +3db, 6mhz on */ - img_r = 0x00; /* image negative */ - filt_q = 0x10; /* r10[4]:low q(1'b1) */ - hp_cor = 0x2b; /* 1.7m disable, +1cap, 1.0mhz */ - ext_enable = 0x60; /* r30[6]=1 ext enable; r30[5]:1 ext at lna max-1 */ - loop_through = 0x00; /* r5[7], lt on */ - lt_att = 0x00; /* r31[7], lt att enable */ - flt_ext_widest = 0x00; /* r15[7]: flt_ext_wide off */ - polyfil_cur = 0x60; /* r25[6:5]:min */ -#endif - /* 7 MHz, second table */ - if_khz = 4570; - filt_cal_lo = 63000; - filt_gain = 0x10; /* +3db, 6mhz on */ - img_r = 0x00; /* image negative */ - filt_q = 0x10; /* r10[4]:low q(1'b1) */ - hp_cor = 0x2a; /* 1.7m disable, +1cap, 1.25mhz */ - ext_enable = 0x60; /* r30[6]=1 ext enable; r30[5]:1 ext at lna max-1 */ - loop_through = 0x00; /* r5[7], lt on */ - lt_att = 0x00; /* r31[7], lt att enable */ - flt_ext_widest = 0x00; /* r15[7]: flt_ext_wide off */ - polyfil_cur = 0x60; /* r25[6:5]:min */ - } else { - if_khz = 4570; - filt_cal_lo = 68500; - filt_gain = 0x10; /* +3db, 6mhz on */ - img_r = 0x00; /* image negative */ - filt_q = 0x10; /* r10[4]:low q(1'b1) */ - hp_cor = 0x0b; /* 1.7m disable, +0cap, 1.0mhz */ - ext_enable = 0x60; /* r30[6]=1 ext enable; r30[5]:1 ext at lna max-1 */ - loop_through = 0x00; /* r5[7], lt on */ - lt_att = 0x00; /* r31[7], lt att enable */ - flt_ext_widest = 0x00; /* r15[7]: flt_ext_wide off */ - polyfil_cur = 0x60; /* r25[6:5]:min */ - } - } + + if_khz = R82XX_DEFAULT_IF_FREQ/1000; + filt_cal_lo = 56000; /* 52000->56000 */ + filt_gain = 0x10; /* +3db, 6mhz on */ + img_r = 0x00; /* image negative */ + ext_enable = 0x60; /* r30[6]=1 ext enable; r30[5]:1 ext at lna max-1 */ + loop_through = 0x00; /* r5[7], lt on */ + lt_att = 0x00; /* r31[7], lt att enable */ + flt_ext_widest = 0x00; /* r15[7]: flt_ext_wide off */ + polyfil_cur = 0x60; /* r25[6:5]:min */ /* Initialize the shadow registers */ memcpy(priv->regs, r82xx_init_array, sizeof(r82xx_init_array)); @@ -865,72 +863,6 @@ static int r82xx_set_tv_standard(struct r82xx_priv *priv, } priv->int_freq = if_khz * 1000; - /* Check if standard changed. If so, filter calibration is needed */ - /* as we call this function only once in rtlsdr, force calibration */ - need_calibration = 1; - - if (need_calibration) { - for (i = 0; i < 2; i++) { - /* Set filt_cap */ - rc = r82xx_write_reg_mask(priv, 0x0b, hp_cor, 0x60); - if (rc < 0) - return rc; - - /* set cali clk =on */ - rc = r82xx_write_reg_mask(priv, 0x0f, 0x04, 0x04); - if (rc < 0) - return rc; - - /* X'tal cap 0pF for PLL */ - rc = r82xx_write_reg_mask(priv, 0x10, 0x00, 0x03); - if (rc < 0) - return rc; - - rc = r82xx_set_pll(priv, filt_cal_lo * 1000); - if (rc < 0 || !priv->has_lock) - return rc; - - /* Start Trigger */ - rc = r82xx_write_reg_mask(priv, 0x0b, 0x10, 0x10); - if (rc < 0) - return rc; - -// usleep_range(1000, 2000); - - /* Stop Trigger */ - rc = r82xx_write_reg_mask(priv, 0x0b, 0x00, 0x10); - if (rc < 0) - return rc; - - /* set cali clk =off */ - rc = r82xx_write_reg_mask(priv, 0x0f, 0x00, 0x04); - if (rc < 0) - return rc; - - /* Check if calibration worked */ - rc = r82xx_read(priv, 0x00, data, sizeof(data)); - if (rc < 0) - return rc; - - priv->fil_cal_code = data[4] & 0x0f; - if (priv->fil_cal_code && priv->fil_cal_code != 0x0f) - break; - } - /* narrowest */ - if (priv->fil_cal_code == 0x0f) - priv->fil_cal_code = 0; - } - - rc = r82xx_write_reg_mask(priv, 0x0a, - filt_q | priv->fil_cal_code, 0x1f); - if (rc < 0) - return rc; - - /* Set BW, Filter_gain, & HP corner */ - rc = r82xx_write_reg_mask(priv, 0x0b, hp_cor, 0xef); - if (rc < 0) - return rc; - /* Set Img_R */ rc = r82xx_write_reg_mask(priv, 0x07, img_r, 0x80); if (rc < 0) @@ -969,11 +901,79 @@ static int r82xx_set_tv_standard(struct r82xx_priv *priv, /* Store current standard. If it changes, re-calibrate the tuner */ priv->delsys = delsys; priv->type = type; - priv->bw = bw; return 0; } +static int r82xx_set_if_filter(struct r82xx_priv *priv, int hpf, int lpf) { + int rc; + uint8_t filt_q, hp_cor; + int cal; + filt_q = 0x10; /* r10[4]:low q(1'b1) */ + + if(lpf <= 2500) { + hp_cor = 0xE0; /* 1.7m enable, +2cap */ + cal = 16*(2500-lpf) / (2500-2000); + } else if(lpf <= 3100) { + hp_cor = 0xA0; /* 1.7m enable, +1cap */ + cal = 16*(3100-lpf) / (3100-2200); + } else if(lpf <= 3900) { + hp_cor = 0x80; /* 1.7m enable, +0cap */ + cal = 16*(3900-lpf) / (3900-2600); + } else if(lpf <= 7100) { + hp_cor = 0x60; /* 1.7m disable, +2cap */ + cal = 16*(7100-lpf) / (7100-5300); + } else if(lpf <= 8700) { + hp_cor = 0x20; /* 1.7m disable, +1cap */ + cal = 16*(8700-lpf) / (8700-6300); + } else { + hp_cor = 0x00; /* 1.7m disable, +0cap */ + cal = 16*(11000-lpf) / (11000-7500); + } + + if(hpf >= 4700) hp_cor |= 0x00; /* 5 MHz */ + else if(hpf >= 3800) hp_cor |= 0x01; /* 4 MHz */ + else if(hpf >= 3000) hp_cor |= 0x02; /* -12dB @ 2.25 MHz */ + else if(hpf >= 2800) hp_cor |= 0x03; /* -8dB @ 2.25 MHz */ + else if(hpf >= 2600) hp_cor |= 0x04; /* -4dB @ 2.25 MHz */ + else if(hpf >= 2400) hp_cor |= 0x05; /* -12dB @ 1.75 MHz */ + else if(hpf >= 2200) hp_cor |= 0x06; /* -8dB @ 1.75 MHz */ + else if(hpf >= 2000) hp_cor |= 0x07; /* -4dB @ 1.75 MHz */ + else if(hpf >= 1800) hp_cor |= 0x08; /* -12dB @ 1.25 MHz */ + else if(hpf >= 1600) hp_cor |= 0x09; /* -8dB @ 1.25 MHz */ + else if(hpf >= 1400) hp_cor |= 0x0A; /* -4dB @ 1.25 MHz */ + else hp_cor |= 0x0B; + + + if(cal < 0) cal = 0; + else if(cal > 15) cal = 15; + priv->fil_cal_code = cal; + + //fprintf(stderr, "Setting IF filter for %d...%d kHz: hp_cor=0x%02x, fil_cal_code=%d\n", hpf, lpf, hp_cor, cal); + + rc = r82xx_write_reg_mask(priv, 0x0a, + filt_q | priv->fil_cal_code, 0x1f); + if (rc < 0) + return rc; + + /* Set BW, Filter_gain, & HP corner */ + rc = r82xx_write_reg_mask(priv, 0x0b, hp_cor, 0xef); + if (rc < 0) + return rc; + + return 0; +} + +int r82xx_set_bw(struct r82xx_priv *priv, uint32_t bw) { + priv->bw = bw; + return r82xx_set_if_filter(priv, ((int)priv->int_freq - (int)bw/2)/1000, ((int)priv->int_freq + (int)bw/2)/1000); +} + +int r82xx_set_if_freq(struct r82xx_priv *priv, uint32_t freq) { + priv->int_freq = freq; + return r82xx_set_if_filter(priv, ((int)freq - (int)priv->bw/2)/1000, ((int)freq + (int)priv->bw/2)/1000); +} + static int r82xx_read_gain(struct r82xx_priv *priv) { uint8_t data[4]; @@ -1079,12 +1079,14 @@ int r82xx_set_freq(struct r82xx_priv *priv, uint32_t freq) uint32_t lo_freq = freq + priv->int_freq; uint8_t air_cable1_in; + r82xx_write_batch_init(priv); + rc = r82xx_set_mux(priv, lo_freq); if (rc < 0) goto err; rc = r82xx_set_pll(priv, lo_freq); - if (rc < 0 || !priv->has_lock) + if (rc < 0) goto err; /* switch between 'Cable1' and 'Air-In' inputs on sticks with @@ -1099,12 +1101,50 @@ int r82xx_set_freq(struct r82xx_priv *priv, uint32_t freq) rc = r82xx_write_reg_mask(priv, 0x05, air_cable1_in, 0x60); } + if (priv->reg_batch) { + rc = r82xx_write_batch_sync(priv); + } err: if (rc < 0) fprintf(stderr, "%s: failed=%d\n", __FUNCTION__, rc); return rc; } +int r82xx_set_nomod(struct r82xx_priv *priv) +{ + int rc = -1; + + fprintf(stderr, "Using R820T no-mod direct sampling mode\n"); + + /* should probably play a bit more with the mux settings + to see if something works even better than this */ + + rc = r82xx_set_mux(priv, 300000000); + if (rc < 0) goto err; + + /* the VCO frequency setting still seems to have some effect on the noise floor */ + rc = r82xx_set_pll(priv, 50000000); + if (rc < 0) goto err; + + /* the most important part: set a divider number that does not really work */ + rc = r82xx_write_reg_mask(priv, 0x10, 0xd0, 0xe0); + if (rc < 0) goto err; + + /* VCO power off */ + rc = r82xx_write_reg_mask(priv, 0x12, 0xe0, 0xe0); + +err: + if (rc < 0) + fprintf(stderr, "%s: failed=%d\n", __FUNCTION__, rc); + return rc; +} + +int r82xx_set_dither(struct r82xx_priv *priv, int dither) +{ + priv->disable_dither = !dither; + return 0; +} + /* * r82xx standby logic */ @@ -1117,6 +1157,7 @@ int r82xx_standby(struct r82xx_priv *priv) if (!priv->init_done) return 0; + priv->reg_cache = 0; rc = r82xx_write_reg(priv, 0x06, 0xb1); if (rc < 0) return rc; @@ -1152,6 +1193,7 @@ int r82xx_standby(struct r82xx_priv *priv) /* Force initial calibration */ priv->type = -1; + priv->reg_cache = 1; return rc; } @@ -1226,16 +1268,21 @@ int r82xx_init(struct r82xx_priv *priv) priv->xtal_cap_sel = XTAL_HIGH_CAP_0P; /* Initialize registers */ + priv->reg_cache = 0; rc = r82xx_write(priv, 0x05, r82xx_init_array, sizeof(r82xx_init_array)); - rc = r82xx_set_tv_standard(priv, 3, TUNER_DIGITAL_TV, 0); - if (rc < 0) - goto err; + rc |= r82xx_init_tv_standard(priv, 3, TUNER_DIGITAL_TV, 0); + + priv->bw = R82XX_DEFAULT_IF_BW; + priv->int_freq = R82XX_DEFAULT_IF_FREQ; + /* r82xx_set_bw will always be called by rtlsdr_set_sample_rate, + so there's no need to call r82xx_set_if_filter here */ - rc = r82xx_sysfreq_sel(priv, 0, TUNER_DIGITAL_TV, SYS_DVBT); + rc |= r82xx_sysfreq_sel(priv, 0, TUNER_DIGITAL_TV, SYS_DVBT); priv->init_done = 1; + priv->reg_cache = 1; err: if (rc < 0)