mirror of
https://github.com/eried/portapack-mayhem.git
synced 2024-10-01 01:26:06 -04:00
297 lines
8.1 KiB
C++
297 lines
8.1 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 "rffc507x.hpp"
|
||
|
|
||
|
#include <array>
|
||
|
|
||
|
#include "utility.hpp"
|
||
|
|
||
|
#include "hackrf_hal.hpp"
|
||
|
#include "hackrf_gpio.hpp"
|
||
|
using namespace hackrf::one;
|
||
|
|
||
|
#include "hal.h"
|
||
|
|
||
|
namespace rffc507x {
|
||
|
|
||
|
/* Empirical tests indicate no minimum reset pulse width, but the speed
|
||
|
* of the processor and GPIO probably produce at least 20ns pulse width.
|
||
|
*/
|
||
|
constexpr float seconds_during_reset = 1.0e-6;
|
||
|
constexpr halrtcnt_t ticks_during_reset = (base_m4_clk_f * seconds_during_reset + 1);
|
||
|
|
||
|
/* Empirical testing indicates >3.5us delay required after reset, before
|
||
|
* registers can be reliably written. Make it 5us, just for fun. Tests were
|
||
|
* conducted at high temperatures (with a hair dryer) increased room
|
||
|
* temperature minimum delay of 2.9us to the requirement above.
|
||
|
*/
|
||
|
constexpr float seconds_after_reset = 5.0e-6;
|
||
|
constexpr halrtcnt_t ticks_after_reset = (base_m4_clk_f * seconds_after_reset + 1);
|
||
|
|
||
|
constexpr auto reference_frequency = rffc5072_reference_f;
|
||
|
|
||
|
namespace vco {
|
||
|
|
||
|
constexpr rf::FrequencyRange range {
|
||
|
.min = 2700000000U,
|
||
|
.max = 5400000000U,
|
||
|
};
|
||
|
|
||
|
} /* namespace vco */
|
||
|
|
||
|
namespace lo {
|
||
|
|
||
|
constexpr size_t divider_log2_min = 0;
|
||
|
constexpr size_t divider_log2_max = 5;
|
||
|
|
||
|
constexpr size_t divider_min = 1U << divider_log2_min;
|
||
|
constexpr size_t divider_max = 1U << divider_log2_max;
|
||
|
|
||
|
constexpr rf::FrequencyRange range {
|
||
|
.min = vco::range.min / divider_max,
|
||
|
.max = vco::range.max / divider_min,
|
||
|
};
|
||
|
|
||
|
size_t divider_log2(const rf::Frequency lo_frequency) {
|
||
|
/* TODO: Error */
|
||
|
/*
|
||
|
if( lo::range.out_of_range(lo_frequency) ) {
|
||
|
return;
|
||
|
}
|
||
|
*/
|
||
|
/* Compute LO divider. */
|
||
|
auto lo_divider_log2 = lo::divider_log2_min;
|
||
|
auto vco_frequency = lo_frequency;
|
||
|
while( vco::range.below_range(vco_frequency) ) {
|
||
|
vco_frequency <<= 1;
|
||
|
lo_divider_log2 += 1;
|
||
|
}
|
||
|
|
||
|
return lo_divider_log2;
|
||
|
}
|
||
|
|
||
|
} /* namespace lo */
|
||
|
|
||
|
namespace prescaler {
|
||
|
|
||
|
constexpr rf::Frequency max_frequency = 1600000000U;
|
||
|
|
||
|
constexpr size_t divider_log2_min = 1;
|
||
|
constexpr size_t divider_log2_max = 2;
|
||
|
|
||
|
constexpr size_t divider_min = 1U << divider_log2_min;
|
||
|
constexpr size_t divider_max = 1U << divider_log2_max;
|
||
|
|
||
|
constexpr size_t divider_log2(const rf::Frequency vco_frequency) {
|
||
|
return (vco_frequency > (prescaler::divider_min * prescaler::max_frequency))
|
||
|
? prescaler::divider_log2_max
|
||
|
: prescaler::divider_log2_min
|
||
|
;
|
||
|
}
|
||
|
|
||
|
} /* namespace prescaler */
|
||
|
|
||
|
struct SynthConfig {
|
||
|
const size_t lo_divider_log2;
|
||
|
const size_t prescaler_divider_log2;
|
||
|
const uint64_t n_divider_q24;
|
||
|
|
||
|
static SynthConfig calculate(
|
||
|
const rf::Frequency lo_frequency
|
||
|
) {
|
||
|
/* RFFC507x frequency synthesizer is is accurate to about 2ppb (two parts
|
||
|
* per BILLION). There's not much point to worrying about rounding and
|
||
|
* tuning error, when it amounts to 8Hz at 5GHz!
|
||
|
*/
|
||
|
const size_t lo_divider_log2 = lo::divider_log2(lo_frequency);
|
||
|
const size_t lo_divider = 1U << lo_divider_log2;
|
||
|
|
||
|
const rf::Frequency vco_frequency = lo_frequency * lo_divider;
|
||
|
|
||
|
const size_t prescaler_divider_log2 = prescaler::divider_log2(vco_frequency);
|
||
|
|
||
|
const uint64_t prescaled_lo_q24 = vco_frequency << (24 - prescaler_divider_log2);
|
||
|
const uint64_t n_divider_q24 = prescaled_lo_q24 / reference_frequency;
|
||
|
|
||
|
return {
|
||
|
lo_divider_log2,
|
||
|
prescaler_divider_log2,
|
||
|
n_divider_q24,
|
||
|
};
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/* Readback values, RFFC5072 rev A:
|
||
|
* 0000: 0x8a01 => dev_id=1000101000000 mrev_id=001
|
||
|
* 0001: 0x3f7c => lock=0 ct_cal=0111111 cp_cal=011111 ctfail=0 0
|
||
|
* 0010: 0x806f => v0_cal=10000000 v1_cal=01101111
|
||
|
* 0011: 0x0000 => rsm_state=00000 f_errflag=00
|
||
|
* 0100: 0x0000 => vco_count_l=0
|
||
|
* 0101: 0x0000 => vco_count_h=0
|
||
|
* 0110: 0xc000 => cal_fbi=1 cal_fbq=1
|
||
|
* 0111: 0x0000 => vco_sel=0 vco_tc_curve=0
|
||
|
*/
|
||
|
|
||
|
void RFFC507x::init() {
|
||
|
gpio_rffc5072_resetx.set();
|
||
|
gpio_rffc5072_resetx.output();
|
||
|
reset();
|
||
|
|
||
|
_bus.init();
|
||
|
|
||
|
_dirty.set();
|
||
|
flush();
|
||
|
}
|
||
|
|
||
|
void RFFC507x::reset() {
|
||
|
/* TODO: Is RESETB pin ignored if sdi_ctrl.sipin=1? Programming guide
|
||
|
* description of sdi_ctrl.sipin suggests the pin is not ignored.
|
||
|
*/
|
||
|
gpio_rffc5072_resetx.clear();
|
||
|
halPolledDelay(ticks_during_reset);
|
||
|
gpio_rffc5072_resetx.set();
|
||
|
halPolledDelay(ticks_after_reset);
|
||
|
}
|
||
|
|
||
|
void RFFC507x::flush() {
|
||
|
if( _dirty ) {
|
||
|
for(size_t i=0; i<_map.w.size(); i++) {
|
||
|
if( _dirty[i] ) {
|
||
|
write(i, _map.w[i]);
|
||
|
}
|
||
|
}
|
||
|
_dirty.clear();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
inline void RFFC507x::write(const address_t reg_num, const spi::reg_t value) {
|
||
|
_bus.write(reg_num, value);
|
||
|
}
|
||
|
|
||
|
inline spi::reg_t RFFC507x::read(const address_t reg_num) {
|
||
|
return _bus.read(reg_num);
|
||
|
}
|
||
|
|
||
|
inline void RFFC507x::write(const Register reg, const spi::reg_t value) {
|
||
|
write(toUType(reg), value);
|
||
|
}
|
||
|
|
||
|
inline spi::reg_t RFFC507x::read(const Register reg) {
|
||
|
return read(toUType(reg));
|
||
|
}
|
||
|
|
||
|
inline void RFFC507x::flush_one(const Register reg) {
|
||
|
const auto reg_num = toUType(reg);
|
||
|
write(reg_num, _map.w[reg_num]);
|
||
|
_dirty.clear(reg_num);
|
||
|
}
|
||
|
|
||
|
void RFFC507x::enable() {
|
||
|
_map.r.sdi_ctrl.enbl = 1;
|
||
|
flush_one(Register::SDI_CTRL);
|
||
|
|
||
|
/* TODO: Reset PLLCPL after CT_CAL? */
|
||
|
|
||
|
/* TODO: After device is enabled and CT_cal is complete and VCO > 3.2GHz,
|
||
|
* change prescaler divider to 2, update synthesizer ratio, change
|
||
|
* lf.pllcpl from 3 to 2.
|
||
|
*/
|
||
|
}
|
||
|
|
||
|
void RFFC507x::disable() {
|
||
|
_map.r.sdi_ctrl.enbl = 0;
|
||
|
flush_one(Register::SDI_CTRL);
|
||
|
}
|
||
|
|
||
|
void RFFC507x::set_mixer_current(const uint8_t value) {
|
||
|
/* MIX IDD = 0b000 appears to turn the mixer completely off */
|
||
|
/* TODO: Adjust mixer current. Graphs in datasheet suggest:
|
||
|
* MIX_IDD=1 has lowest noise figure (10.1dB vs 13dB @ MIX_IDD=7).
|
||
|
* MIX_IDD=5 has highest IP3 (24dBm vs 10.3dBm @ MIX_IDD=1).
|
||
|
* MIX_IDD=5 has highest P1dB (11.8dBm vs 1.5dBm @ MIX_IDD=1).
|
||
|
* Mixer input impedance ~85 Ohms at MIX_IDD=4.
|
||
|
* Mixer input impedance inversely proportional to MIX_IDD.
|
||
|
* Balun balanced (mixer) side is 100 Ohms. Perhaps reduce MIX_IDD
|
||
|
* a bit to get 100 Ohms from mixer.
|
||
|
*/
|
||
|
_map.r.mix_cont.p1mixidd = value;
|
||
|
_map.r.mix_cont.p2mixidd = value;
|
||
|
flush_one(Register::MIX_CONT);
|
||
|
}
|
||
|
|
||
|
void RFFC507x::set_frequency(const rf::Frequency lo_frequency) {
|
||
|
const SynthConfig synth_config = SynthConfig::calculate(lo_frequency);
|
||
|
|
||
|
/* Boost charge pump leakage if VCO frequency > 3.2GHz, indicated by
|
||
|
* prescaler divider set to 4 (log2=2) instead of 2 (log2=1).
|
||
|
*/
|
||
|
if( synth_config.prescaler_divider_log2 == 2 ) {
|
||
|
_map.r.lf.pllcpl = 3;
|
||
|
} else {
|
||
|
_map.r.lf.pllcpl = 2;
|
||
|
}
|
||
|
flush_one(Register::LF);
|
||
|
|
||
|
_map.r.p2_freq1.p2n = synth_config.n_divider_q24 >> 24;
|
||
|
_map.r.p2_freq1.p2lodiv = synth_config.lo_divider_log2;
|
||
|
_map.r.p2_freq1.p2presc = synth_config.prescaler_divider_log2;
|
||
|
_map.r.p2_freq2.p2nmsb = (synth_config.n_divider_q24 >> 8) & 0xffff;
|
||
|
_map.r.p2_freq3.p2nlsb = synth_config.n_divider_q24 & 0xff;
|
||
|
_dirty[Register::P2_FREQ1] = 1;
|
||
|
_dirty[Register::P2_FREQ2] = 1;
|
||
|
_dirty[Register::P2_FREQ3] = 1;
|
||
|
flush();
|
||
|
}
|
||
|
|
||
|
spi::reg_t RFFC507x::readback(const Readback readback) {
|
||
|
/* TODO: This clobbers the rest of the DEV_CTRL register
|
||
|
* Time to implement bitfields for registers.
|
||
|
*/
|
||
|
_map.r.dev_ctrl.readsel = toUType(readback);
|
||
|
flush_one(Register::DEV_CTRL);
|
||
|
|
||
|
return read(Register::READBACK);
|
||
|
}
|
||
|
|
||
|
RegisterMap RFFC507x::registers() {
|
||
|
return _map;
|
||
|
}
|
||
|
|
||
|
#if 0
|
||
|
/* Test of RFFC507x reset over temperature */
|
||
|
while(true) {
|
||
|
first_if.write(rffc507x::Register::P1_FREQ2, 0xAAAA);
|
||
|
first_if.reset();
|
||
|
const auto after_reset = first_if.read(rffc507x::Register::P1_FREQ2);
|
||
|
if( after_reset != 0x6276 ) {
|
||
|
led_usb.off();
|
||
|
led_tx.on();
|
||
|
chThdSleepMilliseconds(100);
|
||
|
} else {
|
||
|
led_usb.on();
|
||
|
led_tx.off();
|
||
|
}
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
} /* namespace rffc507x */
|