Yazar arşivleri: selimpehlivan

selimpehlivan hakkında

Sen anladın onu...

EFM8BB1 LCK ile Geliştirme

LCK muhtmelen “low-cost kit” sözcüklerinin kısaltması.
LCK, 6 dolar gibi bir paraya satılan küçük bir geliştirme kartı. Bizimki gibi ülkelerde 6 birim gavur parası bile yeri geldiğinde üzerinde düşünülmesi gereken bir bütçe olabilir elbette. Ama normal ölçütlerde bu, bu kartın adının hakkını vermesini sağlayan bir fiyat:
En başta, bu paranın 1 dolarını EFM8BB10F8G QSOP24 mcu’ya , 5 dolarını da üstündeki debugger’a verdiğimizi düşünelim. Fena değil.
Sonra, Simplicity programını hemen kullanmaya başlamak ve lisanslı bir Keil C51 derleyicisine erişmek faydalarını da hesaba kattığımızda epey iyi oluyor.
Ben bu board’u, radyo haberleşmeleriyle uğraşırken,bir seri port dönüştürücüsü kullanmadan pc’den giden/gelen radyo paketlerini izlemek için kullandım.
Board’un üstündeki debugger’ı VCOM port olarak kullanmak için iki jumper kısa devre etmemiz gerekiyor:

Artık işlemcinin seri portu debugger’a bağlı. Fakat bilgisayar bunu bir comport olarak görmüyor. Silicon Labs’ın 8 bit işlemciler sayfasından Toolstick Terminal programını indirmeniz gerek. Bu çok basit program zaten kullanmayı planladığınız seri terminal programının yerine geçecek.
PC bağlantısı bana çoğu zaman lazım olduğu üzere, sadece bir aptal terminal olarak değil, bazı işleri otomatize etmek ve veriyle gerçek zamanlı olarak oynamak için gerekiyorsa zaten bildiğimiz yoldan devam etmemiz gerek : Seri dönüştürüc + kendi pc uygulamamız.

Ben, radyo haberleşmesinde, aslında uç nokta olacak (yani bir arayüzü olmayan) cihazların testlerini yapmak için bunu kullanıyorum. Hex veri yollamak için BB1’e bir Send_Byte( ) fonksiyonu yazmak “fazlasıyla” yeterli:

void Send_Byte (char c)
{
  SCON0_TI = 0;
  SBUF0 = c;
  csum += c;
  while(SCON0_TI == 0) ;
}

Fakat, madem terminalden verilere bakacağız desimal sayılar görelim hatta açıklayıcı string’ler yazdıralım falan diyorsak o zaman printf( ) ‘i kullanmalıyız. 8051 çeşitlilik konusunda yeryüzündeki eklembacaklılarla yarışacak kadar türe sahip olduğu için, <stdio.h> include etmenin bu işi çözmesini beklemek yaşadığımız dünyayı hafife almak olur.
printf( ) bir port yazması için putchar fonksiyonunu çağırır ve bu fonksiyon
<program kurulum yeri> \developer\toolchains\keil-8051\9.60\LIB dizininde bir C dosyası olarak yer alıyor. Oraya gidip bunu modifiye edebiliriz. Bakarsanız, orada saçma sapan bir yazdırma kodu olduğunu göreceksiniz.
Veya daha kökten bir çözüm olarak kendiniz, bu işleri yaptığınız C dosyasının içinde aynı adda bir fonksiyon yazarsınız:

char putchar (char c)

Bunu, benim yukarıdaki Send_Byte’ın aynısı yapsanız çalışacaktır. Keil’in kendi dosyalarını değiştirmek zorunda da olmazsınız. printf sizin putchar’ı çağıracaktır (object scope).

Gönderme işlemini belli bir zamanlama (scheduling) içinde firmware’in yapmasını istiyorsanız sprintf kullanıp buffer’ı haberleşme thread’inin yollamasına bırakmak da kullanılabilir.

Burada not etmek istediğim bir konu var: Sadece bir printf() çağrısı yapmak için (örneğin bir int değerini desimal olarak göstermek için) programımıza binen ek kod yaklaşık 1k civarındadır).
Bu, güzel bir örnek. Çünkü EFM8BB1 gibi bir mcu’ya 1k ‘lık bir programla hayret edilecek kadar karmaşık işler yaptırabilirsiniz aslında. Yani bu, standart kütüphane fonksiyonları kullanarak low-code programcılık yapmak istemenin neye malolduğunu göstermesi açısından güzel bir örnek.

Yazının ileride ekleyeceğim bölümlerinde bunu bir PC’ye bağlamadan beslemenin yöntemlerinden söz edeceğim.

Radyo Modülü Bağlamak

nRF24L01+ modülünü çalıştırmak için gerekli sinyalleri şu portlara atadım:
* IRQ : P00
* SCK : P06
* MISO : P07
* MOSI : P10
* CSN : P11
* CE : P12

Slave modüller için nRF24L01+ radyosunun çalışması üstüne yapılacak denemeleri bu board üzerinde yapmak, geliştirme aşamasında kullanışlı oldu. nRF24’ün konfigürasyon modelini şu şekilde tanımladım:

nRF24L01+ radyo modülüne EFM8BB1 ile erişmek için gerekli fonksiyonlar:

EFM8 ile I2C Master

Çalışma Frekansı Ayarı

Master mode’da, SCL ‘yi master ürettiği için çalışma frekansını biz belirliyoruz. EFM8SB1’de I2C modülü iki farklı zamanlama kaynağına ihtiyaç duyar: Biri zaman aşımı denetimi için diğeri clock frekansını belirlemek için (baud-rate generation gibi düşünün).
Bizim için ikincisi önemli. Bunun için, diğer modüllerde olduğundan farklı olarak birden çok seçeneğimiz var. TMR0 pek çok uygulamada genel amaçlı sistem saati ya da harici sayaç, TMR1 uart baud rate generator veya harici sayaç olarak kullanılabildiği için ben TMR2 kullanıyorum. Ayrıca, I2C çalışmasında gerekli taşma hızları çok yüksek olduğu için, 16 bitlik bir sayaca da ihtiyacımız yok. Değerli kaynakları boşa harcamamak için split mode çalışabilen bir timer kullanmak daha mantıklı.
Seçtiğimiz saat kaynağının kesmesini başka işlerde kullanmaya devam edebiliriz. I2C modülü arka planda taşma bayrağına bakarak I2C işlemlerini zamanlayabilir.
Kullanmak istediğimiz SCL frekansının 3 katı hızıyla tick üreten bir sayaç ayarlamamız gerek:

Split mode yaptığın bir sayacın alt yarısı ile üst yarısını kullanmak arasında küçük bir fark var: Sayacın açma/kapama kontrolü yalnızca üst yarıya kumanda eder. Alt yarı sürekli çalışır. Eğer üst yarıyı başka bir iş için kullanacaksak bu I2C için daha uygun bir paylaşım olur.

Program Tasarımı

Aşağıdaki zaman diyagramına bakın. Bu, bir sensörden veri okumak için yapılması gerekenleri gösteriyor:

I2C, ne yazık ki kendi başına iş görme yeteneği düşük bir haberleşme modülüdür. Yukarıdaki gibi, belli bir register’dan 2 byte veri okumasından ibaret bir işin SPI ile ne kadar kolay olacağını ve durumlar tanımlamak gerekmeden tek satırda bitebileceğini düşünün.
I2C’de işe yarar bir konuşma (transaction) yapmak için birden çok durum çalıştırmak gerekir. EFM8SB1’de bunları donanıma yaptırmak için kullandığımız kumanda bitleri tek bir register’da yer alıyor.

// I2C kumanda bitleri:
sbit SMB_SI = SMB0CN0^0;
sbit SMB_ACK = SMB0CN0^1;
sbit SMB_ARBLOST = SMB0CN0^2;
sbit SMB_STO = SMB0CN0^4;
sbit SMB_STA = SMB0CN0^5;

I2C konuşması fazla işlemci meşguliyeti yaratan bir iş olduğu için bu tür çevresel birimlere sürücü yazarken yaklaşımımız ana uygulamanın çok-görevlilik durumuna göre olmalı. Eğer I2C erişimi önceden bilinen zamanlarda yapılmayacaksa ve başka işlerle çakışması olasılığı varsa çok-görevli bir kodlama daha uygun olur. Çok görevlilik, I2C işlerini tüm durumları kapsayan bir kesme fonksiyonu içinde çalıştırmak şeklinde olabilir. Veya ayrı bir thread içinde bu işlerin kotarıldığı task’ler şeklinde bir durum makinesi yazılabilir. Birincisi için Silabs’ın örnek i2c kodlarına bakabilirsiniz. İkincisi benim genellikle tercih ettiğim yöntemdir. Daha büyük işlemcilerde genellikle i2c thread’i içinde erişimleri yazarım.

Benim bu yazıda paylaşacağım yaklaşım yukarıdakilerden biraz daha farklı. Programın asıl işinin zaten bu i2c modülüne erişim olduğu durumlarda bu yöntem daha efektif olacaktır. Burada esas olan, işlemcinin i2c erişimi yaparken başka işinin olmayacak olması. Ek olarak, i2c okuması uygulamanın zamanlamasını kesin olarak bildiği anlarda başlar ve biter. Örneğin, zamanının çoğunu uyku modunda geçiren ve uyanıp, i2c üzerinden bir sensörle haberleşen bir uygulama için bu yöntem biçilmiş kaftandır.

Genel Amaçlı Fonksiyonlar

Aşağıda paylaştığım i2c fonksiyonlarını doğru sıra ile çağırarak, herhangi bir slave aygıt için ihtiyaç duyulan sürücü fonksiyonları gerçekleştirilebilir. Bu fonksiyonlar blocking olarak çalışırlar. Yani, düz bir sıra ile verilen i2c okuma/yazma işlemini yaparlar.

