State Machine mantigi ile program yazmak

Başlatan z, 28 Nisan 2014, 01:16:22

z

Adi sani onemli olmayan bir islemci icin program yazmaya basladik.

Bayagi da bir sey yazdik. Ana program icinde hesaplar yapiyor dudukler otturuyor, LCD de bir seyler gosteriyor gidiyor bir yerlerde donguye giriyor donguden cikiyor seri porttan bir seyler yolluyoruz vs vs vs.

Bu islemcinin sadece 1 tane timeri var. Bu timeri da 2 mili saniyede bir interrupt (kesme) uretecek sekilde kurduk.

Haliyle ana program kostururken her iki mili saniyede bir bu interrupt rutinine giriyoruz.

Gelelim can alici noktaya.

Timer interrupt rutinin icinde A pinine bakmamiz; eger A pini 1 ise bir ledi yakmamiz ardindan da 100 mili saniye sonra da bu ledi sondurmemiz isteniyor olsun.

Sorunumuz aynen bu sekilde.

Islemcilere yeni yeni kod yazmaya baslayan arkadaslar bunu asagidakine benzer sekilde yapmaya calisiyorlar.


void Timer_Interrupt_Rutini()
{
       Timer=2;  // 2 mili saniye sonra tekrar int uret anlaminda

       if (A==1)  // Eger A pini 1 ise asagidaki islemleri yap
         {
            Ledyak();      // ledi yak
            Delay(100);   // 100 mili saniye bekle
            LedSondur(); // ledi sondur
         }
}


Not: Programda A girisi surekli 1 de tutulursa led hep yaniyor gorunecektir. Fakat ilk mesajdaki soruya dikkat ederseniz soru istendigi gibi cevaplanmistir.

Program yukaridaki gibi yazildiginda cok onemli bir hata yapiliyor.

Interrupt rutini, ana programdan islemciyi odunc alan bir rutindir.

Yahu komsu, acil carsiya gitmem gerekiyor arabani bana odunc ver demek gibi bir seydir. Komsunuz arabasini size verdiginde artik o bir yere kimildayamaz. Ne zaman arabayi geri verirsiniz o zaman arabasina biner ve gider.

Bu durumda bir daha arabayi istemeye yuzunuz olmasi icin odunc aldiginiz arabayi olabilecek en kisa zamanda teslim etmemiz gerekir. Cunku komsunuzun arabasina binerek gitmesi  gereken pek cok yer  (işler) varken sizi kiramadigi, size deger verdigi icin arabasini gozunu kirpmadan size odunc verdi.

Siz yukaridaki gibi program yazmakla ne yaptiniz biliyormusunuz?

Komsunuzdan arabayi alip carsiya gittiniz isinizin bir kismini yaptiniz sonra arabayi bir parkyerine parkettiniz. Ardindan bir kafeye gittiniz bir arkadasinizla 1 saat laklak edip kahve ictiniz. Daha sonra arabayi parkettiginiz yerden alip diger islerinizi yaptiniz ve ardindan komsunuza gidip arabayi teslim ve tesekkur ettiniz.

Sizin kafede laklak ettiginiz sure boyunca komsunuz arabasiz oldugu icin cok onemli randevularini kacirdi, karisina soz verdigi seyi alamadi cunku dukkan kapanmisti vs vs.

Devami yarin.
Bana e^st de diyebilirsiniz.   www.cncdesigner.com

z

Hocam kusura bakmazsan bu mesajini bir baska basliga tasiyacagim. Cunku yarin anlatacagim konu;

islemcilerle yeni yeni ugrasmaya baslayip interrupt rutini icinde delay tipi komutlar kullanarak bir seyler yapmaya calisan yani yanlis yolda olan arkadaslara yonelik.

Fakat bir baska baslikta yazacaklarini okumaktan keyif alirim.
Bana e^st de diyebilirsiniz.   www.cncdesigner.com

z

#2
Simdi isin felsefesini anlatmadan once yukaridaki programi bir de asagidaki gibi yazdigimizi dusunelim. Ana program calismaya basladiginda State adini verdigim degiskeni sifirladigini varsayin.

