IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Vous êtes nouveau sur Developpez.com ? Créez votre compte ou connectez-vous afin de pouvoir participer !

Vous devez avoir un compte Developpez.com et être connecté pour pouvoir participer aux discussions.

Vous n'avez pas encore de compte Developpez.com ? Créez-en un en quelques instants, c'est entièrement gratuit !

Si vous disposez déjà d'un compte et qu'il est bien activé, connectez-vous à l'aide du formulaire ci-dessous.

Identifiez-vous
Identifiant
Mot de passe
Mot de passe oublié ?
Créer un compte

L'inscription est gratuite et ne vous prendra que quelques instants !

Je m'inscris !

Apprendre à programmer l'Arduino en langage C,
Un tutoriel de Francesco Balducci, traduit par F-leb

Le , par f-leb

15PARTAGES

9  0 


Si vous êtes férus d'Arduino et que vous souhaitez vous mettre au langage C, ce tutoriel est peut-être fait pour vous :



ou comment apprendre la programmation du microcontrôleur Atmel AVR de l'Arduino en véritable langage C, au cœur des registres de la puce, sans passer par l'EDI standard, et sans utiliser le fameux « langage Arduino ».

J'adore jouer avec ma carte Arduino UNO et son environnement de développement graphique. C'est un outil qui facilite grandement la programmation en dissimulant une foule de détails. D'un autre côté, je ne peux m'empêcher d'observer dans les coulisses de l'EDI, et j'ai senti le besoin de me rapprocher des aspects matériels, de m'écarter des bibliothèques fournies par défaut et de l'EDI Java pour compiler directement mes programmes à partir de la ligne de commandes.

Francesco Balducci


Du code source en langage C jusqu'au téléversement dans la carte Arduino avec la chaîne de compilation avr-gcc.


Bonne lecture, et bon développement, en vrai langage C...

Retrouvez la série de tutoriels Apprendre à programmer l'Arduino en langage C par Francesco Balducci
Les meilleurs cours et tutoriels pour apprendre à utiliser la carte Arduino
Retrouvez les meilleurs cours et tutoriels pour apprendre les systèmes embarqués

Une erreur dans cette actualité ? Signalez-nous-la !

Avatar de Vincent PETIT
Modérateur https://www.developpez.com
Le 21/07/2018 à 20:51
Salut Gérard,
Alors justement non, la variable i n'appartient pas à la fonction func, elle est globale car elle est déclarer et créer en dehors de toutes fonctions.

Ci dessous la variable i est globale et sa portée atteint tout le fichier .c dans le quel elle est. Cette variable sera créée dans ce qu'on appelle le "tas" dans la RAM.
Code C : Sélectionner tout
1
2
3
4
5
6
7
int i; 
  
void func(void) // fonction attendre 
{ 
    i = 0; 
    while (i == 0);  
}


Ci dessous la variable i est locale et sa portée est limitée à la fonction func. Cette variable sera créée dans la "pile" qui se trouve dans la RAM, au moment où on entre dans la fonction. Puis elle est détruite lorsqu'on sort de la fonction.
Code C : Sélectionner tout
1
2
3
4
5
void func(void) // fonction attendre 
{ 
    int i = 0; 
    while (i == 0);  
}


Ce qui se passe dans le code ci dessous est très subtile et on comprend le problème en regardant l'assembleur généré par le compilateur.
Code C : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int i; //variable globale 
  
interrupt (TIMERA0_VECTOR) irq_routine(void) // a chaque débordement du TIMERA, disons toutes les secondes 
{ 
    i = 42; 
} 
  
void func(void) // fonction attendre 
{ 
    i = 0; 
    while (i == 0); // tant que i == 0 alors on tourne en rond 
} 
  
int main(void) // programme principal qui ne fait pas grand chose 
{ 
    func(); 
    printf("si j'arrive ici, c'est que i ne vaut plus 0 et que je suis sortie de la function func()\n"); 
    return 0; 
  
}

