/** * @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 #include #include /*---------------------------------------------------------------------------*/ /* 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(rand()) / (static_cast(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(rampUpMs * samplingRate / 1000.0); rampDownSamples = static_cast(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(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(sizeof(float32)); outputBuf = reinterpret_cast(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(GetInputSignalMemory(0u)); ok = (triggerIn != NULL_PTR(float32 *)); } else if (ok && (nIn == 2u)) { triggerIn = reinterpret_cast(GetInputSignalMemory(0u)); plateauMsIn = reinterpret_cast(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(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(*triggerIn); } bool rising = ((prevTrigger < 0.5) && (trig >= 0.5)); prevTrigger = trig; float64 plateauMs = plateauMsDefault; if (plateauMsIn != NULL_PTR(float32 *)) { plateauMs = static_cast(*plateauMsIn); } if (plateauMs < 0.0) { plateauMs = 0.0; } if (rising) { StartSequence(plateauMs); } else if (autoTriggerPeriodMs > 0.0) { uint64 autoSamples = static_cast(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(phaseElapsed) / static_cast(phaseTotal)); break; case PulsePhaseFlat: base = highLevel; break; case PulsePhaseRampDown: base = highLevel * (1.0 - static_cast(phaseElapsed) / static_cast(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(base + noise + emi); } samplesSinceTrigger += static_cast(nElements); return true; } CLASS_REGISTER(PulseGeneratorGAM, "1.0") } /* namespace MARTe */