WIP: device synchronization

This commit is contained in:
Jan Käberich 2022-08-07 03:01:22 +02:00
parent 047f6ce981
commit 58918f81c1
90 changed files with 8970 additions and 310 deletions

View file

@ -19,6 +19,7 @@
#include "Generator.hpp"
#include "SpectrumAnalyzer.hpp"
#include "HW_HAL.hpp"
#include "Trigger.hpp"
#define LOG_LEVEL LOG_LEVEL_INFO
#define LOG_MODULE "App"
@ -37,7 +38,10 @@ static bool sweepActive;
extern ADC_HandleTypeDef hadc1;
#define FLAG_USB_PACKET 0x01
#define FLAG_USB_PACKET 0x01
#define FLAG_TRIGGER_OUT_ISR 0x02
static bool lastReportedTrigger;
static void USBPacketReceived(const Protocol::PacketInfo &p) {
recv_packet = p;
@ -46,6 +50,12 @@ static void USBPacketReceived(const Protocol::PacketInfo &p) {
portYIELD_FROM_ISR(woken);
}
static void TriggerOutISR() {
BaseType_t woken = false;
xTaskNotifyFromISR(handle, FLAG_TRIGGER_OUT_ISR, eSetBits, &woken);
portYIELD_FROM_ISR(woken);
}
inline void App_Init() {
STM::Init();
Delay::Init();
@ -60,6 +70,7 @@ inline void App_Init() {
Log_SetRedirect(usb_log);
LOG_INFO("Start");
Exti::Init();
Trigger::Init(TriggerOutISR);
#ifdef HAS_FLASH
if(!HWHAL::flash.isPresent()) {
LOG_CRIT("Failed to detect onboard FLASH");
@ -247,16 +258,60 @@ inline void App_Process() {
HW::setAcquisitionFrequencies(recv_packet.acquisitionFrequencySettings);
Communication::SendWithoutPayload(Protocol::PacketType::Ack);
break;
case Protocol::PacketType::SetTrigger:
if(Trigger::GetMode() == Trigger::Mode::USB_GUI) {
Trigger::SetInput(true);
Communication::SendWithoutPayload(Protocol::PacketType::Ack);
} else {
Communication::SendWithoutPayload(Protocol::PacketType::Nack);
}
break;
case Protocol::PacketType::ClearTrigger:
if(Trigger::GetMode() == Trigger::Mode::USB_GUI) {
Trigger::SetInput(false);
Communication::SendWithoutPayload(Protocol::PacketType::Ack);
} else {
Communication::SendWithoutPayload(Protocol::PacketType::Nack);
}
break;
default:
// this packet type is not supported
Communication::SendWithoutPayload(Protocol::PacketType::Nack);
break;
}
}
if(notification & FLAG_TRIGGER_OUT_ISR) {
// trigger output (from FPGA) changed level
bool set = Trigger::GetOutput();
switch(Trigger::GetMode()) {
case Trigger::Mode::Off:
// nothing to do
break;
case Trigger::Mode::USB_GUI:
lastReportedTrigger = set;
Communication::SendWithoutPayload(set ? Protocol::PacketType::SetTrigger : Protocol::PacketType::ClearTrigger);
break;
case Trigger::Mode::ExtRef:
if(set) {
HWHAL::Si5351.Enable(HWHAL::SiChannel::ReferenceOut);
} else {
HWHAL::Si5351.Disable(HWHAL::SiChannel::ReferenceOut);
}
break;
case Trigger::Mode::Trigger:
// not supported by the hardware, nothing to do
break;
}
}
}
if(HW::TimedOut()) {
vTaskDelay(1000);
LOG_WARN("Timed out, FPGA status: 0x%04x", FPGA::GetStatus());
vTaskDelay(1000);
LOG_WARN("Trigger out: %d (last reported: %d), in: %d", (uint8_t) Trigger::GetOutput(), (uint8_t) lastReportedTrigger, (uint8_t) Trigger::GetInput());
HW::SetMode(HW::Mode::Idle);
// insert the last received packet (restarts the timed out operation)
Communication::BlockNextAck();
USBPacketReceived(last_measure_packet);
}
HW::updateDeviceStatus();

View file

@ -8,8 +8,8 @@
static uint8_t inputBuffer[1024];
uint16_t inputCnt = 0;
static uint8_t outputBuffer[1024];
static Communication::Callback callback = nullptr;
static uint8_t blockAcks = 0;
void Communication::SetCallback(Callback cb) {
callback = cb;
@ -66,7 +66,15 @@ void communication_usb_input(const uint8_t *buf, uint16_t len) {
}
bool Communication::SendWithoutPayload(Protocol::PacketType type) {
if(type == Protocol::PacketType::Ack && blockAcks) {
blockAcks--;
return true;
}
Protocol::PacketInfo p;
p.type = type;
return Send(p);
}
void Communication::BlockNextAck() {
blockAcks++;
}

View file

@ -13,6 +13,7 @@ using Callback = void(*)(const Protocol::PacketInfo&);
void SetCallback(Callback cb);
void Input(const uint8_t *buf, uint16_t len);
bool Send(const Protocol::PacketInfo &packet);
void BlockNextAck();
bool SendWithoutPayload(Protocol::PacketType type);
}

View file

@ -117,6 +117,8 @@ uint16_t Protocol::EncodePacket(const PacketInfo &packet, uint8_t *dest, uint16_
case PacketType::RequestFrequencyCorrection:
case PacketType::RequestAcquisitionFrequencySettings:
case PacketType::RequestDeviceStatus:
case PacketType::SetTrigger:
case PacketType::ClearTrigger:
// no payload
break;
case PacketType::VNADatapoint: payload_size = packet.VNAdatapoint->requiredBufferSize(); break;

View file

@ -331,6 +331,8 @@ enum class PacketType : uint8_t {
DeviceStatusV1 = 25,
RequestDeviceStatus = 26,
VNADatapoint = 27,
SetTrigger = 28,
ClearTrigger = 29,
};
using PacketInfo = struct _packetinfo {

View file

@ -325,7 +325,6 @@ bool Si5351C::WriteRegisterRange(Reg start, const uint8_t *data, uint8_t len) {
bool Si5351C::ExtCLKAvailable() {
uint8_t value;
ReadRegister(Reg::DeviceStatus, &value);
LOG_DEBUG("Device status: 0x%02x", value);
if (value & 0x10) {
return false;
} else {

View file

@ -7,6 +7,7 @@
#include "FreeRTOS.h"
#include "task.h"
#include "Util.hpp"
#include "Trigger.hpp"
#include <array>
#define LOG_LEVEL LOG_LEVEL_DEBUG
@ -229,6 +230,7 @@ void SA::Setup(Protocol::SpectrumAnalyzerSettings settings) {
FPGA::Enable(FPGA::Periphery::Amplifier, s.trackingGenerator);
FPGA::Enable(FPGA::Periphery::Port1Mixer);
FPGA::Enable(FPGA::Periphery::Port2Mixer);
Trigger::SetMode((Trigger::Mode) s.syncMode);
if(s.SignalID) {
// use different ADC prescalers depending on RBW: For small RBWs, images with the shifted ADC samplerate can be closer to the IF
@ -431,7 +433,9 @@ void SA::Work() {
// more measurements required for signal ID
signalIDstep++;
}
HW::Ref::update();
if(Trigger::GetMode() != Trigger::Mode::ExtRef) {
HW::Ref::update();
}
StartNextSample();
}

View file

@ -0,0 +1,73 @@
#include "Trigger.hpp"
#include "Drivers/Exti.hpp"
#include "main.h"
#include "HW_HAL.hpp"
#include "Communication/Protocol.hpp"
#include "Hardware.hpp"
static Trigger::Mode mode;
static Trigger::CallbackISR callback = nullptr;
void Trigger::Init(CallbackISR cb) {
mode = Mode::Off;
callback = cb;
Exti::SetCallback(FPGA_TRIGGER_OUT_GPIO_Port, FPGA_TRIGGER_OUT_Pin, Exti::EdgeType::Both, Exti::Pull::Down, [](void*){
STM::DispatchToInterrupt(callback);
}, nullptr);
}
extern "C" {
void vApplicationIdleHook() {
if(mode == Trigger::Mode::ExtRef) {
STM::DispatchToInterrupt([](){
Trigger::SetInput(HW::Ref::available());
});
}
}
}
void Trigger::SetMode(Mode m) {
if(mode == m) {
// already in the correct mdoe
return;
}
if(mode == Mode::ExtRef) {
// reset reference to default settings
HWHAL::Si5351.Disable(HWHAL::SiChannel::ReferenceOut);
}
mode = m;
if(mode == Mode::ExtRef) {
// Disable the external reference
Protocol::ReferenceSettings s;
s.AutomaticSwitch = 0;
s.ExtRefOuputFreq = 0;
s.UseExternalRef = 0;
HW::Ref::set(s);
HW::Ref::update();
HWHAL::Si5351.SetCLK(HWHAL::SiChannel::ReferenceOut, 10000000, Si5351C::PLL::A);
if(GetOutput()) {
HWHAL::Si5351.Enable(HWHAL::SiChannel::ReferenceOut);
} else {
HWHAL::Si5351.Disable(HWHAL::SiChannel::ReferenceOut);
}
}
}
Trigger::Mode Trigger::GetMode() {
return mode;
}
void Trigger::SetInput(bool high) {
if(high) {
FPGA_TRIGGER_IN_GPIO_Port->BSRR = FPGA_TRIGGER_IN_Pin;
} else {
FPGA_TRIGGER_IN_GPIO_Port->BSRR = FPGA_TRIGGER_IN_Pin << 16;
}
}
bool Trigger::GetOutput() {
return FPGA_TRIGGER_OUT_GPIO_Port->IDR & FPGA_TRIGGER_OUT_Pin;
}
bool Trigger::GetInput() {
return FPGA_TRIGGER_IN_GPIO_Port->IDR & FPGA_TRIGGER_IN_Pin;
}

View file

@ -0,0 +1,25 @@
#pragma once
#include <cstdint>
namespace Trigger {
enum class Mode : uint8_t {
Off = 0,
USB_GUI = 1,
ExtRef = 2,
Trigger = 3,
};
using CallbackISR = void(*)(void);
void Init(CallbackISR cb);
void SetMode(Mode m);
Mode GetMode();
void SetInput(bool high);
bool GetOutput();
bool GetInput();
}

View file

@ -13,6 +13,7 @@
#include "task.h"
#include "Util.hpp"
#include "usb.h"
#include "Trigger.hpp"
#include <cmath>
#define LOG_LEVEL LOG_LEVEL_INFO
@ -279,6 +280,7 @@ bool VNA::Setup(Protocol::SweepSettings s) {
FPGA::Enable(FPGA::Periphery::LO1Chip);
FPGA::Enable(FPGA::Periphery::LO1RF);
FPGA::SetupSweep(s.stages, s.port1Stage, s.port2Stage, s.syncMode != 0);
Trigger::SetMode((Trigger::Mode) s.syncMode);
FPGA::Enable(FPGA::Periphery::PortSwitch);
pointCnt = 0;
stageCnt = 0;
@ -350,7 +352,9 @@ bool VNA::MeasurementDone(const FPGA::SamplingResult &result) {
void VNA::Work() {
// end of sweep
HW::Ref::update();
if(Trigger::GetMode() != Trigger::Mode::ExtRef) {
HW::Ref::update();
}
// Compile info packet
Protocol::PacketInfo packet;
packet.type = Protocol::PacketType::DeviceStatusV1;
@ -365,86 +369,87 @@ void VNA::SweepHalted() {
if(!active) {
return;
}
LOG_DEBUG("Halted before point %d", pointCnt);
// Check if IF table has entry at this point
if (IFTableIndexCnt < IFTableNumEntries && IFTable[IFTableIndexCnt].pointCnt == pointCnt) {
Si5351.WriteRawCLKConfig(SiChannel::Port1LO2, IFTable[IFTableIndexCnt].clkconfig);
Si5351.WriteRawCLKConfig(SiChannel::Port2LO2, IFTable[IFTableIndexCnt].clkconfig);
Si5351.WriteRawCLKConfig(SiChannel::RefLO2, IFTable[IFTableIndexCnt].clkconfig);
Si5351.ResetPLL(Si5351C::PLL::B);
IFTableIndexCnt++;
// PLL reset causes the 2.LO to turn off briefly and then ramp on back, needs delay before next point
Delay::us(1300);
}
uint64_t frequency = getPointFrequency(pointCnt);
int16_t power = settings.cdbm_excitation_start
+ (settings.cdbm_excitation_stop - settings.cdbm_excitation_start)
* pointCnt / (settings.points - 1);
bool adcShiftRequired = false;
if (frequency < HW::BandSwitchFrequency) {
auto driveStrength = fixedPowerLowband;
if(!settings.fixedPowerSetting) {
auto amplitude = HW::GetAmplitudeSettings(power, frequency, true, false);
// attenuator value has already been set in sweep setup
driveStrength = amplitude.lowBandPower;
}
// need the Si5351 as Source
bool freqSuccess = Si5351.SetCLK(SiChannel::LowbandSource, frequency, Si5351C::PLL::B, driveStrength);
static bool lowbandDisabled = false;
if (pointCnt == 0) {
// First point in sweep, switch to correct source
FPGA::Disable(FPGA::Periphery::SourceRF);
lowbandDisabled = true;
}
if(lowbandDisabled && freqSuccess) {
// frequency is valid, can enable lowband source now
Si5351.Enable(SiChannel::LowbandSource);
// Resuming the halted sweep requires I2C bus operations to the Si5355. When trigger synchronization is enabled
// in the external reference mode, this might collide with the trigger input check. Instead both these actions
// are handled through the STM::DispatchToInterrupt functionality, ensuring that they do not interrupt each other
STM::DispatchToInterrupt([](){
LOG_DEBUG("Halted before point %d", pointCnt);
// Check if IF table has entry at this point
if (IFTableIndexCnt < IFTableNumEntries && IFTable[IFTableIndexCnt].pointCnt == pointCnt) {
Si5351.WriteRawCLKConfig(SiChannel::Port1LO2, IFTable[IFTableIndexCnt].clkconfig);
Si5351.WriteRawCLKConfig(SiChannel::Port2LO2, IFTable[IFTableIndexCnt].clkconfig);
Si5351.WriteRawCLKConfig(SiChannel::RefLO2, IFTable[IFTableIndexCnt].clkconfig);
Si5351.ResetPLL(Si5351C::PLL::B);
IFTableIndexCnt++;
// PLL reset causes the 2.LO to turn off briefly and then ramp on back, needs delay before next point
Delay::us(1300);
lowbandDisabled = false;
}
// At low frequencies the 1.LO feedthrough mixes with the 2.LO in the second mixer.
// Depending on the stimulus frequency, the resulting mixing product might alias to the 2.IF
// in the ADC which causes a spike. Check for this and shift the ADC sampling frequency if necessary
uint32_t LO_mixing = (HW::getIF1() + frequency) - (HW::getIF1() - HW::getIF2());
if(abs(Util::Alias(LO_mixing, HW::getADCRate()) - HW::getIF2()) <= actualBandwidth * 2) {
// the image is in or near the IF bandwidth and would cause a peak
// Use a slightly different ADC sample rate if possible
if(HW::getIF2() == HW::DefaultIF2) {
adcShiftRequired = true;
uint64_t frequency = getPointFrequency(pointCnt);
int16_t power = settings.cdbm_excitation_start
+ (settings.cdbm_excitation_stop - settings.cdbm_excitation_start)
* pointCnt / (settings.points - 1);
bool adcShiftRequired = false;
if (frequency < HW::BandSwitchFrequency) {
auto driveStrength = fixedPowerLowband;
if(!settings.fixedPowerSetting) {
auto amplitude = HW::GetAmplitudeSettings(power, frequency, true, false);
// attenuator value has already been set in sweep setup
driveStrength = amplitude.lowBandPower;
}
// need the Si5351 as Source
bool freqSuccess = Si5351.SetCLK(SiChannel::LowbandSource, frequency, Si5351C::PLL::B, driveStrength);
static bool lowbandDisabled = false;
if (pointCnt == 0) {
// First point in sweep, switch to correct source
FPGA::Disable(FPGA::Periphery::SourceRF);
lowbandDisabled = true;
}
if(lowbandDisabled && freqSuccess) {
// frequency is valid, can enable lowband source now
Si5351.Enable(SiChannel::LowbandSource);
Delay::us(1300);
lowbandDisabled = false;
}
// At low frequencies the 1.LO feedthrough mixes with the 2.LO in the second mixer.
// Depending on the stimulus frequency, the resulting mixing product might alias to the 2.IF
// in the ADC which causes a spike. Check for this and shift the ADC sampling frequency if necessary
uint32_t LO_mixing = (HW::getIF1() + frequency) - (HW::getIF1() - HW::getIF2());
if(abs(Util::Alias(LO_mixing, HW::getADCRate()) - HW::getIF2()) <= actualBandwidth * 2) {
// the image is in or near the IF bandwidth and would cause a peak
// Use a slightly different ADC sample rate if possible
if(HW::getIF2() == HW::DefaultIF2) {
adcShiftRequired = true;
}
}
} else if(!FPGA::IsEnabled(FPGA::Periphery::SourceRF)){
// first sweep point in highband is also halted, disable lowband source
Si5351.Disable(SiChannel::LowbandSource);
FPGA::Enable(FPGA::Periphery::SourceRF);
}
} else if(!FPGA::IsEnabled(FPGA::Periphery::SourceRF)){
// first sweep point in highband is also halted, disable lowband source
Si5351.Disable(SiChannel::LowbandSource);
FPGA::Enable(FPGA::Periphery::SourceRF);
}
if (pointCnt == 0) {
HAL_Delay(2);
}
if (pointCnt == 0) {
HAL_Delay(2);
}
if(adcShiftRequired) {
FPGA::WriteRegister(FPGA::Reg::ADCPrescaler, alternativePrescaler);
FPGA::WriteRegister(FPGA::Reg::PhaseIncrement, alternativePhaseInc);
adcShifted = true;
} else if(adcShifted) {
// reset to default value
FPGA::WriteRegister(FPGA::Reg::ADCPrescaler, HW::getADCPrescaler());
FPGA::WriteRegister(FPGA::Reg::PhaseIncrement, HW::getDFTPhaseInc());
adcShifted = false;
}
if(adcShiftRequired) {
FPGA::WriteRegister(FPGA::Reg::ADCPrescaler, alternativePrescaler);
FPGA::WriteRegister(FPGA::Reg::PhaseIncrement, alternativePhaseInc);
adcShifted = true;
} else if(adcShifted) {
// reset to default value
FPGA::WriteRegister(FPGA::Reg::ADCPrescaler, HW::getADCPrescaler());
FPGA::WriteRegister(FPGA::Reg::PhaseIncrement, HW::getDFTPhaseInc());
adcShifted = false;
}
if(usb_available_buffer() >= reservedUSBbuffer) {
// enough space available, can resume immediately
FPGA::ResumeHaltedSweep();
} else {
// USB buffer could potentially overflow before next halted point, wait until more space is available.
// This function is called from a low level interrupt, need to dispatch to lower priority to allow USB
// handling to continue
STM::DispatchToInterrupt([](){
if(usb_available_buffer() >= reservedUSBbuffer) {
// enough space available, can resume immediately
FPGA::ResumeHaltedSweep();
} else {
// USB buffer could potentially overflow before next halted point, wait until more space is available.
uint32_t start = HAL_GetTick();
while(usb_available_buffer() < reservedUSBbuffer) {
if(HAL_GetTick() - start > 100) {
@ -457,8 +462,8 @@ void VNA::SweepHalted() {
}
}
FPGA::ResumeHaltedSweep();
});
}
}
});
}
void VNA::Stop() {