Encoder ile açı ölçümü mantık hatası

Başlatan Mucit23, 31 Aralık 2013, 13:33:01

Mucit23

Arkadaşlar elimde 1 turda 2048 puls veren bir encoder var. Bu encoder ile bir milin açısını ölçmeye çalışıyorum. Mil sağa döndüğünde değer 0-360 arası değişecek. Sola döndüğünde ise açı değeri azalacak.

Encoderin hangi yöne döndüğünü bir flip flopla tepit ediyorum. Daha sonra encoderden gelen A sinyalini Timer1 donanımıyla sayıyorum.

Main içerisinde şöyle bir algoritma kurdum.
     if(get_timer1()>0)
     {
      if(input(pin_c1))//Sağa Dönüyorsa
      {
         sayac+=get_timer1();
         set_timer1(0);
         encoder_sayac=sayac;
         if(sayac>=encoder)sayac=0;
      }
      else
      {
         encoder_sayac-=get_timer1();
         set_timer1(0);
         if(encoder_sayac<0) encoder_sayac=encoder-abs(encoder_sayac);
         sayac=encoder_sayac;
      }
      angle_val=(float)(sayac*360.0)/encoder;
      disp_update(angle_val*10);
     }


disp_update fonksiyonuna gönderdiğim değer ekrana basılıyor. 1023 gönderirsem ekranda 1023 çıkar.

Kurduğum mantık şöyle. İlk önce timer1 in değerine bakıyorum 0 dan büyükse diskte hareket var demektir.
Ardından hemen diskin ne tarafa döndüğüne bakıyorum. Eğer c1 pini 1 ise sağa dönüyor demektir.
Sağa döndüğünü varsayıp devam edersek timer1 değerini alıp sayaç değerine ekliyorum.
Ardından hemen timer1 sıfırlıyorumki mil dönmeye devam ederse bir sonraki döngüde yeni timer değerini alabileyim. Sonrasında zaten basit bir orantı kurarak açı değerini hesaplayıp ekrana yazıyorum.

Eğer sola dönüyorsa ise biraz daha karmaşık işlemler yapmam gerekiyor. Timer1 değerini encoder_sayac değerinden çıkarıyorum. encoder_sayac signed int değer olduğu için negatif değer alıyor.

Eğer negatif bir değer alırsa aşağıdaki kodla encoder_sayac'ın pozitif değerini hesaplıyorum
if(encoder_sayac<0) encoder_sayac=encoder-abs(encoder_sayac);

Kodlarda sıkıntı yok gibi amacına hizmet ediyor Fakat şöyle bir problem var.

Puls kaçıyor. Donanımsal bir problem yok. Özel bir program yazıp 360 derece boyunca encoderden gelen pulsları saydım tam 2048 tane clock sinyali geliyor. Burada sıkıntı yok. Sorun gerçekte mili yavaş çevirdiğimiz zaman hata yapıyor olması. mili 360 derece çeviriyorum ekranda  270 küsür bir değer görüyorum. Ama hızlı çevirdiğim zaman 330 veya 340 gibi birşey görüyorum. Kesinlikle yazılımsal olarak bir hata var ama çözemedim.

Mantıkdamı problem var sizce


Mucit23

@selimkoc

Ben encoder kullanmayı biliyorum. Sorun encoder kullanımıyla ilgili değil! Sorun matematiksel olarak yaptığım işlemlerde neden hata çıkıyor. onu çözemiyorum.

Kabil ATICI

if(sayac>=encoder)sayac=0;
sayac değerin encoder değerini aşarsa sıfırla diyorsun. Buradaki fazlalıklar bir sonraki saymada ne yazık ki  aradaki fark kadar eksilmiş olacak..

if(sayac>=encoder)sayac=sayac-encoder;
şeklinde olması gerekir. yoksa aradaki fark sana hata olarak yansır.


tabii
if(abs(encoder_sayac)>=encoder) şartı gerçekleşir mi bilemem.
ambar7

Mucit23

#4
Sorun şurada

         sayac+=get_timer1();
         set_timer1(0);

Ben burada ilk başta timer değerini alıyorum ardından timeri sıfırlıyorum. İşte bu iki kod arasında geçen sürede birçok pals geliyor. ve sonuç olarak timeri sıfırladığım için bu gelen pals lar çöpe gidiyor.

İşlemci 16F877 20 Mhz ye çıkardım. Biraz düzeldi fakat yine problem var.

edit;

Kodun tümü şöyle,
#include <16F877A.h>
#device adc=10

#FUSES NOWDT                    //No Watch Dog Timer
#FUSES HS                       //Crystal osc <= 4mhz for PCM/PCH , 3mhz to 10 mhz for PCD
#FUSES NOBROWNOUT               //No brownout reset
#FUSES NOLVP                    //No low voltage prgming, B3(PIC16) or B5(PIC18) used for I/O

#use delay(clock=20000000)

#use fast_io(a)       //Port Giriş çıkışları yönlendiriliyor
#use fast_io(b)
#use fast_io(c)
#use fast_io(d)
#use fast_io(e)

#define dig0 pin_c7
#define dig1 pin_c6
#define dig2 pin_c5
#define dig3 pin_c4

#define buton pin_a0

#define encoder 2048

// Ortak anot display için veri değerleri
const unsigned int digit[11]={0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8, 0x80, 0x90};
signed int16   encoder_sayac=0;
unsigned int16 sayac=0;
unsigned int8 tim_syc=0,disp_data[4];
static unsigned int1 point=0;
float angle_val=0;
void disp_update(unsigned int16 data);

//****************** Timer0 Kesmesi *****************************
#int_timer0 // Timer0 kesmesi
void Timer0_kesmesi () // Kesme fonksiyonu ismi
{ 
  switch(tim_syc)
  {
    case 0:
      output_high(dig3);
      output_b(disp_data[0]);
      output_low(dig0);
    break;
    case 1:
      output_high(dig0); 
      if(point==1){bit_clear(disp_data[1],7);}else{bit_set(disp_data[1],7);}
      output_b(disp_data[1]);
      output_low(dig1);
    break;
    case 2:
      output_high(dig1);
      output_b(disp_data[2]);
      output_low(dig2);
    break;
    case 3:
      output_high(dig2);
      output_b(disp_data[3]);
      output_low(dig3);
    break; 
  }
  tim_syc++;if(tim_syc>3)tim_syc=0;
set_timer0(131); 
clear_interrupt(int_timer0);       
}

void main()
{
   set_tris_a(0x01);     //Port giriş çıkışları aktif ediliyor.
   set_tris_b(0x00);
   set_tris_c(0x03);
   set_tris_d(0x00);
   set_tris_e(0x00);
   output_a(0x00);
   output_b(0xFF);
   output_c(0x00);
   output_d(0x0F);
   output_e(0x00);
 
   setup_adc(ADC_OFF);         
   setup_timer_0(T0_INTERNAl |  T0_8_BIT | T0_DIV_16 ); 
   setup_timer_1(T1_EXTERNAL | T1_DIV_BY_1);        
   enable_interrupts(INT_timer0);                             
   set_timer0(131);                                    //2ms aralıklarla kesme oluşsun.
   set_timer1(0);
   delay_ms(500);
   disp_update(0);
   point=1;
   enable_interrupts(GLOBAL);
   while(TRUE)
   {  
     if(get_timer1()>0)
     {
      if(input(pin_c1))//Sağa Dönüyorsa
      {
         sayac+=get_timer1();
         
         set_timer1(0);
         encoder_sayac=sayac;
         if(sayac>=encoder)sayac=0;
      }
      else
      {
         encoder_sayac-=get_timer1();
         set_timer1(0);
         if(encoder_sayac<0) encoder_sayac=encoder-abs(encoder_sayac);
         sayac=encoder_sayac;
      }
      angle_val=(float)(sayac*360.0)/encoder;
      disp_update(angle_val*10);
     }

   if(!input(buton))
   {
     encoder_sayac=0;
     sayac=0;
     set_timer1(0);
     angle_val=(float)(sayac*360.0)/encoder;
     disp_update(angle_val*10);
   }
   }
}

void disp_update(unsigned int16 data){
  disp_data[0]=digit[data%10];
  disp_data[1]=digit[(int16)(data/10)%10];
  disp_data[2]=digit[(int16)(data/100)%10];
  disp_data[3]=digit[(int16)(data/1000)%10];
}


Mantığı tümüyle değiştirmem lazım. Yani timer'i hiç sıfırlamadan işi çözmem lazım.

Fikir veren olursa çok makbule geçer



mesaj birleştirme:: 31 Aralık 2013, 16:34:48

Sorunlardan biride şudur tespit ettiğim kadarıyla.

         sayac+=get_timer1();
         set_timer1(0);

bu iki kod arasında kesme oluşursa eğer yine bu iki kod arasında program epeyce bir sekteye uğrayacak dolayısıyla araya bir anlamda delay koymuş olacam.  yani bu sırada gelen pulslar çöpe gidecek.

iyildirim

@Mucit23

En sağlıklısı A B kanallarını işlemciye girip XOR işleminden geçirmek  olurdu. Yön bilgisi de elimizde olurdu.  Ama Timer üzerinden saydırmanın nedeni  her darbe geldiğinde kesmeye girmemek olsa gerek.

Yeterince hızlı bu kodları çalıştırabilirseniz, mantıkta pek sıkıntı yok gibi.
Kurduğunuz mantıkta  timer limite gelip reset yerse, veya siz bu fonksiyona girmeden önce yön değişimi olmuşsa  gibi durumlarda hata olur.

Hatayı daha çok yön değişiminde gördüğüm için  yön değişimi anını sıkı kontrol etmek  gerekir diye düşünüyorum. Sadece yön değişimi anında kesmeye girmeyi gerektirecek söyle bir şey olabilir.

Yön sinyali hem yükselen hemde düşen kenarda kesme üretebilecek gibi bir pine girilip, timer  2048 e set edilir.
Sadece yön değişimlerinde açı hesaplanıp, puls sayacı veya açı bilgisi hafızaya alınıp ve timer sıfırlanır.
Açıyı hesaplamak istediğimiz zaman o andaki açıya, yön değişimi anındaki açıyı ekler veya çıkarırız, bu arada timer da resetlenmez.

Mucit23

#6
Hocam mantık çok güzel çalışıyor. Özellikle 1000 pals ve altı encoderlerde sıkıntı çıkarmaz. Aslında şuanda 2048 puls lık encoderde de yavaş çevirirsem sıkıntı çıkarmıyor ama hızlı çevirirsem çok fazla kaçırıyor. Örnek veriyorum tur başına 2048 puls verecekkken 1700 lerde kalıyor.

Normalde timer kullanmamın sebebi yüksek hızlarda sıkıntı yaşamak istememem. Timeri de her okuduğumda sıfırlıyorumki yüksek hızlarda timer taşmasın.

Kullandığım şema budur.


ücretsiz resim barındırma

Gerçekte devreyi hazırlamışım. Bu yüzden bu donanımı bozmak işime gelmez. Dediğim gibi Timeri hiç sıfırlamadan işi çözmem lazım.

Aslında sadece arttırma şeklinde işi çözdüğümü söyleyebilirim. Timeri hiç sıfırlamıyorum. 0-65535 arasında değişiyor. Ben sadece timeri değerinin mod2048'ini alıyorum. Dolayısıyla timer hiç değişmeden hatasız bir şekilde 0-2047 arasında değişen bir değer oluyor elimde.  Fakat encoder sola döndüğü zaman bu değeri nasıl tersine saydırırım. İşte burada tıkanıyorum.

Örnek veriyorum. Sağa doğru dönerken motor durdu. Bu sırada timer değerimiz 1500 oldu diyelim. Sola dönmeye başladığı zaman bu sefer bu değeri geriye doğru eksiltip negatife indiği zaman ise tekrar 2048 den geriye saydıracağım.

Bunu yaparsam iş çözülür.


iyildirim

Tamam işte hocam, siz de söylüyorsunuz,  sorun yön değişiminde.

