function GXTXT_batch_impulsi_v8()

    clc;
    close all;
    more off;

    % ============================================================
    % GX-TXT-v3
    % ANALISI BATCH DELLA RISPOSTA AL NUMERO DI IMPULSI
    %
    % VERSIONE 8
    %
    % File indice atteso:
    %
    % p_gain,p_sens,vgen_vpp,v_rsense_vpp,n_impulsi,nome_file_csv
    %
    % Esempio:
    %
    % 500,5048,2.10,1.02,20,time_led_N020.csv
    % 500,5048,2.10,1.02,40,time_led_N040.csv
    %
    % CH3 = N_LED_ARANCIO
    % CH4 = V_HOLD
    %
    % Questa versione:
    % - usa V_RSENSE misurata per stimare I_SENSE;
    % - salva numero di impulsi e durata burst nei metadati;
    % - salva i risultati nel master PRIMA di generare i grafici;
    % - non considera un errore la mancata accensione del LED;
    % - non considera un errore l'impossibilita' di eseguire il fit
    %   completo della scarica di HOLD;
    % - gestisce correttamente HOLD piatto o praticamente nullo;
    % - produce un log verboso dell'intero batch.
    %
    % ============================================================

    VERSIONE_ANALISI = 'GXTXT_batch_impulsi_v8';

    % ============================================================
    % FILE INDICE
    % ============================================================

    indexFile = 'prove_impulsi.csv';

    % ============================================================
    % OUTPUT
    % ============================================================

    outputRoot = 'calibrazione script';
    pngDPI = 200;

    % ============================================================
    % PARAMETRI COMUNI DELLA CAMPAGNA
    % ============================================================

    cfg.RLED = 680;               % ohm
    cfg.RSENSE = 51e3;            % ohm

    cfg.frequenza_Hz = 38000;
    cfg.dutyCycle_pct = 50;

    cfg.modalitaStimolo = 'burst';
    cfg.triggerReference = 'burst_start';

    % ============================================================
    % FIT HOLD
    % ============================================================

    cfg.deltaVHold = 0.10;        % V
    cfg.holdFitMargin = 0.30;     % V

    % ============================================================
    % ANALISI LED
    %
    % Mantenere costanti questi parametri se si vogliono confrontare
    % acquisizioni differenti.
    % ============================================================

    cfg.ledBin_ms = 1.0;
    cfg.ledSlopeWindow_ms = 50.0;

    % Criterio validato sulla precedente campagna
    cfg.ledPeakFraction = 0.10;

    cfg.ledNoiseSigma = 5.0;
    cfg.ledStable_ms = 30.0;
    cfg.ledPlotMargin = 0.35;

    % Escursione minima di N_LED per tentare il riconoscimento
    % di una vera transizione.
    cfg.minLedExcursion_V = 0.20;

    % ============================================================
    % DERIVATA HOLD - SCARICA
    % ============================================================

    cfg.holdSlowBin_ms = 5.0;
    cfg.holdSlowSlopeWindow_ms = 100.0;
    cfg.holdDischargeDelay = 0.050;

    % ============================================================
    % DERIVATA HOLD - CARICA
    % ============================================================

    cfg.holdFastBin_ms = 0.10;
    cfg.holdFastSlopeWindow_ms = 0.50;

    cfg.holdFastPre_ms = 5;
    cfg.holdFastPost_ms = 15;

    % ============================================================
    % INIZIALIZZAZIONE BATCH
    % ============================================================

    batchTimestamp = strftime( ...
        '%Y%m%d_%H%M%S', ...
        localtime(time()));

    batchID = ['batch_impulsi_' batchTimestamp];

    if (exist(outputRoot, 'dir') != 7)
        mkdir(outputRoot);
    endif

    batchDir = fullfile(outputRoot, batchID);

    if (exist(batchDir, 'dir') != 7)
        mkdir(batchDir);
    endif

    if (exist(indexFile, 'file') != 2)
        error('File indice non trovato: %s', indexFile);
    endif

    copyfile( ...
        indexFile, ...
        fullfile(batchDir, 'indice_prove.csv'));

    % ============================================================
    % LOG
    % ============================================================

    logFile = fullfile(batchDir, 'batch.log');

    diary(logFile);
    diary on;

    fprintf('\n');
    fprintf('============================================================\n');
    fprintf('GX-TXT-v3 - ANALISI BATCH IMPULSI - VERSIONE 8\n');
    fprintf('============================================================\n\n');

    fprintf('Versione analisi : %s\n', VERSIONE_ANALISI);
    fprintf('Batch ID         : %s\n', batchID);
    fprintf('Indice prove     : %s\n', indexFile);
    fprintf('Cartella output  : %s\n', batchDir);

    fprintf('\n');

    fprintf('Frequenza        : %.0f Hz\n', cfg.frequenza_Hz);
    fprintf('Duty cycle       : %.1f %%\n', cfg.dutyCycle_pct);
    fprintf('R_SENSE          : %.0f ohm\n', cfg.RSENSE);

    fprintf('\n');

    fprintf('Parametri analisi LED:\n');
    fprintf('  media          : %.1f ms\n', cfg.ledBin_ms);
    fprintf('  finestra       : %.1f ms\n', cfg.ledSlopeWindow_ms);
    fprintf('  soglia lobo    : %.1f %% del massimo\n', ...
        cfg.ledPeakFraction * 100);

    fflush(stdout);

    % ============================================================
    % LETTURA FILE INDICE
    % ============================================================

    fprintf('\n');
    fprintf('------------------------------------------------------------\n');
    fprintf('LETTURA INDICE DELLE PROVE\n');
    fprintf('------------------------------------------------------------\n');

    prove = leggi_indice_prove_impulsi(indexFile);

    nProve = length(prove);

    fprintf('Numero di prove trovate: %d\n', nProve);

    if (nProve == 0)
        diary off;
        error('Il file indice non contiene prove.');
    endif

    indexDir = fileparts(indexFile);

    if (isempty(indexDir))
        indexDir = pwd;
    endif

    % ============================================================
    % FILE MASTER
    % ============================================================

    masterCSV = fullfile( ...
        batchDir, ...
        'risultati_batch.csv');

    statusCSV = fullfile( ...
        batchDir, ...
        'stato_batch.csv');

    masterHeader = { ...
        'batch_row', ...
        'testID', ...
        'source_file', ...
        'p_gain_ohm', ...
        'p_sens_ohm', ...
        'vgen_vpp', ...
        'v_rsense_vpp', ...
        'isense_uA', ...
        'frequenza_Hz', ...
        'numero_impulsi', ...
        'durata_burst_ms', ...
        'VCC_V', ...
        'VholdBase_V', ...
        'VholdMax_V', ...
        'tHoldMax_s', ...
        'holdFitValid', ...
        'tauHold_s', ...
        'R2Hold', ...
        'Capp_uF', ...
        'IledMax_mA', ...
        'tIledMax_s', ...
        'ledTransitionValid', ...
        'tempoLedAcceso_s', ...
        'tLedStart_s', ...
        'tLedPeak_s', ...
        'tLedEnd_s', ...
        'transitionTime_ms', ...
        'VholdLedStart_V', ...
        'VholdLedPeak_V', ...
        'VholdLedEnd_V', ...
        'IledStart_mA', ...
        'IledPeak_mA', ...
        'IledEnd_mA', ...
        'dVledMax_Vs', ...
        'analysisVersion' ...
    };

    fidMaster = fopen(masterCSV, 'w');

    if (fidMaster < 0)
        diary off;
        error('Impossibile creare %s', masterCSV);
    endif

    scrivi_riga_csv(fidMaster, masterHeader);

    fclose(fidMaster);

    fidStatus = fopen(statusCSV, 'w');

    scrivi_riga_csv(fidStatus, { ...
        'batch_row', ...
        'testID', ...
        'source_file', ...
        'status', ...
        'message' ...
    });

    fclose(fidStatus);

    % ============================================================
    % CONTATORI
    % ============================================================

    successi = 0;
    erroriAnalisi = 0;
    erroriGrafici = 0;

    % ============================================================
    % CICLO DELLE PROVE
    % ============================================================

    for n = 1:nProve

        fprintf('\n\n');
        fprintf('============================================================\n');
        fprintf('PROVA %d / %d\n', n, nProve);
        fprintf('============================================================\n');

        pgain = prove(n).p_gain;
        psens = prove(n).p_sens;

        vgen_vpp = prove(n).vgen_vpp;
        v_rsense_vpp = prove(n).v_rsense_vpp;

        numeroImpulsi = prove(n).n_impulsi;

        nomeCSV = prove(n).file;

        csvPath = risolvi_file_csv(nomeCSV, indexDir);

        isense_uA = ...
            v_rsense_vpp / cfg.RSENSE * 1e6;

        durataBurst_s = ...
            numeroImpulsi / cfg.frequenza_Hz;

        durataBurst_ms = ...
            durataBurst_s * 1000;

        testID = genera_test_id_impulsi( ...
            n, ...
            pgain, ...
            psens, ...
            isense_uA, ...
            numeroImpulsi, ...
            csvPath);

        testDir = fullfile( ...
            batchDir, ...
            testID);

        if (exist(testDir, 'dir') != 7)
            mkdir(testDir);
        endif

        fprintf('Test ID          : %s\n', testID);
        fprintf('CSV              : %s\n', csvPath);
        fprintf('P_GAIN           : %.6f ohm\n', pgain);
        fprintf('P_SENS           : %.6f ohm\n', psens);
        fprintf('VGEN             : %.6f Vpp\n', vgen_vpp);
        fprintf('V_RSENSE         : %.6f Vpp\n', v_rsense_vpp);
        fprintf('I_SENSE stimata  : %.6f uA\n', isense_uA);
        fprintf('Numero impulsi   : %d\n', numeroImpulsi);
        fprintf('Durata burst     : %.6f ms\n', durataBurst_ms);
        fprintf('Output           : %s\n', testDir);

        fflush(stdout);

        % ========================================================
        % METADATI DELLA PROVA
        % ========================================================

        meta = struct();

        meta.batchRow = n;
        meta.testID = testID;
        meta.sourceFile = csvPath;

        meta.p_gain = pgain;
        meta.p_sens = psens;

        meta.vgen_vpp = vgen_vpp;
        meta.v_rsense_vpp = v_rsense_vpp;
        meta.isense_uA = isense_uA;

        meta.frequenza_Hz = cfg.frequenza_Hz;
        meta.numeroImpulsi = numeroImpulsi;
        meta.durataBurst_ms = durataBurst_ms;

        meta.dutyCycle_pct = cfg.dutyCycle_pct;
        meta.modalitaStimolo = cfg.modalitaStimolo;
        meta.triggerReference = cfg.triggerReference;

        meta.timestamp = strftime( ...
            '%Y-%m-%d %H:%M:%S', ...
            localtime(time()));

        cfgTest = cfg;
        cfgTest.numeroImpulsi = numeroImpulsi;

        % ========================================================
        % ANALISI
        % ========================================================

        analisiOK = false;
        plotOK = false;

        try

            fprintf('\n[1] Lettura e analisi del CSV...\n');
            fflush(stdout);

            [R, D] = analizza_csv_gxtxt_impulsi( ...
                csvPath, ...
                cfgTest);

            analisiOK = true;

            fprintf('    Campioni letti      : %d\n', R.N);
            fprintf('    dt                   : %.3f us\n', R.dt*1e6);
            fprintf('    VCC                  : %.4f V\n', R.VCC);
            fprintf('    baseline HOLD        : %.4f V\n', R.VholdBase);

            fprintf('\n[2] Analisi HOLD...\n');

            fprintf('    V_HOLD max           : %.4f V\n', R.VholdMax);
            fprintf('    tempo V_HOLD max     : %.6f s\n', R.tHoldMax);

            if (R.holdFitValid)

                fprintf('    fit HOLD             : valido\n');
                fprintf('    tau                  : %.6f s\n', R.tauHold);
                fprintf('    R^2                  : %.6f\n', R.R2hold);
                fprintf('    C apparente          : %.3f uF\n', R.Capp_uF);

            else

                fprintf('    fit HOLD             : non disponibile / non necessario\n');

            endif

            fprintf('\n[3] Analisi LED...\n');

            fprintf('    I_LED max            : %.4f mA\n', R.IledMax);

            if (R.ledTransitionValid)

                fprintf('    Transizione LED riconosciuta.\n');
                fprintf('    inizio               : %.6f s\n', R.tLedStart);
                fprintf('    massimo derivata     : %.6f s\n', R.tLedPeak);
                fprintf('    fine                 : %.6f s\n', R.tLedEnd);
                fprintf('    durata transizione   : %.3f ms\n', ...
                    R.transitionTime_ms);
                fprintf('    tempo LED acceso     : %.6f s\n', ...
                    R.tempoLedAcceso_s);
                fprintf('    HOLD inizio/fine     : %.4f / %.4f V\n', ...
                    R.VholdLedStart, ...
                    R.VholdLedEnd);

            else

                fprintf('    Nessuna transizione LED affidabile riconosciuta.\n');

            endif

            fflush(stdout);

            % ====================================================
            % SALVATAGGIO FILE DELLA PROVA
            % ====================================================

            fprintf('\n[4] Scrittura metadati e risultati...\n');

            salva_file_testuali_impulsi( ...
                testDir, ...
                meta, ...
                R, ...
                cfgTest, ...
                VERSIONE_ANALISI);

            fprintf('    metadati.txt         OK\n');
            fprintf('    risultati.txt        OK\n');
            fprintf('    risultati.csv        OK\n');
            fprintf('    tabella_hold.csv     OK\n');

            % ====================================================
            % MASTER CSV
            %
            % IMPORTANTE:
            % viene aggiornato PRIMA dei grafici.
            % ====================================================

            fprintf('\n[5] Aggiornamento risultati_batch.csv...\n');

            fidMaster = fopen(masterCSV, 'a');

            scrivi_riga_csv(fidMaster, { ...
                n, ...
                testID, ...
                csvPath, ...
                pgain, ...
                psens, ...
                vgen_vpp, ...
                v_rsense_vpp, ...
                isense_uA, ...
                cfg.frequenza_Hz, ...
                numeroImpulsi, ...
                durataBurst_ms, ...
                R.VCC, ...
                R.VholdBase, ...
                R.VholdMax, ...
                R.tHoldMax, ...
                R.holdFitValid, ...
                R.tauHold, ...
                R.R2hold, ...
                R.Capp_uF, ...
                R.IledMax, ...
                R.tIledMax, ...
                R.ledTransitionValid, ...
                R.tempoLedAcceso_s, ...
                R.tLedStart, ...
                R.tLedPeak, ...
                R.tLedEnd, ...
                R.transitionTime_ms, ...
                R.VholdLedStart, ...
                R.VholdLedPeak, ...
                R.VholdLedEnd, ...
                R.IledStart, ...
                R.IledPeak, ...
                R.IledEnd, ...
                R.dLedMax, ...
                VERSIONE_ANALISI ...
            });

            fclose(fidMaster);

            fprintf('    master CSV           OK\n');

        catch err

            erroriAnalisi = erroriAnalisi + 1;

            fprintf('\n');
            fprintf('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n');
            fprintf('ERRORE DI ANALISI NELLA PROVA %d\n', n);
            fprintf('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n');

            fprintf('Test ID : %s\n', testID);
            fprintf('File    : %s\n', csvPath);
            fprintf('Errore  : %s\n', err.message);

            if (isfield(err, 'stack'))

                for s = 1:length(err.stack)

                    fprintf( ...
                        '  %s - linea %d\n', ...
                        err.stack(s).name, ...
                        err.stack(s).line);

                endfor

            endif

        end_try_catch

        % ========================================================
        % GRAFICI
        %
        % Un eventuale errore grafico NON cancella il risultato
        % numerico gia' salvato nel master.
        % ========================================================

        if (analisiOK)

            try

                fprintf('\n[6] Generazione grafici PNG...\n');

                salva_grafici_impulsi( ...
                    testDir, ...
                    R, ...
                    D, ...
                    cfgTest, ...
                    pngDPI);

                fprintf('    Grafici completati.\n');

                plotOK = true;

            catch errPlot

                erroriGrafici = erroriGrafici + 1;

                fprintf('\n');
                fprintf('ATTENZIONE: errore nella generazione dei grafici.\n');
                fprintf('I risultati numerici sono stati comunque salvati.\n');
                fprintf('Errore grafico: %s\n', errPlot.message);

                if (isfield(errPlot, 'stack'))

                    for s = 1:length(errPlot.stack)

                        fprintf( ...
                            '  %s - linea %d\n', ...
                            errPlot.stack(s).name, ...
                            errPlot.stack(s).line);

                    endfor

                endif

            end_try_catch

        endif

        % ========================================================
        % STATO
        % ========================================================

        fidStatus = fopen(statusCSV, 'a');

        if (!analisiOK)

            scrivi_riga_csv(fidStatus, { ...
                n, ...
                testID, ...
                csvPath, ...
                'ERROR_ANALYSIS', ...
                'Analisi non completata' ...
            });

        elseif (plotOK)

            scrivi_riga_csv(fidStatus, { ...
                n, ...
                testID, ...
                csvPath, ...
                'OK', ...
                'Analisi e grafici completati' ...
            });

            successi = successi + 1;

        else

            scrivi_riga_csv(fidStatus, { ...
                n, ...
                testID, ...
                csvPath, ...
                'OK_PLOT_ERROR', ...
                'Risultati salvati; errore solo grafico' ...
            });

            successi = successi + 1;

        endif

        fclose(fidStatus);

        if (analisiOK)

            fprintf('\n>>> PROVA %d ELABORATA <<<\n', n);

        else

            fprintf('\n>>> PROVA %d NON ELABORATA <<<\n', n);

        endif

        close all;
        fflush(stdout);

    endfor

    % ============================================================
    % FINE BATCH
    % ============================================================

    fprintf('\n\n');
    fprintf('============================================================\n');
    fprintf('FINE ANALISI BATCH IMPULSI\n');
    fprintf('============================================================\n\n');

    fprintf('Prove totali       : %d\n', nProve);
    fprintf('Risultati salvati  : %d\n', successi);
    fprintf('Errori analisi     : %d\n', erroriAnalisi);
    fprintf('Errori grafici     : %d\n', erroriGrafici);

    fprintf('\n');

    fprintf('Risultati master   : %s\n', masterCSV);
    fprintf('Stato batch        : %s\n', statusCSV);
    fprintf('Log completo       : %s\n', logFile);

    fprintf('\nAnalisi terminata.\n');

    fflush(stdout);

    diary off;

