Accélérer votre Arduino

Accueil/Accélérer votre Arduino
Accélérer votre Arduino 2016-12-20T11:38:30+00:00

Project Description

S’il vous ait déjà arrivé de vouloir changer l’état de plusieurs pins de l’Arduino en même temps, alors vous devez savoir que cela n’est pas possible avec du code Arduino classique, en effet, il existe un décalage de quelques microsecondes entre le changement d’état de deux pins par exemple, ce qui peut être embêtant dans certains cas. De plus, il faut savoir que sur une Arduino UNO, changer l’état d’un pin (HIGH ou LOW) prend environ 4us, cela peut paraître peu mais en réalité, pour certaines applications, ce changement d’état est beaucoup trop long. Aujourd’hui, nous allons voir comment faire diminuer ce temps de latence par 4 et comment faire changer l’état de plusieurs pins en même temps.

Attention cependant, il vous faudra d’abord maîtriser le binaire, ainsi que les conditions binaires pour venir à bout de toutes les notions de ce tutoriel.

Avant de commencer, je souhaite préciser que si vous cherchez juste à accellerer votre Arduino sans chercher à comprendre le comment du pourquoi, vous ouvez simplement utiliser une librairie comme Fast digital I/0 qui utilise les principes que nous allons voir dans ce tutoriel.

La théorie

Pour comprendre d’où vient ce temps de latence, il faut se rendre dans le fichier wiring_digital.c pour y trouver le code source des fonctions digitalWrite et digitalRead(). On y trouve ceci :

void digitalWrite(uint8_t pin, uint8_t val)
{
	uint8_t timer = digitalPinToTimer(pin);
	uint8_t bit = digitalPinToBitMask(pin);
	uint8_t port = digitalPinToPort(pin);
	volatile uint8_t *out;

	if (port == NOT_A_PIN) return;

	// If the pin that support PWM output, we need to turn it off
	// before doing a digital write.
	if (timer != NOT_ON_TIMER) turnOffPWM(timer);

	out = portOutputRegister(port);

	uint8_t oldSREG = SREG;
	cli();

	if (val == LOW) {
		*out &= ~bit;
	} else {
		*out |= bit;
	}

	SREG = oldSREG;
}

int digitalRead(uint8_t pin)
{
	uint8_t timer = digitalPinToTimer(pin);
	uint8_t bit = digitalPinToBitMask(pin);
	uint8_t port = digitalPinToPort(pin);

	if (port == NOT_A_PIN) return LOW;

	// If the pin that support PWM output, we need to turn it off
	// before getting a digital reading.
	if (timer != NOT_ON_TIMER) turnOffPWM(timer);

	if (*portInputRegister(port) & bit) return HIGH;
	return LOW;
}

Comme vous pouvez le voir, ces deux fonctions demandent une dizaine de lignes de codes, qui seront ensuite traduites en une cinquantaine d’instructions machines. Or chaque instruction machine demande un cycle du processeur, qui tourne à 16Mhz dans une Arduino. Or à 16Mhz, un cycle dure 0.06us, donc 50 cycles durent 3us. Voilà, on connait maintenant l’origine de notre delais. Pour avoir une réaction plus rapide de l’Arduino, il faudrait arriver à contourner ces fonctions. Et accrochez-vous, on va rentrer dans du langage machine.

En se renseignant sur l’Arduino, on trouve que les pins sont répartis en 3 groupes, avec dans chaque groupe 8 pins ou moins, 8, ca vous rappelle rien? Et si c’est en rapport avec la taille binaire d’un octet. En effet, un octet (=byte en anglais) écrit en binaire regroupe 8 chiffres binaires, mais le but du tutoriel n’est pas d’expliquer le binaire. Ensuite, chaque groupe regroupe 3 sous-groupes, qui correspondes aux informations liés aux pins, par exemple, on a le sous-groupe qui retient si les pins sont des entrées ou des sorties, un groupe qui retient l’état des pins, HIGH ou LOW et un dernier groupe qui retient le courant en entrée du pin (pour les entrées seulement). L’astuce est que l’on peut accéder directement à ces informations, en tapant des lignes de code un peu plus abstraites. On rentre dans la pratique.

Le codage

Nous allons tout d’abord commencer par donner le noms des trois groupes, pour une Arduino UNO, le groupe B regroupe les pins 8 à 13 (plus les pins 6 et 7 qui ne peuvent pas être utilisés car c’est là qu’est relié le crystal on obtient 8 pins donc la taille de un octet 😉 ), le groupe C contient les pins d’entrée analogique (qui sont 6 sur la UNO mais 8 sur la mini donc encore un octet) et enfin le groupe D contient les pins 0 à 7 (encore et toujours un octet).

