mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2026-08-21 06:59:02 +00:00
fix ADSB RX weak signal on pro (#3282)
This commit is contained in:
@@ -532,6 +532,18 @@ ADSBRxView::ADSBRxView(NavigationView& nav) {
|
||||
logger = std::make_unique<ADSBLogger>();
|
||||
logger->append(logs_dir / u"ADSB.TXT");
|
||||
|
||||
/* First run only: start from the configuration that is known to receive
|
||||
* ADS-B on this hardware -- LNA 32, VGA 32, RF amp ON. The first two are
|
||||
* already ReceiverModel's defaults; the amp is not, and running without it
|
||||
* costs about 14 dB, which is the difference between a busy list and an
|
||||
* empty one. Once the user has saved settings for this app their choice
|
||||
* wins, so this only sets the starting point.
|
||||
* Going through the field rather than receiver_model keeps the displayed
|
||||
* value in step: RFAmpField snapshots rf_amp() in its own constructor,
|
||||
* which has already run by the time we get here. */
|
||||
if (!settings_.loaded())
|
||||
field_rf_amp.set_value(1);
|
||||
|
||||
receiver_model.enable();
|
||||
baseband::set_adsb();
|
||||
|
||||
|
||||
@@ -907,12 +907,24 @@ void ClockManager::set_sampling_frequency(const uint32_t frequency) {
|
||||
// Set FPGA RX decimation register
|
||||
fpga_debug_register_write(FPGA_REG_DECIM, n);
|
||||
|
||||
/* RX Mode: Register 3 is FPGA_REG_RX_DIGITAL_GAIN.
|
||||
* We shift up by (3 * n) to compensate for CIC bit-growth.
|
||||
/* No RX digital-gain register is written here.
|
||||
*
|
||||
* Register 0x03 used to be programmed with (3 * n + 2) as a "CIC
|
||||
* bit-growth" renormalisation. The gateware has no such register: the
|
||||
* RX decimator is a chain of unity-gain half-band FIRs selected by
|
||||
* rx_decim (fpga/top/standard.py), and 0x03 is rx_pstep, whose top two
|
||||
* bits are the quarter-rate shift. Writing a gain here silently
|
||||
* cancelled the shift that set_tuning_frequency() had programmed, which
|
||||
* left the analogue passband offset with no matching rotation.
|
||||
*
|
||||
* The shift depends on the AFE rate we just chose, so re-apply it after
|
||||
* the rate change. ReceiverModel::update_sampling_rate() calls
|
||||
* update_tuning_frequency() straight after this, which does exactly
|
||||
* that; the write below only keeps the register consistent in between.
|
||||
*/
|
||||
uint8_t ds = (3 * n);
|
||||
ds += 2;
|
||||
fpga_debug_register_write(FPGA_REG_RX_DIGITAL_GAIN, ds);
|
||||
fpga_debug_register_write(
|
||||
FPGA_REG_RX_PSTEP,
|
||||
(radio::debug::get_cached_quarter_shift() & 0b11) << FPGA_RX_QUARTER_SHIFT_SHIFT);
|
||||
|
||||
// Re-enable FPGA processing with clean state ===
|
||||
fpga_debug_register_write(1, 0x01);
|
||||
|
||||
@@ -144,6 +144,10 @@ static rf::Direction cached_direction = rf::Direction::Receive;
|
||||
static bool cached_rf_amp = false;
|
||||
static int_fast8_t cached_lna_gain = 0;
|
||||
static int_fast8_t cached_vga_gain = 0;
|
||||
/* FPGA quarter-rate shift mode currently programmed, in gateware encoding
|
||||
* (0b00 none / 0b11 up / 0b01 down). The baseband filter width depends on it,
|
||||
* so ReceiverModel reads it back through get_quarter_shift(). */
|
||||
static uint8_t cached_quarter_shift = 0;
|
||||
#endif
|
||||
|
||||
void init() {
|
||||
@@ -182,18 +186,15 @@ void init() {
|
||||
|
||||
fpga_set_mode(FPGA_MODE_RX);
|
||||
|
||||
// These FPGA registers control DC_BLOCK, Q-Inv, QUARTER SHIFT, and Decimation.
|
||||
fpga_debug_register_write(FPGA_REG_CTRL, FPGA_CTRL_DC_BLOCK_EN); // DC_BLOCK=1, QUARTER_SHIFT=0, Q_INVERT=0
|
||||
fpga_debug_register_write(FPGA_REG_DECIM, 0x00); // RX_DECIM=No Decim
|
||||
|
||||
// RX Mode: Register 3 is RX Digital Gain. Start with 0dB (no shift).
|
||||
fpga_debug_register_write(FPGA_REG_RX_DIGITAL_GAIN, FPGA_RX_DEFAULT_DIGITAL_GAIN);
|
||||
|
||||
/* RX Mode: Initialize DC Block parameters to standard Praline values.
|
||||
* 0x04 Width and 0x08 Adapt Rate are typical for 40MHz stability.
|
||||
*/
|
||||
fpga_debug_register_write(FPGA_REG_RX_DC_BLOCK_WIDTH, FPGA_RX_DEFAULT_DC_WIDTH);
|
||||
fpga_debug_register_write(FPGA_REG_RX_DC_ADAPT_RATE, FPGA_RX_DEFAULT_ADAPT_RATE);
|
||||
/* Boot register state, matching fpga_init() in hackrf/firmware/common/fpga.c:
|
||||
* DC block on, no PRBS, no external trigger, no quarter shift, TX NCO off.
|
||||
* The decimation ratio and the quarter shift are programmed later by
|
||||
* ClockManager::set_sampling_frequency() and set_tuning_frequency(). */
|
||||
fpga_debug_register_write(FPGA_REG_CTRL, FPGA_CTRL_DC_BLOCK_EN);
|
||||
fpga_debug_register_write(FPGA_REG_DECIM, 0x00); // RX_DECIM = no decimation
|
||||
fpga_debug_register_write(FPGA_REG_RX_PSTEP, 0x00); // quarter shift off
|
||||
fpga_debug_register_write(FPGA_REG_TX_CONTROL, 0x00);
|
||||
cached_quarter_shift = 0;
|
||||
|
||||
ssp1_arbiter.invalidate();
|
||||
chThdSleepMilliseconds(10); // Let FPGA registers settle
|
||||
@@ -224,13 +225,13 @@ void set_direction(const rf::Direction new_direction) {
|
||||
fpga_debug_register_write(FPGA_REG_TX_PHASE_STEP, 0x00);
|
||||
} else {
|
||||
fpga_set_mode(FPGA_MODE_RX);
|
||||
// RX Mode: Ensure NCO is disabled and reset digital gain
|
||||
fpga_debug_register_write(FPGA_REG_RX_DIGITAL_GAIN, FPGA_RX_DEFAULT_DIGITAL_GAIN);
|
||||
/* RX Mode: Initialize DC Block parameters to standard Praline values.
|
||||
* 0x04 Width and 0x08 Adapt Rate are typical for 40MHz stability.
|
||||
*/
|
||||
fpga_debug_register_write(FPGA_REG_RX_DC_BLOCK_WIDTH, FPGA_RX_DEFAULT_DC_WIDTH);
|
||||
fpga_debug_register_write(FPGA_REG_RX_DC_ADAPT_RATE, FPGA_RX_DEFAULT_ADAPT_RATE);
|
||||
/* RX Mode: DC block on, TX NCO off. The quarter shift is re-applied by
|
||||
* set_tuning_frequency(); clear it here so a stale TX/RX transition
|
||||
* cannot leave a rotation programmed with no matching LO offset. */
|
||||
fpga_debug_register_write(FPGA_REG_CTRL, FPGA_CTRL_DC_BLOCK_EN);
|
||||
fpga_debug_register_write(FPGA_REG_TX_CONTROL, 0x00);
|
||||
fpga_debug_register_write(FPGA_REG_RX_PSTEP, 0x00);
|
||||
cached_quarter_shift = 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -309,7 +310,19 @@ bool set_tuning_frequency(const rf::Frequency frequency) {
|
||||
final_frequency = final_frequency + portapack::persistent_memory::config_freq_rx_correction();
|
||||
}
|
||||
|
||||
#ifdef PRALINE
|
||||
/* The PRALINE tuning tables offset the analogue passband by a quarter of
|
||||
* the ADC sample rate and have the FPGA rotate it back to DC, so the
|
||||
* planner needs to know the AFE rate. See tuning.cpp. */
|
||||
const uint32_t afe_rate = portapack::clock_manager.get_sampling_frequency()
|
||||
<< portapack::clock_manager.get_resampling_n();
|
||||
const auto tuning_config = tuning::config::create(
|
||||
final_frequency,
|
||||
afe_rate,
|
||||
direction == rf::Direction::Transmit);
|
||||
#else
|
||||
const auto tuning_config = tuning::config::create(final_frequency);
|
||||
#endif
|
||||
if (tuning_config.is_valid()) {
|
||||
first_if.disable();
|
||||
|
||||
@@ -339,6 +352,22 @@ bool set_tuning_frequency(const rf::Frequency frequency) {
|
||||
LPC_GPIO->CLR[0] = (1 << 13); // SGPIO12 = 0 (Q normal)
|
||||
}
|
||||
|
||||
/* Program the FPGA's quarter-rate shift to match the offset the tuning
|
||||
* table just applied to the analogue centre frequency. The gateware
|
||||
* (hackrf/firmware/fpga/top/standard.py) takes both bits from the top
|
||||
* of register 0x03 (rx_pstep):
|
||||
* rx_pstep[6] -> quarter_shift.enable
|
||||
* rx_pstep[7] -> quarter_shift.up
|
||||
* which is exactly fpga_set_rx_quarter_shift_mode() in
|
||||
* hackrf/firmware/common/fpga.c: write (mode & 0b11) << 6.
|
||||
*
|
||||
* These two settings MUST be programmed together. Tuning off-centre
|
||||
* without the rotation puts the signal outside the decimation filter's
|
||||
* passband and it disappears entirely; rotating without the offset
|
||||
* moves the wanted signal off DC by the same amount. */
|
||||
cached_quarter_shift = tuning_config.quarter_shift;
|
||||
fpga_debug_register_write(FPGA_REG_RX_PSTEP, (cached_quarter_shift & 0b11) << 6);
|
||||
|
||||
ssp1_arbiter.invalidate();
|
||||
#else
|
||||
baseband_cpld.set_invert(mixer_invert ^ baseband_invert);
|
||||
@@ -469,6 +498,10 @@ int_fast8_t get_cached_lna_gain() {
|
||||
int_fast8_t get_cached_vga_gain() {
|
||||
return cached_vga_gain;
|
||||
}
|
||||
|
||||
uint8_t get_cached_quarter_shift() {
|
||||
return cached_quarter_shift;
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace first_if {
|
||||
@@ -570,18 +603,11 @@ void register_write(const size_t register_number, uint32_t value) {
|
||||
void init() {
|
||||
fpga_set_mode(FPGA_MODE_RX);
|
||||
|
||||
// These FPGA registers control DC_BLOCK, Q-Inv, QUARTER SHIFT, and Decimation.
|
||||
fpga_debug_register_write(FPGA_REG_CTRL, FPGA_CTRL_DC_BLOCK_EN); // DC_BLOCK=1, QUARTER_SHIFT=0, Q_INVERT=0
|
||||
fpga_debug_register_write(FPGA_REG_DECIM, 0x00); // RX_DECIM=No Decim
|
||||
|
||||
// RX Mode: Register 3 is RX Digital Gain. Start with 0dB (no shift).
|
||||
fpga_debug_register_write(FPGA_REG_RX_DIGITAL_GAIN, FPGA_RX_DEFAULT_DIGITAL_GAIN);
|
||||
|
||||
/* RX Mode: Initialize DC Block parameters to standard Praline values.
|
||||
* 0x04 Width and 0x08 Adapt Rate are typical for 40MHz stability.
|
||||
*/
|
||||
fpga_debug_register_write(FPGA_REG_RX_DC_BLOCK_WIDTH, FPGA_RX_DEFAULT_DC_WIDTH);
|
||||
fpga_debug_register_write(FPGA_REG_RX_DC_ADAPT_RATE, FPGA_RX_DEFAULT_ADAPT_RATE);
|
||||
/* Same boot state as fpga_init() in hackrf/firmware/common/fpga.c. */
|
||||
fpga_debug_register_write(FPGA_REG_CTRL, FPGA_CTRL_DC_BLOCK_EN);
|
||||
fpga_debug_register_write(FPGA_REG_DECIM, 0x00); // RX_DECIM = no decimation
|
||||
fpga_debug_register_write(FPGA_REG_RX_PSTEP, 0x00); // quarter shift off
|
||||
fpga_debug_register_write(FPGA_REG_TX_CONTROL, 0x00);
|
||||
|
||||
ssp1_arbiter.invalidate(); // Force arbiter to reconfigure on next transfer
|
||||
}
|
||||
|
||||
@@ -126,6 +126,10 @@ rf::Direction get_cached_direction();
|
||||
bool get_cached_rf_amp();
|
||||
int_fast8_t get_cached_lna_gain();
|
||||
int_fast8_t get_cached_vga_gain();
|
||||
|
||||
/* FPGA RX quarter-rate shift currently programmed, in gateware encoding:
|
||||
* 0b00 none, 0b11 up, 0b01 down. */
|
||||
uint8_t get_cached_quarter_shift();
|
||||
#endif
|
||||
|
||||
namespace sgpio {
|
||||
|
||||
@@ -244,10 +244,24 @@ void ReceiverModel::enable() {
|
||||
radio::set_direction(rf::Direction::Receive);
|
||||
|
||||
#ifdef PRALINE
|
||||
/* Anchor the Common Mode Voltage (VCM) to 1.2V.
|
||||
* This stabilizes the electrical floor of the I/Q signals.
|
||||
*/
|
||||
radio::set_rx_buff_vcm(1);
|
||||
/* MAX2831 RX IQ common-mode voltage (register 15).
|
||||
*
|
||||
* 0 = 1.1 V, 1 = 1.2 V, 2 = 1.3 V, 3 = 1.45 V.
|
||||
*
|
||||
* The reference firmware leaves this alone: max2831.c's default register
|
||||
* table has reg 15 = 0x0145 (1.1 V) and the line that would raise it is
|
||||
* commented out ("maximum rx output common-mode voltage"). Mayhem used to
|
||||
* force 1.2 V here on the theory that it "stabilises the electrical floor
|
||||
* of the I/Q signals" -- plausible, but never measured, and it was the
|
||||
* last remaining RF-path setting where this branch disagreed with the
|
||||
* configuration that is proven to receive ADS-B on this board.
|
||||
*
|
||||
* Set to 0 to match the reference (writing 1.1 V is a no-op against the
|
||||
* power-on default), or back to 1 to restore the old Mayhem behaviour.
|
||||
* If reception measurably worsens, put it back to 1 and say so -- neither
|
||||
* value has been verified on hardware. */
|
||||
#define PRALINE_RX_IQ_VCM 0
|
||||
radio::set_rx_buff_vcm(PRALINE_RX_IQ_VCM);
|
||||
#endif
|
||||
|
||||
update_tuning_frequency();
|
||||
@@ -326,47 +340,39 @@ void ReceiverModel::update_baseband_bandwidth() {
|
||||
if (enabled_) {
|
||||
#ifdef PRALINE
|
||||
/*
|
||||
* PRALINE LPF bandwidth calculation from GSG hackrf_usb radio.c
|
||||
* PRALINE LPF bandwidth, ported from auto_bandwidth() in
|
||||
* hackrf/firmware/common/radio.c:
|
||||
*
|
||||
* The LPF should be set to capture the desired signal bandwidth
|
||||
* while the FPGA decimation filter handles anti-aliasing.
|
||||
* bb_bandwidth = sample_rate * 3 / 4
|
||||
* lpf_bandwidth = bb_bandwidth + offset_hz * 2
|
||||
*
|
||||
* For most modes: LPF = (output_sample_rate * 3) / 8
|
||||
* For quarter-shift: add offset for shifted spectrum
|
||||
* where offset_hz is the quarter-rate shift, i.e. afe_rate / 4 when a
|
||||
* shift is in use. The doubling is because the wanted signal sits
|
||||
* offset from the analogue centre, so the analogue filter has to stay
|
||||
* open out to that offset on the far side too.
|
||||
*
|
||||
* Note: MAX2831 minimum LPF is 11.6 MHz, so for narrow sample rates
|
||||
* the hardware limit applies and FPGA filter does the real work.
|
||||
* The previous version used /8 in both places and read the shift from
|
||||
* bits 2-3 of FPGA register 1, which do not exist in the gateware, so
|
||||
* it always took the no-shift branch. At the ADS-B rate that asked for
|
||||
* 750 kHz, which is below the MAX2831's 1.75 MHz floor and therefore
|
||||
* also switched in the external narrowband AA filter
|
||||
* (MAX2831::set_lpf_rf_bandwidth_rx), squeezing the RX path shut. The
|
||||
* reference asks for 17.5 MHz at the same rate and the AA filter stays
|
||||
* out of circuit.
|
||||
*/
|
||||
|
||||
uint32_t sample_rate = sampling_rate();
|
||||
uint8_t resampling_n = portapack::clock_manager.get_resampling_n();
|
||||
uint32_t afe_rate = sample_rate << resampling_n;
|
||||
|
||||
// Base LPF: enough to capture desired bandwidth
|
||||
uint32_t lpf_bandwidth = (sample_rate * 3) / 8;
|
||||
|
||||
// Check if quarter-shift is enabled (FPGA register 1, bits 2-3)
|
||||
uint32_t fpga_ctrl = radio::debug::fpga::register_read(1);
|
||||
uint8_t quarter_shift = (fpga_ctrl >> 2) & 0x03;
|
||||
uint32_t lpf_bandwidth = (sample_rate * 3) / 4;
|
||||
|
||||
const uint8_t quarter_shift = radio::debug::get_cached_quarter_shift();
|
||||
if (quarter_shift != 0) {
|
||||
// Quarter-shift moves spectrum by AFE_rate/4, need wider LPF
|
||||
uint32_t offset = afe_rate / 8;
|
||||
const uint32_t offset = afe_rate / 4;
|
||||
lpf_bandwidth += offset * 2;
|
||||
}
|
||||
|
||||
// For best anti-alias performance, also consider AFE Nyquist
|
||||
// If our calculated LPF is below MAX2831 minimum, it doesn't matter
|
||||
// But if we can set LPF to just below AFE Nyquist, that's optimal
|
||||
uint32_t afe_nyquist = afe_rate / 2;
|
||||
|
||||
// Use the larger of: signal bandwidth requirement OR Nyquist protection
|
||||
// (but MAX2831 driver will clamp to its available settings anyway)
|
||||
if (lpf_bandwidth < afe_nyquist) {
|
||||
// Set LPF close to Nyquist for maximum alias rejection
|
||||
lpf_bandwidth = (afe_nyquist * 9) / 10; // 90% of Nyquist
|
||||
}
|
||||
|
||||
radio::set_baseband_filter_bandwidth_rx(lpf_bandwidth);
|
||||
#else
|
||||
radio::set_baseband_filter_bandwidth_rx(baseband_bandwidth());
|
||||
|
||||
+332
-99
@@ -27,8 +27,8 @@ namespace tuning {
|
||||
namespace config {
|
||||
|
||||
// Forward declarations
|
||||
Config low_band(const rf::Frequency target_frequency);
|
||||
Config mid_band(const rf::Frequency target_frequency);
|
||||
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
|
||||
@@ -36,133 +36,366 @@ Config high_band(const rf::Frequency target_frequency);
|
||||
* PRALINE Tuning Configuration
|
||||
* ============================
|
||||
*
|
||||
* Reference: hackrf_usb/common/tune_config.h praline_tune_config_rx[]
|
||||
* 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().
|
||||
*
|
||||
* The hackrf_usb firmware uses a table-driven approach where each entry
|
||||
* specifies:
|
||||
* - rf_range_end_mhz: Upper frequency limit for this config
|
||||
* - if_mhz: IF frequency (what MAX2831 tunes to)
|
||||
* - high_lo: true = high-side injection, false = low-side
|
||||
* - shift: FPGA quarter-shift mode (not implemented in Mayhem yet)
|
||||
* 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
|
||||
*
|
||||
* Key insight: The IF frequency varies to keep the RFFC5072 VCO in a
|
||||
* safe operating range (ideally 3500-5000 MHz, avoiding extremes).
|
||||
* 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.
|
||||
*
|
||||
* RFFC5072 VCO calculation:
|
||||
* High-side injection: LO = IF + RF, VCO = LO × lodiv
|
||||
* Low-side injection: LO = IF - RF, VCO = LO × lodiv
|
||||
* Where lodiv = 2 for frequencies where VCO > 2700 MHz
|
||||
*
|
||||
* From hackrf_usb tune_config_rx (simplified):
|
||||
* 0-2100 MHz: IF=2375, high_lo=true → VCO = (2375+RF)×2
|
||||
* 2105-2115: IF=2375, high_lo=false → VCO = (2375-RF)×2
|
||||
* 2115-2130: IF=2425, high_lo=false → VCO = (2425-RF)×2
|
||||
* ... (more entries for fine-grained control)
|
||||
* 2320-2580: IF=0 (bypass mode, no mixer)
|
||||
* 2580+: High-pass mode
|
||||
* "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()).
|
||||
*/
|
||||
|
||||
// Simplified tune_config lookup for Mayhem
|
||||
// Returns the IF frequency in Hz for a given target frequency
|
||||
constexpr rf::Frequency praline_get_if_frequency(const rf::Frequency target_frequency) {
|
||||
const uint32_t freq_mhz = target_frequency / 1'000'000;
|
||||
namespace {
|
||||
|
||||
// Based on hackrf_usb tune_config_rx table
|
||||
if (freq_mhz < 2100) {
|
||||
// Most low-band frequencies: use 2375 MHz IF
|
||||
// This keeps VCO around 4750-4950 MHz for FM band
|
||||
return 2375'000'000;
|
||||
} else if (freq_mhz < 2320) {
|
||||
// Transition zone: use varying IF to avoid VCO edges
|
||||
// These frequencies are tricky - near MAX2831 minimum
|
||||
// Use 2425 MHz to give some margin
|
||||
return 2425'000'000;
|
||||
} else {
|
||||
// Bypass mode or high-band - IF not used for mixer
|
||||
return 0;
|
||||
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;
|
||||
}
|
||||
|
||||
// Returns true for high-side injection, false for low-side
|
||||
constexpr bool praline_use_high_side_injection(const rf::Frequency target_frequency) {
|
||||
const uint32_t freq_mhz = target_frequency / 1'000'000;
|
||||
|
||||
// Based on hackrf_usb tune_config_rx table
|
||||
if (freq_mhz < 2100) {
|
||||
// Standard low-band: high-side injection
|
||||
// LO = IF + RF, mixer inverts spectrum
|
||||
return true;
|
||||
} else if (freq_mhz < 2105) {
|
||||
// Narrow transition: still high-side
|
||||
return true;
|
||||
} else if (freq_mhz < 2320) {
|
||||
// Near MAX2831 minimum: use low-side injection
|
||||
// LO = IF - RF, no spectrum inversion
|
||||
return false;
|
||||
} else {
|
||||
// Bypass/high-band - doesn't matter, mixer bypassed
|
||||
return false;
|
||||
}
|
||||
/* 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
|
||||
|
||||
// Low band <2170 Mhz (HackRF One) or <2320 MHz (PRALINE):
|
||||
constexpr rf::Frequency low_band_second_lo_frequency(const rf::Frequency target_frequency) {
|
||||
Config low_band(const rf::Frequency target_frequency, const uint32_t afe_rate, const bool transmit) {
|
||||
#ifdef PRALINE
|
||||
// Use the tune_config lookup for PRALINE
|
||||
return praline_get_if_frequency(target_frequency);
|
||||
#else
|
||||
return 2650'000'000 - (target_frequency / 7);
|
||||
#endif
|
||||
}
|
||||
const PralineTuneConfig* entry = select_tune_config(target_frequency, transmit);
|
||||
|
||||
Config low_band(const rf::Frequency target_frequency) {
|
||||
const rf::Frequency second_lo_frequency = low_band_second_lo_frequency(target_frequency);
|
||||
/* Past the end of the table: no usable configuration. */
|
||||
if ((entry->rf_range_end_mhz == 0) && (entry->if_mhz == 0))
|
||||
return {};
|
||||
|
||||
#ifdef PRALINE
|
||||
/* 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 (praline_use_high_side_injection(target_frequency)) {
|
||||
// High-side injection: LO = IF + RF
|
||||
first_lo_frequency = second_lo_frequency + target_frequency;
|
||||
if (entry->high_lo) {
|
||||
first_lo_frequency = second_lo_frequency + analog_rf;
|
||||
mixer_invert = true;
|
||||
} else {
|
||||
// Low-side injection: LO = IF - RF
|
||||
first_lo_frequency = second_lo_frequency - target_frequency;
|
||||
first_lo_frequency = second_lo_frequency - analog_rf;
|
||||
mixer_invert = false;
|
||||
}
|
||||
|
||||
return {first_lo_frequency, second_lo_frequency, rf::path::Band::Low, mixer_invert};
|
||||
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-2580 MHz (PRALINE):
|
||||
Config mid_band(const rf::Frequency target_frequency) {
|
||||
// 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
|
||||
// For Praline with MAX2831 (2.3-2.6 GHz range)
|
||||
// Frequencies 2170-2300 MHz need upconversion since they're below MAX2831 minimum
|
||||
if (target_frequency < 2300'000'000) {
|
||||
// Treat as low band - need mixer
|
||||
return low_band(target_frequency);
|
||||
}
|
||||
// Frequencies 2300-2600 MHz can go direct (no RFFC5072)
|
||||
else if (target_frequency <= 2600'000'000) {
|
||||
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};
|
||||
}
|
||||
// Frequencies 2600-2740 MHz need downconversion since they're above MAX2831 maximum
|
||||
else {
|
||||
// Treat as high band
|
||||
return high_band(target_frequency);
|
||||
/* 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;
|
||||
@@ -203,12 +436,12 @@ Config high_band(const rf::Frequency target_frequency) {
|
||||
return {first_lo_frequency, second_lo_frequency, rf::path::Band::High, mixer_invert};
|
||||
}
|
||||
|
||||
Config create(const rf::Frequency target_frequency) {
|
||||
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);
|
||||
return low_band(target_frequency, afe_rate, transmit);
|
||||
} else if (rf::path::band_mid.contains(target_frequency)) {
|
||||
return mid_band(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 {
|
||||
|
||||
@@ -33,18 +33,21 @@ struct Config {
|
||||
: first_lo_frequency(0),
|
||||
second_lo_frequency(0),
|
||||
rf_path_band(rf::path::Band::Mid),
|
||||
mixer_invert(false) {
|
||||
mixer_invert(false),
|
||||
quarter_shift(0) {
|
||||
}
|
||||
|
||||
constexpr Config(
|
||||
rf::Frequency first_lo_frequency,
|
||||
rf::Frequency second_lo_frequency,
|
||||
rf::path::Band rf_path_band,
|
||||
bool mixer_invert)
|
||||
bool mixer_invert,
|
||||
uint8_t quarter_shift = 0)
|
||||
: first_lo_frequency(first_lo_frequency),
|
||||
second_lo_frequency(second_lo_frequency),
|
||||
rf_path_band(rf_path_band),
|
||||
mixer_invert(mixer_invert) {
|
||||
mixer_invert(mixer_invert),
|
||||
quarter_shift(quarter_shift) {
|
||||
}
|
||||
|
||||
bool is_valid() const {
|
||||
@@ -55,9 +58,23 @@ struct Config {
|
||||
const rf::Frequency second_lo_frequency;
|
||||
const rf::path::Band rf_path_band;
|
||||
const bool mixer_invert;
|
||||
|
||||
/* PRALINE only: FPGA RX quarter-rate shift mode, in the encoding the
|
||||
* gateware expects in the top two bits of register 0x03 (rx_pstep):
|
||||
* 0b00 = none, 0b11 = up, 0b01 = down.
|
||||
* Matches fpga_quarter_shift_mode_t in hackrf/firmware/common/fpga.h.
|
||||
* Always 0 on HackRF One (no FPGA). */
|
||||
const uint8_t quarter_shift;
|
||||
};
|
||||
|
||||
Config create(const rf::Frequency target_frequency);
|
||||
/* afe_rate is the ADC sample rate in Hz (output rate << decimation), needed on
|
||||
* PRALINE to work out how far off centre the quarter-rate shift places the
|
||||
* analogue passband. Pass 0 (or leave defaulted) to disable the shift.
|
||||
* transmit selects the TX tuning table. Both are ignored on HackRF One. */
|
||||
Config create(
|
||||
const rf::Frequency target_frequency,
|
||||
const uint32_t afe_rate = 0,
|
||||
const bool transmit = false);
|
||||
|
||||
} /* namespace config */
|
||||
} /* namespace tuning */
|
||||
|
||||
@@ -125,10 +125,11 @@
|
||||
// ============================================================================
|
||||
// Canonical Default Values - SINGLE SOURCE OF TRUTH
|
||||
// ============================================================================
|
||||
/* Define the canonical RX defaults in ONE place */
|
||||
#define FPGA_RX_DEFAULT_DC_WIDTH 0x04 /* Typical for 40MHz stability */
|
||||
#define FPGA_RX_DEFAULT_ADAPT_RATE 0x08 /* Typical for 40MHz stability */
|
||||
#define FPGA_RX_DEFAULT_DIGITAL_GAIN 0x00 /* No shift initially */
|
||||
/* Define the canonical RX defaults in ONE place.
|
||||
* Matches fpga_init() in hackrf/firmware/common/fpga.c: the gateware only has
|
||||
* rx_decim and rx_pstep on the RX side, both starting at zero. */
|
||||
#define FPGA_RX_DEFAULT_DECIM 0x00 /* No decimation initially */
|
||||
#define FPGA_RX_DEFAULT_PSTEP 0x00 /* No quarter-rate shift initially */
|
||||
|
||||
/* Define TX defaults */
|
||||
#define FPGA_TX_DEFAULT_NCO_CTRL 0x00 /* NCO disabled */
|
||||
@@ -221,12 +222,13 @@ static bool fpga_cdone_read(void) {
|
||||
// These functions allow reading/writing FPGA internal registers via SPI.
|
||||
// The FPGA bitstream implements a simple SPI register interface.
|
||||
//
|
||||
// FPGA Register Map:
|
||||
// Reg 1 (CTRL): DC_BLOCK(b0), QUARTER_SHIFT_EN(b1), QUARTER_SHIFT_UP(b2), PRBS(b6), TRIGGER_EN(b7)
|
||||
// Reg 2 (RX_DECIM): Decimation ratio [2:0]
|
||||
// Reg 3 (RX/TX): RX Digital Shift OR TX NCO Control
|
||||
// Reg 4 (RX_DC_BLOCK_WIDTH/TX_INTERP) [2:0]
|
||||
// Reg 5 (RX_DC_ADAPT_RATE/TX_PSTEP) [7:0]
|
||||
// FPGA Register Map (hackrf/firmware/fpga/top/standard.py):
|
||||
// Reg 1 (CTRL): DC_BLOCK(b0), PRBS(b6), TRIGGER_EN(b7)
|
||||
// Reg 2 (RX_DECIM): Decimation ratio, log2 [2:0]
|
||||
// Reg 3 (RX_PSTEP): QUARTER_SHIFT_EN(b6), QUARTER_SHIFT_UP(b7)
|
||||
// Reg 4 (TX_CTRL): NCO enable (b0)
|
||||
// Reg 5 (TX_INTRP): Interpolation ratio [2:0]
|
||||
// Reg 6 (TX_PSTEP): NCO phase step [7:0]
|
||||
//
|
||||
// SPI Protocol:
|
||||
// Read: Send [reg & 0x7F, 0x00, 0x00] -> value in byte 3
|
||||
@@ -369,22 +371,16 @@ void fpga_rx_enable_dc_block(bool enable) {
|
||||
}
|
||||
|
||||
/* RX Functions with mode assertion */
|
||||
void fpga_rx_set_digital_gain(uint8_t shift) {
|
||||
|
||||
/* Quarter-rate shift, register 0x03 bits [7:6]. Equivalent to
|
||||
* fpga_set_rx_quarter_shift_mode() in hackrf/firmware/common/fpga.c.
|
||||
* mode: 0b00 none, 0b11 up, 0b01 down. */
|
||||
void fpga_rx_set_quarter_shift_mode(uint8_t mode) {
|
||||
if (current_mode != FPGA_MODE_RX) {
|
||||
/* Log error or assert - wrong mode! */
|
||||
return;
|
||||
}
|
||||
fpga_register_write(FPGA_REG_SHARED_3, shift & FPGA_RX_GAIN_SHIFT_MASK);
|
||||
}
|
||||
|
||||
void fpga_rx_set_dc_block_width(uint8_t width) {
|
||||
if (current_mode != FPGA_MODE_RX) return;
|
||||
fpga_register_write(FPGA_REG_SHARED_4, width & FPGA_RX_DC_WIDTH_MASK);
|
||||
}
|
||||
|
||||
void fpga_rx_set_dc_adapt_rate(uint8_t rate) {
|
||||
if (current_mode != FPGA_MODE_RX) return;
|
||||
fpga_register_write(FPGA_REG_SHARED_5, rate);
|
||||
fpga_register_write(FPGA_REG_RX_PSTEP, (uint8_t)((mode & 0x03) << FPGA_RX_QUARTER_SHIFT_SHIFT));
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
@@ -394,12 +390,12 @@ void fpga_rx_set_dc_adapt_rate(uint8_t rate) {
|
||||
/* TX Functions with mode assertion */
|
||||
void fpga_tx_set_nco_enable(bool enable) {
|
||||
if (current_mode != FPGA_MODE_TX) return;
|
||||
uint8_t val = fpga_register_read(FPGA_REG3_TX_NCO_CTRL);
|
||||
uint8_t val = fpga_register_read(FPGA_REG_TX_CONTROL);
|
||||
if (enable)
|
||||
val |= FPGA_TX_NCO_EN;
|
||||
else
|
||||
val &= ~FPGA_TX_NCO_EN;
|
||||
fpga_register_write(FPGA_REG3_TX_NCO_CTRL, val);
|
||||
fpga_register_write(FPGA_REG_TX_CONTROL, val);
|
||||
}
|
||||
|
||||
void fpga_tx_set_interpolation(uint8_t ratio) {
|
||||
@@ -424,17 +420,17 @@ static void fpga_register_init(void) {
|
||||
current_mode = FPGA_MODE_RX;
|
||||
|
||||
fpga_spi_write(FPGA_REG_CTRL, FPGA_CTRL_DC_BLOCK_EN);
|
||||
fpga_spi_write(FPGA_REG_DECIM, 0x00);
|
||||
fpga_spi_write(FPGA_REG_SHARED_3, FPGA_RX_DEFAULT_DIGITAL_GAIN);
|
||||
fpga_spi_write(FPGA_REG_SHARED_4, FPGA_RX_DEFAULT_DC_WIDTH);
|
||||
fpga_spi_write(FPGA_REG_SHARED_5, FPGA_RX_DEFAULT_ADAPT_RATE);
|
||||
fpga_spi_write(FPGA_REG_DECIM, FPGA_RX_DEFAULT_DECIM);
|
||||
fpga_spi_write(FPGA_REG_RX_PSTEP, FPGA_RX_DEFAULT_PSTEP);
|
||||
fpga_spi_write(FPGA_REG_TX_CONTROL, FPGA_TX_DEFAULT_NCO_CTRL);
|
||||
fpga_spi_write(FPGA_REG_TX_INTERP, FPGA_TX_DEFAULT_INTERP);
|
||||
|
||||
/* Update cache */
|
||||
fpga_reg_cache[1] = FPGA_CTRL_DC_BLOCK_EN;
|
||||
fpga_reg_cache[2] = 0x00;
|
||||
fpga_reg_cache[3] = FPGA_RX_DEFAULT_DIGITAL_GAIN;
|
||||
fpga_reg_cache[4] = FPGA_RX_DEFAULT_DC_WIDTH;
|
||||
fpga_reg_cache[5] = FPGA_RX_DEFAULT_ADAPT_RATE;
|
||||
fpga_reg_cache[2] = FPGA_RX_DEFAULT_DECIM;
|
||||
fpga_reg_cache[3] = FPGA_RX_DEFAULT_PSTEP;
|
||||
fpga_reg_cache[4] = FPGA_TX_DEFAULT_NCO_CTRL;
|
||||
fpga_reg_cache[5] = FPGA_TX_DEFAULT_INTERP;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
@@ -16,10 +16,23 @@ extern "C" {
|
||||
|
||||
#ifdef PRALINE
|
||||
|
||||
/* RX path (legacy PRALINE software map kept for compatibility) */
|
||||
#define FPGA_REG_RX_DIGITAL_GAIN 0x03 /* Digital Shift / scaling (RX Mode) */
|
||||
#define FPGA_REG_RX_DC_BLOCK_WIDTH 0x04 /* Notch filter cutoff (RX Mode) */
|
||||
#define FPGA_REG_RX_DC_ADAPT_RATE 0x05 /* Settle time/Integration (RX Mode) */
|
||||
/*
|
||||
* RX path.
|
||||
*
|
||||
* NOTE: the register map below is the one the loaded bitstream actually
|
||||
* implements. Ground truth is hackrf/firmware/fpga/top/standard.py plus
|
||||
* hackrf/firmware/common/fpga_regs.def; Mayhem, hackrf_usb and debug_for_adsb
|
||||
* all load a byte-identical praline_fpga.bin, so that map applies here too.
|
||||
*
|
||||
* An earlier "legacy PRALINE software map" claimed register 0x03 was an RX
|
||||
* digital gain and 0x04/0x05 were DC-block width / adaptation rate. The
|
||||
* gateware has none of those: 0x03 is rx_pstep (whose top two bits are the
|
||||
* quarter-rate shift) and 0x04/0x05 are TX registers. Writing the old "RX DC
|
||||
* width" value of 0x01 to 0x04 actually set tx_ctrl[0] and switched the TX NCO
|
||||
* on, and every write of the fictional digital gain to 0x03 cleared the
|
||||
* quarter shift.
|
||||
*/
|
||||
#define FPGA_REG_RX_PSTEP 0x03 /* RX phase step; bits [7:6] = quarter shift */
|
||||
|
||||
/*
|
||||
* TX path must match the currently built PRALINE standard gateware in
|
||||
@@ -43,70 +56,55 @@ typedef enum {
|
||||
|
||||
/*
|
||||
* FPGA Register Addresses
|
||||
* NOTE:
|
||||
* The currently loaded PRALINE standard gateware uses:
|
||||
* 0x01 CTRL
|
||||
* 0x02 RX_DECIM
|
||||
* 0x03 RX_DIGITAL_GAIN (RX) / TX_NCO_CTRL (TX)
|
||||
* 0x04 TX_CTRL
|
||||
* 0x05 TX_INTRP
|
||||
* 0x06 TX_PSTEP
|
||||
*
|
||||
* From hackrf/firmware/fpga/top/standard.py (spi_regs.add_register):
|
||||
* 0x01 ctrl 8 bits
|
||||
* 0x02 rx_decim 3 bits
|
||||
* 0x03 rx_pstep 8 bits
|
||||
* 0x04 tx_ctrl 1 bit
|
||||
* 0x05 tx_intrp 3 bits
|
||||
* 0x06 tx_pstep 8 bits
|
||||
*
|
||||
* There is no RX gain register: rx_decim selects half-band FIR stages
|
||||
* (hbfir1..hbfir5), which are unity-gain, so there is no CIC bit growth to
|
||||
* renormalise.
|
||||
*/
|
||||
#define FPGA_REG_CTRL 0x01 /* Control register */
|
||||
#define FPGA_REG_DECIM 0x02 /* RX decimation */
|
||||
#define FPGA_REG_SHARED_3 0x03 /* Legacy shared register */
|
||||
#define FPGA_REG_SHARED_4 0x04 /* Legacy shared register */
|
||||
#define FPGA_REG_SHARED_5 0x05 /* Legacy shared register */
|
||||
#define FPGA_REG_SHARED_6 0x06 /* TX phase step */
|
||||
#define FPGA_REG_CTRL 0x01 /* Control register */
|
||||
#define FPGA_REG_DECIM 0x02 /* RX decimation (log2, bits [2:0]) */
|
||||
/* 0x03..0x06 are FPGA_REG_RX_PSTEP / FPGA_REG_TX_CONTROL /
|
||||
* FPGA_REG_TX_INTERP / FPGA_REG_TX_PHASE_STEP, defined above. */
|
||||
|
||||
/*
|
||||
* Register 1 (CTRL) Bit Definitions
|
||||
* standard.py: ctrl[0] -> dc_block.enable, ctrl[6] -> prbs, ctrl[7] -> trigger_en.
|
||||
* Nothing else in this register is decoded.
|
||||
*/
|
||||
#define FPGA_CTRL_DC_BLOCK_EN (1 << 0) /* DC block enable */
|
||||
#define FPGA_CTRL_QUARTER_SHIFT_EN (1 << 1) /* Quarter-rate shift enable */
|
||||
#define FPGA_CTRL_QUARTER_SHIFT_UP (1 << 2) /* Shift direction: 1=up, 0=down */
|
||||
#define FPGA_CTRL_TX_MODE (1 << 5) /* TX mode indicator (if applicable) */
|
||||
#define FPGA_CTRL_PRBS_EN (1 << 6) /* PRBS test mode */
|
||||
#define FPGA_CTRL_TRIGGER_EN (1 << 7) /* External trigger enable */
|
||||
#define FPGA_CTRL_DC_BLOCK_EN (1 << 0) /* DC block enable */
|
||||
#define FPGA_CTRL_PRBS_EN (1 << 6) /* PRBS test mode */
|
||||
#define FPGA_CTRL_TRIGGER_EN (1 << 7) /* External trigger enable */
|
||||
|
||||
/*
|
||||
* Register 3 Dual-Purpose Definitions
|
||||
* Register 3 (RX_PSTEP) Bit Definitions
|
||||
* standard.py: rx_pstep[6] -> quarter_shift.enable, rx_pstep[7] -> quarter_shift.up.
|
||||
* Same encoding as fpga_quarter_shift_mode_t << 6 in hackrf/firmware/common/fpga.c.
|
||||
*/
|
||||
/* RX Mode: Digital gain/shift */
|
||||
#define FPGA_REG3_RX_DIGITAL_GAIN 0x03
|
||||
#define FPGA_RX_GAIN_SHIFT_MASK 0x0F /* Bits [3:0] - shift amount */
|
||||
/* Position of that 2-bit field inside rx_pstep, for direct register writes. */
|
||||
#define FPGA_RX_QUARTER_SHIFT_SHIFT 6
|
||||
#define FPGA_RX_QUARTER_SHIFT_MASK 0xC0
|
||||
|
||||
/* TX Mode: NCO control */
|
||||
#define FPGA_REG3_TX_NCO_CTRL 0x03
|
||||
#define FPGA_TX_NCO_EN (1 << 0) /* NCO enable */
|
||||
#define FPGA_TX_NCO_INVERT (1 << 1) /* Invert spectrum */
|
||||
/* Mode values, matching fpga_quarter_shift_mode_t in
|
||||
* hackrf/firmware/common/fpga.h. Pass these to
|
||||
* fpga_rx_set_quarter_shift_mode(), which shifts them into place. */
|
||||
#define FPGA_QUARTER_SHIFT_MODE_NONE 0b00
|
||||
#define FPGA_QUARTER_SHIFT_MODE_UP 0b11
|
||||
#define FPGA_QUARTER_SHIFT_MODE_DOWN 0b01
|
||||
|
||||
/*
|
||||
* Register 4 Dual-Purpose Definitions
|
||||
* Register 4 (TX_CTRL) / 5 (TX_INTRP) / 6 (TX_PSTEP) Bit Definitions
|
||||
*/
|
||||
/* RX Mode: DC block notch width */
|
||||
#define FPGA_REG4_RX_DC_WIDTH 0x04
|
||||
#define FPGA_RX_DC_WIDTH_MASK 0x07 /* Bits [2:0] */
|
||||
|
||||
/* TX Mode: Interpolation ratio */
|
||||
#define FPGA_REG4_TX_INTERP 0x05
|
||||
#define FPGA_TX_INTERP_MASK 0x07 /* Bits [2:0] */
|
||||
|
||||
/*
|
||||
* Register 5 Dual-Purpose Definitions
|
||||
*/
|
||||
/* RX Mode: DC block adaptation rate */
|
||||
#define FPGA_REG5_RX_DC_RATE 0x05
|
||||
#define FPGA_RX_DC_RATE_MASK 0xFF /* Bits [7:0] */
|
||||
|
||||
/* TX Mode: NCO phase step (frequency) */
|
||||
#define FPGA_REG5_TX_PHASE_STEP 0x06
|
||||
#define FPGA_TX_PHASE_STEP_MASK 0xFF /* Bits [7:0] */
|
||||
|
||||
/* Export default values so other methods can use them */
|
||||
#define FPGA_RX_DEFAULT_DIGITAL_GAIN 0x00
|
||||
#define FPGA_RX_DEFAULT_DC_WIDTH 0x04
|
||||
#define FPGA_RX_DEFAULT_ADAPT_RATE 0x08
|
||||
#define FPGA_TX_NCO_EN (1 << 0) /* tx_ctrl[0]: NCO enable */
|
||||
#define FPGA_TX_INTERP_MASK 0x07 /* tx_intrp bits [2:0] */
|
||||
#define FPGA_TX_PHASE_STEP_MASK 0xFF /* tx_pstep bits [7:0] */
|
||||
|
||||
/*
|
||||
* Core Functions
|
||||
@@ -133,9 +131,8 @@ void fpga_register_write(uint8_t reg, uint8_t value);
|
||||
* RX Mode Functions (only valid when mode == FPGA_MODE_RX)
|
||||
*/
|
||||
void fpga_rx_set_decimation(uint8_t ratio);
|
||||
void fpga_rx_set_digital_gain(uint8_t shift);
|
||||
void fpga_rx_set_dc_block_width(uint8_t width);
|
||||
void fpga_rx_set_dc_adapt_rate(uint8_t rate);
|
||||
/* mode is the gateware encoding: 0b00 none, 0b11 up, 0b01 down. */
|
||||
void fpga_rx_set_quarter_shift_mode(uint8_t mode);
|
||||
void fpga_rx_enable_dc_block(bool enable);
|
||||
|
||||
/*
|
||||
@@ -154,12 +151,13 @@ void fpga_tx_set_phase_step(uint8_t step);
|
||||
* reg: Register number (1-5)
|
||||
* Returns: Register value, or 0xFF if invalid register
|
||||
*
|
||||
* FPGA Register Map:
|
||||
* Reg 1 (CTRL): DC_BLOCK(b0), QUARTER_SHIFT_EN(b1), QUARTER_SHIFT_UP(b2), PRBS(b6), TRIGGER_EN(b7)
|
||||
* Reg 2 (RX_DECIM): Decimation ratio [2:0]
|
||||
* Reg 3 (RX/TX): RX Digital Shift OR TX NCO Control
|
||||
* Reg 4 (RX_DC_BLOCK_WIDTH/TX_INTERP) [2:0]
|
||||
* Reg 5 (RX_DC_ADAPT_RATE/TX_PSTEP) [7:0]
|
||||
* FPGA Register Map (hackrf/firmware/fpga/top/standard.py):
|
||||
* Reg 1 (CTRL): DC_BLOCK(b0), PRBS(b6), TRIGGER_EN(b7)
|
||||
* Reg 2 (RX_DECIM): Decimation ratio, log2 [2:0]
|
||||
* Reg 3 (RX_PSTEP): QUARTER_SHIFT_EN(b6), QUARTER_SHIFT_UP(b7)
|
||||
* Reg 4 (TX_CTRL): NCO_EN(b0)
|
||||
* Reg 5 (TX_INTRP): Interpolation ratio, log2 [2:0]
|
||||
* Reg 6 (TX_PSTEP): NCO phase step [7:0]
|
||||
*/
|
||||
uint8_t fpga_debug_register_read(uint8_t reg);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user