Arduino qui crashe aléatoirement

Bonjour à toutes et à tous,

Je suis en train de créer un drone quadricoptère de A à Z. Il sera commandé depuis mon ordinateur et une manette de jeu en Wifi grâce au Wifi Shield (au début) et, si possible, avec l’Arduino YUN (plus tard) pour bénéficier du Wifi N (et donc d’une plus grande portée).

J’ai déjà reçu le matériel. Je l’ai déjà monté et j’ai commencé à faire mes tests de stabilisation.

Bref, je suis arrivé à la programmation de l’engin et je rencontre un problème assez embêtant sur un projet de ce type : l’arduino (UNO rev 2) qui pilote les moteurs à partir de la centrale inertielle (MPU-9150) plante lamentablement de manière aléatoire. Cela peut aller de 10-15 sec après l’allumage à plusieurs minutes après, mais j’ai toujours le même résultat : plus aucune donnée sur le port série et les moteurs restent allumés au régime qu’ils avaient juste avant le plantage.
Je suis alors obligé d’appuyer sur le bouton reset de la carte (en évitant les hélices) pour la redémarrer.

J’ai déjà rencontré des problèmes sur ce projet mais jusque là, c’était des erreurs dans le programme.

Le code ci-dessous ne sert qu’à stabiliser le drone. Il me renvoie les informations de position sur le port série. Je peux changer dynamiquement les coefficents (kP, kI et kD) pour la régulation. Ce drone étant symétrique, j’applique la même régulation sur le tangage et le roulis.

En plus, les résultats des tests de stabilisation sont concluants. J’ai juste ce problème qui vient me gêner dans la poursuite de mon projet.

Le code :

#include <SPI.h>

#include <WiFiUdp.h>
#include <WiFiClient.h>
#include <WiFi.h>
#include <WiFiServer.h>

#include <SerialCommand.h>
#include <MPU9150.h>

#include <Servo.h>
#include <Wire.h>
#include <math.h>

// Axis name
#define X 0
#define Y 1
#define Z 2

// Throttle limits
#define MIN_THROTTLE 1105
#define MAX_THROTTLE 1455

// Parameters

// kP = 0.60Ku
// kI = 2Kp/Tu
// kD = 0.15KpTu

//float kP = 0.15, kI = 0.05, kD = 0.15; // kP JAMAIS A 1 BORDEL !
float kP = 0.62, kI = 0.03, kD = 0.08; // kP JAMAIS A 1 BORDEL !

// Motors
Servo ESC1, ESC2, ESC3, ESC4;

// Time
unsigned long previousMillis = 0;
unsigned long currentMillis = 0;

// Quadcopter orientation
double gyro_angle[3] = {0};
double accl_angle[3] = {0};
double angle[3] = {0};

MPU9150 mpu;

// Serial
//SerialCommand sCmd;

char emergencyStop = 0;

/* --- SERIAL HANDLERS --- */

void setP()
{
  char *arg;
  arg = sCmd.next();

  if(arg != NULL)
  {
    float P = atof(arg);
    kP = P;
    
    Serial.print("kP set to ");
    Serial.println(kP);
  }
}

void setI()
{
  char *arg;
  arg = sCmd.next();

  if(arg != NULL)
  {
    float I = atof(arg);
    kI = I;
    
    Serial.print("kI set to ");
    Serial.println(kI);
  }
}

void setD()
{
  char *arg;
  arg = sCmd.next();

  if(arg != NULL)
  {
    float D = atof(arg);
    kD = D;
    
    Serial.print("kD set to ");
    Serial.println(kD);
  }
}

void powerOff()
{
  emergencyStop = 1;
  Serial.println("Emergency stop has been actived ! Shutting down ...");
}

void showPID()
{
    Serial.print("kP set to ");
    Serial.println(kP);
    Serial.print("kI set to ");
    Serial.println(kI); 
    Serial.print("kD set to ");
    Serial.println(kD);
    
    delay(100);
}


void unknownCommand(const char *command)
{
  Serial.println("Unkown command");
}

/* --- UTILS --- */

float rangeThrottle(float value)
{
  return MIN_THROTTLE + value * ((MAX_THROTTLE - MIN_THROTTLE) / 100);
}

/* --- PID --- */

float PIDX(float err, float dT)
{
  float P = 0, I = 0, D = 0;
  static float sumerrX = 0;
  static float lasterrX = 0;
  
  sumerrX += (err * dT);
  
  P = err * kP;
  I = sumerrX * kI;
  D = ((err - lasterrX)/dT) * kD;
  
  lasterrX = err;
  return P + I + D;
}

float PIDY(float err, float dT)
{
  float P = 0, I = 0, D = 0;
  static float sumerrY = 0;
  static float lasterrY = 0;
  
  sumerrY += (err * dT);
  
  P = err * kP;
  I = sumerrY * kI;
  D = ((err - lasterrY)/dT) * kD;
  
  lasterrY = err;
  return P + I + D;
}

/* --- MOTORS --- */

