La fonction millis
s'exécuterait sur une période de 100+ microsecondes ou moins. Existe-t-il un moyen fiable de mesurer le temps pris par un seul appel millis?
Une approche qui vient à l'esprit utilise micros
, cependant, un appel à micros
inclut également le temps pris par l'appel de fonction micros
lui-même, donc selon la durée du micros, la mesure millis
peut être désactivée.
Je dois trouver cela car une application sur laquelle je travaille nécessite des mesures de temps précises pour chaque étape prise dans le code, y compris millis
.
miilis
.Réponses:
Si vous voulez savoir exactement combien de temps quelque chose prendra, il n'y a qu'une seule solution: regardez le démontage!
En commençant par le code minimal:
Ce code compilé puis alimenté
avr-objdump -S
produit un démontage documenté. Voici les extraits intéressants:void loop()
produit:Qui est un appel de fonction (
call
), quatre copies (qui copient chacun des octets dans lauint32_t
valeur de retour demillis()
(notez que les documents Arduino appellent cela unlong
, mais ils sont incorrects pour ne pas spécifier explicitement les tailles de variable)), et enfin le retour de fonction.call
nécessite 4 cycles d'horloge, et chacunsts
nécessite 2 cycles d'horloge, nous avons donc un minimum de 12 cycles d'horloge juste pour la surcharge d'appel de fonction.Maintenant, regardons le démontage de la
<millis>
fonction, qui se trouve à0x14e
:Comme vous pouvez le voir, la
millis()
fonction est assez simple:in
enregistre les paramètres du registre d'interruption (1 cycle)cli
désactive les interruptions (1 cycle)lds
copier l'un des 4 octets de la valeur actuelle du compteur milli dans un registre temporaire (2 cycles d'horloge)lds
Octet 2 (2 cycles d'horloge)lds
Octet 3 (2 cycles d'horloge)lds
Octet 4 (2 cycles d'horloge)out
restaurer les paramètres d'interruption (1 cycle d'horloge)movw
lecture aléatoire des registres (1 cycle d'horloge)movw
et encore (1 cycle d'horloge)ret
retour de sous-programme (4 cycles)Donc, si nous les additionnons tous, nous avons un total de 17 cycles d'horloge dans la
millis()
fonction elle-même, plus une surcharge d'appel de 12, pour un total de 29 cycles d'horloge.En supposant une fréquence d'horloge de 16 Mhz (la plupart des Arduinos), chaque cycle d'horloge est en
1 / 16e6
secondes, ou 0,0000000625 seconde, ce qui correspond à 62,5 nanosecondes. 62,5 ns * 29 = 1,812 microsecondes.Par conséquent, le temps d'exécution total pour un seul
millis()
appel sur la plupart des Arduinos sera de 1,812 microsecondes .Référence d'assemblage AVR
En remarque, il y a de la place pour l'optimisation ici! Si vous mettez à jour la
unsigned long millis(){}
définition de la fonctioninline unsigned long millis(){}
, vous supprimez la surcharge d'appel (au prix d' une taille de code légèrement plus grande). De plus, il semble que le compilateur effectue deux mouvements inutiles (les deuxmovw
appels, mais je ne l'ai pas examiné de si près).Vraiment, étant donné que la surcharge de l'appel de fonction est de 5 instructions et que le contenu réel de la
millis()
fonction n'est que de 6 instructions, je pense que lamillis()
fonction devrait vraiment êtreinline
par défaut, mais la base de code Arduino est plutôt mal optimisée.Voici le désassemblage complet pour toute personne intéressée:
la source
sts
ne doivent pas être comptés comme des frais généraux d'appel: c'est le coût de stockage du résultat dans une variable volatile, ce que vous ne feriez normalement pas. 2) Sur mon système (Arduino 1.0.5, gcc 4.8.2), je n'ai pas lemovw
s. Le coût de l'appelmillis()
est alors: 4 cycles de surcharge d'appel + 15 cycles enmillis()
soi = 19 cycles au total (≈ 1,188 µs @ 16 MHz).x
est unuint16_t
. Cela devrait être de 2 exemplaires au maximum si c'est la cause. Quoi qu'il en soit, la question est de savoir combien de tempsmillis()
prend une fois utilisé , pas lorsqu'il est appelé tout en ignorant le résultat. Étant donné que toute utilisation pratique impliquera de faire quelque chose avec le résultat, j'ai forcé le résultat à être stocké viavolatile
. Normalement, le même effet serait obtenu par l'utilisation ultérieure de la variable qui est définie sur la valeur de retour de l'appel, mais je ne voulais pas que cet appel supplémentaire prenne de la place dans la réponse.uint16_t
dans la source ne correspond pas à l'assembly (4 octets stockés dans la RAM). Vous avez probablement posté la source et le démontage de deux versions différentes.Écrivez une esquisse qui millis mille fois, non pas en faisant une boucle, mais en faisant un copier-coller. Mesurez cela et comparez-le au temps réel prévu. Rappelez-vous que les résultats peuvent varier selon les différentes versions de l'IDE (et son compilateur en particulier).
Une autre option consiste à basculer une broche d'E / S avant et après l'appel millis, puis de mesurer le temps pour une très petite valeur et une valeur un peu plus grande. Comparez les horaires mesurés et calculez les frais généraux.
Le moyen le plus précis est de jeter un œil à la liste de démontage, le code généré. Mais ce n'est pas pour les faibles de cœur. Vous devrez étudier attentivement la fiche technique combien de temps chaque cycle d'instruction prend.
la source
millis()
appels?delay
, vous avez raison. Mais l'idée reste la même, vous pouvez chronométrer un grand nombre d'appels et les calculer en moyenne. Désactiver les interruptions dans le monde n'est peut-être pas une très bonne idée; o)J'appelle en second lieu des millis à plusieurs reprises, puis je compare le réel au prévu.
Les frais généraux seront minimes, mais leur importance diminuera à mesure que vous appelez millis ().
Si vous regardez
Vous pouvez voir que millis () est très petit avec seulement 4 instructions
(cli is simply # define cli() \__asm__ \__volatile__ ("cli" ::))
et un retour.Je l'appellerais environ 10 millions de fois en utilisant une boucle FOR qui a un volatile comme conditionnel. Le mot-clé volatile empêchera le compilateur de tenter toute optimisation sur la boucle elle-même.
Je ne garantis pas ce qui suit pour être syntaxiquement parfait ..
ma conjecture est que cela prend ~ 900 ms ou environ 56 us par appel à millis. (Je n'ai pas de guichet automatique Aruduino à portée de main.
la source
int temp1,temp2;
pourvolatile int temp1,temp2;
empêcher le compilateur de les optimiser potentiellement.