Surveiller les cycles d'horloge pour le code sur Arduino / AVR?

11

Est-il possible de surveiller un bloc de code et de déterminer le nombre de cycles d'horloge du processeur que le code a pris sur un processeur atmel Arduino et / ou AVR? ou, dois-je plutôt surveiller les microsecondes passées avant et après l'exécution du code? Remarque: je ne m'inquiète pas autant du temps réel (comme dans, combien de secondes réelles se sont écoulées) que de "combien de cycles d'horloge ce code nécessite-t-il de la part du CPU"

La solution actuelle que je peux trouver est de time.c:

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

câblage.c ajoute:

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

Par ce compte, je pouvais calculer les cycles d'horloge passés en surveillant les microsecondes passées, puis les transmettre à microsecondsToClockCycles (). Ma question est, existe-t-il une meilleure façon?

sidenote: existe-t-il de bonnes ressources pour le contrôle des performances de l'AVR. lmgtfy.com et diverses recherches sur les forums ne fournissent aucun résultat évident, autre que l'exploration des minuteries

Merci

cyphunk
la source

Réponses:

6

La méthode la plus simple consiste à faire en sorte que votre code tire une broche avant d'exécuter le code que vous souhaitez chronométrer, et à le tirer bas une fois qu'il a fini de faire quoi que ce soit. Ensuite, faites la boucle de code (ou utilisez un oscilloscope numérique avec mémoire en mode de prise de vue unique) et visez puis épinglez. La longueur de l'impulsion vous indique combien de temps il a fallu pour exécuter le morceau de code plus un cycle d'horloge pour changer l'état de la broche (je pense qu'il faut un cycle, pas sûr à 100%).

Dago
la source
Merci. Oui, je vois que c'est probablement la solution la plus précise. Je ronge toujours le code qui me donnerait au moins même une analyse générale de l'utilisation du cycle à l'intérieur du code. Je vais l'utiliser pour créer des outils de test et ce serait bien de définir mes limites supérieures pour des paramètres tels que le temps d'exécution maximal autorisé en fonction de l'efficacité avec laquelle le code + tout ce qui s'y rapporte s'exécute sur le processeur Atmel actuel dans utilisation
cyphunk
4

Qu'entendez-vous par «moniteur»?

Il ne devrait pas être difficile de compter les cycles d'horloge pour l'AVR pour les petits morceaux de code d'assemblage.

Vous pouvez également définir un port avant l'exécution du code et le réinitialiser par la suite, et le surveiller avec un analyseur logique ou un oszilloscope pour obtenir le timing.

Et vous pouvez également lire l'heure à partir d'une minuterie à fonctionnement rapide, comme vous le dites.

starblue
la source
Par moniteur, je veux dire déterminer le nombre de cycles utilisés par le code. quelque chose comme (remarque, le formatage du code sera probablement aplati par le moteur de commentaire): clocks = startCountingAtmegaClocks (); for ... {for ... {digitalRead ...}} Serial.print ("nombre de cycles utilisés:"); Serial.print (currentCountingAtmegaClocks () - horloges, DEC);
cyphunk
Mais oui, votre réponse est ce que j'ai supposé être mes options. Je suppose, je suppose que si je peux calculer les cycles d'horloge que l'assembleur prendrait à la main, quelqu'un a peut-être déjà écrit du bon code pour le faire par programmation
cyphunk
3

Ceci est un exemple pour Arduino utilisant la fonction clockCyclesPerMicrosecond () pour calculer les horloges qui sont passées. Ce code attendra 4 secondes, puis imprimera le temps écoulé depuis le début du programme. Les 3 valeurs de gauche sont le temps total (microsecondes, millisecondes, cycles d'horloge totaux) et les 3 plus à droite sont les temps écoulés:

Production:

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

Je suis sûr qu'il y a une explication raisonnable pourquoi les premières boucles avaient également des cycles d'horloge écoulés plus courts que la plupart et pourquoi toutes les autres boucles basculent entre deux longueurs de cycles d'horloge.

Code:

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: si vous supprimez le délai de 4 secondes, vous commencerez à voir les effets de Serial.print () beaucoup plus clairement. Remarque, ici 2 exécutions sont comparées. Je n'ai inclus que 4 échantillons les uns à côté des autres à partir de leurs journaux respectifs.

Exécuter 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

Exécuter 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

Le temps écoulé augmente par rapport au temps d'exécution total. Après une seconde, les horloges augmentent en moyenne de 40k à 44k. Cela se produit régulièrement quelques millisecondes après 1 seconde et les horloges écoulées restent autour de 44k pendant au moins les 10 secondes suivantes (je ne l'ai pas testé plus loin). C'est pourquoi la surveillance est utile ou nécessaire. Peut-être que la baisse d'efficacité a à voir avec la configuration ou les bugs en série? Ou peut-être que le code n'utilise pas la mémoire correctement et a une fuite qui affecte les performances, etc.

cyphunk
la source
de nombreuses années plus tard, je voudrais toujours quelque chose qui montre les horloges avec plus de précision avec du code (comme apposé sur un oscilloscope). À J'essaie de déterminer le nombre de cycles d'horloge requis pour un digitalWrite () dans les deux 16MHZ et 8MHZ. En 16 MHz, j'obtiens 8us / 64clk. Mais en 8MHZ j'obtiens 0us / 0clk.
cyphunk
1

Étant donné que chaque ligne de code ajoutée à votre source aura un impact sur les performances et pourrait modifier les optimisations appliquées. Les modifications doivent être le minimum requis pour effectuer la tâche.

Je viens de trouver un plugin Atmel Studio appelé "Annotated Assembly File Debugger". http://www.atmel.com/webdoc/aafdebugger/pr01.html Il semble que passer à travers le langage d'assemblage généré alors que probablement fastidieux va vous montrer exactement ce qui se passe. Vous devrez peut-être encore décoder le nombre de cycles qu'il faut pour chaque instruction, mais cela serait beaucoup plus proche que certaines des autres options publiées.

Pour ceux qui ne connaissent pas dans le dossier de sortie de votre projet est un fichier avec une extension LSS. Ce fichier contient tout votre code source d'origine sous forme de commentaires et sous chaque ligne se trouve le langage d'assemblage qui a été généré sur la base de cette ligne de code. La génération du fichier LSS peut être désactivée, vérifiez donc le paramètre suivant.

Propriétés du projet | Chaîne d'outils | AVR / GNU Commun | Fichiers de sortie

Case à cocher ".lss (Générer un fichier lss)

James
la source
1

Vous pouvez utiliser l'un des minuteurs intégrés. Obtenez tout configuré pour prescaller = 1 et TCNT = 0 avant le bloc. Activez ensuite le temporisateur sur la ligne avant le bloc et désactivez-le sur la ligne après le bloc. Le TCNT conservera maintenant le nombre de cycles que le bloc a pris, moins les cycles fixes pour le code d'activation et de désactivation.

Notez que le TNCT débordera après 65535 cycles d'horloge sur une minuterie 16 bits. Vous pouvez utiliser l'indicateur de débordement pour doubler la durée d'exécution. Si vous avez encore besoin de plus de temps, vous pouvez utiliser un prédéfinisseur, mais vous obtiendrez moins de résolution.

bigjosh
la source