Kami menghidupkan kembali hexapod. Bagian satu

Dalam artikel sebelumnya, kami berbagi pengalaman kami dalam membuat hexapod menggunakan teknologi cetak 3D. Sekarang kita akan berbicara tentang komponen perangkat lunak, yang memungkinkannya untuk hidup kembali.

Awalnya direncanakan untuk menyajikan semua informasi dalam satu artikel, tetapi dalam proses penulisan menjadi jelas bahwa presentasi seperti itu akan dangkal dan tidak informatif. Oleh karena itu, diputuskan untuk menulis beberapa artikel dengan presentasi topik yang lebih rinci.

Perangkat hexapod


Saat ini, papan UNO R3 dengan Wi-Fi ESP8266 digunakan sebagai pengontrol utama . Bahkan, papan ini dengan dua pengontrol di papan, berinteraksi satu sama lain melalui antarmuka UART.



Terlepas dari kenyataan bahwa Uno memiliki sumber daya komputasi yang agak terbatas, cukup untuk mengajari robot cara melakukan perintah dasar:

  • gerakan garis lurus dengan kecepatan dan durasi yang diberikan
  • gerakan memutar ke kiri atau kanan (putar di tempat)
  • mengambil posisi tungkai yang telah ditetapkan

ESP8266 bertanggung jawab untuk mengatur saluran komunikasi nirkabel dan berfungsi sebagai gateway tempat Uno menerima perintah kontrol.

Robot dapat dikontrol melalui jaringan lokal dalam sesi telnet yang dibuat dengannya atau melalui koneksi kabel ke controller (untuk firmware atau debugging). Untuk kenyamanan, kami juga menulis aplikasi android sederhana yang mengimplementasikan fungsi interaktif minimal untuk mengendalikan robot.



Gambar di bawah ini secara skematis menunjukkan struktur hexapod.



Semua servos terhubung ke kartu ekspansi Multiservo Shield , yang memungkinkan Anda untuk mengontrol 18 servos. Komunikasinya dengan Arduino adalah melalui bus IΒ²C. Oleh karena itu, bahkan dengan 18 servos kemudi pada saat bersamaan, hampir semua pin Arduino akan tetap gratis.
Perlu dicatat bahwa papan ekspansi memiliki konektor untuk memberi daya pada servos yang terhubung. Tetapi arus maksimum yang diijinkan yang dirancang papan adalah sekitar 10A, yang tidak cukup untuk daya drive servo MG996R, total konsumsi arus maksimum yang dapat melebihi nilai yang ditentukan. Oleh karena itu, dalam versi kami, setiap servo terhubung ke saluran listrik terpisah, melewati papan ekspansi.

Manajemen hexapod


Logika kontrol ekstremitas hexapod diimplementasikan dalam program menggunakan kelas GeksaFoot .

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


Metode coxaAngle , femoraAngle , tibiaAngle memungkinkan Anda untuk mengatur atau mengetahui sudut rotasi sendi kaki individu. Metode bantu getAngles dan getPoint mengimplementasikan logika perhitungan kinematika langsung dan terbalik, yang dengannya Anda dapat menghitung nilai sudut kaki untuk titik tertentu di ruang anggota geraknya. Atau sebaliknya, titik ruang untuk nilai sudut saat ini.

Posisi rata-rata dari setiap sambungan sesuai dengan nilai sudut nol, dan rentang rotasi sambungan terletak pada kisaran -90 hingga 90 derajat.

Kelas tingkat atas adalah kelas Geksapod. Ini mengimplementasikan logika seluruh robot. Setiap kaki hexapod termasuk dalam kelas ini sebagai turunan terpisah dari kelas GeksaFoot .

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


The kelebihan beban getPose dan setPose metode dimaksudkan untuk penggunaan internal dan memungkinkan Anda untuk mendapatkan posisi saat anggota badan robot atau set yang baru. Dalam hal ini, posisi kaki diatur dalam bentuk seperangkat nilai sudut rotasi masing-masing sendi atau sebagai satu set koordinat titik akhir tungkai robot relatif terhadap pusatnya.
Untuk gerakan tungkai yang halus saat memanggil metode setPose , Anda dapat menentukan waktu (parameter actionTime ) setelah itu tungkai harus mencapai posisi yang ditentukan.
Robot dikendalikan oleh metode publik bergerak , memutar dan berhenti .

Emulasi multitask