void dispatchMotors(float thr, float pitch, float roll, float yaw)
{
  // Convert actions into speed
  float thr1 = (thr - pitch + roll - yaw);
  float thr2 = (thr - pitch - roll + yaw);
  float thr3 = (thr + pitch + roll + yaw);
  float thr4 = (thr + pitch - roll - yaw);

  // Constraint values
  if(thr1 <= 0)
    thr1 = 0;
  else if(thr1 >= 100)
    thr1 = 100;
  
  if(thr2 <= 0)
    thr2 = 0;
  else if(thr2 >= 100)
    thr2 = 100;
    
  if(thr3 <= 0)
    thr3 = 0;
  else if(thr3 >= 100)
    thr3 = 100;
    
  if(thr4 <= 0)
    thr4 = 0;
  else if(thr4 >= 100)
    thr4 = 100;
  
  refreshMotors(thr1, thr2, thr3, thr4);
}

void refreshMotors(float thr1, float thr2, float thr3, float thr4)
{
  // Convert throttle into time
  float value1 = rangeThrottle(thr1), value2 = rangeThrottle(thr2), value3 = rangeThrottle(thr3), value4 = rangeThrottle(thr4);

  // 0 is a "stop motors" value
  if(thr1 == 0)
    value1 = 0;
  else if(thr2 == 0)
    value2 = 0;
  else if(thr3 == 0)
    value3 = 0;
  else if(thr4 == 0)
    value4 = 0;

  // Send motors' values
  writeMotors((int) value1, (int) value2, (int) value3, (int) value4);
}

void writeMotors(int time1, int time2, int time3, int time4)
{
  ESC1.writeMicroseconds(time1);
  ESC2.writeMicroseconds(time2);
  ESC3.writeMicroseconds(time3);
  ESC4.writeMicroseconds(time4);
}

/* --- INIT --- */

void MPUInit()
{
  mpu.Init();
  mpu.setAccelScale(4);
  mpu.setGyroScale(1000);

  delay(2000);
}

void MotorsInit()
{
  ESC1.attach(9);
  ESC2.attach(6);
  ESC3.attach(5);
  ESC4.attach(3);
  delay(15);

  writeMotors(0, 0, 0, 0); // Init ESCs

  delay(3000);
}

void setup()
{
  // Initialization

  // Serial Init
  Serial.begin(115200);
  sCmd.addCommand("P", setP);             // kP
  sCmd.addCommand("I", setI);             // kI
  sCmd.addCommand("D", setD);             // kD
  sCmd.addCommand("S", showPID);          // Show PID's coeffs
  sCmd.addCommand("STOP", powerOff);      // Emergency stop
  sCmd.setDefaultHandler(unknownCommand); // Others cases

  // Quadcopter Init
  MPUInit();
  MotorsInit();
  
  previousMillis = millis();
}

void loop()
{
  sCmd.readSerial();  // Check for commands
  
  currentMillis = millis();
  double dT = (currentMillis - previousMillis) / 1000.0;
  double frequency = 1 / dT;
  previousMillis = currentMillis;
  
  // Get raw data
  mpu.getScaled();
  
  //Processing sensors values
  gyro_angle[X] = angle[X] + mpu.gyroVector[X] * dT;
  gyro_angle[Y] = angle[Y] + -(mpu.gyroVector[Y]) * dT;
  gyro_angle[Z] = gyro_angle[Z] + mpu.gyroVector[Z] * dT;
  
  accl_angle[X] = -(atan2(-mpu.accelVector[Y], sqrt(square(-mpu.accelVector[X]) + square(mpu.accelVector[Z]))) * 57.2957795);
  accl_angle[Y] = -(atan2(-mpu.accelVector[X], sqrt(square(-mpu.accelVector[Y]) + square(mpu.accelVector[Z]))) * 57.2957795);
  
  // Complementary filter
  angle[X] = 0.96 * gyro_angle[X] + 0.04 * accl_angle[X];
  angle[Y] = 0.96 * gyro_angle[Y] + 0.04 * accl_angle[Y];
  
  // Process stabilisation
  float Xaction = PIDX(0 - angle[X], dT);
  float Yaction = PIDY(0 - angle[Y], dT);

  if(!emergencyStop)
  {
    // Send action to motors
    dispatchMotors(40, (int) Xaction, (int) Yaction, 0);
  }
  else
  {
    writeMotors(0, 0, 0, 0); // Shut down motors to prevent damage
  }

  Serial.print(frequency);
  Serial.print("Hz,");
  Serial.print(angle[X]);
  Serial.print(",");
  Serial.print(angle[Y]);
  Serial.print(",");
  Serial.print(Xaction);
  Serial.print(",");
  Serial.print(Yaction);
  Serial.println();
}

