Interaction avec les moteurs

Exploitation et premiers tests dans le cadre du projet CPEFly

Le

Cet article fait suite à Reverse engineering du firmware d'un drone Parrot (CPEFly), disponible ici.

Dans cette avant-dernière partie, nous allons mettre en pratique les informations obtenues dans l'article précédent : on va maintenant tenter de communiquer avec les moteurs du drone.

Petite note avant de commencer

Parallèlement à l'analyse du protocole, nous avons bricolé le châssis du drone. Nous avons : - coupé le connecteur des motorboards vers la carte mère pour y ressouder des câbles. - protégé les jonctions avec des gaines thermorétractables - fixé deux breadbords sur les deux cotés du drone pour qu'on puisse y connecter tous nos câbles (capteurs, motorboards etc..).

Châssis du drone Parrot ouvert : câbles ressoudés sur le connecteur des motorboards, prêts à rejoindre les breadboards

Breadboard fixée sur le flanc du châssis du drone pour y brancher les câbles des motorboards et des capteurs

De cette manière, on peut connecter les motorboards aux câbles du châssis en utilisant les connecteurs d'origine, puis connecter les câbles du châssis aux breadboard. On peut ensuite connecter le STM32 et tous les autres composants (capteurs, resistance, etc...) directement sur les breadboard.

Tests unitaires

Test préliminaire

Au niveau du circuit, je me suis muni d'une sonde logique, d'un arduino UNO et d'une petite breadboard avec d'effectué des tests unitaires sur les moteurs. J'ai réalisé les connexions suivantes entre la motorboard, l'Arduino et la sonde logique :

Arduino Motorboard Sonde logique
PIN 5V Alimentation 5V
GND GND GND
TX RX+TX Channel 0
PIN 4 + PIN 8 ENABLE Channel 1

Montage de test : Arduino simulant la carte mère, sonde logique et motorboard reliés sur le bureau

J'ai programmé un script de test du protocole BLC que j'ai flashé sur l'Arduino.

void setup() {
  pinMode(4, INPUT);
  pinMode(8, OUTPUT);
  blc_resetIRQ();
  delay(5000);
  blc_init();
}

void loop() {}

void start()
{
  Serial.begin(115200,SERIAL_8N1);
}

void pause()
{
  Serial.end();
  pinMode(1, INPUT);
}
void blc_init()
{
  // Initialisation multicast
  start();
  blc_reset();
  pause();
  delay(100);
  
  start();
  blc_getVersion();
  pause();
  delay(100);
  
  start();
  blc_run_motor();
  pause();
  delay(100);

  // Initialisation moteur
  start();
  Serial.write(2);
  pause();
  delay(100);
  
  start();
  Serial.write(0x40);
  pause();
  delay(100);

  delay(1000);

  blc_startled();
  //blc_stopled();
}

void blc_run_motor()
{
  Serial.write(0xA1);
}

void blc_reset()
{
  Serial.write(0xE0);
}

void blc_getVersion()
{
  Serial.write(0x91);
}

void blc_startled()
{
  start();
  Serial.write(0x60);
  pause();
  delay(100);

  start();
  Serial.write(0x1e);
  pause();
  delay(100);
    
}

void blc_stopled()
{
  start();
  Serial.write(0x60);
  pause();
  delay(100);

  start();
  Serial.write(0x00);
  pause();
  delay(100);
    
}

void blc_multicast()
{
  for (int i = 1; i <= 6; i++) {
  start();
  Serial.write(0xa0);
  pause();
  delay(100);
  }
}

void blc_resetIRQ()
{
     if (digitalRead(4) == HIGH) {
      pinMode(8, OUTPUT);
      digitalWrite(8, LOW);
      delay(500);
      pinMode(8, INPUT);
   }
}

Le script réalise les opérations suivantes :

  • Fixe ENABLE à 0 (car si il est à 1 le moteur est en mode arrêt, il ne peut pas fonctionner).
  • Envoie toutes les 5000ms une succession de commande:
    • 0xE0 qui est sensé nous renvoyer l'état du moteur (0x50: le firmware doit être (re)flashé, 0x00 le firmware est ok)
    • 0x91 qui est sensé nous renvoyer le checksum du firmware du moteur
    • 0xA1 pour fixer l'état du moteur comme étant ok.
    • 0x02 qui donne un ID au moteur pour les commandes multicast. Ici j'ai donné l'ID 2.
    • 0x40 qui nous donne la version du moteur.
  • Essaie ensuite de passer la LED en vert.

Pour rappel, voilà les commandes qu'on a réussies à récupéré lors de l'étape de reverse:

Nom de la commande Valeur Description Valeur de retour
Récupérer le statut du moteur E0 Récupère le statut du moteur 00 : ok
50 : besoin de flash la mémoire
Écrire dans la mémoire du contrôleur du moteur 71 XX…XX Écris dans la mémoire le programme du moteur. Le programme doit faire 64 octets, représenté par les XX…XX dans la commande. La commande doit être envoyée 60 fois (60*64 octets) 70
Récupérer le checksum de la mémoire 91 Demande de récupérer le checksum du moteur. 120 octets, correspondant au checksum de la mémoire
Initier le moteur A1 "Lance" le moteur A0
Assigner un moteur comme étant le moteur 1,2,3 ou 4 01 ou 02 ou 03 ou 04 Assigner un moteur comme étant le moteur 1,2,3 ou 4 00
Effacer le contenu de la mémoire 61 Effacer le contenu de la mémoire 60
Récupérer la version du moteur 40 Récupérer la version du moteur sous la forme de 11 octets. Exemple : 01 0b 03 00 01 01 0a 0a 1a 0a 0a = soft version 1.11, hard version 3.0, supplier 1.1, lot number 10/10, FVT1 26/10/10 11 octets

De cette manière je peux envoyer via l'arduino des commandes à la motorboard, tout en récupérant le résultat renvoyé par la motorboard grâce à la sonde logique. L'exécution du script permet d'obtenir un résultat encourageant.

Analyseur logique : l'Arduino envoie la commande 0xE0 de demande de statut à la motorboard

Analyseur logique : la motorboard répond 0x50, signe que son firmware doit être reflashé

Analyseur logique : après la commande 0x91, la motorboard renvoie un checksum composé uniquement d'octets 0xC0

Analyseur logique : l'Arduino envoie la commande 0xA1 pour initier le moteur

Analyseur logique : la motorboard acquitte la commande d'initialisation en répondant 0xA0

En revanche, un point est alarmant : les réponses de la motorboard nous indiquent que le firmware du moteur testé a besoin d’être (re)flashé. En outre, le checksum du moteur est anormal, ce qui confirme le fait qu'il y a un problème.

\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0\xC0

De plus, aucune LED ne s'allume. J'ai malgré tout décidé de tester mon code un des trois autres motorboard, en supposant que le problème était au niveau de la motorboard testée, la LED s'est allumée en vert.

Banc de test : châssis du drone, grande breadboard, Arduino UNO et sonde reliés à une motorboard

Arduino UNO pilotant une motorboard dont la LED s'allume en vert, preuve que les commandes sont acceptées

Je peux également l’éteindre, ce qui prouve que je contrôle bien le moteur. Alors pourquoi cela ne marche pas sur ma motorboard de test ? Il est possible que lors d'un de mes tests préliminaires, j'ai envoyé la commande 0x61, ou du moins une commande qui a corrompu le firmware de ma motorboard de test. Malheureusement il faut que les 4 moteurs fonctionnent pour que le drone puisse voler. Je suis donc obligé de fixer ce petit problème, et pour cela il faut que je flash le contenu du firmware sur la motorboard.

Réparation du moteur cassé

On sait comment fonctionne la procédure de flashage du firmware sur les motorboards (on envoie 0x71 suivi des 64 premiers octets du firmware à la motorboard, elle nous répond 0x70 puis on envoie les 64 octets suivants et ainsi de suite). Comme le firmware dont je dispose a une taille de 7206 octets, et la motorboard attend 7680 octets (64*60), j'ai complété le firmware avec des octets valant 0xff, conformément à ce qu'on avait vu dans le firmware de la carte mère.

$ ls -la BLC.bin
-rw-rw-r-- 1 ohohoh ohohoh 7206 mars  24 00:12 BLC.bin
$ xxd -p BLC.bin | sed 's/\(..\)/\\x\1/g'
\x12\xc0\x2c\xc0\x2b\xc0\x2a\xc0\xc5\xc3\x28\xc0\x33\xc3\x54\xc3\x57\xc3\x24\xc0\x23\xc0\x08\xc5\x21\xc0\xde\xc4\x1f\xc0
\x1e\xc0\x5e\xc3\x1c\xc0\x1b\xc0\x11\x24\x1f\xbe\xcf\xe5\xd4\xe0\xde\xbf\xcd\xbf\x11\xe0\xa0\xe6\xb0\xe0\xec\xee\xfa\xe1
\x02\xc0\x05\x90\x0d\x92\xaa\x39\xb1\x07\xd9\xf7\x12\xe0\xaa\xe9\xb1\xe0\x01\xc0\x1d\x92\xa3\x37\xb1\x07\xe1\xf7\x9f\xda
\x46\xcd\xd1\xcf\xcf\x92\xdf\x92\xef\x92\xff\x92\xcf\x93\xdf\x93\xcd\xb7\xde\xb7\x28\x97\x0f\xb6\xf8\x94\xde\xbf\x0f\xbe
\xcd\xbf\x80\x91\x3a\x02\x28\x2f\x30\xe0\x40\xe0\x50\xe0\x29\x83\x3a\x83\x4b\x83\x5c\x83\x20\x91\x3b\x02\x30\xe0\x80\x91
\x38\x02\x90\x91\x39\x02\x28\x9f\xa0\x01\x29\x9f\x50\x0d\x38\x9f\x50\x0d\x11\x24\xca\x01\xa0\xe0\xb0\xe0\x8d\x83\x9e\x83
\xaf\x83\xb8\x87\x69\x81\x7a\x81\x8b\x81\x9c\x81\x20\xec\x38\xeb\x45\xea\x5d\xe1\x8f\xdc\x2d\x81\x3e\x81\x4f\x81\x58\x85

Un problème se présente : le firmware fait 7ko et l'Arduino UNO ne peut stocker que des variables d'une taille maximum de 2ko. Pour palier à cela, j'ai utilisé d'une mémoire EEPROM externe (24LC256) de 32ko sur laquelle j'ai stocké l'intégralité du firmware en l'écrivant petit bout par petit bout avec l'Arduino. Une fois le firmware entièrement écrit sur la mémoire, j'ai programmé l'Arduino pour qu'il le lise bloc par bloc, et qu'à chaque bloc lu il l'envoie à la motorboard corrompue.

Écriture du firmware sur l'EEPROM

Arduino relié à une mini breadboard portant l'EEPROM externe 24LC256 pour y stocker le firmware

Le code ci-dessous sert à écrire le firmware sur la mémoire. Il faut remplacer les macros BLOCK_NUMBER par des chiffres de 0 à 8, et lorsque BLOCK_NUMBER = 0, on fixe RESET_MEMORY à 1. Le code complet est disponible ici

#include <I2C_eeprom.h>
#include <Wire.h>
#include <Crypto.h>
#include <SHA256.h>

#define HASH_SIZE 32

#define BLOCK_NUMBER  8 // A modifier
#define RESET_MEMORY  0
#define READ_ONLY     1
#define DATA_LENGTH   7206

unsigned char firmware_code[]=
#if BLOCK_NUMBER == 0
/* -------------------- 0 -------------------- */
"\x12\xc0\x2c\xc0\x2b\xc0\x2a\xc0\xc5\xc3\x28\xc0\x33\xc3\x54\xc3\x57\xc3\x24\xc0\x23\xc0\x08\xc5\x21\xc0\xde\xc4\x1f\xc0"
"\x1e\xc0\x5e\xc3\x1c\xc0\x1b\xc0\x11\x24\x1f\xbe\xcf\xe5\xd4\xe0\xde\xbf\xcd\xbf\x11\xe0\xa0\xe6\xb0\xe0\xec\xee\xfa\xe1"
// ...
#elif BLOCK_NUMBER == 8
/* -------------------- 8 -------------------- */
"\x00\x00\x00\x1e\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
//...
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff";
#endif

SHA256 sha256;
Hash *hasher = &sha256;
int OFFSET = 30*30*BLOCK_NUMBER;

I2C_eeprom eeprom(0x50, 32768/8);

void setup() {
  start();
  Serial.println("");
  Serial.print("[-] Initialisation de la mémoire EEPROM...\n");
  init_eeprom();
  Serial.print("[-] Ecriture du firmware \n");
  write_firmware();
  Serial.print("[-] Ecriture terminé \n");
  Serial.print("[-] Lecture du firmware \n");
  read_firmware();
  Serial.print("[-] Lecture terminé \n");

}

void loop() {}

void start()
{
  Serial.begin(115200,SERIAL_8N1);
}

void pause()
{
  Serial.end();
  pinMode(1, INPUT);
}

void send_data(unsigned int data)
{
  Serial.print("\\x");
  if (data < 16) Serial.print("0");
  Serial.print(data,HEX);
}

void init_eeprom()
{
    Wire.begin();
    eeprom.begin();
}

void write_firmware()
{
    if (READ_ONLY)
      return;
    if (RESET_MEMORY)
      eeprom.setBlock(0,0xff,8192);
    for (int i = 0; i < 30; i++) {
      int start_pos = i*30;
      int end_pos = (i+1)*30;
      for (int c = start_pos; c<end_pos; c++)
      {
        //send_data(firmware_code[c]);
        eeprom.writeByte(OFFSET+c, firmware_code[c]);
        delay(5);
      }
  }
}

void read_firmware()
{
    hasher->reset();
    for (int i = 0; i < 123; i++) {
      int start_pos = i*64;
      int end_pos = (i+1)*64;
      for (int c = start_pos; c<end_pos; c++)
      {
        
        int data = eeprom.readByte(c);
        if (c < DATA_LENGTH)
          hasher->update(&data,(size_t) 1);
        send_data(data);
        //delay(100);
      }
    Serial.println();
  }

  Serial.print("[-] Hash: ");
  uint8_t hash[HASH_SIZE];
  hasher->finalize(hash, HASH_SIZE);
  for (byte i=0; i < HASH_SIZE; i++)
  {
    if (hash[i]<0x10) { Serial.print('0'); }
    Serial.print(hash[i],HEX);
  }
  Serial.println();
}

Une fois le firmware entièrement écrit, j'ai vérifié son intégrité en calculant le condensat des données de l'EEPROM, puis je l'ai comparé avec le condensat du firmware sur mon PC.

Moniteur série Arduino : relecture de l'EEPROM et calcul du hash SHA-256 pour vérifier l'intégrité du firmware

Écriture du firmware sur la motorboard

Le code Arduino ci-dessous flashe le firmware de la mémoire EEPROM vers la motorboard.

#include <I2C_eeprom.h>
#include <Wire.h>

bool DEBUG = false;
I2C_eeprom eeprom(0x50, 32768/8);

void setup() {
  // init EEPROM
  init_eeprom();

  delay(1000);
  // init BLC
  pinMode(4, INPUT);
  pinMode(8, OUTPUT);
  blc_resetIRQ();
  delay(5000);
  blc_init();

  delay(5000);
  // send firmware
  send_firmware();
  
}

void loop() {

}

/*--------------- EEPROM ----------------*/
void init_eeprom()
{
    Wire.begin();
    eeprom.begin();
}

void send_firmware()
{
    blc_send(0x61);
    delay(50);
    for (int i = 0; i < 120; i++) {
      int start_pos = i*64;
      int end_pos = (i+1)*64;
      blc_send(0x71);
      delay(100);
      for (int c = start_pos; c<end_pos; c++)
      {
        
        int data = eeprom.readByte(c);
        blc_send(data);
        delay(50);
      }
  }
}

/*--------------- BLC ----------------*/
void start()
{
  Serial.begin(115200,SERIAL_8N1);
}

void pause()
{
  Serial.end();
  pinMode(1, INPUT);
}

void blc_send(int data)
{
  start();
  Serial.write(data);
  pause();
}

void blc_init()
{

  // Initialisation multicast
  blc_reset();
  delay(100);
  
  blc_getVersion();
  delay(100);

  delay(1000);
}

void blc_reset()
{
  blc_send(0xE0);
}

void blc_getVersion()
{
  blc_send(0x91);
}

void blc_resetIRQ()
{
     if (digitalRead(4) == HIGH) {
      pinMode(8, OUTPUT);
      digitalWrite(8, LOW);
      delay(500);
      pinMode(8, INPUT);
   }
}

Logic 2 : capture UART du flashage, avec la trame d'octets envoyée à la motorboard décodée dans l'analyseur

Montage de flashage : Arduino, breadboard avec l'EEPROM et motorboard câblés sur le bureau

Vérification de l'écriture

Une fois le programme exécuté l'envoi de la commande 0x91 retourne un checksum différent de celui composé uniquement de 0xC0 que l'on avait obtenu précédement, ce qui signifie que des données ont bien été flashées sur la motorboard. En revanche, après l'envoi de la commande 0xA1, la motorboard renvoie 0xA0 puis la LED s'éteint et plus rien ne répond.

L'explication la plus probable est que la commande 0xA1 a pour effet d'éxecuter le code flashé sur la carte, et celui-ci contient une erreur (comme une particularité qu'on aurait loupé durant le reverse, un problème lié à la fréquence d"envoie ou autre). Lorsque la motorboard jump sur le code invalide, il crash. Cette hypothèse nous permet de statuer sur l'organisation de la mémoire dans l'atmega8a.

Schéma de la mémoire 8 Ko de l'atmega8a : bootloader, firmware en lecture seule et firmware réinscriptible lancé par A1

Faute de temps, je n'ai pas eu le temps de chercher à comprendre où étais mon erreur dans le flashage. J'ai opté pour la facilité et j'ai acheté un drone du même modèle (un AR DRONE 2), à 20€ sur Leboncoin, l'objectif étant d'extraire une motorboard fonctionnel. Après vérification du bon fonctionnement de celui-ci, je l'ai intègré au drone.

Le démultiplexeur

L'objectif est maintenant de faire tourner les 4 moteurs en même temps. Le problème est que le protocole BLC des motorboards a deux modes : - un mode unicast où les commandes doivent être envoyées à chaque moteur séparément - un mode broadcast où les commandes sont envoyées à tous les moteurs.

Il est techniquement compliqué de dédié une liaison UART unique entre chaque moteur et le STM32. Afin de pouvoir géré les communications UNICAST et le BROADCAST via une seule connexion, nous avons opté pour l'intégration d'un démultiplexeur.

Un démultiplexeur est un dispositif électronique ou un circuit logique utilisé pour envoyer un signal unique vers l'une des nombreuses lignes de sortie. Il peut être considéré comme l'inverse d'un multiplexeur, qui combine plusieurs signaux d'entrée en un seul signal de sortie.

L'objectif initial était de récupérer les valeurs retourner par les motorboards, en plus d'envoyer les commandes. Notre premier choix s'est donc porté sur le multiplexeur analogique CD4052, qui permet la circulation du signal dans les deux sens (ce qui n'est pas le cas de tout les démultiplexeur). Le problème est que (visiblement) celui-ci déformait le signal en introduisant des temps de régime transitoire trop important, ce qui le rendait inutilisable dans notre circuit.

Schéma interne du multiplexeur analogique CD4052B : entrées A et B sélectionnant les canaux X et Y

Comme nous n'avions pas d'autre démultiplexeur pouvant assurer la circulation du signal dans les deux sens, nous avons abandonné l'idée de récupérer le retour des commandes des motorboards. Nous avons opté pour un composant agissant comme un décodeur/démultiplexeur numérique, le 74HCT139 (qui, comme il est prévu pour traiter des signaux numérique, ne devrait pas déformer le signal outre mesure). On a égalemnt ajouté quelque diode au circuit afin d’empêcher le retour des commandes moteurs d'arriver dans les broches de sorties du démultiplexeur.

Schéma : le démultiplexeur aiguille le signal TX vers l'un des 4 moteurs selon A et B, avec une diode par sortie