endfunction


% ==============================================================
% LETTURA FILE INDICE
% ==============================================================

function prove = leggi_indice_prove_impulsi(filename)

    fid = fopen(filename, 'r');

    if (fid < 0)
        error('Impossibile aprire il file indice %s', filename);
    endif

    header = fgetl(fid);

    if (!ischar(header))
        fclose(fid);
        error('File indice vuoto.');
    endif

    if (!isempty(strfind(header, ';')) && isempty(strfind(header, ',')))
        delimitatore = ';';
    else
        delimitatore = ',';
    endif

    prove = struct( ...
        'p_gain', {}, ...
        'p_sens', {}, ...
        'vgen_vpp', {}, ...
        'v_rsense_vpp', {}, ...
        'n_impulsi', {}, ...
        'file', {});

    n = 0;

    while (true)

        riga = fgetl(fid);

        if (!ischar(riga))
            break;
        endif

        riga = strtrim(riga);

        if (isempty(riga))
            continue;
        endif

        if (riga(1) == '#')
            continue;
        endif

        campi = strsplit(riga, delimitatore);

        if (length(campi) != 6)

            fclose(fid);

            error( ...
                'Riga indice non valida: attese 6 colonne: %s', ...
                riga);

        endif

        pg = str2double(strtrim(campi{1}));
        ps = str2double(strtrim(campi{2}));
        vg = str2double(strtrim(campi{3}));
        vr = str2double(strtrim(campi{4}));
        ni = str2double(strtrim(campi{5}));

        nf = strtrim(campi{6});

        if (length(nf) >= 2)

            if (nf(1) == '"' && nf(end) == '"')
                nf = nf(2:end-1);
            endif

        endif

        if ( ...
            isnan(pg) || ...
            isnan(ps) || ...
            isnan(vg) || ...
            isnan(vr) || ...
            isnan(ni) ...
        )

            fclose(fid);

            error( ...
                'Valori numerici non validi nella riga: %s', ...
                riga);

        endif

        n = n + 1;

        prove(n).p_gain = pg;
        prove(n).p_sens = ps;
        prove(n).vgen_vpp = vg;
        prove(n).v_rsense_vpp = vr;
        prove(n).n_impulsi = round(ni);
        prove(n).file = nf;

    endwhile

    fclose(fid);

