Kategori arşivi: Referans

Function Pointers in C

Fonksiyon işaretçisi, bir fonksiyonu bildirim referansını kullanarak çağırabilmemizi sağlar. Yani, bir fonksiyonu parametrik olarak çalıştırma imkanımız olur.

void (* func_name) ( ) = function_name;

Bunun bendeki en sık kullanma amacı Fonksiyon Listesi hazırlamak:


 int funcA(void);
 int funcB(void);
 int funcC(void);
 int funcD(void);
 int funcE(void);
 int funcF(void);

 int (* functionList[]) () = 
 {
  funcA, funcB, funcC, funcD, funcE, funcF 
 };

 // bu bildirimin cümle içinde kullanımı şöyle:
 ret_val = functionList[list_index]();

Her ne kadar bir kaynak dosyası içinde kullanılacak bir fonksiyon için deklarasyon yazmak zorunda olmasak da, fonksiyon listesi hazırlarken deklarasyon yazmalıyız. Deklarasyon bize yürütme kısmından önce o fonksiyona dair işaretçiyi kullanma şansı verir. Bu şekilde liste bildirimi yaparken liste elemanlarını sabit olarak kullanabiliriz.

UDP Load Data

Bu soket görevi, belirtilen konumdan itibaren, belirtilen miktarda veriyi W5500 üstündeki ilgili soketin (socket-0) TX buffer’ına yazar.


 

W5500’deki bir soketin TX buffer’ına yazma işlemleri şöyle olmalı:

  1. Buffer’da yeterince yer olup olmadığını Sn_TX_FSR okuması yapıp öğrenmek.
  2. Sn_TX_RD okuması yapıp, yazma işlemine başlayacağımız konumu öğrenmek.
  3. Veriyi buradan itibaren TX buffer’a aktarmak.
  4. Sn_TX_WR içeriğini yazdığımız byte sayısı kadar artırmak.

W5500_TX_bufferSEND komutu verildiğinde W5500, Sn_TX_RD‘den Sn_TX_WR‘ye doğru TX buffer’ındaki verileri sırayla gönderir. İşlem tamamlandığında Sn_TX_RD = Sn_TX_WR olacaktır. O yüzden, yeni bir veri yüklemesi yaptıktan sonra, Sn_TX_WR içeriğini Sn_TX_RD’den gidecek veri miktarı kadar öteye alıyorum.

Bu işaretçilerin tümü 16 bitlik işaretsiz sayılardır. W5500 0xFFFF -> 0x0000 başa sarmasını önemsemiyor. Bu durumda biz de önemsemiyoruz. Paketimiz yüklenirken bu başa sarma olsa bile bu gönderme işlemini etkilemeyecek. Ki bu başa sarma da zaten eninde sonunda olacak..

Ben ayrıca, 1. adımda söz ettiğim Free Size Register’ını da okumuyorum. Çünkü benim uygulamalarda hem yollanan veri boyları küçük, hem de uygulamalarım veri yollama işlemini senkronize olarak kullanıyorlar.

Burada not etmek istediğim son şey, veri YÜKLEME işlemi ile GÖNDER komutunun farklı şeyler olduğuna dikkat etmeniz. Birden çok kez veri yüklemesi yapıp, hepsini tek seferde yollamak istemeniz çok muhtemel.. İşlemci üstünde integral bir çıkış paketi oluşturma zorunluluğunu ortadan kaldırmak için böyle yaptım. Yani, gidecek verinin tam gideceği paket yapısı ile bir yerde üretilmiş olmasına gerek yok. İstediğiniz datayı bir yerden, sonrakini bir başka yerden W5500’e yükleyip, asıl paketi TX Buffer üzerinde oluşturabilirsiniz. Toplamda 16kB TX buffer’ımız var ne de olsa!

W5500 Interrupt Logic

W5500’den durum sinyali almanın bir yolu çipin /EC_INT pinini okumaktır. Benim uygulama periyodik SPI okuması yerine bu pinin durumunu izleyerek soketlere dair durum güncellemelerinden haberdar olur.
Her kesme kaynağının bir de mask biti var. Mask biti 1 yapılmadıkça o kaynak bir sonraki kademeye kesme iletmez.
W5500’den PC’ye bağlantı veren /EC_INT girişinin etkin olmasını sağlayan iki kaynak var:
* Çip kesmeleri : IR register’ındaki bir bitin aktif olması ile oluşur.
Benim uygulamada çip kesmelerini kullanmıyorum.
* Soket kesmeleri: SIR register’ındaki bir bitin aktif olması ile oluşur. 8 soket kesmesi register’ına (Sn_IR) ait kesme kaynaklarından birinin aktif olması SIR üzerindeki o sokete ait bitin aktif olmasını sağlar.

w5500_interrupt_logic

Benim uygulama, bir soket açıldığında o sokete ait TIMEOUT, RECEIVE ve DISCON kesmelerini otomatik olarak yetkilendirir. Ayrıca, açılan soketin Sn_IMR maske bitini de etkinleştirir. Kullanıcının bu kesmelerin devreye alınması üzerinde bir denetimi yoktur.
Yani, EC_INT pininin aktif olması bize açık soketlerden birinde zaman aşımı, veri alma ya da bağlantının sonlandırıldığı bilgilerini verir.

W5500’ün kesme pininin izlenmesi işini W5500_SPI_Thread( ) halleder. Bu thread aynı zamanda, kesme algılar algılamaz SIR register içeriğini de okur.

W5500_SPI_Thread( ) kesme durumu algıladığında işlemesi gereken bir komut yoksa derhal SIR okuma işlemine atlar.
Bu işlem esnasında; hEC.status = 200 olur.
İşlem tamamlandığında; hEC.interrupt_flag = 255 yapılarak üst thread uyarılır.
hEC.socket_interrupt içeriğine de SIR değeri yüklenmiş olunur.

W5500_SPI_Thread( )

Bu thread W5500 Ethernet Controller çipini bir üst düzey (soket işlemlerinin yapıldığı ECThread() katmanı) koddan yalıtır.
SPI okuma / yazma işlemleri, kesme yanıtlanması ve çipin donanımsal resetlenmesi görevleri bu katmanda halledilir.

w5500_SPI_Thread

Sürekli olarak /EC_INT pininin durumunu okur.
hEC.command komutunu bekler.

EC_INT = 0 durumu algılanırsa;
SIR (Socket Interrupt) register içeriğini alanına kopyalar,
hEC.socket_interrupt <– SIR (Socket Interrupt) okuması yapar.
İşlem esnasında: hEC.status = 200
hEC.interrupt_flag = 255 yapar
hEC.interrupt içeriği sıfırlanmadan bir daha EC_INT pini okuması yapılmaz.

hEC.command = 1000 yapılmışsa:
hEC’de belirtilen parametrelerle SPI okuması yapılır.
İşlem esnasında : hEC.status = 10

hEC.command = 2000 yapılmışsa:
hEC’de belirtilen parametrelerle SPI yazması yapılır.
İşlem esnasında : hEC.status = 20;

hEC.command = 3000 yapılmışsa:
W5500 resetlenir.
İşlem esnasında : hEC.status = 30;

Üstteki ECThread() ile iletişim ECHANDLE değişkeni üzerinden sağlanır:

typedef struct
{
   unsigned int 	command;		// yazma veya okuma komutu (>0 ise yazma yapma)
   WORD                 offset_address;		// erişilecek register adresi
   unsigned char        control_phase;		// blok ve okuma/yazma komutu
   unsigned char        *rampos;		// RAM tarafı hedef/kaynak adresi
   unsigned char        bytecount;		// erişilecek byte sayısı
   unsigned char        status;                 // EC durumu / işlemi gösteren bayrak
   unsigned char        interrupt_flag;         // kesme algılama durum göstergesi (yazılımla sıfırlanmalı)
   unsigned char        socket_interrupt;       // Kesme kaynağının hangi soket olduğunu gösteren SIR reg. içeriği
} ECHANDLE;
  ECHANDLE    hEC;             // W5500_SPI_Thread 'in çalışmasını ayarlayan değişken

Controller Configuration

Şu veri tipi, cihaz sıfırlandıktan sonra common register block’taki temel W5500 ayarlarının yapılması için gerekli bilgileri saklar:

typedef struct
{
    unsigned char       gateway_IP[4];      
    unsigned char       subnet_mask[4];
    unsigned char       MAC[6];
    unsigned char       device_IP[4];
    WORD                retry_period;   // Little-endian olarak yeniden deneme periyot süresi
} ECCONFIG;

w5500.c içindeki cEC değişkeni reset sonrası, çipin kullanacağı konfigürasyon değerlerini içeriyor olmalıdır:

ECCONFIG    cEC;       // ec configuration: (reset sonrası yüklenir)

NOT: retry_time hariç tüm değişkenler BIG ENDIAN olarak saklanıyor olmalı!

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..