Go Down

Topic: Mesure de temps d'une instruction (Read 405 times) previous topic - next topic

iutgeiisoissons

May 15, 2018, 02:24 pm Last Edit: May 16, 2018, 05:28 pm by iutgeiisoissons Reason: ajout d'une carte Arduino DUE
Dans certains cas, nous sommes amenés à devoir réguler un système. On utilise dans ce cas un régulateur PID (Proportionnel Intégrale Dérivé)
Un PID est constitué d'additions, de soustractions, et de multiplication, ce qui nous amène d'abord à traiter en premier lieu les calculs de bases.




Vous avez ci-dessus un tableau récapitulant le temps que prend une carte arduino pour traiter une instruction. Les instructions sont basiques sauf pour le PID. Pour trouver ces valeurs, nous avons défini notre sortie LED sur la pin 13. Nous l'avons mise à 1 avant le calcul et remise à 0 après. On mesure ensuite le temps lorsque la sortie est à 1 en simulation sur ISIS ou en réelle grâce à un oscilloscope. On écrit sur un terminal pour vérifier si le calcul est correct.

Le programme de base est le suivant : la ligne de calcul est à changer ainsi que la déclaration en int ou float.

Code: [Select]
#define LED 13
int a = 10, b=5,c; //ligne de déclaration
void setup() {
 // put your setup code here, to run once:
pinMode(LED,OUTPUT);
Serial.begin(9600);
}

void loop() {
 // put your main code here, to run repeatedly:
  digitalWrite(LED, 1);
  c=a+b; //ligne de calcul
  digitalWrite(LED, 0);
  Serial.print(c);
  Serial.print("\n\r");
}



Soustraction
Code: [Select]
c=a-b

Multiplication
Code: [Select]
c=a*b


Division
Code: [Select]
c=a/b

/!\ si en int, la valeur sera arrondi a l'entier près.

Exposant
Code: [Select]

pow(a,b)



/!\ cette commande ne fonctionne correctement que si vous utilisez des floats. Si vous utilisez des entiers, il faudra « caster » le calcul c'est-à-dire forcer le résultat à être en float. On écrira donc
Code: [Select]
Pow(float(a),float(b));
Le cast ajoute du temps au calcul et il est donc plus rapide d'utiliser des floats.

PID
Nous n'utilisons que des floats pour le PID


Code: [Select]
#define LED 13
float c;

const float kp = 2;
const float ki = 1;
const float kd = 1;
byte PWM=0 ;
float erreurP = 0;
float erreur = 0;
float Integral = 0;
float derive = 0;
float Proportionnel = 0;
float output = 0;
float Setpoint=0;

void setup() {
 // put your setup code here, to run once:
pinMode(LED,OUTPUT);
Serial.begin(9600);
}

void loop() {
erreurP=erreur;                 //erreur precedente ou erreur erreur*Z^Ts  pour faire le calcul de la derive
erreur=Setpoint-a;
Proportionnel=kp*erreur;
Integral= Integral+erreur*ki;
if (Integral>=255) Integral=255;
derive=(erreur-erreurP)*kd;             //derive=(erreur-erreur*Z^Ts)/Ts   avec Ts=Tsampleur

output=Proportionnel+Integral+derive;   //ce n'est pas la peine de divisée par Ts car si Ts est petit cela donne des valeurs enormes
if (output>=255) {output=255;}
if (output<=0) {output=0;}
PWM=output;
analogWrite(PWM10,PWM);   //PWM
  digitalWrite(LED, 0);
  Serial.print(c);
  Serial.print("\t");
  Serial.println(";");
}



On peut remarquer que les temps de calculs sont plus rapides pour une arduino nano que pour une Mega. Sauf que la carte Méga possède plus de ports. Si vous cherchez la vitesse d'exécution, vous prendrez une nano, si vous avez besoin de beaucoup de pin, vous prendrez une méga.

A savoir que ce que nous avons fait peut être fait pour n'importe quelle instruction ou série d'instructions que vous voulez (pas forcément des calculs).