// START durumu üretir ve ardından verilen slave adresini yazdırır (read ya da write)
bool I2C_Start(unsigned char slave_address)
{
  SMB0CN0 = 0x20;   // START durumu başlat!
  while (SMB_SI == 0)  ;

  if (SMB_ARBLOST) return(false);
  SMB0DAT = slave_address;
  SMB0CN0 = 0;
  return(true);
  // fonksiyon, işlemin bitmesini beklemeden hemen döner..
}

// ilk erişimde, yazma adreslemesi sonrasında erişilecek
// çip adresini (/yollanacak komutu) gönderir:
bool I2C_Register_Set(unsigned char reg_addr)
{
   while (SMB_SI == 0) ;  // yazma adreslemesinin bitmesini bekle

   if (SMB_ACK)   // yazma adreslemesine ACK aldıksa..
   {
     SMB0DAT = reg_addr;
     SMB_SI = 0;
     return(true);
   }
   else
   {
     SMB0CN0 = 0x10;   // STOP durumu üret
     return(false);
   }

}


// Start'tan farkı, devam etmek için önceki işlemin sonuçlanmasını bekler
// ve slave adres yüklemesi sonrası da slave'den ACK bekler
bool I2C_ReStart(unsigned char slave_address)
{
  while (SMB_SI == 0) ;  // önceki yazma işleminin bitmesini bekle

  if (SMB_ACK == 0)
  {
    SMB0CN0 = 0x10;  // STOP durumu üret
    return(false);
  }

  // önceki yazmaya ACK almışsak devam edelim:
  SMB_STA = 1;
  SMB_SI = 0;     // Repeated-Start üret
  while (SMB_SI == 0) ;

  if (SMB_ARBLOST)
  {
     SMB0CN0 = 0x10;   // STOP durumu üret
     return(false);
  }

  // repeated start sonrası, verilen slave adresini yolla:
  SMB0DAT = slave_address;
  SMB0CN0 = 0;
  while (SMB_SI == 0) ;

  if (SMB_ACK)  // re-start adreslemesine ACK aldık mı?
  {
      // slave'e adres yazması sonrası modülü hold durumda bırakıp dön:
     return(true);
  }
  else
  {
    SMB0CN0 = 0x10;
    return(false);
  }
}

// bu fonksiyon i2c denetimini bir önceki adımdan hold durumda devralmıştır
// (ilk okuma için read address yazması sonrası,
// sonraki okumalar için master ACK üretimi sonrası)
// ack = 1 olursa daha veri okunacak demektir.
// slave'e ACK üretilir ve i2c makinesi hold durumda iken fonksiyon döner.
// ack = 0 olursa stop durumu üretilir ve durum makinesi de boşa çıkar.
unsigned char I2C_Byte_Read(bool ack)
{
  unsigned char x;

  SMB_SI = 0;   // state machine'i sal..
  while( SMB_SI == 0)

  if (ack) SMB_ACK = 1; else SMB_ACK = 0;

  x = SMB0DAT;  // gelen veriyi al.

  if (!ack)
  {
    SMB0CN0 = 0x10;
    while (SMB_SI == 0) ;
    SMB_STO = 0;
  }

  return(x);
}


// kendisinden önceki yazma (pointer set / data yazma) devam ederken çağrılır
// geri döndüğünde de yükleme yapılan yazma sürüyordur.
bool I2C_Byte_Write(unsigned char b)
{
  while (SMB_SI == 0) ;  // önceki yazma işleminin bitmesini bekle

  if (SMB_ACK == 0)
  {
    SMB0CN0 = 0x10;  // STOP durumu üret
    return(false);
  }

  SMB0DAT = x;
  SMB_SI = 0;
  return(true);
}

void I2C_Stop(void)
{

  while (SMB_SI == 0) ;  // önceki işlemin bitmesini bekle
  SMB0CN0 = 0x10;
  SMB_SI = 0;
  while(SMB_SI == 0) ;
  SMB0CN0 = 0;
}

Synapse ile Seri Port Arayüzü

Lazarus/Freepascal ile seri port kullanımına basit bir örnek olarak bu notları paylaşıyorum.
Aşağıdaki notları Lazarus ile yaptığım bir veri toplama ünitesi bağlantı programından özetledim.
Synapse kütüphanesi hakkında gerekli bilgilere şu bağlantıdan ulaşılabilir:
https://wiki.freepascal.org/Synapse

Synapse, kurulması gereken bir kütüphane değil. Kütüphane klasörünü bir yere olduğu gibi kopyalayıp klasörün içindeki laz_synapse.lpk kütüphane projesi dosyasını açmış ve derlemiş olmamız yeterli. (Package > Open Package File (.lpk) seçimi ile yapılıyor)

Bu kütüphaneyi kullanacak bir uygulama projesi başlattığında,
Project Inspector’de sol üstteki ADD butonuna ve sonra New Requirement’e tıkla.
(Project inspector görünür değilse Project > Project Inspector seçimi yap, bunun tuş kısayolu yok!)
Gelen listede laz_synapse seçilebilir olmalı, (çünkü derlemiştik) onu seç.
Project Inspector’de artık projenin Required Packages dalının altında laz_synapse da gözüküyor olmalı.
Artık, projenin seri port kullanması gereken ünitesinin uses kısmında synaser unit’ini ekleyebilirsin.
Bu ünite görünür bir form da içeriyorsa (ayarlar için içerse iyi olur) o formun private alanında programın ana mantığının bilmeye ihtiyaç duyacağı olayları tanımla:

 FonDeviceConnect : TNotifyEvent; 
 FonSpecificPacketArrived : TNotifyEvent; 

Olaylar, belli bir paketin gelmesi ya da belli bir işlemin tamamlanması gibi durumlara ait olabilirler. Kaç tane olacağı uygulamanın karmaşıklığına kalmış.

Ardından, gelen veriyi almak için bir dinleyici nesne yaz. Bu, doğal olarak bir TThread ardılı olacak:

   TCommListenThread = class(TThread)
    private
      fCom: TBlockSerial;
      fcmdTerminate: boolean;
      fPacketTimeout: integer;
      fErrorDesc: String;
      frxdata: AnsiString;
      rxstate: TRXState;
      rxstring: AnsiString;
      Frxsize: integer;

      FRXEvent : TNotifyEvent;
      FErrEvent: TNotifyEvent;

    public
      constructor Create(Acom: TBlockSerial); overload;
      procedure Start_Listening(poll_period: integer);
      procedure Stop_Listening;
      property onRX : TNotifyEvent read FRXEvent write FRXEvent;
      property onErr: TNotifyEvent read FErrEvent write FErrEvent;
      property ReceivedString: AnsiString read frxdata;
      property rxsize: integer read Frxsize;
    protected
       procedure Execute; override;
  end;


Overload ettiğimiz Create constructor’unda kullanıcıdan TBlockSerial tipli bir seri port erişim nesnesi vermesini istiyoruz. Bu, ünite içinde tanımlı bir değişken olmalı ve bu dinleme nesnesinden önce yaratılmış olmalı.
Thread’in execute’u içinde state’ler tanımlayarak veri gelirken anında bazı işler yapabiliriz ama seri portta buna çok gerek yok. O yüzden execute döngüsü şöyle olsa şimdilik yeterli:

procedure TCommListenThread.Execute;
begin
  while not(fcmdTerminate) do
  begin
    case rxstate of
    //////////////////////////////////////////////////////////////////////////
    RX_IDLE:
    begin

    end;
    //////////////////////////////////////////////////////////////////////////
    //////////////////////////////////////////////////////////////////////////
    RX_LISTEN:
    begin
      // gelen veriyi bekle:
      rxstring:= Fcom.RecvPacket(fPacketTimeout);
      if Fcom.LastError = ErrTimeout then continue;
      // hata döndüyse
      if Fcom.LastError <> sOK then
      begin
        rxstate:= RX_ERROR;
      end else
      begin
        // rxdata string'ini kullanıcıyla property olarak paylaştığımız
        // nesne alanına kopyala:
        Frxsize:= Length(rxstring);
        frxdata:= Copy(rxstring,0, Frxsize);
        if Assigned(onRX) then onRX(self);
      end;
    end;
    //////////////////////////////////////////////////////////////////////////
    //////////////////////////////////////////////////////////////////////////
    RX_ERROR:
    begin
      FErrorDesc:= Fcom.LastErrorDesc;
      Fcom.Flush;
      if Assigned(onErr) then onErr(self);
      rxstate:= RX_IDLE;
      self.Suspended:= true;
    end;
    //////////////////////////////////////////////////////////////////////////
    end;
  end;
 end;   

rxstring, içine com dosyasından veri gelmesini beklediğimiz temel değişken. Veri geldiğinde bunu frxdata ‘ya kopyalayıp dinleyiciyi serbest bırakıyoruz.
frxdata da ReceivedString property’si ile okunabilen bir değişken.
Böylece, onRX olayı ile tetiklenen bir fonksiyon ReceivedString’de gelen veriyi hazır bulacak.

Bir veri alındığında, dinleyici thread onRX olayını tetikleyecek. Bu olayı karşılayan bir procedure olmalı.
Bu procedure’ü yukarıda bahsettiğim, formun private alanında tanımlı bir fonksiyona bağla. Bu bağlama işini de
haberleşmeyi başlatmak (/ayarlamak) için kullanılan bir kullanıcı buton tıklamasının içine yaz. Burada nesneler de başlatılıyor olmalı:

  com:= TBlockSerial.Create;
  data_processor:= TCommListenThread.Create(com);
  data_processor.onRX:= @fcomm.Process_RXData;
  data_processor.onRX:= @commform.Process_RXData;
  data_processor.onErr:= @commform.Process_CommError;   