Si on le déroule on voit que le main lance la fonction func qui initialise la variable i à 0 puis tant que cette dernière est à 0 on tourne en rond. Sans routine d'interruption on a simplement réussi a planter le micro ou plutôt à le faire entrer une boucle infinie. Pour être plus précis la variable i va d'abord être mis dans un registre de travail pour être mis à jour avec la valeur 0 dans la RAM puis pour manipulation, donc ce registre sera mis à 0 (valeur de la variable i) puis testé en boucle avec la valeur 0 dans une boucle via l'ALU (l'unité arithmétique et logique).


Ensuite arrive l'interruption du TIMERA, une seconde après le démarrage du micro. Le micro va donc empiler (stocker dans la pile qui se trouve dans la RAM) les registres de travail, le status register et le pointeur de pile, pour retrouver ces petits et recommencer à travailler là où il était une fois que le routine d'interruption sera terminée.


Le programme d'interruption va affecter la valeur 42 à la variable i qui se trouve en RAM


Sortie de la fonction d'interruption, le micro va dépiler tout ce qu'il avait empiler avant d'être interrompu et le CPU va être rechargé avec les valeurs d'avant l'interruption pour reprendre où il était.


Malheureusement il était dans une boucle infinie qui ne fait que comparer un registre qui vaut 0 avec la valeur 0. Malgré que la variable i vaut bien 42 en RAM pourquoi diable est ce que le CPU irait relire la variable i en RAM ? Il ne fait que reprendre ce qu'il faisait. Et ce qu'il faisait c'est juste une comparaison dans le while (i == 0);. D'ailleurs il n'y a pas d'affectation dans cette instruction donc pas d'excuse pour aller causer avec la RAM ou recharger un registre de travail.


Note qu'avec des variables locales, ce phénomène bien tordu n'arrive pas. Si on a besoin impérativement d'une variable globale, il faut spécifier avec GCC (compilateur de Arduino) : volatile int i; pour forcer le compilateur a ajouter des instructions qui consistent justement à aller recharger le registre de travail avec la valeur dans la variable globale i, à chaque manipilation de celle ci, moyennant une petite perte de temps d'accès à la RAM à chaque fois.

Je ne sais pas si c'était plus clair comme ça ?
A+
2  0 
Avatar de Vincent PETIT
Modérateur https://www.developpez.com
Le 04/10/2016 à 15:25
Citation Envoyé par f-leb  Voir le message
c'est peut-être un souci dans la traduction :

Non non pas du tout !

C'est que j'ai pensé a un problème très sournois du genre :
Code C : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
int i; 
  
interrupt (TIMERA0_VECTOR) irq_routine(void) // a chaque débordement du TIMERA 
{ 
    i = 42; 
} 
  
void func(void) // fonction attendre 
{ 
    i = 0; 
    while (i == 0);  
}

  1. Lorsque la fonction "func" sera appelée, "i" va être mis dans un registre de travail pour manipulation, donc ce registre sera mis à 0 puis testé avec la valeur 0 dans une boucle.
  2. Puis le TIMERA va déborder et la routine d'interruption va s'exécuter en stoppant tout (on empile), l'emplacement mémoire de "i" va valoir 42.
  3. Enfin, on sort de l'interruption, on retourne dans le code de la fonction "func" (on dépile) et le registre qui valait 0, avant, et qui est testé avec la valeur 0 n'a aucune raison de se mettre à jour avec la nouvelle valeur de "i", soit 42 !


Et c'est la boucle infinie.

Pire encore, si jamais les options d'optimisation sont activées alors GCC va voir tout de suite une comparaison avec la valeur 0 et un registre qui est mis a 0 .... et il risque de retirer la condition qui pour lui ne sert strictement a rien et c'est vrai puisqu'on teste 0 avec 0. Ce problème se règle facilement avec le mot clé volatile qui va forcer le compilateur a aller voir l'emplacement mémoire de la variable "i" à chaque vérification ou manipulation.
Code C : Sélectionner tout
volatile int i;

