Kategori arşivi: Referans

Resource Bitmap

Bir uygulamanın içinde yer alacak görselleri uygulamanın içine gömmenin eski ve tarafımdan sıkça başvurulan bir yoludur.
TBitmap nesnesinin LoadFromResourceName() diye bir alanı var demem kullanım kısmını açıklamaya yeter sanırım.
Oluşturma kısmında ise önce bir resource script dosyası oluşturuyorsun:
Bir text editörü ile

bitmap_adi BITMAP "D:\klasor\bitmap.bmp"

şeklinde listeni oluşturuyorsun.
Bu script’i derlemek için BRCC32 konsol programını kullanıyorsun. Komut satırından şunu çalıştırıyorsun:

BRCC32 <....\benim_resource.rc

Artık rc dosyasının yanında bir de .RES dosyası var. Bize lazım olan bu.
Ana formun pas dosyasına bir compiler directive eklemek yeterli olacak. Delphi’nin form için verdiği $R direktifinin altına bizim direktifi ekliyoruz:

{$R *.dfm}
{$R benim_resource.res}

Artık uygulama içindeki herhangi bir bitmap’i runtime yeşillendirebiliriz. Cümle içinde kullanımına bir örnek:

BitBtn1.Glyph.LoadFromResourceName(HInstance, 'off');

TNotifyEvent

Data üreten bir nesnem var: Diyelim ki buna haberleşme ile dışarıdan veri geliyor. Bu nesne üzerinde gerekli parsing işlerini yapıyorum, integrity check yapıyorum ve veriyi bir buffer’a yüklüyorum. Bu nesne bir formun parçası ya da ayrı bir ünitede tanımlı bir sınıf.
Öte yandan uygulamamda bu veriyi başka yerlerde, başka başka şekillerde kullanmam gerek. Mesela bir trend grafiğine çizim, bir faceplate nesnesini güncelleme ya da bu veriyi giriş kabul eden bir kontrol çevriminde….
Veriyi alma ve protokol dahilinde haberleşme kontrolünü yapma işini veriyi kullanma işinden basit biçimde ayırmak istiyorsam bunu şöyle yapıyorum:

Veriyle ilk uğraştığım yerdeki sınıfın private alanında bir TNotifyEvent değişken tanımlıyorum:


private
   FPrivateEvent : TNotifyEvent;

Sonra, nesnenin published alanında onMyEvent diye bir bir property tanımlıyorum:

published
   property onMyEvent: TNotifyEvent read FPrivateEvent write FPrivateEvent;

Dış dünyayı bu olayı kullanarak uyarmak istediğim yerde (mesela gelen veriyi tarayan parser’ın sorgularının başarılı döndüğü durumda) şunu yapıyorum:

   if Assigned(onMyEvent) then onMyEvent(self);

Bu olay tetiklendiğinde işlem yapmak istediğim yerde, öncelikle yapacağım işlemi içeren procedure’ü tanımlıyorum:

   private
     procedure ProcessMyEvent(Sender: TObject);

Birden çok kaynak nesne söz konusuysa buradaki sender parametresini ayırt etmede kullanabilirim. Öte yandan, burada veriyi taşıma gereksinimi duymadığım, sadece olayın tetiklendiğini belirtmek istediğim durumdaki event tetiklemesini anlattım. Eğer datayı taşımam gerekiyorsa kendi event handler’ımı oluşturabilirim. Bu başka bir hikaye…

Eğer handler ayrı bir formda tanımlıysa o formun başlangıcında (onActivate iyi bir yer, eğer ana form üzerinde tanımlama yapıyorsak creation order canımızı sıkmamış olur) elimizdeki event handler procedure’ünü (ProcessMyEvent) veri kaynağı olan nesnenin event’ine atamamız gerek:

   mySourceObject.onMyEvent:= userform.ProcessMyEvent;

Ben bu yöntemi kendi cihazlarımla haberleştiğim PC programlarını geliştirirken kullanıyorum. İşin haberleşme arayüzünü bir form (ya da görsel bile olması gerekmeyen bir nesne) üzerinde bir kere hallettikten sonra bunu hiç değiştirmeden farklı arayüzler ya da uygulamalarda aynı haberleşmeyi kullanabiliyorum.

drive.itemstatus durumları

