mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2026-07-26 10:38:52 +00:00
PRALINE Tx and clock fix (Pr 3192 takeover (#3198) )
* fix clock init and add mayhem tx capacity * aligned external clock and RX config * fix(clock): cap TX interpolation to x16 to prevent doubled waveform speed The TX gateware only implements interpolation factors x1..x16 (tx_intrp 0..4), but the rate search could select n==5 (x32) for very low TX sample rates. That left the AFE/CLK0/CLK1 running at the x32 rate while the FPGA only interpolated by x16, clocking the TX datapath at twice the gateware output rate. Cap afe_rate and _resampling_n to the x16 limit in TX so the clocks stay aligned with the interpolation setting. * fix(praline): source PLL1 from 40MHz GP_CLKIN instead of 12MHz XTAL For the intermediate 90-110MHz PLL1 stepping stage, source PLL1 from GP_CLKIN (Si5351 CLK2/MCU_CLK at 40MHz) giving N=2, M=10, Fclk=100MHz, rather than the on-chip 12MHz XTAL bootstrap path. This matches the non-PRALINE path. Remove the dead commented-out XTAL enable/PLL code. * docs(fpga_bridge): correct register 0x03 description for PRALINE gateware Register 0x03 is RX_DIGITAL_GAIN in RX and TX_NCO_CTRL in TX, not RX_PSTEP. * Fix fpga_tx_set_nco_enable to use FPGA_REG3_TX_NCO_CTRL (0x03)
This commit is contained in:
@@ -33,6 +33,7 @@
|
||||
#include <variant>
|
||||
|
||||
#include "file.hpp"
|
||||
#include "hackrf_hal.hpp"
|
||||
#include "max283x.hpp"
|
||||
#include "string_format.hpp"
|
||||
|
||||
@@ -134,7 +135,7 @@ struct AppSettings {
|
||||
uint8_t vga = 32;
|
||||
uint8_t rx_amp = 0;
|
||||
uint8_t tx_amp = 0;
|
||||
uint8_t tx_gain = 35;
|
||||
uint8_t tx_gain = hackrf::one::default_tx_gain_db;
|
||||
uint32_t channel_bandwidth = 1;
|
||||
uint32_t rx_frequency;
|
||||
uint32_t tx_frequency;
|
||||
|
||||
@@ -238,6 +238,7 @@ APRSTXView::APRSTXView(NavigationView& nav) {
|
||||
process_coordinates(lat, lon);
|
||||
});
|
||||
};
|
||||
|
||||
// process_coordinates(last_lat, last_lon); //don't load last, so won't confuse users
|
||||
}
|
||||
|
||||
|
||||
@@ -325,6 +325,12 @@ static constexpr ClockControl::MultiSynthSource get_si5351a_reference_clock_gene
|
||||
? ClockControl::MultiSynthSource::PLLA
|
||||
: ClockControl::MultiSynthSource::PLLB;
|
||||
}
|
||||
|
||||
static constexpr ClockControl::MultiSynthSource get_si5351c_reference_clock_generator_pll(const ClockManager::ReferenceSource reference_source) {
|
||||
return (reference_source == ClockManager::ReferenceSource::Xtal)
|
||||
? ClockControl::MultiSynthSource::PLLA
|
||||
: ClockControl::MultiSynthSource::PLLB;
|
||||
}
|
||||
#else
|
||||
static constexpr ClockControl::MultiSynthSource get_si5351c_reference_clock_generator_pll(const ClockManager::ReferenceSource reference_source) {
|
||||
return (reference_source == ClockManager::ReferenceSource::Xtal)
|
||||
@@ -424,32 +430,22 @@ using namespace hackrf::one;
|
||||
|
||||
void ClockManager::init_clock_generator() {
|
||||
#ifdef PRALINE
|
||||
// thoese PIN can review platform_scu file
|
||||
// have many conflict and modify for clock init
|
||||
// P2_10 -> GPIO0[14] P1_CTRL0
|
||||
/* Route the PRALINE P22 10MHz reference to the Si5351 CLKIN pin. */
|
||||
LPC_SCU->SFSP[2][10] = 0xF0;
|
||||
// P6_8 -> GPIO5[16] P1_CTRL1
|
||||
LPC_SCU->SFSP[6][8] = 0xF4;
|
||||
// P6_9 -> GPIO3[5] P1_CTRL2
|
||||
LPC_SCU->SFSP[6][9] = 0xF0;
|
||||
// P1_20 -> GPIO0[15] CLKIN_CTRL
|
||||
LPC_SCU->SFSP[1][20] = 0xF0;
|
||||
|
||||
constexpr GPIO gpio_p1_ctrl0 = gpio[GPIO0_14];
|
||||
constexpr GPIO gpio_p1_ctrl1 = gpio[GPIO5_16];
|
||||
constexpr GPIO gpio_p1_ctrl2 = gpio[GPIO3_5];
|
||||
constexpr GPIO gpio_clkin_ctrl = gpio[GPIO0_15];
|
||||
|
||||
gpio_p1_ctrl0.output();
|
||||
gpio_p1_ctrl1.output();
|
||||
gpio_p1_ctrl2.output();
|
||||
gpio_clkin_ctrl.output();
|
||||
|
||||
// P1 control = 100 and choose P22 clock external clock on portapack
|
||||
gpio_p1_ctrl0.write(0);
|
||||
gpio_p1_ctrl1.write(0);
|
||||
gpio_p1_ctrl2.write(1);
|
||||
gpio_clkin_ctrl.write(1);
|
||||
|
||||
chThdSleepMilliseconds(20);
|
||||
|
||||
#else
|
||||
@@ -465,35 +461,45 @@ void ClockManager::init_clock_generator() {
|
||||
clock_generator.enable_fanout();
|
||||
|
||||
#ifdef PRALINE
|
||||
/* PRALINE has Si5351A (NOT Si5351C like HackRF One OG).
|
||||
* Must use Si5351A configuration: PLLA only, no CLKIN support.
|
||||
*
|
||||
* IMPORTANT: Follow HackRF reference sequence:
|
||||
* 1. Set PLL input sources
|
||||
* 2. Configure PLL and multisynths
|
||||
* 3. Set clock control registers (AFTER multisynths!)
|
||||
* 4. Reset PLLs
|
||||
* 5. Enable outputs
|
||||
*/
|
||||
// change there detect method same as hackrf one
|
||||
// PLLA -> XTAL,PLLB -> CLKIN
|
||||
clock_generator.set_pll_input_sources(si5351c_pll_input_sources);
|
||||
// although the name a and the branch define CLK2 MCU CLOCK
|
||||
// only use CLK2
|
||||
auto si5351_clock_control_common = si5351a_clock_control_common;
|
||||
constexpr size_t clock_generator_output_pro_mcu_clkin = 2;
|
||||
// clk_src define CLKIN
|
||||
auto si5351_clock_control_common = si5351a_clock_control_common;
|
||||
|
||||
/*
|
||||
* Match the HackRF One init flow:
|
||||
* 1. PLLA stays on the local 25MHz crystal.
|
||||
* 2. PLLB is configured from the Si5351 CLKIN pin.
|
||||
* 3. CLK2 temporarily drives GP_CLKIN directly from CLKIN so we can
|
||||
* detect whether P22 is carrying a valid 10MHz reference.
|
||||
* 4. Once the source is chosen, all runtime clocks are switched to the
|
||||
* selected PLL and the working multisynths are programmed.
|
||||
*/
|
||||
clock_generator.set_pll_input_sources(si5351c_pll_input_sources);
|
||||
|
||||
clock_generator.set_clock_control(
|
||||
clock_generator_output_pro_mcu_clkin,
|
||||
si5351_clock_control_common[clock_generator_output_pro_mcu_clkin]
|
||||
.clk_src(ClockControl::ClockSource::CLKIN)
|
||||
.clk_pdn(ClockControl::ClockPowerDown::Power_On));
|
||||
clock_generator.enable_output(clock_generator_output_pro_mcu_clkin);
|
||||
chThdSleepMilliseconds(20);
|
||||
// should be extern icon and 10MHz
|
||||
reference = choose_reference();
|
||||
|
||||
/* Clock control will be set AFTER multisynth configuration - see below */
|
||||
chThdSleepMilliseconds(20);
|
||||
reference = choose_reference();
|
||||
clock_generator.disable_output(clock_generator_output_pro_mcu_clkin);
|
||||
|
||||
const auto ref_pll =
|
||||
get_si5351c_reference_clock_generator_pll(reference.source);
|
||||
|
||||
const ClockControls si5351_clock_control = ClockControls{{
|
||||
si5351_clock_control_common[0].ms_src(ref_pll),
|
||||
si5351_clock_control_common[1].ms_src(ref_pll),
|
||||
si5351_clock_control_common[2].ms_src(ref_pll),
|
||||
si5351_clock_control_common[3].ms_src(ref_pll),
|
||||
si5351_clock_control_common[4].ms_src(ref_pll),
|
||||
si5351_clock_control_common[5].ms_src(ref_pll),
|
||||
si5351_clock_control_common[6].ms_src(ref_pll),
|
||||
si5351_clock_control_common[7].ms_src(ref_pll),
|
||||
}};
|
||||
clock_generator.set_clock_control_single_byte(si5351_clock_control);
|
||||
#else
|
||||
clock_generator.set_pll_input_sources(hackrf_r9
|
||||
? si5351a_pll_input_sources
|
||||
@@ -538,86 +544,50 @@ void ClockManager::init_clock_generator() {
|
||||
#endif
|
||||
|
||||
#ifdef PRALINE
|
||||
clock_generator.write_pll_single_byte(0, si5351_pll_a_afe_800m);
|
||||
|
||||
/* * Praline HackRF Pro Clock Assignments (800 MHz VCO Configuration)
|
||||
* VCO Frequency: 800,000,000 Hz (Master Reference)
|
||||
* * CLK0: AFE_CLK (MAX5864 Codec & FPGA ADC Interface)
|
||||
* - Note: Defines hardware sample rate. Essential for WFM purity.
|
||||
* * CLK1: SCT_CLK (iCE40 FPGA System/Timing Clock)
|
||||
* - Note: Timing for SGPIO data bus; scales to 2x SR in wideband modes.
|
||||
* * CLK2: MCU_CLKIN (LPC43xx MCU External Clock Input)
|
||||
* - Note: Synchronizes MCU processing to the RF clock tree.
|
||||
* * CLK3: SG_CLK (Switching Regulator/Internal Logic Sync) SMA Port 1
|
||||
* - Note: Used for internal FPGA logic/gateware synchronization.
|
||||
* * CLK4: P_CLK (MAX2831 Peripheral/Expansion Clock)
|
||||
* - Note: Routed to expansion headers for external hardware sync.
|
||||
* * CLK5: AUX_CLK (RFFC5371 Auxiliary reference for secondary logic)
|
||||
* - Note: Provides additional timing flexibility for the iCE40 FPGA.
|
||||
* * CLK6: SG_CLK (Switching Regulator/Internal Logic Sync) SMA Port 2
|
||||
* - Note: Used for internal FPGA logic/gateware synchronization.
|
||||
* * CLK7: Unused / Power-Down
|
||||
* - State: Disabled (si5351a_ms6_7_off_reg)
|
||||
* - Note: Kept OFF to reduce EMI/RFI near the RF front-end.
|
||||
* * CLKOUT: Optional external clock output on the header.
|
||||
*/
|
||||
|
||||
/* Write PLL A (800 MHz based on 25 MHz xtal for RF) and
|
||||
* PLL B (800 MHZ based on 25 MHz xtalfor Digital)
|
||||
* Use single-byte writes to debug I2C issues
|
||||
*/
|
||||
{
|
||||
// Write PLLA (Registers 26-33)
|
||||
/* Write PLL A configuration (Base 26) */
|
||||
const auto& pll_a = si5351_pll_a_800_reg;
|
||||
for (size_t i = 1; i < pll_a.size(); i++) {
|
||||
clock_generator.write_register(pll_a[0] + i - 1, pll_a[i]);
|
||||
}
|
||||
|
||||
// Write PLLB (Registers 34-41)
|
||||
/* Write PLL B configuration (Base 34) */
|
||||
const auto& pll_b = si5351_pll_b_800_reg;
|
||||
const auto& pll_b = si5351c_pll_b_clkin_reg;
|
||||
for (size_t i = 1; i < pll_b.size(); i++) {
|
||||
clock_generator.write_register(pll_b[0] + i - 1, pll_b[i]);
|
||||
}
|
||||
}
|
||||
// CLK0: DAFE_CLK initial 4 MHz
|
||||
clock_generator.write_ms_single_byte(
|
||||
0,
|
||||
si5351_ms_afe_4m);
|
||||
|
||||
/* Write multisynth configurations using single-byte writes */
|
||||
// These cover all active channels on the Praline board
|
||||
clock_generator.write_ms_single_byte(0, si5351_ms_afe_4m); // CLK0: PLL A AFE Codec (4 MHz)
|
||||
clock_generator.write_ms_single_byte(1, si5351_ms_10m); // CLK1: PLL B SGPIO/FPGA Timing (10 MHz)
|
||||
clock_generator.write_ms_single_byte(2, si5351_ms_afe_40m); // CLK2: PLL A Audio and MCU Input (40 MHz)
|
||||
clock_generator.write_ms_single_byte(3, si5351_ms_0_4m); // CLK3: PLL B SMA Port 1 Logic Sync (4 MHz or 10 MHz)
|
||||
clock_generator.write_ms_single_byte(4, si5351_ms_afe_40m); // CLK4: PLL A MAX2831 Second IF (40 MHz)
|
||||
clock_generator.write_ms_single_byte(5, si5351_ms_afe_40m); // CLK5: PLL A RFFC5071First IF (40 MHz)
|
||||
clock_generator.write_ms_single_byte(6, si5351_ms_0_4m); // CLK6: PLL B SMA Port 2 Logic Sync (4 MHz or 10 MHz)
|
||||
clock_generator.write_ms_single_byte(7, si5351_ms_0_4m); // CLK7: PLL B Unused (4 MHz)
|
||||
// CLK1: DSCT_CLK initial 10 MHz
|
||||
clock_generator.write_ms_single_byte(
|
||||
1,
|
||||
si5351_ms_10m);
|
||||
|
||||
/* NOW set clock control registers (AFTER multisynths per HackRF reference) */
|
||||
const auto ref_pll_a = ClockControl::MultiSynthSource::PLLA;
|
||||
const auto ref_pll_b = ClockControl::MultiSynthSource::PLLB;
|
||||
const ClockControls si5351_clock_control = ClockControls{{
|
||||
si5351a_clock_control_common[0].ms_src(ref_pll_a),
|
||||
si5351a_clock_control_common[1].ms_src(ref_pll_b),
|
||||
si5351a_clock_control_common[2].ms_src(ref_pll_a),
|
||||
si5351a_clock_control_common[3].ms_src(ref_pll_b),
|
||||
si5351a_clock_control_common[4].ms_src(ref_pll_a),
|
||||
si5351a_clock_control_common[5].ms_src(ref_pll_a),
|
||||
si5351a_clock_control_common[6].ms_src(ref_pll_b),
|
||||
si5351a_clock_control_common[7].ms_src(ref_pll_b),
|
||||
}};
|
||||
// clock_generator.set_clock_control(si5351_clock_control);
|
||||
// Use single-byte writes instead of multi-byte
|
||||
clock_generator.set_clock_control_single_byte(si5351_clock_control);
|
||||
// CLK2: MCU_CLK 40 MHz
|
||||
clock_generator.write_ms_single_byte(
|
||||
2,
|
||||
si5351_ms_afe_40m);
|
||||
|
||||
// Don't write CLKS 3, 6, and 7 multisynth
|
||||
// Ensure CLK3 clock control has Power_Off
|
||||
// Verify output is disabled
|
||||
clock_generator.disable_output(3);
|
||||
clock_generator.disable_clock(3);
|
||||
clock_generator.disable_output(6);
|
||||
clock_generator.disable_clock(6);
|
||||
clock_generator.disable_output(7);
|
||||
clock_generator.disable_clock(7);
|
||||
// CLK3: P2 clock mux, disabled below
|
||||
clock_generator.write_ms_single_byte(
|
||||
3,
|
||||
si5351_ms_10m);
|
||||
|
||||
// CLK4: DXCVR_CLK 40 MHz
|
||||
clock_generator.write_ms_single_byte(
|
||||
4,
|
||||
si5351_ms_afe_40m);
|
||||
|
||||
// CLK5: DMIX_CLK 40 MHz
|
||||
clock_generator.write_ms_single_byte(
|
||||
5,
|
||||
si5351_ms_afe_40m);
|
||||
|
||||
{
|
||||
const auto& ms6_7 = si5351a_ms6_7_off_reg;
|
||||
for (size_t i = 1; i < ms6_7.size(); i++) {
|
||||
clock_generator.write_register(ms6_7[0] + i - 1, ms6_7[i]);
|
||||
}
|
||||
}
|
||||
#else
|
||||
if (hackrf_r9) {
|
||||
const PLLReg pll_reg = (reference.source == ReferenceSource::Xtal)
|
||||
@@ -645,15 +615,14 @@ void ClockManager::init_clock_generator() {
|
||||
|
||||
// Wait for PLL(s) to lock.
|
||||
#ifdef PRALINE
|
||||
// PRALINE: Wait for 0x60 (0x20 | 0x40), PLLA and PLLB to lock (0x20 = LOL_A bit, 0x40 = LOL_B bit)
|
||||
uint8_t device_status_mask = 0x60;
|
||||
uint8_t device_status_mask =
|
||||
(ref_pll == ClockControl::MultiSynthSource::PLLB) ? 0x40 : 0x20;
|
||||
uint32_t pll_timeout = 100000;
|
||||
while ((clock_generator.device_status() & device_status_mask) != 0 && pll_timeout > 0) {
|
||||
pll_timeout--;
|
||||
}
|
||||
// Store PLL lock status for debugging
|
||||
static volatile uint32_t pll_lock_timeout = pll_timeout;
|
||||
(void)pll_lock_timeout;
|
||||
|
||||
clock_generator.enable_output(clock_generator_output_pro_mcu_clkin);
|
||||
|
||||
#else
|
||||
// Wait for PLL(s) to lock - with timeout to prevent hang
|
||||
@@ -753,13 +722,13 @@ void ClockManager::shutdown() {
|
||||
|
||||
void ClockManager::enable_codec_clocks() {
|
||||
#ifdef PRALINE
|
||||
/* PRALINE: CLK0 (AFE_CLK) for codec/FPGA, CLK1 (SCT_CLK) for FPGA timing.
|
||||
* Reference hackrf_core.c shows PRALINE needs both CLK0 and CLK1. */
|
||||
/* PRALINE: only gate the clocks owned by the RF datapath here.
|
||||
* CLK2 is MCU_CLKIN and must stay independent of runtime RX/TX clock
|
||||
* management or the SGPIO/GPIO subsystem can glitch mid-stream. */
|
||||
clock_generator.enable_clock(clock_generator_output_og_codec); /* CLK0 MAX5864*/
|
||||
clock_generator.enable_clock(clock_generator_output_og_cpld); /* CLK1 iCE40 FPGA*/
|
||||
clock_generator.enable_clock(clock_generator_output_og_sgpio); /* CLK2 LPC43xx*/
|
||||
clock_generator.enable_output_mask(
|
||||
(1U << clock_generator_output_og_codec) | (1U << clock_generator_output_og_cpld) | (1U << clock_generator_output_og_sgpio));
|
||||
(1U << clock_generator_output_og_codec) | (1U << clock_generator_output_og_cpld));
|
||||
#else
|
||||
if (hackrf_r9) {
|
||||
clock_generator.enable_clock(clock_generator_output_r9_sgpio);
|
||||
@@ -787,12 +756,11 @@ void ClockManager::disable_codec_clocks() {
|
||||
* CLKx_DISABLE_STATE.
|
||||
*/
|
||||
#ifdef PRALINE
|
||||
/* PRALINE: CLK0 (AFE_CLK), CLK1 (SCT_CLK), and CLK2 MCU used for codec/FPGA */
|
||||
/* PRALINE: leave CLK2/MCU_CLKIN alone; only disable datapath-owned clocks. */
|
||||
clock_generator.disable_output_mask(
|
||||
(1U << clock_generator_output_og_codec) | (1U << clock_generator_output_og_cpld) | (1U << clock_generator_output_og_sgpio));
|
||||
(1U << clock_generator_output_og_codec) | (1U << clock_generator_output_og_cpld));
|
||||
clock_generator.disable_clock(clock_generator_output_og_codec);
|
||||
clock_generator.disable_clock(clock_generator_output_og_cpld);
|
||||
clock_generator.disable_clock(clock_generator_output_og_sgpio);
|
||||
#else
|
||||
if (hackrf_r9) {
|
||||
clock_generator.disable_output_mask(1U << clock_generator_output_r9_sgpio);
|
||||
@@ -851,7 +819,7 @@ void ClockManager::set_sampling_frequency(const uint32_t frequency) {
|
||||
/*
|
||||
* PRALINE sample rate strategy:
|
||||
* 1. Maximize AFE rate to push Nyquist above MAX2831's 11.6 MHz LPF minimum
|
||||
* 2. Use FPGA decimation to achieve desired output rate
|
||||
* 2. Use FPGA decimation/interpolation to achieve desired output rate
|
||||
* 3. Ensure AFE rate is achievable by Si5351 (clean division from 800 MHz VCO)
|
||||
*/
|
||||
|
||||
@@ -872,50 +840,96 @@ void ClockManager::set_sampling_frequency(const uint32_t frequency) {
|
||||
n++;
|
||||
}
|
||||
|
||||
_resampling_n = n;
|
||||
|
||||
// === Stop FPGA processing and flush filters ===
|
||||
fpga_debug_register_write(1, 0x00); // Disable FPGA filters (resets CIC accumulators)
|
||||
|
||||
// Verify we're in RX mode before writing RX registers
|
||||
if (fpga_get_mode() != FPGA_MODE_RX) {
|
||||
// Either set mode or return error
|
||||
fpga_set_mode(FPGA_MODE_RX);
|
||||
/* The TX gateware only implements interpolation factors x1..x16
|
||||
* (tx_intrp 0..4). The rate search above can select n == 5 (x32) for very
|
||||
* low TX sample rates. If we kept afe_rate/CLK0/CLK1 at the x32 rate while
|
||||
* the FPGA only interpolated by x16, the TX datapath would be clocked at
|
||||
* twice the gateware's output rate, doubling the waveform speed. Cap the
|
||||
* AFE rate and _resampling_n to the x16 limit in TX so the clocks and the
|
||||
* interpolation setting stay aligned. */
|
||||
if (radio::debug::get_cached_direction() == rf::Direction::Transmit) {
|
||||
constexpr uint8_t MAX_TX_N = 4; // x16, matches max tx_intrp
|
||||
if (n > MAX_TX_N) {
|
||||
n = MAX_TX_N;
|
||||
afe_rate = frequency << n;
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
* Relationship: ds = (Stages * n) - Offset
|
||||
* For a 3-stage filter, every increment of n grows the signal by 3 bits.
|
||||
* We subtract a baseline offset to keep the signal within 8-bit bounds.
|
||||
* Add a baseline shift to ensure the signal isn't too quiet
|
||||
*/
|
||||
uint8_t ds = (3 * n);
|
||||
ds += 2;
|
||||
fpga_debug_register_write(FPGA_REG_RX_DIGITAL_GAIN, ds);
|
||||
_resampling_n = n;
|
||||
|
||||
radio::invalidate_spi_config();
|
||||
|
||||
// Configure Si5351 clocks
|
||||
// CLK0: AFE_CLK (with r_div=1 for ÷2)
|
||||
// CLK1: SCT_CLK (with r_div=0 for ÷1, runs at 2× AFE for FPGA timing)
|
||||
// Configure Si5351 clocks using the correct AFE VCO
|
||||
// CLK0 always drives the AFE/MAX5864 clock.
|
||||
clock_generator.set_ms_frequency(0, afe_rate * 2, si5351_vco_afe_f, 1);
|
||||
clock_generator.set_ms_frequency(1, afe_rate * 2, si5351_vco_afe_f, 0);
|
||||
|
||||
// === Reset PLL A for phase alignment ===
|
||||
clock_generator.write_register(si5351::Register::PLLReset, 0x20);
|
||||
/* Do not reset PLLA here. On PRALINE, CLK2/MCU_CLKIN is sourced from PLLA,
|
||||
* so a runtime PLLA reset can glitch the LPC43xx peripheral clock tree and
|
||||
* break SGPIO-driven RX apps such as ADSB/APRS. Updating the multisynths is
|
||||
* sufficient for sample-rate changes. */
|
||||
|
||||
// Brief delay for PLL lock and clock stability ===
|
||||
// ~1ms at 96MHz = ~96000 cycles, use 10ms for safety
|
||||
volatile uint32_t delay = 240000; // ~2.5ms
|
||||
while (delay--);
|
||||
if (radio::debug::get_cached_direction() == rf::Direction::Transmit) {
|
||||
/*
|
||||
* TX path:
|
||||
* Baseband generators such as AFSK still synthesize samples at their
|
||||
* legacy logical sample rates (for APRS this is 1.536MHz). On PRALINE,
|
||||
* the FPGA must interpolate those samples up to the active AFE rate.
|
||||
*
|
||||
* The TX datapath is clocked from CLK1/fpgaclk. Therefore CLK1 must
|
||||
* equal the desired complex sample rate at the DAC side. Driving CLK1
|
||||
* at 2x the intended TX sample rate causes the entire APRS waveform to
|
||||
* run twice as fast, which matches the "energy present, undecodable"
|
||||
* symptom seen on HackRF One receivers.
|
||||
*
|
||||
* Gateware mapping from standard.py:
|
||||
* tx_intrp = 0 -> x1
|
||||
* tx_intrp = 1 -> x2
|
||||
* tx_intrp = 2 -> x4
|
||||
* tx_intrp = 3 -> x8
|
||||
* tx_intrp = 4 -> x16
|
||||
*/
|
||||
if (fpga_get_mode() != FPGA_MODE_TX) {
|
||||
fpga_set_mode(FPGA_MODE_TX);
|
||||
}
|
||||
|
||||
// Re-enable FPGA processing with clean state ===
|
||||
fpga_debug_register_write(1, 0x01);
|
||||
// TX gateware consumes complex samples at the rate presented on CLK1.
|
||||
clock_generator.set_ms_frequency(1, afe_rate, si5351_vco_afe_f, 0);
|
||||
|
||||
uint8_t tx_interp = 0;
|
||||
uint32_t tx_rate = frequency;
|
||||
while ((tx_rate < afe_rate) && (tx_interp < 4)) {
|
||||
tx_rate <<= 1;
|
||||
tx_interp++;
|
||||
}
|
||||
fpga_tx_set_interpolation(tx_interp);
|
||||
fpga_tx_set_nco_enable(false);
|
||||
fpga_tx_set_phase_step(0);
|
||||
}
|
||||
// for RX mode
|
||||
else {
|
||||
// RX path keeps CLK1 at 2x AFE for receive timing / SGPIO alignment.
|
||||
clock_generator.set_ms_frequency(1, afe_rate * 2, si5351_vco_afe_f, 0);
|
||||
|
||||
// === Stop FPGA processing and flush filters ===
|
||||
fpga_debug_register_write(1, 0x00); // Disable FPGA filters (resets CIC accumulators)
|
||||
|
||||
if (fpga_get_mode() != FPGA_MODE_RX) {
|
||||
fpga_set_mode(FPGA_MODE_RX);
|
||||
}
|
||||
|
||||
// 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.
|
||||
*/
|
||||
uint8_t ds = (3 * n);
|
||||
ds += 2;
|
||||
fpga_debug_register_write(FPGA_REG_RX_DIGITAL_GAIN, ds);
|
||||
|
||||
// Re-enable FPGA processing with clean state ===
|
||||
fpga_debug_register_write(1, 0x01);
|
||||
}
|
||||
|
||||
#else
|
||||
/* Codec clock is at sampling frequency, CPLD and SGPIO clocks are at
|
||||
|
||||
@@ -360,19 +360,12 @@ static void set_cpu_clock_speed() {
|
||||
|
||||
/* Step into the 90-110MHz M4 clock range */
|
||||
#ifdef PRALINE
|
||||
/* PRALINE: Enable and use 12MHz XTAL directly (no GP_CLKIN from Si5351) */
|
||||
|
||||
/* Step 1: Enable the crystal oscillator */
|
||||
LPC_CGU->XTAL_OSC_CTRL.ENABLE = 0; // 0 = enable (active low)
|
||||
LPC_CGU->XTAL_OSC_CTRL.HF = 0; // 0 = low frequency mode (1-20MHz)
|
||||
|
||||
/* Step 2: Wait for oscillator to stabilize (~250us at IRC speed) */
|
||||
volatile uint32_t delay = 3000; // ~250us at 12MHz IRC
|
||||
while (delay--);
|
||||
|
||||
/* Step 3: Configure PLL1 from XTAL
|
||||
* Fclkin = 12M, /N=1 = 12M, Fcco = 12M * 17 = 204M
|
||||
* Fclk = Fcco / (2*(P=1)) = 102M
|
||||
/* PRALINE: source PLL1 from GP_CLKIN, which is driven by the Si5351
|
||||
* CLK2/MCU_CLK output at 40MHz (see ClockManager Si5351 setup). The
|
||||
* on-chip 12MHz XTAL bootstrap path is intentionally not used here.
|
||||
*
|
||||
* Fclkin = 40M (GP_CLKIN), /N=2 = 20M, Fcco = 20M * 10 = 200M
|
||||
* Fclk = Fcco / (2*(P=1)) = 100M
|
||||
*/
|
||||
cgu::pll1::ctrl({
|
||||
.pd = 1,
|
||||
@@ -381,10 +374,11 @@ static void set_cpu_clock_speed() {
|
||||
.direct = 0,
|
||||
.psel = 0,
|
||||
.autoblock = 1,
|
||||
.nsel = 0, // N = 1
|
||||
.msel = 16, // M = 17, so 12MHz * 17 = 204MHz
|
||||
.clk_sel = cgu::CLK_SEL::XTAL,
|
||||
.nsel = 1UL, // N = 2
|
||||
.msel = 9UL, // M = 10
|
||||
.clk_sel = cgu::CLK_SEL::GP_CLKIN,
|
||||
});
|
||||
|
||||
#else
|
||||
/* OG:
|
||||
* Fclkin = 40M, /N=2 = 20M, Fcco = 20M * 10 = 200M
|
||||
@@ -613,6 +607,7 @@ init_status_t init() {
|
||||
cgu::pll1::disable();
|
||||
|
||||
set_cpu_clock_speed();
|
||||
|
||||
/* sample max: 1023 sample_t AKA uint16_t
|
||||
* touch_sensitivity: range: 1 to 128
|
||||
* threshold range: 1023/1 to 1023/128 = 1023 to 8
|
||||
|
||||
@@ -390,11 +390,13 @@ void set_baseband_rate(const uint32_t rate) {
|
||||
}
|
||||
|
||||
void set_antenna_bias(const bool on) {
|
||||
/* Pull MOSFET gate low to turn on antenna bias. */
|
||||
#ifdef PRALINE
|
||||
// Praline: P2_12 = GPIO1[12], ANT_BIAS_EN_N (active LOW)
|
||||
LPC_GPIO->CLR[1] = on ? (1 << 12) : 0;
|
||||
LPC_GPIO->SET[1] = on ? 0 : (1 << 12);
|
||||
/* Keep bias control inside the PRALINE RF path state machine so later
|
||||
* band/direction/amp updates don't silently force ANT_BIAS_EN_N off.
|
||||
* Reassert output mode here because this pin is safety-critical and
|
||||
* direct GPIO writes on an input-configured pad have no effect. */
|
||||
gpio_ant_bias_disable.output();
|
||||
rf_path.set_antenna_bias(on);
|
||||
#else
|
||||
if (hackrf_r9) {
|
||||
gpio_r9_not_ant_pwr.write(on ? 0 : 1);
|
||||
@@ -627,4 +629,4 @@ uint32_t register_read(const size_t register_number) {
|
||||
|
||||
} /* namespace debug */
|
||||
|
||||
} /* namespace radio */
|
||||
} /* namespace radio */
|
||||
|
||||
@@ -263,6 +263,11 @@ void Path::set_rf_amp(const bool new_rf_amp) {
|
||||
update();
|
||||
}
|
||||
|
||||
void Path::set_antenna_bias(const bool new_antenna_bias) {
|
||||
antenna_bias = new_antenna_bias;
|
||||
update();
|
||||
}
|
||||
|
||||
void Path::update() {
|
||||
/* 0 ^ 0 => 0 & 0 = 0 ^ 0 = 0 (no change)
|
||||
* 0 ^ 1 => 1 & 0 = 0 ^ 0 = 0 (ignore change to 1)
|
||||
@@ -301,8 +306,8 @@ void Path::update() {
|
||||
/* RF amp when amplification requested */
|
||||
config.rf_amp_en = rf_amp;
|
||||
|
||||
/* Antenna bias off by default */
|
||||
config.ant_bias_en_n = true;
|
||||
/* Preserve requested antenna bias state across band/direction/amp updates. */
|
||||
config.ant_bias_en_n = !antenna_bias;
|
||||
|
||||
config.apply();
|
||||
|
||||
|
||||
@@ -65,6 +65,7 @@ class Path {
|
||||
void set_direction(const Direction direction);
|
||||
void set_band(const Band band);
|
||||
void set_rf_amp(const bool rf_amp);
|
||||
void set_antenna_bias(const bool antenna_bias);
|
||||
|
||||
Band get_band() const { return _band; } //_band is used solely for debugging purposes.
|
||||
|
||||
@@ -72,6 +73,7 @@ class Path {
|
||||
Direction direction{Direction::Receive};
|
||||
Band band{Band::Mid};
|
||||
bool rf_amp{false};
|
||||
bool antenna_bias{false};
|
||||
|
||||
void update();
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#include <cstddef>
|
||||
|
||||
#include "app_settings.hpp"
|
||||
#include "hackrf_hal.hpp"
|
||||
#include "max2837.hpp"
|
||||
#include "message.hpp"
|
||||
#include "receiver_model.hpp"
|
||||
@@ -40,8 +41,9 @@ class TransmitterModel {
|
||||
uint32_t baseband_bandwidth = max283x::filter::bandwidth_minimum;
|
||||
uint32_t sampling_rate = 3'072'000;
|
||||
uint32_t channel_bandwidth = 1;
|
||||
/* 35 should give approx 1m transmission range. */
|
||||
uint8_t tx_gain_db = 35;
|
||||
/* Platform default chosen to avoid PRALINE MAX2831 overdrive while
|
||||
* preserving legacy HackRF One behavior. */
|
||||
uint8_t tx_gain_db = hackrf::one::default_tx_gain_db;
|
||||
bool rf_amp = false;
|
||||
};
|
||||
|
||||
|
||||
@@ -140,7 +140,7 @@
|
||||
// ============================================================================
|
||||
static fpga_mode_t current_mode = FPGA_MODE_OFF;
|
||||
// Cached register values for debug reads (since reads may require mode switch)
|
||||
static uint8_t fpga_reg_cache[6] = {0, 0x01, 0x00, 0x00, 0x00, 0x00};
|
||||
static uint8_t fpga_reg_cache[7] = {0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
|
||||
// Context structure for SPIFI-based reading
|
||||
struct spifi_fpga_read_ctx {
|
||||
@@ -315,7 +315,7 @@ static void fpga_spi_write(uint8_t reg, uint8_t value) {
|
||||
// Public function to read FPGA register (callable from C++ application code)
|
||||
// Switches SPI mode, reads register, switches back
|
||||
uint8_t fpga_debug_register_read(uint8_t reg) {
|
||||
if (reg == 0 || reg > 5) return 0xFF;
|
||||
if (reg == 0 || reg > 6) return 0xFF;
|
||||
|
||||
uint8_t value;
|
||||
ssp1_set_mode_ice40();
|
||||
@@ -328,7 +328,7 @@ uint8_t fpga_debug_register_read(uint8_t reg) {
|
||||
|
||||
// Public function to write FPGA register (callable from C++ application code)
|
||||
void fpga_debug_register_write(uint8_t reg, uint8_t value) {
|
||||
if (reg == 0 || reg > 5) return;
|
||||
if (reg == 0 || reg > 6) return;
|
||||
|
||||
ssp1_set_mode_ice40();
|
||||
fpga_spi_write(reg, value);
|
||||
@@ -342,7 +342,7 @@ void fpga_debug_register_write(uint8_t reg, uint8_t value) {
|
||||
// ============================================================================
|
||||
|
||||
uint8_t fpga_register_read(uint8_t reg) {
|
||||
if (reg == 0 || reg > 5) return 0xFF;
|
||||
if (reg == 0 || reg > 6) return 0xFF;
|
||||
|
||||
ssp1_set_mode_ice40();
|
||||
uint8_t val = fpga_spi_read(reg);
|
||||
@@ -353,7 +353,7 @@ uint8_t fpga_register_read(uint8_t reg) {
|
||||
}
|
||||
|
||||
void fpga_register_write(uint8_t reg, uint8_t value) {
|
||||
if (reg == 0 || reg > 5) return;
|
||||
if (reg == 0 || reg > 6) return;
|
||||
|
||||
ssp1_set_mode_ice40();
|
||||
fpga_spi_write(reg, value);
|
||||
@@ -422,22 +422,22 @@ 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_REG_SHARED_3);
|
||||
uint8_t val = fpga_register_read(FPGA_REG3_TX_NCO_CTRL);
|
||||
if (enable)
|
||||
val |= FPGA_TX_NCO_EN;
|
||||
else
|
||||
val &= ~FPGA_TX_NCO_EN;
|
||||
fpga_register_write(FPGA_REG_SHARED_3, val);
|
||||
fpga_register_write(FPGA_REG3_TX_NCO_CTRL, val);
|
||||
}
|
||||
|
||||
void fpga_tx_set_interpolation(uint8_t ratio) {
|
||||
if (current_mode != FPGA_MODE_TX) return;
|
||||
fpga_register_write(FPGA_REG_SHARED_4, ratio & FPGA_TX_INTERP_MASK);
|
||||
fpga_register_write(FPGA_REG_TX_INTERP, ratio & FPGA_TX_INTERP_MASK);
|
||||
}
|
||||
|
||||
void fpga_tx_set_phase_step(uint8_t step) {
|
||||
if (current_mode != FPGA_MODE_TX) return;
|
||||
fpga_register_write(FPGA_REG_SHARED_5, step);
|
||||
fpga_register_write(FPGA_REG_TX_PHASE_STEP, step);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
@@ -16,17 +16,21 @@ extern "C" {
|
||||
|
||||
#ifdef PRALINE
|
||||
|
||||
/* FPGA Register Map Address 0x03 (Dual Purpose) */
|
||||
/* RX path (legacy PRALINE software map kept for compatibility) */
|
||||
#define FPGA_REG_RX_DIGITAL_GAIN 0x03 /* Digital Shift / scaling (RX Mode) */
|
||||
#define FPGA_REG_TX_CONTROL 0x03 /* NCO_EN and TX flags (TX Mode) */
|
||||
|
||||
/* FPGA Register Map Address 0x04 (Shared) */
|
||||
#define FPGA_REG_RX_DC_BLOCK_WIDTH 0x04 /* Notch filter cutoff (RX Mode) */
|
||||
#define FPGA_REG_TX_INTERP 0x04 /* Interpolation ratio (TX Mode) */
|
||||
|
||||
/* FPGA Register Map Address 0x05 (Shared) */
|
||||
#define FPGA_REG_RX_DC_ADAPT_RATE 0x05 /* Settle time/Integration (RX Mode) */
|
||||
#define FPGA_REG_TX_PHASE_STEP 0x05 /* NCO frequency step (TX Mode) */
|
||||
|
||||
/*
|
||||
* TX path must match the currently built PRALINE standard gateware in
|
||||
* hackrf/firmware/fpga/top/standard.py:
|
||||
* 0x04 tx_ctrl
|
||||
* 0x05 tx_intrp
|
||||
* 0x06 tx_pstep
|
||||
*/
|
||||
#define FPGA_REG_TX_CONTROL 0x04
|
||||
#define FPGA_REG_TX_INTERP 0x05
|
||||
#define FPGA_REG_TX_PHASE_STEP 0x06
|
||||
|
||||
/*
|
||||
* FPGA Operating Mode
|
||||
@@ -39,13 +43,21 @@ typedef enum {
|
||||
|
||||
/*
|
||||
* FPGA Register Addresses
|
||||
* Note: Registers 3-5 are dual-purpose (meaning depends on RX/TX mode)
|
||||
* 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
|
||||
*/
|
||||
#define FPGA_REG_CTRL 0x01 /* Control register */
|
||||
#define FPGA_REG_DECIM 0x02 /* Decimation (RX) / unused (TX) */
|
||||
#define FPGA_REG_SHARED_3 0x03 /* Dual-purpose register */
|
||||
#define FPGA_REG_SHARED_4 0x04 /* Dual-purpose register */
|
||||
#define FPGA_REG_SHARED_5 0x05 /* Dual-purpose 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 */
|
||||
|
||||
/*
|
||||
* Register 1 (CTRL) Bit Definitions
|
||||
@@ -77,7 +89,7 @@ typedef enum {
|
||||
#define FPGA_RX_DC_WIDTH_MASK 0x07 /* Bits [2:0] */
|
||||
|
||||
/* TX Mode: Interpolation ratio */
|
||||
#define FPGA_REG4_TX_INTERP 0x04
|
||||
#define FPGA_REG4_TX_INTERP 0x05
|
||||
#define FPGA_TX_INTERP_MASK 0x07 /* Bits [2:0] */
|
||||
|
||||
/*
|
||||
@@ -88,7 +100,7 @@ typedef enum {
|
||||
#define FPGA_RX_DC_RATE_MASK 0xFF /* Bits [7:0] */
|
||||
|
||||
/* TX Mode: NCO phase step (frequency) */
|
||||
#define FPGA_REG5_TX_PHASE_STEP 0x05
|
||||
#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 */
|
||||
|
||||
@@ -54,6 +54,12 @@ constexpr ClockFrequency max283x_reference_f = 40000000U;
|
||||
constexpr ClockFrequency mcu_clkin_og_f = 40000000U;
|
||||
constexpr ClockFrequency mcu_clkin_r9_f = 10000000U;
|
||||
|
||||
#ifdef PRALINE
|
||||
constexpr int8_t default_tx_gain_db = 16;
|
||||
#else
|
||||
constexpr int8_t default_tx_gain_db = 35;
|
||||
#endif
|
||||
|
||||
constexpr uint8_t si5351_i2c_address = 0x60;
|
||||
|
||||
/* Clock Generator */
|
||||
|
||||
Reference in New Issue
Block a user