Elektronik Devreler Projeler Elektronik ve biraz daha fazlası İletişim - Araçlar - Dikkat - Topluluk
Elektronik / Mikrodenetleyici Projeleri/

Encoder Kullanımı, Açı ölçümü ve CCS C PIC16F628 Örnek Uygulama

Sponsorlu Bağlantılar

Merhaba Arkadaşlar

Bu yazımda incremental enkoderlerin kullanımına değineceğim. Bu enkoderler ile hassas bir şekilde nasıl açı ölçümü veya konum tespiti yapılacağını anlatacağım.

Piyasada Absolute (Mutlak) Enkoderler ve İncremental (Artımsal)  Enkoderler olmak üzere iki çeşit enkoder bulunmaktadır.

Absolute Enkoderler her pozisyona göre farklı çıkışlar verirler. Bu tip enkoderlerde Gray ve Binary olmak üzere kendi aralarında ikiye ayrılırlar. Haberleşme açısından aralarında fark yoktur. Tek fark kodlamada dır. Absolute enkoderlerin genelde çıkışları paralel olmakla birlikte piyasada 0-10V analog çıkış veren modellerde mevcuttur.

Bu enkoderlerin en büyük artısı enkoderin enerjisi kesilse dahi çıkışta ki konumunu korur. Eksi tarafı ise fiyatlarının daha pahalı olmasıdır.

2. olarak piyasada incremental enkoderler kullanılır. Bu tip enkoderler Absolute Enkoderler’de olduğu gibi paralel çıkış vermez. Enkoder her pozisyonda benzer kare dalga sinyalleri üretir. İncremental enkoderlerde enkoderin çözünürlüğünü bir başka deyişle hassasiyetini belirleyen faktör enkoderin PPR(pulse per rotation) değeridir. Bu değer nekadar yüksek olursa enkoder okadar hassastır diyebiliriz.

Aşağıda ki resimde incremental enkoder ile Absolute enkoder arasındaki farkı görebilirsiniz.

incremental-enkoder-absolute-enkoder-arasindaki-fark

Piyasada 100 puls’den 4096 puls’e kadar encoder bulunmaktadır. Hatta ihtiyaca göre daha yüksek çözünürlükte enkoderlerde yapılabilmektedir.

Absolute enkoderlerde enkoderin konumu direk alındığı için enkoderin ne tarafa döndüğünü öğrenmek kolaydır. Konum zaten her zaman bellidir. Enkoderin sıfır noktası herzaman sabittir. İncremental enkoderlerde ise böyle bir durum söz konusu değildir. Enkoder ilk enerjilendiği andan itibaren sıfırdan saymaya başlar. Enkoderi okuyacak olan donanım kaç puls geldiğini sayarak ne kadar döndüğünü hesaplamalıdır.

İncremental enkoderlerde birde Quadrature denen bir terim vardır. Endüstriyel olarak üretilen bütün incremental enkoderler bu özelliğe sahiptir diyebiliriz. Quadrature enkoderlerden iki adet kare dalga sinyal çıkar. Bunlar A ve B sinyalleri olarak isimlendirilir. Bu sinyaller arasında 90 derece faz farkı vardır. Enkoderi okuyacak olan donanım bu iki kare dalgayı karşılaştırarak hangi tarafa döndüğünü kolaylıkla tespit edebilir. Quadrature enkoderlerdeki temel amaç budur.

Son olarak incremental enkoderlerde birde Z (veya INDEX) sinyali bulunurki bu çıkıştan A ve B sinyallerinden farklı olarak tur başına 1 puls üretilir. Puls’ın üretildiği nokta herzaman sabittir.  Dolayısıyla sabit referans noktası gereken yerlerde bu sinyalden faydalanılır.

Aşağıdaki resimde incremental enkoderden çıkan A, B ve Z sinyalleri gösterilmiştir.

incremental-enkoderden-cikan-a-b-z-sinyalleri

A ve B sinyalleri birbirinden 90derece kayık olduğunu görebilirsiniz.

