[Résolue] Aide S.V.P pour les pointeurs « * » et « & » en langage Arduino,pas...

barbudor:
Stop !
...
La première erreur que te retourne la compilation est due à la conversion de type : le compilateur refuse de convertir un int (0x128) en un pointeur sans que tu lui dise explicitement de le faire. C'est ce qu'on appelle un cast : il faut forcer un changement de type sur 0x128 pour le faire considérer comme un pointeur afin que l'initialisation soit effectué.

Merci pour toutes les explications !

Et merci pour l'explication de ce qu'est un « cast », je ne connaissais pas !

L'ami René

Bonjour fdufnews,

Merci pour votre aide !

fdufnews:
...
Il faut apprendre à lire les messages d'erreur. La ligne importante est celle-là:

Arduinobot_Cinci_01_typedef:672: error: invalid conversion from ‘int’ to ‘uint16_t*’

C est stricte sur les types. On ne peut pas affecter une valeur/variable d'un type à une variable d'un autre type.
...

Je suis bien d'accord, c'est une faute d'inattention de ma part, la fatigue explique un peu la faute...

L'ami René

Salut Super_Cinci,

Super_Cinci:
le #define d'origine marchait si bien... =>[]

Désolé pour le long détour !

Mais, cela ne fut pas inutile, je préfère poser une question au risque de paraître stupide, plutôt que de rester stupide.

Me voilà une coche plus connaissent.

Merci encore !

L'ami René

LamiRene:
La meilleure manière de faire la désignation des broches analogiques et digitales des cartes Arduino, est-ce aussi avec des « #define » ?

Par exemple aurais-je avantage a changer pour des « #define » les désignations de broche, pour un capteur sonar HC-SR04 ou un capteur infrarouge pour télécommande VS1838B ou un pont en H L298N pour contrôler des moteurs CC :

Pour le moment, j'ai comme ligne de code :

...

// Broche d'Arduino pour recevoir le signale du capteur infrarouge.
const byte CAPTEUR_IR_1_1SORTIE = 53;
...
// Broche d'Arduino pour le capteur a ultrason 2, le sonar.
const byte Sonar_1_TRIG = 24;
const byte Sonar_1_ECHO = 25;
...
// Broche d'Arduino pour le contrôleur moteur 1 (double Pont en H).
const byte CONTROLE_MOTEUR_1_ENA = 14;
const byte CONTROLE_MOTEUR_1_IN1 = 15;
const byte CONTROLE_MOTEUR_1_IN2 = 16;
const byte CONTROLE_MOTEUR_1_ENB = 17;
const byte CONTROLE_MOTEUR_1_IN3 = 18;
const byte CONTROLE_MOTEUR_1_IN4 = 19;
...

Nous avions eu une discussion sur le sujet il y a un bon moment (sur l'ancien forum il me semble) et quelqu'un avait montré que cela ne changeait rien au code généré. Par contre les #define peuvent être quelque fois la source de problèmes difficile à détecter.

Bonjour fdufnews,

Je suis curieux de lire les commentaires de barbudor, skywodd et Super_Circin sur ces points ?!?

Car, en fin de compte, c'est des choses que l'on tente de clarifier.

Donc, a suivre !!!

L'ami René

De mon côté, j'utilise principalement les #define, car c'est de la substitution pure et dure de texte:

#define toto 25
(...)
  byte var = toto;

est exactement la même chose que :

  byte var = 25;

A la seule différence, c'est que passer par toto me permet de changer le 25 en 26 en une seule opération plutôt que de me retapper tout le code pour remplacer tous les 25.

J'avais aussi posé une question à propos du mot clé const. car le compilateur peut faire deux choses : soit le traiter comme un #define, soit créer une variable (dans la mémoire de l'arduino) mais toute tentative d'écriture sur cette variable dans le code renvoie une erreur de compilation. comme c'est pas très clair pour moi, je garde le #define. Mais avec un const, alors on sait exactement la nature de la variable (type) alors qu'un #define, ben on sait pas à moins de faire #define toto (byte)25 ou un truc du genre...