void Timer_Interrupt_Rutini()
{
       Timer=2;  // 2 mili saniye sonra tekrar int uret anlaminda
       if (State==0)
         {
            if (A==1) 
               {
                 State=1;
                 Ledyak();
                 N=0;
               }
          }
       else 
         {
            N++;
            if (N==50)
              {
                 LedSondur();
                 State=0;
              }
         }
 }


Dikkat ettiyseniz Timer_Interrupt_Rutini icinde delay gibi komutlarla hic bekleme yapmadik.

Kafanizda bir seyler canlandi mi?


Bu programda da komsumuzdan arabayi odunc aliyoruz. Isimizi yapmaya basliyoruz fakat isimiz bitsin bitmesin kisa bir sure sonra komsumuza arabayi geri veriyoruz. Komsum islerim bitmedi senin de cok isin var. Sen islerini yapmaya basla ben bir ara gelip senden gene arabayi alacagim diyoruz. (Yuzsuzlugun de bu kadarina pes ama komsumuzu asla zor durumda da birakmiyoruz)

Not: Programda A girisi surekli 1 de tutulursa led hep yaniyor gorunecektir. Fakat ilk mesajdaki soruya dikkat ederseniz soruyu istendigi gibi cevapladigimizi goreceksiniz.

Ana programimiz, interrupt rutininin ledi 100ms sonra sondurme girisiminden dolayi hic etkilendi mi? Hic isleyemez durumda kaldimi?

Interrupt rutinlerinde nicin bekleme yapilmaz (yapilmamali)  yada uzun sure alacak islemler yapilmaz (yapilmamali) dendigini simdi anladiniz mi?

Simdi soz sizde. Daha sonra tekrar devam edecegim.
Bana e^st de diyebilirsiniz.   www.cncdesigner.com

fgokcegoz

Peki @z hocam,
Timer interrupt rutinine hiç yüklenmeden yani tanımladığımız timeTick gibi bir değişkeni bir artırıp çıksak ve pinin lojik1 olup olmadığını - olduysa ledi yakıp 100ms sonra söndürmemiz gerektiği işini yapan durum makinesi yapısını ana döngüye yüklesek daha doğru olmaz mı ?
"Vicdanın ziyası, ulûm-u diniyedir. Aklın nuru, fünun-u medeniyedir. İkisinin imtizacıyla hakikat tecelli eder." (Bediüzzaman Said Nursi)

Kabil ATICI

Bunuda şöyle bakalım, şimdi değişkenin kontrolunu ana döngüde yapıyorsun, ama senin ana döngüdeki işlerin kesme süresinin çok üzerinde bir zamanda işlemi yapıyor olsun, olur mu olur.
Şimdi senin değerini kontrol edene kadar kesmede 2 veya 3 değer artışı olursa ne olur? Karşılaştırma noktası geçilir gider.

Bunu sadece timer bazında inceledik, birçok defalar aynı olay seri port haberleşmesinde de yaşanan bir durumdur.
ambar7

aliveli

konuyla ilgili güzel bir örnek
biraz ccs c ve ingilizce bilmek gerektirir
#include <16F877.H>
#device adc=8
#fuses XT, NOWDT, NOPROTECT, BROWNOUT, PUT, NOLVP
#use delay(clock=4000000)
#use rs232(baud=9600, xmit=PIN_C6, rcv=PIN_C7, ERRORS)

#define LED_PIN  PIN_B0

// This equation calculates the duration of each timer tick in
// ms.  The timer is clocked at 1/4 of Fosc.  Because we are
// using a 4 MHz crystal, the timer clock is therefore 1 MHz.
// We are using a pre-scaler of 32.  Timer0 rolls over from
// 0xFF to 0x00 every 256 clocks
#define TICK_MS ((1000 * 256 * 32) / (4000000/4)) // 8 ms

#define TASK1_TIMER_VALUE (2000/TICK_MS)  // Every 2 seconds 
#define TASK2_TIMER_VALUE (500/TICK_MS)   // Every 500 ms
#define TASK3_TIMER_VALUE (100/TICK_MS)   // Every 100 ms


int16 task1_timer;
int16 task2_timer;
int16 task3_timer;

//--------------------------
void timer_tick(void);
void task1(void);
void task2(void);
void task3(void);