İncremental enkoderleri okumak için yeni nesil işlemcilerde hazır donanımlar bulunur. Örneğin PIC18F4431′de QEI modülü bulunmakta. STM32′lerde ise timer donanımları ile yapılmakta. Bu tür donanımlarda yön tayini donanımsal olarak yapılmaktadır. Fakat mikrodenetleyici olarak Pic16F628 gibi düşük seviye bir mikroişlemci kullanılıyorsa yön tayini konusunda birkaç seçenek mevcuttur.

Eğer düşük çözünürlüğe sahip enkoderler kullanılıyorsa bu işlem kesmeyle veya yazılımlada yapılabilir fakat hem işlemciyi çok meşgul eder hemde de yüksek hızlarda puls kaçırmalar yaşanabilir. Bu yüzden yön tespitini donanımla yapmak işlem açısından büyük bir külfeti ortadan kaldırıyor.

Ben yön tespiti için basitçe bir D tipi flip-flop kullandım. Örnek bağlantı aşağıdaki gibidir

d-tipi-flip-flop-kullandim-ornek-baglant

Bu bağlantıya göre Flip-Flop’un Çıkışı dönüş yönüne göre 1 veya 0 olur. Bu sayede yön tayini yapabilir. Enkoderin A veya B sinyalinden herhangi biri ilede gelen puls’lar sayılır.

Açıkçası yön kısmını bu enteğreye hallettikten sonra geriye kalan kısım timer ile puls saymaktan başka birşey değil. Yön tayininin donanımsal olarak yapmamızın en büyük artısı işlem yükünü çok azaltması. Bu sayede herhangi bir timer donanımı barındıran mikrodenetleyici ile açı ölçümü yapılabilir. Yapılan işlemler hız gerektirmiyor.

Piyasadaki herhangi bir D tipi flip-flop burada iş görür. Ben 4013 kullandım ama isteyen başka enteğreleri kullanabilir.

Enkoderler hakkında bu kadar bilgi yeterli sanırım. Şimdi yaptığımız uygulamaya bakalım.

Yaptığım uygulamada Pic16F628 kullandım. Sistemden kısaca bahsetmek gerekirse Enkoderden gelen puls’ları 4013′den geçirip ne tarafa döndüğünü tespit ediyorum. Ayrıca Gelen puls’ları timer donanımına girip pulsları saydırıyorum. Yazılımla enkoderin kunumunu tespit açısını hesapladıktan sonra 7 Segment display de gösteriyorum.

Devre şemamız aşağıdaki gibidir.

pic16F628-cd4013-ccs-encoder-proteus-isis-display

Devreyi simülasyonda test etmek için motor tipi enkoderler den kullanmak gerekiyor. Yukarıdaki şemada kutu içerisine aldığım bölüm sadece simülasyon amaçlı kullanılıyor. Gerçekte bu devre kullanılmaz.

Yaptığım devrede Enkoderin Z yani referans sinyalini kullanmadım. Bunun yerine devreye bir adet buton ekledim. Bu buton sayesinde istenilen zamanda bütün değerler sıfırlanabilmektedir. Bu sayede istenilen nokta referans alınabilir.

Ayrıca sisteme bir adet menü ekledim. Menüden istenildiği taktirde enkoder çözünürlüğü değiştirilebilmektedir. Şimdilik ben menüye 100, 256, 360, 400, 512, 600, 720, 1000, 1024, 1800, 2048, 3000, 3600, 4000, 4096 değerlerini ekledim. Enkoder çözünürlüğü kaç puls ise menüden o değer seçilmelidir.

Menüye giriş işlemi için Sıfırlama butonuna yaklaşık 2 sn basmanız gerekmektedir. Menüye girdiği zaman girişte ufak bir yazı geçer. Sonra en son seçtiğiniz değer ekrana gelir. Burada yine butona basarak değerleri değiştirebilirsiniz. Seçtiğiniz değeri kaydetmek için yine aynı butona 2 sn civarı basmanız gerekir.

Şimdi açı ölçümünün yazılım tarafına bakalım. Yazılımı CCS C ile yazdım.