En son ölçümde timer sıfırlanmış, 1500 e geldiğinde yön değişmiş, sizin kodunuzun çalıştığı anda da timer değeri 2000 olsun.  Bu koda göre sayaca 1500 ekleyip 500 çıkarmanız gerekirken, yani sayaç 1000 artması gerekirken  2000 eksiliyor.

Yön değişimi anını yakayayıp gereken hesabı o anda yapıp timer sadece yön değişimi anında sıfırlanırsa doğru çalışır sanırım.
Olası başka sorunlar gürültü vs gibi şeyler olabilir. Mil çok yavaş dönme de mesela step motorla yavaş döndürülüyor gibi vuruntulu çalışıyorsa  yada yön değişimi anı için low pass filtre gibi birşeyler de eklenebilir.

engerex

Kesme ile niye yapmıyorsun? Z ucunu kullanırsan 1 puls hatayı bile fark edebilirsin.

musti463

Mustafa Emir SADE

Mucit23

Kesmeyle felan olmaz. Gelen palsler çok fazla hepsini kesmeyle yakalamam mümkün değil. Yine en sağlıklı yöntem timer kullanmak.

@i yıldırım hocam. Ben başka bir mantıkla yapmaya çalıştım. Bi inceleyin isterseniz.

     sayac=(get_timer1()%encoder);

     if(sayac!=old_value)
     {
      old_value=sayac;
      if(input(pin_c1))//Sağa Dönüyorsa
      { 
        encoder_sayac+=(sayac-encoder_val);
        if(encoder_sayac>encoder)encoder_sayac=0;
        encoder_val=sayac;
      }
      else
      {
        encoder_sayac-=(sayac-encoder_val);
        if(encoder_sayac<0)encoder_sayac=encoder-abs(encoder_sayac);
        encoder_val=sayac;
      }
      disp_update(encoder_sayac);
     }


Yaptığım işlem şöyle. Bahsettiğim gibi hiç timeri felan sıfırlamadan sürekli 2048'den mod alıyorum. Dolayısıyla sayaç değeri değiştikçe elimde sürekli 0-2047 arasında değişen bir değer oluyor. Bundan sonra sayacın bir önceki değeri ile bir sonraki değerine bakıp fark varsa eğer bu fark kadar ana sayacımıza ekliyorum veya çıkarıyorum. Çalışıyor fakat durup ters yöne felan çevirdiğimizde değerler anlamsızlaşıyor. Yaptığım işlemlere bakıyorum. Neden böyle oluyor ona bakıyorum. Gözünüze takılan birşey varmı

Şu dakika realtime debug yapma imkanım olsa çözecem meseleyi.

yldzelektronik

Kardeş eğer halen timer1 sıfırlama ile uğraşıyorsan veya timer1 değerini çekerken değer kaçırmalar oluyorsa timer1 değerini ccp h-l reglerinden al.aynı değerler orada da dönüyor.Proteusu kullansana?Oradan real time debug yaparsın ama işte elindeki encodere uygun enc var mı bilmiyorum.
Kişinin başına gelen hayır Allah'tandır. Kişinin başına gelen şer nefsindendir. Nefislerimizle kendimize zulüm ediyoruz.

Mucit23

#12
Tamam Çözdüm.

#include <16F877A.h>
#device adc=10

#FUSES NOWDT                    //No Watch Dog Timer
#FUSES HS                       //Crystal osc <= 4mhz for PCM/PCH , 3mhz to 10 mhz for PCD
#FUSES NOBROWNOUT               //No brownout reset
#FUSES NOLVP                    //No low voltage prgming, B3(PIC16) or B5(PIC18) used for I/O

#use delay(clock=20000000)

#use fast_io(a)       //Port Giriş çıkışları yönlendiriliyor
#use fast_io(b)
#use fast_io(c)
#use fast_io(d)
#use fast_io(e)

#define dig0 pin_c7
#define dig1 pin_c6
#define dig2 pin_c5
#define dig3 pin_c4

#define buton pin_a0

#define encoder_resoloution 2048

// Ortak anot display için veri değerleri
const unsigned int digit[11]={0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8, 0x80, 0x90};
signed int16 encoder_loc=0;
unsigned int16 counter=0,old_counter=0,fark=0;
unsigned int8 tim_syc=0,disp_data[4];
static unsigned int1 point=0;
float angle_val=0;
void disp_update(unsigned int16 data);

