Kategori arşivi: Referans

I/O Config. on PIC24

PIC24 işlemcilerinde her bir port için aşağıdaki register’lar tanımlanmış:

PORTx, LATx, TRISx, ODCx, ANSELx, CNPUx, CNPDx, CNENx

Bunlardan, PORT, LAT, TRIS zaten tüm PIC’lerde olan register’lar.. Bunlar hakkında bir şey dememiz gerekmiyor.
UART pinlerinden TX olacak olanın TRIS’ini 0 yapıyorum. (Tam hatırlamıyorum ama ya PIC18 ya da uğraştığım başka bir mcu’da uart pinlerini TX de olsa RX de olsa giriş olarak konfigüre ediyordun, bu öyle değil!)

Bir pin analog giriş olarak kullanılacaksa ilgili TRIS biti 1 olmalı, ANSEL biti de 1 olmalı (bu zaten reset sonrası varsayılan durumdur).
ANSELx.y = 0 yaptığımız zaman x portunun y. bitini digital I/O ya da uart gibi bir peripheral olarak kullanabilir oluruz.
Bu arada, ben kullanmadığım işlemci portlarını donanımı tasarlarken herhangi bir nete bağlamıyorum (en iyi ihtimal bir header konnektöre çıkar bırakırım) ve bu portları ÇIKIŞ yapıp LAT değerine de 0 yazarım.

Peripheral Remapping
Pin sayısı az, çevresel fonksiyonları çok işlemcilerde pinleri fonksiyonlara paylaştırmak için kesinlikle bir çeşit pin-peripheral multiplexing’e ihtiyaç var. Silabs işlemcilerdeki priority crossbar’a benzer iş yapan bir özellik PIC24’lerde de var ve Peripheral Pin Select fonksiyonu olarak adlandırılıyor.

Bu yapı iki kısımdan oluşuyor: Input Mapping, Output Mapping.
Eğer şu PIC24EP işlemcisini bir-iki projede daha kullanacak olursam port konfigürasyonu için kendim bir program hazırlayacağım. (Harmony Configurator diye bir şey zaten var ama bildiğim kadarıyla 24EP desteklemiyor, hem ben kendi kodlama tarzıma ve kullanımıma uygun bir şey yapacağım) Bunu burada paylaşırım. Çip konfigürasyonunu hızlandırmamıza yardımcı olur.

Input Mapping
Bu, peripheral temelli bir kontroldür. Yani her peripheral’in kendisine ait bir register alanı (7 bit) vardır. O alana, peripheral’i hangi pine route edeceğimizi söyleyen değeri yazarız. Bu durumda her bir giriş pini için de bir adres değeri tanımlanmıştır. Bu, datasheet’te tablo olarak verilmiştir.
Böyle olmasının mantığı açık: Bir giriş birden çok kaynağa bağlanabilir. Sonuçta hedefin (peripheral) bir tane olması yeterli.
Bazı remappable pinler sadece giriş olabilirken bazıları hem çıkış hem de giriş olabilirler. İsimlendirmeden bunu ayırt edersiniz. RPIx ya da RPy gibi..

Örnek olarak, ben UART1 RX fonksiyonunu RF0/RPI96 pinine atayacaksam gider tablodan RPI96 adres değerine bakarım: 0x60 gördüm.
Sonra da bu değeri U1RX’in adres tanım register’ına yazarım: RPINR18 = 0x0060

Özetle, giriş mapping’de kullanacağım pinin adres değerini bulurum, bunu kullanacağım fonksiyonun register’ına yazarım…

Output Mapping
Bu, giriş mantığının tam tersi ile çalışır. Yani her bir pinin kendisine ait bir register alanı vardır. O alana, pini hangi fonksiyona route edeceğimizi söyleyen değeri yazarız. Her bir fonksiyonun bir remap değeri vardır. Bu, tablo olarak verilmiştir. Bunun mantığı bence daha da açık: Bir çıkış ancak tek bir fonksiyona atanabilir. Hedefn (pin) bir tane olması gereklidir.

Benim UART1 ‘in TX pinini RF1/RP97’ye atayacağım. Tabloya bakıyorum: U1TX için peripheral değeri = 1
Tamam.. Sonra gidiyorum, RP97’yi ayarlayan register’ı buluyorum: RPOR7
RPOR7bits.RP97R = 1;

Konfigürasyon Kilidi
Bazı register’lara erişimin ancak özel bir kilit açma işlemi sonrası münkün olduğunu bilirsiniz (mesela on-chip EEPROM). Benzer şey bu peripheral mapping’de de var. Yukarıdaki register’lara erişebilmeniz için önce
OSCCONbits.IOLOCK = 0;
yapmak zorundayız. Öte yandan IOLOCK bitine yazabilmek için önce özel bir kilit açma yazması yapmak gerek.

XC16’da bunu yapan iki builtin fonksiyonu var. OSCCONL ve OSCCONH için built-in yazma:

__builtin_write_OSCCONL (OSCCON | 0x40) ; // IOLOCK, OSCCON’daki 6. bit.

İşler bitince kilidi yine devreye almak için:

__builtin_write_OSCCONL (OSCCON & 0xBF);

Bu, IOLOCK bitinin PIC24’lerin hepsinde olmadığını okudum. Emin olmak için kullandığınız çipin datasheet’indeki OSCCON register açıklamasına bakın..