#include <16F628A.h>

#FUSES NOWDT                    //No Watch Dog Timer
#FUSES INTRC_IO                 //Internal RC Osc, no CLKOUT
#FUSES NOBROWNOUT               //No brownout reset
#FUSES NOLVP                    //No low voltage prgming, B3(PIC16) or B5(PIC18) used for I/O
#FUSES NOMCLR

#use delay(int=4000000)

#use fast_io(a)
#use fast_io(b)

#include "74595.c"

#define seg0 pin_b0
#define seg1 pin_b1
#define seg2 pin_b2
#define seg3 pin_b3

#define yon pin_b7
#define button pin_b4

const int digit[10]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F};
const int anim[22]={0x79,0x54,0x58,0x5C,0x5E,0x79,0x50,0x00,0x50,0x79,0x6D,0x5C,0x38,0x1C,0x78,0x10,0x5C,0x54,0x00,0x00,0x00,0x00};
signed int16 encoder_loc=0;
unsigned int16 counter=0,old_counter=0,fark=0;
unsigned int16 encoder_res=0;
unsigned int8 syc=0,btn_syc2=0,buffer=0;
unsigned int8 data[4];
unsigned int1 nokta=0,exit=0;
float angle=0;

void menu         (void);
void show_display (unsigned int16 value);
void clear_display(void);
void menu_animation(void);

//****************** Timer0 Kesmesi *****************************
#int_timer0 // Timer0 kesmesi
void Timer0_kesmesi () // Kesme fonksiyonu ismi
{ 
  switch(syc)
  {
    case 0:
      output_low(seg3);
      buffer=data[0];
      write_expanded_outputs(&buffer);
      output_high(seg0);
    break;
    case 1:
      output_low(seg0);
      if(nokta){bit_set(data[1],7);}else{bit_clear(data[1],7);}
      buffer=data[1];
      write_expanded_outputs(&buffer);
      output_high(seg1);
    break;
    case 2:
      output_low(seg1);
      buffer=data[2];
      write_expanded_outputs(&buffer);
      output_high(seg2);
    break;
    case 3:
      output_low(seg2);
      buffer=data[3];
      write_expanded_outputs(&buffer);
      output_high(seg3);
    break; 
  }
  syc++;if(syc>3)syc=0;
set_timer0(193);
clear_interrupt(int_timer0);       
}

void main()
{
   set_tris_a(0x00);
   set_tris_b(0xD0);
   output_a(0x00);
   output_b(0x00);

   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(193);                                    //2ms aralıklarla kesme oluşsun.
   set_timer1(0);
   enable_interrupts(INT_timer0);
   encoder_res=(((unsigned int16)read_eeprom(1)<<8) | read_eeprom(0));
   delay_ms(100);
   enable_interrupts(GLOBAL);                         //Kesmeler Açılıyor.
   if(encoder_res>4096)menu();                       
   nokta=1;                                
   show_display(0);
   while(TRUE)
   {
     counter=(get_timer1()%encoder_res);//Timer1 İn modu alınıyor. 

     if(counter!=old_counter)// Sayaçta Değişim varsa
     {
	    if(counter<old_counter){    
          fark=(encoder_res-old_counter)+counter;
        }else{
          fark=counter-old_counter;
        }
      if(input(yon))//Sağa Dönüyorsa
      { 
        encoder_loc+=fark;
        if(encoder_loc>=encoder_res)encoder_loc=encoder_loc-encoder_res;
      }
      else
      {
        encoder_loc-=fark;
        if(encoder_loc<0)encoder_loc=encoder_res-abs(encoder_loc);
      }   
      old_counter=counter;
      angle=(float)(encoder_loc*360.0)/encoder_res;
      show_display(angle*10);
     }  

     if(!input(button))
     {
       while(!input(button))
       {
         btn_syc2++;
          if(btn_syc2>100)
          {
            exit=1;
            break;
          }
         delay_ms(10);
       }
       btn_syc2=0;
       if(!exit)
       {
          encoder_loc=0;
          counter=0;old_counter=0;
          fark=0;
          set_timer1(0);
          angle=(float)(encoder_loc*360.0)/encoder_res;
          show_display(angle*10);
       }
     }

     if(exit)
     {
       exit=0;
       nokta=0;
       clear_display();
       menu();  
     }
   }
}

