Çoklu servo çıkışı çalışmaları

Sumer Yamaner

Moderator
Katılım
17 Eyl 2013
Mesajlar
8,773
Tepkime puanı
23,158
Yaş
61
Konum
İstanbul
Web sitesi
www.sumeryamaner.com
İlgi Alanı
Uçak
Lütfen yanlış anlaşılmasın. Bunu paylaşma nedenim ukalalık değil. Tamamen arşiv amacıyla buraya koyuyorum. İlgilenen arkadaşlar olursa üzerinde tartışırız.


Bildiğiniz gibi kullandığımız RC sistemlerinin bazı standartları var. Servolara giden sinyal 1000 ile 2000 (hatta 800 ile 2200) mikrosaniye uzunluğunda darbeler. Bu darbelerin genişliği servo pozisyonunu belirliyor. (Darbe genişliği modülasyonu, pulse width modulation, PWM).
Ancak iş bu kadar basit değil. Özellikle standart servolar için darbelerin 20 milisaniye ara ile yani saniyede 50 kez gelmesi de gerekli. Daha sık darbe gelirse bazı analog servolar saçmalıyor hatta yanabiliyorlar. Daha seyrek darbe gelirse bazı digital servolar fail safe konumuna geçebiliyorlar.
Mikrokontrolörlerle yapılacak uygulamalarda bu standardın tutturulması büyük önem taşıyor. Allah'tan 20 milisaniye süresi çok sıkı bir sınırlama değil. Yani 18 ya da 25 milisaniye olsa da zararı yok. Ama çok da fazla şaşmamak gerekli.
Mikrokontrolörlerde bu düzeni sağlamanın iki yolu var. Birisi servo reverser, v tail mikser ya da servo yavaşlatıcı örneğinde olduğu gibi bir giriş darbesine karşılık bir çıkış darbesi üretmek. Bu durumda zamanlama sorunu yaşanmıyor. Ancak çoklu servo çıkışı gerektiren kompleks uygulamalarda iş biraz karışabiliyor. İşte o zaman işlemcinin interrupt özelliklerinin kullanılması gerekiyor. Interrupt demek belirli aralıklarla işlemcinin yaptığı işi bir kenara bırakıp zamanlanmış görevi yapıp sonra bıraktığı işe kaldığı yerden geri dönmesi demek. Yani işlemcinin her 20 milisaniyede bir işi gücü bırakıp servolara gerekli sinyali göndermesini sağlamamız gerekiyor.
Bunu daha önce yapıp burada paylaşmıştım. Ancak şöyle bir sorun var:
Eğer birden fazla servoya farklı değerlerde çıkış verilecek ise bunları peş peşe yapyordum. Yani önce bir servoya 800 - 2200 mikrosaniye arasında darbe gönderip sonra ikinci servoya geçiyordum. Toplam dört servo olduğunu ve her birinin 2200 mikrosaniye değerde olduğunu varsayalım. En az 8800 mikrosaniye hatta biraz daha fazla yani kabaca 9000 mikrosaniye gerekli. Yani 9 milisaniye... Her 20 milisaniyede bir interrupta gidip burada 9 milisaniye geçirirsek işlemcinin vaktinin yaklaşık % 45'i sadece bununla geçer. Arada yapılacak kompleks hesaplamalar falan olursa kalan 11 milisaniye yetersiz kalabilir. İşler aksayabilir.
Gerçi şu ana kadarki uygulamalarda bunun herhangi bir soruna yol açtığını görmüş değilim. Ancak bir adım ileriye gidebilme adına bir algoritma oluşturup test etmeye karar verdim.