Process_RXData, seri porta bir veri geldiğinde, her ne gelirse gelsin çalışacak fonksiyondur.
Gelen veriyi data_processor thread’inin ReceivedString property’sinden okuyup rxdata dizisine (ünite değişkeni) yazarak işe başla:

  len:= Length(data_processor.ReceivedString);
  SetLength(rxdata, len);
  for i:=1 to len do
  begin
    x:= Byte(data_processor.ReceivedString[i]);
    rxdata[i-1]:= x;
  end;   

Buradan sonrası daha uygulamaya has kısım.
Paket tümleşikliğini (integrity) burada kontrol edip hata varsa işi sonlandırabilirsin.
Paket iyi gözüküyorsa beklediğin paketlerden hangisi olduğuna burada bakıp bir yönlendirme mantığı çalıştırabilirsin.
Başta yazmış olduğum
FonSpecificPacketArrived gibi olayları bu ayrımı yaptıktan sonra çağırabilirsin.
Bağlantı işleminin başında, bu olayların çağıracağı ana program procedure’lerini de bunlara bağlamış olmalısın (farklı ünitelerde çalışan farklı procedure’ler).

Sizin görüşünüz nedir?

Merhaba..
Uzun zamandır blog yazıyorum. Bu sitede 13 sene öncesinde yazılmış yazı var. Geriye bakınca içerik ve görsellik açısından pek yol kat edemediğimi görüyorum. Aslında epeydir bu siteyi güzelleştirmek gibi bir düşüncem vardı ama hem çok vaktim yok hem de nereden başlayacağımı bilemiyorum.
Bu siteyi biraz daha zenginleştirmek istiyorum. Sizce nereden başlamak lazım, neler yapmak daha iyi olur. Düşüncelerinizi benimle paylaşırsanız sevinirim.

Aşağıda, basit bir anket var. İsterseniz oradaki soruları yanıtlayın ya da doğrudan sol alttaki görüşünüzü yazın butonuna basıp bir şeyler yazın.

Teşekkür ederim.

EFM8BB1 IDLE Mode

Küçük işlemcilerle yaptığımız uygulamaların azımsanmayacak bir kısmı şu tarife uyar: Bunlar “belli bir olay“a tepki vermek için “bekleyen” programlardır.

Örneğin bir butona basıldığında bir işlem yapan bir program düşünelim. Program, butona basılmadığı zaman sadece butona basılmasını bekliyor (aslında belli zamanlarda yaptığı başka işler olması bu hikayenin teorisini çok değiştirmiyor).

İşte bu belli olayı beklediğimiz uzun müddetler boyunca işlemciyi boşa alarak akım tüketimini azaltabiliriz. Beklediğimiz olay için bir kesme kurmamız yeterlidir. EFM8 işlemcilerdeki IDLE mode, bu senaryo için uygundur.
– Idle mode’da iken işlemciyi istediğimiz saat hızında bekletebiliriz ve istediğimiz modülleri açık tutabiliriz.
-Idle mode’dan çıkmak için mevcut kesmelerin herhangi birini ya da bir kaçını kullanabiliriz.
– Çıkış bir interrupt fonksiyonuna atlama kadar hızlı olacağı için olaya tepkimiz hızlı olabilir.
Yani, Idle mode kullanımı basit ve sağladığı tasarruf bizim tercihlerimize bağlı bir çalışma ayarıdır. Bununla beraber, işlemci aslında kapanmadığı için bu en yüksek düzeyde güç tasarrufu sağlayan mod değildir.

Bir uygulama geliştirmek çalışan bir kod elde etmekten çok fazlasıdır. Bir uygulama tasarlamak temelde, seçimler yapmak demektir. Bu seçimler gereksinimlerinize en uygun seçimler olurlarsa başarılı olursunuz.
İşte bu yazının konusu olan Idle mode, sıklıkla ve zamanlamasını tam bilemediğiniz anlarda görev başına geçmesi gereken bir programınız varsa doğru bir seçim olabilir.

Tipik bir kullanımda kod
– Beklerken gerekli olmayan birimleri kapatır,
– Makul bir çalışma frekansına yavaşlar,
– IDLE mode’dan çıkış kaynaklarını ayarlar,
– Ve işlemciyi idle mode’a sokar..


IDLE mode’a giriş:

   TMOD = 0x31;
   TH0 = 0;
   CKCON0 = 0x02;  // tmr0 prescaler SYSCLK/48 olsun
   CLKSEL = 0x40;  // sistem saatini 1,53MHz'e indir..
   TCON = 0x11;    // TMR0 çali$sin, EX0 kenarda tetiklemeli
   IE = 0x83;      // = 0x83 TMR0 ve EX0 kesmeleri devrede
   PCON0 = 0x01;   // IDLE=1
   PCON0 =  PCON0;
   _nop_();

Yukarıdaki kod parçası işlemciyi EX0 ve TMR0 kesmeleri ile normal çalışmaya geri dönmek üzere boşa çıkarır.
İşlemcinin kötü bir şans eseri IDLE=1 komutu işlenirken gelecek bir kesme yüzünden hiçbir zaman IDLE durumundan çıkamayacak olması ihtimaline karşılık, bu komutun hemen ardından bir byte’tan büyük opcode’lu bir komut işletilmelidir. PCON0=PCON0 dummy komutu bu yüzdendir.
İşlemcinin normal çalışma moduna dönünce, kendisini uyandıran kesme kodunu işledikten sonra _nop_() ile devam edeceğini de not edelim. Yani uyanma kodu, buradan sonra yer almalı (muhtemelen yapılacak ilk iş çalışma hızını arttırmak olacaktır).
Bu şekilde her şeyi tek düzeyli düz bir while(1) süper döngüsü içinde halledebiliriz.
Son bir not olarak da EFM8BB1 için IDLE mode çalışma akımı vereyim. 1,53MHz için işlemci akımı 550 uA ‘dir. ( -G sıcaklık sınıfındaki çipler için )

DPS310

Infineon firmasının duyarlılığı yüksek ve düşük güç tüketimli basınç sensörü DPS310’u birkaç sene önce bir reklamda görmüştüm. Orada bu malzemenin giyilebilir teknolojiler için ideal olduğu vurgulanıyordu.
Ardından bir süre önce, bugünlerin popüler konusu iç ortamda konum bulma (indoor navigation) ile ilgili bir proje için çalışırken aklıma bu malzeme geldi ve bunun demo board‘undan sipariş verdim.
Board çiklet kutusu gibi bir kutuda geldi. İçinden de Infineon’un standart hale getirdiği bir form faktöründe üretilmiş (ki sonra bunun host tarafı için bir board da sipariş ettim), prototip çalışması yapması kolay bir kart çıktı. Aslında bu board’un herhangi bir özelliği yok. Tek işlevi montajı kolay olmayabilecek olan sensörü üzerinde taşımak.

İvme ölçerlerde gördüğümüz hem SPI hem I2C ile kullanılabilen senkron seri port bunda da var. Ben mümkün oldukça SPI seçen biri olduğum için bu sensörde de SPI bağlantısını kullanıyorum.
Bu sensörü çalıştırmak için elimdeki PIC32MM USB Curiosity dev. board’u kullandım.
Geliştirme kartının üstündeki USB ile PC bağlantısı kurup I2C üzerinden de bir LCD ekran bağlayıp sensör ölçümlerini izlemeyi planladım.

Söylemeye gerek yok, hassas basınç ölçümü düşey konumun belirlenmesi için kullanılabilir (benim bu sensörü ilgi alanıma almamın ilk nedeni aslında böyle bir kullanım değildi, bundan ileride söz edeceğim).
Bu sensörlerde aradığım meziyet mutlak doğruluktan çok (atmosfer basıncının o anki tam değerini bize söyleyecek bir barometre istemiyoruz) ölçüm çözünürlüğünün yüksek olması ve bağıl duyarlılık. Bu sayede 2-3 santimetrelik yükseklik değişimlerini yanılgısız ayırt edebiliyoruz.

Sensörün düşük güç tüketimli olması ve çalışma modu için farklı seçeneklerimiz olması da elbette pille çalışacak bir cihazda olmazsa olmazlarımızdan ikisi.

Bu sensörde beğenmediğim tek şey düzeltme katsayılarını kullanarak ölçüm değerini kullanıcının hesaplamak zorunda olması. (Düzeltme işine dökümantasyonlarda kaibrasyon demişler, düzeltme/kompanzasyon sanki daha uygun bir tabir). 8 bitlik bir işlemci ile bunu arabirimlemeyi düşünüyorsanız makineyi epey yoracaksınız demektir. Diğer taraftan deneme yanılmalarla, düzeltme uygulamadan da anlamlı sayısal sonuçlar elde edebilirsiniz (kompanzasyon işi sıcaklığa göre de yapıldığı için bunu tavsiye etmem).

Arayüz

Bu çipe SPI üzerinden erişiyorum. Çipte 3 telli SPI modu da var, bu modu etkinleştirdiğimizde SDO çıkışı INT çıkışına dönüşüyor. Ben bunu kullanmıyorum. 4-wire SPI çalışması için mode ’10’ olarak ayarladım. Yani mikrodenetleyicinin SPI ayarlarında;
CKP = 1  // clock polarity = 1 (Boşta iken 1)
CKE = 0  // MOSI 1->0 geçişinde güncellenir.
SMP =1 // MISO 0->1 geçişinde güncellenir.
Prototip kurulumumda sensor board’unu yaklaşık 15cm’lik jumper kablolarıyla geliştirme kartına bağlamıştım. Bu şekilde 1MHz SPI hızında sorunsuz haberleşme yapabiliyorum. Bu iş için yapılmış bir board’da bu hız çok daha fazla olabilir. Datasheet 10MHz SPI hızında çalışılabilir diyor.

SCLK’ın boşta durumunun 1 olması I2C ile uyumlu olmanın bir zorunluluğu. Çipin açılıştan hemen sonraki varsayılan modu I2C. Ancak CS pinini bir kere 0’a çekip SPI ‘ı etkinleştirince, arayüz sonraki yeniden başlatmaya dek SPI modunda kalır. Ayrıca, 3/4 wire seçimi için de config register’ında bir ayar biti var. Oraya hiçbir şey yazmazsanız 4wire kullanmış oluyorsunuz.

Her konuşmanın ilk byte’ı adrestir. Her adresin ilk biti de (MSB) bu erişimin yazma mı yoksa okuma mı olduğunu belirtir. Yazma yalnızca belirtilen adrese 1 byte olarak yapılabilirken okuma ardışık olarak çok byte devam ettirilebilir. Çipten okuma yapmak bize şu durumlarda lazım:
** Katsayılar 18 byte;
** Ölçüm sonuçları 3’er byte;
** Sensör durumunu öğrenmek için MEAS_CFG’i okumak (1 byte)

Sensörün Başlatılması

Çipin, enerjilenmesi sonrası 40ms kadar bir başlatma süresi var. Başlatma sonrasında alet otomatik olarak ölçüme başlamıyor. Zaten ilk işimiz (eğer daha önceden işlemci üstünde saklamadıysanız) sensörün düzeltme katsayılarını okumak olmalı.

Ben, başlatma kodunda öncelikle MEAS_CFG register’ını okuyup COEF_RDY ve SENSOR_RDY flag’lerinin set edilmiş olup olmadıklarına bakıyorum. Eğer bu bitlerin her ikisi de 1 değilse Init fonksiyonumuz işleme devam etmeyip FALSE dönmeli. Çünkü ana uygulama henüz ölçüm yapamayacağını bilmeli.
Eğer çip çalışmaya hazırsa, ilk işimiz PROD_ID register’ını okumak. Bu register’da DPS310’un ürün kimliği ve revizyon numarası yazılı. Beklenen değer 0x10 olmalıdır.

Her şey normal gözüküyorsa ikinci işimiz kalibrasyon katsayıları denen diziyi okumak olacak. Burada da can sıkıcı bir durum var. Parametreleri burst mode olarak 18 byte’lık bir dizi içine okuyorum. Ancak buradaki bazı katsayılar 24 bit, bazıları ise 12 bitlik işaretli (2’nin tümleyeni) tamsayılardır. Bunları açabileceğimiz uygun değişken boyları 32 bit ve 16 bitlik işaretli sayılar olacağı için bir dönüştürme işlemi yapmamız gerekiyor. Buna örnek datasheet’te verilmiş. Kısaca anlatmak gerekirse, açılacak katsayı kaç byte’lıksa en yüksek anlamlı bitine bakıyorsun, o bit eğer 1 ise sayı – işaretli olduğu için senin değişkenini sayının kendisinden o sayının bit genişliğinin alabileceği değerden çıkarıyorsun. Yani, 12 bitlik 2’s complement bir sayıdan 16 bitlik negatif bir sayı elde etmek için;
signed short c = (okunan sayı) – 4096;
Hedef değişken 15 bit (1 bit de işaret) olduğu için rollover olmadan sayının negatifini buluyoruz. c0 parametresinin üretilmesini aşağıda görebilirsiniz:

    // c0:
    w = (unsigned short) c[0] << 4;
    x = c[1] >> 4;
    dps310_info.c0 = w | x;
    // 2's comp. signed dönüşümü yap:   
    if (w & 0x0800)  dps310_info.c0 = dps310_info.c0  - 4096;

Datasheet Sayfa 37’de buradaki değişkenlerin, 18 byte’lık bir stream içindeki ardışık konumları verilmiş. Ne yazık ki padding olmadığı için dizinin bazı elemanları iki farklı parametrenin nibble’larını taşıyor olacaklar. Burada anlatmanın artık geyiğe gireceği bit kaydırma işlemleri ile zaman harcamak zorundasınız.

Sensörün Ölçmesi

Tıpkı nem ölçümünde olduğu gibi, yalnızca basınç ölçmek diye bir şey yok. Kompanzasyon için, sıcaklığı da hassas biçimde, tam bu çipin üstünde ölçmeniz gerekiyor. O yüzden, ölçüm çevrimlerimizin bir kısmında sıcaklığı da ölçmemiz gerekli (sıcaklığın daha yavaş değiştiğinden eminsek her çevrimde ölçmemiz gerekmez ama bu konuda benim sözüme değil kendi uygulamanızın durumuna bakın).

Bu sensör, süreki ya da talep edildiğinde ölçüm yapacak şekilde çalıştırılabilir. Üzerinde bir FIFO var ve son 32 ölçüm sonucu saklanabiliyor (sıcaklık ve basınç için ortak kullanılan bir buffer). Ben deneme uygulamamda bu özelliği tercih etmedim.
Diğer ölçüm modunda biz sensöre basınç ya da sıcaklık ölçümü yapmasını söylüyoruz, çip ölçümü yapıyor ve duruyor. Sonuç da PRS_Bn ya da TMP_Bn register’larında güncelleniyor.
INT pinini kullanmadığımız için işlemin tamamlandığını MEAS_CFG register’ındaki TMP_RDY ve PRS_RDY flag’lerini okuyarak anlıyoruz. İlgili flag 1 oldu ise TMP_B2 ya da PRS_B2 register’larından itibaren 3’er byte okuyup sonucu alıyoruz (okuma işlemi flag’leri otomatik olarak sıfırlar).
Okuma ile aldığımız değerler 24 bitlik işaretli sayılardır üstelik bunlar big endian’dılar. Ham okumaları, düzeltme işlemi için yapacağımız aritmetik işlemlerde kullanabilmek için 32 bitlik işaretli tamsayılara çevirmemiz gerekir. Bu işlem de yukarıda, kalibrasyon katsayılarını açarken kullandığımız yöntemle dönüşüm gerektirir.

Okumanın ne genlikte sonuç döndüreceği seçtiğimiz oversample ayarına bağlı. Örneğin ben, aşağıdaki ayarları tercih ettim:

Oversampling x ölçüm periyodu değeri 1’i geçmemeli. 32 oversampling seçmekle 0,3 Pascal’lık bir ölçüm duyarlılığına erişiyoruz (3cm). Ancak bu, ölçüm hızımızı 16 Hz ile sınırlıyor. Buna ek olarak bir de bit shift ayarı hikayesi var. x32 oversampling, 24 bitlik sonuç register’ını taşırdığı için sensöre sonucu sağa kaydırıp toplama yapmasını söylememize yarayan bir bit var. O da, CFG_REG register’ının P_SHIFT ve T_SHIFT bitlerini set ederek oluyor.

Peki ölçüm aralığımız oversampling’e göre değişebiliyorsa ama katsayılarımız hep aynıysa sonuçları nasıl normalize edeceğiz? İşte burada Infineon bizi floating point aritmetiğine mecbur bırakan hareketini yapıyor. Kalibrasyon katsayıları, ölçüm sonucunun normalize edilmiş değerine göre verilmiş. Bu normalizasyon için okuma sonucunu kullandığınız oversample değerine göre bir sayıya bölmeniz gerek. Bu da datasheet’in 15. sayfasında verilmiş:

Bu bölme işleminden sonra, ölçüm değerlerini c katsayılarıyla çarpıp toplayıp fiziksel büyüklükleri elde edeceksiniz.

Almanların Dünya Savaşı’nı neden kaybettiklerine bakıyor gibiyiz, değil mi!

nRF24L01+

Nordic Semiconductor her ne kadar son zamanlarda bluetooth’a odaklanmış gözükse de uzun yıllardır sub-ghz rf çiplerini kullandığımız bir marka.
İnsanlar artık “uysa da uymasa da” bir protokol seçip, onu çalıştıran stack’in yüklü olduğu işlemciyi barındıran bir SOC alıp, ilave bir-iki özelliğini kurcalayıp proje üretiyorlar. Özellikle farklı cihazlarla beraber çalışma gibi durumlar söz konusu olduğunda bu elbette doğru bir yaklaşım. Öte yandan, eğer bir bluetooth uzaktan kumandası kullanıyorsanız, bunun kitlesel olarak üretilmiş cihazlarda bile bazen ne kadar başarısız sonuçlar verdiğini de yaşayarak görmüşsünüzdür.

Tam çalışma şeklini, iletişim protokolünü, güç yönetimini ve tüm radyo parametrelerini kendimiz ayarlayarak, tam olarak uygulamanızın ihtiyacına göre kablosuz haberleşme gerçekleyebildiğimiz çözümler de var. Bunlar genelde üreticilerin Proprietary RF (uygulamaya özel protokol) başlığı altında ele aldığı çözümlerdir. Teorik olarak herhangi bir RF SOC’unu alıp, dökümantasyonla mücadele edip bu şekilde çalıştırmak mümkün. Ancak, anlaşılır sebeplerle, 1GHz altında çalışan modüller protokolü size bırakılmış, böyle uygulamalar için oluyor genelde. Bizim bu yazıda ele alacağımız 24L01+ bu tarzın 2,4GHz’deki temsilcisi olması sebebiyle de ilginç.

Aslında bu çipin uygulama devresinde çok az harici eleman var, bu yüzden bunu board’a eklemek zor bir iş değil. Ama hazır modüller o kadar ucuza bulunabiliyor ki çipi temin etmeye çalışmaya (ki muhtemelen modülü bulmaktan daha zor) buna değmez.

nRF24L01+ modüllerle ilgili, ilk gördüğümde dikkatimi çeken şey çok ucuz olmaları olmuştu. Eğer uygar ve gelişmiş bir ülkede yaşıyor olsak, para birimimizin bir kesiri ile belirtilen bir fiyat etiketiyle bu ürüne sahip olabilirdik. Muhtemelen bir bardak çay parasından ucuza. İçinde olduğumuz duruma rağmen, bu modüller hâlâ herhangi bir uygulamaya eklemek için çok da düşünmeyeceğiniz kadar ucuzlar. Öte yandan bu ucuzluğuna rağmen pek çok kablosuz habereşme işinde kullanmaya da uygun gibi gözüküyorlar. Yani, bunlara bir bakmaya değer.

Donanım

Gerekenler;
– 3 port SPI,
– iki çıkış : CE ve CS
– bir tane de giriş (isteğe bağlı) IRQ.

Elimde nRF24L01+ modülünü doğrudan takmak için bir board var. Bunu bir ana dinleyici olarak tasarladım. Görevi, sürekli dinlemek ve kendine yollanan verileri RS485 üzerinden aktarmak. Daha otonom çalışma için board’un üzerinde röle çıkışları da var.

Alıcısı olan şeyin bir de göndericisi olmalı elbette. Bu taraf, uygulamaya göre şekillenir. Çünkü biz kablosuz haberleşmeyi data-agnostik işlerde kullanmıyoruz. Elimizde, benim eskiden kalma alışkanlıklarla “saha” dediğim, bir şey ölçmek ya da kontrol etmek istediğimiz bir uzak noktada bir modül var. Radyo haberleşmesi bize bu uzak modül ile, daha merkezi bir kontrol noktası arasında iletişim kurmak için gerekiyor. Yani bizde radyo haberleşmesi, yollanacak veriden bağımsız bir iş değil. Bu yüzden bir alıcı pek çok farklı işte kullanılabilir ancak gönderici kapasitif bir sıvı sensörü de olabilir, radar kullanan bir mesafe sensörü de olabilir, bir sıcaklık sensörü de olabilir, bir kapıya takılmış güvenlik anahtarı da olabilir. Ve işi bu modül tanımlar.
Ben, gönderici tarafın çalışma şeklini denemek için daha önce, bambaşka bir proje için hazırladığım bir prototip board’unu kullandım. Bu kartı seçmemin sebebi boyutları değil, üzerinde bir header ile bağlantısı olan SPI portu olmasıdır.

Bu arada ben gündelik konuşmadaki anlamıyla “alıcı” ve “gönderici” tabirlerini kullanıyorum. Gerçekte, modüller hangi tarafta olurlarsa olsunlar hem alma hem de yollama radyo fonksiyonlarını yürütmek zorundadırlar. Bizim alıcı ve verici derkenki kastımız “data” (veri) yönünü ima etmiyor, “information” (bilgi) yönünü ima ediyor.

Şimdi kullanacağımız pinlerden bahsedelim:
SPI = SCK, MISO, MOSI, CSN
Bu, bildiğiniz SPI faz 0. CSN de 0 iken etkin SPI slave select çıkışıdır.
Ben 1MHz SPI clock ile modüllerle sorunsuz haberleşiyorum. Gönderici tarafta salkım saçak kablolarla bağlantı olduğu için çok hızlandırmayı düşünmedim.

CE = Chip enable çıkışı. Nordic bu tabiri çiplerine dışarıdan bir işlem başlatan girişlerini tanımlarken kullanıyor. Mesela veri yollama işlemini başlatmak ya da alıcıyı etkinleştirmek gibi.

IRQ = Gönderme ya da alma işlemlerinin tamamlanışını, eğer ilgili maske bitini ayarlanmışsanız bu pinin 1->0 geçişinden anlayabiliyorsunuz. O yüzden “kesme” ismi verilmiş. Donanımda pin kıtlığı varsa bunu bağlamak için bir giriş ayırmadan da çipi çalıştırabiliriz ama o zaman periyodik olarak SPI konuşması yapmamız gerekir.

Sürücünün Tasarımı

Sürücü ile kastettiğim, bizim kartlar üzerinde çalışan firmware’in radyo modülleri üzerinden iletişim kurmasını sağlayan fonksiyonlar ve değişkenler. Bunu doğru tasarlarsak donanımımız asıl işini yaptıktan sonra (bir sensörden veri okumak gibi) ileteceği veya alacağı veriyi UART’tan yollamaktan çok da farklı olmayan bir şekilde iletip çevrimini tamamlayacaktır.

Burada konu ettiğimiz radyo çipi gibi amatörlerin beğenisini kazanmış, bilindik ürünlerle ilgili internette firmware araştırmaları yaptığımda karşıma çok fazla boş içerik çıkıyor. Bunun en basit nedeni, adamın birinin (hatta bazen çipin üreticisinin) bir arduino kütüphanesi yazması. Konuyla ilgilenen çoğu kişi bu kütüphaneyi referans gösteriyor. “Nasıl kullanılacağını açıklama” amaçlı pek çok makale gidip bir arduino kütüphanesine dair nesneyi main.c içine koyup .begin demekten öteye geçmiyor.

Bu çip için geçerli değil ama daha dehşet verici olan ise şu: Pek çok durumda, yazılmış arduino kütüphanesinin “kendisi” de ürünün pek çok özelliğini kullanmayan, dökümantasyonla tam uyuşmayan hasbel kader çalışmış bir kod oluyor. Ürünün üstünde olan ve benim tam da onu seçme sebebim olan bazı yönlere dair hiçbir ayrıntı görmüyorum. Yani, internetten çalışan bir kod arama macerası genellikle konuya ilgi duyan ama ayrıntıları çözecek motivasyonu ya da zamanı olmayan kişileri yanlış yönlendirecek ya da asıl işe yarar bilgiden yalıtacak şekilde çalışıyor. Ben bir şeyleri çalıştırmaya çabalarken de, yaptığım bir şeyle ilgili bilgiler paylaşırken de bundan uzak durmaya özen gösteriyorum. Açık dökümantasyonu olan ve geliştirme araçlarına sahip olduğumuz her şeyi nasıl çalıştığını anlayıp, kendi ihtiyacımız için en optimum ayarlarla kullanabilmeliyiz. Aksi halde başkasının gelinliğiyle/damatlığıyla düğün yapmış oluruz. En sonunda ortaya çıkan sonuç da çoğu zaman “optimal” bir çözüm olmaz.

Benim hemen hemen her yeni işte karşılaştığım bu can sıkıcı duruma dair düşüncelerimi de paylaşmamın ardından nRF24L01+ ‘e nasıl erişeceğimiz konusuna dönebiliriz:
SPI’ı , ilk byte’ı bir komut olan master paketlerine yanıt vererek haberleşiyor. Okuma, yazma, register/fifo erişimi gibi tüm olasılıklar ilk byte’ta verilen komuta göre belirleniyor:

Bu da demek oluyor ki, çipe erişme işlerimiz daima bir komutu yazarak başlamalı:

static unsigned char NRF_Command(unsigned char cmd);

Komutlar nrf24l01.h içinde tanımlı bir byte’lık sabitlerdir. Datasheet’in 51. sayfasında listesi verilmiştir. Yukarıdaki fonksiyonda CSN=0 yapıp, verilen komut byte’ını yazıp bu esnada MISO’dan da STATUS içeriğini okuyup sonraki eylemi beklemeye koyuluruz.

nRF24L01+ ‘e yaptıracağımız işler şunlar:
– Çipi konfigüre etmek;
– Çipin çalışma durumunu (MODE) değiştirmek,
– STATUS’ü okumak
– Veri buffer’larına gidecek veriyi yazmak ya da onlardan gelen veriyi okumak.

Bu görevler kısa süren eşzamanlı işlemlerle halledildikleri için generic bir sürücü, gerektiğinde çağrılacak fonksiyonlardan ibaret olabilir.

KONFİGÜRASYON:

Power-up sonrası nRF24’ün lojik kısımlarının çalışmaya başlaması için 100ms geçmesi gerekir. Bu yüzden, program başlar başlamaz çipi konfigüre edeceksek önce bir 100ms beklemek zorundayız. Sonra, çipin POWER_DOWN mode’da olduğundan emin olabiliriz.

nRF24L01+’de çalışma öncesi yapılması gereken ayarlamalar şunlar:
Auto-Ack :
Firmware Sabiti.
Bu özelliği kullanıyorum, o yüzden hem alıcıda hem de göndericide pipe-0 için ack enable yapıyorum.
RX Address Enable:
Firmware sabiti.
Alıcıda pipe-0 ve pipe-1 yollayıcıda ise pipe-0 (ack’i almak için) etkin yapıyorum.
Address Width:
Firmware sabiti.
4 byte adresleme kullanıyorum.
Auto-Retransmission:
Firmware sabiti.
Alıcıda 0 ama göndericide sıfırdan farklı değerlere ayarlanmalı.
RF Channel:
Uygulama için parametrik.
7 bitlik radyo kanalı tanımı
RF Setup:
Uygulama için parametrik.
250kbps veri hızı kullanıyorum. Çıkış gücü ayarlanabilir olabilir.
Payload Width:
Firmware sabiti.
Sadece alıcı tarafta tanımlanması gerek. Yollayıcı tarafta TX payload olarak ne yüklersek radyo onu yolluyor.
P0 için RX Adresi:
Uygulama için parametrik
Alıcıda, bu ağı tanımlayan master adresi budur. Yollayıcıda burada alıcının adresi olmalı (ACK almak için böyle yapmak gerekiyor).
P1 için RX Adresi:
Uygulama için parametrik
Alıcıda, bu konfigürasyon çağrı adresi. Normal veri mesajlaşması dışındaki işlemler için rezerve tutuluyor. Yollayıcıda kullanılmayabilir veya konfigürasyon adresi olarak tanımlanabilr.
TX adresi:
Uygulama için parametrik.
Yollayıcıda bu ağın tanımlayıcısı olan master RX adresi (alıcının P0 adresi). Alıcı için ise tanımlanması gerekmiyor (konfigürasyon için adres gereksin dersek kullanılır).

Çip POWER-DOWN durumundayken konfigüre edilebilir. Ben de bunu tercih ediyorum. Çip yollama veya alma yapmadan önce STAND_BY-1 durumuna geçmiş olmalıdır. Bunu yaptırmak için CONFIG register’ına yazma işlemi yapan bir fonksiyon kullanıyorum:

void Radio_Stby(void)

CONFIG register’ında sürekli değiştirmemiz gereken bit PWR_UP biti (bit-1) olsa da burada CRC ayarlarını, TX/RX çalışmasını ve IRQ pinini sürecek kesme kaynaklarını belirleyen bitler de var. Her Stand_By’a geçişte bunları da doğru değerlere ayarlamak gerekiyor.

GÖNDERİCİ İÇİN İŞLEMLER:

Radyo STAND_BY -1 durumundayken;
– Yollanacak veriyi TX FIFO’ya yüklemek,
– CE = 1 yapmak, 15us beklemek ve CE=0 yapmak,
Yollama işlemini başlatır.

Bu işleri

void Radio_TX(unsigned char xdata *ptx)

isimli fonksiyonumda hallediyorum. Bu fonksiyon her çağrıldığında TX FIFO’ya belirtilen konumdaki paketi yükler ve sonra CE pulse’ını üretir. Burada TX adresini yeniden yüklemiyorum. Flash’tan yüklenen radyo ayarları bellek alanında master adresi tanımlanmıştır ve konfigürasyon aşamasının ilgili adımında bu adres TX adresi olarak yüklenir. Radyo üzerinden yeniden konfigürasyon işlemi için config çağrı adresinin yeninden yüklenmesi gerekecektir.

Auto-Retransmit fonksiyonunu kullandığımız için, belirtilen süre içinde ACK almazsa radyo tanımlanmış sayı kadar tekrar yollama denemesi yapacaktır:


TX için radyoyu STAND_BY-1 durumuna getirirken config register’ına ;

#define CONFIG_TX  0x4E

sabit değerini yazıyorum. Bu durumda nRF24L01+ ‘in IRQ pini veri yollandığında veya max. retransmit count deneme sayısına ulaşıldığında 0 olacak demektir. Ana programın IRQ pininin 1 -> 0 geçişini beklemesi gerekir.

Bu olduktan sonra

void Radio_Clear_IRQ(void)

fonksiyonu ile kesme nedeni okunup hemen sonra da flag’ler sıfırlanmış olunur. Yollayıcı program MAX_RT kesmesi gelmişse alıcının veriyi almadığı varsayımıyla uygun işlemi yapabilir. Tipik kullanımda ben her durumda veri yollaması sonrası donanımı düşük enerjili moda alıp işlemciyi de rtc kesmesi ile uyanacak şekilde kurup kapatıyorum. Gönderme işlemimiz MAX_RT kesmesi ile sonuçlanmışsa daha kısa, TX_DS kesmesi ile tamamlanmış ise de normalde düşündüğümüz uyku süremizi ayarlıyoruz.

Pil Seviyesi Ölçümü:

Kablosuz cihazlar genellikle pille çalıştıkları için, pil düzeyini ölçmek, bu programlarda çoğu zaman olmasını isteyeceğimiz bir özelliktir.
Pil düzeyini doğru ölçmenin basit bir yolu, pilden her seferinde belli bir akım geçerken onun gerilimini ölçmek olabilir. Kablosuz bir uç nokta cihazında nRF24, muhtemelen en çok akım akıtan parça olacağı için, nRF etkinken pil seviyesini ölçmek bize mantıklı bir okuma sonucu verecektir (kullandığımız işlemciler radyo modülüyle kıyaslanmayacak kadar az akımla çalışırlar).
İşte bu yüzden, radyo tam gönderme işleminin ortasındayken pil gerilimini okumak mantıklıdır.

TX işlemi sırasında radyo 7mA ile 11.3mA arasında değişen bir akım çeker (değer, çıkış gücüne bağlı olarak değişir).
Bu durumda, pil seviyesi ölçme işlemini
Radio_TX() fonksiyonu içinde,
CE pulse’ı ürettikten hemen sonra başlatabiliriz.
Yukarıda belirttiğim 710us Time on Air süresi, benim protokoldeki paket süresidir.

ALICI İÇİN İŞLEMLER

Bu tür düşük enerjili saha node’larının master-slave çalıştıkları ağlarda alıcı sürekli açık olsa daha uygun olur gibi gözüküyor. Yani, en basit başlangıçta, bize sürekli dinleme durumunda olan bir radyo gerekli. Bunun için öncelikle nRF24L01+ ‘i başlatıyorum ve ardından onu STAND_BY-1 durumuna sokuyorum. Sonra biraz bekleyip CE pinini 1 yapıyorum. Bu durumda radyo daha önce konfigürasyon aşamasında bahsettiğim üzere, pipe-0 ve pipe-1 için tanımladığımız adreslere gelecek paketleri beklemeye başlıyor. Ayrıca kesmeyi de DATA_READY flag set edildiğinde pini sürmek üzere

#define CONFIG_RX  0x3F      

konfigürasyon değeriyle ayarlamış oluyorum. Bu durumda bir veri geldiğinde IRQ = 0 olacağını varsayıp bekliyoruz.

IRQ=0 algılandığında hemen çipin STATUS register’ını okuyorum. Aslında bu durumda iken tek kesme kaynağı veri alınması olsa da hangi dinleme pipe’ına veri geldiğini ayırt etmemiz için status içeriğini biliyor olmamız gerek.

void Radio_Read_RXData(unsigned char xdata *dest)

fonksiyonu RX FIFO’nun geçerli içeriğini dest ile belirtilen ram konumuna aktarır. Status ile belirlediğimiz dinleyici kanalına uygun kayıt konumunu seçeriz. Bu kısım gelen veriyi işlemeyle ilgili..

Read_RXData() fonksiyonu ile FIFO’yu okuduktan sonra radyo üstündeki gelen veri buffer’ının boş olduğundan emin olmak için bir kez daha STATUS register’ını okuyorum. Buffer boş ise işlemler bitmiştir, dolu ise bu kez hangi pipe’tan veri almışsak onunla ilgili hedef konumuna fifo içeriğini okuyorum. Bu şekilde, birbirine yakın zamanlarda gelmiş olan iki paketi de atlamadan alacağımızı ümit ediyorum.

Yeri gelmişken yazayım: Bu, birden çok dinleme “pipe” ‘ı mevzusu alıcının toplam iş çıkarma kapasitesini arttırmıyor. Çünkü, radyo belli bir anda tek bir kanalda dinleme yapıyor ve sadece bir paketi alabiliyor. Birden çok RX adresi olması otomatik ACK üretme işinde ve bir seviyeye kadar gelen paketleri sınıflandırmada işe yarar.

FIFO boş olduktan sonra kesme bayrağını TX işleminde yaptığım gibi STATUS register’ına yazarak sıfırlıyorum ve alma işlemi bitiyor.

Test programında yapılan son iş gelen veriyi seri porttan yollamak.

alpha-S

decolit alpha-s rgb led lamba modüllerinin çıplak kart halleri

Üç renkli LED elemanlar kullanarak rengarenk aydınlatmalar yapmak son zamanlarda bana da çekici gelmeye başlamıştı. Aslında bunun için hazır pek çok seçenek var ancak ben küçük bir board yapıp, üzerine kendi seçtiğim RGB LED’leri yerleştirip kendi tanımladığım kumanda şemasını yürütebilecek bir şeyler yapmak istedim. Sonrasında bu küçük kartları istediğim yerlere yerleştirip, onları değişken renklerle yakmak niyetindeyim. Noktasal olarak büyük bir ışık yoğunluğunu hedeflemiyor olsam da bir board üstüne 5 tane PLCC kılıf LED koymaktan kaçınmadım.

Kartın boyutları 58mm x 13.5mm oldu.


Deneme amacıyla ürettiğim board’ların üzerine Cree marka CLP6C-FKB model LED’ler taktım. Sonuç gayet güzel.

LED’leri doğrudan kartların besleme hattı üzerinden besliyorum ve her renk için bir pull-down transistör ile sürüyorum. Şemada 100R olarak gözüken seri dirençler besleme gerilimine göre seçilmesi gereken denge dirençleri.

Bu projeyi bir hafta sonu çalışması olarak uğraşmaya değer bulmama neden olan şey bu kartları seri haberleşme ile kumanda edilebilir ve kaskat bağlanabilir yapmam. Bu şekilde iş basit bir renk ayarından çok daha fazlası olabilir.

Aslında arka arkaya bağlanabilir renkli LED deyince akla gelen Neopixel diye bir LED çipi de var. Bunun Çin malı kopyaları ucuza bulunabiliyor. Ama bunlar adreslenebilir modüller değiller. Ben uzun clock katarları üretmek zorunda kalmak yerine doğrudan UART kullanan bir şey yapmak istedim. Bu şekilde tek bir komutla tüm lambalara aynı anda aynı şeyi yaptırabilmek mümkün. Veya istediğiniz belli bir adrese doğrudan komut verebilirsiniz. Eğer hızlı animasyonlar yapmak söz konusu ise bu daisy-chanining’e göre çok büyük bir fark yaratabilir.

Her modülün sol tarafında uart’ının RX ‘i sağ tarafında ise TX çıkışı bulunuyor.

Modülleri arka arkaya (kaskat) bağladığımızda soldan sağa birbirlerini sürmüş olacaklar.Bir modül sol tarafından bir paket aldığında bu paket onu adreslemiyorsa veya bir genel çağrı adresi taşıyorsa o komutu sağ taraftaki TX pininden aktaracaktır. 0-3V gerilim düzeyinde çalışıyor olsak bile bu şekilde alavere yaparak uzun bir lamba zincirini bir uçtan kumanda edebilmeyi umuyorum. Bu arada, 24mA kaynak akımı verebilen bir logic buffer kullanmakla sonraki modül ile aramızdaki kablonun kapasitesinden kaynaklanacak yavaşlamaları azaltmış olacağız. Şemadaki paralel kondansatöre takılmayın. Orada bir komponent yeri olsun istedim sadece.

Benim “adreslenebilir” bir lamba modülünden beklediğim kumanda işlevleri her kanal için ayrı ayrı olmak üzere;
* Açma ve kapama denetimi,
* Çıkış gücünü ayarlama,
* Rampalı açma/kapama,
* Çıkış gücünü rampalı değiştirme,
* Rampa sürelerini değiştirebilme
* Çıkış gücünü çıkışa yansıtmaksızın değiştirebilme (komut verildiğinde uygulanmak üzere)
* Tüm modüllere eşzamanlı komut verebilme

Bu fonksiyonlar basit seri komutlarla kumanda edilebildiğinde ana kontrolcünün karmaşık efektleri yürütmesi kolayca mümkündür.

Firmware Yapısı

Ana program, dört ayrı thread’den oluşur:
+ Sürekli olarak çalışan ve her aşaması belirli bir zaman aşımı denetimi ile korunan bir seri haberleşme thread’i..
+ Zaman çoğullamalı olarak çalışan ve her biri bir renk sürücüsüne çıkış veren üç özdeş paralel renk kontrol thread’i.

Her renk kontrol thread’i 4 farklı duruma dair task’ler çalıştırır. Her task’in o renk için bir “durum” olduğunu düşünün. Bunlar;
OFF
ON
RAMPUP
RAMPDOWN
task’leridir. OFF ve ON task’leri kalıcı durumlardır. Bir komut alınmadığı sürece denetim bu task’i çalıştırmayı sürdürür. RAMP task’leri ise belli bir süre boyunca çalışan geçiş durumlarıdır. Set değeri değiştirildiğinde (kademeli geçiş komutu verilmişse) veya kademeli açma kapama komutları verildiğinde yeni set değerine ulaşılıncaya dek bu adımlarda kalınır.

Çıkış kontrol task’lerinin zaman bölümlemesi 10,7ms ‘dir. Yani her 10,7ms’de bir sıradaki renk kanalının geçerli durumuna dair task çalışır. Ve bu çevrim art arda sürekli devam eder. İki rezerve zaman slotunun da eklenmesiyle, bir tam çevrim 5×10,7 = 53,5ms ‘de tamamlanır. Bu demektir ki, çıkış güncellemeleri 53,5ms çözünürlükle yapılmaktadır. Durağan adımlarda bu önemli olmamakla beraber kademeli geçiş adımlarında bu süre değişim hızını tanımlar.

PWM

İşlemcinin üzerindeki 3 PWM kanalını da kullanıyoruz. PWM çözünürlüğü 8 bittir. PWM frekansı da 7,98 kHz’dir.
Bu projenin donanımını tasarlarken EFM8BB1 işlemcisini kullanmıştım. Firmware’e de bu işlemci üzerinde çalışarak başladım. Sonra iş deneme aşamasına gelince prototip montajları için bana yardımcı olan arkadaşımın elimdeki board’lara BB1 yerine yanlışlıkla SB1 takmış olduğunu fark ettim.
Sleepy-Bee ile Busy-Bee “hemen hemen” pin uyumludur. Ancak firmware’de oldukça yaşamsal farklar vardır. Ne yazık ki buna PCA modülü de dahil, ki bu board’da PWM’i bununla üretiyorum. O yüzden, elde olan asıl olandır mantığıyla benim firmware’i SB1 üzerinde çalışacak şekilde değiştirdim. Ancak asıl üretimim BB1 ile olacak. Çünkü bunda daha çok özellik var.
Bu işlemcilerde PWM çıkışı aktif duty cycle’da 0, pasif durumda da 1 oluyor. Ben BB1’de PCA0POL register’ı ile bu evirme işlemini donanımsal olarak halledebiliyordum. Ancak SB1’de POL register’ı yok. Bu durumda çıkış gücü 0 yazarsak en büyük 255 yazarsak en küçük olmuş olur. Ayrıca, kanalı kapatmak istediğimizde önce çıkışa 255 yazmalı sonra da _ECOM bitini sıfırlamalıyız.
8 bit PWM’de set değerini PCA0CPHx register’ına yazmak gerek. Daha yüksek çözünürlüklü PWM ayarlarında kullanılabilen auto-reload register’larının kullanım seçimi bu modda kullanılmıyor.
Çalışma sırasında PCA modülü ve sayacı sürekli devrede. Bir kanalın kapalı olması gerektiğinde
PCA0CPMx = 0x02; yazarak o çıkışın toggle olmasını engelliyorum. (PCA0CPHx = 255)
PCA0CPMx = 0x42; yazdığımda da PWM çalışmaya devam ediyor.

BB1 kullanan versiyonda işler biraz daha değişik. Burada o kanalın POL bitini = 0 yapıp _ECOM=0 yapmam çıkışı kapatmaya yetiyor. Duty cycle’ı değiştirmeme gerek yok.
Ayrıca, BB1 ‘de PWM’i center-aligned kullanıyorum.

Kontrolcü, komutlar tamamlanmadan yeni komutu işleme almaz. Ancak rampa komutları icra edilirken seri haberleşmeden yeni bir komut alınırsa komut kuyruğa eklenir ve geçerli komut tamamlanınca işleme alınır. Ani açma / kapama /set değeri değiştirme komutlarında bunun olma olasılığı yoktur. Rampa işlemlerinde komutun tamamlanması rampa ayarına bağlı olarak uzun sürebileceği için bu olasılık vardır.

Üç renk kanalı birbirinden tamamen bağımsız olarak çalışabilir ve kumanda edilebilirler. Ancak tamamen eşzamanlı olarak da kumanda edilebilirler.

Bir zincirde 128 adet modül arka arkaya bağlı olabilir. Her modülün adresi sabittir. Bir ağda aynı adresli iki modül olması çalışmayı bozmaz, sadece bu modüller ayrı ayrı ayarlanamamış olurlar. 0x81 adresi tüm modüllerin ortak çağrı adresidir.

Seri portun haberleşme hızı 38400bps’dir. Yazdığım ilk versiyonda modül aldığı paketin adresi kendi adresi değil ise paketi tamamlanmasını beklemeden iletmeye başlıyordu. Ancak sonra, kesme kodunu kısa tutma takıntım yüzünden bu özellikten vazgeçtim. Bu durumda, her modül için paket aktarım gecikmesi yaklaşık 1,6ms’dir.

Haberleşme Yapısı

Paket boyu tüm komutlar için sabittir ve 6 byte’tır. Her paket sync byte’ı (0xAA) ve hedef node adresi ile başlar (0x01..0x81). 0x81 genel çağrı adresidir.
Ardından gelen cmd byte’ı paketi tanımlar. Geçerli komutlar ve kullandıkları parametrelerin anlamı şöyledir:

node: 1..128 arası, hedef adresi. 129 tüm modülleri adresler.
cmd_X : kanal komutları. anlamları şunlar:
0: Bu kanala ait komut yok.
1: forced-on : Kanal kapalıysa hemen açılmasını sağlar. Eğer açıksa out_X’te o an yazılı olan set değerini çıkışa yansıtır.
2: turn_off : Kanalı hemen kapatır.
3: ramp-up: Çıkış gücünü kademeli olarak out_X ‘te yazılmış değere yükseltir. Kanal kapalı idiyse açılır ve set değerine kademeli yükselir.
4: ramp-down: Çıkış gücünü kademeli olarak out_X’te yazılmış değere azaltır.
5: rampdown-off: Çıkış gücünü sıfır değerine kadar kademeli azaltır ve sonra kanalı kapatır.
NOT: Ramp-up ve ramp-down komutları, o anki çıkış oranı, out_X set değerinden sırasıyla yüksekse ve düşükse tek seferde yeni set değerine gelir.

Bu projede önemli bileşenin lambaların kontrolcüsü olduğunu düşünüyorum. Firmware’i tamamlayıp denemelere başladığımda işin bu yönünün geliştirilmeye oldukça açık olduğunu gördüm. Yukarıdaki komutların çalışmasını hızlı biçimde deneyebilmek için basit bir PC programı hazırladım. Aşağıda onun görüntüsünü görüyorsunuz. Üzerine tıklayarak programı indirebilir ve deneyebilirsiniz:

Ermeni “meselesi” hakkındaki düşüncelerim

Bu blog, kişisel notlarımı üzerinde uzun uzun düşünme gereği duymadan not ettiğim bir karalama defteri. Üzerine çok eğilme fırsatı bulamamış olsam da oldukça uzun süredir sürdürdüğüm bir çeşit kaçamağım.
Bu bloga sahip olmanın benim için şöyle güzel bir yanı var: 12 sene önce yazdığım bir yazıya baktığımda vay be o zaman nasıl da farklı düşünüyormuşum diyebiliyorum.
Kişisel düşünce tarihimin aşamalarını bir bakışta görebiliyorum.

“Dünya görüşü” denen şeyi iki boyutlu bir harita gibi düşünürsek, ben o haritada epey yolculuk ettim. Kalmak istemeyeceğim duraklara da uğradım, sırf meraktan turist gibi de dolaştım. Yolculuğa başladığım yer ile şimdi olduğum yer o kadar farklı ki, hâlâ ben nereye aitim sorusunun yanıtından emin değilim. Bu ülkede insanların dünya görüşlerini birbirinden ayıran anahtar parametrelerden, din, muhafazakarlık, militarizm, seçkincilik, halkçılık, devlete bakış, sol/sağ ayrımı vs. konularda benim düşüncelerim 10-15 sene öncesine göre taban tabana zıt artık. Türkiye’nin son 10 senesini bilinci açık yaşamış her Türk vatandaşı için bu tür bir dönüşüm kısmen kaçınılmazdır diye düşünüyorum.

Fakat, böylesi bir dönüşüm sürecinde bakıyorum, yine önemli tartışma konularımızdan biri olan Ermeni soykırımı ya da techiri meselesinde benim düşüncem pek değişmemiş. İlginç. Konunun kendisi aslında üzerinde yazı yazmaya değer bile değil artık. Yıllar önce olup bitmiş bir olay. Ve şimdi ben, birine sırf Ermeni ya da başka bir milletten olduğu için kötülük eder miyim? O bana Türk olduğum için kötülük etmeye kalkarsa Çingene mahallesi kavgasına girer miyim? Geçelim bunları. Küçük ya da büyük hiçbir kavgamız kimliğimizle ilgili değil bizim. Biz Türk ya da Ermeni diye birbirimizi yerken paranın ırkının en saf ırk olduğunu keşfetmiş birileri bizim kavgamız üstünden kâr etmenin yollarını bulmuş olurlar bile ve bunu düşünmek öfkemizin yönünü de belirler, değil mi? En azından bu kadarını el yordamıyla bulabiliriz. Öyleyse ilginç olan ne bu hikayede? İlginç olan bu meselede benim görüşümü netleştirmemi sağlayan çıkarımlar. Onlardan söz edelim.

Birincisi, bu ülkede, uzmanlık alanı bu olan insanlar dahil hiç kimse, Ermeni meselesini bir dış sebep olmadan konuşmuyor. Bu yazıyı, dün Amerikan Başkanı 24 Nisan anma bildirisinde “genocide” sözcüğünü kullandığı için, evet, sırf bir iki danışman böyle bir sözcük yazdığı için yazıyorum. Çünkü dünden beri sırf bu basit sebep yüzünden kıyamet kopuyor. Madem bu kadar doluyuz bu konuda, madem bu kadar haklıyız, neden tüm kavgamızı Allahın dünyadan bi haber Amerikan demokrat kırolarının laf arasında deyip geçtikleri bir saçma söz üzerinden yapıyoruz?

İkincisi arşivleri açalım muhabbeti.. Bunu biraz daha entelektüel açıdan özgüveni olan arkadaşlar dillendiriyor. Herhalde arşivde mühürlü bir kağıt var ve üzerinde yaptık ya da yapmadık yazıyor. Allahın orijinal sözlerinden oluşan ve sırf bu işle iştigal eden bir melek tarafından getirilmiş, tek bir harfi bile değiştirilmemiş bir kitap üzerine bile “ilahiyat” diye bir “bilim” dalı üretmişiz ama arşiv denen şeyin kapısını açıp gidip bakarsak sonucu göreceğimiz ve tartışmaları bitireceğimiz bir şey olduğunu iddia edip tarih bilimini çöpe atabiliyoruz. Bu sanırım çok düşünmemiş olmanın, bir konu üstüne farklı kitaplar okuyup kararı kendin vermek gibi bir ayrıcalığı hayat boyu yaşamamış olmanın hazin sonucu.

Üçüncüsü ve bence en yüksek ağırlığa sahip olanı şu: Bu memlekette son birkaç senede, devleti yönettiğini iddia eden iktidar kurduğu ortaklıklar bitince fikir ve dünya görüşü olarak tamamen kendinden olanlara bile nasıl zulmetti görüyoruz. Onları geç, bunun bir iç kavga olduğunu düşünebilirsiniz haklı olarak. Bu ülkede kendisi gibi düşünmeyen insanlara düşman olma, ona gün yüzü göstermeme kültürünün ne kadar derin kökleri olduğunu her gün görüyoruz. Bu adamlar 106 sene önce “gavur” lara mı merhamet edeceklerdi? Mantıklı geliyor mu? Gelmiyor değil mi..
Diyeceksiniz ki, şimdiki iktidar ittihatçılara misal olur mu? Tamamen olmaz tabi. Ama benim örnek gösterdiğim ikitdar/ittihatçılar değil. Onların yaptıklarına kayıtsız kalan hatta destek veren halk. 12 Eylül’e destek verenlerle tarikat ortaklı dinci faşizme destek verenler aynı halk. Bunlar için Enver Paşa ile Erdoğan çok fark etmiyordur bence. Bu “potansiyel” ürkütücü değil mi?

Bu kadar büyük bir vahşet yaşanmış olabilir mi diye ben de bazen kuşkuya düşüyorum. Ama bir de şunu düşünün: Şu günlerde konuştuğumuz bir skandal var. İktidar belediyeleri hizmet pasaportları çıkarıp binlerce insanı Avrupa’ya kaçırmış. Bunlar Malatya Elazığ Bingöl gibi illerden adam toplamışlar.Belediye meclisi’nde karar çıkarıp kaymakamlıklara onaylatıp Almanya’ya insan götürmüşler. Aynı adamlar Almanya bizi kıskanıyor diyorlar mı? Diyorlar.. Aynı halk inanıyor mu inanıyor.. Devlet imkanları ile ülkeden kaçan o binlerce insanı tanıyan, sülalesine kadar bilen yüz binlerce insan yaşıyordur oralarda. Ve hepsi de Almanya bizi kıskanıyor denince inanıyor, alkışlıyor. İşte bu insanların 106 sene önceki hallerinden söz ediyoruz.
Sanırım paragrafın devamını yazmama gerek yok.

Bugünden geçmişe bakmak adına güzel bir konu oldu bu ama fazlası değil. Biz kabul etsek nolur etmesek ne olur. Biz kendimizden dediğimiz insanlara ne kadar merhamet gösteriyoruz ona baksak yeter. 106 sene önce, Dünya Savaşı’nın ardından gelen kaosta yaşanmış şeyleri yorumlamak bizim için verimli bir çalışma alanı değil bence. Şimdilerde muhalefet olma iddiasındaki düşük profilli insanların Amerika’ya tarih dersi vermesine tanıklık edeceksiniz. Sabırlar dilerim.

PIC32MM ile PWM

Aslında PIC32MM çiplerinde pin remap fonksiyonu “peripheral pin select” adıyla var. Ancak ilk üç capture/compare modülü için serbest pin ataması yapılamıyor. Bunlar aynı zamanda birden çok çıkışla kullanılabilen modüller. Bu çıkışların yerleri sabit. Örneğin, QFN28 kılıf olan versiyonda Output-Compare çıkışlarının hangi pinlerle paylaşıldığını aşağıda görüyorsunuz:

Pinler OCMxy designatoru ile isimlendirilmişler. Burada;
x modül adı oluyor. Yerleşik pin fonksiyonuna sahip modüller CCP1, CCP2 ve CCP3
y modülden çıkış alabilen kanal indeksi oluyor. A,B,C,D,E ve F olarak 6 “olası” kanal var. Bunlar tahmin edebileceğiniz gibi birbirlerinden tamamen bağımsız çıkış üretmiyorlar. Hatta; A,C,E’yi bir grup B,D,F’yi bir başka grup olarak kabul edebiliriz.

PIC32MM USB Curiosity Development Board üzerine PWM’leri denememiz için bir RGB LED koymuşlar. Bu malzemenin,
* Kırmızı LED’i OCM1B
* Yeşil LED’i OCM2B
* Mavi LED’i OCM3E
çıkışlarına bağlı. Her LED başka bir CCP modülünde olduğu için sayısız renk kombinasyonu elde edebiliriz.

LED’leri farklı PWM duty cycle’ları ile sürüp renk ve parlaklık ayarı yaptırmak, bu demonun ana fikrini oluşturuyor. Galiba, Microchip’in sitesinde de bununla ilgili bir demo görmüştüm ama indirip çalıştırmaya fırsatım olmadı. Zaten buna gerek de yok. Çok basit bir-iki ayarlamayla üç PWM kanalı emrinize amade oluyor.

Şart mı bilmiyorum ama ben bu deneme için elimdeki Initialize() kodunda bu pinleri (RD1, RC3 ve RC15) çıkış yaptım.

CCP modüllerini devreye almak için yapılacak minimum ayarlama; timebase seçmek, çalışma modunu ayarlamak, çıkış pinlerini ve polaritesini ayarlamak ve periyot register’ına pwm periyodunu girmekten ibaret.
RGB LED sürerken en basit anlamıyla birbirinden bağımsız edge aligned bir PWM yeterli olduğu için her modül 16 bit dual edge compare modunda, bağımsız çalışıyor (dead time da yok). Bu çok basit bir kullanım şekli. PIC32 MCCP Referans Manual’ini incelerseniz CCP modüllerinin uygun bir donanım tasarımıyla çok enteresan şeyler yapılabilecek özellikleri olduğunu göreceksiniz.

CCPxPR : PWM periyodunu belirler.
CCP1RA : Edge aligned PWM için bunu 0 kullanıyorum.
CCP1RB : Falling-edge match değeri. Duty Cycle’ı bu belirliyor.

Dev. Board’lar üzerindeki potansiyometreyi LED’lerin parlaklığını (PWM DC) ayarlamak için kullanıyorum. Hangi rengin seçileceğini de yandaki üç push-button belirliyor. Potansiyometre RC8 pini ile paylaşılan AN14 analog kanalına bağlı. MM’in ADC’sinden ayrıca bahsediyor olsam da burada ADC’yi 10 bit okuma için başlatan ve AN14’ü okuyan bir kod da main.c içinde var.

PIC32MM USB Curiosity Board üzerinde çalışmaya hazır kodlara bu klasörden erişebilirsiniz: