LibreVNA/Software/VNA_embedded/Application/Drivers/max2871.cpp

428 lines
9.9 KiB
C++

#include "max2871.hpp"
#include <string.h>
#include <algorithm.hpp>
#include "delay.hpp"
#include <cmath>
#define LOG_LEVEL LOG_LEVEL_ERR
#define LOG_MODULE "MAX2871"
#include "Log.h"
bool MAX2871::Init() {
return Init(10000000, true, 1, false);
}
bool MAX2871::Init(uint32_t f_ref, bool doubler, uint16_t r, bool div2) {
for (uint8_t i = 0; i < 6; i++) {
regs[i] = 0;
}
ChipEnable(false);
RFEnable(false);
SetReference(f_ref, doubler, r, div2);
// non-inverting loop filter
regs[2] |= (1UL << 6);
// select digital lock detect
regs[5] |= (0x1UL << 22);
// fundamental VCO feedback
regs[4] |= (1UL << 23);
// reserved, set to 0x03
regs[4] |= (0x3UL << 29);
// enable double buffering for register 4
regs[2] |= (1UL << 13);
// automatically switch to integer mode if F = 0
regs[5] |= (1UL << 24);
// recommended phase setting
regs[1] |= (1UL << 15);
SetMode(Mode::LowNoise);
// for all other CP modes the PLL reports unlock condition (output signal appears to be locked)
SetCPMode(CPMode::CP20);
SetCPCurrent(15);
SetFrequency(1000000000);
// initial register write according to datasheet timing
ChipEnable(true);
Write(5, regs[5]);
Delay::ms(20);
Write(4, regs[4]);
Write(3, regs[3]);
Write(2, regs[2]);
Write(1, regs[1]);
Write(0, regs[0]);
Write(5, regs[5]);
Delay::ms(20);
Write(4, regs[4]);
Write(3, regs[3]);
Write(2, regs[2]);
Write(1, regs[1]);
Write(0, regs[0]);
return true;
}
void MAX2871::ChipEnable(bool on) {
if(!CE) {
return;
}
if (on) {
CE->BSRR = CEpin;
} else {
CE->BSRR = CEpin << 16;
}
}
void MAX2871::RFEnable(bool on) {
if(!RF_EN) {
return;
}
if (on) {
RF_EN->BSRR = RF_ENpin;
Read();
} else {
RF_EN->BSRR = RF_ENpin << 16;
}
}
bool MAX2871::Locked() {
return LD->IDR & LDpin;
}
void MAX2871::SetPowerOutA(Power p, bool enabled) {
// only set power of port A
regs[4] &= ~0x38;
regs[4] |= ((uint16_t) p << 3);
if (enabled) {
regs[4] |= 0x20;
}
}
void MAX2871::SetPowerOutB(Power p, bool enabled) {
// only set power of port B
regs[4] &= ~0x1C0;
regs[4] |= ((uint16_t) p << 6);
if (enabled) {
regs[4] |= 0x100;
}
}
void MAX2871::SetMode(Mode m) {
regs[2] &= ~0x60000000;
regs[2] |= ((uint32_t) m << 29);
}
void MAX2871::SetCPMode(CPMode m) {
regs[1] &= ~0x60000000;
regs[1] |= ((uint32_t) m << 29);
}
void MAX2871::SetCPCurrent(uint8_t mA) {
if(mA > 15) {
LOG_WARN("Clipping charge pump current to 15mA");
mA = 15;
}
regs[2] &= ~0x1E00;
regs[2] |= ((uint16_t) mA << 9);
}
bool MAX2871::SetFrequency(uint64_t f) {
if (f < 23500000 || f > MaxFreq) {
LOG_ERR("Frequency must be between 23.5MHz and 6GHz");
return false;
}
LOG_DEBUG("Setting frequency to %lu%06luHz...", (uint32_t ) (f / 1000000),
(uint32_t ) (f % 1000000));
// select divider
uint64_t f_vco = f;
uint8_t div = 0;
if (f < 46875000) {
div = 0x07;
f_vco *= 128;
} else if (f < 93750000) {
div = 0x06;
f_vco *= 64;
} else if (f < 187500000) {
div = 0x05;
f_vco *= 32;
} else if (f < 375000000) {
div = 0x04;
f_vco *= 16;
} else if (f < 750000000) {
div = 0x03;
f_vco *= 8;
} else if (f < 1500000000) {
div = 0x02;
f_vco *= 4;
} else if (f < 3000000000) {
div = 0x01;
f_vco *= 2;
}
LOG_DEBUG("F_VCO: %lu%06luHz",
(uint32_t ) (f_vco / 1000000), (uint32_t ) (f_vco % 1000000));
if (gotVCOMap) {
// manual VCO selection for lock time improvement
uint16_t compare = f_vco / 100000;
uint8_t vco = 0;
for (; vco < 64; vco++) {
if (VCOmax[vco] >= compare) {
break;
}
} LOG_DEBUG("Manually selected VCO %d", vco);
regs[3] &= ~0xFC000000;
regs[3] |= (uint32_t) vco << 26;
}
uint16_t N = f_vco / f_PFD;
if (N < 19 || N > 4091) {
LOG_ERR("Invalid N value, should be between 19 and 4091, got %lu", N);
return false;
}
uint32_t rem_f = f_vco - N * f_PFD;
LOG_DEBUG("Remaining fractional frequency: %lu", rem_f);
LOG_DEBUG("Looking for best fractional match");
float fraction = (float) rem_f / f_PFD;
auto approx = Algorithm::BestRationalApproximation(fraction, 4095);
if (approx.denom == approx.num) {
// got an impossible result due to floating point limitations(?)
// Set fractional part to zero, increase integer part instead
approx.num = 0;
approx.denom = 2;
N++;
}
if(approx.denom == 1) {
// M value must be at least 2
approx.denom = 2;
}
int32_t rem_approx = ((uint64_t) f_PFD * approx.num) / approx.denom;
if(rem_approx != rem_f) {
LOG_WARN("Best match is F=%u/M=%u, deviation of %luHz",
approx.num, approx.denom, abs(rem_f - rem_approx));
}
uint64_t f_set = (uint64_t) N * f_PFD + rem_approx;
f_set /= (1UL << div);
// write values to registers
regs[4] &= ~0x00700000;
regs[4] |= ((uint32_t) div << 20);
regs[0] &= ~0x7FFFFFF8;
regs[0] |= ((uint32_t) N << 15) | ((uint32_t) approx.num << 3);
regs[1] &= ~0x00007FF8;
regs[1] |= ((uint32_t) approx.denom << 3);
LOG_DEBUG("Set frequency to %lu%06luHz...",
(uint32_t ) (f_set / 1000000), (uint32_t ) (f_set % 1000000));
outputFrequency = f_set;
return true;
}
bool MAX2871::SetReference(uint32_t f_ref, bool doubler, uint16_t r,
bool div2) {
if (f_ref < 10000000) {
LOG_ERR("Reference frequency must be >=10MHz, is %lu", f_ref);
return false;
} else if (f_ref > 105000000 && doubler) {
LOG_ERR(
"Reference frequency must be <=105MHz when used with doubler, is %lu",
f_ref);
return false;
} else if (f_ref > 210000000) {
LOG_ERR("Reference frequency must be <=210MHz, is %lu", f_ref);
return false;
}
if (r < 1 || r > 1023) {
LOG_ERR("Reference divider must be between 1 and 1023, is %d", r);
return false;
}
// calculate PFD frequency
uint32_t pfd = f_ref;
if (doubler) {
pfd *= 2;
}
pfd /= r;
if (div2) {
pfd /= 2;
}
if (pfd > 125000000) {
LOG_ERR("PFD frequency must be <=125MHz, is %d",
pfd);
return false;
}
if(pfd > 32000000) {
regs[2] |= (1UL << 31);
} else {
regs[2] &= ~(1UL << 31);
}
// input values are valid, adjust registers
regs[2] &= ~0x03FFC000;
if (doubler) {
regs[2] |= (1UL << 25);
}
if (div2) {
regs[2] |= (1UL << 24);
}
regs[2] |= (r << 14);
f_PFD = pfd;
LOG_INFO("Set PFD frequency to %lu", f_PFD);
// updating VAS state machine clock
uint16_t BS = f_PFD / 50000;
if (BS > 1023) {
BS = 1023;
} else if (BS < 1) {
BS = 1;
}
LOG_DEBUG("BS set to %lu", BS);
regs[4] &= ~0x030FF000;
regs[4] |= ((BS & 0xFF) << 12);
regs[4] |= (((BS >> 8) & 0x03) << 24);
// update ADC clock
uint16_t cdiv = f_PFD/100000;
LOG_DEBUG("CDIV set to %u", cdiv);
regs[3] &= ~0x00007FF8;
regs[3] |= (cdiv & 0xFFF) << 3;
return true;
}
void MAX2871::Update() {
Write(5, regs[5]);
Write(4, regs[4]);
Write(3, regs[3]);
Write(2, regs[2]);
Write(1, regs[1]);
Write(0, regs[0]);
}
void MAX2871::UpdateFrequency() {
Write(4, regs[4]);
Write(3, regs[3]);
Write(1, regs[1]);
Write(0, regs[0]);
}
void MAX2871::Write(uint8_t reg, uint32_t val) {
uint8_t data[4];
// split value into two 16 bit words
data[0] = (val >> 24) & 0xFF;
data[1] = (val >> 16) & 0xFF;
data[2] = (val >> 8) & 0xFF;
data[3] = (val & 0xF8) | reg;
Delay::us(1);
HAL_SPI_Transmit(hspi, (uint8_t*) data, 4, 20);
LE->BSRR = LEpin;
Delay::us(1);
LE->BSRR = LEpin << 16;
}
// Assumes that the MUX pin is already configured as "Read register 6" and connected to MISO
uint32_t MAX2871::Read() {
uint8_t transmit[4] = {0x00, 0x00, 0x00, 0x06};
HAL_SPI_Transmit(hspi, (uint8_t*) transmit, 4, 20);
LE->BSRR = LEpin;
memset(transmit, 0, sizeof(transmit));
uint8_t recv[4];
HAL_SPI_TransmitReceive(hspi, (uint8_t*) transmit, (uint8_t*) recv, 4, 20);
LE->BSRR = LEpin << 16;
// assemble readback result
uint32_t result = ((uint32_t) recv[0] << 24) | ((uint32_t) recv[1] << 16
) | ((uint32_t) recv[2] << 8) | (recv[3] & 0xFF);
result <<= 2;
LOG_DEBUG("Readback: 0x%08x", result);
return result;
}
bool MAX2871::BuildVCOMap() {
memset(VCOmax, 0, sizeof(VCOmax));
// save output frequency
uint64_t oldFreq = outputFrequency;
constexpr uint32_t step = 10000000;
for (uint64_t freq = 3000000000; freq <= MaxFreq; freq += step) {
SetFrequency(freq);
UpdateFrequency();
uint32_t start = HAL_GetTick();
// set MUX to LD
regs[2] &= ~(7UL << 26);
regs[5] &= ~(1UL << 18);
regs[2] |= (6UL << 26);
Write(5, regs[5]);
Write(2, regs[2]);
while (!(MUX->IDR & MUXpin)) {
if (HAL_GetTick() - start > 100) {
LOG_ERR(
"Failed to lock during VCO map build process, aborting (f=%lu%06luHz)",
(uint32_t )(freq / 1000000),
(uint32_t ) (freq % 1000000));
gotVCOMap = false;
// revert back to previous frequency
SetFrequency(oldFreq);
LE->BSRR = LEpin << 16;
// Mux pin back to high impedance
regs[2] &= ~(7UL << 26);
regs[5] &= ~(1UL << 18);
Update();
return false;
}
}
// set MUX to SPI read
regs[2] &= ~(7UL << 26);
regs[5] &= ~(1UL << 18);
regs[2] |= (4UL << 26);
regs[5] |= (1UL << 18);
Write(5, regs[5]);
Write(2, regs[2]);
auto readback = Read();
uint8_t vco = (readback & 0x01F8) >> 3;
VCOmax[vco] = freq / 100000;
LOG_INFO("VCO map: %lu%06luHz uses VCO %d",
(uint32_t ) (freq / 1000000), (uint32_t ) (freq % 1000000), vco);
}
gotVCOMap = true;
// revert back to previous frequency
SetFrequency(oldFreq);
// Mux pin back to high impedance
regs[2] &= ~(7UL << 26);
regs[5] &= ~(1UL << 18);
// Turn off VAS, select VCO manually from now on
regs[3] |= (1UL << 25);
Update();
return true;
}
uint8_t MAX2871::GetTemp() {
// select temperature channel and start ADC
regs[5] &= ~0x00000078;
regs[5] |= 0x00000048;
Write(5, regs[5]);
Delay::us(100);
// set MUX to SPI read
regs[2] &= ~(7UL << 26);
regs[5] &= ~(1UL << 18);
regs[2] |= (4UL << 26);
regs[5] |= (1UL << 18);
Write(5, regs[5]);
Write(2, regs[2]);
uint8_t ADC_raw = (Read() >> 16) & 0x7F;
LOG_DEBUG("Raw temp ADC: %d", ADC_raw);
// Disable ADC
regs[5] &= ~0x00000078;
// Mux pin back to high impedance
regs[2] &= ~(7UL << 26);
regs[5] &= ~(1UL << 18);
Write(5, regs[5]);
Write(2, regs[2]);
// convert to celsius and return
return 95 - 1.14f * ADC_raw;
}