Titreşim Analizi ile Hata Öngörüsü

İvme ölçerden aldığım ivme değişimi değerlerini bir mekanik sistemin “titreşimi” olarak kabul ediyorum. Çalışmamız çok basit bir iki kabule dayanıyor:
** İvme ölçerin ölçtüğü ivme sinyali, cihazın üzerine tam olarak sabitlendiği sistemin titreşimi ile bire bir bir sinyal üretir.
** Bu sinyalin bir (ya da iki) eksendeki ölçümlerinin belli bir frekans aralığındaki değerleri bana belli bir mekanik durum hakkında bilgi verir. Çünkü toplam titreşim sinyali sisteme etki eden hareket bileşenlerinin süperpozisyonudur. Dönen bir motoru sallanan bir salıncağa bindirirsem iki ivmelenmenin kompozisyonunu okurum.
** İvme ölçerin ölçtüğü genlik potansiyel olarak “istenmeyen” durumun şiddetini belirtirken frekans spektrumu durumun kimliği hakkında bilgi içeriri. Belli bir frekans aralığındaki bileşenler belli bir değerin üstüne çıktığında o mekanik sistemde “normal olmayan” ya da “bozulmuş” bir hareket olduğu sonucunu çıkarabilirim.
**Bu frekans aralığı ve hata için kabul edeceğim eşik her sistem için farklı olacaktır, o yüzden bunları ayarlanabilir yapmam gerekebilir.

Bu durumda, elimde iki şey olması gerekiyor:
1) Belli bir hızda sürekli olarak okuyabildiğim bir ivme ölçer…
2) Ondan aldığım sinyallerin frekans analizini yapabilecek bir hesaplayıcı

İşe PC üzerinde başlıyorum. Önce, bir sinyalin frekans bileşenlerini birbirlerine göre nasıl kıyaslarım sorusunu ele alıyorum. Bu, elbette Fourier analizi yapmam demek.

Elimdeki bir sinyal dizisinin FFT’sini almak için bir program hazırladım. Ama önce bu işlerin nasıl olduğuyla ilgili birkaç deneme yapmak istiyorum.

Deneme amaçlı olarak 3 bileşenli bir sinyal üretiyorum:

fx[i]:= DC + (A * sin(2*pi*i*f1/k) + (B * sin(2*pi*i*f2/k)) );

Buradaki k, benim örnekleme frekansım oluyor. Zaman domeninde sinyali çizdiğimde ekranın kaç saniyelik örnekleme içerdiğini bu oran belirliyor (çünkü ben sample’ları indeksleriyle sayabiliyorum, bağımsız değişkenim zaman değil).

Bu fonksiyonu 4000 noktalı olarak üretiyorum. (Gerçekte 16 bitlik tam sayılarla ve bundan daha uzun sinyallerle uğraşacağım).

f1 ve f2, sinyalin içerdiği iki sinüsoidalin frekansını gösteriyor.. DC, A ve B bileşenlerin genliklerini belirtiyor. DC bir offset her zaman karşılaşacağım bir durum çünkü sistemim yeryüzüne yakın bir noktada çalıştığı sürece mutlaka 1g’lik bir yerçekimi ivmesine maruz olacak..

Örnek değerler:

k = 400;
DC = 1;
A = 10;
B = 4;
f1 = 25;
f2 = 50;

Bu değerler için sinyal şöyle bir şey oluyor:

sine_synth_signal

Bu sinyalin 512 noktalı FFT’sini alırsak ve dikdörtgen (uniform) ağırlıklandırma penceresi uygularsak şöyle bir genlik spektrumu görüyoruz:

sine_synth_freq_spec

  1. bileşen DC component
  2. bileşen 25 Hz ‘e karşılık gelen sinüs.
  3. bileşen de 50 Hz olan sinüs..

Benim örnekleme frekansım 400Hz olduğu için, sinyalin spektrum genişliği 200Hz oluyor.
BW = fs / 2;

Spektrum çözünürlüğü de;
H = BW / (spect_size / 2)
oluyor.

Bant genişliğim 200Hz, spektrum çözünürlüğüm de 200/256 oluyor. Yani spektrum penceremdeki her bir çubuk 0,781Hz’lik bir harmoniği gösteriyor.

Yukarıdaki örnekte 200’ü tam sayıya bölen frekanslar seçmiştim. Şimdi öyle olmayan bileşenler içeren bir sinyal üretelim:

sine_synth_signal_2

sine_synth_freq_spec_2

Artık harmonikler, gerçekte bir tane saf sinüsten oluşsalar da burada birden çok komponent olarak gözüküyorlar. 
Sonrası FFT spektrum boyu, sinyal boyu ve pencereleme fonksiyonu ile ilgili bir tercih olacak.
Bizim için genliklerin doğru gösterilmesi frekansların doğru gösteriminden daha önemlidir. Şu anda dikdörtgen pencere fena gözükmüyor diyebiliriz.

Genel olarak ihtiyacımız olan şeyler şunlar;
Bant genişliği sınırlanmış sürekli bir sensör verisi kaydı.
Hatadan kaynaklanan frekans bileşenini biliyor olmamız.
Bu bileşenin değerine göre hata ya da sağlıklı durumuna karar verebilmemiz için güvenilir bir eşik değeri

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;
    ////////////////////////////////////////////////////////////////////////////////