drive thread’inin bir task’i sürücüyle belli bir iş yaptıktan sonra busy bayrağını 0 yapmadan hemen önce sürücünün (kapının) o anki durumunu drive.itemstatus bayrağında gösterir. Ana thread bu bayrağı sorgulayarak yoluna devam eder.

  • 0 : DRIVE_UNINITIALIZED Sürücü başlatılmamış ya da hata durumu.
  • 1 : DRIVE_UNDEFINED Sürücü yeni başlamış ya da durum bilgisi kaybedildi
  • 2 : DRIVE_OPENING Açılma yönünde hareket ediliyor.
  • 3 : DRIVE_CLOSING Kapanma yönünde hareket ediliyor.
  • 4 : DRIVE_OPEN_OBS Açılma esnasında hareket durdu (engel ya da komutla)
  • 5 : DRIVE_CLOSE_OBS Kapanma esnasında hareket durdu (engel ya da komutla)
  • 6 : DRIVE_OPENED Kapı Açık
  • 7 : DRIVE_CLOSED Kapı Kapalı

[10] : Sabit Hızla KAPATMA

Bu komut, Vemg hızı ile kapının kapatılmasını sağlar. Kapı boyu + aralık toleransı kadar bir mesafe sonra engel algılanmasa bile motor duracaktır.
Bu işlemde hata üretilmez. İşlem bittikten sonra sürücü durumu DRIVE_CLOSED olacak ve busy = 0 yapılacaktır.

      // SABIT HIZLA KAPATMA:  Vemg hizi ile kapi kapatma komutu
      //////////////////////////////////////////////////////////////////////////
        case 10:
            drive.command = 0;

            rampset = Vemg;             // set degeri Vemg olacak!
            current_threshold = Tmc;    // durma zorlanma degeri: kapanma limit algılama yükü
                                        // burada engel algılama yapmıyorum..
            PID.overloaded = 0;         // zorlanma algilama sayacını da
            OCcounter = 0;              // sifirla

            /* Position Breakpoint Ayari:
             *  Kapi boyu + olcum toleransi kadar bir mesafe hareket hakki var.
             *  Tetiklenen encodertick flag hareketi sonlandıracak
             *  Bu kadar mesafe boyunca donus olmussa kapı sonuna gelinmiş olmalı
             *  motor duracak, hata da verilebilirdi..
             */
            ClearEncoderCounter();
            bppos.ul = dt.ul + APERTURE_TOLERANCE;
            bppos.l = (signed long *) ( -1 * bppos.l);
            SetREVBreakpoint();

            // sabit rampali kalkis gorevini baslat:
            dir = 1;                    // yon: REV
            drive.subindex = 11;
            drive.status = 1100;        // sabit rampa adimi alt gorevi
        break;


        // Hareket sonu mesafesine erişim ya da zorlanma limiti bekleme adımı
        case 11:

            // ZORLANMA ALGILANDI:
            if (PID.overloaded)
            {
                PID.nocontrol = 1;      // PID kontrol kapalı olmalı!

                // Demek zorlanma oldu.. Bayrağı sıfırla...
                PID.overloaded = 0;
                OCcounter = 0;
                drive.status = 12;
            }

            // kapı boyu kadar hareket edildiyse motor durmalı:
            if (encodertick)
            {
                // TODO: burada bir hata kodu uretebilirim ama
                // onu handle edecek mekanizma da olmali...
                // bunun yerine "bu kadar hareket ettiysem kapı kapanmıştır artık"
                // deyip bitiriyorum..
                drive.status = 1000;
                drive.subindex = 12;
            }


            // bu esnada kullanıcı durdurma komutu verdiyse:
            if (drive.command == 1000)
            {
                drive.command = 0;
                drive.status = 1000;
                drive.subindex = 12;
            }
        break;


        // kapı sonu, hareket bitti:
        case 12:
            // 90ms. daha kapiyi kapama yonunde zorla ve bitir:
            if (PID.controlcounter > 8)
            {
                PID.controlcounter = 0;
                speedref = 0;
                MDC = 0;
                MOTOR_BRK_L;
                drive.itemstatus = DRIVE_CLOSED;           // kapi KAPALI
                drive.status = 0;
                drive.busy = 0;
                // position sayaçlarini sifirla:
                ClearEncoderCounter();
                position.ul = 0;
            }
        break;



[1100]: Sabit Rampa ile Hızlanma

Bu alt-göreve atlandığında motor hızı 0’dan rampset değerine sabit bir ivme ile artırılır.
Alt-görev çağrıldığında dir bayrağının durumuna göre hareketin yönü belirlenir. dir = 1 ise REV dir = 0 ise FWD yönünde hareket başlatılacak.
Zorlanma algılanırsa rutinden hemen çıkılır. Zorlanma sonucu alt görevden dönülürse overloaded bayrağı 1 olarak dönülür.
busy ve itemstatus bayrakları bu görevlerden etkilenmez.