Kelas Geksapod mewarisi implementasi kelas AJobManager dan berisi instance kelas MotionJob , yang pada gilirannya mewarisi dari kelas AJob . Kelas-kelas ini memungkinkan Anda untuk mengimplementasikan apa yang disebut multitasking non-preemptive, memungkinkan Anda untuk abstrak dari linearitas program dan melakukan beberapa tugas pada saat yang bersamaan.

ajob kelas
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
};


Kelas AJob adalah kelas dasar untuk semua tugas yang membutuhkan eksekusi simultan. Ahli warisnya harus mengganti metode onRun , yang mengimplementasikan logika dari tugas yang sedang dilakukan. Mengingat spesifik multitasking preemptive, memanggil metode ini seharusnya tidak terlalu memakan waktu. Disarankan untuk memecah logika tugas menjadi beberapa subtugas yang lebih ringan, yang masing-masing akan dilakukan untuk panggilan onRun yang terpisah .

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


Kelas AJobManager memiliki deklarasi yang lebih sederhana dan hanya berisi dua metode publik: setup dan loop . Metode setup harus dipanggil satu kali sebelum memulai loop program utama. Di dalamnya, satu per satu inisialisasi semua tugas terjadi, dengan memanggil metode onInit yang sesuai untuk setiap tugas dari daftar.
Suatu tugas ditambahkan ke daftar secara otomatis ketika konstruktornya dipanggil dan dapat dihapus dengan memanggil metode penyelesaian publik dari tugas itu sendiri.

Metode loopItu disebut berulang kali dalam siklus program utama dan bertanggung jawab untuk secara berurutan mengeksekusi logika setiap tugas dari daftar pada interval yang ditentukan (jika diinstal).

Jadi, ketika membuat instance dari kelas Geksapod yang diwarisi dari kelas AJobManager , kita dapat menggunakan alat multitasking yang nyaman.

Implementasi gerakan


Setiap gerakan tubuh dapat dijelaskan oleh beberapa fungsi yang menentukan posisinya pada titik waktu tertentu. Fungsi semacam itu dapat berupa gabungan, yaitu, ia dapat berupa serangkaian fungsi, yang masing-masing hanya berlaku untuk periode waktu tertentu.

Berbagai jenis gerakan anggota badan hexapod didefinisikan dan dapat diperpanjang menggunakan kelas yang diwarisi dari kelas Motion .

Gerak kelas
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; };       
};


Untuk mengimplementasikan gerakan, metode getPose yang ditimpa harus mengembalikan posisi anggota robot untuk periode waktu tertentu dalam rentang dari 0 hingga mMaxTime (dalam milidetik). Dalam kasus ketika gerakan diulang ( m_IsLooped == true) , waktu gerakan dapat dibatasi dengan mengatur durasi dalam m_TotalTime . Dan akhirnya, Anda dapat mengatur urutan gerakan dengan menggabungkannya ke dalam daftar.

Dengan demikian, kami memiliki kesempatan untuk menggambarkan pergerakan robot. Tetapi deskripsi itu sendiri (dalam kasus kami, beberapa instance dari kelas yang diwarisi dari Motion) tidak akan membuat robot bergerak. Kita membutuhkan mekanisme yang akan mengatur ulang kaki hexapod, dipandu oleh deskripsi yang diberikan padanya.

Dan mekanisme ini adalah turunan dari kelas MotionJob yang dideklarasikan di kelas Geksapod .

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


Sebuah instance dari kelas MotionJob , yang diwarisi dari AJob, adalah tugas di mana metode onRun dipanggil secara berkala . Itu juga menerapkan mekanisme yang memaksa robot kami untuk melakukan gerakan. Kami hanya perlu memberitahunya cara bergerak, yang menunjukkan deskripsi gerakan saat memanggil metode eksekusi. Itu saja untuk saat ini. Masih banyak masalah yang belum diterangi yang akan saya coba tulis di artikel selanjutnya. Saya harap saya tidak membuat lelah para pembaca dengan kode terlalu banyak. Siap menjawab semua pertanyaan Anda. Untuk dilanjutkan ... PS Dalam proses mempersiapkan artikel berikutnya, saya sedikit mengubah struktur visibilitas metode di kelas AJob . Metode OnInit







, onRun dan onDone tidak perlu akses publik, karena panggilan mereka berasal dari kelas AJobManager yang ramah . Mempertimbangkan bahwa metode ini harus tumpang tindih dalam ahli waris, cukup menempatkannya di bagian yang dilindungi .

ajob kelas
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