endfunction


% ==============================================================
% RISOLUZIONE FILE CSV
% ==============================================================

function pathCSV = risolvi_file_csv(nome, indexDir)

    if (exist(nome, 'file') == 2)

        pathCSV = nome;
        return;

    endif

    candidato = fullfile(indexDir, nome);

    if (exist(candidato, 'file') == 2)

        pathCSV = candidato;
        return;

    endif

    error('CSV non trovato: %s', nome);

endfunction


% ==============================================================
% GENERAZIONE TEST ID
% ==============================================================

function testID = genera_test_id_impulsi( ...
    numero, ...
    pgain, ...
    psens, ...
    isense_uA, ...
    numeroImpulsi, ...
    csvPath)

    g = strrep(sprintf('%.1f', pgain), '.', 'p');
    s = strrep(sprintf('%.1f', psens), '.', 'p');
    i = strrep(sprintf('%.2f', isense_uA), '.', 'p');

    [~, base, ~] = fileparts(csvPath);

    base = regexprep( ...
        base, ...
        '[^A-Za-z0-9_-]', ...
        '_');

    testID = sprintf( ...
        'T%03d_G%s_S%s_I%su_N%03d_%s', ...
        numero, ...
        g, ...
        s, ...
        i, ...
        numeroImpulsi, ...
        base);