// SABIT RAMPALI HIZLANMA: (FWD/REV)
 ////////////////////////////////////////////////////////////////////////////
        case 1100:
          speedref = 50;
          MDC = 50;
          PID.controlcounter = 0;
          PID.restart = 1;
          PID.nocontrol = 0;     // kalkış PID kontrol ile, bu önemli!
          SetDir();                     // sürücü yönünü belirle..
          _PTEN = 1;                    // PWM'i başlat!
          drive.status++;
        break;


        // her 40ms'de bir hizi biraz artirmak:
        case 1101:
            if (PID.controlcounter &gt; 3)
            {
                speedref += 10;
                drive.status++;
                PID.controlcounter = 0;
            }
            // bu esnada aynı zamanda  motor zorlanıyor mu diye de bakıyoruz:
            if (PID.overloaded)
            {
                PID.nocontrol = 1;      // zorlanma durumunda PID devre dışı
                speedref = 0;
                drive.status = drive.subindex;       // ve hemen dönüyoruz..
            }
        break;


        // referans degere ulastik mi sorgulamak:
        case 1102:
            if (speedref &lt; rampset) drive.status = 1101;
            else
            {
                speedref = rampset;
                drive.status = drive.subindex;
            }
        break;
    ////////////////////////////////////////////////////////////////////////////////

[1000] : Sabit Frenleme İle Durma

Bu alt-göreve atlandığında motor frenleme ile durdurulur.
busy flag değiştirilmiyor.
itemstatus flag değiştirilmiyor.

   /// SABIT FRENLEME ILE DURMA :  900ms sürer..
   /////////////////////////////////////////////////////////////////////////////////
        case 1000:
            // kullanici durmak istiyorsa, position breakpoint kesmesi gerekmiyor
            // diye dusunurum:
            _PCLEQIEN = 0;
            _PCHEQIEN = 0;

            // motoru bosa at:
            speedref = 0;
            MDC = 0;
            PID.nocontrol = 1;
            MOTOR_BRK_L;
            PID.controlcounter = 0;
            drive.status++;
        break;

        case 1001:
            if (PID.controlcounter > 19)     // 200ms bosta bekle
            {
                // bosta suresi gectikten sonra biraz fren yap:
                speedref = 1000;
                drive.status++;
                PID.controlcounter = 0;
            }
        break;

        case 1002:
            if (PID.controlcounter > 9)  // 100ms bekle
            {
                speedref = 2000;        // freni artir
                drive.status++;
                PID.controlcounter = 0;
            }
        break;

        case 1003:
            if (PID.controlcounter > 9)    // 100ms bekle
            {
                speedref = 3000;            // freni artir
                drive.status++;
                PID.controlcounter = 0;
            }
         break;

       case 1004:
           if (PID.controlcounter > 9)      // 100ms bekle
           {
               speedref = 0;
               drive.status++;
               PID.controlcounter = 0;
           }
       break;

        case 1005:
            if (PID.controlcounter > 19)     // 200ms bekle
            {
                speedref = 4000;            // freni artir
                drive.status++;
                PID.controlcounter = 0;
            }
        break;

       case 1006:
           if (PID.controlcounter > 19)      // 200ms bekle
           {
               speedref = 0;                // ve islemi sonlandir...
               PID.controlcounter = 0;
               drive.status = drive.subindex;
           }
       break;
//////////////////////////////////////////////////////////////////////////////////

Motor Kontrolü için PID

PID kontrol veri tipi:

typedef struct
{
    unsigned int pv;                    // olculen deger (feedback)
    signed int ev;                      // hata
    signed int ev_pre;                  // bir onceki hata
    signed int ev_sum;                  // hata integrali
    signed long int P;                  // hesaplanan PID komponentleri
    signed long int I;
    signed long int D;
    signed long int out;                // hesaplanan cikis
    unsigned int controlcounter;
    unsigned overloaded: 1;             // loadlevel belirlenen esigi asti ise bu bayrak 1 olur.
    unsigned restart : 1;               // pid ara degerleri baslatilir, akümülatörler sifirlanir
    unsigned nocontrol: 1;              // MDC = speedref, no pid control.. kullanici sifirlamali
    unsigned controlphase: 2;           // siradaki speedcontrol cevriminde ne is yapilacak?
} PIDParType;

