mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2026-08-13 11:23:41 +00:00
454 lines
17 KiB
C++
454 lines
17 KiB
C++
/*
|
|
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
|
|
*
|
|
* This file is part of PortaPack.
|
|
*
|
|
* 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 "tuning.hpp"
|
|
|
|
#include "utility.hpp"
|
|
|
|
namespace tuning {
|
|
namespace config {
|
|
|
|
// Forward declarations
|
|
Config low_band(const rf::Frequency target_frequency, const uint32_t afe_rate, const bool transmit);
|
|
Config mid_band(const rf::Frequency target_frequency, const uint32_t afe_rate, const bool transmit);
|
|
Config high_band(const rf::Frequency target_frequency);
|
|
|
|
#ifdef PRALINE
|
|
/*
|
|
* PRALINE Tuning Configuration
|
|
* ============================
|
|
*
|
|
* These tables are copied verbatim from the reference firmware,
|
|
* hackrf/firmware/common/tune_config.h (praline_tune_config_rx /
|
|
* praline_tune_config_tx), and the selection and offset maths below reproduce
|
|
* hackrf/firmware/common/radio.c radio_update_frequency() /
|
|
* analog_from_digital_rf() / compute_offset().
|
|
*
|
|
* Each entry gives, for target frequencies up to rf_range_end_mhz:
|
|
* if_mhz the IF the MAX2831 tunes to (0 = mixer bypassed, IF = RF)
|
|
* high_lo true -> LO = IF + analogue RF (mixer inverts the spectrum)
|
|
* false -> LO = IF - analogue RF (no inversion)
|
|
* shift the FPGA quarter-rate shift mode used for this entry
|
|
*
|
|
* The quarter-rate shift is the part Mayhem was previously missing. RX entries
|
|
* deliberately place the analogue passband a quarter of the ADC rate away from
|
|
* the requested frequency (+8 MHz at the usual 32 Msps AFE rate) so that the
|
|
* wanted signal never sits on the DC offset / LO leakage, and then ask the FPGA
|
|
* to rotate it back down to DC. Both halves have to be programmed together:
|
|
* - tuning to target + offset without asking the FPGA to rotate leaves the
|
|
* signal 8 MHz out and the decimation filter deletes it;
|
|
* - tuning to target with no offset (what Mayhem did) parks the signal on DC,
|
|
* under the LO leakage and the gateware's adaptive DC block.
|
|
*
|
|
* "up" and "down" are named for the direction the FPGA rotates, so
|
|
* FPGA_QUARTER_SHIFT_MODE_UP means the analogue centre is placed ABOVE the
|
|
* requested frequency, and DOWN below it (radio.c analog_from_digital_rf()).
|
|
*/
|
|
|
|
namespace {
|
|
|
|
struct PralineTuneConfig {
|
|
uint16_t rf_range_end_mhz;
|
|
uint16_t if_mhz;
|
|
bool high_lo;
|
|
uint8_t shift; /* 0b00 none, 0b11 up, 0b01 down */
|
|
};
|
|
|
|
/* The tables below are kept column-aligned to match the reference source, so
|
|
* they are exempt from reformatting. */
|
|
// clang-format off
|
|
|
|
/* tuning table optimized for RX */
|
|
constexpr PralineTuneConfig praline_tune_config_rx[] = {
|
|
{ 0, 2360, true, 0b00},
|
|
{ 50, 2320, true, 0b11},
|
|
{ 100, 2320, true, 0b01},
|
|
{ 140, 2320, true, 0b11},
|
|
{ 406, 2560, true, 0b11},
|
|
{ 511, 2380, true, 0b11},
|
|
{ 578, 2560, true, 0b01},
|
|
{ 741, 2340, true, 0b11},
|
|
{ 861, 2560, true, 0b01},
|
|
{ 921, 2560, true, 0b11},
|
|
{ 1049, 2340, true, 0b01},
|
|
{ 1169, 2380, true, 0b11},
|
|
{ 1360, 2340, true, 0b11},
|
|
{ 1544, 2560, true, 0b01},
|
|
{ 1675, 2560, true, 0b11},
|
|
{ 1992, 2380, true, 0b01},
|
|
{ 2070, 2340, true, 0b01},
|
|
{ 2150, 2360, true, 0b01},
|
|
{ 2168, 2560, false, 0b11},
|
|
{ 2185, 2580, false, 0b11},
|
|
{ 2202, 2580, false, 0b01},
|
|
{ 2205, 2520, false, 0b11},
|
|
{ 2216, 2560, false, 0b11},
|
|
{ 2223, 2540, false, 0b11},
|
|
{ 2234, 2580, false, 0b11},
|
|
{ 2240, 2560, false, 0b11},
|
|
{ 2251, 2580, false, 0b01},
|
|
{ 2258, 2580, false, 0b11},
|
|
{ 2265, 2540, false, 0b01},
|
|
{ 2271, 2580, false, 0b11},
|
|
{ 2273, 2560, false, 0b11},
|
|
{ 2275, 2580, false, 0b01},
|
|
{ 2280, 2500, false, 0b01},
|
|
{ 2284, 2540, false, 0b11},
|
|
{ 2289, 2580, false, 0b01},
|
|
{ 2293, 2540, false, 0b01},
|
|
{ 2298, 2520, false, 0b01},
|
|
{ 2300, 2580, false, 0b11},
|
|
{ 2302, 2540, false, 0b01},
|
|
{ 2309, 2560, false, 0b01},
|
|
{ 2311, 2580, false, 0b01},
|
|
{ 2314, 2540, false, 0b11},
|
|
{ 2315, 2540, false, 0b01},
|
|
{ 2320, 2580, false, 0b11},
|
|
{ 2380, 0, false, 0b11},
|
|
{ 2440, 0, false, 0b01},
|
|
{ 2500, 0, false, 0b11},
|
|
{ 2580, 0, false, 0b01},
|
|
{ 2583, 2360, false, 0b11},
|
|
{ 2584, 2380, false, 0b11},
|
|
{ 2587, 2340, false, 0b11},
|
|
{ 2593, 2340, false, 0b01},
|
|
{ 2607, 2340, false, 0b11},
|
|
{ 2609, 2360, false, 0b11},
|
|
{ 2615, 2360, false, 0b01},
|
|
{ 2627, 2340, false, 0b01},
|
|
{ 2629, 2360, false, 0b01},
|
|
{ 2631, 2380, false, 0b11},
|
|
{ 2644, 2340, false, 0b11},
|
|
{ 2649, 2380, false, 0b11},
|
|
{ 2651, 2380, false, 0b01},
|
|
{ 2654, 2500, false, 0b11},
|
|
{ 2665, 2360, false, 0b11},
|
|
{ 2669, 2380, false, 0b01},
|
|
{ 2672, 2360, false, 0b01},
|
|
{ 2682, 2340, false, 0b11},
|
|
{ 2687, 2380, false, 0b11},
|
|
{ 2692, 2340, false, 0b11},
|
|
{ 2695, 2500, false, 0b11},
|
|
{ 2705, 2360, false, 0b11},
|
|
{ 2707, 2380, false, 0b01},
|
|
{ 2712, 2340, false, 0b01},
|
|
{ 2717, 2520, false, 0b11},
|
|
{ 2728, 2380, false, 0b11},
|
|
{ 2730, 2560, false, 0b11},
|
|
{ 2734, 2500, false, 0b11},
|
|
{ 2758, 2340, false, 0b11},
|
|
{ 2780, 2360, false, 0b11},
|
|
{ 2787, 2520, false, 0b11},
|
|
{ 2802, 2380, false, 0b11},
|
|
{ 2809, 2540, false, 0b11},
|
|
{ 2822, 2380, false, 0b01},
|
|
{ 2831, 2560, false, 0b11},
|
|
{ 2854, 2340, false, 0b11},
|
|
{ 2875, 2360, false, 0b11},
|
|
{ 2898, 2380, false, 0b11},
|
|
{ 2918, 2380, false, 0b01},
|
|
{ 2936, 2520, false, 0b01},
|
|
{ 2944, 2380, false, 0b01},
|
|
{ 2959, 2560, false, 0b11},
|
|
{ 2976, 2340, false, 0b11},
|
|
{ 2985, 2500, false, 0b01},
|
|
{ 3003, 2340, false, 0b11},
|
|
{ 3009, 2540, false, 0b11},
|
|
{ 3027, 2380, false, 0b11},
|
|
{ 3034, 2560, false, 0b11},
|
|
{ 3050, 2380, false, 0b01},
|
|
{ 3069, 2500, false, 0b11},
|
|
{ 3094, 2520, false, 0b11},
|
|
{ 3119, 2540, false, 0b11},
|
|
{ 3144, 2560, false, 0b11},
|
|
{ 3169, 2560, false, 0b01},
|
|
{ 3180, 2500, false, 0b11},
|
|
{ 3204, 2340, false, 0b11},
|
|
{ 3232, 2360, false, 0b11},
|
|
{ 3292, 2340, false, 0b01},
|
|
{ 3340, 2380, false, 0b01},
|
|
{ 3369, 2340, false, 0b11},
|
|
{ 3399, 2360, false, 0b11},
|
|
{ 3429, 2380, false, 0b11},
|
|
{ 3464, 2500, false, 0b11},
|
|
{ 3489, 2520, false, 0b11},
|
|
{ 3512, 2540, false, 0b11},
|
|
{ 3551, 2500, false, 0b01},
|
|
{ 3582, 2540, false, 0b11},
|
|
{ 3611, 2560, false, 0b11},
|
|
{ 3639, 2520, false, 0b11},
|
|
{ 3729, 2340, false, 0b11},
|
|
{ 3817, 2380, false, 0b01},
|
|
{ 3942, 2360, false, 0b01},
|
|
{ 4049, 2540, false, 0b11},
|
|
{ 4134, 2500, false, 0b01},
|
|
{ 4194, 2560, false, 0b11},
|
|
{ 4353, 2520, false, 0b11},
|
|
{ 4449, 2360, false, 0b01},
|
|
{ 4562, 2500, false, 0b11},
|
|
{ 4672, 2560, false, 0b11},
|
|
{ 4769, 2540, false, 0b11},
|
|
{ 4849, 2560, false, 0b01},
|
|
{ 4889, 2560, false, 0b11},
|
|
{ 4929, 2560, false, 0b11},
|
|
{ 4969, 2560, false, 0b11},
|
|
{ 5009, 2560, false, 0b11},
|
|
{ 5049, 2560, false, 0b11},
|
|
{ 5092, 2360, false, 0b11},
|
|
{ 5209, 2340, false, 0b01},
|
|
{ 5298, 2380, false, 0b01},
|
|
{ 5468, 2340, false, 0b01},
|
|
{ 5582, 2520, false, 0b11},
|
|
{ 5702, 2340, false, 0b11},
|
|
{ 5888, 2520, false, 0b01},
|
|
{ 6092, 2340, false, 0b01},
|
|
{ 6240, 2560, false, 0b11},
|
|
{ 6609, 2340, false, 0b11},
|
|
{ 6752, 2380, false, 0b01},
|
|
{ 6930, 2520, false, 0b01},
|
|
{ 7000, 2560, false, 0b11},
|
|
{ 7070, 2560, false, 0b01},
|
|
{ 7251, 2580, false, 0b01},
|
|
{ 0, 0, false, 0b00},
|
|
};
|
|
|
|
/* tuning table optimized for TX */
|
|
constexpr PralineTuneConfig praline_tune_config_tx[] = {
|
|
{ 2100, 2375, true, 0b00},
|
|
{ 2105, 2375, false, 0b00},
|
|
{ 2115, 2425, false, 0b00},
|
|
{ 2130, 2375, false, 0b00},
|
|
{ 2150, 2425, false, 0b00},
|
|
{ 2160, 2475, false, 0b00},
|
|
{ 2175, 2425, false, 0b00},
|
|
{ 2190, 2475, false, 0b00},
|
|
{ 2195, 2425, false, 0b00},
|
|
{ 2210, 2375, false, 0b00},
|
|
{ 2248, 2425, false, 0b00},
|
|
{ 2265, 2525, false, 0b00},
|
|
{ 2300, 2425, false, 0b00},
|
|
{ 2320, 2525, false, 0b00},
|
|
{ 2580, 0, false, 0b00},
|
|
{ 3000, 2325, false, 0b00},
|
|
{ 3140, 2375, false, 0b00},
|
|
{ 3200, 2425, false, 0b00},
|
|
{ 3280, 2375, false, 0b00},
|
|
{ 3340, 2425, false, 0b00},
|
|
{ 3420, 2475, false, 0b00},
|
|
{ 3480, 2525, false, 0b00},
|
|
{ 3500, 2475, false, 0b00},
|
|
{ 3595, 2425, false, 0b00},
|
|
{ 3625, 2375, false, 0b00},
|
|
{ 3670, 2475, false, 0b00},
|
|
{ 3710, 2425, false, 0b00},
|
|
{ 3760, 2525, false, 0b00},
|
|
{ 3790, 2475, false, 0b00},
|
|
{ 3860, 2425, false, 0b00},
|
|
{ 3915, 2375, false, 0b00},
|
|
{ 4000, 2425, false, 0b00},
|
|
{ 4055, 2375, false, 0b00},
|
|
{ 4125, 2425, false, 0b00},
|
|
{ 4700, 2375, false, 0b00},
|
|
{ 4800, 2425, false, 0b00},
|
|
{ 5000, 2375, false, 0b00},
|
|
{ 5260, 2475, false, 0b00},
|
|
{ 5465, 2525, false, 0b00},
|
|
{ 5560, 2375, false, 0b00},
|
|
{ 5720, 2425, false, 0b00},
|
|
{ 5860, 2475, false, 0b00},
|
|
{ 5970, 2575, false, 0b00},
|
|
{ 6000, 2375, false, 0b00},
|
|
{ 6500, 2325, false, 0b00},
|
|
{ 6750, 2375, false, 0b00},
|
|
{ 6850, 2425, false, 0b00},
|
|
{ 6950, 2475, false, 0b00},
|
|
{ 7000, 2525, false, 0b00},
|
|
{ 7251, 2575, false, 0b00},
|
|
{ 0, 0, false, 0b00},
|
|
};
|
|
|
|
// clang-format on
|
|
|
|
/* radio.c select_tune_config(): first entry whose range end is above the
|
|
* requested frequency. The list is terminated by an all-zero entry, which is
|
|
* also what a frequency past the end of the table lands on. */
|
|
const PralineTuneConfig* select_tune_config(const rf::Frequency target_frequency, const bool transmit) {
|
|
const PralineTuneConfig* entry = transmit ? praline_tune_config_tx : praline_tune_config_rx;
|
|
const uint32_t freq_mhz = static_cast<uint32_t>(target_frequency / 1'000'000);
|
|
|
|
while ((entry->rf_range_end_mhz != 0) || (entry->if_mhz != 0)) {
|
|
if ((target_frequency == 0) || (entry->rf_range_end_mhz > freq_mhz))
|
|
break;
|
|
entry++;
|
|
}
|
|
return entry;
|
|
}
|
|
|
|
/* radio.c compute_offset(): a quarter of the AFE (ADC) sample rate, or zero if
|
|
* no shift is in use or the AFE rate isn't known yet. */
|
|
constexpr uint32_t quarter_shift_offset(const uint8_t shift, const uint32_t afe_rate) {
|
|
return (shift == 0) ? 0 : (afe_rate / 4);
|
|
}
|
|
|
|
/* radio.c analog_from_digital_rf(). */
|
|
rf::Frequency analog_from_digital_rf(const rf::Frequency target_frequency, const uint8_t shift, const uint32_t afe_rate) {
|
|
const rf::Frequency offset = quarter_shift_offset(shift, afe_rate);
|
|
|
|
if (shift == 0b11)
|
|
return target_frequency + offset;
|
|
|
|
if (shift == 0b01)
|
|
return (offset > target_frequency) ? (offset - target_frequency)
|
|
: (target_frequency - offset);
|
|
|
|
return target_frequency;
|
|
}
|
|
|
|
} // namespace
|
|
#endif // PRALINE
|
|
|
|
Config low_band(const rf::Frequency target_frequency, const uint32_t afe_rate, const bool transmit) {
|
|
#ifdef PRALINE
|
|
const PralineTuneConfig* entry = select_tune_config(target_frequency, transmit);
|
|
|
|
/* Past the end of the table: no usable configuration. */
|
|
if ((entry->rf_range_end_mhz == 0) && (entry->if_mhz == 0))
|
|
return {};
|
|
|
|
/* afe_rate == 0 means the caller doesn't know the ADC rate, so no LO offset
|
|
* is applied. The FPGA rotation has to be dropped with it: rotating without
|
|
* the matching offset moves the wanted signal off DC by afe_rate / 4. */
|
|
const uint8_t shift = (afe_rate == 0) ? 0 : entry->shift;
|
|
const rf::Frequency analog_rf = analog_from_digital_rf(target_frequency, shift, afe_rate);
|
|
|
|
/* if_mhz == 0 means the mixer is bypassed and the transceiver tunes the RF
|
|
* directly; there is no first LO in that case. */
|
|
const rf::Frequency second_lo_frequency =
|
|
(entry->if_mhz == 0) ? analog_rf : (static_cast<rf::Frequency>(entry->if_mhz) * 1'000'000);
|
|
|
|
if (entry->if_mhz == 0)
|
|
return {0, second_lo_frequency, rf::path::Band::Low, false, shift};
|
|
|
|
/* The low band always runs through the low-pass image-reject filter, so
|
|
* the spectrum is inverted exactly when the first LO ends up above the IF,
|
|
* i.e. for high-side injection. This is hackrf_usb.c radio_changed():
|
|
* invert = (img_reject == RF_PATH_FILTER_LOW_PASS) && (freq_lo > freq_if)
|
|
*/
|
|
rf::Frequency first_lo_frequency;
|
|
bool mixer_invert;
|
|
|
|
if (entry->high_lo) {
|
|
first_lo_frequency = second_lo_frequency + analog_rf;
|
|
mixer_invert = true;
|
|
} else {
|
|
first_lo_frequency = second_lo_frequency - analog_rf;
|
|
mixer_invert = false;
|
|
}
|
|
|
|
return {first_lo_frequency, second_lo_frequency, rf::path::Band::Low, mixer_invert, shift};
|
|
#else
|
|
(void)afe_rate;
|
|
(void)transmit;
|
|
const rf::Frequency second_lo_frequency = 2650'000'000 - (target_frequency / 7);
|
|
const rf::Frequency first_lo_frequency = target_frequency + second_lo_frequency;
|
|
const bool mixer_invert = true;
|
|
return {first_lo_frequency, second_lo_frequency, rf::path::Band::Low, mixer_invert};
|
|
#endif
|
|
}
|
|
|
|
// Mid band 2170-2740 Mhz (HackRF One) or 2320-2740 MHz (PRALINE):
|
|
Config mid_band(const rf::Frequency target_frequency, const uint32_t afe_rate, const bool transmit) {
|
|
#ifdef PRALINE
|
|
/* radio.c select_img_reject() / tuning.c: on PRALINE the MAX2831 tunes
|
|
* direct (mixer bypassed) from 2320 to 2580 MHz. band_mid starts at
|
|
* TRANSITION = 2320 MHz, so everything below that already went to
|
|
* low_band(). */
|
|
if (target_frequency <= 2580'000'000) {
|
|
const PralineTuneConfig* entry = select_tune_config(target_frequency, transmit);
|
|
const uint8_t shift = (afe_rate == 0) ? 0 : entry->shift;
|
|
const rf::Frequency analog_rf = analog_from_digital_rf(target_frequency, shift, afe_rate);
|
|
|
|
/* Mixer bypassed: no first LO, the MAX2831 tunes the (offset)
|
|
* analogue RF directly and the FPGA rotates it back. */
|
|
return {0, analog_rf, rf::path::Band::Mid, false, shift};
|
|
}
|
|
|
|
/* 2580-2740 MHz: above the bypass window, downconvert. */
|
|
return high_band(target_frequency);
|
|
#else
|
|
(void)afe_rate;
|
|
(void)transmit;
|
|
const rf::Frequency second_lo_frequency = target_frequency;
|
|
const rf::Frequency first_lo_frequency = 0;
|
|
const bool mixer_invert = false;
|
|
return {first_lo_frequency, second_lo_frequency, rf::path::Band::Mid, mixer_invert};
|
|
#endif
|
|
}
|
|
|
|
// High band >2740 Mhz (HackRF One) or >2580 MHz (PRALINE):
|
|
constexpr rf::Frequency high_band_second_lo_frequency(const rf::Frequency target_frequency) {
|
|
#ifdef PRALINE
|
|
// Praline formula tuned for MAX2831 (2.3-2.6 GHz range)
|
|
// Keep second_lo in MAX2831's range while allowing RFFC5072 to work
|
|
//
|
|
// For high-band, we use LOW-side injection: LO = RF - IF
|
|
// So IF should be chosen to keep LO (and thus VCO) in a good range
|
|
//
|
|
// Based on hackrf_usb tune_config_tx patterns:
|
|
if (target_frequency < 3600'000'000)
|
|
return 2400'000'000 + ((target_frequency - 2740'000'000) / 4);
|
|
else if (target_frequency < 5100'000'000)
|
|
return 2500'000'000 + ((target_frequency - 3600'000'000) / 6);
|
|
else
|
|
return 2550'000'000 + ((target_frequency - 5100'000'000) / 10);
|
|
#else
|
|
if (target_frequency < 3600'000'000)
|
|
return (2170'000'000 + (((target_frequency - 2740'000'000) * 57) / 86));
|
|
else if (target_frequency < 5100'000'000)
|
|
return (2350'000'000 + ((target_frequency - 3600'000'000) / 5));
|
|
else
|
|
return (2500'000'000 + ((target_frequency - 5100'000'000) / 9));
|
|
#endif
|
|
}
|
|
|
|
Config high_band(const rf::Frequency target_frequency) {
|
|
const rf::Frequency second_lo_frequency = high_band_second_lo_frequency(target_frequency);
|
|
const rf::Frequency first_lo_frequency = target_frequency - second_lo_frequency;
|
|
const bool mixer_invert = false;
|
|
return {first_lo_frequency, second_lo_frequency, rf::path::Band::High, mixer_invert};
|
|
}
|
|
|
|
Config create(const rf::Frequency target_frequency, const uint32_t afe_rate, const bool transmit) {
|
|
/* TODO: This is some lame code. */
|
|
if (rf::path::band_low.contains(target_frequency)) {
|
|
return low_band(target_frequency, afe_rate, transmit);
|
|
} else if (rf::path::band_mid.contains(target_frequency)) {
|
|
return mid_band(target_frequency, afe_rate, transmit);
|
|
} else if (rf::path::band_high.contains(target_frequency)) {
|
|
return high_band(target_frequency);
|
|
} else {
|
|
return {};
|
|
}
|
|
}
|
|
|
|
} /* namespace config */
|
|
} /* namespace tuning */
|