endfunction


% ==============================================================
% ANALISI SINGOLO CSV
% ==============================================================

function [R, D] = analizza_csv_gxtxt_impulsi(csvPath, cfg)

    fprintf('    Apertura CSV...\n');

    fid = fopen(csvPath, 'r');

    if (fid < 0)
        error('Impossibile aprire %s', csvPath);
    endif

    riga1 = fgetl(fid);
    riga2 = fgetl(fid);

    if (!ischar(riga2))
        fclose(fid);
        error('Header CSV Rigol incompleto.');
    endif

    campi = strsplit(riga2, ',');

    if (length(campi) < 5)
        fclose(fid);
        error('Header temporale Rigol non valido.');
    endif

    tStart = str2double(campi{4});
    dt     = str2double(campi{5});

    if (isnan(tStart) || isnan(dt) || dt <= 0)

        fclose(fid);
        error('Impossibile ricavare Start o Increment dall''header.');

    endif

    fprintf('    Lettura campioni...\n');

    dati = fscanf( ...
        fid, ...
        '%f,%f,%f,', ...
        [3, Inf])';

    fclose(fid);

    if (isempty(dati))
        error('CSV privo di campioni.');
    endif

    if (size(dati,2) != 3)
        error('Numero di colonne inatteso nel CSV.');
    endif

    seq   = dati(:,1);
    vLed  = dati(:,2);   % CH3 = N_LED_ARANCIO
    vHold = dati(:,3);   % CH4 = V_HOLD

    t = tStart + seq .* dt;

    N = length(t);

    % ============================================================
    % BASELINE
    % ============================================================

    idxPre = find(t < -0.1);

    if (isempty(idxPre))

        nPre = max( ...
            10, ...
            round(0.05*N));

        nPre = min(nPre,N);

        idxPre = 1:nPre;

    endif

    VholdBase = median(vHold(idxPre));
    VCC       = median(vLed(idxPre));

    % ============================================================
    % CORRENTE LED
    % ============================================================

    iLed_mA = ...
        1000 * (VCC-vLed) ./ cfg.RLED;

    iLed_mA(iLed_mA < 0) = 0;

    % ============================================================
    % MASSIMI DOPO IL TRIGGER
    %
    % Evita che una traccia HOLD piatta restituisca il primo
    % campione del pretrigger come massimo.
    % ============================================================

    idxPost = find(t >= 0);

    if (isempty(idxPost))
        error('Nessun campione disponibile dopo il trigger.');
    endif

    [VholdMax, idxRelHold] = max(vHold(idxPost));

    idxHoldMax = idxPost(idxRelHold);
    tHoldMax = t(idxHoldMax);

    [IledMax, idxIledMax] = max(iLed_mA);
    tIledMax = t(idxIledMax);

    % ============================================================
    % TABELLA SCARICA HOLD
    % ============================================================

    vTop = ...
        floor(VholdMax/cfg.deltaVHold) * ...
        cfg.deltaVHold;

    vBottom = ...
        ceil( ...
            (VholdBase+cfg.holdFitMargin) / ...
            cfg.deltaVHold ...
        ) * cfg.deltaVHold;

    if (vTop >= vBottom)

        livelliHold = ...
            (vTop:-cfg.deltaVHold:vBottom)';

    else

        livelliHold = [];

    endif

    nLivelli = length(livelliHold);

    tabTempo = NaN(nLivelli,1);
    tabVled  = NaN(nLivelli,1);
    tabIled  = NaN(nLivelli,1);

    for k = 1:nLivelli

        target = livelliHold(k);

        idxRel = find( ...
            vHold(idxHoldMax:end) <= target, ...
            1, ...
            'first');

        if (isempty(idxRel))
            continue;
        endif

        idx = idxHoldMax + idxRel - 1;

        if (idx <= idxHoldMax)

            tcross = t(idx);

        else

            v1 = vHold(idx-1);
            v2 = vHold(idx);

            t1 = t(idx-1);
            t2 = t(idx);

            if (abs(v2-v1) < eps)

                tcross = t2;

            else

                tcross = ...
                    t1 + ...
                    (target-v1) * ...
                    (t2-t1) / ...
                    (v2-v1);

            endif

        endif

        tabTempo(k) = tcross;

        tabVled(k) = interp1( ...
            t, ...
            vLed, ...
            tcross, ...
            'linear');

        tabIled(k) = max( ...
            0, ...
            1000 * ...
            (VCC-tabVled(k)) / ...
            cfg.RLED);

    endfor

    valid = !isnan(tabTempo);

    livelliHold = livelliHold(valid);
    tabTempo    = tabTempo(valid);
    tabVled     = tabVled(valid);
    tabIled     = tabIled(valid);

    % ============================================================
    % FIT HOLD
    %
    % Il fit e' opzionale. Se a 200 ms/div la scarica non contiene
    % abbastanza punti, la prova resta valida e il fit viene marcato
    % come non disponibile.
    % ============================================================

    holdFitValid = false;

    tauHold = NaN;
    AHold   = NaN;
    R2hold  = NaN;
    Capp_uF = NaN;

    if (length(livelliHold) >= 3)

        xFit = tabTempo - tHoldMax;
        vFit = livelliHold - VholdBase;

        validFit = ...
            isfinite(xFit) & ...
            isfinite(vFit) & ...
            vFit > cfg.holdFitMargin & ...
            xFit >= 0;

        xFit = xFit(validFit);
        vFit = vFit(validFit);

        if (length(xFit) >= 3)

            pHold = polyfit( ...
                xFit, ...
                log(vFit), ...
                1);

            if ( ...
                isfinite(pHold(1)) && ...
                pHold(1) < 0 ...
            )

                tauCandidate = -1/pHold(1);
                ACandidate = exp(pHold(2));

                if ( ...
                    isfinite(tauCandidate) && ...
                    tauCandidate > 0 && ...
                    isfinite(ACandidate) ...
                )

                    vPred = ...
                        VholdBase + ...
                        ACandidate .* ...
                        exp(-xFit./tauCandidate);

                    vMis = ...
                        VholdBase + vFit;

                    SSres = ...
                        sum((vMis-vPred).^2);

                    SStot = ...
                        sum((vMis-mean(vMis)).^2);

                    if (SStot > 0)

                        R2candidate = ...
                            1-SSres/SStot;

                    else

                        R2candidate = NaN;

                    endif

                    tauHold = tauCandidate;
                    AHold   = ACandidate;
                    R2hold  = R2candidate;
                    Capp_uF = tauHold / 100e3 * 1e6;

                    holdFitValid = true;

                endif

            endif

        endif

    endif

    % ============================================================
    % DERIVATA LED
    % ============================================================

    [tLedB, vLedB] = media_blocchi( ...
        t, ...
        vLed, ...
        cfg.ledBin_ms/1000);

    [tLedSlope, dVled_dt] = derivata_locale( ...
        tLedB, ...
        vLedB, ...
        cfg.ledSlopeWindow_ms/1000);

    ledTransitionValid = false;

    tLedStart = NaN;
    tLedPeak  = NaN;
    tLedEnd   = NaN;

    transitionTime_ms = NaN;
    tempoLedAcceso_s   = NaN;

    VholdLedStart = NaN;
    VholdLedPeak  = NaN;
    VholdLedEnd   = NaN;

    VledStart = NaN;
    VledPeak  = NaN;
    VledEnd   = NaN;

    IledStart = NaN;
    IledPeak  = NaN;
    IledEnd   = NaN;

    dLedMax = NaN;
    thresholdLed = NaN;

    if (!isempty(tLedSlope))

        idxLedPost = find(tLedSlope > max(0,tHoldMax));

        if (!isempty(idxLedPost))

            [dLedMax, idxRel] = ...
                max(dVled_dt(idxLedPost));

            idxLedPeak = ...
                idxLedPost(idxRel);

            tLedPeak = ...
                tLedSlope(idxLedPeak);

            escursioneLed = ...
                max(vLed) - min(vLed);

            if ( ...
                escursioneLed >= cfg.minLedExcursion_V && ...
                isfinite(dLedMax) && ...
                dLedMax > 0 ...
            )

                % =================================================
                % STIMA DEL FONDO DELLA DERIVATA
                % =================================================

                tBaseStart = ...
                    max(0,tHoldMax) + 0.050;

                tBaseEnd = ...
                    max(0,tHoldMax) + ...
                    0.60 * ...
                    (tLedPeak-max(0,tHoldMax));

                idxBase = find( ...
                    tLedSlope >= tBaseStart & ...
                    tLedSlope <= tBaseEnd);

                if (length(idxBase) < 10)

                    idxBase = find( ...
                        tLedSlope > max(0,tHoldMax) & ...
                        tLedSlope < tLedPeak);

                endif

                if (isempty(idxBase))

                    dBase = 0;

                else

                    dBase = dVled_dt(idxBase);

                endif

                muBase = median(dBase);

                MADbase = median( ...
                    abs(dBase-muBase));

                sigmaBase = ...
                    1.4826*MADbase;

                if ( ...
                    !isfinite(sigmaBase) || ...
                    sigmaBase <= eps ...
                )

                    sigmaBase = std(dBase);

                endif

                if (!isfinite(sigmaBase))
                    sigmaBase = 0;
                endif

                thresholdNoise = ...
                    muBase + ...
                    cfg.ledNoiseSigma*sigmaBase;

                thresholdPeak = ...
                    cfg.ledPeakFraction*dLedMax;

                thresholdLed = max([ ...
                    0, ...
                    thresholdNoise, ...
                    thresholdPeak ...
                ]);

                dtSlope = ...
                    median(diff(tLedSlope));

                nStable = max( ...
                    1, ...
                    round( ...
                        (cfg.ledStable_ms/1000) / ...
                        dtSlope));

                % =================================================
                % INIZIO DEL LOBO
                % =================================================

                idxStart = NaN;

                for i = idxLedPeak-1:-1:nStable

                    j1 = i-nStable+1;
                    j2 = i;

                    if (all( ...
                        dVled_dt(j1:j2) <= thresholdLed))

                        idxStart = i+1;
                        break;

                    endif

                endfor

                % =================================================
                % FINE DEL LOBO
                % =================================================

                idxEnd = NaN;

                for i = ...
                        idxLedPeak+1 : ...
                        length(dVled_dt)-nStable+1

                    j1 = i;
                    j2 = i+nStable-1;

                    if (all( ...
                        dVled_dt(j1:j2) <= thresholdLed))

                        idxEnd = i-1;
                        break;

                    endif

                endfor

                if ( ...
                    !isnan(idxStart) && ...
                    !isnan(idxEnd) && ...
                    idxEnd > idxStart ...
                )

                    ledTransitionValid = true;

                    tLedStart = ...
                        tLedSlope(idxStart);

                    tLedEnd = ...
                        tLedSlope(idxEnd);

                    transitionTime_ms = ...
                        (tLedEnd-tLedStart)*1000;

                    % t = 0 coincide con l'inizio del burst
                    tempoLedAcceso_s = ...
                        max(0,tLedEnd);

                    VholdLedStart = interp1( ...
                        t,vHold,tLedStart,'linear');

                    VholdLedPeak = interp1( ...
                        t,vHold,tLedPeak,'linear');

                    VholdLedEnd = interp1( ...
                        t,vHold,tLedEnd,'linear');

                    VledStart = interp1( ...
                        t,vLed,tLedStart,'linear');

                    VledPeak = interp1( ...
                        t,vLed,tLedPeak,'linear');

                    VledEnd = interp1( ...
                        t,vLed,tLedEnd,'linear');

                    IledStart = max( ...
                        0, ...
                        1000*(VCC-VledStart)/cfg.RLED);

                    IledPeak = max( ...
                        0, ...
                        1000*(VCC-VledPeak)/cfg.RLED);

                    IledEnd = max( ...
                        0, ...
                        1000*(VCC-VledEnd)/cfg.RLED);

                endif

            endif

        endif

    endif

    % ============================================================
    % DERIVATE HOLD
    % ============================================================

    [tHF, vHF] = media_blocchi( ...
        t, ...
        vHold, ...
        cfg.holdFastBin_ms/1000);

    [tHoldFastSlope, dVholdFast_dt] = ...
        derivata_locale( ...
            tHF, ...
            vHF, ...
            cfg.holdFastSlopeWindow_ms/1000);

    [tHS, vHS] = media_blocchi( ...
        t, ...
        vHold, ...
        cfg.holdSlowBin_ms/1000);

    [tHoldSlowSlope, dVholdSlow_dt] = ...
        derivata_locale( ...
            tHS, ...
            vHS, ...
            cfg.holdSlowSlopeWindow_ms/1000);

    % ============================================================
    % RISULTATI
    % ============================================================

    R.N = N;
    R.dt = dt;
    R.tStart = tStart;

    R.VCC = VCC;
    R.VholdBase = VholdBase;

    R.VholdMax = VholdMax;
    R.tHoldMax = tHoldMax;

    R.IledMax = IledMax;
    R.tIledMax = tIledMax;

    R.holdFitValid = holdFitValid;
    R.tauHold = tauHold;
    R.AHold = AHold;
    R.R2hold = R2hold;
    R.Capp_uF = Capp_uF;

    R.ledTransitionValid = ledTransitionValid;

    R.tLedStart = tLedStart;
    R.tLedPeak  = tLedPeak;
    R.tLedEnd   = tLedEnd;

    R.transitionTime_ms = transitionTime_ms;
    R.tempoLedAcceso_s = tempoLedAcceso_s;

    R.VholdLedStart = VholdLedStart;
    R.VholdLedPeak  = VholdLedPeak;
    R.VholdLedEnd   = VholdLedEnd;

    R.IledStart = IledStart;
    R.IledPeak  = IledPeak;
    R.IledEnd   = IledEnd;

    R.dLedMax = dLedMax;
    R.thresholdLed = thresholdLed;

    R.livelliHold = livelliHold;
    R.tabTempo = tabTempo;
    R.tabVled = tabVled;
    R.tabIled = tabIled;

    % ============================================================
    % DATI PER GRAFICI
    % ============================================================

    D.t = t;
    D.vHold = vHold;
    D.vLed = vLed;
    D.iLed_mA = iLed_mA;

    D.idxHoldMax = idxHoldMax;

    D.tLedSlope = tLedSlope;
    D.dVled_dt = dVled_dt;

    D.tHoldFastSlope = tHoldFastSlope;
    D.dVholdFast_dt = dVholdFast_dt;

    D.tHoldSlowSlope = tHoldSlowSlope;
    D.dVholdSlow_dt = dVholdSlow_dt;