//==========================
void main()
{
// Setup Timer0 so it rolls over at a 122 Hz rate.
// This gives a timer tick of approximately 8 ms.   
// All tasks (together) must execute in less than 8 ms.
setup_timer_0(RTCC_DIV_32); 

task1_timer = TASK1_TIMER_VALUE;
task2_timer = TASK2_TIMER_VALUE;
task3_timer = TASK3_TIMER_VALUE;


// The sum of the time to complete all 3 tasks
// must be less than the timer tick duration.
// Currently the tick is set at 8 Ms.
while(1)
  {
   task1();
   task2();
   task3();

   timer_tick();
  }

}
//================================

// This task transmits a character (A to Z)
// to the PC once every 2 seconds.
void task1(void)
{
static char value = 'A';

if(task1_timer)
   return;
else
  task1_timer = TASK1_TIMER_VALUE;


putc(value);
value++;
if(value > 'Z')
   value = 'A';


}

//--------------------------
// This task blinks an LED at a 1 Hz rate.

void task2(void)
{
static int8 led_on = FALSE;

if(task2_timer)
   return;
else
  task2_timer = TASK2_TIMER_VALUE;

// Toggle the LED on/off.
led_on = !led_on;

if(led_on)
   output_high(LED_PIN);
else
   output_low(LED_PIN);

}

//--------------------------
// This task does slightly more complex blinking
// of an LED.  It is on for 100 ms, and off for 500 ms.

void task3(void)
{
if(task3_timer)
   return;
else
  task3_timer = TASK3_TIMER_VALUE;


// Task 3 doesn't do anything yet.
}

//--------------------------
// This function waits for the hardware timer0
// to count up to 0xFF and roll over to 0x00.
void timer_tick(void)
{
// Wait until the Timer0 rolls over from 0xFF to 0x00.
while(!bit_test(get_timer0(), 7));  // Wait until MSB goes high
while(bit_test(get_timer0(), 7));  // Wait until Timer rolls over to 0

if(task1_timer)
   task1_timer--;

if(task2_timer)
   task2_timer--;

if(task3_timer)
   task3_timer--;
 
}



MrDarK

Bana kalırsa süresinden emin olamadığımız kritik tüm karşılaştırma işlemleri interrupt rutinleri içinde yapılmalı, ıvır zıvır diğer kod parçaları ana program ve modüller içinde yapılmalı. En azından incelediğim state machine programlarında böyle yapılar kullanılmakta.

Watchdog'a da takılmamak için süre taşma yapabilecek yerlerde refresh kullanmakta yarar var reset yememek için.
Picproje Eğitim Gönüllüleri ~ MrDarK

z

Şimdi de daha güzel bir soru soracağım.

Ana program içinde gene bir sürü ıvır zıvır kodumuz var ve bunlardan bazıları bazen dakikalarca bir döngü içinde kalabiliyor.

Seri porttan ne zaman ve ne sıklıkta geleceği belli olmayan veriler geliyor.

Soru: Seri port receive interrupt rutini içine öyle bir kod yazalım ki;

Seri porttan peş peşe 0x41, 0x42, 0x43,  şeklinde datalar alınırsa ilk gelecek data A portuna yazılsın.

Örneğin 0x41 0x42 0x43 0x37 dataları alındığında A portuna 0x37 yazacağız.

0x41 0x42 0x41 0x43 0x41 0x42 0x43 0x55 alırsak bu kez A portuna 0x55 yazacağız.

Öyle bir interrupt rutini yazalım ki ana programı bloke etmeyelim.

Gene bu programı da state machine mantığı ile yazmamız gerekecek.
Bana e^st de diyebilirsiniz.   www.cncdesigner.com

picusta

Z hocam pedagojik yönden konuya çok güzel giris yapiyor, yeni baslayanlar için oldugunu belirtiyor.
Fakat gel gör arkadan gelen mesajlar tam yapilmamasi gerekeni yapiyor, Z'nin tekerine çomak sokuyor.
Konunu temelini anlatmaya çalisan makalede biri professyonel araç linki veriyor (yeni baslayanin bu araci anlamasi ve kullanmasi için birkaç derse daha ihtiyaci olacak).
Digeri ise ingilizce yorumlarla dolu bilmem kaç satirlik kod veriyor, konu ile uzaktan alakasi var üstelik (interrupt kullanmiyor), ve anlatilmaya çalisanin tam aksi (ana döngüde bekleme yapiyor).

