Nous ressuscitons l'hexapode. Partie un

Dans un article précédent, nous avons partagé notre expérience dans la création d'un hexapode utilisant la technologie d'impression 3D. Nous allons maintenant parler du composant logiciel, qui lui a permis de renaßtre.

Il était initialement prévu de présenter toutes les informations dans un seul article, mais au cours de la rédaction, il est devenu clair qu'une telle présentation serait superficielle et non informative. Par conséquent, il a été décidé d'écrire plusieurs articles avec une présentation plus détaillée du sujet.

Appareil hexapode


Actuellement, la carte UNO R3 avec Wi-Fi ESP8266 est utilisée comme contrÎleur principal . En fait, cette carte avec deux contrÎleurs à bord, interagissant entre eux via une interface UART.



Malgré le fait qu'Uno dispose d'une quantité assez limitée de ressources informatiques, il suffit d'apprendre au robot à exécuter des commandes de base:

  • mouvement en ligne droite avec une vitesse et une durĂ©e donnĂ©es
  • mouvement circulaire vers la gauche ou la droite (tourner en place)
  • prendre des positions de membre prĂ©dĂ©finies

L'ESP8266 est responsable de l'organisation du canal de communication sans fil et sert de passerelle à travers laquelle Uno reçoit les commandes de contrÎle.

Le robot peut ĂȘtre contrĂŽlĂ© via le rĂ©seau local au sein de la session telnet Ă©tablie avec lui ou via une connexion filaire au contrĂŽleur (pour le firmware ou le dĂ©bogage). Pour plus de commoditĂ©, nous avons Ă©galement Ă©crit une application Android simple qui implĂ©mente une fonctionnalitĂ© interactive minimale pour contrĂŽler le robot.



La figure ci-dessous montre schématiquement la structure hexapode.



Tous les servos sont connectĂ©s Ă  la carte d' extension Multiservo Shield , qui vous permet de contrĂŽler 18 servos. Sa communication avec Arduino se fait via le bus IÂČC. Par consĂ©quent, mĂȘme avec 18 servos de direction en mĂȘme temps, presque toutes les broches Arduino resteront libres.
Il convient de noter que la carte d'extension a un connecteur pour alimenter les servos connectés. Mais le courant maximal admissible pour lequel la carte est conçue est d'environ 10 A, ce qui n'est pas suffisant pour alimenter les servo-variateurs MG996R, dont la consommation de courant maximale totale peut dépasser la valeur spécifiée. Par conséquent, dans notre version, chaque servo était connecté à une ligne d'alimentation distincte, contournant la carte d'extension.

Gestion des hexapodes


La logique de contrÎle des membres hexapodes est implémentée dans le programme à l'aide de la classe GeksaFoot .

classe GeksaFoot
class GeksaFoot {
private:
//         
  Vector3D m_p0;
//          
  Vector3D m_r0;
//  Multiservo,    
  Multiservo m_coxaServo;   //  
  Multiservo m_femoraServo; //  
  Multiservo m_tibiaServo;  //  
public:
  GeksaFoot(Vector3D p0,Vector3D r0);
//   
  void begin(int coxaPin, int femoraPin, int tibiaPin);
//   
  void end();   

//   

  void coxaAngle(int);      //      (-90 .. 90 )
  int coxaAngle();          //     

  void femoraAngle(int);    //       (-90 .. 90 )
  int femoraAngle();        //     

  void tibiaAngle(int);     //       (-90 .. 90 )
  int tibiaAngle();         //     

//         

  //         
  int getAngles(Vector3D p, int& coxaAngle, int& femoraAngle, int& tibiaAngle);
  //       
  int getPoint(int coxaAngle, int femoraAngle, int tibiaAngle, Vector3D& p);
};


