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

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Arduino Discussion :

Programmer l'Arduino en langage C [Tutoriel]


Sujet :

Arduino

  1. #1
    Responsable Arduino et Systèmes Embarqués


    Avatar de f-leb
    Homme Profil pro
    Enseignant
    Inscrit en
    Janvier 2009
    Messages
    12 612
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : France, Sarthe (Pays de la Loire)

    Informations professionnelles :
    Activité : Enseignant

    Informations forums :
    Inscription : Janvier 2009
    Messages : 12 612
    Points : 56 717
    Points
    56 717
    Billets dans le blog
    40
    Par défaut Programmer l'Arduino en langage C


    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
    Nom : toolchainArduino.JPG
Affichages : 30872
Taille : 31,8 Ko
    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

  2. #2
    Modérateur

    Avatar de Vincent PETIT
    Homme Profil pro
    Consultant en Systèmes Embarqués
    Inscrit en
    Avril 2002
    Messages
    3 190
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43
    Localisation : France, Pas de Calais (Nord Pas de Calais)

    Informations professionnelles :
    Activité : Consultant en Systèmes Embarqués
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Avril 2002
    Messages : 3 190
    Points : 11 573
    Points
    11 573
    Par défaut
    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 :
    et
    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 :

    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,
    La science ne nous apprend rien : c'est l'expérience qui nous apprend quelque chose.
    Richard Feynman

  3. #3
    Responsable Arduino et Systèmes Embarqués


    Avatar de f-leb
    Homme Profil pro
    Enseignant
    Inscrit en
    Janvier 2009
    Messages
    12 612
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : France, Sarthe (Pays de la Loire)

    Informations professionnelles :
    Activité : Enseignant

    Informations forums :
    Inscription : Janvier 2009
    Messages : 12 612
    Points : 56 717
    Points
    56 717
    Billets dans le blog
    40
    Par défaut
    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 :
    et
    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().

  4. #4
    Modérateur

    Avatar de Vincent PETIT
    Homme Profil pro
    Consultant en Systèmes Embarqués
    Inscrit en
    Avril 2002
    Messages
    3 190
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43
    Localisation : France, Pas de Calais (Nord Pas de Calais)

    Informations professionnelles :
    Activité : Consultant en Systèmes Embarqués
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Avril 2002
    Messages : 3 190
    Points : 11 573
    Points
    11 573
    Par défaut
    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+
    La science ne nous apprend rien : c'est l'expérience qui nous apprend quelque chose.
    Richard Feynman

  5. #5
    Responsable Arduino et Systèmes Embarqués


    Avatar de f-leb
    Homme Profil pro
    Enseignant
    Inscrit en
    Janvier 2009
    Messages
    12 612
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : France, Sarthe (Pays de la Loire)

    Informations professionnelles :
    Activité : Enseignant

    Informations forums :
    Inscription : Janvier 2009
    Messages : 12 612
    Points : 56 717
    Points
    56 717
    Billets dans le blog
    40
    Par défaut
    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.

  6. #6
    Membre actif
    Étudiant
    Inscrit en
    Juin 2010
    Messages
    70
    Détails du profil
    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Juin 2010
    Messages : 70
    Points : 204
    Points
    204
    Par défaut
    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...

    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.

  7. #7
    Modérateur

    Avatar de Vincent PETIT
    Homme Profil pro
    Consultant en Systèmes Embarqués
    Inscrit en
    Avril 2002
    Messages
    3 190
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43
    Localisation : France, Pas de Calais (Nord Pas de Calais)

    Informations professionnelles :
    Activité : Consultant en Systèmes Embarqués
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Avril 2002
    Messages : 3 190
    Points : 11 573
    Points
    11 573
    Par défaut
    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    DDRB |= 0x03; // mettre a en sortie PB0, PB1 et PB2
    Même si je ne me ferai jamais a la première écriture....
    La science ne nous apprend rien : c'est l'expérience qui nous apprend quelque chose.
    Richard Feynman

  8. #8
    Modérateur

    Avatar de Vincent PETIT
    Homme Profil pro
    Consultant en Systèmes Embarqués
    Inscrit en
    Avril 2002
    Messages
    3 190
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43
    Localisation : France, Pas de Calais (Nord Pas de Calais)

    Informations professionnelles :
    Activité : Consultant en Systèmes Embarqués
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Avril 2002
    Messages : 3 190
    Points : 11 573
    Points
    11 573
    Par défaut
    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 - Visualiser dans une fenêtre à part
    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.

    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
    La science ne nous apprend rien : c'est l'expérience qui nous apprend quelque chose.
    Richard Feynman

  9. #9
    Modérateur

    Homme Profil pro
    Ingénieur électricien
    Inscrit en
    Septembre 2008
    Messages
    1 267
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : Suisse

    Informations professionnelles :
    Activité : Ingénieur électricien

    Informations forums :
    Inscription : Septembre 2008
    Messages : 1 267
    Points : 4 830
    Points
    4 830
    Par défaut
    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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

  10. #10
    Membre régulier
    Homme Profil pro
    Retraité
    Inscrit en
    Avril 2016
    Messages
    51
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 73
    Localisation : France, Drôme (Rhône Alpes)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Avril 2016
    Messages : 51
    Points : 75
    Points
    75
    Par défaut
    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.

  11. #11
    Modérateur

    Avatar de Vincent PETIT
    Homme Profil pro
    Consultant en Systèmes Embarqués
    Inscrit en
    Avril 2002
    Messages
    3 190
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43
    Localisation : France, Pas de Calais (Nord Pas de Calais)

    Informations professionnelles :
    Activité : Consultant en Systèmes Embarqués
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Avril 2002
    Messages : 3 190
    Points : 11 573
    Points
    11 573
    Par défaut
    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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).

    Nom : cpu1.png
Affichages : 13561
Taille : 81,2 Ko

    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.

    Nom : cpu2.png
Affichages : 13451
Taille : 105,8 Ko

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

    Nom : cpu3.png
Affichages : 13635
Taille : 61,5 Ko

    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.

    Nom : cpu4.png
Affichages : 13420
Taille : 77,6 Ko

    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.

    Nom : cpu5.png
Affichages : 13274
Taille : 56,6 Ko

    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+
    La science ne nous apprend rien : c'est l'expérience qui nous apprend quelque chose.
    Richard Feynman

  12. #12
    Membre régulier
    Homme Profil pro
    Retraité
    Inscrit en
    Avril 2016
    Messages
    51
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 73
    Localisation : France, Drôme (Rhône Alpes)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Avril 2016
    Messages : 51
    Points : 75
    Points
    75
    Par défaut
    Longue explication avec beaucoup de schémas!!
    C'est clair maintenant. Merci.

    A propos de C et Arduino, je viens d'installer la dernière version de MPLAB qui prend en compte les µ ATMEL en plus des PIC.
    Quelqu'un a-t-il déjà essayé (C ou ASM) de programmer un ARDUINO à partir de MPLAB??

    @+
    Gérard.

Discussions similaires

  1. Python, pourquoi programmer avec un autre langages?
    Par kayzra dans le forum Général Python
    Réponses: 9
    Dernier message: 31/03/2007, 19h57
  2. Réécrire un programme dans un autre langage/Licence ?
    Par VinnieMc dans le forum Licences
    Réponses: 1
    Dernier message: 15/03/2007, 11h32
  3. Création d'un programme de Gestion / compta : langage ?
    Par Alexino2 dans le forum Langages de programmation
    Réponses: 7
    Dernier message: 11/04/2006, 15h00

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo