TIPE

Bonjour,
Dans le cadre d'un TIPE (épreuve de projet personnel en CPGE) je souhaite réaliser un asservissement à l'aide d'une arduino.
J'ai déjà codé un petit morceau pour l'analyse fréquentielle d'un servo moteur.
Le code suivant à pour fonction de synthétiser un signal sinusoidal en l'échantillonant afin de créer une grille ordre(échelon)/temps à envoyer au servomoteur et de mesurer la réponse. Initialement prévu pour 100 mesures il nous faut maintenant 500 points de mesure (voir plus). En changeant simplement la taille des tableaux de mesure et la durée des boucles je pensais que cela allait suffir....

Hélàs ça bug: à 200 points les ordres ne sont plus executés et à 300 points la console me renvoit des symboles bizarres...

Voila le code:

#include <Servo.h>
Servo servo;
int potpin;

int RMAX= 1023;

int readCommand ();

void setup() 
{
  potpin= 1;
  servo.attach(9);
  Serial.begin(9600);
  
  servo.write(90);
  int ampE=0, start;
  double pulsE= 0;
  double tEch= 0;
  
  Serial.println("Signal d'entree: ");
  
  Serial.print("Amplitude: ");
  ampE= readCommand();
  Serial.println(ampE);
  
  Serial.print("Pulsation: ");
  pulsE= readCommand();
  Serial.println(pulsE);
  
  tEch= 3.1415/(10*pulsE);
  
  double tableau[100]={0};
  int mesure[100]={0};
  for(int i=0; i<100; i++)
  {
    tableau[i]= ampE*sin(pulsE*i*tEch)+90;
    Serial.println(i*tEch);
  }
  Serial.println(" ");
    Serial.println(" ");
      Serial.println(" ");
  for (long i=0; i<100; i++)
  {
    Serial.println(tableau[i]);
  }  
    
  
  start= millis();
  for (long i=0; i<100; i++)
  {
    servo.write(tableau[i]);
    while((millis()-start)<i*tEch*1000){}
    mesure[i]= analogRead(potpin);
  }
  
  Serial.println(" ");
   Serial.println(" ");
    Serial.println(" ");
  for(long i=0; i<100; i++)
  {
    mesure[i]= map(mesure[i], 0,1023 ,0,180);
    Serial.println(mesure[i]);
    
  }
  
  
  
  
  /*for()
  {
    
    
  }*/
  
  Serial.println("...Fin de sequence");
  
}

void loop() 
{
  
}

int readCommand ()    // bidouillage pour envoyer des entiers (et contrer le ASCII de mes deux...) Si quelqu'un connnait une fonction plus    
{                              // conventionnelle
  int detec, nbrByte, actByte, nbr;
  nbr=0;
  detec=0;
  while(detec==0)
  {
    detec= Serial.available();
  }
  delay(5);
  
  nbrByte= Serial.available();
  for(long i=0; i<nbrByte; i++)
  {
    actByte= Serial.read();
    nbr= nbr*10 + actByte -48;
  }
  return nbr;
}

L'Arduino n'a pas beaucoup de mémoire. Il va falloir envisager de stocker les valeurs sur une carte mémoire externe.

ou alors, tu fais envoyer tes valeurs par le port série, que tu inscrits dans un fichier depuis un programme sur le PC. Parce que ça peut revenir cher à force d'acheter du matos

Et dans la librairie 'math.h', tu as la constante pi :slight_smile:

Bonjour
Le codage 'trivial' utilisé içi convient sur un PC largement pourvu de Ram.
Cette ram est le point faible de l'Arduino. Il faut donc ruser et contourner l'obstacle.

Remarque : Tu sors par 'servo.write' des valeurs entières sur l'intervalle [0, 180]
Inutile de garnir un tableau centaines de valeurs 32bits (double) pour synthétiser une sinusoïde sommaire
Il ya a un énorme gaspillage de la ressource la plus rare sur L'Arduino !!

Je propose d'aller faire quelques recherches sur la Synthèse Numérique Directe (DDS).
Içi, vu le contexte 'servomoteur', pas besoin de puce DDS spécialisée.
Tout peut être mis dans l'Arduino.
-> Un tableau constant (mémoire Flash) contient une période (voire 1/4 de période) de sinusoïde, codée sur 8 bits avec peut etre 512 ou 1024 points. (Ce tableau peut être créé avec un tableur.)
A partir de cette unique sinusoide en ROM on peut générer pour servo.write des sinusoides de fréquence variable.

Adieu fonction sinus, variables 'double'.....

Hélàs je n'ai ni les connaissances ni le materiel ni le temps pour envisager un support de stockage extérieur, quand à la solution par port série l'envoi de données est soit trop instable soit prend trop de temps...

Je pense cependant retenir la solution DDS (j'ai pas trop compris vu que je n'ai pas trouvé grand chose là desssus). Je vais remplir un tableau d'un quart de période et le parcourir dans un sens puis dans l'autre à vitesse variable et voir ce que ça donne. Par contre je ne comprend pas une chose: si j'ai un tableau de 100 double j'ai donc 800 octets de mémoire pris. En comptant le poid du sketch et les deux tableaux j'aurais donc quelque chose comme 10000 octetx max. Or sur l'arduino on dispose de 32ko non? dans ce cas je ne dépasse pas la limite alors pourquoi ça bugue?

Du coup, moi j'opterais pour la carte sd quand meme...

SparkFun microSD Transflash Breakout - BOB-00544 - SparkFun Electronics 7.47€, ça passe encore

Tu écris dessus un fichier .txt avec séparation ";" et t'as plus qu'à ouvrir avec excel

LaurentD:
Par contre je ne comprend pas une chose: si j'ai un tableau de 100 double j'ai donc 800 octets de mémoire pris. En comptant le poid du sketch et les deux tableaux j'aurais donc quelque chose comme 10000 octetx max. Or sur l'arduino on dispose de 32ko non? dans ce cas je ne dépasse pas la limite alors pourquoi ça bugue?

Sauf que ton tableau n'est pas stocké en flash, mais en RAM. Et une Arduino Uno n'en dispose que de 2ko... Par contre si tu utilises un tableau constant comme le suggère al1fch, celui-ci sera stocké en flash. Et là ça passe...

PS : pense à utiliser les balises code, ça facilite la lecture...

exemple de DDS simple codée dans un Arduino (mega16pour comprendre le principe et manipuler un peu :
http://interface.khm.de/index.php/lab/experiments/arduino-dds-sinewave-generator/
Les performances devraient suffire pour gérer le servo.
0 à 16kHz avec une période de sinus sur 256 points !

Bonjour,

Il y a un défaut d'information et de compréhension.
Les Micro-contrôleurs RISC, comme les familles ATmega, sont en grande majorité basés sur des architectures Harvard (Mémoire programme et Mémoire de données séparées).
Les processeurs utilisent à grande majorité une architecture Von Neumann (Mémoire commune pour programme et données).

Dans la datasheet de l'ATMega328P, celui utilisé sur les UNO, tu verras en première page qu'il comporte 2k de RAM (données) et 32k de Flash (programme).

Optimise ton programme pour un µcontrôleur et non pour un ordinateur, et ça devrait marcher.
Si un problème persiste, n'hésites pas à venir ici.

Bonne journée


Stéphane.

Oui, +1 avec Snootlab :

Tu inclus des multiplications 32bits dans des boucles while, et ça, ça prend du temps (entre autre). Sache aussi que la librairie Servo est nulle. Oui, tant pis pour celui qui l'a écrite, mais il utilise du delay(), du millis(), du INT à foison (10% environ du temps CPU), alors qu'en utilisant un timer en PWM, on gère très bien un servo, la seule utilisation soft est l'affectation à OCRNx de la tempo de commande du servo pour le faire bouger (utilisation CPU : 0%). Il faut que tu optimises ton programme, car là, tu te crois sur une machine surpuissante alors que tu 'es que sur un petit µP 8bits qui rame dès qu'on utilise des librairies toutes faites. oublies les fonctions delay(), millis() qui sont très mal écrites (désolé, mais j'ai trop galéré avec), et passe aux timers, tu verras, c'est 1000 fois plus rapide et tu sais exactement ce que tu fais.

Le timer1 en 16 bits te permet de générer une PWM de 15 à 20ms, et environ 2000 pas pour ton servo, la commande est déjà bien plus précise que ce que te donnera ton servo.

Certaines librairies toutes faites ne sont pas compatibles entre elles, car l'une va modifier un timer utilisé par l'autre etc etc, mais ça, il n'y a qu'en rentrant dedans qu'on peut le voir, car il n'y a pas de détails clairs. Non, pour de la précision, Arduino n'est pas l'outil idéal sauf si on oublie les librairies et qu'on code ses "drivers" soi-même en connaissance de l'utilisation des ressources que l'on utilise. Dans ce cas, tu feras ce que tu veux, mais ça demande du temps d'apprentissage... ton TIPE n'est pas à rendre demain, j'espère?

On croit gagner du temps en prenant un truc où il suffit d'empiler des codes déjà faits, mais on en perd bien plus quand il y a une embrouille entre les codes, car ici, il n'y a pas une boite de dialogue qui va s'afficher pour dire qu'une fonction tente d'utiliser une ressource déjà utilisée par un autre programme...

Merci de ces réponses cependant plus il y a d'erreurs et plus il y a pour moi de choses à expliquer donc tant mieux ^^ de plus la précision reste suffisante pour l'instant (ça marche)! donc je continue

Je vous tient au courant pour la suite

Par contre, quelqu'un saurait s'il est possible de pousser les décimales quand la carte renvoit une valeur? Parceque là j'ai le droit à 2 chiffres aprés la virgule ce qui est trés limitant...

Merci d'avance

2 chiffres après la virgule ??? normalement c'est pas limité... tu utilises bien le type float ?

envoie ton code pour voir

Floats have only 6-7 decimal digits of precision. That means the total number of digits, not the number to the right of the decimal point. Unlike other platforms, where you can get more precision by using a double (e.g. up to 15 digits), on the Arduino, double is the same size as float.

Up, le code je l'ai posté plus haut... Je n'ai toujours pas de réponse pour les décimales (j'ai essayé le Float mais cela ne change rien)
D'autre part je me suis rendu compte que pour une entrée: Amplitude: 60
Pulsation: 1
j'obtient une valeur de tEch= 0,31, pourtant je devrait avoir 0,16 ??! Des idées?

merci

Il ne m'a pas fallu bien longtemps pour voir cette ligne :

tEch= 3.1415/(10*pulsE);

Donc je te laisse calculer quand pulsE = 1...

Désolé pour le dernier post, j'étais trés fatigué et je me rend compte maintenant que ce n'était pas du tout ce que je voulais dire...J'avais un problême avec la fréquence d'échantillonage qui ne correspondait pas à celle que je souhaitais. Evidemment le calcul est juste mais c'est la formule qui ne convenait pas... Bref, j'ai reglé le problême avec un nouveau code (2.0 ^^) qui élimine ce problême (et bien d'autres...) Par contre j'ai toujours pas trouvé le moyen d'envoyer plus de deux décimales...

#include <Servo.h>
Servo servo;
int potpin;


float readCommand ();

void setup() 
{
  potpin= 1;
  servo.attach(9);
  Serial.begin(9600);
  
  int numP=0;
  servo.write(90);
  float ampE=0, start=0, currentT=0;
  float pulsE= 0;
  float tEch= 0;
  
  Serial.println("Signal d'entree: ");
  
  Serial.print("Amplitude: ");
  ampE= readCommand();
  Serial.println(ampE);
  
  Serial.print("Pulsation: ");
  pulsE= readCommand();
  Serial.println(pulsE);
  
  Serial.print("Nombre de points: ");
  numP= readCommand();
  Serial.println(numP);
  
  float tableau[numP];
  
  start= millis();
  
  for(long i=0; i<numP; i++)
  {
    currentT= millis();
    while((millis()-currentT) <4*3.1415/(numP*pulsE)*1000)
    {
      servo.write(90+ampE*sin(i*4*3.1415/numP));
    }
    tableau[i]= analogRead(potpin);
  }
  
  for (long i=0; i<numP; i++)
  {
    tableau[i]= map(tableau[i], 0,1023 ,0,180);
    Serial.println(tableau[i]);
  }
  
  Serial.println("test");
}

void loop() 
{
  
}

float readCommand ()
{
  float detec, nbrByte, actByte, nbr;
  nbr=0;
  detec=0;
  while(detec==0)
  {
    detec= Serial.available();
  }
  delay(5);
  
  nbrByte= Serial.available();
  for(long i=0; i<nbrByte; i++)
  {
    actByte= Serial.read();
    nbr= nbr*10 + actByte -48;
  }
  return nbr;
}

pour envoyer un float, peut-être devrais-tu envoyer un mot de 4 octets (la variable elle-même), puis la traduire sur le PC, car je crois que la fonction Serial.print() limite en interne le format envoyé sur le port série. Je n'ai pas vérifié, mais j'ai un prog qui envoie des données numériques par série à VB6 sur PC, et c'est dans le programme VB6 que je traite le format d'affichage à partir de la donnée numérique reçue (mais je ne joue qu'avec des word (entiers non signés 16 bits).

Normalement c'est la seule façon de faire, envoyé et traité de l'autre côté.
En fait le port série n'envoie qu'un octet à la fois, que tu fasses print("toto"), println("toto"), print(156445645), print(12.56) la seule méthode à manipuler le port série est :

void HardwareSerial::write(uint8_t c)

Les méthodes print, println, write (char[]) (String) (float) (int) ... ne font que traité les donnée et appelé celle ci.

Edit:

LaurentD:
Par contre j'ai toujours pas trouvé le moyen d'envoyer plus de deux décimales...

En relisant ton dernier message, ton problème c'est que tu n'arrive pas à atteindre un précision de plus de 2 décimale.
Le problème ne viendrait pas de là ?

Les variables float ont seulement 6 à 7 chiffres de précision. Ceci concerne le nombre total de chiffres, pas seulement le nombre à droite de la virgule. A la différence d'autres plateformes, où vous pouvez obtenir davantage de précision en utilisant une variable de type double ( c'est à dire avec plus de 15 chiffres), sur Arduino, les variables double sont de la même taille que les float.