mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2026-09-13 10:09:30 +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 = std::make_unique<ADSBLogger>();
|
||||||
logger->append(logs_dir / u"ADSB.TXT");
|
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();
|
receiver_model.enable();
|
||||||
baseband::set_adsb();
|
baseband::set_adsb();
|
||||||
|
|
||||||
|
|||||||
@@ -907,12 +907,24 @@ void ClockManager::set_sampling_frequency(const uint32_t frequency) {
|
|||||||
// Set FPGA RX decimation register
|
// Set FPGA RX decimation register
|
||||||
fpga_debug_register_write(FPGA_REG_DECIM, n);
|
fpga_debug_register_write(FPGA_REG_DECIM, n);
|
||||||
|
|
||||||
/* RX Mode: Register 3 is FPGA_REG_RX_DIGITAL_GAIN.
|
/* No RX digital-gain register is written here.
|
||||||
* We shift up by (3 * n) to compensate for CIC bit-growth.
|
*
|
||||||
|
* 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);
|
fpga_debug_register_write(
|
||||||
ds += 2;
|
FPGA_REG_RX_PSTEP,
|
||||||
fpga_debug_register_write(FPGA_REG_RX_DIGITAL_GAIN, ds);
|
(radio::debug::get_cached_quarter_shift() & 0b11) << FPGA_RX_QUARTER_SHIFT_SHIFT);
|
||||||
|
|
||||||
// Re-enable FPGA processing with clean state ===
|
// Re-enable FPGA processing with clean state ===
|
||||||
fpga_debug_register_write(1, 0x01);
|
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 bool cached_rf_amp = false;
|
||||||
static int_fast8_t cached_lna_gain = 0;
|
static int_fast8_t cached_lna_gain = 0;
|
||||||
static int_fast8_t cached_vga_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
|
#endif
|
||||||
|
|
||||||
void init() {
|
void init() {
|
||||||
@@ -182,18 +186,15 @@ void init() {
|
|||||||
|
|
||||||
fpga_set_mode(FPGA_MODE_RX);
|
fpga_set_mode(FPGA_MODE_RX);
|
||||||
|
|
||||||
// These FPGA registers control DC_BLOCK, Q-Inv, QUARTER SHIFT, and Decimation.
|
/* Boot register state, matching fpga_init() in hackrf/firmware/common/fpga.c:
|
||||||
fpga_debug_register_write(FPGA_REG_CTRL, FPGA_CTRL_DC_BLOCK_EN); // DC_BLOCK=1, QUARTER_SHIFT=0, Q_INVERT=0
|
* DC block on, no PRBS, no external trigger, no quarter shift, TX NCO off.
|
||||||
fpga_debug_register_write(FPGA_REG_DECIM, 0x00); // RX_DECIM=No Decim
|
* The decimation ratio and the quarter shift are programmed later by
|
||||||
|
* ClockManager::set_sampling_frequency() and set_tuning_frequency(). */
|
||||||
// RX Mode: Register 3 is RX Digital Gain. Start with 0dB (no shift).
|
fpga_debug_register_write(FPGA_REG_CTRL, FPGA_CTRL_DC_BLOCK_EN);
|
||||||
fpga_debug_register_write(FPGA_REG_RX_DIGITAL_GAIN, FPGA_RX_DEFAULT_DIGITAL_GAIN);
|
fpga_debug_register_write(FPGA_REG_DECIM, 0x00); // RX_DECIM = no decimation
|
||||||
|
fpga_debug_register_write(FPGA_REG_RX_PSTEP, 0x00); // quarter shift off
|
||||||
/* RX Mode: Initialize DC Block parameters to standard Praline values.
|
fpga_debug_register_write(FPGA_REG_TX_CONTROL, 0x00);
|
||||||
* 0x04 Width and 0x08 Adapt Rate are typical for 40MHz stability.
|
cached_quarter_shift = 0;
|
||||||
*/
|
|
||||||
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);
|
|
||||||
|
|
||||||
ssp1_arbiter.invalidate();
|
ssp1_arbiter.invalidate();
|
||||||
chThdSleepMilliseconds(10); // Let FPGA registers settle
|
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);
|
fpga_debug_register_write(FPGA_REG_TX_PHASE_STEP, 0x00);
|
||||||
} else {
|
} else {
|
||||||
fpga_set_mode(FPGA_MODE_RX);
|
fpga_set_mode(FPGA_MODE_RX);
|
||||||
// RX Mode: Ensure NCO is disabled and reset digital gain
|
/* RX Mode: DC block on, TX NCO off. The quarter shift is re-applied by
|
||||||
fpga_debug_register_write(FPGA_REG_RX_DIGITAL_GAIN, FPGA_RX_DEFAULT_DIGITAL_GAIN);
|
* set_tuning_frequency(); clear it here so a stale TX/RX transition
|
||||||
/* RX Mode: Initialize DC Block parameters to standard Praline values.
|
* cannot leave a rotation programmed with no matching LO offset. */
|
||||||
* 0x04 Width and 0x08 Adapt Rate are typical for 40MHz stability.
|
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_DC_BLOCK_WIDTH, FPGA_RX_DEFAULT_DC_WIDTH);
|
fpga_debug_register_write(FPGA_REG_RX_PSTEP, 0x00);
|
||||||
fpga_debug_register_write(FPGA_REG_RX_DC_ADAPT_RATE, FPGA_RX_DEFAULT_ADAPT_RATE);
|
cached_quarter_shift = 0;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -309,7 +310,19 @@ bool set_tuning_frequency(const rf::Frequency frequency) {
|
|||||||
final_frequency = final_frequency + portapack::persistent_memory::config_freq_rx_correction();
|
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);
|
const auto tuning_config = tuning::config::create(final_frequency);
|
||||||
|
#endif
|
||||||
if (tuning_config.is_valid()) {
|
if (tuning_config.is_valid()) {
|
||||||
first_if.disable();
|
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)
|
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();
|
ssp1_arbiter.invalidate();
|
||||||
#else
|
#else
|
||||||
baseband_cpld.set_invert(mixer_invert ^ baseband_invert);
|
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() {
|
int_fast8_t get_cached_vga_gain() {
|
||||||
return cached_vga_gain;
|
return cached_vga_gain;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint8_t get_cached_quarter_shift() {
|
||||||
|
return cached_quarter_shift;
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace first_if {
|
namespace first_if {
|
||||||
@@ -570,18 +603,11 @@ void register_write(const size_t register_number, uint32_t value) {
|
|||||||
void init() {
|
void init() {
|
||||||
fpga_set_mode(FPGA_MODE_RX);
|
fpga_set_mode(FPGA_MODE_RX);
|
||||||
|
|
||||||
// These FPGA registers control DC_BLOCK, Q-Inv, QUARTER SHIFT, and Decimation.
|
/* Same boot state as fpga_init() in hackrf/firmware/common/fpga.c. */
|
||||||
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_CTRL, FPGA_CTRL_DC_BLOCK_EN);
|
||||||
fpga_debug_register_write(FPGA_REG_DECIM, 0x00); // RX_DECIM=No Decim
|
fpga_debug_register_write(FPGA_REG_DECIM, 0x00); // RX_DECIM = no decimation
|
||||||
|
fpga_debug_register_write(FPGA_REG_RX_PSTEP, 0x00); // quarter shift off
|
||||||
// RX Mode: Register 3 is RX Digital Gain. Start with 0dB (no shift).
|
fpga_debug_register_write(FPGA_REG_TX_CONTROL, 0x00);
|
||||||
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);
|
|
||||||
|
|
||||||
ssp1_arbiter.invalidate(); // Force arbiter to reconfigure on next transfer
|
ssp1_arbiter.invalidate(); // Force arbiter to reconfigure on next transfer
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -126,6 +126,10 @@ rf::Direction get_cached_direction();
|
|||||||
bool get_cached_rf_amp();
|
bool get_cached_rf_amp();
|
||||||
int_fast8_t get_cached_lna_gain();
|
int_fast8_t get_cached_lna_gain();
|
||||||
int_fast8_t get_cached_vga_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
|
#endif
|
||||||
|
|
||||||
namespace sgpio {
|
namespace sgpio {
|
||||||
|
|||||||
@@ -244,10 +244,24 @@ void ReceiverModel::enable() {
|
|||||||
radio::set_direction(rf::Direction::Receive);
|
radio::set_direction(rf::Direction::Receive);
|
||||||
|
|
||||||
#ifdef PRALINE
|
#ifdef PRALINE
|
||||||
/* Anchor the Common Mode Voltage (VCM) to 1.2V.
|
/* MAX2831 RX IQ common-mode voltage (register 15).
|
||||||
* This stabilizes the electrical floor of the I/Q signals.
|
*
|
||||||
*/
|
* 0 = 1.1 V, 1 = 1.2 V, 2 = 1.3 V, 3 = 1.45 V.
|
||||||
radio::set_rx_buff_vcm(1);
|
*
|
||||||
|
* 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
|
#endif
|
||||||
|
|
||||||
update_tuning_frequency();
|
update_tuning_frequency();
|
||||||
@@ -326,47 +340,39 @@ void ReceiverModel::update_baseband_bandwidth() {
|
|||||||
if (enabled_) {
|
if (enabled_) {
|
||||||
#ifdef PRALINE
|
#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
|
* bb_bandwidth = sample_rate * 3 / 4
|
||||||
* while the FPGA decimation filter handles anti-aliasing.
|
* lpf_bandwidth = bb_bandwidth + offset_hz * 2
|
||||||
*
|
*
|
||||||
* For most modes: LPF = (output_sample_rate * 3) / 8
|
* where offset_hz is the quarter-rate shift, i.e. afe_rate / 4 when a
|
||||||
* For quarter-shift: add offset for shifted spectrum
|
* 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 previous version used /8 in both places and read the shift from
|
||||||
* the hardware limit applies and FPGA filter does the real work.
|
* 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();
|
uint32_t sample_rate = sampling_rate();
|
||||||
uint8_t resampling_n = portapack::clock_manager.get_resampling_n();
|
uint8_t resampling_n = portapack::clock_manager.get_resampling_n();
|
||||||
uint32_t afe_rate = sample_rate << resampling_n;
|
uint32_t afe_rate = sample_rate << resampling_n;
|
||||||
|
|
||||||
// Base LPF: enough to capture desired bandwidth
|
uint32_t lpf_bandwidth = (sample_rate * 3) / 4;
|
||||||
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;
|
|
||||||
|
|
||||||
|
const uint8_t quarter_shift = radio::debug::get_cached_quarter_shift();
|
||||||
if (quarter_shift != 0) {
|
if (quarter_shift != 0) {
|
||||||
// Quarter-shift moves spectrum by AFE_rate/4, need wider LPF
|
const uint32_t offset = afe_rate / 4;
|
||||||
uint32_t offset = afe_rate / 8;
|
|
||||||
lpf_bandwidth += offset * 2;
|
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);
|
radio::set_baseband_filter_bandwidth_rx(lpf_bandwidth);
|
||||||
#else
|
#else
|
||||||
radio::set_baseband_filter_bandwidth_rx(baseband_bandwidth());
|
radio::set_baseband_filter_bandwidth_rx(baseband_bandwidth());
|
||||||
|
|||||||
+332
-99
@@ -27,8 +27,8 @@ namespace tuning {
|
|||||||
namespace config {
|
namespace config {
|
||||||
|
|
||||||
// Forward declarations
|
// Forward declarations
|
||||||
Config low_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);
|
Config mid_band(const rf::Frequency target_frequency, const uint32_t afe_rate, const bool transmit);
|
||||||
Config high_band(const rf::Frequency target_frequency);
|
Config high_band(const rf::Frequency target_frequency);
|
||||||
|
|
||||||
#ifdef PRALINE
|
#ifdef PRALINE
|
||||||
@@ -36,133 +36,366 @@ Config high_band(const rf::Frequency target_frequency);
|
|||||||
* PRALINE Tuning Configuration
|
* 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
|
* Each entry gives, for target frequencies up to rf_range_end_mhz:
|
||||||
* specifies:
|
* if_mhz the IF the MAX2831 tunes to (0 = mixer bypassed, IF = RF)
|
||||||
* - rf_range_end_mhz: Upper frequency limit for this config
|
* high_lo true -> LO = IF + analogue RF (mixer inverts the spectrum)
|
||||||
* - if_mhz: IF frequency (what MAX2831 tunes to)
|
* false -> LO = IF - analogue RF (no inversion)
|
||||||
* - high_lo: true = high-side injection, false = low-side
|
* shift the FPGA quarter-rate shift mode used for this entry
|
||||||
* - shift: FPGA quarter-shift mode (not implemented in Mayhem yet)
|
|
||||||
*
|
*
|
||||||
* Key insight: The IF frequency varies to keep the RFFC5072 VCO in a
|
* The quarter-rate shift is the part Mayhem was previously missing. RX entries
|
||||||
* safe operating range (ideally 3500-5000 MHz, avoiding extremes).
|
* 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:
|
* "up" and "down" are named for the direction the FPGA rotates, so
|
||||||
* High-side injection: LO = IF + RF, VCO = LO × lodiv
|
* FPGA_QUARTER_SHIFT_MODE_UP means the analogue centre is placed ABOVE the
|
||||||
* Low-side injection: LO = IF - RF, VCO = LO × lodiv
|
* requested frequency, and DOWN below it (radio.c analog_from_digital_rf()).
|
||||||
* 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
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Simplified tune_config lookup for Mayhem
|
namespace {
|
||||||
// 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;
|
|
||||||
|
|
||||||
// Based on hackrf_usb tune_config_rx table
|
struct PralineTuneConfig {
|
||||||
if (freq_mhz < 2100) {
|
uint16_t rf_range_end_mhz;
|
||||||
// Most low-band frequencies: use 2375 MHz IF
|
uint16_t if_mhz;
|
||||||
// This keeps VCO around 4750-4950 MHz for FM band
|
bool high_lo;
|
||||||
return 2375'000'000;
|
uint8_t shift; /* 0b00 none, 0b11 up, 0b01 down */
|
||||||
} else if (freq_mhz < 2320) {
|
};
|
||||||
// Transition zone: use varying IF to avoid VCO edges
|
|
||||||
// These frequencies are tricky - near MAX2831 minimum
|
/* The tables below are kept column-aligned to match the reference source, so
|
||||||
// Use 2425 MHz to give some margin
|
* they are exempt from reformatting. */
|
||||||
return 2425'000'000;
|
// clang-format off
|
||||||
} else {
|
|
||||||
// Bypass mode or high-band - IF not used for mixer
|
/* tuning table optimized for RX */
|
||||||
return 0;
|
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
|
/* radio.c compute_offset(): a quarter of the AFE (ADC) sample rate, or zero if
|
||||||
constexpr bool praline_use_high_side_injection(const rf::Frequency target_frequency) {
|
* no shift is in use or the AFE rate isn't known yet. */
|
||||||
const uint32_t freq_mhz = target_frequency / 1'000'000;
|
constexpr uint32_t quarter_shift_offset(const uint8_t shift, const uint32_t afe_rate) {
|
||||||
|
return (shift == 0) ? 0 : (afe_rate / 4);
|
||||||
// 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 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
|
#endif // PRALINE
|
||||||
|
|
||||||
// Low band <2170 Mhz (HackRF One) or <2320 MHz (PRALINE):
|
Config low_band(const rf::Frequency target_frequency, const uint32_t afe_rate, const bool transmit) {
|
||||||
constexpr rf::Frequency low_band_second_lo_frequency(const rf::Frequency target_frequency) {
|
|
||||||
#ifdef PRALINE
|
#ifdef PRALINE
|
||||||
// Use the tune_config lookup for PRALINE
|
const PralineTuneConfig* entry = select_tune_config(target_frequency, transmit);
|
||||||
return praline_get_if_frequency(target_frequency);
|
|
||||||
#else
|
|
||||||
return 2650'000'000 - (target_frequency / 7);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
Config low_band(const rf::Frequency target_frequency) {
|
/* Past the end of the table: no usable configuration. */
|
||||||
const rf::Frequency second_lo_frequency = low_band_second_lo_frequency(target_frequency);
|
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;
|
rf::Frequency first_lo_frequency;
|
||||||
bool mixer_invert;
|
bool mixer_invert;
|
||||||
|
|
||||||
if (praline_use_high_side_injection(target_frequency)) {
|
if (entry->high_lo) {
|
||||||
// High-side injection: LO = IF + RF
|
first_lo_frequency = second_lo_frequency + analog_rf;
|
||||||
first_lo_frequency = second_lo_frequency + target_frequency;
|
|
||||||
mixer_invert = true;
|
mixer_invert = true;
|
||||||
} else {
|
} else {
|
||||||
// Low-side injection: LO = IF - RF
|
first_lo_frequency = second_lo_frequency - analog_rf;
|
||||||
first_lo_frequency = second_lo_frequency - target_frequency;
|
|
||||||
mixer_invert = false;
|
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
|
#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 rf::Frequency first_lo_frequency = target_frequency + second_lo_frequency;
|
||||||
const bool mixer_invert = true;
|
const bool mixer_invert = true;
|
||||||
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};
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mid band 2170-2740 Mhz (HackRF One) or 2320-2580 MHz (PRALINE):
|
// Mid band 2170-2740 Mhz (HackRF One) or 2320-2740 MHz (PRALINE):
|
||||||
Config mid_band(const rf::Frequency target_frequency) {
|
Config mid_band(const rf::Frequency target_frequency, const uint32_t afe_rate, const bool transmit) {
|
||||||
#ifdef PRALINE
|
#ifdef PRALINE
|
||||||
// For Praline with MAX2831 (2.3-2.6 GHz range)
|
/* radio.c select_img_reject() / tuning.c: on PRALINE the MAX2831 tunes
|
||||||
// Frequencies 2170-2300 MHz need upconversion since they're below MAX2831 minimum
|
* direct (mixer bypassed) from 2320 to 2580 MHz. band_mid starts at
|
||||||
if (target_frequency < 2300'000'000) {
|
* TRANSITION = 2320 MHz, so everything below that already went to
|
||||||
// Treat as low band - need mixer
|
* low_band(). */
|
||||||
return low_band(target_frequency);
|
if (target_frequency <= 2580'000'000) {
|
||||||
}
|
const PralineTuneConfig* entry = select_tune_config(target_frequency, transmit);
|
||||||
// Frequencies 2300-2600 MHz can go direct (no RFFC5072)
|
const uint8_t shift = (afe_rate == 0) ? 0 : entry->shift;
|
||||||
else if (target_frequency <= 2600'000'000) {
|
const rf::Frequency analog_rf = analog_from_digital_rf(target_frequency, shift, afe_rate);
|
||||||
const rf::Frequency second_lo_frequency = target_frequency;
|
|
||||||
const rf::Frequency first_lo_frequency = 0;
|
/* Mixer bypassed: no first LO, the MAX2831 tunes the (offset)
|
||||||
const bool mixer_invert = false;
|
* analogue RF directly and the FPGA rotates it back. */
|
||||||
return {first_lo_frequency, second_lo_frequency, rf::path::Band::Mid, mixer_invert};
|
return {0, analog_rf, rf::path::Band::Mid, false, shift};
|
||||||
}
|
|
||||||
// Frequencies 2600-2740 MHz need downconversion since they're above MAX2831 maximum
|
|
||||||
else {
|
|
||||||
// Treat as high band
|
|
||||||
return high_band(target_frequency);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 2580-2740 MHz: above the bypass window, downconvert. */
|
||||||
|
return high_band(target_frequency);
|
||||||
#else
|
#else
|
||||||
|
(void)afe_rate;
|
||||||
|
(void)transmit;
|
||||||
const rf::Frequency second_lo_frequency = target_frequency;
|
const rf::Frequency second_lo_frequency = target_frequency;
|
||||||
const rf::Frequency first_lo_frequency = 0;
|
const rf::Frequency first_lo_frequency = 0;
|
||||||
const bool mixer_invert = false;
|
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};
|
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. */
|
/* TODO: This is some lame code. */
|
||||||
if (rf::path::band_low.contains(target_frequency)) {
|
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)) {
|
} 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)) {
|
} else if (rf::path::band_high.contains(target_frequency)) {
|
||||||
return high_band(target_frequency);
|
return high_band(target_frequency);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -33,18 +33,21 @@ struct Config {
|
|||||||
: first_lo_frequency(0),
|
: first_lo_frequency(0),
|
||||||
second_lo_frequency(0),
|
second_lo_frequency(0),
|
||||||
rf_path_band(rf::path::Band::Mid),
|
rf_path_band(rf::path::Band::Mid),
|
||||||
mixer_invert(false) {
|
mixer_invert(false),
|
||||||
|
quarter_shift(0) {
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr Config(
|
constexpr Config(
|
||||||
rf::Frequency first_lo_frequency,
|
rf::Frequency first_lo_frequency,
|
||||||
rf::Frequency second_lo_frequency,
|
rf::Frequency second_lo_frequency,
|
||||||
rf::path::Band rf_path_band,
|
rf::path::Band rf_path_band,
|
||||||
bool mixer_invert)
|
bool mixer_invert,
|
||||||
|
uint8_t quarter_shift = 0)
|
||||||
: first_lo_frequency(first_lo_frequency),
|
: first_lo_frequency(first_lo_frequency),
|
||||||
second_lo_frequency(second_lo_frequency),
|
second_lo_frequency(second_lo_frequency),
|
||||||
rf_path_band(rf_path_band),
|
rf_path_band(rf_path_band),
|
||||||
mixer_invert(mixer_invert) {
|
mixer_invert(mixer_invert),
|
||||||
|
quarter_shift(quarter_shift) {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool is_valid() const {
|
bool is_valid() const {
|
||||||
@@ -55,9 +58,23 @@ struct Config {
|
|||||||
const rf::Frequency second_lo_frequency;
|
const rf::Frequency second_lo_frequency;
|
||||||
const rf::path::Band rf_path_band;
|
const rf::path::Band rf_path_band;
|
||||||
const bool mixer_invert;
|
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 config */
|
||||||
} /* namespace tuning */
|
} /* namespace tuning */
|
||||||
|
|||||||
@@ -125,10 +125,11 @@
|
|||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Canonical Default Values - SINGLE SOURCE OF TRUTH
|
// Canonical Default Values - SINGLE SOURCE OF TRUTH
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
/* Define the canonical RX defaults in ONE place */
|
/* Define the canonical RX defaults in ONE place.
|
||||||
#define FPGA_RX_DEFAULT_DC_WIDTH 0x04 /* Typical for 40MHz stability */
|
* Matches fpga_init() in hackrf/firmware/common/fpga.c: the gateware only has
|
||||||
#define FPGA_RX_DEFAULT_ADAPT_RATE 0x08 /* Typical for 40MHz stability */
|
* rx_decim and rx_pstep on the RX side, both starting at zero. */
|
||||||
#define FPGA_RX_DEFAULT_DIGITAL_GAIN 0x00 /* No shift initially */
|
#define FPGA_RX_DEFAULT_DECIM 0x00 /* No decimation initially */
|
||||||
|
#define FPGA_RX_DEFAULT_PSTEP 0x00 /* No quarter-rate shift initially */
|
||||||
|
|
||||||
/* Define TX defaults */
|
/* Define TX defaults */
|
||||||
#define FPGA_TX_DEFAULT_NCO_CTRL 0x00 /* NCO disabled */
|
#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.
|
// These functions allow reading/writing FPGA internal registers via SPI.
|
||||||
// The FPGA bitstream implements a simple SPI register interface.
|
// The FPGA bitstream implements a simple SPI register interface.
|
||||||
//
|
//
|
||||||
// FPGA Register Map:
|
// FPGA Register Map (hackrf/firmware/fpga/top/standard.py):
|
||||||
// Reg 1 (CTRL): DC_BLOCK(b0), QUARTER_SHIFT_EN(b1), QUARTER_SHIFT_UP(b2), PRBS(b6), TRIGGER_EN(b7)
|
// Reg 1 (CTRL): DC_BLOCK(b0), PRBS(b6), TRIGGER_EN(b7)
|
||||||
// Reg 2 (RX_DECIM): Decimation ratio [2:0]
|
// Reg 2 (RX_DECIM): Decimation ratio, log2 [2:0]
|
||||||
// Reg 3 (RX/TX): RX Digital Shift OR TX NCO Control
|
// Reg 3 (RX_PSTEP): QUARTER_SHIFT_EN(b6), QUARTER_SHIFT_UP(b7)
|
||||||
// Reg 4 (RX_DC_BLOCK_WIDTH/TX_INTERP) [2:0]
|
// Reg 4 (TX_CTRL): NCO enable (b0)
|
||||||
// Reg 5 (RX_DC_ADAPT_RATE/TX_PSTEP) [7:0]
|
// Reg 5 (TX_INTRP): Interpolation ratio [2:0]
|
||||||
|
// Reg 6 (TX_PSTEP): NCO phase step [7:0]
|
||||||
//
|
//
|
||||||
// SPI Protocol:
|
// SPI Protocol:
|
||||||
// Read: Send [reg & 0x7F, 0x00, 0x00] -> value in byte 3
|
// 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 */
|
/* 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) {
|
if (current_mode != FPGA_MODE_RX) {
|
||||||
/* Log error or assert - wrong mode! */
|
/* Log error or assert - wrong mode! */
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
fpga_register_write(FPGA_REG_SHARED_3, shift & FPGA_RX_GAIN_SHIFT_MASK);
|
fpga_register_write(FPGA_REG_RX_PSTEP, (uint8_t)((mode & 0x03) << FPGA_RX_QUARTER_SHIFT_SHIFT));
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -394,12 +390,12 @@ void fpga_rx_set_dc_adapt_rate(uint8_t rate) {
|
|||||||
/* TX Functions with mode assertion */
|
/* TX Functions with mode assertion */
|
||||||
void fpga_tx_set_nco_enable(bool enable) {
|
void fpga_tx_set_nco_enable(bool enable) {
|
||||||
if (current_mode != FPGA_MODE_TX) return;
|
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)
|
if (enable)
|
||||||
val |= FPGA_TX_NCO_EN;
|
val |= FPGA_TX_NCO_EN;
|
||||||
else
|
else
|
||||||
val &= ~FPGA_TX_NCO_EN;
|
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) {
|
void fpga_tx_set_interpolation(uint8_t ratio) {
|
||||||
@@ -424,17 +420,17 @@ static void fpga_register_init(void) {
|
|||||||
current_mode = FPGA_MODE_RX;
|
current_mode = FPGA_MODE_RX;
|
||||||
|
|
||||||
fpga_spi_write(FPGA_REG_CTRL, FPGA_CTRL_DC_BLOCK_EN);
|
fpga_spi_write(FPGA_REG_CTRL, FPGA_CTRL_DC_BLOCK_EN);
|
||||||
fpga_spi_write(FPGA_REG_DECIM, 0x00);
|
fpga_spi_write(FPGA_REG_DECIM, FPGA_RX_DEFAULT_DECIM);
|
||||||
fpga_spi_write(FPGA_REG_SHARED_3, FPGA_RX_DEFAULT_DIGITAL_GAIN);
|
fpga_spi_write(FPGA_REG_RX_PSTEP, FPGA_RX_DEFAULT_PSTEP);
|
||||||
fpga_spi_write(FPGA_REG_SHARED_4, FPGA_RX_DEFAULT_DC_WIDTH);
|
fpga_spi_write(FPGA_REG_TX_CONTROL, FPGA_TX_DEFAULT_NCO_CTRL);
|
||||||
fpga_spi_write(FPGA_REG_SHARED_5, FPGA_RX_DEFAULT_ADAPT_RATE);
|
fpga_spi_write(FPGA_REG_TX_INTERP, FPGA_TX_DEFAULT_INTERP);
|
||||||
|
|
||||||
/* Update cache */
|
/* Update cache */
|
||||||
fpga_reg_cache[1] = FPGA_CTRL_DC_BLOCK_EN;
|
fpga_reg_cache[1] = FPGA_CTRL_DC_BLOCK_EN;
|
||||||
fpga_reg_cache[2] = 0x00;
|
fpga_reg_cache[2] = FPGA_RX_DEFAULT_DECIM;
|
||||||
fpga_reg_cache[3] = FPGA_RX_DEFAULT_DIGITAL_GAIN;
|
fpga_reg_cache[3] = FPGA_RX_DEFAULT_PSTEP;
|
||||||
fpga_reg_cache[4] = FPGA_RX_DEFAULT_DC_WIDTH;
|
fpga_reg_cache[4] = FPGA_TX_DEFAULT_NCO_CTRL;
|
||||||
fpga_reg_cache[5] = FPGA_RX_DEFAULT_ADAPT_RATE;
|
fpga_reg_cache[5] = FPGA_TX_DEFAULT_INTERP;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|||||||
@@ -16,10 +16,23 @@ extern "C" {
|
|||||||
|
|
||||||
#ifdef PRALINE
|
#ifdef PRALINE
|
||||||
|
|
||||||
/* RX path (legacy PRALINE software map kept for compatibility) */
|
/*
|
||||||
#define FPGA_REG_RX_DIGITAL_GAIN 0x03 /* Digital Shift / scaling (RX Mode) */
|
* RX path.
|
||||||
#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) */
|
* 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
|
* TX path must match the currently built PRALINE standard gateware in
|
||||||
@@ -43,70 +56,55 @@ typedef enum {
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* FPGA Register Addresses
|
* FPGA Register Addresses
|
||||||
* NOTE:
|
*
|
||||||
* The currently loaded PRALINE standard gateware uses:
|
* From hackrf/firmware/fpga/top/standard.py (spi_regs.add_register):
|
||||||
* 0x01 CTRL
|
* 0x01 ctrl 8 bits
|
||||||
* 0x02 RX_DECIM
|
* 0x02 rx_decim 3 bits
|
||||||
* 0x03 RX_DIGITAL_GAIN (RX) / TX_NCO_CTRL (TX)
|
* 0x03 rx_pstep 8 bits
|
||||||
* 0x04 TX_CTRL
|
* 0x04 tx_ctrl 1 bit
|
||||||
* 0x05 TX_INTRP
|
* 0x05 tx_intrp 3 bits
|
||||||
* 0x06 TX_PSTEP
|
* 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_CTRL 0x01 /* Control register */
|
||||||
#define FPGA_REG_DECIM 0x02 /* RX decimation */
|
#define FPGA_REG_DECIM 0x02 /* RX decimation (log2, bits [2:0]) */
|
||||||
#define FPGA_REG_SHARED_3 0x03 /* Legacy shared register */
|
/* 0x03..0x06 are FPGA_REG_RX_PSTEP / FPGA_REG_TX_CONTROL /
|
||||||
#define FPGA_REG_SHARED_4 0x04 /* Legacy shared register */
|
* FPGA_REG_TX_INTERP / FPGA_REG_TX_PHASE_STEP, defined above. */
|
||||||
#define FPGA_REG_SHARED_5 0x05 /* Legacy shared register */
|
|
||||||
#define FPGA_REG_SHARED_6 0x06 /* TX phase step */
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Register 1 (CTRL) Bit Definitions
|
* 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_DC_BLOCK_EN (1 << 0) /* DC block enable */
|
||||||
#define FPGA_CTRL_QUARTER_SHIFT_EN (1 << 1) /* Quarter-rate shift enable */
|
#define FPGA_CTRL_PRBS_EN (1 << 6) /* PRBS test mode */
|
||||||
#define FPGA_CTRL_QUARTER_SHIFT_UP (1 << 2) /* Shift direction: 1=up, 0=down */
|
#define FPGA_CTRL_TRIGGER_EN (1 << 7) /* External trigger enable */
|
||||||
#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 */
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* 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 */
|
/* Position of that 2-bit field inside rx_pstep, for direct register writes. */
|
||||||
#define FPGA_REG3_RX_DIGITAL_GAIN 0x03
|
#define FPGA_RX_QUARTER_SHIFT_SHIFT 6
|
||||||
#define FPGA_RX_GAIN_SHIFT_MASK 0x0F /* Bits [3:0] - shift amount */
|
#define FPGA_RX_QUARTER_SHIFT_MASK 0xC0
|
||||||
|
|
||||||
/* TX Mode: NCO control */
|
/* Mode values, matching fpga_quarter_shift_mode_t in
|
||||||
#define FPGA_REG3_TX_NCO_CTRL 0x03
|
* hackrf/firmware/common/fpga.h. Pass these to
|
||||||
#define FPGA_TX_NCO_EN (1 << 0) /* NCO enable */
|
* fpga_rx_set_quarter_shift_mode(), which shifts them into place. */
|
||||||
#define FPGA_TX_NCO_INVERT (1 << 1) /* Invert spectrum */
|
#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_TX_NCO_EN (1 << 0) /* tx_ctrl[0]: NCO enable */
|
||||||
#define FPGA_REG4_RX_DC_WIDTH 0x04
|
#define FPGA_TX_INTERP_MASK 0x07 /* tx_intrp bits [2:0] */
|
||||||
#define FPGA_RX_DC_WIDTH_MASK 0x07 /* Bits [2:0] */
|
#define FPGA_TX_PHASE_STEP_MASK 0xFF /* tx_pstep bits [7: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
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Core Functions
|
* 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)
|
* RX Mode Functions (only valid when mode == FPGA_MODE_RX)
|
||||||
*/
|
*/
|
||||||
void fpga_rx_set_decimation(uint8_t ratio);
|
void fpga_rx_set_decimation(uint8_t ratio);
|
||||||
void fpga_rx_set_digital_gain(uint8_t shift);
|
/* mode is the gateware encoding: 0b00 none, 0b11 up, 0b01 down. */
|
||||||
void fpga_rx_set_dc_block_width(uint8_t width);
|
void fpga_rx_set_quarter_shift_mode(uint8_t mode);
|
||||||
void fpga_rx_set_dc_adapt_rate(uint8_t rate);
|
|
||||||
void fpga_rx_enable_dc_block(bool enable);
|
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)
|
* reg: Register number (1-5)
|
||||||
* Returns: Register value, or 0xFF if invalid register
|
* Returns: Register value, or 0xFF if invalid register
|
||||||
*
|
*
|
||||||
* FPGA Register Map:
|
* FPGA Register Map (hackrf/firmware/fpga/top/standard.py):
|
||||||
* Reg 1 (CTRL): DC_BLOCK(b0), QUARTER_SHIFT_EN(b1), QUARTER_SHIFT_UP(b2), PRBS(b6), TRIGGER_EN(b7)
|
* Reg 1 (CTRL): DC_BLOCK(b0), PRBS(b6), TRIGGER_EN(b7)
|
||||||
* Reg 2 (RX_DECIM): Decimation ratio [2:0]
|
* Reg 2 (RX_DECIM): Decimation ratio, log2 [2:0]
|
||||||
* Reg 3 (RX/TX): RX Digital Shift OR TX NCO Control
|
* Reg 3 (RX_PSTEP): QUARTER_SHIFT_EN(b6), QUARTER_SHIFT_UP(b7)
|
||||||
* Reg 4 (RX_DC_BLOCK_WIDTH/TX_INTERP) [2:0]
|
* Reg 4 (TX_CTRL): NCO_EN(b0)
|
||||||
* Reg 5 (RX_DC_ADAPT_RATE/TX_PSTEP) [7:0]
|
* 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);
|
uint8_t fpga_debug_register_read(uint8_t reg);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user