Les méthodes coxaAngle , femoraAngle , tibiaAngle vous permettent de définir ou de trouver l'angle de rotation d'une articulation de jambe individuelle. Les méthodes auxiliaires getAngles et getPoint implémentent la logique de calcul de la cinématique directe et inverse, avec laquelle vous pouvez calculer la valeur des angles de jambe pour un point donné dans l'espace de son membre. Ou vice versa, un point d'espace pour les valeurs d'angle actuelles.

La position moyenne de chaque joint correspond à une valeur nulle de l'angle, et la plage de rotation du joint se situe dans la plage de -90 à 90 degrés.

La classe de niveau supĂ©rieur est la classe Geksapod. Il met en Ɠuvre la logique de l'ensemble du robot. Chaque jambe hexapode est incluse dans cette classe en tant qu'instance distincte de la classe GeksaFoot .

geksapod de classe
class Geksapod: public AJobManager {
  friend class MotionJob;
  friend CommandProcessorJob;
  //  
  GeksaFoot m_LeftFrontFoot;
  GeksaFoot m_LeftMidleFoot;
  GeksaFoot m_LeftBackFoot;
  GeksaFoot m_RigthFrontFoot;
  GeksaFoot m_RigthMidleFoot;
  GeksaFoot m_RigthBackFoot;
  // ,    
  MotionJob m_MotionJob;
private:
  //       
  //       
  //     
  int _setPose(int idx, int ca, int fa, int ta);
  int _setPose(int[FOOTS_COUNT][3]);  
  int _setPose(Vector3D points[FOOTS_COUNT]); 
protected:
  //          
  int setPose(int idx, int ca, int fa, int ta, int actionTime); 
  int setPose(int pose[FOOTS_COUNT][3], int actionTime);
  int setPose(int idx, Vector3D p, int actionTime);
  int setPose(Vector3D points[FOOTS_COUNT], int actionTime = 0);
  int setPose(int ca, int fa, int ta, int actionTime);
  //      
  void getPose(int idx, int& ca, int& fa, int& ta);
  void getPose(int pose[FOOTS_COUNT][3]);
  void getPose(int idx, Vector3D& p);
  void getPose(Vector3D points[FOOTS_COUNT]);
  //    
  int execute(Motion* pMotion);
public:
  Geksapod();
  void setup();
  //   
  int move(int speed, int time);    //   
  int rotate(int speed, int time);  //   
  void stop();                      //  
  //         
  int getAngles(int idx, Vector3D p, int& ca, int& fa, int& ta);
  int getPoint(int idx, int coxaAngle, int femoraAngle, int tibiaAngle, Vector3D& p);
  int getAngles(Vector3D points[FOOTS_COUNT], int pose[FOOTS_COUNT][3]);
  int getPoints(int pose[FOOTS_COUNT][3], Vector3D points[FOOTS_COUNT]);
};


Les méthodes getPose et setPose surchargées sont destinées à un usage interne et vous permettent d'obtenir la position actuelle des membres du robot ou d'en définir une nouvelle. Dans ce cas, la position des jambes est définie sous la forme d'un ensemble de valeurs des angles de rotation de chaque articulation ou comme un ensemble de coordonnées des points d'extrémité des membres du robot par rapport à son centre.
Pour un mouvement fluide des membres lors de l'appel des méthodes setPose , vous pouvez spécifier le temps (paramÚtre actionTime ) aprÚs lequel les jambes doivent atteindre la position spécifiée.
Le robot est contrĂŽlĂ© par les mĂ©thodes publiques de dĂ©placement , rotation et arrĂȘt .

Émulation multitñche


La classe Geksapod hĂ©rite de l'implĂ©mentation de la classe AJobManager et contient une instance de la classe MotionJob , qui Ă  son tour hĂ©rite de la classe AJob . Ces classes vous permettent d'implĂ©menter ce que l'on appelle le multitĂąche non prĂ©emptif, vous permettant de vous abstraire de la linĂ©aritĂ© des programmes et d'effectuer plusieurs tĂąches en mĂȘme temps.

classe ajob
class AJob {
  friend class AJobManager;
private:
  AJobManager* m_pAJobManager;   
  AJob* mJobNext;                      
  unsigned long m_counter;         //    onRun
  unsigned long m_previousMillis; //     onRun
  unsigned long m_currentMillis;  //   
  unsigned long m_delayMillis;    //    onRun
  void run();
public:
  AJob(AJobManager*, unsigned long delay = 0L);
  ~AJob();
                  
  void finish();  //     
  long counter(); //    onRun    
  long setDelay(unsigned long); //    onRun
  unsigned long previousMillis();//      onRun
  unsigned long currentMillis(); //    
                              
  virtual void onInit();  //      
  virtual void onRun();   //     
  virtual void onDone();  //        finish
};


La classe AJob est la classe de base pour toutes les tĂąches qui nĂ©cessitent une exĂ©cution simultanĂ©e. Ses hĂ©ritiers doivent remplacer la mĂ©thode onRun , qui implĂ©mente la logique de la tĂąche en cours d'exĂ©cution. Étant donnĂ© les spĂ©cificitĂ©s du multitĂąche prĂ©emptif, l'appel de cette mĂ©thode ne devrait pas prendre trop de temps. Il est recommandĂ© de diviser la logique de la tĂąche en plusieurs sous-tĂąches plus lĂ©gĂšres, chacune Ă©tant exĂ©cutĂ©e pour un appel onRun distinct .

classe AJobManager
class AJobManager {
  friend class AJob;
  AJob* mJobFirst;  //      
  void attach(AJob*);   //    
  void dettach(AJob*); //    
  void dettachAll();   //   
public:  
  AJobManager();
  ~AJobManager();
  void setup();
  void loop();
};


La classe AJobManager a une dĂ©claration plus modeste et contient seulement deux mĂ©thodes publiques: setup et loop . La mĂ©thode de configuration doit ĂȘtre appelĂ©e une fois avant de dĂ©marrer la boucle du programme principal. Dans ce document, une initialisation une par une de toutes les tĂąches se produit, en appelant la mĂ©thode onInit correspondante pour chaque tĂąche de la liste.
Une tĂąche est automatiquement ajoutĂ©e Ă  la liste lorsque son constructeur est appelĂ© et peut ĂȘtre supprimĂ©e en appelant la mĂ©thode de fin publique de la tĂąche elle-mĂȘme.

Méthode de boucleIl est appelé à plusieurs reprises dans le cycle de programme principal et est responsable de l'exécution séquentielle de la logique de chaque tùche à partir de la liste à des intervalles spécifiés (si installés).

Ainsi, lors de la création d'une instance de la classe Geksapod héritée de la classe AJobManager , nous mettons à notre disposition un outil multitùche pratique.

Mise en Ɠuvre du mouvement


Tout mouvement du corps peut ĂȘtre dĂ©crit par une fonction qui dĂ©termine sa position Ă  un moment donnĂ©. Une telle fonction peut ĂȘtre composite, c'est-Ă -dire qu'elle peut ĂȘtre un ensemble de fonctions, dont chacune n'est applicable que pendant une certaine pĂ©riode de temps.

Les diffĂ©rents types de mouvement des membres de l'hexapode sont dĂ©finis et peuvent ĂȘtre Ă©tendus Ă  l'aide de classes hĂ©ritĂ©es de la classe Motion .

mouvement de classe
class Motion {
  friend class MotionJob;
protected:
  long m_MaxTime;     //      
  long m_TotalTime;   //      
  bool m_IsLooped;    //   
  Motion* m_pNext;    //     
public:  
  Motion(long maxTime, bool isLooped, long totalTime = -1, Motion* pNext = NULL);
  ~Motion();
  
  inline long maxTime() { return m_MaxTime; }
  inline long totalTime() { return m_TotalTime; }
  inline bool isLooped() { return m_IsLooped; }

