LibreVNA/Software/VNA_embedded/Application/Hardware.cpp
Andre Dunford 5bb6fdf686 Revert "evaluate AUX3 in standby state"
This reverts commit ec5e0e208c.
2022-12-20 09:11:13 -08:00

477 lines
13 KiB
C++

#include <HW_HAL.hpp>
#include "Hardware.hpp"
#include "Si5351C.hpp"
#include "max2871.hpp"
#include "FPGA/FPGA.hpp"
#include "Exti.hpp"
#include "VNA.hpp"
#include "Manual.hpp"
#include "delay.hpp"
#include "SpectrumAnalyzer.hpp"
#include "Communication.h"
#include "Trigger.hpp"
#include <cstring>
#define LOG_LEVEL LOG_LEVEL_INFO
#define LOG_MODULE "HW"
#include "Log.h"
static uint32_t extOutFreq = 0;
static bool extRefInUse = false;
HW::Mode activeMode;
static bool unlevel = false;
static bool StatusUpdateFlag = true;
static Protocol::ReferenceSettings ref;
static volatile uint64_t lastISR;
static uint32_t IF1 = HW::DefaultIF1;
static uint32_t IF2 = HW::DefaultIF2;
static uint32_t ADCsamplerate = HW::DefaultADCSamplerate;
static uint8_t ADCprescaler = HW::DefaultADCprescaler;
static uint16_t DFTphaseInc = HW::DefaultDFTphaseInc;
using namespace HWHAL;
static void HaltedCallback() {
switch(activeMode) {
case HW::Mode::VNA:
VNA::SweepHalted();
break;
default:
break;
}
}
static void ReadComplete(const FPGA::SamplingResult &result) {
bool needs_work = false;
switch(activeMode) {
case HW::Mode::VNA:
needs_work = VNA::MeasurementDone(result);
break;
case HW::Mode::Manual:
needs_work = Manual::MeasurementDone(result);
break;
case HW::Mode::SA:
needs_work = SA::MeasurementDone(result);
break;
default:
break;
}
if(needs_work) {
STM::DispatchToInterrupt(HW::Work);
}
}
static void FPGA_Interrupt(void*) {
FPGA::InitiateSampleRead(ReadComplete);
lastISR = Delay::get_us();
}
void HW::Work() {
switch(activeMode) {
case HW::Mode::VNA:
VNA::Work();
break;
case HW::Mode::Manual:
Manual::Work();
break;
case HW::Mode::SA:
SA::Work();
break;
default:
break;
}
}
bool HW::Init() {
#if USE_DEBUG_PINS
// initialize debug pins
GPIO_InitTypeDef gpio;
gpio.Pin = DEBUG1_PIN;
gpio.Mode = GPIO_MODE_OUTPUT_PP;
gpio.Speed = GPIO_SPEED_HIGH;
HAL_GPIO_Init(DEBUG1_GPIO, &gpio);
gpio.Pin = DEBUG2_PIN;
HAL_GPIO_Init(DEBUG2_GPIO, &gpio);
#endif
LOG_DEBUG("Initializing...");
activeMode = Mode::Idle;
Si5351.Init();
// Use Si5351 to generate reference frequencies for other PLLs and ADC
Si5351.SetPLL(Si5351C::PLL::A, HW::SI5351CPLLConstantFrequency, Si5351C::PLLSource::XTAL);
while(!Si5351.Locked(Si5351C::PLL::A));
Si5351.SetPLL(Si5351C::PLL::B, HW::SI5351CPLLAlignedFrequency, Si5351C::PLLSource::XTAL);
while(!Si5351.Locked(Si5351C::PLL::B));
extRefInUse = 0;
extOutFreq = 0;
Si5351.Disable(SiChannel::ReferenceOut);
// Both MAX2871 get a 100MHz reference
// Si5351.SetBypass(SiChannel::Source, Si5351C::PLLSource::XTAL);
Si5351.SetCLK(SiChannel::Source, HW::PLLRef, Si5351C::PLL::B, Si5351C::DriveStrength::mA2);
Si5351.Enable(SiChannel::Source);
// Si5351.SetBypass(SiChannel::LO1, Si5351C::PLLSource::XTAL);
Si5351.SetCLK(SiChannel::LO1, HW::PLLRef, Si5351C::PLL::B, Si5351C::DriveStrength::mA2);
Si5351.Enable(SiChannel::LO1);
// 16MHz FPGA clock
Si5351.SetCLK(SiChannel::FPGA, HW::FPGAClkInFrequency, Si5351C::PLL::A, Si5351C::DriveStrength::mA2);
Si5351.Enable(SiChannel::FPGA);
// Generate second LO with Si5351
Si5351.SetCLK(SiChannel::Port1LO2, IF1 - IF2, Si5351C::PLL::B, Si5351C::DriveStrength::mA2);
Si5351.Enable(SiChannel::Port1LO2);
Si5351.SetCLK(SiChannel::Port2LO2, IF1 - IF2, Si5351C::PLL::B, Si5351C::DriveStrength::mA2);
Si5351.Enable(SiChannel::Port2LO2);
Si5351.SetCLK(SiChannel::RefLO2, IF1 - IF2, Si5351C::PLL::B, Si5351C::DriveStrength::mA2);
Si5351.Enable(SiChannel::RefLO2);
// PLL reset appears to realign phases of clock signals
Si5351.ResetPLL(Si5351C::PLL::B);
if(Si5351.WaitForLock(Si5351C::PLL::B, 10)) {
LOG_DEBUG("Si5351 locked");
}
// FPGA clock is now present, can initialize
if (!FPGA::Init(HaltedCallback)) {
LOG_ERR("Aborting due to uninitialized FPGA");
return false;
}
FPGA::DisableHardwareOverwrite();
// Set default ADC samplerate
FPGA::WriteRegister(FPGA::Reg::ADCPrescaler, ADCprescaler);
// Set phase increment according to
FPGA::WriteRegister(FPGA::Reg::PhaseIncrement, DFTphaseInc);
Exti::SetCallback(FPGA_INTR_GPIO_Port, FPGA_INTR_Pin, Exti::EdgeType::Rising, Exti::Pull::Down, FPGA_Interrupt);
// Initialize PLLs and build VCO maps
// enable source synthesizer
FPGA::Enable(FPGA::Periphery::SourceChip);
FPGA::SetMode(FPGA::Mode::SourcePLL);
Source.Init(HW::PLLRef, false, 1, false);
Source.SetPowerOutA(MAX2871::Power::n4dbm);
// output B is not used
Source.SetPowerOutB(MAX2871::Power::n4dbm, false);
if(!Source.BuildVCOMap()) {
LOG_WARN("Source VCO map failed");
} else {
LOG_INFO("Source VCO map complete");
}
Source.SetFrequency(1000000000);
Source.UpdateFrequency();
LOG_DEBUG("Source temp: %u", Source.GetTemp());
// disable source synthesizer/enable LO synthesizer
FPGA::SetMode(FPGA::Mode::FPGA);
FPGA::Disable(FPGA::Periphery::SourceChip);
FPGA::Enable(FPGA::Periphery::LO1Chip);
FPGA::SetMode(FPGA::Mode::LOPLL);
LO1.Init(HW::PLLRef, false, 1, false);
LO1.SetPowerOutA(MAX2871::Power::n4dbm);
LO1.SetPowerOutB(MAX2871::Power::n4dbm);
if(!LO1.BuildVCOMap()) {
LOG_WARN("LO1 VCO map failed");
} else {
LOG_INFO("LO1 VCO map complete");
}
LO1.SetFrequency(1000000000 + IF1);
LO1.UpdateFrequency();
LOG_DEBUG("LO temp: %u", LO1.GetTemp());
FPGA::SetMode(FPGA::Mode::FPGA);
// disable both synthesizers
FPGA::Disable(FPGA::Periphery::LO1Chip);
FPGA::WriteMAX2871Default(Source.GetRegisters());
LOG_INFO("Initialized");
FPGA::Enable(FPGA::Periphery::ReadyLED);
Ref::update();
return true;
}
void HW::SetMode(Mode mode) {
if(activeMode == mode) {
// already the correct mode
return;
}
unlevel = false;
switch(activeMode) {
case Mode::Manual:
Manual::Stop();
break;
case Mode::VNA:
VNA::Stop();
break;
default:
break;
}
if(mode != Mode::Idle) {
// do a full initialization when switching directly between modes
HW::Init();
lastISR = Delay::get_us();
}
SetIdle();
activeMode = mode;
}
bool HW::GetTemps(uint8_t *source, uint8_t *lo) {
FPGA::SetMode(FPGA::Mode::SourcePLL);
*source = Source.GetTemp();
FPGA::SetMode(FPGA::Mode::LOPLL);
*lo = LO1.GetTemp();
FPGA::SetMode(FPGA::Mode::FPGA);
return true;
}
void HW::SetIdle() {
unlevel = false;
Trigger::SetInput(false);
FPGA::AbortSweep();
FPGA::SetMode(FPGA::Mode::FPGA);
FPGA::DisableAllInterrupts();
FPGA::DisableHardwareOverwrite();
FPGA::Enable(FPGA::Periphery::SourceChip, false);
FPGA::Enable(FPGA::Periphery::SourceRF, false);
FPGA::Enable(FPGA::Periphery::LO1Chip, false);
FPGA::Enable(FPGA::Periphery::LO1RF, false);
FPGA::Enable(FPGA::Periphery::Amplifier, false);
FPGA::Enable(FPGA::Periphery::Port1Mixer, false);
FPGA::Enable(FPGA::Periphery::Port2Mixer, false);
FPGA::Enable(FPGA::Periphery::RefMixer, false);
FPGA::Enable(FPGA::Periphery::PortSwitch, false);
activeMode = Mode::Idle;
}
HW::AmplitudeSettings HW::GetAmplitudeSettings(int16_t cdbm, uint64_t freq, bool applyCorrections, bool port2) {
if (applyCorrections) {
auto correction = Cal::SourceCorrection(freq);
if (port2) {
cdbm += correction.port2;
} else {
cdbm += correction.port1;
}
}
AmplitudeSettings ret;
if(freq < BandSwitchFrequency) {
if(cdbm <= HW::LowBandMinPower) {
// can use the low power setting
ret.lowBandPower = Si5351C::DriveStrength::mA2;
cdbm -= HW::LowBandMinPower;
} else {
// needs the high power setting
ret.lowBandPower = Si5351C::DriveStrength::mA8;
cdbm -= HW::LowBandMaxPower;
}
} else {
if(cdbm <= HW::HighBandMinPower) {
// can use the low power setting
ret.highBandPower = MAX2871::Power::n4dbm;
cdbm -= HW::HighBandMinPower;
} else {
// needs the high power setting
ret.highBandPower = MAX2871::Power::p5dbm;
cdbm -= HW::HighBandMaxPower;
}
}
// calculate required attenuation
int16_t attval = -cdbm / 25;
if(attval > 127) {
attval = 127;
ret.unlevel = true;
} else if(attval < 0) {
attval = 0;
ret.unlevel = true;
} else {
ret.unlevel = false;
}
ret.attenuator = attval;
return ret;
}
bool HW::TimedOut() {
constexpr uint64_t timeout = 1000000;
auto bufISR = lastISR;
uint64_t now = Delay::get_us();
uint64_t timeSinceLast = now - bufISR;
if(activeMode != Mode::Idle && activeMode != Mode::Generator && !VNA::GetStandbyMode() && timeSinceLast > timeout) {
LOG_WARN("Timed out, last ISR was at %lu%06lu, now %lu%06lu"
, (uint32_t) (bufISR / 1000000), (uint32_t)(bufISR%1000000)
, (uint32_t) (now / 1000000), (uint32_t)(now%1000000));
if(activeMode == Mode::VNA) {
VNA::PrintStatus();
}
return true;
} else {
return false;
}
}
void HW::SetOutputUnlevel(bool unlev) {
unlevel = unlev;
}
void HW::getDeviceStatus(Protocol::DeviceStatusV1 *status, bool updateEvenWhenBusy) {
if(activeMode == Mode::Idle || updateEvenWhenBusy) {
// updating values from FPGA allowed
// read PLL temperatures
uint8_t tempSource, tempLO;
GetTemps(&tempSource, &tempLO);
LOG_INFO("PLL temperatures: %u/%u", tempSource, tempLO);
// Read ADC min/max
auto limits = FPGA::GetADCLimits();
LOG_INFO("ADC limits: P1: %d/%d P2: %d/%d R: %d/%d",
limits.P1min, limits.P1max, limits.P2min, limits.P2max,
limits.Rmin, limits.Rmax);
#define ADC_LIMIT 27000
if(limits.P1min < -ADC_LIMIT || limits.P1max > ADC_LIMIT
|| limits.P2min < -ADC_LIMIT || limits.P2max > ADC_LIMIT
|| limits.Rmin < -ADC_LIMIT || limits.Rmax > ADC_LIMIT) {
status->ADC_overload = true;
} else {
status->ADC_overload = false;
}
auto FPGA_status = FPGA::GetStatus();
status->LO1_locked = (FPGA_status & (int) FPGA::Interrupt::LO1Unlock) ? 0 : 1;
status->source_locked = (FPGA_status & (int) FPGA::Interrupt::SourceUnlock) ? 0 : 1;
status->extRefAvailable = Ref::available();
status->extRefInUse = extRefInUse;
status->unlevel = unlevel;
status->temp_LO1 = tempLO;
status->temp_source = tempSource;
FPGA::ResetADCLimits();
}
status->temp_MCU = STM::getTemperature();
}
bool HW::Ref::available() {
return Si5351.ExtCLKAvailable();
}
void HW::Ref::set(Protocol::ReferenceSettings s) {
ref = s;
}
bool HW::Ref::usingExternal() {
return extRefInUse;
}
void HW::Ref::update() {
if(extOutFreq != ref.ExtRefOuputFreq) {
extOutFreq = ref.ExtRefOuputFreq;
if(extOutFreq == 0) {
Si5351.Disable(SiChannel::ReferenceOut);
LOG_INFO("External reference output disabled");
} else {
Si5351.SetCLK(SiChannel::ReferenceOut, extOutFreq, Si5351C::PLL::A);
Si5351.Enable(SiChannel::ReferenceOut);
LOG_INFO("External reference output set to %luHz", extOutFreq);
}
}
bool useExternal = ref.UseExternalRef;
if (ref.AutomaticSwitch) {
useExternal = Ref::available();
}
if(useExternal != extRefInUse) {
// switch between internal and external reference
extRefInUse = useExternal;
if(extRefInUse) {
if(!Ref::available()) {
LOG_WARN("Forced switch to external reference but no signal detected");
}
Si5351.ConfigureCLKIn(10000000);
Si5351.SetPLL(Si5351C::PLL::A, HW::SI5351CPLLConstantFrequency, Si5351C::PLLSource::CLKIN);
Si5351.SetPLL(Si5351C::PLL::B, HW::SI5351CPLLAlignedFrequency, Si5351C::PLLSource::CLKIN);
LOG_INFO("Switched to external reference");
FPGA::Enable(FPGA::Periphery::ExtRefLED);
} else {
Si5351.SetPLL(Si5351C::PLL::A, HW::SI5351CPLLConstantFrequency, Si5351C::PLLSource::XTAL);
Si5351.SetPLL(Si5351C::PLL::B, HW::SI5351CPLLAlignedFrequency, Si5351C::PLLSource::XTAL);
LOG_INFO("Switched to internal reference");
FPGA::Disable(FPGA::Periphery::ExtRefLED);
}
}
constexpr uint32_t lock_timeout = 100;
uint32_t start = HAL_GetTick();
while(!Si5351.Locked(Si5351C::PLL::A) || !Si5351.Locked(Si5351C::PLL::A)) {
if(HAL_GetTick() - start > lock_timeout) {
LOG_ERR("Clock distributor PLLs failed to lock");
return;
}
}
}
void HW::setAcquisitionFrequencies(Protocol::AcquisitionFrequencySettings s) {
IF1 = s.IF1;
ADCprescaler = s.ADCprescaler;
DFTphaseInc = s.DFTphaseInc;
float ADCrate = (float) FPGA::Clockrate / ADCprescaler;
IF2 = ADCrate * DFTphaseInc / 4096;
ADCsamplerate = ADCrate;
}
uint32_t HW::getIF1() {
return IF1;
}
uint32_t HW::getIF2() {
return IF2;
}
uint32_t HW::getADCRate() {
return ADCsamplerate;
}
uint8_t HW::getADCPrescaler() {
return ADCprescaler;
}
uint64_t HW::getLastISRTimestamp() {
return lastISR;
}
bool HW::getStatusUpdateFlag(){
return StatusUpdateFlag;
}
void HW::setStatusUpdateFlag(bool flag){
StatusUpdateFlag = flag;
}
void HW::updateDeviceStatus() {
if(StatusUpdateFlag && (activeMode == Mode::Idle || activeMode == Mode::Generator)) {
static uint32_t last_update = 0;
if(HAL_GetTick() - last_update >= 1000) {
last_update = HAL_GetTick();
HW::Ref::update();
Protocol::PacketInfo packet;
packet.type = Protocol::PacketType::DeviceStatusV1;
// Enable PLL chips for temperature reading
bool srcEn = FPGA::IsEnabled(FPGA::Periphery::SourceChip);
bool LOEn = FPGA::IsEnabled(FPGA::Periphery::LO1Chip);
FPGA::Enable(FPGA::Periphery::SourceChip);
FPGA::Enable(FPGA::Periphery::LO1Chip);
HW::getDeviceStatus(&packet.statusV1, true);
// restore PLL state
FPGA::Enable(FPGA::Periphery::SourceChip, srcEn);
FPGA::Enable(FPGA::Periphery::LO1Chip, LOEn);
Communication::Send(packet);
}
}
}
uint16_t HW::getDFTPhaseInc() {
return DFTphaseInc;
}