pv: Process Value
motor encoder’ından örneklenen dönüş hızı değerinin PID çıkış veri tipine göre ölçeklenmiş halidir.
ev: Error Value
Referans değeri ile geçerli değerin farkı. pv çıkış gücü değeri cinsinden ölçekli olduğu için sadece çıkarma işlemi yeterlidir: ev = speedref – pv.
ev_pre:
Previous Error Value, Bir önceki hesaplama çevriminde saklanan hata değeri (türev hesabında kullanılır).
ev_sum:
Sum of Error Values, Hata değerlerinin toplamı (integral hesabında kullanılır)
P,I,D:
Hesaplanan P,I,D bileşenleri
out:
Hesaplanan çıkış. Basitçe, P,I ve D bileşenlerinin toplamından oluşur ancak bu değer fiziksel çıkışın ötesinde olabilir.
controlcounter:
Her SpeedControl( ) çevriminde +1 ilerleyen serbest sayaç. Motor kontrol zamanlamaları sırasında kullanılabilir, PID çevrimiyle eşzamanlı zamanlama sağlar.
overloaded:
Aşırı yüklenme bayrağı. Sürücü thread’i içinde motor akımı izleme ile ya da PID çıkışı üzerinden tetikleniyor olabilir. Kontrol onu sadece set eder. Haricen sıfırlanmalıdır.
restart:
Kontrol biti: Bu bit 1 yapıldığında tüm PID ara değerleri sıfırlanır. İşlem yapılınca bit sıfırlanır.
nocontrol:
Kontrol biti: Bu bit 1 yapıldığında PID kontrol devre dışı olur. speedref = MDC
controlphase
PID scheduling control flag. SpeedControl( ) fonksiyonunun her çağrılmasında hangi PID işlem parçalarının yapılacağını belirler.

Kontrol Çevrimi

Motor Kontrolü için PID hesaplama blok şeması

Motor Kontrolü için PID hesaplama blok şeması

 
// motor hiz kontrolü yapmak için 10ms'de bir çagrilmali:
void SpeedControl(void)
{
    LongData w;

    PID.controlcounter++;

    if (PID.nocontrol)
    {
        MDC = speedref;
        position.i[0] = POS1CNTL;    // position update
        position.i[1] = POS1HLD;
        return;
    }

// controlphase scheduling'e gore pv guncellemeleri:
////////////////////////////////////////////////////////////////////////////////////
    switch (PID.controlphase)
    {
        case 0:
            position.i[0] = POS1CNTL;    // position update
            position.i[1] = POS1HLD;

            // hiz bilgisini guncelle (40ms'de bir)
            rpm  = VEL1CNT;        
            w.ul = abs(rpm);
            w.ul *= 5000;
            w.ul /= pm;
            PID.pv = w.ul;

            PID.ev = speedref - PID.pv;

            PID.controlphase = 1;
        break;


        case 1:
            // integral icin, errorvalue_sum'i güncelle:
            PID.ev_sum += PID.ev;
            // ev_sum limitleme (anti windup)
            if (PID.ev_sum > 3000) PID.ev_sum = 3000;
            else if (PID.ev_sum < -4000) PID.ev_sum = -4000;
            // I komponentini hesapla:
            PID.I = (signed long*)(KiN * PID.ev_sum);

            PID.controlphase = 2;
        break;


        case 2:
            // hiz bilgisini guncelle (40ms'de bir)
            rpm  = VEL1CNT;
            w.ul = abs(rpm);
            w.ul *= 5000;
            w.ul /= pm;
            PID.pv = w.ul;
            // hata degerini guncelle:
            PID.ev_pre = PID.ev;        // simdiki deger onceki deger olarak kaydedilir
            PID.ev = speedref - PID.pv;

            PID.controlphase = 3;
        break;


        case 3:
            // D komponentini hesapla:
            w.i[0] = PID.ev - PID.ev_pre;
            if (w.i[0] > 2000) w.i[0] = 2000;
            else if (w.i[0] < -2000) w.i[0] = -2000;

            PID.D = (signed long*)(KdN * w.i[0]);

            PID.controlphase = 0;
        break;
    }
//////////////////////////////////////////////////////////////////////////////////////////

    if (PID.restart)
    {
        PID.ev = speedref;
        PID.ev_pre = 0;
        PID.ev_sum = 0;
        PID.I = speedref / 3;
        PID.D = speedref / 3;
        PID.controlphase = 0;
        PID.restart = 0;
    }

//// P bileseninin hesabi: P = Kp * ev
///////////////////////////////////////////////////////////////////////////////
    PID.P = (signed long*) (KpN * PID.ev);
///////////////////////////////////////////////////////////////////////////////
    

//// cikisin birlestirilmesi:
//////////////////////////////////////////////////////////////////////////////
   PID.out = (signed long*)(PID.P + PID.I + PID.D);
   PID.out = PID.out / 10;
//////////////////////////////////////////////////////////////////////////////

//// output update: (with limiting)
//////////////////////////////////////////////////////////////////////////////
   if (PID.out < -1000)
   {
        MDC = 2000;
        MOTOR_BRK_L;
        return;
   }
    
   if (PID.out < 0)
   {
        MDC = 500;
        MOTOR_BRK_L;
        return;
   }

   if (PID.out > 5000)
   {
       MDC = 4900;
       SetDir();
       return;
   }

   MDC = PID.out;
   SetDir();
/////////////////////////////////////////////////////////////////////////////////

}