//****************** Timer0 Kesmesi *****************************
#int_timer0 // Timer0 kesmesi
void Timer0_kesmesi () // Kesme fonksiyonu ismi
{ 
  switch(tim_syc)
  {
    case 0:
      output_high(dig3);
      output_b(disp_data[0]);
      output_low(dig0);
    break;
    case 1:
      output_high(dig0); 
      if(point==1){bit_clear(disp_data[1],7);}else{bit_set(disp_data[1],7);}
      output_b(disp_data[1]);
      output_low(dig1);
    break;
    case 2:
      output_high(dig1);
      output_b(disp_data[2]);
      output_low(dig2);
    break;
    case 3:
      output_high(dig2);
      output_b(disp_data[3]);
      output_low(dig3);
    break; 
  }
  tim_syc++;if(tim_syc>3)tim_syc=0;
set_timer0(99); 
clear_interrupt(int_timer0);       
}

void main()
{
   set_tris_a(0x01);     //Port giriş çıkışları aktif ediliyor.
   set_tris_b(0x00);
   set_tris_c(0x01);
   set_tris_d(0x01);
   set_tris_e(0x00);
   output_a(0x00);
   output_b(0xFF);
   output_c(0x0F);
   output_d(0x00);
   output_e(0x00);
 
   setup_adc(ADC_OFF);         
   setup_timer_0(T0_INTERNAl |  T0_8_BIT | T0_DIV_64 ); 
   setup_timer_1(T1_EXTERNAL | T1_DIV_BY_1);        
   enable_interrupts(INT_timer0);                             
   set_timer0(99);                                    //2ms aralıklarla kesme oluşsun.
   set_timer1(0);
   delay_ms(500);
   disp_update(0);
   point=1;
   enable_interrupts(GLOBAL);
   while(TRUE)
   {  
   
     counter=(get_timer1()%encoder_resoloution);//Timer1 in modu alınıyor. 
     
     if(counter!=old_counter)// Sayaçta Değişim varsa
     {
      if(input(pin_d0))//Sağa Dönüyorsa
      { 
        if(counter<old_counter){    
          fark=(encoder_resoloution-old_counter)+counter;
        }else{
          fark=counter-old_counter;
        }
        encoder_loc+=fark;
        if(encoder_loc>=encoder_resoloution)encoder_loc=0;
      }
      else
      {
        if(counter<old_counter){
          fark=(encoder_resoloution-old_counter)+counter;
        }else{
          fark=counter-old_counter;
        }
        encoder_loc-=fark;
        if(encoder_loc<0)encoder_loc=encoder_resoloution-abs(encoder_loc);
      }   
      old_counter=counter;
      angle_val=(float)(encoder_loc*360.0)/encoder_resoloution;
      disp_update(angle_val*10);
     }

   if(!input(buton))
   {
     encoder_loc=0;
     counter=0;old_counter=0;
     fark=0;
     set_timer1(0);
     angle_val=(float)(encoder_loc*360.0)/encoder_resoloution;
     disp_update(angle_val*10);
   }
   }
}

void disp_update(unsigned int16 data){
  disp_data[0]=digit[data%10];
  disp_data[1]=digit[(int16)(data/10)%10];
  disp_data[2]=digit[(int16)(data/100)%10];
  disp_data[3]=digit[(int16)(data/1000)%10];
}


Bazı hataların vardı. Örneğin Encoderin yön bilgisini pin_c1 den alıyordum. Fakat bu pin aynı zamanda encoderin OSC1 girişi olduğu için buradaki lojik hareketler timer değerini arttırıyordu. Yön bilgisini başka pine taşıdım.

Yazılım mantığında çok fazla hata vardı. Onları düzelttim şimdi sıkıntı kalmadı. Timer1'i hiç sıfırlamadan işi çözdüm. Timer1'in sürekli modu alınıyor.  Puls kaçırma, dolayısıyla hata yok.


Kinqwolf

Hocam hangi editörde derledin programı