endfunction


% ==============================================================
% MEDIA A BLOCCHI
% ==============================================================

function [tb, yb] = media_blocchi(t, y, bin_s)

    tb = [];
    yb = [];

    if (length(t) < 2)
        return;
    endif

    dt = median(diff(t));

    if ( ...
        !isfinite(dt) || ...
        dt <= 0 ...
    )
        return;
    endif

    nBin = max( ...
        1, ...
        round(bin_s/dt));

    nBlocchi = floor(length(t)/nBin);

    if (nBlocchi < 1)
        return;
    endif

    nUsati = nBlocchi*nBin;

    tt = t(1:nUsati);
    yy = y(1:nUsati);

    tb = mean( ...
        reshape(tt,nBin,nBlocchi), ...
        1)';

    yb = mean( ...
        reshape(yy,nBin,nBlocchi), ...
        1)';

endfunction


% ==============================================================
% DERIVATA LOCALE MEDIANTE REGRESSIONE
% ==============================================================

function [ts, dydt] = derivata_locale(t, y, finestra_s)

    ts = [];
    dydt = [];

    if (length(t) < 3)
        return;
    endif

    dt = median(diff(t));

    if ( ...
        !isfinite(dt) || ...
        dt <= 0 ...
    )
        return;
    endif

    nWin = round(finestra_s/dt);

    if (nWin < 3)
        nWin = 3;
    endif

    if (mod(nWin,2) == 0)
        nWin = nWin+1;
    endif

    if (nWin > length(t))

        nWin = length(t);

        if (mod(nWin,2) == 0)
            nWin = nWin-1;
        endif

    endif

    if (nWin < 3)
        return;
    endif

    halfWin = floor(nWin/2);

    x = ...
        (-halfWin:halfWin)' .* dt;

    den = sum(x.^2);

    if (den <= 0)
        return;
    endif

    kernel = ...
        flipud(x) ./ den;

    dydt = conv( ...
        y, ...
        kernel, ...
        'valid');

    ts = ...
        t(halfWin+1:end-halfWin);