D'où la frousse que j'ai eu en voyant ce _BV(bit) que je ne connaissais pas et la phrase :
En C, on a l'habitude d'utiliser [...], mais le compilateur reconnaît ce genre d'accès et produit un code d'assemblage optimisé dans le cas d'opérations bit par bit, sans opération de lecture supplémentaire.

J'ai pris le "mais" dans le sens attention alors que ce n'est pas ça... bref c'est rien, c'était juste un peu de paranoïa mais je me soigne C'est rien Vincent, ce n'est qu'une macro, ça va aller
1  0 
Avatar de Delias
Modérateur https://www.developpez.com
Le 15/11/2016 à 22:36
Bonsoir

Pour le coup j'ai lu ce tuto à la sortie du suivant.

Juste pour "optimisation", en fait c'est bien de le connaître, une écriture sur le registre PINx inverse les sorties dont la valeur écrite est à 1.

Donc la boucle
Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
  while(1) {
  /* set pin 5 high to turn led on */
  PORTB |= _BV(PORTB5);
  _delay_ms(BLINK_DELAY_MS);
 
  /* set pin 5 low to turn led off */
  PORTB &= ~_BV(PORTB5);
  _delay_ms(BLINK_DELAY_MS);
 };
peut-être simplifiée de la manière suivante:
Code : Sélectionner tout
1
2
3
4
5
  while(1) {
  /* toggle pin 5 to blink LED */
  PINB = _BV(PORTB5);
  _delay_ms(BLINK_DELAY_MS);
};
Ici cela n'a pas d'importance, mais dans les projets un peu plus complexe cela peut beaucoup aider.

Je manque d’expérience en C, les AVR je les travaille en ASM. Je ne peux pas apporter de réponse à vos questions sur le volatile.

Bonne soirée.

Delias
1  0 
Avatar de Vincent PETIT
Modérateur https://www.developpez.com
Le 27/09/2016 à 23:56
Merci pour ce tuto Fabien !
Voilà qui pourrait intéresser les copains du forum C qui n'ont qu'un petit pas à franchir pour jouer sur un microcontrôleur.

Sur le rôle de la macro _BV
En C, on a l'habitude d'utiliser les opérateurs d'affectation bit à bit |= et &= pour lire ou écrire dans une variable, mais le compilateur reconnaît ce genre d'accès et produit un code d'assemblage optimisé dans le cas d'opérations bit par bit, sans opération de lecture supplémentaire.



Tu es entrain de me faire peur car je n'avais jamais constaté de différence entre :
Code C : Sélectionner tout
DDRB |= _BV(DDB5);
et
Code C : Sélectionner tout
DDRB |= 0x20;
Je vais regarder ça de prés et surtout les options d'optimisation de mon compilateur car si la lecture ne se fait pas dans le cas de :

Code C : Sélectionner tout
DDRB = DDRB | 0x20;
J'espère bien que DDRB soit lu puis aille dans un registre de travail pour se voir appliqué le OU 0x20 et enfin être remis dans DDRB

A bientôt,
0  0 
Avatar de f-leb
Responsable Arduino et Systèmes Embarqués https://www.developpez.com
Le 30/09/2016 à 18:30
Salut Vincent

Citation Envoyé par Vincent PETIT  Voir le message
Tu es entrain de me faire peur car je n'avais jamais constaté de différence entre :
Code C : Sélectionner tout
DDRB |= _BV(DDB5);
et
Code C : Sélectionner tout
DDRB |= 0x20;

Je ne te suis pas, finalement cela revient au même, non ?

http://www.nongnu.org/avr-libc/user-...5a93800b3d9546
#define _BV(bit ) (1 << (bit))

#include <avr/io.h>

Converts a bit number into a byte value.

Note
The bit shift is performed by the compiler which then inserts the result into the code. Thus, there is no run-time overhead when using _BV().

0  0 
Avatar de Vincent PETIT
Modérateur https://www.developpez.com
Le 01/10/2016 à 22:06
Citation Envoyé par f-leb Voir le message
Je ne te suis pas, finalement cela revient au même, non ?
Oui oui mais lorsque j'ai lu cette phrase, qui introduit _BV dans le tuto :
En C, on a l'habitude d'utiliser les opérateurs d'affectation bit à bit |= et &= pour lire ou écrire dans une variable,
Jusque là pas de soucis nous sommes d'accord.

