Aller au contenu


Photo
- - - - -

Arduino - Trucs et astuces


  • Veuillez vous connecter pour répondre
6 réponses à ce sujet

#1 Oracid

Oracid

    Pilier du forum

  • Modérateur
  • PipPipPipPipPip
  • 6 129 messages
  • Gender:Male

Posté 10 janvier 2022 - 09:16

Je n'ai jamais compris pourquoi l'instruction Serial.print() était si peu pratique, sur Arduino.

Et là, je viens de découvrir une "fonction" String() dans le print, avec une syntaxe qui me surprend, mais qui fonctionne très bien.

Peut-être connaissez vous déjà ?

Voici un exemple :

 

int x=777, y=888, z=999;
 
void setup() {
  Serial.begin(9600);
}
 
void loop() {
  Serial.print(String() +("\n\t  x = ") +x +("\t  y = ") +y +("\t  z = ") +z);
  delay(1000);
}


#2 Sandro

Sandro

    Pilier du forum

  • Membres
  • PipPipPipPipPip
  • 1 119 messages
  • Gender:Male

Posté 10 janvier 2022 - 10:54

Bonsoir,

je dois avouer que je n'avais jamais pensé a utiliser la concaténation de chaînes et les conversions implicites de cette manière là.

 

À première vue, c'est super barbare comme méthode :

1) On crée un 1er String de longueur 0 (c'est ce que fait String() )

2) On crée un 2nd String contenant "\n\t x = "

3) On crée un 3ième String contenant la concaténation des strings 1 et 2 : il contiendra donc "\n\t x = "     (NB : les strings 1 et 2 sont désormais inutiles, si le compilateur est bien fait ils sont supprimés)

4) On crée un 4ième String contenant la valeur de x (ie "777")

5) On crée un 5ième String contenant la concaténation des strings 3 et 4 : il contient donc "\n\t x = 777"

...

13) On crée un 13ième String contenant la concaténation des strings 11 ( "\n\t  x = 777\t  y = 888\t  z = ") et 12 ("999") : il contient donc "\n\t  x = 777\t  y = 888\t  z = 999"

 

 

Une méthode alternative (mais elle aussi lourde), est l'utilisation de la fonction sprintf, qui permet de créer une chaine de caractères (tableau de char terminé par \0) par formatage (sur le même principe que printf qu'on utilise en C "normal" pour afficher du texte dans la console). sprintf permet d'insérer des entiers signés ou non signés, ou d'autres chaines de caractères dans une chaine de caractères. NB : la version du C "normale" permet aussi d'y insérer des flotants et des doubles, mais la version allégée présente pour l'arduino a fait l'impasse sur les nombres à virgule.

 

 

Honnêtement, je m'attendais à ce que ta méthode soit la plus lente, mais au final c'est la plus rapide :

volatile int x=777, y=888, z=999;

char buffer[40];

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

void loop() {
    // put your main code here, to run repeatedly:
    long start=millis();
    for(int i=0; i<10000; i++)
    {
        //4951
        //Serial.print(String() +("\n\t  x = ") +x +("\t  y = ") +y +("\t  z = ") +z);

        //5745
        //Serial.print("\n\t  x = ");
        //Serial.print(x);
        //Serial.print("\t  y = ");
        //Serial.print(y);
        //Serial.print("\t  z = ");
        //Serial.print(z);

        //5006
        //sprintf(buffer,"\n\t  x = %d\t  y = %d\t  z = %d",x,y,z);
        //Serial.print(buffer);
    }
    long stop=millis();
    Serial.println("\n");
    Serial.println(stop-start);
    while(1);
}

Les valeurs en commentaires sont les durées pour chacune des méthodes (ie le résultat de stop-start). Rq : j'ai mis x, y et z en volatile pour éviter que le compilateur se rende compte qu'on affiche toujous la même chose (ce qu'on ne fait pas en pratique)

Ta méthode est ici la plus rapide, sprintf est à peine plus long, et les lignes séparées est ce qu'il y a de plus lent.

Par contre, je penses que ta méthode utilise plus de RAM que les lignes séparées (pour sprintf je sais pas trop). NB : il s'agit de RAM sur la pile ("variables alouées dynamiquement"), donc ça  n'apparait pas à la compilation, et vu que c'est sur une ligne, on peut pas aller lire et afficher la taille de la pile utilisée (ça marcherait peut-être via des interruptions, mais la flemme d'essayer)



#3 Oracid

Oracid

    Pilier du forum

  • Modérateur
  • PipPipPipPipPip
  • 6 129 messages
  • Gender:Male

Posté 11 janvier 2022 - 08:05

Super ! Merci Sandro pour cette analyse.

 

Une méthode alternative (mais elle aussi lourde) . . .

Perso, je ne trouve pas que cette méthode soit lourde, au contraire, cela va me permettre de supprimer tous ces print() que l'on met à la queue leu leu.

 

Ton allusion à la conversion implicite m'a interpellé. Voici une nouvelle version. As-tu une idée de comment imprimer plus de 2 caractères après la virgule pour la variable  y ?

 
int x=777;
float y=-888.12345;
long z=-999999999;
 
void setup() {  
  Serial.begin(9600);
}
 
void loop() {
  Serial.print(String() +("\n\t  x = ") +x +("\t  y = ") +y +("\t  z = ") +z);
  delay(1000);
}


#4 Sandro

Sandro

    Pilier du forum

  • Membres
  • PipPipPipPipPip
  • 1 119 messages
  • Gender:Male

Posté 11 janvier 2022 - 10:44