Maintenant les sous-groupes, on a l’octet DDR (Data Direction Register) qui contient pour chaque pin d’un groupe un chiffre binaire indiquant si c’est une entrée ou une sortie (0 pour une entrée, 1 pour une sortie), on peut lire et écrire cet octet. Il y  a aussi l’octet PORT qui a un chiffre binaire pour chaque pin indiquant l’état du pin (1 pour HIGH, 0 pour LOW), on peut lire et écrire cet octet. Et enfin on a l’octet PIN qui a aussi un chiffre pour chaque pin, qui contient le courant en entrée (1 pour HIGH, 0 pour LOW) on ne peut que le lire, et seuls les ports déclarés comme entrés seront actualisés (les autres ne contiennent pas d’infos valides).

Voyons à présent comment accéder aux différents ports, il suffit de mettre en premier de nom du sous-groupe, puis le noms du groupe de pin qui nous interresse. Par exemple, pour connaitre l’état des pins 0 à 7, on demande l’octet PORTD, pour savoir quels sont les entrées et les sorties parmis les pins 8 à 13, on demande l’octet DDRB. J’espère que vous comprenez à peu près. Voyons maintenant ce que contient ces octets. Prenons l’exemple du PORTD, il pourrait très bien contenir : B11111110, ce qui voudrait dire que le pin 0 (le chiffre tout à droite) vaut 0 et donc qu’il actuellement relié à la masse, alors que les pins 1,2,3,4,5,6 et 7 sont reliés au +5V. On peut donc maintenant définir l’état des pins, en tapant : PORTD = B00000000; par exemple, les pins 0 à 7 sont tous reliés à la masse. De même avec la définission entrée/sortie : DDRD = B11111111; défini tous les ports en temps que sortie, alors que : DDRD = B11110000; ne met que les pins 4,5,6,7 en sortie, et les pins 0,1,2,3 en entrée.

Pour lire l’état des différents octet, il vous faudra connaitre les conditions binaires, afin d’extraire des 8 bits le bit qui vous interrese. Mais ce n’est pas là exactement le but du tutoriel. Voyons maintenant à travers un exemple de code comment utiliser ces commandes.

Exemple de programmation

L’exemple suivant reprend l’exemple culte de la led qui clignote, mais en le faisant avec les nouvelles commandes que l’on a vu :

void setup(){
DDRD = DDRD | B01000000; // met le pin 6 en OUTPUT, sans changer la valeur des autres pins. Voir les conditions binaires pour plus de détail
}
void loop(){
PORTD = PORTD | B01000000; // met le pin 6 à HIGH, sans changer les autres
delay(500);
PORTD = PORTD & B10111111; // met le pin 6 à LOW sans changer les autres
delay(500);
}

Les plus et les moins

  • Changement d’état des pins 4 fois plus rapide
  • Possibilité de changer l’état de plusieurs pins en même temps (vraiment en même temps)
  • Rend le code plus léger
  • Le code est beaucoup plus dur à corriger et à comprendre
  • Nécessite des adaptations dès que l’on change de microcontrolleur
  • Peut interférer avec les réglages de l’Arduino, comme la liaison série si l’on touche au pins 0 et 1! Attention à ça!

3 Commentaires

  1. ad 19 octobre 2015 à 18 h 14 min␣- Répondre

    Merci pour ça, vraiment j’aimerai apprendre à utiliser les registres mais la plupart des francais peurs de ça

  2. Bastos 22 janvier 2016 à 1 h 14 min␣- Répondre

    Merci,
    je débute et je dois dire que je me suis un peu cassé les dents là dessus. Je comprenais bien la localisation des différents pin sur le code, mais pas forcément l’action du code. (Je cherchais à coder pour 3 leds simultanément, et ne comprenais pas toutes les configurations d’allumage que j’obtenais)

    Après expérimentation, j’en arrive à cette conclusion sur mon point d’ombre :
    & permet de placer le pin en LOW avec un 0 (donc allumage)
    | (Alt + Maj + l sur Mac) permet de placer le pin en HIGH avec un 1 (donc extinction)
    ^ permet le changement d’état du pin avec un 1 (allumage ou extinction selon l’état initial)

    ça vous paraît bon ?

  3. arnaud9212 24 janvier 2016 à 20 h 14 min␣- Répondre

    Merci de vos commentaires,
    C’est bien ca Bastos !

Laisser un commentaire