Monitorare i cicli di clock per il codice su arduino / AVR?


11

È possibile monitorare un blocco di codice e determinare il numero di cicli di clock del processore che il codice ha eseguito su un processore Atmel Arduino e / o AVR? o, dovrei piuttosto monitorare i microsecondi passati prima e dopo l'esecuzione del codice? Nota: non mi occupo del tempo reale (come in, quanti secondi reali sono passati) tanto quanto lo sono in "quanti cicli di clock richiede questo codice dalla CPU"

La soluzione attuale che posso trovare è di time.c:

#define clockCyclesPerMicrosecond() ( F_CPU / 1000000L )
#define clockCyclesToMicroseconds(a) ( (a) / clockCyclesPerMicrosecond() )

cablaggio.c aggiunge:

#define microsecondsToClockCycles(a) ( (a) * clockCyclesPerMicrosecond() )

Con questo account ho potuto calcolare gli stili di clock passati monitorando i microsecondi passati e quindi passarli a microsecondiToClockCycles (). La mia domanda è: esiste un modo migliore?

sidenote: ci sono buone risorse per il monitoraggio delle prestazioni dell'AVR. lmgtfy.com e le varie ricerche nei forum non forniscono risultati evidenti, a parte l'esplorazione dei timer

Grazie

Risposte:


6

Il metodo più semplice è fare in modo che il tuo codice tira su un po 'di pin prima che esegua il codice che vuoi tempo, e lo abbassa dopo che ha finito di fare qualsiasi cosa. Quindi crea il loop di codice (o usa l'oscilloscopio digitale con memoria in modalità single-shot) e basta l'oscilloscopio e poi il pin. La lunghezza dell'impulso indica il tempo impiegato per eseguire il pezzo di codice più un ciclo di clock dalla modifica dello stato del pin (penso che richieda un ciclo, non sicuro al 100%).


Grazie. Sì, vedo che questa è probabilmente la soluzione più accurata. Sto ancora buttando via il codice che mi darebbe almeno anche l'analisi dell'uso del ciclo generale all'interno del codice. Lo userò per costruire alcuni strumenti di test e sarebbe bello impostare i miei limiti superiori per parametri come il tempo di esecuzione massimo consentito in base all'efficienza del codice + tutto ciò che è in esecuzione sulla CPU Atmel corrente in uso
cyphunk il

4

Cosa intendi con "monitor"?

Non dovrebbe essere difficile contare i cicli di clock per AVR per piccoli pezzi di codice assembly.

È inoltre possibile impostare una porta prima dell'esecuzione del codice e reimpostarla in seguito, e monitorarla con un analizzatore logico o un oszilloscopio per ottenere i tempi.

E potresti anche leggere l'ora da un timer ad alta velocità, come dici tu.


Per monitor intendo determinare il numero di cicli utilizzati dal codice. qualcosa del tipo (nota, la formattazione del codice sarà probabilmente appiattita dal motore di commento): clocks = startCountingAtmegaClocks (); for ... {for ... {digitalRead ...}} Serial.print ("numero di cicli usati:"); Serial.print (currentCountingAtmegaClocks () - clocks, DEC);
Cyphunk,

Ma sì, la tua risposta è quella che ho ipotizzato siano le mie opzioni. Immagino, suppongo che se potessi calcolare i cicli di clock che l'assemblatore prenderebbe a mano che qualcuno forse ha già scritto un bel codice per farlo programmaticamente
cyphunk

3

Questo è un esempio di Arduino che utilizza la funzione clockCyclesPerMicrosecond () per calcolare gli orologi che sono passati. Questo codice attenderà 4 secondi, quindi stampa il tempo trascorso dall'inizio del programma. I valori di sinistra 3 sono il tempo totale (microsecondi, millisecondi, cicli di clock totali) e la maggior parte dei 3 di destra sono tempi trascorsi:

Produzione:

clocks for 1us:16
runtime us, ms, ck :: elapsed tme us, ms ck
4003236 4002	64051776	::	4003236	4002	64051760
8006668 8006	128106688	::	4003432	4004	64054912
12010508    12010	192168128	::	4003840	4004	64061440
16014348    16014	256229568	::	4003840	4004	64061440
20018188    20018	320291008	::	4003840	4004	64061440
24022028    24022	384352448	::	4003840	4004	64061440
28026892    28026	448430272	::	4004864	4004	64077824
32030732    32030	512491712	::	4003840	4004	64061440
36034572    36034	576553152	::	4003840	4004	64061440
40038412    40038	640614592	::	4003840	4004	64061440
44042252    44042	704676032	::	4003840	4004	64061440
48046092    48046	768737472	::	4003840	4004	64061440
52050956    52050	832815296	::	4004864	4004	64077824

Sono sicuro che ci sia una spiegazione ragionevole del perché anche i primi loop hanno cicli di clock più brevi rispetto alla maggior parte e perché tutti gli altri loop si alternano tra due lunghezze di cicli di clock.