Toplam dört servo için bir planlama yaptım. Zafer Şahin'i mutlu etmek için de array kullandım. Deneme için hazırladığım programda her bir servo için kafadan sallama değerler yazdım.
2 x 4 boyutlu ilk array'in (endpwm) ilk satırı birden dörde (aslında sıfırdan üçe) kadar servo numarasını, ikinci satırı da o servoya gidecek darbe uzunluğunu içeriyor. Sonra bu değerleri darbe süresine göre küçükten büyüğe sıralıyoruz. Sıralarken tabii ki servo numaraları da yer değiştiriyor. Sonra bu array (endpwm) değişkenini tekrar ele alıyoruz. Darbe sürelerini içeren satırın ilk elemanı en düşük darbe süresini içeriyor ve öylece kalıyor. Ama sonraki elemanlar darbe süresi değil de bir önceki darbe süresinden fark şeklinde hesaplanıp kaydediliyor.
Tüm bunlar zaman alan işlemler. Ama interrupt sırasında kullanılacak değerler hesaplanırken arada interrupt gelmesi halinde bazen saçma rakamlar oluşabiliyor. O nedenle nihai rakamları oluştururken kısa süreliğine interruptları kapatmak gerekiyor. İşte tüm hesaplamalar yapıldıktan sonra interruptlar kapatılıp hesaplanan değerler ayrı bir array (servopwm) değişkenine kopyalanıyor. Sonra interruptlar tekrar serbest bırakılıyor.

Tüm bu laf kalabalığından sonra servopwm array değişkeninde şöyle bir örnek yapı var:

0: 4, 1, 3, 2
1: 912, 23, 312, 36

Bu ne demek? En kısa darbe süresi 912 mikrosaniye ve 4 numaralı servoya gidecek. Sonra bir numaralı servoya bundan 23 mikrosaniye daha uzun yani 935 mikrosaniyelik bir darbe gidecek...
Servo çıkış zamanı geldiğinde dört servoya birden logic HIGH sinyali veriliyor. delayMicroseconds() komutu ile ilk (ve en kısa) darbe süresi kadar bekleniyor ve ilk sıradaki servo (bu örnekte 4 numaralı olan) çıkışına logic LOW yazılıyor.
Sonra bir sonraki değere bakılıyor. Eğer sıfırdan (bunu dörtten büyük olarak değiştirebilirim ileride) büyük bir değer ise yine delayMicroseconds() ile beklenerek, sıfır ise hiç beklenmeden ikinci sıradaki servonun (bu örnekte 1 numaralı servo) çıkışına logic LOW yazılıyor.
Döngü bu şekilde dördüncü sıradaki servonun darbe süresi de tamamlanana kadar devam ediyor.

Standart yönteme göre ne fark var?

Tüm servolara darbe aynı anda başlayarak gidiyor. Her birinin darbe süresine göre darbe sonlandırılıyor. Böylece tüm işlem en fazla 2200 mikrosaniye + bir miktar idarî süre içinde gerçekleşiyor. Kısaca interrupt işlemcinin zamanından 9 milisaniye yerine yaklaşık 2.5 milisaniye çalmış oluyor.

Bakalım gerçek hayat uygulamasında beklediğim gibi çalışacak mı... :)
 
Kod:
int senspin = 0;
int pwm = 1500;
int pwmout[] = {1500, 1500, 1500, 1500};
const int servopin[] = {9, 10, 11, 12};
volatile unsigned long timer;
volatile int x = 0;
volatile byte sregvalue;
int endpwm [2][4] = { {0, 1, 2, 3},
                          {10, 10, 10, 10} };
volatile int servopwm [2][4] = { {0, 1, 2, 3},
                          {10, 10, 10, 10} };

int temp = 0;

void setup() 
{
  for(int n = 0; n < 4; n++)
  {
    pinMode(servopin[n], OUTPUT);
  }
  timerinit();
}