Informations supplémentaires (certaines sont inutiles pour le problème mais indispensable si vous voulez en savoir plus sur le projet) :

  • Carte de pilotage : Arduino UNO rev 2
  • Récepteur Wifi : Arduino Wifi Shield
  • Centrale inertielle : MPU-9150 => J’utilise la breakout board produite par Sparkfun et vendu sur lextronic : MPU-9150
  • Châssis / Frame : Configuration en X
  • Moteurs : SunnySky V3508 580Kv
  • ESCs : RcTimer 20A Opto (SimonK)
  • Hélices : 12"x3.8"
  • Batterie (Li-Po) : Turnigy Multi-Star 4S 10000mAh (10C) => 14.8V nominale, 16.8V chargé (le régulateur de l’arduino suffit actuellement mais il est peut-être aux limites, je peux rajouter un régulateur 7805 au montage si cela est nécessaire)
  • Masse : entre 1,8 et 2 kg (sans charge utile)
  • Taille : 40cm de côté (entraxe moteurs), environ 56cm en diagonale (entraxe moteurs)

Merci d’avance !

PS : Est-ce qu’il y a un moyen de colorer le code sur le forum ? J’ai pas trouvé de fonction “coloration syntaxique” pour le langage Arduino.

Salut!

Non pas de coloration syntaxique, c'est très bien comme tu la fait!

As tu des erreurs de compilation?

Tu dis que le régulateur 5v de l'arduino est à ces limites, tu l'utilise pour alimenté quoi derrière.

Il est possible que tu lui envois des parasites...

Tu pourrais faire des essais en utilisant un regulateur externe pour tout ce qui n'a pas besoin d’être alimenté par l'Arduino.

Dudu.

Neopibox: - Batterie (Li-Po) : Turnigy Multi-Star 4S 10000mAh (10C) => 14.8V nominale, 16.8V chargé (le régulateur de l'arduino suffit actuellement mais il est peut-être aux limites, je peux rajouter un régulateur 7805 au montage si cela est nécessaire)

avec une tension pareil le régulateur doit chauffer, tu aurais intérêt (sur ce genre de montage ou l'autonomie est un critère important) d'utiliser un module step-down a découpage (rendement de 90 a 95% au lieu des 30% avec un 7805)

+1

Je ne serais pas étonné outre mesure si tu trouvais que tu manques de mémoire.

Re,

Que de réponses à ce que je vois ! Je vais répondre point par point :

-> Le régulateur de l'arduino alimente la carte, le shield et l'IMU. A l'avenir, il aurait alimenté l'arduino YUN (si elle a un régulateur ...) + IMU + capteur barométrique + GPS.

-> Non, j'ai pas d'erreurs de compilation. Le programme se lance et après la carte plante. C'est comme si elle "freezait".

-> Le régulateur chauffe pas mal mais je me suis pas trop inquiété ...

Quand je rentrerais chez moi (je suis en appart actuellement en semaine) ce Week-End, je testerais un régulateur 7805 qui traîne par là. Si c'est bien ça le problème, j'envisagerais l'achat d'un module "step-down"

Merci de m'avoir fait penser du côté de l'alimentation. Sachant que la batterie alimente les 4 moteurs + l'arduino, il est fort probable que les parasites en provenance des moteurs remontent jusqu'à l'arduino, en plus de ceux généré par le régulateur interne de l'arduino

Personnellement, je pensais plutôt à une fuite mémoire dans mon code ou, du moins, à une "surcharge" mémoire.

PS : Je ne connaissais pas le nom de ces modules "step-down" mais j'en avais déjà vu. PS 2 : Si j'ai un régulateur externe, je doit alimenter la carte via la pin "5V" ou dans "Vin" ? Je suppose que c'est "5V" vu que l'autre pin est prévu pour réguler ...

EDIT : C'est vrai que je n'ai pas optimisé le programme niveau mémoire. Je vais essayer de le faire quand j'aurais le temps.

Tu pourrais déjà libéré de la flash en mettant (F(“mon commentaire”)

exemple:
Serial.print("kI set to ");
Serial.println(kI);

a remplacer par

Serial.print(F("kI set to "));
Serial.println(kI);

Pour moi ta carte Arduino doit être alimentée comme tu le fait actuellement et la carte wifi si tu veux.

et pour le reste tu utilise ton alim step down qui elle est branché sur ton alim en parallele a l’Arduino et non pas sur les sortie 5v ou 3.3 de l’Arduino.

Ce qui est important dans un projet comme cela se sont les masses commune sur les alim, mais tu devrais pas avoir de problème vu que tu prend ton alim principale sur la batterie.

J’ai pas tout suivi, tu peux listé tout tes dipoles, modules, moteur, avec les consommations nominales.

Et pour comprendre un petit schéma serais intéressant, même un truc vite fais papier, ou paint…

Tes moteurs sont alimenté comment?

Il nous manque trop d’info pour te donné une réponse précise…

Utiliser memoryFree pour suivre l'évolution de l'utilisation de la mémoire.

dudux2: Tu pourrais déjà libéré de la flash en mettant (F("mon commentaire")

J’espère bien que le compilateur stocke les chaines constantes dans la flash même sans mettre de F() Par exemple pour Serial.print("kI set to "), j'espère bien que "kI set to " est en flash

Edit: Je viens de faire l'essai et tu as raison, la chaîne semble bien être recopiée en RAM dans ce cas.

Sur les Atmega c'est lié au fait que l'adressage mémoire n'est pas continu (les deux plages démarre à 0x0000) il faut donc spécifier au compilateur où on veut stocker une variable et jouer avec des fonctions spécifiques.