edit : Concernant le carte Arduino DUE, j'ai rencontré quelques problèmes : quelques difficultés à compiler mon programme à cause d'un fichier du logiciel (https://arduino.stackexchange.com/questions/24143/unable-to-upload-to-arduino-due-using-version-1-6-8-of-sam-tools-bossac-exe-thr aller sur ce site (en anglais) pour voir comment résoudre le problème si vous l'avez).
Cette carte est plus rapide que les autres comme on peut le voir (en écrivant en langage C/C++ comme j'ai pu voir les commentaires plus bas). Donc pour ceux qui ne veulent pas mettre les mains dans l'assembleur et qui veulent juste écrire leur ligne de code.

bilbo83

Bonjour,

Vous tenez compte de la durée du "digitalWrite(LED, 0)" ?

68tjs

digitalWrite(LED, 0) --> Environ 60 cycles horloge système.

Perso pour mesurer le temps d'une instruction j'utilise le compteur d'un timer. (registre TCNTx)
Selon la valeur du temps à mesurer on peut ajuster la valeur du pré-diviseur d'horloge système.

Une fois le timer configuré l'utilisation est simple.
TCNTx= 0 ;  // Raz du compteur pour ne pas avoir à gérer les débordement
instruction();
Mesure = TCNTx;   // Mesure contient le nombre de cycles de l'horloge du timer

La précision maximale est obtenue avec un pré-diviseur de 1, le résultat est directement en nombre de cycle horloge système.

_pepe_

#3
May 15, 2018, 03:59 pm Last Edit: May 15, 2018, 04:07 pm by _pepe_
Bonjour

La mesure du temps d'exécution d'opérations isolées telle que présentée ici pose un certain nombre de problèmes.


Tout d'abord, parce que l'opération testée est écrite en C/C++, à partir de ce code source le compilateur génère un code machine qui peut varier au gré des optimisations qu'il parvient à réaliser, du fait notamment du contexte de ces opérations (par exemple, selon que les données finalement traitées sont des constantes, proviennent de la mémoire ou sont déjà présentes dans des registres). Ce code machine peut d'ailleurs dépendre de la version de la chaîne de compilation et de celle des bibliothèques utilisées.

Et pour un code machine fixé, le temps d'exécution peut varier en fonction des valeurs traitées, notamment du fait de la présence de sauts conditionnels.


Par ailleurs, dans le code proposé ci-dessus, des traitements étrangers à l'opération testée interviennent dans le temps d'exécution mesuré.

D'une part, comme l'a suggéré bilbo83, la durée d'exécution des instructions digitalWrite() est notable, et sa valeur exacte n'est pas connue a priori.

D'autre part, le temps mesuré est susceptible d'être rallongé par le traitement des interruptions matérielles. En l'occurrence, ici l'interruption de l'interface série est active.


Concernant l'écart de vitesse constaté entre l'Arduino Mega et l'Arduino Nano, il dépend juste des différences de compilation du code. En effet, comme les cœurs RISC des micro-contrôleurs qui les animent sont identiques, une même opération prend exactement le même temps sur les deux cartes cadencées à 16 MHz. On ne peut donc rien en conclure a priori au sujet de la rapidité d'exécution de l'un ou de l'autre.

NB: sous d'anciennes versions de l'IDE, pour un code source donné (mais un code machine généré différent), j'ai même pu constater des cas où l'Arduino Mega était plus rapide que l'Arduino Nano.

_pepe_

#4
May 15, 2018, 04:52 pm Last Edit: May 18, 2018, 10:42 am by _pepe_
Pour donner un exemple, voici le code généré pour l'addition (c=a+b) de deux entiers (type int) issus de la mémoire dont le résultat est restocké en mémoire :

; chargement de a
  lds  r24, 0x0102  ; 2 cycles
  lds  r25, 0x0103  ; 2 cycles
; chargement de b
  lds  r18, 0x0100  ; 2 cycles
  lds  r19, 0x0101  ; 2 cycles
; addition a+b
  add r24, r18      ; 1 cycle
  adc r25, r19      ; 1 cycle
; stockage du résultat dans c
  sts  0x0105, r25  ; 2 cycles
  sts  0x0104, r24  ; 2 cycles

L'exécution dure 14 cycles, soit 0,875 µs à 16 MHz. Ce temps est le même sur le Mega et sur le Nano.

Mais selon ce qui précède et ce qui suit, il pourrait être nécessaire de sauvegarder sur la pile (avec push et pop) le contenu des quatre registres, ce qui rajouterait 16 cycles, soit un total de 30 cycles ou 1,875 µs.

À l'inverse si les données sont déjà présentes dans les registres avant l'opération et si le résultat est utilisé depuis les registres, alors l'opération se résume aux deux instructions d'addition (add et adc) et ne prend que 2 cycles ou 0,125 µs.

Mieux, si l'une des données de départ est reconnue comme étant une constante sur un octet et si l'autre est contenue dans un double registre (i.e. dans l'une des quatre dernières paires de registres), alors l'addition peut être immédiate et réalisée en une fois :

; addition (r25:r24)+234
  adiw r24, 234     ; 1 cycle

L'addition ne dure alors plus qu'un cycle, soit 0,0625 µs.


En résumé, sur une opération banale correspondant à la même portion de code source (c=a+b), le temps nécessaire à l'exécution peut varier entre 0,0625 µs et 1,875 µs.

Il n'est donc pas forcément pertinent de vouloir mesurer le temps d'une opération sortie de son contexte.

De plus, les temps d'exécution résultant du processus de mesure ne doivent pas être pris en compte. Ici il est anormal de présenter un temps de 8,56 µs pour une addition sur deux entiers alors qu'on sait que cette opération dure quatre fois moins longtemps, voire mieux.

Go Up