Mais comme le souligne fdufnews, le fait que plusieurs #define se succèdent peut porter à confusion

#define toto 25
(...)
#define tata toto
(...)
#define tutu tata

se comporte au final comme #define tutu 25, mais en cas d'erreur de compilation, si le compilateur nous parle de tata, ben bonjour l'embrouille pour trouver ce qui peut clocher...

Un #define est une opération du préprocesseur qui est exécuté avant la compilation.
Le préprocesseur est une opération purement textuelle qui ne connait rien du C/C++ mais qui ne sait que

  • évaluer des expressions (#if)
  • insérer des fichiers (#include)
  • faire des remplacement de caractères (#define)

Quand tu écris

#define PIN_LED 13
digitalWrite( PIN_LED, HIGH );

Le préprocesseur intervient avant le compilateur de manière à ce le compilateur compile effectivement:

digitalWrite( 13, 1 );

Comme indiqué plus haut, OCR5A est déjà un define équivalent à

#define OCR5A   (*(uint16_t*)0x128)

Si tu rajoutes un define sur FrequenceBroche46

#define FrequenceBroche46 OCR5A

Quand tu écriras

FrequenceBroche46 = 5;

Le compilateur recevra comme ligne à compiler:

(*(uint16_t*)0x128) = 5;

Pour ce qui est d'une constante, quelles sont donc les différences entre les 2 lignes ci-dessous ?

#define CONTROLE_MOTEUR_1_ENA 14
const byte CONTROLE_MOTEUR_1_ENA = 14;

Tout d'abord, la différence fondamentale :

  • la première agit au niveau du préprocesseur pour un remplacement de chaine de caractère. Elle ne définit rien au niveau du langage C/C++.
  • la première est au contraire une définition de variable avec initialisation.
    C'est bien à 100% une définition de variable de la même manière que si le mot clé 'const' n'était pas là. Sauf que cette variable est affublé du modificateur 'const' qui fait qu'elle n'est plus variable justement mais que sa valeur ne peut pas être changée.
    En théorie, à l'issue de la phase de compilation, il y a bien création d'une variable, avec son emplacement mémoire. Emplacement mémoire qui est initialisé avec la valeur 14.
    Le mot clé 'const' empêche seulement d'utiliser cette variable sur la gauche d'une affectation et l'écriture de 'CONTROLE_MOTEUR_1_ENA = 0;' retournera une erreur de compilation.

Dans le 1er cas, l'écriture de 'digitalWrite( CONTROLE_MOTEUR_1_ENA, HIGH );' sera dans toujours traitée par le préprocesseur pour fournir au compilateur la forme :

digitalWrite( 14, 1 );

Car les 2 chaînes de caractères sont replacés par le préprocesseur par leur valeur substituée correspondante.
Dans le 2nd cas, la même écriture fournit au comiplateur la forme:

digitalWrite( CONTROLE_MOTEUR_1_ENA, 1 );

Et seule le HIGH est une macro préprocesseur remplacée alors par sa chaîne de caractère équivalente.

Vous avez suivit jusque là ?
On continue.

Donc le compilateur est sensé générer du code qui va aller lire en mémoire la valeur de la variable CONTROLE_MOTEUR_1_ENA afin de pouvoir passer cette valeur à la fonction digitalWrite().
Ce qui donc a priori utilise plus de code que l'utilisation du define puisque dans le cas du define, la valeur est immédiatement connue et donc le code assembleur généra devrait être plus simple ....

.....

Sauf que .....

Il existe encore une 3me phase, après le pré-processeur et après le compilateur qui est l'optimiseur. Et l'optimiseur, il est pas con lui.
Il a bien vu que la variable c'est une constante qui va toujours valoir la même valeur.
Donc, ni une, ni deux, il supprime l'accès à la variable et il utilise directement la valeur.
Et puis si à la fin personne n'a besoin d'aller chercher de valeur dans la case mémoire, il la supprime aussi de la map et donc pas d'allocation mémoire inutile.

En résultat, dans la plupart des cas le code généré est donc strictement identique pour l'une ou l'autre des écritures.

Quelle forme choisir alors ?

C'est une question philosophique.
Historiquement, on a tendance à favoriser l'écriture à base de #define car au début les compilateurs n'étaient pas toujours capable de pratiquer cette optimisation.
Maintenant on a plutôt tendance à recommander l'écriture sous la forme de variable 'const' qui présente en plus l'avantage de typer la valeur est donc un meilleur contrôle du code C.
Il reste toutefois des choses qui ne sont possible que sous la forme de #define.

Par exemple le define de OCR5A permet de l'utiliser comme une pseudo variable : OCR5A = 5; est parfaitement légal.
En C, il n'est pas possible de faire cela autrement.
L'écriture la plus proche en C serait comme on à vu:

volatile unit16_t * const PTR_OCR5A = (volatile uint16_t *)0x128;

ce qui impose l'écriture :

*PTR_OCR5A = 5;

Notez l'emplacement du mot clé const qui indique de c'est bien PTR_OCR5A qui est une constante (le pointeur) et non pas la valeur pointée!!!!
j'ai vérifié et cette écriture génère bien le même code court sans déréférencement d'une variable pointeur.

Comme indiqué, en C++ on a le droit de passer par une référence ce qui donne:

volatile uint16_t &OCR5A = *(volatile uint16_t *)0x128;

Mais dans ce cas, il est impossible de spécifier qu'il s'agit d'une référence 'const' ce qui implique que le compilateur générera toujours un déréférencement de pointeur(lecture de la variable référence OCR5A pour connaitre l'adresse réelle).

Au fait. J'ai écris plus haut :

En résultat, dans la plupart des cas le code généré est donc strictement identique pour l'une ou l'autre des écritures.

En effet, il existe des cas où l'écriture

const byte CONTROLE_MOTEUR_1_ENA = 14;

peut se traduire exactement comme une variable avec lecture de la case mémoire concernée.

En effet, si vous utilisez plusieurs fichiers. Si vous placez dans le fichier Module1.cpp la définition de constante :

const byte CONTROLE_MOTEUR_1_ENA = 14;

Et que ensuite pour pouvoir utiliser cette constante vous mettez dans le fichier d'interface Module1.h la déclaration :

extern const byte CONTROLE_MOTEUR_1_ENA;

De façon à pouvoir écrire dans Module2.cpp :

#include "Module1.h"
digitalWrite( CONTROLE_MOTEUR_1_ENA; HIGH );

Alors dans ce cas, CONTROLE_MOTEUR_1_ENA sera traitée dans Module2 comme une variable externe et aucune optimisation dans Module2 ne sera effectuée (mais toujorus effectuée dans Module1).
La solution étant donc de mettre la définition de la constante dans le fichier Module1.h et non pas dans le corps du Module1.cpp.

Bon.
J'espère avoir été clair car ce n'est pas forcément immédiat quand on vient d'un langage tel que le Pascal.
Le Pascal est un langage très formalisé et contrôlé, on est assez loin de la machine. Les pointeurs sont avant tout un raccourcit d'écriture qui ne permettent pas d'erreur (on ne peut pas en Pascal avoir un pointeur qui ne pointe pas vers quelque chose de connu). En C/C++, aucun contrôle n'est effectué sur la validité d'un pointeur (comme sur un index de tableau d'ailleurs). Ce qui est la cause de bien des problèmes avec les débutants qui ne comprennent pas que derrière un pointeur il faut qu'il y ait une case mémoire réelle allouée par quelqu'un.

A+

Bonjour à tous,

Merci encore au grand pédagogue que vous êtes !

À chaque relecture, j'en saisis un peu plus et quand c'est possible, je fais des tests.

Pour m'éviter une recherche qui pourrait m'être long, est-ce qu'il y aurait une âme charitable pour m'indique où sont stocké dans mon disque dur, le ou les fichiers de la dernière compilation avec l'IDE Arduino, ceux qui par la suite sont transférés dans la carte Arduino ?

Pour information, je suis sous Linux Kubuntu 12.04 64 bits et mon dossier Arduino et :

/home/rene/sda6/Électroniques/Arduino_Linux/

Merci d'avance !

L'ami René

Dans l'IDE Arduino, va dans File -> Preferences*
Coche les 2 cases "Show verbose output during [ ] compilation [ ]upload"
Comme cela tu verras tout ce qui se passe en cachette ainsi que où cela se passe.

Rebonjour barbudor,

Et rapide sur la gâchette en plus !

Un grand merci !

L'ami René

Eh, LamiRené, tu dors quand même un peu ?
Si tu es réellement à Sherbrooke, tu as commencé à écrire sur le forum à 3h du matin ?

Oui,

Disons que c'est compliqué, j'ai pas d'horaire de veille et de sommeil, je n'ai pas de cycle circadien.

Pour faire simple, imagine que je travaille sur appel, de jour, de soir et de nuit, alors je dore quand je peu, parfois j'ai du travaille pendent 48 heures en continu, d'autres fois, je suis libre pendent des jours.

C'est une maladie qui ne pas de nom et pas de traitement et pas de remède.

C'est de naissance, je fais avec !

L'ami René

Bonjour barbudor,

Avant ma question, je tiens a te souligner mon appréciation de tes explications détaillées et aux informations que tu as eu l'amabilité de nous transmettre. Merci !

barbudor:
...
Si tu veux passer par un typedef, ca donne :

typedef uint16_t *PointeurSurUint16;

PointeurSurUint16 ServomoteurBroche46 = &OCR5A;






> Oui, mais cela est plus lourd en ressource mémoire et processeur, etc.



Archi faux.
Chercher à remettre l'adresse de OCR5A dans une variable puis écrire une indirection via cette variable :
- occupe plus de mémoire RAM : création d'une variable supplémentaire qui prend 2 octets
- occupe plus de flash : définit une table qui sert à l'initialisation de la dite variable
- occupe plus de code : pour obtenir l'adresse du registre il faut d'abord aller lire la variable avant de pouvoir faire l'indirection
- prend plus de cycles CPU : puisqu'il y a plus de code pour faire cela.

Rien n'est plus efficace que d'écrire


OCR5A=valeur;

Ma question !

Si j'utilisais le code suivant, est-ce qu'il y aurait un gain en quoi que ce soit et est-ce que cela fonctionnerait pour modifier la valeur de la fréquence de la PWM de la broche 46 sur le "Timer5" ? :

typedef uint16_t *PointeurBrochePWM;
PointeurBrochePWM ServomoteurBroche46 = 0x128; // Adresse du registre OCR5A, pour la fréquence du Timer5 broche 46.

Et si l'une de ces lignes ne respecte pas le C++, est-ce qu'une âme charitable pourrait me corriger en m'écrivant la ligne exacte que je devais utiliser dans mon code source, je pense, en particulier à ce qu'il doit y avoir après le « = » de mon exemple ?

Le but étant double, performance du code source et le rendre plus clair et compréhensible littérairement parlant.

Merci d'avance !

L'ami René
Modification : faute de frappe sur PointeurBrochePWM.

En partie, mais pas complètement, je me réponds après avoir poursuivi ma relecture de tout le sujet...

Le code serait plutôt :

typedef uint16_t *PointeurBrochePWM;
PointeurBrochePWM ServomoteurBroche46 = (uint16_t *)0x128; // Adresse du registre OCR5A, pour la fréquence du Timer5 broche 46.

En attente de vos commentaires et suggestion.

L'ami René
Modification : faute de frappe "PointeurBrochePWM" remplace "PointeyrBrochePWM".

Rebonjour,

barbudor:
...

void test()

{
  typedef uint16_t *PointeurSurUint16;
  PointeurSurUint16 ptr = (uint16_t *)0x128;
 
  *ptr  = 55;
}




Voir même :



void test()
{
  typedef uint16_t *PointeurSurUint16;
  PointeurSurUint16 ptr = (PointeurSurUint16)0x128;
 
  *ptr  = 55;
}

Avec le « typedef », l'étoile « * » ne devrait-elle pas disparaitre pour la dernière ligne et s'écrire :

  ptr = 55;

L'une des raisons d'être du « typedef », ne plus avoir à utiliser le « * » devant le nom de la variable ?

Tu voulais voir si je suivais bien, c'est ça ? :grin:

L'ami René

Bonjour

L'utilisation d'une variable pointeur ne sera pas plus performante que la macro car le compilateur doit:

  • charger l'adresse de la variable pointeur
  • lire à cette adresse la valeur qui y est stockée
  • utiliser cette valeur comme l'adresse où écrire 55

Tu peux améliorer cela en déclarant le pointeur comme const ce qui permettra d'optimiser la 1ere étape.

De plus il te manque le qualificateur volatile sur le type uint16_t.

En utilisant un pointeur, tu sera toujours obligé d'utiliser '*'
Seule l'utilisation d'une référence permet de s'en passer mais alors impossible d'optimiser la 1ère étape.

Le typedef ne change rien au comportememt, ce n'est qu'un raccourci d'écriture.

A la louche, ce que va pondre le compilateur :

  typedef uint16_t *PointeurSurUint16;
  PointeurSurUint16 ptr = (PointeurSurUint16)0x128;
  
  ptr  = 55;  // soit ptr = 0x0037;

Supposons que le compilateur déclare la variable ptr à l'adresse SRAM 0x200 :
Assembleur AVR :

init:                // il faut initialiser la variable ptr avec la valeur 0x0128 au début du programme
  LDI  R24,0x01
  LDI  R31,0x02
  LDI  R30,0x01   // incrémenter Z, on écrit toujours le MSB en premier  
  ST   Z,R24      // mets la valeur 0x01 à l'adresse Z (0x201)
  LDI  R24,0x28
  ST   -Z,R24      // mets la valeur 0x28 à l'adresse Z-1 (0x200)
             // soit 8 cycles d'horloge
utilisation:
  LDI  R24,0x00
  LDI  R31,0x02
  LDI  R30,0x01
  LD   R29,Z      // récupérer le MSB de l'adresse pointée par ptr (@0x201)
  LD   R28,-Z     // récupérer le LSB de l'adresse pointée par ptr (@0x200)
  ST   Y,R24      // envoyer la valeur sur le registre pointé par Y
  LDI  R24,0x37   // charger la valeur 0x37
  ST   -Y,R24     // envoyer la valeur sur le registre pointé par Y-1
             // soit 12 cycles

en passant par un #define :

#define servomoteurBroche46 OCR5A

  servomoteurBroche46 = 55;

Assembleur AVR :

init:                // rien à initialiser sur ce coup là
             // soit 0 cycle
utilisation:
  LDI R24,0x00   // charger la valeur 0x00
  LDI R31,0x01
  LDI R30,0x29
  ST  Z,R24      // et l'envoyer à Z=0x0129  (MSB)
  LDI R24,0x37
  ST  -Z,R24      // envoyer la valeur 0x37 à l'adresse Z-1 (LSB)
             // soit 8 cycles

J'ai optimisé à mort ma traduction en assembleur, et je doute qu'AVR_gcc le fasse aussi bien (à moins que je me sois planté, ce qui fort probable)... Mais si je fais la compilation avec ma version windows de l'IDE, je pense qu'il doit y avoir un rapport 3 entre les vitesses d'exécution.

A force d'étudier l'assembleur, je vais finir par m'y mettre...

Super_Cinci:
Assembleur AVR :

init:                // rien à initialiser sur ce coup là

// soit 0 cycle
utilisation:
  LDI R24,0x00  // charger la valeur 0x00
  LDI R31,0x01
  LDI R30,0x29
  ST  Z,R24      // et l'envoyer à Z=0x0129  (MSB)
  LDI R24,0x37
  ST  -Z,R24      // envoyer la valeur 0x37 à l'adresse Z-1 (LSB)
            // soit 8 cycles




J'ai optimisé à mort ma traduction en assembleur, et je doute qu'AVR_gcc le fasse aussi bien (à moins que je me sois planté, ce qui fort probable)...

Je crains que ce soit une bataille perdue d'avance. Les compilateurs modernes sont quand même des monstres d'ingéniosité lorsqu'il s'agit d'optimisation. Je n'ai pas vérifié, mais dans un cas comme celui-ci, OCR5A étant situé dans la mémoire E/S étendue, je suis presque certain que le compilateur aurait utilisé STS au lieu de ST : la taille du code aurait été la même, mais au moins 2 cycles d'horloge auraient été économisés.

Super_Cinci:
A force d'étudier l'assembleur, je vais finir par m'y mettre...

Bonne idée. Même si je suis persuadé que se lancer en assembleur dans l'espoir de « battre » les compilateurs est illusoire (je parle du cas général, il y a des exceptions), la connaissance du fonctionnement « bas niveau » du MCU permet bien souvent de gagner en efficacité, même lorsqu'on programme en C. En plus les filles trouvent ça super sexy. Ou pas.

Salut.

Dans ce cas,

STS 0x0129,R24

ferait la même chose que :

LDI R31, 0x01
LDI R30, 0x29
ST Z,R24

?

On gagne certainement une instruction (STS doit en prendre deux je crois) et deux cycles. Mais dans mon cas, j'ai un vieux compilateur, et il a tendance à charger Z deux fois avec deux valeurs consécutives au lieux de l'incrémenter (ADIW). contre ce compilateur, oui, je suis plus fort. j'aurais voulu essayer un compilateur plus récent, mais je n'arrive pas à l'installer (mais on s'en fout un peu je dirais, c'est pas le sujet ici).

Rebonjour,

LamiRene:
...
Je teste le code avec le « typedef ... » ... et je vous reviens avec les résultats comparatifs avec le code utilisant le « #define ... ».

Merci encore !

L'ami René

Bon, voilà, avec comme seule modification d'un « #define » par un « typedef » dans le code source :

...
#define Servomoteur46 OCR5A
...
    ServomoteurBroche46 = map (Servomoteur_1_Angle, Servomoteur_1_AngleMinimum, 
                          Servomoteur_1_AngleMaximum, Servomoteur_1_FrequenceMinimum,
                          Servomoteur_1_FrequenceMaximum);
...

Le tout change en :

...
typedef uint16_t *PointeurBrochePWM;
PointeurBrochePWM ServomoteurBroche46 = (uint16_t *)0x128;  
...
    *ServomoteurBroche46 = map (Servomoteur_1_Angle, Servomoteur_1_AngleMinimum, 
                         Servomoteur_1_AngleMaximum, Servomoteur_1_FrequenceMinimum,
                         Servomoteur_1_FrequenceMaximum);
...

Je passe pour la version « #define » de 59,991 octets à 60,326 octets pour la version « typedef », une différence conséquente de 335 octets pour cette seule modification, c'est énorme.

La question des pointeurs peut être considérée comme résolue.

Je remercie chaleureusement tous les intervenants sur cette discussion pour leurs apports inestimables !

C'est beau de voir une telle entraide bénévole aussi altruiste et généreuse !

Merci encore !

L'ami René