Quand je parles de "lourd", je parles en terme de calcul internes et/ou mémoire utilisée, pas de syntaxe (donc pour beaucoup de programmes ça ne pose aucun problème, c'est que pour les quelques un où on est limité en vitesse d'exécution ou en mémoire qu'il faut se poser la question).

 

Pour afficher un nombre différent de 2 chiffres après la virgule, il faut faire une conversion explicite en String, ce qui permet d'ajouter un argument optionnel : le nombre de chiffres après la virgule :

int x=777;
float y=-888.12345;
long z=-999999999;
 
void setup() {  
  Serial.begin(9600);
}
 
void loop() {
  Serial.print(String() +("\n\t  x = ") +x +("\t  y = ") +String(y,3) +("\t  z = ") +z);
  delay(1000);
}

Le code affiche maintenant 3 chiffres après la virgule.

Si javais mis juste String(y), ça aurait mis les 2 chiffres par défaut.

Pour x et y (des entiers), on ne peut pas préciser le nombre de chiffres après la virgule (ou alors il faut convertir en float : par exemple String( (float)x,1) donnera 777.0 ). En revanche, on peut choisir la base dans laquelle on les affiche : String(x) ou String(x, DEC) : par défaut, en décimal ; String(x, HEX) : en héxadécimal (base 16), String(x, BIN) : en binaire.

 

A noter que Serial.print(ln) offre les mêmes options de formatage (nombre de chiffres après la virgule, BIN/DEC/HEX).

 

Attention, un float contient "seulement" 23 bits pour le "nombre" de base (+1 pour le signe et 8 pour l'exposant), donc ça ne fait qu'une précision de 6 à 7 chiffres au total. Donc si tu as un nombre avec 4 chiffres avant la virgule, et que tu demandes 4 chiffres en plus après la virgule, le dernier est aléatoire, et le précédent peut avoir une légère erreur. Une fois que tu fais des calculs sur des floats, les erreurs s'accumulent, donc tu peux considérer qu'au mieux tu as 5-6 chiffres significatifs, si tu fais du n'importe quoi, tu en aura aucun (par exemple : (1.0 + 0.00000000000001 - 1.0)*100000000000000 vaut 0.0, alors que (1.0 - 1.0+ 0.00000000000001)*100000000000000 vaut 1.0

 

 

Si tu veux afficher un nombre fixe de caractères pour un float, tu peux faire :

String(y,10).substring(0,4)

où 10 est un nombre assez grand pour être sur d'avoir assez de décimales

et 4 est le nombre de caractères que tu souhaites (nb : la virgule compte)

 

 

Si tu veux un nombre fixe de caractères pour un int/long, ça devient plus complexe, et je n'ai plus de solution compacte :

String s1="00000000"+String(x);
String s2=s1.substring(s1.length()-4);
Serial.print(String()+"x="+s2);

//en plus compact :
String s1="00000000"+String(x);
Serial.print(String()+"x="+s1.substring(s1.length()-4););

où 4 est le nombre de caractères à afficher.

Nb : si tu préfères des espaces à des 0, il suffit de rajouter "000000000" par "                     "
 



#5 Oracid

Oracid

    Pilier du forum

  • Modérateur
  • PipPipPipPipPip
  • 6 129 messages
  • Gender:Male

Posté 11 janvier 2022 - 02:14

J'étais persuadé que tu allais me donner la réponse, mais là, je dois dire que je suis comblé.

Je ne suis pas certain d'avoir l'occasion de tout utiliser, mais le String(y,3) ou le String( (float)x,1), je vais mettre ça dans un coin de ma tête.

 

Un grand merci, Sandro.



#6 Oracid

Oracid

    Pilier du forum

  • Modérateur
  • PipPipPipPipPip
  • 6 129 messages
  • Gender:Male

Posté 24 septembre 2022 - 01:33

Les sorties PWM ( ~ ) de l'Arduino fournissent 2 fréquences différentes (490Hz et 976Hz) en fonction de la pin utilisée, https://www.arduino....io/analogwrite/

 

Mais, peut-on changer la fréquence de sortie de ces pins PWM ?

 

Et bien, oui ! Voici la solution :

 



#7 Sandro

Sandro

    Pilier du forum

  • Membres
  • PipPipPipPipPip
  • 1 119 messages
  • Gender:Male

Posté 26 septembre 2022 - 07:35

En effet, on peut changer les fréquences PWM.

En plus des fréquences déjà citées, sauf erreur de ma part, on peut obtenir le double de chacune des fréquences mentionnées dans la vidéo (nb : le fast-pwm n'est pas aligné sur le milieu de la période, donc un changement de valeur de pwm crée un écart "anormal" entre deux pulses au moment du changement).

À noter également que le timer1 (pins 9 et 10) peut être configuré à une résolution plus élevée que 8 bits (on a le choix entre 8,9,10 ou 16 bits si j'ai bien compris).

 

Et une chose importante passée sous silence par la vidéo : quand on modifie la fréquence des PWM, on modifie le timer sous-jacent, ce qui impacte tout ce qui utilise ce timer :

- timer0 (pins 5 et 6) est utilisé par les fonctions millis et micros : si vous multipliez par X la fréquence des PWMs du timer0, le temps mesuré par millis/micros s'écoulera X fois plus vite

- timer1(pins 9 et 10) est utilisé par la bibliothèque servo : si vous y touchez, les servo-moteurs commandés depuis la bibliothèque servo ne fonctionneront plus

- timer2(pins 3 et 11) est probablement le plus safe à bidouiller. Il est entre autre utilisé par la fonction tone (qui fait des sons)

NB : si vous utilisez des libraries ayant de près ou de loin un rapport avec le temps, vérifiez bien s'ils utilisent un timer donné avant de le bidouiller.






0 utilisateur(s) li(sen)t ce sujet

0 members, 0 guests, 0 anonymous users