Files
hackrf/firmware/common/radio.c
Michael Ossmann a5af0edabb Ignore re-tune requests
If the requested frequency is the same as the already-configured
frequency, ignore the tuning request.
2026-01-13 14:54:26 -05:00

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;
}