void menu(void){
  clear_display();
  menu_animation();
  unsigned int8 menu_syc=read_eeprom(2);if(menu_syc>15)menu_syc=0;
  unsigned int8 btn_syc=0;
  int1 exit_flag=0;
  while(TRUE)
  {
    switch(menu_syc)
    {
      case 0:
        encoder_res=100;
        show_display(encoder_res);
      break;
      case 1:
        encoder_res=256;
        show_display(encoder_res);
      break;
      case 3:
        encoder_res=360;
        show_display(encoder_res);
      break;
      case 4:
        encoder_res=400;
        show_display(encoder_res);
      break;
      case 5:
        encoder_res=512;
        show_display(encoder_res);
      break;
      case 6:
        encoder_res=600;
        show_display(encoder_res);
      break;
      case 7:
        encoder_res=720;
        show_display(encoder_res);
      break;
      case 8:
        encoder_res=1000;
        show_display(encoder_res);
      break;
      case 9:
        encoder_res=1024;
        show_display(encoder_res);
      break;
      case 10:
        encoder_res=1800;
        show_display(encoder_res);
      break;
      case 11:
        encoder_res=2048;
        show_display(encoder_res);
      break;
      case 12:
        encoder_res=3000;
        show_display(encoder_res);
      break;
      case 13:
        encoder_res=3600;
        show_display(encoder_res);
      break;
      case 14:
        encoder_res=4000;
        show_display(encoder_res);
      break;
      case 15:
        encoder_res=4096;
        show_display(encoder_res);
      break;
    }
    if(!input(button))
    {
      menu_syc++;if(menu_syc>15)menu_syc=0;
      while(!input(button))
      {
        btn_syc++;
        if(btn_syc>100)
        {
          exit_flag=1;
          break;
        }
        delay_ms(10);
      }
      btn_syc=0;
    }

    if(exit_flag)break;

  }
  write_eeprom(0,(encoder_res&0x00FF));
  write_eeprom(1,((encoder_res>>8)&0x00FF));
  write_eeprom(2,(menu_syc-1));
  data[0]=64;
  data[1]=64;
  data[2]=64;
  data[3]=64;
  delay_ms(500);
  while(!input(button)){}
  nokta=1;
  show_display(0);
}

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

void clear_display(void){
  data[0]=0;
  data[1]=0;
  data[2]=0;
  data[3]=0;
}

void menu_animation(void){
  int i=0;
  for(i=0;i<22;i++)
  {
    data[3]=data[2];
    data[2]=data[1];
    data[1]=data[0];
    data[0]=anim[i];
    delay_ms(150);
  }

}

Önemli olan main programı, Gerisi Teferruat. Bu yüzden sadece main programını anlatacağım. Şimdi donanım kısmında 4013 ile yön tespiti yaptık. Ayrıca enkoderden gelen puls’lar Timer1 in external clock girişine bağladık.(pic16F628 için RB6) Timer ayarlarınıda yaptıktan sonra Geriye kalan tek şey enkoderin konumunu hesaplamak.

Bunu yapabilmek için ilk başta enkoderin hareketi esnasında enkoderde kaç puls lık bir hareket olduğunu bulmak gerekiyor. Bunu yapmadan önce ilerideki işlemlerde bize kolaylık sağlaması açısından Timer1 değerinin enkoder çözünürlüğüne bölümünden kalanını buluyoruz.

counter=(get_timer1()%encoder_res);

Buradaki encoder_res parametresi bizim menüden seçtiğimiz değerdir. Eğer menüden 1024′ü seçdiysek bu işlem sonucunda counter değeri 0-1023 arası bir değer alır.

Bundan sonraki yapılan işlem sayaçta bir değişim varmı yokmu onu yoklamak.

