mirror of
https://github.com/greatscottgadgets/hackrf.git
synced 2026-03-05 23:15:54 +01:00
If the requested frequency is the same as the already-configured frequency, ignore the tuning request.
554 lines
14 KiB
C
554 lines
14 KiB
C
/*
|
|
* Copyright 2012-2025 Great Scott Gadgets <info@greatscottgadgets.com>
|
|
* Copyright 2012 Jared Boone
|
|
* Copyright 2013 Benjamin Vernoux
|
|
*
|
|
* This file is part of HackRF.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2, or (at your option)
|
|
* any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; see the file COPYING. If not, write to
|
|
* the Free Software Foundation, Inc., 51 Franklin Street,
|
|
* Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#include "hackrf_core.h"
|
|
#include "tuning.h"
|
|
|
|
#include "radio.h"
|
|
|
|
radio_error_t radio_set_sample_rate(
|
|
radio_t* radio,
|
|
radio_chan_id chan_id,
|
|
radio_sample_rate_id element,
|
|
radio_sample_rate_t sample_rate)
|
|
{
|
|
// we only support the clock generator at the moment
|
|
if (element != RADIO_SAMPLE_RATE_CLOCKGEN) {
|
|
return RADIO_ERR_INVALID_ELEMENT;
|
|
}
|
|
|
|
radio_config_t* config = &radio->channel[chan_id].config;
|
|
|
|
/*
|
|
* Store the sample rate rounded to the nearest Hz for convenience.
|
|
*/
|
|
if (sample_rate.div == 0) {
|
|
return RADIO_ERR_INVALID_PARAM;
|
|
}
|
|
sample_rate.hz = (sample_rate.num + (sample_rate.div >> 1)) / sample_rate.div;
|
|
|
|
if (config->mode == TRANSCEIVER_MODE_OFF) {
|
|
config->sample_rate[element] = sample_rate;
|
|
return RADIO_OK;
|
|
}
|
|
|
|
#ifdef PRALINE
|
|
#define MAX_AFE_RATE 40000000
|
|
#define MAX_N 5
|
|
uint8_t n = 0; // resampling ratio is 2**n
|
|
if ((config->mode == TRANSCEIVER_MODE_RX) ||
|
|
(config->mode == TRANSCEIVER_MODE_RX_SWEEP)) {
|
|
n = 1;
|
|
uint32_t afe_rate_x2 = 2 * sample_rate.hz;
|
|
while ((afe_rate_x2 <= MAX_AFE_RATE) && (n < MAX_N)) {
|
|
afe_rate_x2 <<= 1;
|
|
n++;
|
|
}
|
|
fpga_set_rx_decimation_ratio(&fpga, n);
|
|
}
|
|
config->resampling_n = n;
|
|
bool ok = sample_rate_frac_set(sample_rate.num << n, sample_rate.div);
|
|
if (ok) {
|
|
config->sample_rate[element] = sample_rate;
|
|
radio_channel_t* channel = &radio->channel[chan_id];
|
|
radio_frequency_t frequency =
|
|
radio_get_frequency(radio, channel->id, RADIO_FREQUENCY_RF);
|
|
ok = radio_set_frequency(
|
|
radio,
|
|
channel->id,
|
|
RADIO_FREQUENCY_RF,
|
|
frequency);
|
|
}
|
|
#else
|
|
bool ok = sample_rate_frac_set(sample_rate.num, sample_rate.div);
|
|
#endif
|
|
if (!ok) {
|
|
return RADIO_ERR_INVALID_PARAM;
|
|
}
|
|
|
|
config->sample_rate[element] = sample_rate;
|
|
return RADIO_OK;
|
|
}
|
|
|
|
radio_sample_rate_t radio_get_sample_rate(
|
|
radio_t* radio,
|
|
radio_chan_id chan_id,
|
|
radio_sample_rate_id element)
|
|
{
|
|
return radio->channel[chan_id].config.sample_rate[element];
|
|
}
|
|
|
|
radio_error_t radio_set_filter(
|
|
radio_t* radio,
|
|
radio_chan_id chan_id,
|
|
radio_filter_id element,
|
|
radio_filter_t filter)
|
|
{
|
|
// we only support the baseband filter at the moment
|
|
if (element != RADIO_FILTER_BASEBAND) {
|
|
return RADIO_ERR_INVALID_ELEMENT;
|
|
}
|
|
|
|
radio_config_t* config = &radio->channel[chan_id].config;
|
|
|
|
if (config->mode == TRANSCEIVER_MODE_OFF) {
|
|
config->filter[element] = filter;
|
|
return RADIO_OK;
|
|
}
|
|
|
|
#ifndef PRALINE
|
|
max283x_set_lpf_bandwidth(&max283x, filter.hz);
|
|
#else
|
|
uint32_t lpf_bandwidth =
|
|
(config->sample_rate[RADIO_SAMPLE_RATE_CLOCKGEN].hz * 3) / 8;
|
|
uint32_t offset = 0;
|
|
if (config->shift != FPGA_QUARTER_SHIFT_MODE_NONE) {
|
|
offset = (config->sample_rate[RADIO_SAMPLE_RATE_CLOCKGEN].hz
|
|
<< config->resampling_n) /
|
|
8;
|
|
}
|
|
lpf_bandwidth += offset * 2;
|
|
max2831_set_lpf_bandwidth(&max283x, lpf_bandwidth);
|
|
#endif
|
|
|
|
config->filter[element] = filter;
|
|
return RADIO_OK;
|
|
}
|
|
|
|
radio_filter_t radio_get_filter(
|
|
radio_t* radio,
|
|
radio_chan_id chan_id,
|
|
radio_filter_id element)
|
|
{
|
|
return radio->channel[chan_id].config.filter[element];
|
|
}
|
|
|
|
radio_error_t radio_set_gain(
|
|
radio_t* radio,
|
|
radio_chan_id chan_id,
|
|
radio_gain_id element,
|
|
radio_gain_t gain)
|
|
{
|
|
if (element > RADIO_GAIN_COUNT) {
|
|
return RADIO_ERR_INVALID_ELEMENT;
|
|
}
|
|
|
|
radio_config_t* config = &radio->channel[chan_id].config;
|
|
|
|
if (config->mode == TRANSCEIVER_MODE_OFF) {
|
|
config->gain[element] = gain;
|
|
return RADIO_OK;
|
|
}
|
|
|
|
uint8_t real_db;
|
|
switch (element) {
|
|
case RADIO_GAIN_RF_AMP:
|
|
rf_path_set_lna(&rf_path, gain.enable);
|
|
break;
|
|
case RADIO_GAIN_RX_LNA:
|
|
#ifndef PRALINE
|
|
real_db = max283x_set_lna_gain(&max283x, gain.db);
|
|
#else
|
|
real_db = max2831_set_lna_gain(&max283x, gain.db);
|
|
#endif
|
|
if (real_db == 0) {
|
|
return RADIO_ERR_INVALID_PARAM;
|
|
}
|
|
break;
|
|
case RADIO_GAIN_RX_VGA:
|
|
#ifndef PRALINE
|
|
real_db = max283x_set_vga_gain(&max283x, gain.db);
|
|
#else
|
|
real_db = max2831_set_vga_gain(&max283x, gain.db);
|
|
#endif
|
|
if (real_db == 0) {
|
|
return RADIO_ERR_INVALID_PARAM;
|
|
}
|
|
break;
|
|
case RADIO_GAIN_TX_VGA:
|
|
#ifndef PRALINE
|
|
real_db = max283x_set_txvga_gain(&max283x, gain.db);
|
|
#else
|
|
real_db = max2831_set_txvga_gain(&max283x, gain.db);
|
|
#endif
|
|
if (real_db == 0) {
|
|
return RADIO_ERR_INVALID_PARAM;
|
|
}
|
|
break;
|
|
}
|
|
|
|
config->gain[element] = gain;
|
|
return RADIO_OK;
|
|
}
|
|
|
|
radio_gain_t radio_get_gain(radio_t* radio, radio_chan_id chan_id, radio_gain_id element)
|
|
{
|
|
return radio->channel[chan_id].config.gain[element];
|
|
}
|
|
|
|
static uint64_t applied_freq = 0xffffffffffffffff;
|
|
|
|
radio_error_t radio_set_frequency(
|
|
radio_t* radio,
|
|
radio_chan_id chan_id,
|
|
radio_frequency_id element,
|
|
radio_frequency_t frequency)
|
|
{
|
|
// we only support setting the final rf frequency at the moment
|
|
if (element != RADIO_FREQUENCY_RF) {
|
|
return RADIO_ERR_INVALID_ELEMENT;
|
|
}
|
|
|
|
radio_config_t* config = &radio->channel[chan_id].config;
|
|
|
|
if (config->mode == TRANSCEIVER_MODE_OFF) {
|
|
config->frequency[element] = frequency;
|
|
return RADIO_OK;
|
|
}
|
|
|
|
// explicit
|
|
if (frequency.if_hz || frequency.lo_hz) {
|
|
bool ok = set_freq_explicit(
|
|
frequency.if_hz,
|
|
frequency.lo_hz,
|
|
frequency.path);
|
|
#ifdef PRALINE
|
|
if (ok) {
|
|
fpga_set_rx_quarter_shift_mode(
|
|
&fpga,
|
|
FPGA_QUARTER_SHIFT_MODE_NONE);
|
|
config->shift = FPGA_QUARTER_SHIFT_MODE_NONE;
|
|
radio_channel_t* channel = &radio->channel[chan_id];
|
|
radio_filter_t filter = radio_get_filter(
|
|
radio,
|
|
channel->id,
|
|
RADIO_FILTER_BASEBAND);
|
|
ok = radio_set_filter(
|
|
radio,
|
|
channel->id,
|
|
RADIO_FILTER_BASEBAND,
|
|
filter);
|
|
}
|
|
#endif
|
|
if (!ok) {
|
|
return RADIO_ERR_INVALID_PARAM;
|
|
}
|
|
|
|
config->frequency[element] = frequency;
|
|
return RADIO_OK;
|
|
}
|
|
|
|
// auto-tune
|
|
if (frequency.hz == applied_freq) {
|
|
return RADIO_OK;
|
|
}
|
|
applied_freq = frequency.hz;
|
|
bool ok;
|
|
#ifndef PRALINE
|
|
switch (config->mode) {
|
|
case TRANSCEIVER_MODE_RX:
|
|
case TRANSCEIVER_MODE_RX_SWEEP:
|
|
case TRANSCEIVER_MODE_TX:
|
|
ok = set_freq(frequency.hz);
|
|
break;
|
|
default:
|
|
return RADIO_ERR_INVALID_CONFIG;
|
|
}
|
|
#else
|
|
const tune_config_t* tune_config;
|
|
switch (config->mode) {
|
|
case TRANSCEIVER_MODE_RX:
|
|
tune_config = praline_tune_config_rx;
|
|
break;
|
|
case TRANSCEIVER_MODE_RX_SWEEP:
|
|
tune_config = praline_tune_config_rx_sweep;
|
|
break;
|
|
case TRANSCEIVER_MODE_TX:
|
|
tune_config = praline_tune_config_tx;
|
|
break;
|
|
default:
|
|
return RADIO_ERR_INVALID_CONFIG;
|
|
}
|
|
bool found = false;
|
|
for (; (tune_config->rf_range_end_mhz != 0) || (tune_config->if_mhz != 0);
|
|
tune_config++) {
|
|
if ((frequency.hz == 0) ||
|
|
(tune_config->rf_range_end_mhz > (frequency.hz / FREQ_ONE_MHZ))) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!found) {
|
|
return RADIO_ERR_INVALID_PARAM;
|
|
}
|
|
|
|
fpga_set_rx_quarter_shift_mode(&fpga, tune_config->shift);
|
|
config->shift = tune_config->shift;
|
|
uint32_t offset = (config->sample_rate[RADIO_SAMPLE_RATE_CLOCKGEN].hz
|
|
<< config->resampling_n) /
|
|
8;
|
|
ok = tuning_set_frequency(tune_config, frequency.hz, offset);
|
|
if (ok) {
|
|
radio_channel_t* channel = &radio->channel[chan_id];
|
|
radio_filter_t filter =
|
|
radio_get_filter(radio, channel->id, RADIO_FILTER_BASEBAND);
|
|
ok = radio_set_filter(radio, channel->id, RADIO_FILTER_BASEBAND, filter);
|
|
}
|
|
#endif
|
|
if (!ok) {
|
|
return RADIO_ERR_INVALID_PARAM;
|
|
}
|
|
|
|
config->frequency[element] = frequency;
|
|
return RADIO_OK;
|
|
}
|
|
|
|
radio_frequency_t radio_get_frequency(
|
|
radio_t* radio,
|
|
radio_chan_id chan_id,
|
|
radio_frequency_id element)
|
|
{
|
|
return radio->channel[chan_id].config.frequency[element];
|
|
}
|
|
|
|
radio_error_t radio_set_antenna(
|
|
radio_t* radio,
|
|
radio_chan_id chan_id,
|
|
radio_antenna_id element,
|
|
radio_antenna_t value)
|
|
{
|
|
if (element > RADIO_ANTENNA_COUNT) {
|
|
return RADIO_ERR_INVALID_ELEMENT;
|
|
}
|
|
|
|
radio_config_t* config = &radio->channel[chan_id].config;
|
|
|
|
if (config->mode == TRANSCEIVER_MODE_OFF) {
|
|
config->antenna[element] = value;
|
|
return RADIO_OK;
|
|
}
|
|
|
|
switch (element) {
|
|
case RADIO_ANTENNA_BIAS_TEE:
|
|
rf_path_set_antenna(
|
|
&rf_path,
|
|
config->antenna[RADIO_ANTENNA_BIAS_TEE].enable);
|
|
break;
|
|
}
|
|
|
|
config->antenna[element] = value;
|
|
return RADIO_OK;
|
|
}
|
|
|
|
radio_antenna_t radio_get_antenna(
|
|
radio_t* radio,
|
|
radio_chan_id chan_id,
|
|
radio_antenna_id element)
|
|
{
|
|
return radio->channel[chan_id].config.antenna[element];
|
|
}
|
|
|
|
radio_error_t radio_set_clock(
|
|
radio_t* radio,
|
|
radio_chan_id chan_id,
|
|
radio_clock_id element,
|
|
radio_clock_t value)
|
|
{
|
|
radio_config_t* config = &radio->channel[chan_id].config;
|
|
|
|
if (element > RADIO_CLOCK_COUNT) {
|
|
return RADIO_ERR_INVALID_ELEMENT;
|
|
}
|
|
|
|
// CLKIN is not supported as it is automatically detected from hardware state
|
|
if (element == RADIO_CLOCK_CLKIN) {
|
|
return RADIO_ERR_UNSUPPORTED_OPERATION;
|
|
}
|
|
|
|
si5351c_clkout_enable(&clock_gen, value.enable);
|
|
|
|
config->clock[element] = value;
|
|
return RADIO_OK;
|
|
}
|
|
|
|
radio_clock_t radio_get_clock(
|
|
radio_t* radio,
|
|
radio_chan_id chan_id,
|
|
radio_clock_id element)
|
|
{
|
|
if (element == RADIO_CLOCK_CLKIN) {
|
|
return (radio_clock_t){
|
|
.enable = si5351c_clkin_signal_valid(&clock_gen),
|
|
};
|
|
}
|
|
|
|
return radio->channel[chan_id].config.clock[element];
|
|
}
|
|
|
|
radio_error_t radio_set_trigger_enable(radio_t* radio, radio_chan_id chan_id, bool enable)
|
|
{
|
|
radio_config_t* config = &radio->channel[chan_id].config;
|
|
|
|
config->trigger_enable = enable;
|
|
return RADIO_OK;
|
|
}
|
|
|
|
bool radio_get_trigger_enable(radio_t* radio, radio_chan_id chan_id)
|
|
{
|
|
return radio->channel[chan_id].config.trigger_enable;
|
|
}
|
|
|
|
transceiver_mode_t radio_get_mode(radio_t* radio, radio_chan_id chan_id)
|
|
{
|
|
return radio->channel[chan_id].config.mode;
|
|
}
|
|
|
|
rf_path_direction_t radio_get_direction(radio_t* radio, radio_chan_id chan_id)
|
|
{
|
|
radio_config_t* config = &radio->channel[chan_id].config;
|
|
|
|
switch (config->mode) {
|
|
case TRANSCEIVER_MODE_RX:
|
|
case TRANSCEIVER_MODE_RX_SWEEP:
|
|
return RF_PATH_DIRECTION_RX;
|
|
case TRANSCEIVER_MODE_TX:
|
|
return RF_PATH_DIRECTION_TX;
|
|
default:
|
|
return RF_PATH_DIRECTION_OFF;
|
|
}
|
|
}
|
|
|
|
clock_source_t radio_get_clock_source(radio_t* radio, radio_chan_id chan_id)
|
|
{
|
|
return radio->channel[chan_id].clock_source;
|
|
}
|
|
|
|
radio_error_t radio_switch_mode(
|
|
radio_t* radio,
|
|
radio_chan_id chan_id,
|
|
transceiver_mode_t mode)
|
|
{
|
|
radio_error_t result;
|
|
radio_channel_t* channel = &radio->channel[chan_id];
|
|
radio_config_t* config = &channel->config;
|
|
|
|
// configure firmware direction from mode (but don't configure the hardware yet!)
|
|
rf_path_direction_t direction;
|
|
switch (mode) {
|
|
case TRANSCEIVER_MODE_RX:
|
|
case TRANSCEIVER_MODE_RX_SWEEP:
|
|
direction = RF_PATH_DIRECTION_RX;
|
|
break;
|
|
case TRANSCEIVER_MODE_TX:
|
|
direction = RF_PATH_DIRECTION_TX;
|
|
break;
|
|
default:
|
|
rf_path_set_direction(&rf_path, RF_PATH_DIRECTION_OFF);
|
|
config->mode = mode;
|
|
return RADIO_OK;
|
|
}
|
|
config->mode = mode;
|
|
|
|
// sample rate
|
|
radio_sample_rate_t sample_rate =
|
|
radio_get_sample_rate(radio, channel->id, RADIO_SAMPLE_RATE_CLOCKGEN);
|
|
result = radio_set_sample_rate(
|
|
radio,
|
|
channel->id,
|
|
RADIO_SAMPLE_RATE_CLOCKGEN,
|
|
sample_rate);
|
|
if (result != RADIO_OK) {
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* Because of offset tuning on Praline, the sample rate can affect the
|
|
* tuning configuration, so radio_set_sample_rate() calls
|
|
* radio_set_frequency(). Also because of offset tuning, the tuning
|
|
* configuration can affect the baseband filter bandwidth (in addition
|
|
* to the filter bandwidth being automatically based on the sample
|
|
* rate), so radio_set_frequency() calls radio_set_filter().
|
|
*/
|
|
#ifndef PRALINE
|
|
// tuning frequency
|
|
radio_frequency_t frequency =
|
|
radio_get_frequency(radio, channel->id, RADIO_FREQUENCY_RF);
|
|
result = radio_set_frequency(radio, channel->id, RADIO_FREQUENCY_RF, frequency);
|
|
if (result != RADIO_OK) {
|
|
return result;
|
|
}
|
|
|
|
// baseband filter bandwidth
|
|
radio_filter_t filter =
|
|
radio_get_filter(radio, channel->id, RADIO_FILTER_BASEBAND);
|
|
result = radio_set_filter(radio, channel->id, RADIO_FILTER_BASEBAND, filter);
|
|
if (result != RADIO_OK) {
|
|
return result;
|
|
}
|
|
#endif
|
|
|
|
// rf_amp enable
|
|
radio_gain_t enable = radio_get_gain(radio, channel->id, RADIO_GAIN_RF_AMP);
|
|
result = radio_set_gain(radio, channel->id, RADIO_GAIN_RF_AMP, enable);
|
|
if (result != RADIO_OK) {
|
|
return result;
|
|
}
|
|
|
|
// gain
|
|
radio_gain_t gain;
|
|
if (config->mode == TRANSCEIVER_MODE_RX ||
|
|
config->mode == TRANSCEIVER_MODE_RX_SWEEP) {
|
|
gain = radio_get_gain(radio, channel->id, RADIO_GAIN_RX_LNA);
|
|
result = radio_set_gain(radio, channel->id, RADIO_GAIN_RX_LNA, gain);
|
|
if (result != RADIO_OK) {
|
|
return result;
|
|
}
|
|
|
|
gain = radio_get_gain(radio, channel->id, RADIO_GAIN_RX_VGA);
|
|
result = radio_set_gain(radio, channel->id, RADIO_GAIN_RX_VGA, gain);
|
|
if (result != RADIO_OK) {
|
|
return result;
|
|
}
|
|
|
|
} else if (config->mode == TRANSCEIVER_MODE_TX) {
|
|
gain = radio_get_gain(radio, channel->id, RADIO_GAIN_TX_VGA);
|
|
result = radio_set_gain(radio, channel->id, RADIO_GAIN_TX_VGA, gain);
|
|
if (result != RADIO_OK) {
|
|
return result;
|
|
}
|
|
}
|
|
|
|
// antenna
|
|
radio_antenna_t bias_tee =
|
|
radio_get_antenna(radio, channel->id, RADIO_ANTENNA_BIAS_TEE);
|
|
result = radio_set_antenna(radio, channel->id, RADIO_ANTENNA_BIAS_TEE, bias_tee);
|
|
if (result != RADIO_OK) {
|
|
return result;
|
|
}
|
|
|
|
// finally, set the rf path direction
|
|
rf_path_set_direction(&rf_path, direction);
|
|
|
|
return RADIO_OK;
|
|
}
|