diff --git a/include/tuner_r82xx.h b/include/tuner_r82xx.h index a67a208..3781662 100755 --- a/include/tuner_r82xx.h +++ b/include/tuner_r82xx.h @@ -68,6 +68,9 @@ enum r82xx_xtal_cap_value { struct r82xx_config { uint8_t i2c_addr; + uint8_t vco_curr_min; /* VCO min/max current for R18/0x12 bits [7:5] in 0 .. 7. use 0xff for default */ + uint8_t vco_curr_max; /* value is inverted: programmed is 7-value, that 0 is lowest current */ + uint8_t vco_algo; uint32_t xtal; enum r82xx_chip rafael_chip; unsigned int max_i2c_msg_len; @@ -89,6 +92,7 @@ struct r82xx_priv { * on which the band center shall be positioned */ uint8_t fil_cal_code; uint8_t input; + uint8_t last_vco_curr; int has_lock; int init_done; int sideband; diff --git a/src/librtlsdr.c b/src/librtlsdr.c index c176f6a..dee9805 100644 --- a/src/librtlsdr.c +++ b/src/librtlsdr.c @@ -99,6 +99,8 @@ #define LOG_API_SET_FREQ 0 #define INIT_R820T_TUNER_GAIN 0 +#define ENABLE_VCO_OPTIONS 1 + /* activate/use RTL's IF AGC control .. from https://github.com/old-dab/rtlsdr * purpose: make AGC more smooth .. and NOT freeze @@ -379,6 +381,10 @@ int r820t_init(void *dev) { devt->r82xx_c.rafael_chip = CHIP_R820T; } + devt->r82xx_c.vco_curr_min = 0xff; /* VCO min/max current for R18/0x12 bits [7:5] in 0 .. 7. use 0xff for default */ + devt->r82xx_c.vco_curr_max = 0xff; /* value is inverted: programmed is 7-value, that 0 is lowest current */ + devt->r82xx_c.vco_algo = 0x00; + rtlsdr_get_xtal_freq(devt, NULL, &devt->r82xx_c.xtal); devt->r82xx_c.max_i2c_msg_len = 8; @@ -4184,6 +4190,11 @@ const char * rtlsdr_get_opt_help(int longInfo) "\t\t 0: use I & Q; 1: use I; 2: use Q; 3: use I below threshold frequency;\n" "\t\t 4: use Q below threshold frequency (=RTL-SDR v3)\n" "\t\t other values set the threshold frequency\n" +#if ENABLE_VCO_OPTIONS + "\t\tvcocmin= set R820T/2 VCO current min: 0..7: higher value is more current\n" + "\t\tvcocmax= set R820T/2 VCO current max: 0..7\n" + "\t\tvcoalgo= set R820T/2 VCO algorithm. 0: default. 1: with vcomax=3.9G. 2: Youssef/Carl\n" +#endif "\t\tTp= set GPIO pin for Bias T, default =0 for rtl-sdr.com compatible V3\n" "\t\tT= 1 activates power at antenna one some dongles, e.g. rtl-sdr.com's V3\n" #ifdef WITH_UDP_SERVER @@ -4196,7 +4207,12 @@ const char * rtlsdr_get_opt_help(int longInfo) "\t[-O\tset RTL options string seperated with ':', e.g. -O 'bc=30000:agc=0' ]\n" "\t\tverbose:f=:bw=:bc=:sb=\n" "\t\tagc=:gain=:ifm=:dagc=\n" +#if ENABLE_VCO_OPTIONS + "\t\tds=:dm=:vcocmin=:vcocmax=:vcoalgo=\n" + "\t\tT=\n" +#else "\t\tds=:dm=:T=\n" +#endif #ifdef WITH_UDP_SERVER "\t\tport=\n" #endif @@ -4314,6 +4330,47 @@ int rtlsdr_set_opt_string(rtlsdr_dev_t *dev, const char *opts, int verbose) dev->direct_sampling_threshold = dm; ret = rtlsdr_set_ds_mode(dev, dev->direct_sampling_mode, dev->direct_sampling_threshold); } +#if ENABLE_VCO_OPTIONS + else if (!strncmp(optPart, "vcocmin=", 8)) { + int current = atoi(optPart +8); + if ( 0 <= current && current <= 7 ) + { + dev->r82xx_c.vco_curr_min = 7 - current; + ret = 0; + if (verbose) + fprintf(stderr, "\nrtlsdr_set_opt_string(): parsed vcocmin config %d\n", current); + } else if (verbose) { + fprintf(stderr, "\nrtlsdr_set_opt_string(): error parsing vcocmin config: valid range 0 .. 7\n"); + ret = 1; + } + } + else if (!strncmp(optPart, "vcocmax=", 8)) { + int current = atoi(optPart +8); + if ( 0 <= current && current <= 7 ) + { + dev->r82xx_c.vco_curr_max = 7 - current; + ret = 0; + if (verbose) + fprintf(stderr, "\nrtlsdr_set_opt_string(): parsed vcocmax config %d\n", current); + } else if (verbose) { + fprintf(stderr, "\nrtlsdr_set_opt_string(): error parsing vcocmax config: valid range 0 .. 7\n"); + ret = 1; + } + } + else if (!strncmp(optPart, "vcoalgo=", 8)) { + int algo = atoi(optPart +8); + if ( 0 <= algo && algo <= 2 ) + { + dev->r82xx_c.vco_curr_max = algo; + ret = 0; + if (verbose) + fprintf(stderr, "\nrtlsdr_set_opt_string(): parsed vcoalgo config %d\n", algo); + } else if (verbose) { + fprintf(stderr, "\nrtlsdr_set_opt_string(): error parsing vcoalgo config: valid range 0 .. 2\n"); + ret = 1; + } + } +#endif else if (!strncmp(optPart, "tp=", 3) || !strncmp(optPart, "Tp=", 3) || !strncmp(optPart, "TP=", 3) ) { int gpio_pin_no = atoi(optPart +3); if (verbose) diff --git a/src/rtl_test.c b/src/rtl_test.c index 3b4a890..0b378d9 100644 --- a/src/rtl_test.c +++ b/src/rtl_test.c @@ -107,6 +107,8 @@ void usage(void) "\t[-d device_index or serial (default: 0)]\n" "%s" "\t[-t enable tuner range benchmark]\n" + "\t[-f first/begin frequency for tuner range benchmark, default: 0]\n" + "\t[-e end frequency for tuner range benchmark, default: 3e9 = 3G ]\n" #ifndef _WIN32 "\t[-p[seconds] enable PPM error measurement (default: 10 seconds)]\n" #endif @@ -338,9 +340,9 @@ static int set_center_freq_wait(rtlsdr_dev_t *dev, uint32_t freq, const char * s } -void tuner_benchmark(void) +void tuner_benchmark(uint32_t beg_freq, uint32_t end_freq) { - uint32_t current = max_step(0); + uint32_t current = beg_freq; /* max_step(0); */ uint32_t band_start = 0; uint32_t low_bound = 0, high_bound = 0; int rc; @@ -357,16 +359,16 @@ void tuner_benchmark(void) */ /* handle bands starting at 0Hz */ - rc = set_center_freq_wait(dev, 0, "FIND_START"); + rc = set_center_freq_wait(dev, current, "FIND_START"); if (rc < 0) state = FIND_START; else { - band_start = 0; + band_start = current; report_band_start(band_start); state = FIND_END; } - while (current < 3e9 && !do_exit) { + while (current < end_freq && !do_exit) { switch (state) { case FIND_START: /* scanning for the start of a new band */ @@ -508,10 +510,12 @@ int main(int argc, char **argv) int dev_index = 0; int dev_given = 0; uint32_t out_block_size = DEFAULT_BUF_LENGTH; + uint32_t tuner_bench_beg_freq = 0; + uint32_t tuner_bench_end_freq = 0; int count; int gains[100]; - while ((opt = getopt(argc, argv, "d:s:b:O:tp::Sh")) != -1) { + while ((opt = getopt(argc, argv, "d:s:b:O:tf:e:p::Sh")) != -1) { switch (opt) { case 'd': dev_index = verbose_device_search(optarg); @@ -529,6 +533,12 @@ int main(int argc, char **argv) case 't': test_mode = TUNER_BENCHMARK; break; + case 'f': + tuner_bench_beg_freq = (uint32_t)atofs(optarg); + break; + case 'e': + tuner_bench_end_freq = (uint32_t)atofs(optarg); + break; case 'p': test_mode = PPM_BENCHMARK; if (optarg) @@ -598,7 +608,7 @@ int main(int argc, char **argv) } if (test_mode == TUNER_BENCHMARK) { - tuner_benchmark(); + tuner_benchmark(tuner_bench_beg_freq, tuner_bench_end_freq); goto exit; } diff --git a/src/tuner_r82xx.c b/src/tuner_r82xx.c index 4d4c83a..938b7d1 100644 --- a/src/tuner_r82xx.c +++ b/src/tuner_r82xx.c @@ -33,6 +33,9 @@ #define WITH_ASYM_FILTER 0 #define PRINT_PLL_ERRORS 0 #define PRINT_VGA_REG 0 +#define PRINT_INITIAL_REGISTERS 0 +#define PRINT_ACTUAL_VCO_AND_ERR 0 + /* #define VGA_FOR_AGC_MODE 16 */ #define DEFAULT_IF_VGA_VAL 11 @@ -280,35 +283,35 @@ static const uint8_t r82xx_init_array[] = { 0xc0, /* Reg 0x08 */ 0x40, /* Reg 0x09 */ - 0xdb, /* Reg 0x0a */ + 0xdb, /* Reg 0x0a */ 0x6b, /* Reg 0x0b */ /* Reg 0x0c: * for manual gain was: set fixed VGA gain for now (16.3 dB): 0x08 * with active agc was: set fixed VGA gain for now (26.5 dB): 0x0b */ 0xe0 | DEFAULT_IF_VGA_VAL, /* Reg 0x0c */ - 0x53, /* Reg 0x0d */ - 0x75, /* Reg 0x0e */ + 0x53, /* Reg 0x0d */ + 0x75, /* Reg 0x0e */ 0x68, /* Reg 0x0f */ - 0x6c, /* Reg 0x10 */ - 0xbb, /* Reg 0x11 */ - 0x80, /* Reg 0x12 */ + 0x6c, /* Reg 0x10 */ + 0xbb, /* Reg 0x11 */ + 0x80, /* Reg 0x12 */ VER_NUM & 0x3f, /* Reg 0x13 */ - 0x0f, /* Reg 0x14 */ - 0x00, /* Reg 0x15 */ - 0xc0, /* Reg 0x16 */ + 0x0f, /* Reg 0x14 */ + 0x00, /* Reg 0x15 */ + 0xc0, /* Reg 0x16 */ 0x30, /* Reg 0x17 */ - 0x48, /* Reg 0x18 */ - 0xec, /* Reg 0x19 */ - 0x60, /* Reg 0x1a */ + 0x48, /* Reg 0x18 */ + 0xec, /* Reg 0x19 */ + 0x60, /* Reg 0x1a */ 0x00, /* Reg 0x1b */ 0x24, /* Reg 0x1c */ - 0xdd, /* Reg 0x1d */ - 0x0e, /* Reg 0x1e */ + 0xdd, /* Reg 0x1d */ + 0x0e, /* Reg 0x1e */ 0x40 /* Reg 0x1f */ }; @@ -711,6 +714,128 @@ static int r82xx_set_mux(struct r82xx_priv *priv, uint32_t freq) return rc; } + +/* function of Youssef (AirSpy) and Carl (RTL-SDR) */ +static int r82xx_set_pll_yc(struct r82xx_priv *priv, uint32_t freq) +{ + const uint32_t vco_min = 1770000000; + const uint32_t vco_max = 3900000000; + uint32_t pll_ref = (priv->cfg->xtal); + uint32_t pll_ref_2x = (pll_ref * 2); + + int rc; + uint32_t vco_exact; + uint32_t vco_frac; + uint32_t con_frac; + uint32_t div_num; + uint32_t n_sdm; + uint16_t sdm; + uint8_t ni; + uint8_t si; + uint8_t nint; + uint8_t val_dith; + uint8_t data[5]; + + /* Calculate divider */ + for (div_num = 0; div_num < 5; div_num++) + { + vco_exact = freq << (div_num + 1); + if (vco_exact >= vco_min && vco_exact <= vco_max) + { + break; + } + } + + vco_exact = freq << (div_num + 1); + nint = (uint8_t) ((vco_exact + (pll_ref >> 16)) / pll_ref_2x); + vco_frac = vco_exact - pll_ref_2x * nint; + + nint -= 13; + ni = (nint >> 2); + si = nint - (ni << 2); + + /* Set the phase splitter */ + rc = r82xx_write_reg_mask(priv, 0x10, (uint8_t) (div_num << 5), 0xe0); + if(rc < 0) + return rc; + + /* Disable Dither */ + val_dith = (priv->disable_dither) ? 0x10 : 0x00; + rc = r82xx_write_reg_mask(priv, 0x12, val_dith, 0x18); + if (rc < 0) + return rc; + + /* Set the rough VCO frequency */ + rc = r82xx_write_reg(priv, 0x14, (uint8_t) (ni + (si << 6))); + if(rc < 0) + return rc; + + if (vco_frac == 0) + { + /* Disable frac pll */ + rc = r82xx_write_reg_mask(priv, 0x12, 0x08, 0x08); + if(rc < 0) + return rc; + } + else + { + vco_frac += pll_ref >> 16; + sdm = 0; + for(n_sdm = 0; n_sdm < 16; n_sdm++) + { + con_frac = pll_ref >> n_sdm; + if (vco_frac >= con_frac) + { + sdm |= (uint16_t) (0x8000 >> n_sdm); + vco_frac -= con_frac; + if (vco_frac == 0) + break; + } + } + +/* + actual_freq = (((nint << 16) + sdm) * (uint64_t) pll_ref_2x) >> (div_num + 1 + 16); + delta = freq - actual_freq + if (actual_freq != freq) + { + fprintf(stderr,"Tunning delta: %d Hz", delta); + } +*/ + rc = r82xx_write_reg(priv, 0x15, (uint8_t)(sdm & 0xff)); + if (rc < 0) + return rc; + + rc = r82xx_write_reg(priv, 0x16, (uint8_t)(sdm >> 8)); + if (rc < 0) + return rc; + + /* Enable frac pll */ + rc = r82xx_write_reg_mask(priv, 0x12, 0x00, 0x08); + if (rc < 0) + return rc; + } + +/***/ + + /* Check if PLL has locked */ + rc = r82xx_read(priv, 0x00, data, 3); + if (rc < 0) + return rc; + if (!(data[2] & 0x40)) { +#if PRINT_PLL_ERRORS + fprintf(stderr, "[R82XX] PLL not locked at Tuner LO %u Hz for RF %u Hz!\n", + freq, priv->rf_freq); +#endif + priv->has_lock = 0; + return -1; + } + priv->has_lock = 1; + + return rc; + +} + + static int r82xx_set_pll(struct r82xx_priv *priv, uint32_t freq) { /* freq == tuner's LO frequency */ @@ -718,7 +843,7 @@ static int r82xx_set_pll(struct r82xx_priv *priv, uint32_t freq) uint64_t vco_freq; uint64_t vco_div; uint32_t vco_min = 1770000; /* kHz */ - uint32_t vco_max = vco_min * 2; /* kHz */ + uint32_t vco_max = (priv->cfg->vco_algo == 0) ? (vco_min * 2) : 3900000; /* kHz */ uint32_t freq_khz, pll_ref; uint32_t sdm = 0; uint8_t mix_div = 2; @@ -727,8 +852,24 @@ static int r82xx_set_pll(struct r82xx_priv *priv, uint32_t freq) uint8_t vco_power_ref = 2; uint8_t refdiv2 = 0; uint8_t ni, si, nint, vco_fine_tune, val; + uint8_t vco_curr_min = (priv->cfg->vco_curr_min == 0xff) ? 0x80 : ( priv->cfg->vco_curr_min << 5 ); + uint8_t vco_curr_max = (priv->cfg->vco_curr_max == 0xff) ? 0x60 : ( priv->cfg->vco_curr_max << 5 ); + /* devt->r82xx_c.vco_min = 0xff; * VCO min/max current for R18/0x12 bits [7:5] in 0 .. 7. use 0xff for default */ + /* devt->r82xx_c.vco_max = 0xff; * value is inverted: programmed is 7-value, that 0 is lowest current */ uint8_t data[5]; + if (priv->cfg->vco_algo == 2) + { + /* r82xx_set_pll_yc() assumes fixed maximum current */ + if (priv->last_vco_curr != vco_curr_max) { + rc = r82xx_write_reg_mask(priv, 0x12, vco_curr_max, 0xe0); + if (rc < 0) + return rc; + priv->last_vco_curr = vco_curr_max; + } + return r82xx_set_pll_yc(priv, freq); + } + /* Frequency in kHz */ freq_khz = (freq + 500) / 1000; pll_ref = priv->cfg->xtal; @@ -743,9 +884,12 @@ static int r82xx_set_pll(struct r82xx_priv *priv, uint32_t freq) return rc; /* set VCO current = 100 */ - rc = r82xx_write_reg_mask(priv, 0x12, 0x80, 0xe0); - if (rc < 0) - return rc; + if (priv->last_vco_curr != vco_curr_min) { + rc = r82xx_write_reg_mask(priv, 0x12, vco_curr_min, 0xe0); + if (rc < 0) + return rc; + priv->last_vco_curr = vco_curr_min; + } /* Calculate divider */ while (mix_div <= 64) { @@ -802,10 +946,10 @@ static int r82xx_set_pll(struct r82xx_priv *priv, uint32_t freq) nint = (uint32_t) (vco_div / 65536); sdm = (uint32_t) (vco_div % 65536); -#if 0 +#if PRINT_ACTUAL_VCO_AND_ERR { uint64_t actual_vco = (uint64_t)2 * pll_ref * nint + (uint64_t)2 * pll_ref * sdm / 65536; - fprintf(stderr, "[R82XX] requested %uHz; selected mix_div=%u vco_freq=%lu nint=%u sdm=%u; actual_vco=%lu; tuning error=%+dHz\n", + fprintf(stderr, "[R82XX] requested %u Hz; selected mix_div=%u vco_freq=%lu nint=%u sdm=%u; actual_vco=%lu; tuning error=%+dHz\n", freq, mix_div, vco_freq, nint, sdm, actual_vco, (int32_t) (actual_vco - vco_freq) / mix_div); } #endif @@ -850,14 +994,17 @@ static int r82xx_set_pll(struct r82xx_priv *priv, uint32_t freq) rc = r82xx_read(priv, 0x00, data, 3); if (rc < 0) return rc; - if (data[2] & 0x40) + if ( (data[2] & 0x40) || vco_curr_max == vco_curr_min ) break; if (!i) { /* Didn't lock. Increase VCO current */ - rc = r82xx_write_reg_mask(priv, 0x12, 0x60, 0xe0); - if (rc < 0) - return rc; + if (priv->last_vco_curr != vco_curr_max) { + rc = r82xx_write_reg_mask(priv, 0x12, vco_curr_max, 0xe0); + if (rc < 0) + return rc; + priv->last_vco_curr = vco_curr_max; + } } } @@ -1752,6 +1899,19 @@ int r82xx_init(struct r82xx_priv *priv) { int rc; +#if PRINT_INITIAL_REGISTERS +#define INIT_NUM_READ_REGS 16 + uint8_t initial_register_values[INIT_NUM_READ_REGS]; /* see what is 'default' */ + int k; + /* get initial register values - just to see .. */ + memset( &(initial_register_values[0]), 0, sizeof(initial_register_values) ); + printf("R820T/2 initial register settings:\n"); + r82xx_read(priv, 0x00, initial_register_values, sizeof(initial_register_values)); + for (k=0; k < INIT_NUM_READ_REGS; ++k) + printf("register 0x%02x: 0x%02x\n", k, initial_register_values[k]); + printf("\n"); +#endif + /* TODO: R828D might need r82xx_xtal_check() */ priv->xtal_cap_sel = XTAL_HIGH_CAP_0P; @@ -1764,6 +1924,7 @@ int r82xx_init(struct r82xx_priv *priv) priv->last_LNA_value = 0; priv->last_Mixer_value = 0; priv->last_VGA_value = DEFAULT_IF_VGA_VAL; + priv->last_vco_curr = 0xff; /* Initialize override registers */ memset( &(priv->override_data[0]), 0, NUM_REGS * sizeof(uint8_t) ); @@ -1773,6 +1934,8 @@ int r82xx_init(struct r82xx_priv *priv) rc = r82xx_write_arr(priv, 0x05, r82xx_init_array, sizeof(r82xx_init_array)); + priv->last_vco_curr = r82xx_init_array[0x12 - 0x05] & 0xe0; + rc = r82xx_set_tv_standard(priv, TUNER_DIGITAL_TV, 0); if (rc < 0) goto err;