Konuyla ilgili nacizane yorumlarim :
if State= 0 ... State = 3 ...
Seklindeki satirlar yerine söyle yapsak :

  • 0, 3 , 45 gibi"literal" rakamlari koddan çikartsak, onun yerine define, durum makineleri için daha da iyisi Enumeration kullansak :
    typedef enum ENU_LED_STATE{
    YAK,
    BEKLE,
    SONUK
    }
  • State degsikeni yerine StateLed veya LedState degiskeni kullansak.  Program birden fazla nesnenin durumunu kontrol edebilir kanimca, daha sonra karisiklik yasamasak

switch (LedState)
{
case YAK:
  ...
  break;

case BEKLE:
   ...
   break;

case SONUK:
  ...
  break;
}


z

#9
Aslında Gerbay'ın suçu yok. Suç benim. Konu başlığını açtığımda  mesaj olarak içine sadece "yarın devam" edeceğim diye not düşmüştüm. Bu nedenle de Gerbay olaya giriş yaptı.

Ama dediklerine tamamen katılıyorum. Dönen tekere farkında olmadan çomak sokma durumları olabiliyor.

Enumeration konusunda da çok haklısın. Sadece enumeration konusunu bir başlık altında başlı başına bir konu olarak ele alalım.

Enumeration işlemi için tıklayın.

Bu ikinci örnek programda da enumeration işlemini es geçeceğim.

mesaj birleştirme:: 28 Nisan 2014, 11:09:09

Seri porttan peş peşe 0x41, 0x42, 0x43,  şeklinde datalar alınırsa ilk gelecek data A portuna yazılsın.

Bu programı interrupt kullanmadan ana program içinde yazmaya kalksaydık;

   
            Rx=WaitData(); // Seri porttan data alıncaya kadar bekle, gelen datayı Rx içine yaz anlamında
            if (Rx==0x41)
              {
                 Rx=WaitData();
                 if (Rx==0x42)
                   {
                      Rx=WaitData();
                      if (Rx==0x43)
                        {
                           A=WaitData();
                         }
                   }  
               } 


Şekline yazabilir ve bir döngü içine yerleştirebilirdik. Gerçekten de program isteneni yapmaktadır. Fakat bu şekilde bir program yazımı işlemciyi veri gelinceye kadar beklemeye zorlar ve bu esnada interruptlar haricinde ilave hiç bir şey cevaplanmaz kod işletilmez.

Bu program parçacığını bu şekliyle interrupt rutini içine de yazamayız. 

Yukarıdaki yazım şekli berbat ötesi rezil bir kodlama şeklidir. Seri port hata kontrolu yapmamış olsak da soruda isteneni layıki ile yapar.

Bir de aşağıdaki kodlama şekline bakalım. Gene State değişkeninin sıfır olduğunu varsayın.

void Receive_Interrupt() // seri porttan data gelirse bu rutin çalışacak demektir.
{
       Rx=SeriPortRx_register; // Gelen datayı oku

       switch (State)         
           {
                 case 0: 
                              if (Rx==0x41) State=1;
                              break;
                 case 1: 
                              if (Rx==0x42) State=2;
                              else State=0;
                              break;
                 case 2: 
                              if (Rx==0x43) State=3;
                              else State=0;
                              break;
                 case 3: 
                              A=Rx
                              State=0;
                              break;
           }
}


Ya da daha case switch kullanmaya başlamadım ben if- else ile yazacağım diyorsanız

void Receive_Interrupt() // seri porttan data gelirse bu rutin çalışacak demektir.
{
       Rx=SeriPortRx_register; // Gelen datayı oku

       if ((State==0) && (Rx==0x41)) State=1;
       else if (State==1) 
               {
                  if (Rx==0x42) State=2;
                  else State=0;
               }
       else if (State==2) 
               {
                  if (Rx==0x43) State=3;
                  else State=0;
               }
       else if (State==3) 
               {
                  A=Rx;
                  State=0;
               }
}
Bana e^st de diyebilirsiniz.   www.cncdesigner.com

