RC Servo İçin PWM Üretmek

Başlatan Tagli, 27 Nisan 2011, 09:34:01

Tagli

Bilindiği üzere RC servoların kontrolü için 20 ms periyodlu bir PWM gerekiyor ve 1 kalma süresi 1 - 2 ms arasında değiştirilerek kontrol sağlanıyor. Bu iş için donanımsal PWM modüllerinin kullanılamayacağı zaten bilinen bir durum, çünkü bunlar düşük frekansta çalışamıyorlar (PIC yavaşlatılmadığı sürece, ki bu da benim için mümkün değil).

Bu işin genelde uygulanan çözümü nedir? Bir timer kesmesi kurarak istenildiği kadar motor kontrol edilebilir. Bunu daha önce yaptım, sorun yok. Benim sorunum çözünürlük, yani o 1 ms'lik aralığın kaç parçaya bölüneceği. Daha önce 10 parçaya bölmüştüm. Bu da 100 us'de bir kesme gelmesi anlamına geliyor. Şimdi ise bana daha yüksek çözünürlük gerekiyor, 100 parça gibi. Bu durumda 10 us'de bir kesme gelecek.

Şu anda üzerinde çalıştığım PIC 18F2550. Hızının 12 MIPS olduğunu göz önünde bulundurursak, 10 us demek 120 komut demek. Ben timer kesmesini (yüksek öncelikli olarak) assembly ile yazsam bile, içerideki işlemin 30 - 40 komutluk zaman (cycle) alacağını tahmin ediyorum. Bu durumda işlem gücümü de yaklaşık %30 oranında kaybediyorum.

Bunun başka bir yolu var mıdır? Her ne kadar pek deneyimim olmasa da elimde birkaç tane dsPIC var, emin değilim ama onların PWM modüllerini kontrol eden timer'ları başka bir clock ile beslenebiliyor, yani yavaşlatılabilirler. Ama bana 4 tane çıkış gerekiyor. O yüzden bu da bir seçenek değil. Son çare olarak PWM sinyallerini slave konumundaki başka bir PIC ile üreteceğim sanırım.
Gökçe Tağlıoğlu

Andromeda

#1
4 kanalı  kontrol etmek 40 komut ile yapılıyorsa bu da yaklaşık 3 us sürer...
bu da yeterli gözüküyor. sorun olmaması lazım..

birde şu var, 20 ms içinde çok boşluk var.. orada birçok iş yapılır..
tabi problemi yanlış anlamadı isem....
" Tanrı, iradesini hakim kılmak için yeryüzündeki iyi insanları kullanır, yeryüzündeki kötü insanlar ise kendi iradelerini hakim kılmak için Tanrı'yı kullanırlar." ..." Tanrı'dan mesaj gelmiyor, biz Tanrı'ya mesaj gönderiyoruz"

Tagli

Ben süre olarak değil oran olarak bakıyorum soruna. Söz konusu 3 us'den sonra benim işlem yapmak için 7 us zamanım olacak. Sonra bir 3 us daha zaman kaybı...

Ayrıca boş kalan süre için de yine kesme gelmesi gerekiyor. Bilmiyorum, belki de ben tamamen yanlış düşünüyorum, zaten soruyu da o yüzden sordum.

Benim mantık şu şekilde:

10 us'de bir kesme gelsin. Her kesmede bir değişkeni 1 arttırayım. Bu değişken 0'dan 2000'e kadar değerler alsın, 2000 olunca yine sıfırlansın. Böylece 2000 * 10 us = 20 ms yapıyor. Her motor için ise bir kontrol değişkeni olacak, bunlara da kesme dışında ihtiyaca göre 100'den 200'e kadar değerler vereceğim, bu da 1 - 2 ms aralığına denk gelecek bu aralığı 100'e bölmüş olacak. Kesme kodunda 4 motorun kontrol değerini o anki sayaç değeriyle karşılaştıracağım. Sayaç kontrol değerinden büyük ise 0, küçük ise 1 verilecek çıkışa.

Tahminimce bu işlemler 30 - 40 cycle tutar. 10 us ise 120 cycle yapıyor. %30'luk işlem gücü kaybına da bu şekilde ulaştım.
Gökçe Tağlıoğlu

XX_CİHAN_XX

Bence 18-19ms lik kesmeler yapmanız yeterlidir. Bütün servoların pals başlangıçlarını senkronladığınızda sorun kalmayacaktır.
Yirmi yaşındaki bir insan, dünyayı değiştirmek ister . Yetmiş yaşına gelince , yine dünyayı değiştirmek ister, ama yapamayacağını bilir.

Tagli

Hocam kesme 20 ms'de bir gelirse, 1 ms'lik aralığı nasıl böleceğim? Yani 1 kalma süresini nasıl kontrol edebilirim?
Gökçe Tağlıoğlu

XX_CİHAN_XX

Programının 1-2ms boyunca kesme vektöründe pals üretmekle meşgul kalması ana programda ciddi sorunlara yol açmayacaksa kesme vektöründe tüm servoların için gerekli palsleri aynı anda üretebilirsin.
Yirmi yaşındaki bir insan, dünyayı değiştirmek ister . Yetmiş yaşına gelince , yine dünyayı değiştirmek ister, ama yapamayacağını bilir.

Tagli

Evet, bu mümkün olabilir belki. Aslında, sadece o 1 ms'lik bölümü 100'e bölüp, bu sürenin bitiminde timer'ı 20 ms sonra kesme oluşturacak şekilde tekrar ayarlarsam, bu durumda işlem gücü kaybı %5 civarında olacaktır. Kodlama biraz karışık olacak ama yapılmayacak iş değil. Fikir için teşekkürler XX_CİHAN_XX.

Yine de başka fikri olan varsa o arkadaşları da duymak isterim.
Gökçe Tağlıoğlu

iyildirim

Timer kurup darbeleri tek tek saymak eğer tek işiniz servo sürmek ise olabilir.  Ancak başka bir sürü işin yanında bir de servo için 50Hz lik darbe üretecekseniz doğru çözüm CCP modülünü kullanarak PWM üretmeniz olacaktır.

18F leri iyi bilmiyorum, ancak frekans 50Hz 'e inemiyor diye bu forumda okuduğumu hatırlıyorum. dsPIC'ler de 50Hz üretmek sorun değil. İşlemci hızına bağlı olarak, 10 bit ve hatta üzeri çözünürlükte servo darbesi üretilebiliyor. CCP kanal sayınız kadar da servo kontrolünü yaparbilirsiniz.

18F lerde ise timer'ı sabit bir 10usec gibi değere set edip 10usec leri saymaktansa, direkt olarak servo darbe uzunluğuna set etmek, darbenin sadece yükselen ve düşen kenarlarında kesmeye girmek daha doğru.


4 adet servoda max darbe süresi 2 ms olsa toplam 8ms high darbe süresi olarak demektir.  Bu da 20 ms lik çevrim boyunu aşmıyor.

Tek timer la bile rahatça uygulanabilir.

Örneğin 20ms'yi 4 ' bölelim.  bir servo için 5 ms çıkar.

1.servonun pinini high yapıp, timer'ı 1 servonun high darbe süresine kuralım.
kesme de pini low yapıp  5 - (1.servonun high süresi) ms ye kuralım..

Kesme geldiğinde 5ms geçmiş olacak.
2 servonun pinini high yapıp timer'i 2 servonun high süresine kuralım... .


şeklinde bir yapı ile tek timer ile 12MIPS de 10 bit üzeri bir çözünürlük elde edilebilir.

Bu durumda CCP veya PWM kullanmanın tek avantajı da saniye de 50*4*2 adet kesme de kaybedilen işlem gücünün geri kazanmak oluyor.

iyildirim

Yukarıda bahsettiğim yönteme örnek..
derleyici C18 ..

4 Servo için timer0 kullanarak 12MIPS de 50Hz lik frekansda 1ms için 6000 gibi bir çözünürlük sağlanabiliyor. Darbe üretmek işi sadece kesme de yapılıyor. Duty'nin değişmesi için Duty dizisini 1 ms için 6000 kullanarak herhangi bir yerde update etmek yeterli. 

OSC ayarlarını 18F leri pek bilmediğimden MPLAB içerisinde tanımladım. Testi de MPLAB simulatorü ile yaptım.
Paylaşım sitelerinde beklemek vs. nedeni ile proje klasörü yerine kodu paylaşmakla yetineceğim.

#include <p18f4550.h>

#define FCY 12000000L
#define TIMER0_PRESCALER 2L
#define SERVO_FREQ    50L
#define SERVO_COUNT  4L

#define TIMER0_5MS  FCY / (SERVO_FREQ * SERVO_COUNT) / TIMER0_PRESCALER
#define TIMER0_1MS  TIMER0_5MS / 5L
#define TIMER0_1US  TIMER0_1MS / 1000L


void timer_isr (void);
void timer_high_isr (void);


unsigned int duty[] = {6000,7500,9000,12000};        //12MIPS de 1ms = 6000 
unsigned char servoNdx = 0;

char highEdge = 0;
 
unsigned int tmrval;
unsigned int lastDuty;


#pragma code high_vector=0x08
void interrupt_at_high_vector(void)
{
_asm GOTO timer_high_isr _endasm
}
#pragma code

void timer_high_isr (void)
{
    
    LATB = 0x00;
    
    if (highEdge == 1)
    {
        lastDuty = duty[servoNdx];
        tmrval = 0xFFFF - lastDuty;
        LATB = 1<<  servoNdx;
        highEdge = 0;
    }
    else
    {

        tmrval = 0xFFFF - (60000 - lastDuty); 
        
        highEdge = 1;
        servoNdx++;
        if (servoNdx>=SERVO_COUNT)
            servoNdx = 0;
    }
        
    
      TMR0H = tmrval >>8;
      TMR0L = tmrval;

    INTCONbits.TMR0IF = 0;
    INTCONbits.GIE = 1;

}





void main (void)
{
    
    LATB  = 0;
    TRISB = 0;
    PORTB = 0;
    
    T0CON = 0b00000000;    //PRESCALER 1/2
    //            X  PRE ON OFF   0 PRESCALER ON 
    //             XXX PRESCALER

    servoNdx  = 0;
    TRISB = 0;
     
      TMR0H = 0;
      TMR0L = 0;
      
    highEdge = 1;
    
    tmrval = 0xFFFF - TIMER0_1US * 1;

      TMR0H = tmrval >>8;
      TMR0L = tmrval;

      T0CONbits.TMR0ON = 1;

      INTCONbits.TMR0IF = 0;
   INTCONbits.TMR0IE = 1;
    RCONbits.IPEN = 1;
    INTCONbits.GIE = 1;
    INTCONbits.PEIE = 1;

    while (1)
    {
    }
}


Tagli

Teşekkürler hocam, eline sağlık. Yalnız aklıma bir takılan bir mesele var: Bu durumda 1. motor ile 4. motorun emirleri arasında yaklaşık 20 ms fark olacak. Gerçi öyle sanıyorum ki 5 ms beklemek gerekmiyor. Her motor için 2 ms versek de yeterli olur ve bu durumda azami gecikme, daha doğrusu senkronizasyon hatası 8 ms civarında olur. Uygulamamda bu durum sorun çıkarır mı bilmiyorum, sanırım denemeden anlamak da mümkün değil. Fikir için tekrardan teşekkürler...
Gökçe Tağlıoğlu

iyildirim

Zaten sinyaller havada seri olarak geliyor,  kumanda alıcından da mcu ya da aynı şekilde sıra ile geliyor. Sorun olmaz..
2 ms ye düşersen tam sınırda, kritik olur.  3-5 ms uygun bence.

Gerçek devre de denenmedi.  Sonradan farkettim. 18F leri de fazla tanımıyorum. Bu ilk. Kesme oluşturmak ne zormuş. 
Simulasyonda sanki timer ikişer ikişer sayıyormuş gibi bir durum var. 
Sanırım ya 6000 lik 1 ms değeri 12000 olacak, yada 60000 lik 5ms değeri 30000..