Le code de l'Arduino a été modifié afin qu'il assigne un ID à chaque moteur (compris entre 1 et 4) et envoie ensuite une vitesse constante. Pour cela, on a utilisé deux nouveaux PINs de l'Arduino (2 et 7) qu'on a paramètré en tant que sortie. On les a connecté sur les broches A et B du démux 74HCT139. Ces deux pins codent en binaire le numéro de la sortie du démux à activer.

void setup() {
  
  pinMode(4, INPUT);
  pinMode(8, OUTPUT);

  pinMode(7, OUTPUT);
  pinMode(2, OUTPUT);
  digitalWrite(7, HIGH);
  digitalWrite(2, HIGH);

  
  blc_resetIRQ();
  delay(1000);
  blc_init();

}

void loop() {
  run_motor();
  //delay(50);
}

void start()
{
  Serial.begin(115200,SERIAL_8N1);
}

void pause()
{
  Serial.end();
  pinMode(1, INPUT);
}
void blc_init()
{

  for (int motor_id = 0; motor_id < 4; motor_id+= 1)
  {
      delay(800);
      // Selection du moteur
      int A = (motor_id & 1);
      int B = (motor_id >> 1) & 1;
      if (A == 1)
        digitalWrite(7, HIGH);
      else
        digitalWrite(7, LOW);
      if (B == 1)
        digitalWrite(2, HIGH);
      else
        digitalWrite(2, LOW);
    
      // Initialisation multicast
      start();
      blc_reset();
      pause();
      delay(100);
      
      start();
      blc_getVersion();
      pause();
      delay(100);
      
      start();
      blc_run_motor();
      pause();
      delay(100);
    
      // Initialisation moteur
      start();
      Serial.write(motor_id+1);
      pause();
      delay(100);
      
      start();
      Serial.write(0x40);
      pause();
      delay(100);
    
      delay(1000);
    
      blc_startled();
    
      delay(1000);
      run_motor();
      //blc_stopled();
    
      start();
      blc_reset();
      pause();
      delay(100);
  }
}

void blc_run_motor()
{
  Serial.write(0xA1);
}

void blc_reset()
{
  Serial.write(0xE0);
}

void blc_getVersion()
{
  Serial.write(0x91);
}

void blc_startled()
{
  start();
  Serial.write(0x60);
  pause();
  delay(100);

  start();
  Serial.write(0x1e);
  pause();
  delay(100);
    
}

void blc_stopled()
{
  start();
  Serial.write(0x60);
  pause();
  delay(100);

  start();
  Serial.write(0x00);
  pause();
  delay(100);
    
}

void blc_multicast()
{
  for (int i = 1; i <= 6; i++) {
  start();
  Serial.write(0xa0);
  pause();
  delay(100);
  }
}

void blc_resetIRQ()
{
     if (digitalRead(4) == HIGH) {
      pinMode(8, OUTPUT);
      digitalWrite(8, LOW);
      delay(500);
      pinMode(8, INPUT);
   }
}

void run_motor()
{
    for (int motor_id = 0; motor_id < 4; motor_id+= 1)
    {
      //delay(10);
      // Selection du moteur
      int A = (motor_id & 1);
      int B = (motor_id >> 1) & 1;
      if (A == 1)
        digitalWrite(7, HIGH);
      else
        digitalWrite(7, LOW);
      if (B == 1)
        digitalWrite(2, HIGH);
      else
        digitalWrite(2, LOW);

        
      start();
      Serial.write(35);
      pause();
      delay(5);
    
        start();
      Serial.write(255);
      pause();
      delay(5);
    
        start();
      Serial.write(255);
      pause();
      delay(5);
    
        start();
      Serial.write(255);
      pause();
      delay(5);
    
      start();
      Serial.write(255);
      pause();
      delay(5);
    }
}

NB: On a ajouté une nouvelle breadboards, la vert, sur la queue du drone. C'est sur cette breadboard qu'on a placé le démultiplexeur.

C'est un succès, les moteurs tournent tout et le drone commence à décoller.

Portage sur STM32