Codice:

unsigned long us, ms, ck;
unsigned long _us, _ms, _ck;
unsigned long __us, __ms, __ck;
void setup() {
        Serial.begin(9600);
}
boolean firstloop=1;
void loop() { 
        delay(4000);

        if (firstloop) {
                Serial.print("clocks for 1us:");
                ck=microsecondsToClockCycles(1);
                Serial.println(ck,DEC);
                firstloop--;
                Serial.println("runtime us, ms, ck :: elapsed tme us, ms ck");
        }

        _us=us;
        _ms=ms;
        _ck=ck;

        us=micros(); // us since program start
        ms=millis();
        //ms=us/1000;
        ck=microsecondsToClockCycles(us);
        Serial.print(us,DEC);
        Serial.print("\t");
        Serial.print(ms,DEC);
        Serial.print("\t");
        Serial.print(ck,DEC);     
        Serial.print("\t::\t");

        __us = us - _us;
        __ms = ms - _ms;
        __ck = ck - _ck;
        Serial.print(__us,DEC);
        Serial.print("\t");
        Serial.print(__ms,DEC);
        Serial.print("\t");
        Serial.println(__ck,DEC);     

}

Sidenote: se rimuovi il ritardo di 4 secondi inizierai a vedere gli effetti di Serial.print () molto più chiaramente. Nota, qui vengono confrontate 2 corse. Ho incluso solo 4 campioni uno vicino all'altro dai rispettivi registri.

Esegui 1:

5000604 5000	80009664	::	2516	2	40256
6001424 6001	96022784	::	2520	3	40320
7002184 7002	112034944	::	2600	3	41600
8001292 8001	128020672	::	2600	3	41600

Esegui 2:

5002460 5002	80039360	::	2524	3	40384
6000728 6000	96011648	::	2520	2	40320
7001452 7001	112023232	::	2600	3	41600
8000552 8000	128008832	::	2604	3	41664

Il tempo trascorso aumenta rispetto al tempo di esecuzione totale. Dopo che è trascorso un secondo, gli orologi aumentano in media da 40k a 44k. Ciò accade in modo coerente alcuni millisecondi dopo 1 secondo e gli orologi trascorsi rimangono circa 44k per almeno i successivi 10 secondi (non l'ho ancora testato ulteriormente). Ecco perché il monitoraggio è utile o necessario. Forse la ridotta efficienza ha a che fare con la configurazione o i bug in seriale? O forse il codice non utilizza correttamente la memoria e presenta una perdita che influisce sulle prestazioni, ecc.


molti anni dopo, vorrei ancora qualcosa che mostra gli orologi in modo più accurato con il codice (come apposto a un oscilloscopio). Sto provando a determinare il numero di cicli di clock richiesti per un digitalWrite () sia in 16MHZ che in 8MHZ. In 16MHZ ottengo 8us / 64clk. Ma in 8MHZ ottengo 0us / 0clk.
cyphunk,

1

Poiché ogni riga di codice aggiunta alla tua sorgente avrà un impatto sulle prestazioni e potrebbe cambiare le ottimizzazioni applicate. Le modifiche dovrebbero essere il minimo richiesto per eseguire l'attività.

Ho appena trovato un plug-in Atmel Studio chiamato "Debugger file di assembly annotato". http://www.atmel.com/webdoc/aafdebugger/pr01.html Sembra che passi dall'attuale linguaggio assembly generato mentre probabilmente noioso ti mostrerà esattamente cosa sta succedendo. Potrebbe essere necessario decodificare il numero di cicli necessari per ciascuna istruzione, ma sarebbe molto più vicino di alcune delle altre opzioni pubblicate.

Per coloro che non conoscono nella cartella Output del progetto è presente un file con estensione LSS. Questo file contiene tutto il codice sorgente originale come commenti e sotto ogni riga è il linguaggio assembly che è stato generato in base a quella riga di codice. La generazione del file LSS può essere disattivata, quindi controlla le seguenti impostazioni.

Proprietà del progetto | Toolchain | AVR / GNU Comune | OutputFiles

Checkbox ".lss (Genera file lss)


1

È possibile utilizzare uno dei timer integrati. Ottieni tutto impostato per prescaller = 1 e TCNT = 0 prima del blocco. Quindi abilitare il timer sulla linea prima del blocco e disabilitarlo sulla linea dopo il blocco. Il TCNT ora manterrà il numero di cicli necessari al blocco, meno i cicli fissi per il codice di abilitazione e disabilitazione.

Si noti che il TNCT traboccerà dopo 65535 cicli di clock su un timer a 16 bit. È possibile utilizzare il flag di overflow per raddoppiare il tempo di esecuzione. Se hai ancora bisogno di più tempo, puoi usare un prescaler, ma otterrai una risoluzione inferiore.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.