endfunction


% ==============================================================
% SALVATAGGIO FILE TESTUALI
% ==============================================================

function salva_file_testuali_impulsi( ...
    testDir, meta, R, cfg, versione)

    % -----------------------------------------------------------
    % METADATI.TXT
    % -----------------------------------------------------------

    fid = fopen( ...
        fullfile(testDir,'metadati.txt'), ...
        'w');

    fprintf(fid,'GX-TXT-v3 - METADATI PROVA IMPULSI\n');
    fprintf(fid,'============================================\n\n');

    fprintf(fid,'Versione analisi : %s\n', versione);
    fprintf(fid,'Timestamp        : %s\n', meta.timestamp);
    fprintf(fid,'Test ID          : %s\n', meta.testID);
    fprintf(fid,'CSV sorgente     : %s\n', meta.sourceFile);

    fprintf(fid,'\nCONDIZIONI PROVA\n');
    fprintf(fid,'--------------------------------------------\n');

    fprintf(fid,'P_GAIN           : %.6f ohm\n', meta.p_gain);
    fprintf(fid,'P_SENS           : %.6f ohm\n', meta.p_sens);

    fprintf(fid,'VGEN             : %.6f Vpp\n', meta.vgen_vpp);
    fprintf(fid,'V_RSENSE         : %.6f Vpp\n', meta.v_rsense_vpp);
    fprintf(fid,'I_SENSE stimata  : %.6f uA\n', meta.isense_uA);

    fprintf(fid,'Frequenza        : %.0f Hz\n', meta.frequenza_Hz);
    fprintf(fid,'Numero impulsi   : %d\n', meta.numeroImpulsi);
    fprintf(fid,'Durata burst     : %.6f ms\n', meta.durataBurst_ms);
    fprintf(fid,'Duty cycle       : %.3f %%\n', meta.dutyCycle_pct);

    fprintf(fid,'\nPARAMETRI ANALISI\n');
    fprintf(fid,'--------------------------------------------\n');

    fprintf(fid,'deltaVHold       : %.6f V\n', cfg.deltaVHold);
    fprintf(fid,'holdFitMargin    : %.6f V\n', cfg.holdFitMargin);
    fprintf(fid,'ledBin           : %.6f ms\n', cfg.ledBin_ms);
    fprintf(fid,'ledSlopeWindow   : %.6f ms\n', cfg.ledSlopeWindow_ms);
    fprintf(fid,'ledPeakFraction  : %.6f\n', cfg.ledPeakFraction);
    fprintf(fid,'ledStable        : %.6f ms\n', cfg.ledStable_ms);

    fclose(fid);

    % -----------------------------------------------------------
    % RISULTATI.TXT
    % -----------------------------------------------------------

    fid = fopen( ...
        fullfile(testDir,'risultati.txt'), ...
        'w');

    fprintf(fid,'GX-TXT-v3 - RISULTATI PROVA IMPULSI\n');
    fprintf(fid,'============================================\n\n');

    fprintf(fid,'Test ID          : %s\n\n', meta.testID);

    fprintf(fid,'CONDIZIONI\n');
    fprintf(fid,'--------------------------------------------\n');

    fprintf(fid,'P_GAIN           : %.6f ohm\n', meta.p_gain);
    fprintf(fid,'P_SENS           : %.6f ohm\n', meta.p_sens);
    fprintf(fid,'V_RSENSE         : %.6f Vpp\n', meta.v_rsense_vpp);
    fprintf(fid,'I_SENSE          : %.6f uA\n', meta.isense_uA);
    fprintf(fid,'Numero impulsi   : %d\n', meta.numeroImpulsi);
    fprintf(fid,'Durata burst     : %.6f ms\n', meta.durataBurst_ms);

    fprintf(fid,'\nRISULTATI GENERALI\n');
    fprintf(fid,'--------------------------------------------\n');

    fprintf(fid,'VCC              : %.6f V\n', R.VCC);
    fprintf(fid,'Baseline HOLD    : %.6f V\n', R.VholdBase);

    fprintf(fid,'V_HOLD max       : %.6f V\n', R.VholdMax);
    fprintf(fid,'t V_HOLD max     : %.9f s\n', R.tHoldMax);

    fprintf(fid,'I_LED max        : %.6f mA\n', R.IledMax);
    fprintf(fid,'t I_LED max      : %.9f s\n', R.tIledMax);

    fprintf(fid,'\nFIT HOLD\n');
    fprintf(fid,'--------------------------------------------\n');

    fprintf(fid,'Fit valido       : %d\n', R.holdFitValid);
    fprintf(fid,'Tau HOLD         : %.9f s\n', R.tauHold);
    fprintf(fid,'R^2              : %.9f\n', R.R2hold);
    fprintf(fid,'C apparente      : %.6f uF\n', R.Capp_uF);

    fprintf(fid,'\nTRANSIZIONE LED\n');
    fprintf(fid,'--------------------------------------------\n');

    fprintf(fid,'Valida           : %d\n', R.ledTransitionValid);
    fprintf(fid,'Tempo LED acceso : %.9f s\n', R.tempoLedAcceso_s);
    fprintf(fid,'Durata transiz.  : %.6f ms\n', R.transitionTime_ms);

    fprintf(fid,'\n');

    fprintf(fid, ...
        '%-20s %12s %12s %12s\n', ...
        'Punto', ...
        'Tempo[s]', ...
        'HOLD[V]', ...
        'I_LED[mA]');

    fprintf(fid, ...
        '---------------------------------------------------------------\n');

    fprintf(fid, ...
        '%-20s %12.6f %12.4f %12.4f\n', ...
        'Inizio', ...
        R.tLedStart, ...
        R.VholdLedStart, ...
        R.IledStart);

    fprintf(fid, ...
        '%-20s %12.6f %12.4f %12.4f\n', ...
        'Max derivata', ...
        R.tLedPeak, ...
        R.VholdLedPeak, ...
        R.IledPeak);

    fprintf(fid, ...
        '%-20s %12.6f %12.4f %12.4f\n', ...
        'Fine', ...
        R.tLedEnd, ...
        R.VholdLedEnd, ...
        R.IledEnd);

    fprintf(fid,'\n\nTABELLA SCARICA HOLD\n');
    fprintf(fid,'--------------------------------------------\n');

    fprintf(fid, ...
        '%12s %12s %12s %12s\n', ...
        'HOLD[V]', ...
        'Tempo[s]', ...
        'N_LED[V]', ...
        'I_LED[mA]');

    for k = 1:length(R.livelliHold)

        fprintf(fid, ...
            '%12.3f %12.6f %12.3f %12.3f\n', ...
            R.livelliHold(k), ...
            R.tabTempo(k), ...
            R.tabVled(k), ...
            R.tabIled(k));

    endfor

    fclose(fid);

    % -----------------------------------------------------------
    % TABELLA HOLD CSV
    % -----------------------------------------------------------

    fid = fopen( ...
        fullfile(testDir,'tabella_hold.csv'), ...
        'w');

    scrivi_riga_csv(fid,{ ...
        'V_HOLD_V', ...
        'tempo_s', ...
        'N_LED_V', ...
        'I_LED_mA' ...
    });

    for k = 1:length(R.livelliHold)

        scrivi_riga_csv(fid,{ ...
            R.livelliHold(k), ...
            R.tabTempo(k), ...
            R.tabVled(k), ...
            R.tabIled(k) ...
        });

    endfor

    fclose(fid);

    % -----------------------------------------------------------
    % RISULTATI CSV DELLA PROVA
    % -----------------------------------------------------------

    fid = fopen( ...
        fullfile(testDir,'risultati.csv'), ...
        'w');

    header = { ...
        'testID', ...
        'p_gain_ohm', ...
        'p_sens_ohm', ...
        'vgen_vpp', ...
        'v_rsense_vpp', ...
        'isense_uA', ...
        'numero_impulsi', ...
        'durata_burst_ms', ...
        'VholdMax_V', ...
        'holdFitValid', ...
        'tauHold_s', ...
        'R2Hold', ...
        'Capp_uF', ...
        'IledMax_mA', ...
        'ledTransitionValid', ...
        'tempoLedAcceso_s', ...
        'transitionTime_ms', ...
        'VholdLedStart_V', ...
        'VholdLedPeak_V', ...
        'VholdLedEnd_V' ...
    };

    scrivi_riga_csv(fid,header);

    scrivi_riga_csv(fid,{ ...
        meta.testID, ...
        meta.p_gain, ...
        meta.p_sens, ...
        meta.vgen_vpp, ...
        meta.v_rsense_vpp, ...
        meta.isense_uA, ...
        meta.numeroImpulsi, ...
        meta.durataBurst_ms, ...
        R.VholdMax, ...
        R.holdFitValid, ...
        R.tauHold, ...
        R.R2hold, ...
        R.Capp_uF, ...
        R.IledMax, ...
        R.ledTransitionValid, ...
        R.tempoLedAcceso_s, ...
        R.transitionTime_ms, ...
        R.VholdLedStart, ...
        R.VholdLedPeak, ...
        R.VholdLedEnd ...
    });

    fclose(fid);

