Wir beleben den Hexapod wieder. Teil eins

In einem früheren Artikel haben wir unsere Erfahrungen bei der Erstellung eines Hexapods mithilfe der 3D-Drucktechnologie geteilt. Jetzt werden wir über die Softwarekomponente sprechen, die es ihm ermöglichte, wiederzubeleben.

Es war ursprünglich geplant, alle Informationen in einem Artikel zu präsentieren, aber beim Schreiben wurde klar, dass eine solche Präsentation oberflächlich und nicht informativ sein würde. Aus diesem Grund wurde beschlossen, mehrere Artikel mit einer detaillierteren Darstellung des Themas zu verfassen.

Hexapod-Gerät


Derzeit wird die UNO R3-Karte mit Wi-Fi ESP8266 als Hauptcontroller verwendet . Tatsächlich ist diese Karte mit zwei Controllern an Bord, die über eine UART-Schnittstelle miteinander interagieren.



Trotz der Tatsache, dass Uno nur über eine begrenzte Menge an Rechenressourcen verfügt, reicht es aus, dem Roboter das Ausführen grundlegender Befehle beizubringen:

  • Gerade Bewegung mit einer bestimmten Geschwindigkeit und Dauer
  • Kreisbewegung nach links oder rechts (an Ort und Stelle drehen)
  • Nehmen Sie voreingestellte Gliedmaßenpositionen ein

Der ESP8266 ist für die Organisation des drahtlosen Kommunikationskanals verantwortlich und dient als Gateway, über das Uno Steuerbefehle empfängt.

Der Roboter kann über das lokale Netzwerk innerhalb der damit eingerichteten Telnet- Sitzung oder über eine Kabelverbindung zur Steuerung (für Firmware oder Debugging) gesteuert werden. Der Einfachheit halber haben wir auch eine einfache Android-Anwendung geschrieben, die nur minimale interaktive Funktionen zur Steuerung des Roboters implementiert.



Die folgende Abbildung zeigt schematisch die Hexapod-Struktur.



Alle Servos sind mit der Multiservo Shield- Erweiterungskarte verbunden , mit der Sie 18 Servos steuern können. Ihre Kommunikation mit Arduino erfolgt über den I²C-Bus. Daher bleiben auch bei gleichzeitiger Lenkung von 18 Servos fast alle Arduino-Stifte frei.
Es ist zu beachten, dass die Erweiterungskarte über einen Anschluss zur Stromversorgung der angeschlossenen Servos verfügt. Der maximal zulässige Strom, für den die Karte ausgelegt ist, beträgt jedoch etwa 10 A, was nicht ausreicht, um die MG996R-Servoantriebe mit Strom zu versorgen, deren maximaler Gesamtstromverbrauch den angegebenen Wert überschreiten kann. Daher wurde in unserer Version jedes Servo unter Umgehung der Erweiterungskarte an eine separate Stromleitung angeschlossen.

Hexapod-Management


Die Hexapod-Gliedmaßensteuerlogik wird im Programm unter Verwendung der GeksaFoot- Klasse implementiert .

Klasse 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);
};


Mit den Methoden coxaAngle , femoraAngle , tibiaAngle können Sie den Drehwinkel eines einzelnen Beingelenks einstellen oder herausfinden. Die Hilfsmethoden getAngles und getPoint implementieren die Logik der Berechnung der direkten und inversen Kinematik, mit der Sie den Wert der Beinwinkel für einen bestimmten Punkt im Raum ihrer Extremität berechnen können. Oder umgekehrt, ein Raumpunkt für die aktuellen Winkelwerte.

Die durchschnittliche Position jedes Gelenks entspricht einem Nullwert des Winkels, und der Drehbereich des Gelenks liegt im Bereich von -90 bis 90 Grad.

Die oberste Klasse ist die Geksapod- Klasse. Es implementiert die Logik des gesamten Roboters. Jedes Hexapod-Bein ist in dieser Klasse als separate Instanz der GeksaFoot- Klasse enthalten .

Klasse Geksapod
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]);
};


Die überladenen Methoden getPose und setPose sind für den internen Gebrauch vorgesehen und ermöglichen es Ihnen, die aktuelle Position der Gliedmaßen des Roboters abzurufen oder eine neue festzulegen . In diesem Fall wird die Position der Beine in Form eines Satzes von Werten der Drehwinkel jedes Gelenks oder als Satz von Koordinaten der Endpunkte der Gliedmaßen des Roboters relativ zu seiner Mitte eingestellt.
Für eine reibungslose Bewegung der Gliedmaßen beim Aufrufen der setPose- Methoden können Sie die Zeit ( actionTime- Parameter ) angeben, nach der die Beine die angegebene Position erreichen sollen.
Der Roboter wird durch die öffentlichen Methoden zum Bewegen , Drehen und Stoppen gesteuert .

Multitask-Emulation