void loop()
{
  pwm = 1000 + analogRead(senspin);
  pwmout[0] = 1560;
  pwmout[1] = 1350;
  pwmout[2] = 1890;
  pwmout[3] = 1050;
  
  for(int n = 0; n < 4; n++)
  {
    endpwm[0] [n] = n;
    endpwm[1] [n] = pwmout[n];
  }
// İç içe iki for döngüsü ile pwm değerlerinin (ve ait oldukları servo numaralarının) küçükten büyüğe sıralanmasını sağlıyoruz.  
  for(int n = 0; n < 3; n++)
  {
    for(int m = 0; m < 3; m++)
    {
      if(endpwm[1][m] > endpwm[1][m + 1])
      {
        temp = endpwm[1][m];
        endpwm[1][m] = endpwm[1][m + 1];
        endpwm[1][m + 1] = temp;
        temp = endpwm[0][m];
        endpwm[0][m] = endpwm[0][m + 1];
        endpwm[0][m + 1] = temp;
      }
    }
  }
  
// İlk sırada en küçük pwm değeri, sonraki sıralarda ise fark pwm değerleri yer alıyor
    endpwm[1][1] = endpwm[1][1] - endpwm[1][0];
    endpwm[1][2] = endpwm[1][2] - endpwm[1][1] - endpwm[1][0];
    endpwm[1][3] = endpwm[1][3] - endpwm[1][2] - endpwm[1][1] - endpwm[1][0];
    
// Hazırlanan değerler interruptlar kapatılarak volatile değişkene yazılıyor
  noInterrupts();
  for(int n = 0; n < 2; n++)
  {
    for(int m = 0; m < 4; m++)
    {
      servopwm[n][m] = endpwm[n][m];
    }
  }
  interrupts();
}



void timerinit()
{
  // initialize Timer1
  noInterrupts();  // disable global interrupts
  TCCR2A = 0;
  // prescaler 256
  TCCR2B = 0;
  TCCR2B |= (0 << CS20);
  TCCR2B |= (1 << CS21);
  TCCR2B |= (1 << CS22);
  TCNT2 = 0;
  TIMSK2 |= (1 << TOIE2);
  // enable global interrupts
  interrupts();
}

ISR(TIMER2_OVF_vect)
{
  sregvalue = SREG;
  x++;
  if(x > 4) // 4 olursa 20.5 mS, 5 olursa 24.6 mS PPM frame
  {
    x = 0;
    for(int n = 0; n < 4; n++)
    {
      digitalWrite(servopin[n], HIGH);
    }
    delayMicroseconds(servopwm[1][0]);
    digitalWrite(servopin[servopwm[0][0]], LOW);
    if(servopwm[1][1] > 0)
    {
      delayMicroseconds(servopwm[1][1]);
    }
    digitalWrite(servopin[servopwm[0][1]], LOW);
    if(servopwm[1][2] > 0)
    {
      delayMicroseconds(servopwm[1][2]);
    }
    digitalWrite(servopin[servopwm[0][2]], LOW);
    if(servopwm[1][3] > 0)
    {
      delayMicroseconds(servopwm[1][3]);
    }
    digitalWrite(servopin[servopwm[0][3]], LOW);
  }
  SREG = sregvalue;
}
 
Pratik uygulamada A0 ... A3 analog girişlerine birer potansiyometre bağlayacağım. Kodun içinde pwmout değerlerini değiştirdiğim bölümü şu şekilde modifiye edeceğim.

Kod:
  for (int n = 0; n < 4; n++)
  {
    pwmout[n] = 1000 + analogRead(senspin[n]);
  }

Yani programın ana loop'unda dört girişi peş peşe okuyup elde ettiğim değerleri hesaplamalarda kullanacağım.

Hedef birbirinden bağımsız olarak ve takılmadan, sapıtmadan hareket eden, potansiyometrelerle kumanda edilen dört servo elde etmek.
 
Sümer Yamaner' Alıntı:
Lütfen yanlış anlaşımlasın. Bunu paylaşma nedenim ukalalık değil. Tamamen arşiv amacıyla buraya koyuyorum. İlgilenen arkadaşlar olursa üzerinde tartışırız.
Abi aramizda bu sekilde algilacak duzeyde arkadas oldugunu dusunmuyorum :)

Diger taraftan yaptigin, ogrendigin her seyi arsiv niteliginde emek verip bizlerle de paylastigin icin ayrica tesekkur ederiz :thumbup: Bu tur Degerli paylasimlari indekslemek bizler icin ayri bir zevk :RCKolik:

Bu arada DIY indeks sayfasina soyle bir baktim da, kisa sure epey bir yol alindi sayenizde. Nerede ise Yok Yok :halay:

 
Çoklu servo çıkışı çalışmaları

Interrupt kullanmadan yaptığım deneme başarılı. Tam istediğim şekilde çalışıyor. Kodu aşağıya ekliyorum. Şimdi bunu interrupt ile daha düzenli yapmaya çalışacağım.

Kod:
int senspin[] = {0, 1, 2, 3}; // Potansiyometrelerin bağlı olduğu analog uçlar
int pwmout[] = {1500, 1500, 1500, 1500};
const int servopin[] = {9, 10, 11, 12};
volatile unsigned long timer;
volatile int x = 0;
volatile byte sregvalue;
int endpwm [2][4] = { {0, 1, 2, 3},
  {10, 10, 10, 10}
};
volatile int servopwm [2][4] = { {0, 1, 2, 3},
  {10, 10, 10, 10}
};

int temp = 0;

void setup()
{
  for (int n = 0; n < 4; n++)
  {
    pinMode(servopin[n], OUTPUT);
  }
}

void loop()
{
  timer = millis();
  for (int n = 0; n < 4; n++)
  {
    pwmout[n] = 1000 + analogRead(senspin[n]);
  }
  for (int n = 0; n < 4; n++)
  {
    endpwm[0] [n] = n;
    endpwm[1] [n] = pwmout[n];
  }
  // İç içe iki for döngüsü ile pwm değerlerinin (ve ait oldukları servo numaralarının) küçükten büyüğe sıralanmasını sağlıyoruz.
  for (int n = 0; n < 3; n++)
  {
    for (int m = 0; m < 3; m++)
    {
      if (endpwm[1][m] > endpwm[1][m + 1])
      {
        temp = endpwm[1][m];
        endpwm[1][m] = endpwm[1][m + 1];
        endpwm[1][m + 1] = temp;
        temp = endpwm[0][m];
        endpwm[0][m] = endpwm[0][m + 1];
        endpwm[0][m + 1] = temp;
      }
    }
  }
  // İlk sırada en küçük pwm değeri, sonraki sıralarda ise fark pwm değerleri yer alıyor
  endpwm[1][1] = endpwm[1][1] - endpwm[1][0];
  endpwm[1][2] = endpwm[1][2] - endpwm[1][1] - endpwm[1][0];
  endpwm[1][3] = endpwm[1][3] - endpwm[1][2] - endpwm[1][1] - endpwm[1][0];

  // Hazırlanan değerler interruptlar kapatılarak volatile değişkene yazılıyor
  for (int n = 0; n < 2; n++)
  {
    for (int m = 0; m < 4; m++)
    {
      servopwm[n][m] = endpwm[n][m];
    }
  }

    for (int n = 0; n < 4; n++)
    {
      digitalWrite(servopin[n], HIGH);
    }
    delayMicroseconds(servopwm[1][0]);
    digitalWrite(servopin[servopwm[0][0]], LOW);

    if (servopwm[1][1] > 4) delayMicroseconds(servopwm[1][1]);
    digitalWrite(servopin[servopwm[0][1]], LOW);

    if (servopwm[1][2] > 4) delayMicroseconds(servopwm[1][2]);
    digitalWrite(servopin[servopwm[0][2]], LOW);

    if (servopwm[1][3] > 4) delayMicroseconds(servopwm[1][3]);
    digitalWrite(servopin[servopwm[0][3]], LOW);

  while((millis() - timer) < 20)
  {
  }
}
 
Çoklu servo çıkışı çalışmaları

Interrupt kullanılarak da gayet güzel oluyor. Artık rahat uyuyabilirim bu gece. :)

Kod:
int senspin[] = {0, 1, 2, 3}; // Potansiyometrelerin bağlı olduğu analog uçlar
int pwmout[] = {1500, 1500, 1500, 1500};
const int servopin[] = {9, 10, 11, 12};
volatile unsigned long timer;
volatile int x = 0;
volatile byte sregvalue;
int endpwm [2][4] = { {0, 1, 2, 3},
  {10, 10, 10, 10}
};
volatile int servopwm [2][4] = { {0, 1, 2, 3},
  {10, 10, 10, 10}
};

int temp = 0;

void setup()
{
  for (int n = 0; n < 4; n++)
  {
    pinMode(servopin[n], OUTPUT);
  }
  timerinit();
}

void loop()
{
  timer = millis();
  for (int n = 0; n < 4; n++)
  {
//    pwmout[n] = 800 + 1.4 * analogRead(senspin[n]);
    pwmout[n] = 1000 + analogRead(senspin[n]);
  }
  for (int n = 0; n < 4; n++)
  {
    endpwm[0] [n] = n;
    endpwm[1] [n] = pwmout[n];
  }
  // İç içe iki for döngüsü ile pwm değerlerinin (ve ait oldukları servo numaralarının) küçükten büyüğe sıralanmasını sağlıyoruz.
  for (int n = 0; n < 3; n++)
  {
    for (int m = 0; m < 3; m++)
    {
      if (endpwm[1][m] > endpwm[1][m + 1])
      {
        temp = endpwm[1][m];
        endpwm[1][m] = endpwm[1][m + 1];
        endpwm[1][m + 1] = temp;
        temp = endpwm[0][m];
        endpwm[0][m] = endpwm[0][m + 1];
        endpwm[0][m + 1] = temp;
      }
    }
  }
  // İlk sırada en küçük pwm değeri, sonraki sıralarda ise fark pwm değerleri yer alıyor
  endpwm[1][1] = endpwm[1][1] - endpwm[1][0];
  endpwm[1][2] = endpwm[1][2] - endpwm[1][1] - endpwm[1][0];
  endpwm[1][3] = endpwm[1][3] - endpwm[1][2] - endpwm[1][1] - endpwm[1][0];

  // Hazırlanan değerler interruptlar kapatılarak volatile değişkene yazılıyor
  noInterrupts();
  for (int n = 0; n < 2; n++)
  {
    for (int m = 0; m < 4; m++)
    {
      servopwm[n][m] = endpwm[n][m];
    }
  }
  interrupts();

// Burada başka işler yapılabilir. Hatta istenirse navigasyoın ışıkları, strobe ışıkları falan çaktırılabilir.

}



void timerinit()
{
  noInterrupts();
  TCCR2A = 0;
  TCCR2B = 0;
  TCCR2B |= (0 << CS20); // prescaler 256
  TCCR2B |= (1 << CS21);
  TCCR2B |= (1 << CS22);
  TCNT2 = 0;
  TIMSK2 |= (1 << TOIE2);
  interrupts();
}

ISR(TIMER2_OVF_vect)
{
  sregvalue = SREG;
  x++;
  if (x > 4) // 4 olursa 20.5 mS, 5 olursa 24.6 mS PPM frame
  {
    x = 0;
    for (int n = 0; n < 4; n++)
    {
      digitalWrite(servopin[n], HIGH);
    }
    delayMicroseconds(servopwm[1][0]);
    digitalWrite(servopin[servopwm[0][0]], LOW);

    if (servopwm[1][1] > 4) delayMicroseconds(servopwm[1][1]);
    digitalWrite(servopin[servopwm[0][1]], LOW);

    if (servopwm[1][2] > 4) delayMicroseconds(servopwm[1][2]);
    digitalWrite(servopin[servopwm[0][2]], LOW);

    if (servopwm[1][3] > 4) delayMicroseconds(servopwm[1][3]);
    digitalWrite(servopin[servopwm[0][3]], LOW);
  }
  SREG = sregvalue;
}