  //          

  //       0 <= time <= m_MaxTime 
  virtual int getPose(long time, Vector3D points[FOOTS_COUNT]) { return E_NOT_IMPL; };  
  //        0 <= time <= m_MaxTime
  virtual int getPose(long time, int pose[FOOTS_COUNT][3]) { return E_NOT_IMPL; };       
};


Pour mettre en oeuvre le mouvement, la substituĂ©e getPose mĂ©thode doit retourner la position des membres du robot pour une pĂ©riode de temps donnĂ©e de temps dans la plage de 0 Ă  mMaxTime (en millisecondes). Dans le cas oĂč le mouvement est bouclĂ© ( m_IsLooped == true) , la durĂ©e du mouvement peut ĂȘtre limitĂ©e en dĂ©finissant la durĂ©e dans m_TotalTime . Et enfin, vous pouvez organiser une sĂ©quence de mouvements en les combinant dans une liste.

Ainsi, nous avons l'occasion de dĂ©crire les mouvements du robot. Mais la description elle-mĂȘme (dans notre cas, une instance de la classe hĂ©ritĂ©e de Motion) ne fera pas bouger le robot. Nous avons besoin d'un mĂ©canisme qui rĂ©organisera les jambes de l'hexapode, guidĂ© par la description qui lui est donnĂ©e.

Et ce mécanisme est une instance de la classe MotionJob déclarée dans la classe Geksapod .

classe MotionJob
class MotionJob: public AJob {
  enum STATUS { 
    NONE, RUNING, STOPING 
   } m_Status;
  
  Geksapod* m_pGeksapod;
  Motion* m_pMotion;
  long m_MotionTime;
  long m_TotalTime;
public:  
  MotionJob(Geksapod* pGeksapod);
  int execute(Motion* pMotion);
  void onRun();
};


Une instance de la classe MotionJob , hĂ©ritĂ©e d' AJob, est une tĂąche dans laquelle la mĂ©thode onRun est appelĂ©e Ă  intervalles rĂ©guliers . Il implĂ©mente Ă©galement un mĂ©canisme qui oblige notre robot Ă  effectuer des mouvements. Nous avons juste besoin de lui dire comment se dĂ©placer, en indiquant une description du mouvement lors de l'appel de la mĂ©thode execute. C'est tout pour le moment. Il y a encore de nombreux problĂšmes non Ă©clairĂ©s que j'essaierai d'Ă©crire dans le prochain article. J'espĂšre que je n'ai pas fatiguĂ© les lecteurs avec trop de code. PrĂȘt Ă  rĂ©pondre Ă  toutes vos questions. A suivre ... PS En prĂ©paration du prochain article, j'ai lĂ©gĂšrement modifiĂ© la structure de la visibilitĂ© des mĂ©thodes dans la classe AJob . MĂ©thodes OnInit







, onRun et onDone n'ont pas besoin d'un accÚs public, car leur appel provient de la classe conviviale AJobManager . Considérant que ces méthodes doivent se chevaucher chez les héritiers, il suffit de les placer dans la section protégée .

classe ajob
class AJob {
  friend class AJobManager;
private:
  AJobManager* m_pAJobManager;   
  AJob* mJobNext;                      
  unsigned long m_counter;         //    onRun
  unsigned long m_previousMillis; //     onRun
  unsigned long m_currentMillis;  //   
  unsigned long m_delayMillis;    //    onRun
  void run();
protected:
  virtual void onInit();  //      
  virtual void onRun();   //     
  virtual void onDone();  //        finish
public:
  AJob(AJobManager*, unsigned long delay = 0L);
  ~AJob();

  void finish();  //     
  long counter(); //    onRun    
  long setDelay(unsigned long); //    onRun
  unsigned long previousMillis();//      onRun
  unsigned long currentMillis(); //    
};


All Articles