Maintenant que le code fonctionne sur un Arduino, on l'a porté sur STM32. On l'a complété avec un programme développé en parallèle, qui récupère les données des capteurs (accéléromètre et distance). Il reste un problème : le signal UART délivré par le STM32 a pour tension de niveau logique haut 3.3V, et les motorboards attendent un signal logique haut de 5V. Afin de réhausser la tension, nous avons réalisé un circuit additionnel, un "Logic Level Shifting Basics". Ce circuit élève la tension 3.3 au 5V.

Schéma d'un convertisseur de niveau logique à MOSFET BS170 élevant le signal UART de 3,3 V à 5 V

On peut maintenant tester :

Les 4 moteurs tourne et son piloté par le STM32, il ne reste plus qu'à le faire voler.

Les 3 problèmes

  • Le premier problème : l'effacement accidentel

Pour connaitre la vitesse limite qui fait s'envoler le drone, on a réalisé une boucle dans laquelle on a augmenté périodiquement (toutes les 50ms) la vitesse d’une unité, jusqu'à atteindre son maximum (c'est-à-dire $2^9-1=511$). On y va progressivement, d'abord de 1 à 400, puis de 1 à 511. Lors de la deuxième mesure (de 1 à 511), les moteurs se sont éteints, puis plus rien ne marchait (même en utilisant nos codes précédents censés être fonctionnels). En interceptant le signal en sortie des motorboard à l'aide de la sonde logique, on s'est aperçu que l'on avait supprimé le firmware de tous les moteurs. Pour rappel, il s'agit du même problème que l'on avait déjà rencontré avec notre moteur de test. C'est le premier problème. Cependant, par rapport à avant, nous avons désormais a un deuxième drone qui dispose d'une carte mère Parrot fonctionnelle. Comme nous l'avons vu dans le précédant article, la carte mère effectue une vérification de l'intégrité du firmware sur les motorboards au démarrage, et flash le firmware qu'elle a sur son disque si celui n'est pas valide. L'objectif était de connecter nos moteurs sur la carte mère d'origine (la fonctionnelle), de l'alimenter et de profiter du processus d'initialisation pour réparer les 5 motorboard. Comme nous n'avions pas de batterie pour la motorboard, on a alimenté la carte mère en 11V à l'aide d'un générateur de tension.

  • Deuxième problème : Alimentation faible

Nos tests nous ont permis de remarqué plusieurs points bloquants : - les moteurs ne tournent pas à une vitesse suffisante pour permettre au drone de voler ; - si l'on envoie une vitesse sur un seul des moteurs il tourne beaucoup plus vite. Il s'agissait d'un problème de courant. Lorsqu'on utilise un seul des 4 moteurs, la consommation de courant est de 2,5 A, ce qui signifie qu'un moteur consomme probablement un courant d'environ 2 A. Or on ne fournit au total que 3A maximum à l'ensemble du circuit, ce qui n'est pas suffisant. On a réglé un problème en se munissant d'un générateur de tension pouvant délivrer des courants plus importants, ce qui règlé le souci.

⚠️ Nous n'avions pas pensé à l'éventualité que les breadboards et les cables du système ne pouvaient potentiellement pas supporté de tel courant. Par chance, tout s'est bien passé sur le moment, mais des tests réalisé ulterieurement ont fait fondre certains cables.

  • Troisième problème : Mauvais sens de rotation

Comme on peut le voir sur la vidéo, seule la partie arrière du drone décole tandis que l'autre reste cloué au sol. C'est dû au fait que les moteurs ne tournent pas dans le bon sens sur l'avant du drone, ce qui génère une force en direction du sol. On peut se rendre compte de cette force en essayant de tirer le drone vers le haut. Ce problème a été règlé en inversant simplement la position des 2 moteurs.

Conclusion

On est en mesure de piloter les moteurs en vitesse à l'aide du STM32, qui reçoit également les données des capteurs. Il ne reste plus qu'à asservir le système à l'aide des capteurs et d'un PID. Je présente cette étape dans mon prochain post, disponible ici

Accès Restreint

Cet article n'est pas (encore) publique.
Pour obtenir un accès anticipé, contactez-moi.

[[ passwordError ]]

[[ post.title ]]

[[ post.description ]]

Le [[ post.date ]]

[[ tag ]]
[[ currentMatchIndex + 1 ]] / [[ searchMatches.length ]] 0 / 0

Sommaire

Page principale