326 lines
11 KiB
C++
326 lines
11 KiB
C++
/**
|
|
* @file PulseGeneratorGAM.cpp
|
|
* @brief Source file for class PulseGeneratorGAM
|
|
* @date 29/08/2026
|
|
* @author Martino Ferrari
|
|
*
|
|
* @copyright Copyright 2015 F4E | European Joint Undertaking for ITER and
|
|
* the Development of Fusion Energy ('Fusion for Energy').
|
|
* Licensed under the EUPL, Version 1.1 or - as soon they will be approved
|
|
* by the European Commission - subsequent versions of the EUPL (the "Licence")
|
|
* You may not use this work except in compliance with the Licence.
|
|
* You may obtain a copy of the Licence at: http://ec.europa.eu/idabc/eupl
|
|
*
|
|
* @warning Unless required by applicable law or agreed to in writing,
|
|
* software distributed under the Licence is distributed on an "AS IS"
|
|
* basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
|
* or implied. See the Licence permissions and limitations under the Licence.
|
|
*/
|
|
|
|
#define DLL_API
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
/* Standard header includes */
|
|
/*---------------------------------------------------------------------------*/
|
|
#include <cmath>
|
|
#include <cstdlib>
|
|
#include <ctime>
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
/* Project header includes */
|
|
/*---------------------------------------------------------------------------*/
|
|
#include "AdvancedErrorManagement.h"
|
|
#include "PulseGeneratorGAM.h"
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
/* Method definitions */
|
|
/*---------------------------------------------------------------------------*/
|
|
|
|
namespace MARTe {
|
|
|
|
/* File-local PRNG helpers: uniform [0,1) and gaussian (Box-Muller). */
|
|
static float64 PulseRandUnit() {
|
|
return static_cast<float64>(rand()) / (static_cast<float64>(RAND_MAX) + 1.0);
|
|
}
|
|
|
|
static float64 PulseGaussian(float64 sigma) {
|
|
float64 u1 = PulseRandUnit();
|
|
if (u1 < 1e-12) {
|
|
u1 = 1e-12;
|
|
}
|
|
float64 u2 = PulseRandUnit();
|
|
static const float64 TWO_PI = 6.28318530717958647692;
|
|
return sigma * std::sqrt(-2.0 * std::log(u1)) * std::cos(TWO_PI * u2);
|
|
}
|
|
|
|
PulseGeneratorGAM::PulseGeneratorGAM() :
|
|
GAM(),
|
|
samplingRate(1000000.0),
|
|
rampUpMs(1.0),
|
|
rampDownMs(100.0),
|
|
highLevel(-40000.0),
|
|
noiseStdDev(333.33),
|
|
emiAmplitude(5000.0),
|
|
emiProbabilityPerSample(0.00001),
|
|
emiSpikeSamples(5u),
|
|
plateauMsDefault(500.0),
|
|
autoTriggerPeriodMs(0.0),
|
|
seed(0u),
|
|
nElements(0u),
|
|
outputBuf(NULL_PTR(float32 *)),
|
|
triggerIn(NULL_PTR(float32 *)),
|
|
plateauMsIn(NULL_PTR(float32 *)),
|
|
phase(PulsePhaseOff),
|
|
startLevel(0.0),
|
|
currentLevel(0.0),
|
|
phaseElapsed(0ull),
|
|
phaseTotal(0ull),
|
|
rampUpSamples(0ull),
|
|
rampDownSamples(0ull),
|
|
plateauSamples(0ull),
|
|
prevTrigger(0.0),
|
|
samplesSinceTrigger(0ull),
|
|
spikeRemaining(0u),
|
|
spikeValue(0.0) {
|
|
}
|
|
|
|
PulseGeneratorGAM::~PulseGeneratorGAM() {
|
|
}
|
|
|
|
bool PulseGeneratorGAM::Initialise(StructuredDataI &data) {
|
|
bool ok = GAM::Initialise(data);
|
|
if (ok && !data.Read("SamplingRate", samplingRate)) {
|
|
samplingRate = 1000000.0;
|
|
}
|
|
if (ok && !data.Read("RampUpMs", rampUpMs)) {
|
|
rampUpMs = 1.0;
|
|
}
|
|
if (ok && !data.Read("RampDownMs", rampDownMs)) {
|
|
rampDownMs = 100.0;
|
|
}
|
|
if (ok && !data.Read("HighLevel", highLevel)) {
|
|
highLevel = -40000.0;
|
|
}
|
|
if (ok && !data.Read("NoiseStdDev", noiseStdDev)) {
|
|
noiseStdDev = 333.33;
|
|
}
|
|
if (ok && !data.Read("EMIAmplitude", emiAmplitude)) {
|
|
emiAmplitude = 5000.0;
|
|
}
|
|
if (ok && !data.Read("EMIProbabilityPerSample", emiProbabilityPerSample)) {
|
|
emiProbabilityPerSample = 0.00001;
|
|
}
|
|
if (ok && !data.Read("EMISpikeSamples", emiSpikeSamples)) {
|
|
emiSpikeSamples = 5u;
|
|
}
|
|
if (ok && !data.Read("PlateauMsDefault", plateauMsDefault)) {
|
|
plateauMsDefault = 500.0;
|
|
}
|
|
if (ok && !data.Read("AutoTriggerPeriodMs", autoTriggerPeriodMs)) {
|
|
autoTriggerPeriodMs = 0.0;
|
|
}
|
|
if (ok && !data.Read("Seed", seed)) {
|
|
seed = 0u;
|
|
}
|
|
|
|
if (ok && (samplingRate <= 0.0)) {
|
|
REPORT_ERROR(ErrorManagement::InitialisationError,
|
|
"PulseGeneratorGAM: SamplingRate must be > 0.");
|
|
ok = false;
|
|
}
|
|
if (ok && (rampUpMs < 0.0)) {
|
|
REPORT_ERROR(ErrorManagement::InitialisationError,
|
|
"PulseGeneratorGAM: RampUpMs must be >= 0.");
|
|
ok = false;
|
|
}
|
|
if (ok && (rampDownMs <= 0.0)) {
|
|
REPORT_ERROR(ErrorManagement::InitialisationError,
|
|
"PulseGeneratorGAM: RampDownMs must be > 0.");
|
|
ok = false;
|
|
}
|
|
if (ok && (noiseStdDev < 0.0)) {
|
|
REPORT_ERROR(ErrorManagement::InitialisationError,
|
|
"PulseGeneratorGAM: NoiseStdDev must be >= 0.");
|
|
ok = false;
|
|
}
|
|
if (ok && ((emiProbabilityPerSample < 0.0) || (emiProbabilityPerSample > 1.0))) {
|
|
REPORT_ERROR(ErrorManagement::InitialisationError,
|
|
"PulseGeneratorGAM: EMIProbabilityPerSample must be in [0, 1].");
|
|
ok = false;
|
|
}
|
|
if (ok && (autoTriggerPeriodMs < 0.0)) {
|
|
REPORT_ERROR(ErrorManagement::InitialisationError,
|
|
"PulseGeneratorGAM: AutoTriggerPeriodMs must be >= 0.");
|
|
ok = false;
|
|
}
|
|
|
|
if (ok) {
|
|
rampUpSamples = static_cast<uint64>(rampUpMs * samplingRate / 1000.0);
|
|
rampDownSamples = static_cast<uint64>(rampDownMs * samplingRate / 1000.0);
|
|
if (rampUpSamples == 0ull) {
|
|
REPORT_ERROR(ErrorManagement::Warning,
|
|
"PulseGeneratorGAM: RampUpMs too short for the sample rate; "
|
|
"the ramp phase is skipped.");
|
|
}
|
|
if (seed == 0u) {
|
|
srand(static_cast<unsigned int>(time(0)));
|
|
} else {
|
|
srand(seed);
|
|
}
|
|
}
|
|
return ok;
|
|
}
|
|
|
|
bool PulseGeneratorGAM::Setup() {
|
|
bool ok = (GetNumberOfOutputSignals() == 1u);
|
|
if (!ok) {
|
|
REPORT_ERROR(ErrorManagement::InitialisationError,
|
|
"PulseGeneratorGAM: exactly one output signal is required.");
|
|
return false;
|
|
}
|
|
|
|
uint32 sz = 0u;
|
|
ok = GetSignalByteSize(OutputSignals, 0u, sz);
|
|
if (ok) {
|
|
nElements = sz / static_cast<uint32>(sizeof(float32));
|
|
outputBuf = reinterpret_cast<float32 *>(GetOutputSignalMemory(0u));
|
|
ok = (outputBuf != NULL_PTR(float32 *)) && (nElements > 0u);
|
|
if (!ok) {
|
|
REPORT_ERROR(ErrorManagement::InitialisationError,
|
|
"PulseGeneratorGAM: failed to resolve output signal memory.");
|
|
}
|
|
}
|
|
|
|
uint32 nIn = GetNumberOfInputSignals();
|
|
if (ok && (nIn == 1u)) {
|
|
triggerIn = reinterpret_cast<float32 *>(GetInputSignalMemory(0u));
|
|
ok = (triggerIn != NULL_PTR(float32 *));
|
|
} else if (ok && (nIn == 2u)) {
|
|
triggerIn = reinterpret_cast<float32 *>(GetInputSignalMemory(0u));
|
|
plateauMsIn = reinterpret_cast<float32 *>(GetInputSignalMemory(1u));
|
|
ok = (triggerIn != NULL_PTR(float32 *)) && (plateauMsIn != NULL_PTR(float32 *));
|
|
} else if (ok && (nIn > 2u)) {
|
|
REPORT_ERROR(ErrorManagement::InitialisationError,
|
|
"PulseGeneratorGAM: at most two input signals are supported "
|
|
"(Trigger, PlateauMs).");
|
|
ok = false;
|
|
}
|
|
return ok;
|
|
}
|
|
|
|
void PulseGeneratorGAM::StartSequence(float64 plateauMs) {
|
|
phase = PulsePhaseRampUp;
|
|
startLevel = currentLevel;
|
|
phaseElapsed = 0ull;
|
|
phaseTotal = rampUpSamples;
|
|
plateauSamples = static_cast<uint64>(plateauMs * samplingRate / 1000.0);
|
|
samplesSinceTrigger = 0ull;
|
|
/* Skip zero-length phases (e.g. RampUpMs that rounds to 0 samples). */
|
|
if (phaseTotal == 0ull) {
|
|
AdvanceToNextPhase();
|
|
}
|
|
}
|
|
|
|
void PulseGeneratorGAM::AdvanceToNextPhase() {
|
|
for (;;) {
|
|
if (phase == PulsePhaseRampUp) {
|
|
phase = PulsePhaseFlat;
|
|
phaseElapsed = 0ull;
|
|
phaseTotal = plateauSamples;
|
|
} else if (phase == PulsePhaseFlat) {
|
|
phase = PulsePhaseRampDown;
|
|
phaseElapsed = 0ull;
|
|
phaseTotal = rampDownSamples;
|
|
} else {
|
|
phase = PulsePhaseOff;
|
|
phaseElapsed = 0ull;
|
|
currentLevel = 0.0;
|
|
return;
|
|
}
|
|
if (phaseTotal > 0ull) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool PulseGeneratorGAM::Execute() {
|
|
/* Trigger detection: rising edge on the optional Trigger input. */
|
|
float64 trig = 0.0;
|
|
if (triggerIn != NULL_PTR(float32 *)) {
|
|
trig = static_cast<float64>(*triggerIn);
|
|
}
|
|
bool rising = ((prevTrigger < 0.5) && (trig >= 0.5));
|
|
prevTrigger = trig;
|
|
|
|
float64 plateauMs = plateauMsDefault;
|
|
if (plateauMsIn != NULL_PTR(float32 *)) {
|
|
plateauMs = static_cast<float64>(*plateauMsIn);
|
|
}
|
|
if (plateauMs < 0.0) {
|
|
plateauMs = 0.0;
|
|
}
|
|
|
|
if (rising) {
|
|
StartSequence(plateauMs);
|
|
} else if (autoTriggerPeriodMs > 0.0) {
|
|
uint64 autoSamples = static_cast<uint64>(autoTriggerPeriodMs * samplingRate / 1000.0);
|
|
if ((autoSamples > 0ull) && (samplesSinceTrigger >= autoSamples)) {
|
|
StartSequence(plateauMs);
|
|
}
|
|
}
|
|
|
|
for (uint32 i = 0u; i < nElements; i++) {
|
|
float64 base = 0.0;
|
|
switch (phase) {
|
|
case PulsePhaseRampUp:
|
|
base = startLevel +
|
|
(highLevel - startLevel) * (static_cast<float64>(phaseElapsed) /
|
|
static_cast<float64>(phaseTotal));
|
|
break;
|
|
case PulsePhaseFlat:
|
|
base = highLevel;
|
|
break;
|
|
case PulsePhaseRampDown:
|
|
base = highLevel * (1.0 - static_cast<float64>(phaseElapsed) /
|
|
static_cast<float64>(phaseTotal));
|
|
break;
|
|
case PulsePhaseOff:
|
|
default:
|
|
base = 0.0;
|
|
break;
|
|
}
|
|
currentLevel = base;
|
|
|
|
if (phase != PulsePhaseOff) {
|
|
phaseElapsed++;
|
|
if (phaseElapsed >= phaseTotal) {
|
|
AdvanceToNextPhase();
|
|
}
|
|
}
|
|
|
|
/* Always-on gaussian noise plus random EMI spikes (on and off phase). */
|
|
float64 noise = 0.0;
|
|
if (noiseStdDev > 0.0) {
|
|
noise = PulseGaussian(noiseStdDev);
|
|
}
|
|
float64 emi = 0.0;
|
|
if (spikeRemaining > 0u) {
|
|
emi = spikeValue;
|
|
spikeRemaining--;
|
|
} else if ((emiProbabilityPerSample > 0.0) && (PulseRandUnit() < emiProbabilityPerSample)) {
|
|
spikeValue = ((rand() & 1u) == 0u) ? emiAmplitude : -emiAmplitude;
|
|
spikeRemaining = (emiSpikeSamples > 0u) ? emiSpikeSamples : 1u;
|
|
emi = spikeValue;
|
|
spikeRemaining--;
|
|
}
|
|
|
|
outputBuf[i] = static_cast<float32>(base + noise + emi);
|
|
}
|
|
samplesSinceTrigger += static_cast<uint64>(nElements);
|
|
return true;
|
|
}
|
|
|
|
CLASS_REGISTER(PulseGeneratorGAM, "1.0")
|
|
|
|
} /* namespace MARTe */
|