Mais a partir du mais justement :
mais le compilateur reconnaît ce genre d'accès et produit un code d'assemblage optimisé dans le cas d'opérations bit par bit, sans opération de lecture supplémentaire.
Ça laisse penser qu'il y a un problème et que _BV est né de ça !

Je me suis donc interrogé sur ce point.
Je sais que sur GCC, et même d'autre compilateur, les options de compilations peuvent avoir un effet désastreux en programmation embarqué car des variables en attentes du matériel ou d'un registre peuvent être éliminées du source car le compilateur pense qu'elles ne servent a rien. Certain flag dans des registres se mettent a 0 après une simple lecture et là aussi certain compilateur, voyant dans le code un simple lecture, vont essayer d'optimiser et malheureusement ça fini en "comme la variable ne fait que lire une adresse et ne fait rien de cette valeur, pas de calcul ni d'opération" bah c'est qu'elle ne sert pas au final !

A+
0  0 
Avatar de f-leb
Responsable Arduino et Systèmes Embarqués https://www.developpez.com
Le 02/10/2016 à 17:07
bon, je ne suis pas assez calé

je n'ai pas compris ce "mais" comme ca, mais c'est peut-être un souci dans la traduction :
In C we use the bitwise “|=” and “&=” assignment operators, which usually read and write a variable, but the compiler recognizes those kind of accesses generating optimized assembly in case of bit-wise operations, and there is no read operation involved.
0  0 
Avatar de neuneutrinos
Membre actif https://www.developpez.com
Le 04/10/2016 à 9:24
Dans ma tête quand je vois ça :
une voix me dit "mais pourquoi il n'utilise pas un xor ?"
A part ce détails des plus utile...

Code : Sélectionner tout
_BV(bit) 1<<(bit)
DDB5 est une constante
1<<(DDB5) peut être déterminé lors de la compilation comme une constante, et donc ne pas d'opération superflue lors de l’exécution.( pas d'opération de décalage inutile lors de l’exécution )

Je l'ai compris comme ça.
0  0 
Avatar de Vincent PETIT
Modérateur https://www.developpez.com
Le 04/10/2016 à 15:17
Je suis d'accord et pour tout dire, je cherchais une raison technique à cette macro _BV(bit) mais c'est simplement pour des raisons de lisibilité/compréhension du programme qu'elle est défini.
Je dois probablement avoir un raisonnement beaucoup trop électronique (binaire/assembleur/registre) car effectivement, faire :
Code C : Sélectionner tout
DDRB |= (_BV(DDB0) | _BV(DDB1) | _BV(DDB2)); // mettre a en sortie PB0, PB1 et PB2
Semble plus aisé à lire et à comprendre que ma version a moi :
Code C : Sélectionner tout
DDRB |= 0x03; // mettre a en sortie PB0, PB1 et PB2
Même si je ne me ferai jamais a la première écriture....
0  0 
Avatar de F6EEQ
Membre régulier https://www.developpez.com
Le 21/07/2018 à 14:49
Bonjour à tous,

J'ai repris le tuto sur "ARDUINO en C".
Cette discussion est vieille, mais toujours d'actualité, et je me pose une question:

Enfin, on sort de l'interruption, on retourne dans le code de la fonction "func" (on dépile) et le registre qui valait 0, avant, et qui est testé avec la valeur 0 n'a aucune raison de se mettre à jour avec la nouvelle valeur de "i", soit 42 !
Le deuxième "i" appartient à la fonction "func", donc il est en local et n'est pas affecté par la valeur "42".
Pour moi le "int i " du début vaut pour la "boucle principale" donc le programme d'interruption, mais pas pour la fonction "func"... ou me trompe-je (!).
Bon, je ne suis pas un grand spécialiste du C , mais j'ai au moins compris la portée des variable, ou du moins je pense

Gérard.
0  0