MrDarK

#10
Tek tek gelen kodu kontrol etmek bence böyle mantıklı değil ;

Ana kod içindeyken interrupt rutini uart'dan gelen datayı bir buffer array içine aktarmalı ; daha sonra ana programda bu buffer registerı kontrol edecek uygulamayı aktif etmeli. Bu sayede zaten 19200 gibi baudrate datalar çoktan gelmiş olur. Yani tek tek sorgu çekmek yerine bir buffer içersine doldurup sadece uygulama kontrol programını aktif ederek tüm datayı kontrol edebiliriz. Süre tutmassa tüm dataların gelmesi için usart fonksiyonunu değiştirip önce data uzunluğu gönderilerek çözüm sağlanabilir. Ona göre interrupt fonksiyonu kendini konfigure edebilir.

Akış olarak düşünürsek ;

/** Main **/
static void Usart_Control_Execute (void)
{
      if(g_control_usartFlag != SET)
      return;

      Array_Kontrol_Fonksiyonu();
      g_control_usartFlag = CLEAR;
}

void main (void)
{
....

Usart_Control_Execute ();
}

void Receive_Interrupt() // seri porttan data gelirse bu rutin çalışacak demektir.
{
     RX_Array = RX;   // Gelen datalar array içine kayıtlanacak
     g_control_usartFlag = SET;
}
Picproje Eğitim Gönüllüleri ~ MrDarK

z

#11
Diyelimki senin dediğin yöntemle kodlama yaptık.

Ana program içinde bir bölüm 1 sn den önce işini bitiremiyor olsun.

Interrupt rutini atıyorum 10 ms içinde 0x41 0x42 0x43 0x55 datalarını aldı ve buffera yazdı.

Ana program ancak  1 sn süren işini bitirdikten sonra buffera gelen verilerin neler olduğuna bakabilir ve gerekli tepkiyi verebilir.

Vermiş olduğum yazım şekli ana programa hiç müdahale etmez, ana programın ne yaptığı ile işini ne zaman bitireceği ile ilgilenmez fakat soruda sorulan işi layıkı ile yapar.
Bana e^st de diyebilirsiniz.   www.cncdesigner.com

MrDarK

Yapmaz demedik ki zaten hocam ;

Bence state machine mantığında önemli olan öncelikleri belirlemek sizin önceliğiniz datayı bi an önce yollamaksa tabiki bu işi interrupt içinde yapacaksınız.

Onun dışında state machine mantığı çoğu projede modüler kod tasarımını arttırıyor. Bu sayede istediğiniz işlemciye istediğiniz modülün taşımak için kodu çok bölmeden uygulayabiliyorsunuz. Ben o manada kontrol işini modülün kendi içinde denetlenmesini daha doğru buluyorum.
Picproje Eğitim Gönüllüleri ~ MrDarK

z

#13
Örneklere devam edelim.

Ledimiz ve 1 ms de bir int üreten timerımız ve seri portumuz var. Başkada bir donanım yok varsa da kullanımı yasak.

Seri portta 1 ms den daha kısa zamanda olmamak kaydıyla, ne zaman ve ne sıklıkta geleceği belli olmayan datalar ulaşmakta. Bu esnada ana programımız son derece önemli işlerle meşgul olmakta.  Aşağıda adı geçen X in ilk değeri 1 olsun.

Seri porttan;

0x01 gelirse derhal led yanacak ama X saniye sonra sönecek.
0x02 gelirse derhal led sönecek ama X saniye sonra yanacak.
0x03 gelirse derhal led tam tersi konuma geçecek (yanıyorsa sönecek, sönük ise yanacak) ama X saniye sonra ilk konumuna geri dönecek.
0x04 gelirse seri porttan gelecek ilk veri X değeri olarak atanacak.

Bu programı state machine mantığı kullanmadan yazabilecek varmı?
Bana e^st de diyebilirsiniz.   www.cncdesigner.com

bakar

Hocam  önceki örneğinizde  interrupt rutinini daha çabuk terketmek için switch  case   kullanmak daha mantıklı bir yazım şeklimidir?