Files
mayhem-firmware/firmware/application/radio.cpp
T
Pezsma 2ad34bbc09 Gpio modify v3 (#3238)
* Refactor GPIO mappings and definitions for improved clarity and functionality

- Updated SCU_ARRAY_SIZE definitions in pal_lld.h for PRALINE and non-PRALINE configurations.
- Modified pin mappings in gpio.hpp to reflect new hardware configurations, including additional control pins for RF and audio components.
- Removed obsolete GPIO definitions from hackrf_gpio.hpp and streamlined the code for better maintainability.
- Added new GPIO definitions for mix bypass, amplifier control, and other RF-related functionalities to enhance the system's capabilities.

* Refactor GPIO mappings and update SCU_ARRAY_SIZE for LPC43xx platform

- Updated SCU_ARRAY_SIZE to 80 for PRALINE configuration and 58 for others.
- Added new GPIO mappings for auxiliary power control, antenna bias, and R9 clock enable signals in gpio.hpp.
- Removed commented-out PPS output mapping in hackrf_gpio.hpp.
- Adjusted GPIO definitions for R9 clock signals to ensure proper functionality.

* Refactor RF path initialization and configuration for improved clarity and functionality

* Refactor RF path configuration and GPIO mappings for improved clarity and functionality

* Potential fix for pull request finding

* comment

* aux power polarrity

* Fix formatting of pin_aux_power_enable declaration
2026-07-02 10:05:34 +02:00

628 lines
20 KiB
C++

/*
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#include "radio.hpp"
#include "rf_path.hpp"
#include "rffc507x.hpp"
#ifdef PRALINE
#include "max2831.hpp"
extern "C" {
#include "fpga_bridge.h"
}
#else
#include "max2837.hpp"
#include "max2839.hpp"
#include "baseband_cpld.hpp"
#endif
#include "max5864.hpp"
#include "tuning.hpp"
#include "spi_arbiter.hpp"
#include "hackrf_hal.hpp"
#include "hackrf_gpio.hpp"
using namespace hackrf::one;
#include "cpld_update.hpp"
#include "portapack.hpp"
#include "portapack_persistent_memory.hpp"
#include "baseband_api.hpp"
#include "hal.h" // For LPC_SGPIO
#include "gpio.hpp"
using namespace gpio_control;
#include <array>
/* Direct access to the radio. Setting values incorrectly can damage
* the device. Applications should use ReceiverModel or TransmitterModel
* instead of calling these functions directly. */
namespace radio {
static constexpr uint32_t ssp1_cpsr = 2;
static constexpr uint32_t ssp_scr(
const float pclk_f,
const uint32_t cpsr,
const float spi_f) {
return static_cast<uint8_t>(pclk_f / cpsr / spi_f - 1);
}
#ifdef PRALINE
/* MAX2831 uses 9-bit SPI transfers */
static constexpr SPIConfig ssp_config_max283x = {
.end_cb = NULL,
.ssport = gpio_max283x_select.port(),
.sspad = gpio_max283x_select.pad(),
.cr0 =
CR0_CLOCKRATE(ssp_scr(ssp1_pclk_f, ssp1_cpsr, max283x_spi_f) + 3) | CR0_FRFSPI | CR0_DSS9BIT,
.cpsr = ssp1_cpsr,
};
static max283x::MAX283x* transceiver = nullptr;
void set_rx_buff_vcm(const size_t v) {
if (transceiver) {
transceiver->set_rx_buff_vcm(v);
}
}
#else
/* MAX2837/MAX2839 use 16-bit SPI transfers */
static constexpr SPIConfig ssp_config_max283x = {
.end_cb = NULL,
.ssport = gpio_max283x_select.port(),
.sspad = gpio_max283x_select.pad(),
.cr0 =
CR0_CLOCKRATE(ssp_scr(ssp1_pclk_f, ssp1_cpsr, max283x_spi_f) + 3) | CR0_FRFSPI | CR0_DSS16BIT,
.cpsr = ssp1_cpsr,
};
#endif
static constexpr SPIConfig ssp_config_max5864 = {
.end_cb = NULL,
.ssport = gpio_max5864_select.port(),
.sspad = gpio_max5864_select.pad(),
.cr0 =
CR0_CLOCKRATE(ssp_scr(ssp1_pclk_f, ssp1_cpsr, max5864_spi_f)) | CR0_FRFSPI | CR0_DSS8BIT,
.cpsr = ssp1_cpsr,
};
static spi::arbiter::Arbiter ssp1_arbiter(portapack::ssp1);
static spi::arbiter::Target ssp1_target_max283x{
ssp1_arbiter,
ssp_config_max283x};
static spi::arbiter::Target ssp1_target_max5864{
ssp1_arbiter,
ssp_config_max5864};
static rf::path::Path rf_path;
rffc507x::RFFC507x first_if;
max283x::MAX283x* second_if;
#ifdef PRALINE
max2831::MAX2831 second_if_max2831{ssp1_target_max283x};
#else
max2837::MAX2837 second_if_max2837{ssp1_target_max283x};
max2839::MAX2839 second_if_max2839{ssp1_target_max283x};
static baseband::CPLD baseband_cpld;
#endif
static max5864::MAX5864 baseband_codec{ssp1_target_max5864};
// load_sram() is called at boot in portapack.cpp, including verify CPLD part, so default direction is Receive
static rf::Direction direction{rf::Direction::Receive};
static bool baseband_invert = false;
static bool mixer_invert = false;
#ifdef PRALINE
static rf::Direction cached_direction = rf::Direction::Receive;
static bool cached_rf_amp = false;
static int_fast8_t cached_lna_gain = 0;
static int_fast8_t cached_vga_gain = 0;
#endif
void init() {
#ifdef PRALINE
/* PRALINE uses MAX2831 transceiver */
second_if = (max283x::MAX283x*)&second_if_max2831;
#else
second_if = hackrf_r9
? (max283x::MAX283x*)&second_if_max2839
: (max283x::MAX283x*)&second_if_max2837;
#endif
rf_path.init();
first_if.init();
second_if->init();
baseband_codec.init();
#ifdef PRALINE
/* Praline-Specific Bus and Gateware Configuration */
// SYNC SGPIO TO FPGA CLOCK:
// Configure all 16 SGPIO slices to use the external clock (SGPIO8)
// provided by the FPGA. This allows the MCU to stay at 40MHz
// while the data bus scales to the RF sample rate.
// Bit 2:1 of SGPIO_MUX_CFG = 01 (External clock from SGPIO8)
// SYNC SGPIO TO FPGA CLOCK WITH FALLING EDGE LATCH
for (int i = 0; i < 16; i++) {
// (1 << 1) = External clock from SGPIO8
// (1 << 3) = Sample on the FALLING edge of the clock
LPC_SGPIO->SGPIO_MUX_CFG[i] = (1 << 1) | (1 << 3);
}
/* Initialize FPGA registers - DC_BLOCK must be enabled for RX */
// debug::fpga::init();
fpga_set_mode(FPGA_MODE_RX);
// These FPGA registers control DC_BLOCK, Q-Inv, QUARTER SHIFT, and Decimation.
fpga_debug_register_write(FPGA_REG_CTRL, FPGA_CTRL_DC_BLOCK_EN); // DC_BLOCK=1, QUARTER_SHIFT=0, Q_INVERT=0
fpga_debug_register_write(FPGA_REG_DECIM, 0x00); // RX_DECIM=No Decim
// RX Mode: Register 3 is RX Digital Gain. Start with 0dB (no shift).
fpga_debug_register_write(FPGA_REG_RX_DIGITAL_GAIN, FPGA_RX_DEFAULT_DIGITAL_GAIN);
/* RX Mode: Initialize DC Block parameters to standard Praline values.
* 0x04 Width and 0x08 Adapt Rate are typical for 40MHz stability.
*/
fpga_debug_register_write(FPGA_REG_RX_DC_BLOCK_WIDTH, FPGA_RX_DEFAULT_DC_WIDTH);
fpga_debug_register_write(FPGA_REG_RX_DC_ADAPT_RATE, FPGA_RX_DEFAULT_ADAPT_RATE);
ssp1_arbiter.invalidate();
chThdSleepMilliseconds(10); // Let FPGA registers settle
#else
/* HackRF One uses CPLD for Q inversion control.
* PRALINE uses FPGA and the pin (P2_3) is used for LCD_TE on H4M. */
baseband_cpld.init();
#endif
}
void set_direction(const rf::Direction new_direction) {
/* TODO: Refactor all the various "Direction" enumerations into one. */
/* TODO: Only make changes if direction changes, but beware of clock enabling. */
// That below code line , was used to prevent RX interf ghosting when switching back to RX from any TX mode, but in recent code. it seems not necessary.
// Deleting that load_sram_no_verify() (or the original , load_sram() ), solves random TX swap I/Q problem in H1R1 , others OK- (and no side effects to all).
// hackrf::cpld::load_sram_no_verify(); // After commit "removed the use of the hackrf cpld eeprom #1732", in a H1R1, Mic App wrong SSB TX with random USB/LSB change.
#ifdef PRALINE
cached_direction = new_direction; // Track state for debug and potentially other purposes.
if (new_direction == rf::Direction::Transmit) {
fpga_set_mode(FPGA_MODE_TX);
// TX Mode: Clear RX gain and ensure NCO is off initially
fpga_debug_register_write(FPGA_REG_TX_CONTROL, 0x00);
// Placeholder: Set TX-specific interpolation and phase
fpga_debug_register_write(FPGA_REG_TX_INTERP, 0x00);
fpga_debug_register_write(FPGA_REG_TX_PHASE_STEP, 0x00);
} else {
fpga_set_mode(FPGA_MODE_RX);
// RX Mode: Ensure NCO is disabled and reset digital gain
fpga_debug_register_write(FPGA_REG_RX_DIGITAL_GAIN, FPGA_RX_DEFAULT_DIGITAL_GAIN);
/* RX Mode: Initialize DC Block parameters to standard Praline values.
* 0x04 Width and 0x08 Adapt Rate are typical for 40MHz stability.
*/
fpga_debug_register_write(FPGA_REG_RX_DC_BLOCK_WIDTH, FPGA_RX_DEFAULT_DC_WIDTH);
fpga_debug_register_write(FPGA_REG_RX_DC_ADAPT_RATE, FPGA_RX_DEFAULT_ADAPT_RATE);
}
#endif
direction = new_direction;
if (hackrf_r9) {
/*
* HackRF One r9 inverts analog baseband only for RX. Previous hardware
* revisions inverted analog baseband for neither direction because of
* compensation in the CPLD. If we ever simplify the CPLD to handle RX
* and TX the same way, we will need to update this baseband_invert
* logic.
*/
baseband_invert = (direction == rf::Direction::Receive);
} else {
/*
* Analog baseband is inverted in RX but not TX. The RX inversion is
* corrected by the CPLD, but future hardware or CPLD changes may
* change this for either or both directions. For a given hardware+CPLD
* platform, baseband inversion is set here for RX and/or TX. Spectrum
* inversion resulting from the mixer is tracked separately according
* to the tuning configuration. We ask the CPLD to apply a correction
* for the total inversion.
*/
baseband_invert = false;
}
#ifdef PRALINE
// Q inversion controlled by GPIO0[13] (SGPIO12), not FPGA register
bool q_invert = mixer_invert ^ baseband_invert;
if (q_invert) {
LPC_GPIO->SET[0] = (1 << 13); // SGPIO12 = 1 (Q inverted)
} else {
LPC_GPIO->CLR[0] = (1 << 13); // SGPIO12 = 0 (Q normal)
}
ssp1_arbiter.invalidate();
#else
baseband_cpld.set_invert(mixer_invert ^ baseband_invert);
#endif
second_if->set_mode((direction == rf::Direction::Transmit) ? max283x::Mode::Transmit : max283x::Mode::Receive);
rf_path.set_direction(direction);
baseband_codec.set_mode((direction == rf::Direction::Transmit) ? max5864::Mode::Transmit : max5864::Mode::Receive);
if (direction == rf::Direction::Receive)
led_rx.setActive();
else
led_tx.setActive();
}
bool set_tuning_frequency(const rf::Frequency frequency) {
rf::Frequency final_frequency = frequency;
// if converter feature is enabled
if (portapack::persistent_memory::config_converter()) {
// downconvert
if (portapack::persistent_memory::config_updown_converter()) {
final_frequency = frequency - portapack::persistent_memory::config_converter_freq();
} else // upconvert
{
final_frequency = frequency + portapack::persistent_memory::config_converter_freq();
}
}
// apply frequency correction
if (direction == rf::Direction::Transmit) {
if (portapack::persistent_memory::config_freq_tx_correction_updown()) // tx freq correction down
final_frequency = final_frequency - portapack::persistent_memory::config_freq_tx_correction();
else // tx freq correction up
final_frequency = final_frequency + portapack::persistent_memory::config_freq_tx_correction();
} else {
if (portapack::persistent_memory::config_freq_rx_correction_updown()) // rx freq correction down
final_frequency = final_frequency - portapack::persistent_memory::config_freq_rx_correction();
else // rx freq correction up
final_frequency = final_frequency + portapack::persistent_memory::config_freq_rx_correction();
}
const auto tuning_config = tuning::config::create(final_frequency);
if (tuning_config.is_valid()) {
first_if.disable();
// Program first local oscillator frequency (if there is one) into RFFC507x
if (tuning_config.first_lo_frequency) {
first_if.set_frequency(tuning_config.first_lo_frequency);
first_if.enable();
#ifdef PRALINE
first_if.flush(); // Force register write with reference clock present
chThdSleepMilliseconds(10); // Allow PLL to settle
#endif
}
// Program second local oscillator frequency into MAX283x
const auto result_second_if = second_if->set_frequency(tuning_config.second_lo_frequency);
rf_path.set_band(tuning_config.rf_path_band);
mixer_invert = tuning_config.mixer_invert;
#ifdef PRALINE
// Q inversion controlled by GPIO0[13] (SGPIO12), not FPGA register
bool q_invert = mixer_invert ^ baseband_invert;
if (q_invert) {
LPC_GPIO->SET[0] = (1 << 13); // SGPIO12 = 1 (Q inverted)
} else {
LPC_GPIO->CLR[0] = (1 << 13); // SGPIO12 = 0 (Q normal)
}
ssp1_arbiter.invalidate();
#else
baseband_cpld.set_invert(mixer_invert ^ baseband_invert);
#endif
return result_second_if;
} else {
return false;
}
}
void set_rf_amp(const bool rf_amp) {
#ifdef PRALINE
cached_rf_amp = rf_amp; // Track state for debug and potentialy other purposes.
#endif
rf_path.set_rf_amp(rf_amp);
}
void set_lna_gain(const int_fast8_t db) {
#ifdef PRALINE
cached_lna_gain = db; // Track state for debug and potentially other purposes.
#endif
second_if->set_lna_gain(db);
}
void set_vga_gain(const int_fast8_t db) {
#ifdef PRALINE
cached_vga_gain = db; // Track state for debug and potentially other purposes.
#endif
second_if->set_vga_gain(db);
}
void set_tx_gain(const int_fast8_t db) {
second_if->set_tx_vga_gain(db);
}
void set_baseband_filter_bandwidth_rx(const uint32_t bandwidth_minimum) {
second_if->set_lpf_rf_bandwidth_rx(bandwidth_minimum);
}
void set_baseband_filter_bandwidth_tx(const uint32_t bandwidth_minimum) {
second_if->set_lpf_rf_bandwidth_tx(bandwidth_minimum);
}
void set_baseband_rate(const uint32_t rate) {
portapack::clock_manager.set_sampling_frequency(rate);
// TODO: actually set baseband too?
}
void set_antenna_bias(const bool on) {
#ifdef PRALINE
/* Praline: P2_12 = GPIO1[12], ANT_BIAS_EN_N (active LOW) */
rf_path.set_ant_bias(on);
#else
if (hackrf_r9) {
rf_path.set_ant_bias(on);
} else {
first_if.set_gpo1(on ? 0 : 1);
}
#endif
}
bool get_mixer_invert() {
return mixer_invert;
}
bool get_baseband_invert() {
return baseband_invert;
}
void set_tx_max283x_iq_phase_calibration(const size_t v) {
second_if->set_tx_LO_iq_phase_calibration(v);
}
void set_rx_max283x_iq_phase_calibration(const size_t v) {
second_if->set_rx_LO_iq_phase_calibration(v);
}
void disable() {
if (direction == rf::Direction::Transmit && baseband::is_image_running()) {
static constexpr uint32_t radio_tx_drain_timeout_ms = 100;
shared_memory.radio_tx_drain = 1; // Request drain of the current DMA queue.
for (uint32_t waited_ms = 0;
shared_memory.radio_tx_drain && (waited_ms < radio_tx_drain_timeout_ms);
++waited_ms) {
chThdSleepMilliseconds(1);
}
}
/* Never allow shutdown to block indefinitely waiting for a drain
* acknowledgement that may never arrive in normal operation. */
shared_memory.radio_tx_drain = 0;
set_antenna_bias(false);
baseband_codec.set_mode(max5864::Mode::Shutdown);
#ifdef PRALINE
second_if->set_mode(max283x::Mode::Standby);
#else
second_if->set_mode(max2837::Mode::Standby);
#endif
first_if.disable();
set_rf_amp(false);
led_rx.setInactive();
led_tx.setInactive();
}
#ifdef PRALINE
void invalidate_spi_config() {
ssp1_arbiter.invalidate();
}
#endif
namespace debug {
#ifdef PRALINE
rf::Direction get_cached_direction() {
return cached_direction;
}
bool get_cached_rf_amp() {
return cached_rf_amp;
}
int_fast8_t get_cached_lna_gain() {
return cached_lna_gain;
}
int_fast8_t get_cached_vga_gain() {
return cached_vga_gain;
}
#endif
namespace first_if {
uint32_t register_read(const size_t register_number) {
return radio::first_if.read(register_number);
}
void register_write(const size_t register_number, uint32_t value) {
radio::first_if.write(register_number, value);
}
#ifdef PRALINE
extern "C" {
extern struct rffc507x_debug_t {
uint32_t requested_freq_mhz;
uint32_t calculated_vco_mhz;
uint32_t expected_n;
uint8_t expected_lodiv;
uint8_t expected_presc;
bool was_called;
uint32_t calc_lo_freq_mhz;
uint32_t calc_vco_inside_mhz;
uint8_t calc_lodiv_log2;
uint8_t calc_presc_log2;
uint64_t calc_n_q24;
} rffc507x_debug_info;
}
/*struct TuningInfo {
uint32_t requested_freq_mhz;
uint32_t expected_n;
uint8_t expected_lodiv;
uint8_t expected_presc;
bool was_called;
};*/
TuningInfo get_tuning_info() {
return {
rffc507x_debug_info.requested_freq_mhz,
rffc507x_debug_info.calculated_vco_mhz,
rffc507x_debug_info.expected_n,
rffc507x_debug_info.expected_lodiv,
rffc507x_debug_info.expected_presc,
rffc507x_debug_info.was_called,
rffc507x_debug_info.calc_lo_freq_mhz,
rffc507x_debug_info.calc_vco_inside_mhz,
rffc507x_debug_info.calc_lodiv_log2,
rffc507x_debug_info.calc_presc_log2,
rffc507x_debug_info.calc_n_q24,
};
}
#endif
} /* namespace first_if */
namespace second_if {
uint32_t register_read(const size_t register_number) {
return radio::second_if->read(register_number);
}
void register_write(const size_t register_number, uint32_t value) {
radio::second_if->write(register_number, value);
}
int8_t temp_sense() {
return radio::second_if->temp_sense();
}
} /* namespace second_if */
namespace rf_path_info {
rf::path::Band get_current_band() {
return radio::rf_path.get_band();
}
} /* namespace rf_path_info */
#ifdef PRALINE
namespace fpga {
/* Use fpga_bridge.c functions for FPGA register access.
* These properly switch SPI mode between iCE40 (Mode 3, 8-bit)
* and MAX2831 (Mode 0, 9-bit). After each access, we must
* invalidate the SPI arbiter's cached config since fpga_bridge.c
* modifies SSP1 registers directly. */
uint32_t register_read(const size_t register_number) {
uint32_t result = fpga_debug_register_read(static_cast<uint8_t>(register_number));
ssp1_arbiter.invalidate(); // Force arbiter to reconfigure on next transfer
return result;
}
void register_write(const size_t register_number, uint32_t value) {
fpga_debug_register_write(static_cast<uint8_t>(register_number), static_cast<uint8_t>(value));
ssp1_arbiter.invalidate(); // Force arbiter to reconfigure on next transfer
}
void init() {
fpga_set_mode(FPGA_MODE_RX);
// These FPGA registers control DC_BLOCK, Q-Inv, QUARTER SHIFT, and Decimation.
fpga_debug_register_write(FPGA_REG_CTRL, FPGA_CTRL_DC_BLOCK_EN); // DC_BLOCK=1, QUARTER_SHIFT=0, Q_INVERT=0
fpga_debug_register_write(FPGA_REG_DECIM, 0x00); // RX_DECIM=No Decim
// RX Mode: Register 3 is RX Digital Gain. Start with 0dB (no shift).
fpga_debug_register_write(FPGA_REG_RX_DIGITAL_GAIN, FPGA_RX_DEFAULT_DIGITAL_GAIN);
/* RX Mode: Initialize DC Block parameters to standard Praline values.
* 0x04 Width and 0x08 Adapt Rate are typical for 40MHz stability.
*/
fpga_debug_register_write(FPGA_REG_RX_DC_BLOCK_WIDTH, FPGA_RX_DEFAULT_DC_WIDTH);
fpga_debug_register_write(FPGA_REG_RX_DC_ADAPT_RATE, FPGA_RX_DEFAULT_ADAPT_RATE);
ssp1_arbiter.invalidate(); // Force arbiter to reconfigure on next transfer
}
} /* namespace fpga */
#endif
namespace sgpio {
/* SGPIO register map for debug viewing
* We expose key registers for diagnosing data flow issues.
* Register numbers map to:
* 0: CTRL_ENABLE - Which slices are enabled
* 1: GPIO_INREG - GPIO input register (data pins state)
* 2: GPIO_OUTREG - GPIO output register (direction, disable, etc)
* 3: GPIO_OENREG - GPIO output enable register
* 4: STATUS_1 - Exchange interrupt status (slice A = bit 0)
* 5: REG_SS[0] - Shadow register slice A (current sample data)
*/
uint32_t register_read(const size_t register_number) {
switch (register_number) {
case 0:
return LPC_SGPIO->CTRL_ENABLE;
case 1:
return LPC_SGPIO->GPIO_INREG;
case 2:
return LPC_SGPIO->GPIO_OUTREG;
case 3:
return LPC_SGPIO->GPIO_OENREG;
case 4:
return LPC_SGPIO->STATUS_1;
case 5:
return LPC_SGPIO->REG_SS[0];
default:
return 0xFFFFFFFF;
}
}
} /* namespace sgpio */
} /* namespace debug */
} /* namespace radio */