if(counter!=old_counter)….

Sayaçta değişim yoksa enkoderde hareket yok demektir. Enkoderin konumunu hesaplamayada gerek yok bu yüzden bütün işlemler es geçilir.

Eğer sayaçta değişim var ise sonraki işlem ne kadarlık bir değişim var onu bulmak. Bu işlem için bir önceki timer değeri ile yeni timer değerini birbirinden çıkartıyoruz. Bu sayede fark hesaplanıyor.

if(counter<old_counter)
{
fark=(encoder_res-old_counter)+counter;
}else{
fark=counter-old_counter;
}

Bundan sonraki işlem enkoderin ne tarafa döndüğüne bakmak. Eğer sağa dönüyorsa Asıl konum sayacına fark kadar ekleme yaparız.

encoder_loc+=fark;
if(encoder_loc>=encoder_res)encoder_loc=encoder_loc-encoder_res;

Tabi bu sırada konum sayacının Enkoder çözünürlüğünü aşmamasına dikkat etmeliyiz. Yazılım bu sırada bunuda kontrol eder. Eğer sayaç değeri enkoder çözünürlüğünden büyük ise sayaç sıfırdan saymaya başlar.

Eğer sola dönüyorsa bu işlemin tam tersi yapılır. Yani konum sayacından fark kadar çıkarma yaparız. Eğer konum sayacı sıfırdan küçük olursa tekrar alabileceği max. değerden geriye saymaya başlar.

encoder_loc-=fark;
if(encoder_loc<0)encoder_loc=encoder_res-abs(encoder_loc);

Yapılan işlemler bundan ibaret diyebiliriz. Bundan sonrası enkoderin konumuna denk gelecek olan açıyı hesaplayıp ekrana yazmak. Açı hesaplama dediğim işlem orantıdan başka birşey değil.

Sistemi denemek için ben ufak bir kart hazırladım.

encoder-kullanimi-pic6f628-ile-aci-olcer-2014-02-20-21-39-00

Devredeki display’ler ortak katot’dur ve multiplex olarak sürülüyor. Displaylerin seğment bilgilerini vermek için 74HC595 yani port çoğullayıcı kullandım.

Dediğim gibi sistemin en büyük artılarından biri kesme kullanılmaması ve hata yapmaması olduğunu söyleyebilirim. Daha önce kesme kullanarak bir iki test yaptım ama fark ettimki enkoder hızlandıkça sistem puls kaçırmaya başlıyor. Bu sistemde sayım işlemi timer ile yapıldığı için kaçırma gibi problemler olmuyor.

Encoder Kullanımı ve Açı ölçer projesine ait CCS C Proteus isis simülasyon ve PCB dosyalar: encoder-kullanimi-aci-olcumu-ve-ccs-c-pic16f628-ornek-uygulama.RAR

Sağlıcakla kalın..

  • hasanali

    Selamlar üstadım,

    Devreyi inceledim gayet güzel çalışıyor. Ancak devrenin üzerinde deneme yapmak üzere yeniden compile ettiğimde display kısmı çalışmıyor. ben devreyi 999 dan döndürmeyi denemeye çalışıyordum. Acaba bu mümkün müdür ve neden yeniden compile edince devre çalışmadı sizce?
    İyi çalışmalar.

  • Harun Reşit Cansız

    yararlı paylaşım için teşekkürler. hocam ben 4096 puls encoderi yön bilgisine ihtiyacım olmayan bi çalışmamda tek kanal bağlayıp buton mantığıyla saydırıyorum. yükselen bekle alçalan bekle mantığı ve arada encoder tekeri çapıyla alakalı bölme işlemi yaptırıyorum. 16f877 de encoderin dönüş hızı çok artarsa misal saniyede 2 tur atsa 4096 x2 = 8192 pps hızında pic gelen sinyalleri kaçırır mı ?

  • Metin U.

    Merhabalar açısal konum ölçmek için kullanılan mutlak çözümleyicinin 8-bit üzerinden çalışma prensibinin çizerek açıklaması yapılabilir mi?