Die Geksapod- Klasse erbt die Implementierung der AJobManager- Klasse und enthält eine Instanz der MotionJob- Klasse , die wiederum von der AJob- Klasse erbt . Mit diesen Klassen können Sie das sogenannte nicht-präemptive Multitasking implementieren, um von der Linearität von Programmen zu abstrahieren und mehrere Aufgaben gleichzeitig auszuführen.

Klasse 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
};


Die AJob- Klasse ist die Basisklasse für alle Aufgaben, die gleichzeitig ausgeführt werden müssen. Seine Erben sollten die onRun- Methode überschreiben , die die Logik der ausgeführten Aufgabe implementiert. Angesichts der Besonderheiten des präventiven Multitasking sollte das Aufrufen dieser Methode nicht zu zeitaufwändig sein. Es wird empfohlen, die Aufgabenlogik in mehrere leichtere Unteraufgaben zu unterteilen, von denen jede für einen separaten onRun- Aufruf ausgeführt wird .

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


Die AJobManager- Klasse hat eine bescheidenere Deklaration und enthält nur zwei öffentliche Methoden: setup und loop . Die Setup- Methode muss vor dem Starten der Hauptprogrammschleife einmal aufgerufen werden. Darin erfolgt eine einzelne Initialisierung aller Aufgaben, indem die entsprechende onInit- Methode für jede Aufgabe aus der Liste aufgerufen wird .
Eine Aufgabe wird automatisch zur Liste hinzugefügt, wenn ihr Konstruktor aufgerufen wird, und kann durch Aufrufen der öffentlichen Abschlussmethode der Aufgabe selbst gelöscht werden . Loop

- VerfahrenEs wird im Hauptprogrammzyklus wiederholt aufgerufen und ist dafür verantwortlich, die Logik jeder Aufgabe in festgelegten Intervallen (falls installiert) nacheinander aus der Liste auszuführen.

Somit wird , wenn eine Instanz der Erstellung Geksapod Klasse von der geerbten AJobManager Klasse , die wir zur Verfügung eines bequemes Multitasking - Tool erhalten.

Bewegungsimplementierung


Jede Bewegung des Körpers kann durch eine Funktion beschrieben werden, die seine Position zu einem bestimmten Zeitpunkt bestimmt. Eine solche Funktion kann zusammengesetzt sein, dh es kann sich um eine Reihe von Funktionen handeln, von denen jede nur für einen bestimmten Zeitraum anwendbar ist.

Die verschiedenen Bewegungsarten der Gliedmaßen des Hexapods sind definiert und können mithilfe von Klassen erweitert werden, die von der Motion- Klasse geerbt wurden .

Klasse Motion
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; };       
};


Um die Bewegung zu implementieren, die überschriebene getPose Verfahren muss die Position der Glieder des Roboters für eine bestimmte Zeitdauer zurück Zeit im Bereich von 0 bis mMaxTime (in Millisekunden). In dem Fall, in dem die Bewegung geloopt wird ( m_IsLooped == true) , kann die Bewegungszeit durch Einstellen der Dauer in m_TotalTime begrenzt werden . Und schließlich können Sie eine Abfolge von Bewegungen organisieren, indem Sie sie zu einer Liste kombinieren.

Somit haben wir die Möglichkeit, die Bewegungen des Roboters zu beschreiben. Die Beschreibung selbst (in unserem Fall eine von Motion geerbte Instanz der Klasse) bewegt den Roboter jedoch nicht. Wir brauchen einen Mechanismus, der die Beine des Hexapods neu anordnet und sich an der Beschreibung orientiert.

Und dieser Mechanismus ist eine Instanz der MotionJob- Klasse , die in der Geksapod- Klasse deklariert ist .

Klasse 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();
};


Eine Instanz der MotionJob Klasse von ererbten ajob, ist eine Aufgabe , bei der die OnRun Verfahren genannt wird bei regelmäßigen Intervallen . Es implementiert auch einen Mechanismus, der unseren Roboter zwingt, Bewegungen auszuführen. Wir müssen ihm nur sagen, wie er sich bewegen soll, und beim Aufrufen der Ausführungsmethode eine Beschreibung der Bewegung angeben. Das ist alles für jetzt. Es gibt noch viele unbeleuchtete Probleme, über die ich im nächsten Artikel schreiben werde. Ich hoffe, ich habe die Leser nicht mit zu viel Code müde gemacht. Bereit, alle Ihre Fragen zu beantworten. Fortsetzung folgt ... PS Während der Vorbereitung des nächsten Artikels habe ich die Struktur der Sichtbarkeit von Methoden in der AJob- Klasse geringfügig geändert . OnInit- Methoden







, onRun und onDone benötigen keinen öffentlichen Zugriff, da ihr Anruf von der freundlichen AJobManager- Klasse stammt . In Anbetracht der Tatsache, dass sich diese Methoden bei den Erben überschneiden sollten, reicht es aus, sie im geschützten Bereich zu platzieren .

Klasse 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