endfunction


% ==============================================================
% GRAFICI
% ==============================================================

function salva_grafici_impulsi(testDir, R, D, cfg, pngDPI)

    % Tutte le figure sono invisibili durante il batch.

    % -----------------------------------------------------------
    % 1 - HOLD + FIT
    % -----------------------------------------------------------

    fprintf('    [1/9] HOLD + fit...\n');

    f = figure('visible','off');

    h = [];

    h(end+1) = plot(D.t,D.vHold,'-');
    hold on;

    labels = {'V_{HOLD} misurata'};

    if (!isempty(R.tabTempo))

        h(end+1) = plot(R.tabTempo,R.livelliHold,'o');
        labels{end+1} = 'Punti tabella';

    endif

    if ( ...
        R.holdFitValid && ...
        !isempty(R.tabTempo) ...
    )

        tFit = linspace( ...
            R.tHoldMax, ...
            max(R.tabTempo), ...
            1000);

        vFit = ...
            R.VholdBase + ...
            R.AHold .* ...
            exp(-(tFit-R.tHoldMax)./R.tauHold);

        h(end+1) = plot(tFit,vFit,'--');
        labels{end+1} = 'Fit esponenziale';

        title(sprintf( ...
            'GX-TXT-v3 - Scarica di HOLD - tau = %.3f s', ...
            R.tauHold));

    else

        title('GX-TXT-v3 - Nodo HOLD');

    endif

    grid on;
    xlabel('Tempo [s]');
    ylabel('V_{HOLD} [V]');

    if (!isempty(h))
        legend(h,labels,'location','northeast');
    endif

    print( ...
        f, ...
        fullfile(testDir,'01_hold_fit.png'), ...
        '-dpng', ...
        sprintf('-r%d',pngDPI));

    close(f);

    % -----------------------------------------------------------
    % 2 - N_LED COMPLETO
    % -----------------------------------------------------------

    fprintf('    [2/9] N_LED completo...\n');

    f = figure('visible','off');

    plot(D.t,D.vLed,'-');

    grid on;

    xlabel('Tempo [s]');
    ylabel('N\_LED\_ARANCIO [V]');

    title('GX-TXT-v3 - Tensione sul nodo N\_LED\_ARANCIO');

    xlim([D.t(1) D.t(end)]);

    print( ...
        f, ...
        fullfile(testDir,'02_n_led_completo.png'), ...
        '-dpng', ...
        sprintf('-r%d',pngDPI));

    close(f);

    % -----------------------------------------------------------
    % 3 - ACCENSIONE LED
    % -----------------------------------------------------------

    fprintf('    [3/9] Accensione LED...\n');

    f = figure('visible','off');

    plot(D.t,D.vLed,'-');

    grid on;

    xlabel('Tempo [s]');
    ylabel('N\_LED\_ARANCIO [V]');

    title('GX-TXT-v3 - Accensione del LED dopo il burst');

    x1 = max(D.t(1),-0.020);
    x2 = min(D.t(end), 0.020);

    if (x2 > x1)
        xlim([x1 x2]);
    endif

    print( ...
        f, ...
        fullfile(testDir,'03_accensione_led_zoom.png'), ...
        '-dpng', ...
        sprintf('-r%d',pngDPI));

    close(f);

    % -----------------------------------------------------------
    % 4 - CORRENTE LED
    % -----------------------------------------------------------

    fprintf('    [4/9] Corrente LED...\n');

    f = figure('visible','off');

    plot(D.t,D.iLed_mA,'-');

    grid on;

    xlabel('Tempo [s]');
    ylabel('I_{LED} [mA]');

    title('GX-TXT-v3 - Corrente del LED arancio nel tempo');

    xlim([D.t(1) D.t(end)]);

    print( ...
        f, ...
        fullfile(testDir,'04_corrente_led_tempo.png'), ...
        '-dpng', ...
        sprintf('-r%d',pngDPI));

    close(f);

    % -----------------------------------------------------------
    % 5 - DERIVATA LED
    % -----------------------------------------------------------

    fprintf('    [5/9] Derivata LED...\n');

    f = figure('visible','off');

    if (!isempty(D.tLedSlope))

        plot(D.tLedSlope,D.dVled_dt,'-');
        hold on;

        if (R.ledTransitionValid)

            yTop = max( ...
                1, ...
                1.12*R.dLedMax);

            xMin = max( ...
                D.tLedSlope(1), ...
                R.tLedStart-cfg.ledPlotMargin);

            xMax = min( ...
                D.tLedSlope(end), ...
                R.tLedEnd+cfg.ledPlotMargin);

            if (xMax > xMin)

                plot( ...
                    [xMin xMax], ...
                    [R.thresholdLed R.thresholdLed], ...
                    '--');

                plot( ...
                    [R.tLedStart R.tLedStart], ...
                    [0 yTop], ...
                    '--');

                plot( ...
                    R.tLedPeak, ...
                    R.dLedMax, ...
                    'o');

                plot( ...
                    [R.tLedEnd R.tLedEnd], ...
                    [0 yTop], ...
                    '--');

                xlim([xMin xMax]);

            endif

        endif

    endif

    grid on;

    xlabel('Tempo [s]');
    ylabel('dV_{NLED}/dt [V/s]');

    title(sprintf( ...
        ['GX-TXT-v3 - Transizione LED dalla derivata ' ...
         '- media %.0f ms, finestra %.0f ms'], ...
        cfg.ledBin_ms, ...
        cfg.ledSlopeWindow_ms));

    print( ...
        f, ...
        fullfile(testDir,'05_derivata_led_transizione.png'), ...
        '-dpng', ...
        sprintf('-r%d',pngDPI));

    close(f);

    % -----------------------------------------------------------
    % 6 - CARICA HOLD
    % -----------------------------------------------------------

    fprintf('    [6/9] Carica HOLD...\n');

    f = figure('visible','off');

    plot(D.t,D.vHold,'-');

    grid on;

    xlabel('Tempo [s]');
    ylabel('V_{HOLD} [V]');

    title('GX-TXT-v3 - Carica rapida del nodo HOLD');

    x1 = max( ...
        D.t(1), ...
        -cfg.holdFastPre_ms/1000);

    x2 = min( ...
        D.t(end), ...
        max(0,R.tHoldMax) + ...
        cfg.holdFastPost_ms/1000);

    if (x2 <= x1)
        x2 = min(D.t(end), x1+0.020);
    endif

    if (x2 > x1)
        xlim([x1 x2]);
    endif

    print( ...
        f, ...
        fullfile(testDir,'06_hold_carica_zoom.png'), ...
        '-dpng', ...
        sprintf('-r%d',pngDPI));

    close(f);

    % -----------------------------------------------------------
    % 7 - DERIVATA CARICA HOLD
    % -----------------------------------------------------------

    fprintf('    [7/9] Derivata carica HOLD...\n');

    f = figure('visible','off');

    if (!isempty(D.tHoldFastSlope))

        plot( ...
            D.tHoldFastSlope, ...
            D.dVholdFast_dt, ...
            '-');

        x1 = max( ...
            D.tHoldFastSlope(1), ...
            -cfg.holdFastPre_ms/1000);

        x2 = min( ...
            D.tHoldFastSlope(end), ...
            max(0,R.tHoldMax) + ...
            cfg.holdFastPost_ms/1000);

        if (x2 > x1)
            xlim([x1 x2]);
        endif

    endif

    grid on;

    xlabel('Tempo [s]');
    ylabel('dV_{HOLD}/dt [V/s]');

    title('GX-TXT-v3 - Derivata di HOLD durante la carica');

    print( ...
        f, ...
        fullfile(testDir,'07_hold_derivata_carica.png'), ...
        '-dpng', ...
        sprintf('-r%d',pngDPI));

    close(f);

    % -----------------------------------------------------------
    % 8 - DERIVATA SCARICA HOLD
    % -----------------------------------------------------------

    fprintf('    [8/9] Derivata scarica HOLD...\n');

    f = figure('visible','off');

    if (!isempty(D.tHoldSlowSlope))

        idx = find( ...
            D.tHoldSlowSlope > ...
            max(0,R.tHoldMax)+cfg.holdDischargeDelay);

        if (!isempty(idx))

            tDis = D.tHoldSlowSlope(idx);
            dDis = D.dVholdSlow_dt(idx);

            plot(tDis,dDis,'-');
            hold on;

            if (R.holdFitValid)

                dFit = ...
                    -(R.AHold/R.tauHold) .* ...
                    exp(-(tDis-R.tHoldMax)./R.tauHold);

                plot(tDis,dFit,'--');

            endif

        endif

    endif

    grid on;

    xlabel('Tempo [s]');
    ylabel('dV_{HOLD}/dt [V/s]');

    title('GX-TXT-v3 - Velocita di scarica del nodo HOLD');

    print( ...
        f, ...
        fullfile(testDir,'08_hold_derivata_scarica.png'), ...
        '-dpng', ...
        sprintf('-r%d',pngDPI));

    close(f);

    % -----------------------------------------------------------
    % 9 - I_LED vs HOLD
    % -----------------------------------------------------------

    fprintf('    [9/9] I_LED vs HOLD...\n');

    f = figure('visible','off');

    idxStart = max(1,D.idxHoldMax);

    plot( ...
        D.vHold(idxStart:end), ...
        D.iLed_mA(idxStart:end), ...
        '-');

    hold on;

    if (R.ledTransitionValid)

        plot(R.VholdLedStart,R.IledStart,'o');
        plot(R.VholdLedPeak,R.IledPeak,'o');
        plot(R.VholdLedEnd,R.IledEnd,'o');

    endif

    grid on;

    xlabel('V_{HOLD} [V]');
    ylabel('I_{LED} [mA]');

    title('GX-TXT-v3 - Corrente LED in funzione di V_{HOLD}');

    print( ...
        f, ...
        fullfile(testDir,'09_corrente_led_vs_hold.png'), ...
        '-dpng', ...
        sprintf('-r%d',pngDPI));

    close(f);

endfunction


% ==============================================================
% SCRITTURA CSV
% ==============================================================

function scrivi_riga_csv(fid, valori)

    for k = 1:length(valori)

        v = valori{k};

        if (ischar(v))

            v = strrep(v, '"', '""');
            s = ['"' v '"'];

        elseif (islogical(v))

            s = sprintf('%d', v);

        elseif (isnumeric(v))

            if (isempty(v))

                s = '';

            elseif (isnan(v))

                s = 'NaN';

            elseif (isinf(v))

                if (v > 0)
                    s = 'Inf';
                else
                    s = '-Inf';
                endif

            else

                s = sprintf('%.12g', v);

            endif

        else

            s = '';

        endif

        fprintf(fid,'%s',s);

        if (k < length(valori))
            fprintf(fid,',');
        else
            fprintf(fid,'\n');
        endif

    endfor

endfunction
