enter_critical_section();

Başlatan kantirici, 06 Eylül 2016, 11:36:16

kantirici

Merhaba arkadaşlar,

C dili ile mcu programlarken çeşitli kesmelerin aktif olduğunu varsayalım. Bu kesme alt programlarında çeşitli değişkenlere değerler atansın. Örneğin software timer için bir timer tick, kesme anında dışarıdan sıcaklık değerini alıp saklayan bir temp değişkeni ve bir kaç adet kesme içerisinde set/reset yapılan durum değişkeni olsun.

Programımızda timer tick değerine göre software timerlar olsun ve timer tick değerine göre bu soft timerların taşma durumlar ve o anki değerleri program akışında okunsun, sıfırlansın.

Sıcaklık değeri ile ilgili olarak; program sürekli olarak bu değeri okuyup belirli değerler için bazı işlemler yapsın.

Herhangi bir I/O yada bir çevre birimden etkilenen durum değişkenleri de yine döngü içinde kontrol edilerek işlemler yapılsın ve set/reset yapılsın.


Platformun 8-16-32 bit olma durumunda göre değişkenlerin 8-16-32 bit olarak değişiklik gösterdiği durumda göz önüne alarak, Sorum şu;

Bu işlemler yapılırken hangi durumlar kritik bölüm olarak değerlendirilmeli ?

Elbette kullanılan platform, çevre birimler, algoritma,  haberleşme kanallarına v.s göre bu durum çok çeşitlilik gösterebilir ama tecrübeli arkadaşların cevapları ile bu konular hakkında biraz konuşmak istiyorum.

t2

#1
Pc programlamada olduğu gibi, enter_critical_section wait for single object, mutex gibi yöntemlerin benzeri MCU için uyarlanmştır.
Hazır yazılım bileşenleri, şablonları vardır.

Bunlar kullanılmayacak ise kendimiz yapabiliriz. Çok katmanlı yazılım ile ayrı iş parçacıkları tarafından ortak kullanılacak kaynaklar varsa,  benzer yöntemler kullanılır.

Örneğin dizi içerisinde 100 bayt olsun.
mcu ana döngüsünde, state machine çalışırken, fırsat buldukça bir rutin çalışıyor bu baytları sırayla işleyerek yeni bir  diziye atıyor olsun.
fakat bir interrupt rutini bu diziyi daha işlenmeden veya işleme bitmeden değiştiriyor ise sorun çıkacaktır.

bunun yerine, 2 dizi tanımlanır. biri işleniyorken diğeri doldurulur. vs.. veya bir bayrak kontrol edilir. "Dizi şu an işleniyor bu diziye dokunma !"  manasında  bir değişken tanımlanır. bunu gören interrupt rutini başka diziye yönelir. 



void main() {
  int state = 0; // wait for button
  int floor = getLimit(); // returns 0, 1, 2, or 3 for whichever limit switch is currently tripped (0 if between floors)
  int button = 0; // no button pressed
  int direction = 0; // motor off

  loop {
    switch (state) {
      case 1: // get direction
        direction = getDirection(button, floor);      
        state = 2;
        break;
      case 2: // move in direction
        motorOn(direction);
        state = 3;
        break;
      case 3: // check limit
        if (atLimit(button)) {
          state = 0;
          floor = button;
        }
        break;
      default:
        motorOn(direction);
        button = getButton(); // returns 0, 1, 2, or 3 for whichever button is pressed
        state = (button > 0) ? 1 : 0;
    }
  }
}

int getDirection(int floor_wanted, int floor_on) {
  int direction = 0;

  if (floor_on > 0) {
    if (floor_on > floor_wanted) {
      direction = 1; // down
    }
    else if (floor_on < floor_wanted) {
      direction = 2; // up
    }
  }
  else {
    direction = 1; // down
  }

  return (direction);
}

void motorOn(int direction) {
  switch (direction) {
    case 1:
      // motor on "down"
      break;
    case 2:
      // motor on "up"
      break;
    default:
      // motor off/stop
  }
}

int atLimit(int limit) {
  int currLimit = getLimit(); // returns 0, 1, 2, or 3 for whichever limit switch is tripped
  return ((currLimit == limit) ? 1 : 0);
}

kantirici

cevaplar için teşekkür ederim. Aslında olayın biraz daha temelindeki olaylardan bahsetmek istiyorum. Şuradaki doküman tamda bahsettiğim konuya giriş niteliğinde.

https://courses.cs.washington.edu/courses/cse466/02au/Lectures/Interrupts-II.pdf



kantirici

Hocam tamda bunlardan bahsetmek ve başlamak istemiştim. Bu örnekte kolayca bu durum görülebiliyor. Daha çeşitlendirmek istiyorum bu tarz örnekleri. Bu durumlar neler olabilir.

Mesela bir ring buffer da bufferda veri olup olmadığını kontrol ederken, bufferdan veri çekerken kuyruk ve baş değişkenlerinin durumu v.s. Yani ring buffer ile ilgili tüm işlemlerde int. disable yapmak gerekiyor..? Çünkü hepsi birbirine bağlı. Bufferdan veri çektik ve baş(head) bir artmadan uarttan kesme geldi.

Bu gibi bir durumu genelleştirirsek kesme fonksiyonunda kullanılan bir değişken nerede kullanılırsa kullanılsın orada int. disable etmek gerektiği sonucu çıkıyor değil mi ? Böyle düşünüyorum ama bu defa da sürekli int. enable/disable yapılması bir sorun .. ? Bu soruna nasıl bir çözüm bulunmuş.

Yuunus

@gerbay Arm icin konusursak hic task kullanmadan  bir while() icerisinde "int tipinde" bir degiskene deger atamasi yapiyor olalim yine yukaridaki durum gecerli degil midir? Yani interrupt aktif oldugunda yine islemi rastgele bir anda kesip olumsuz bir durum olusmayacak mi? Yanlis mi anladim.

kantirici

#5
@gerbay teşekkürler hocam, ellerinize sağlık.

Olayı biraz daha rtos özelinden standart mikrokontroller moda kaydırsak daha anlaşılır olacağını düşünüyorum, RTOS kullanmıyorum. Olayın temelini aslında anladık.

@digiman in verdiği örnekten gidersek, mcu sonsuz döngüde 5 adet process alt programını sırayla kontrol etsin. Bu alt programların birinde interrupt rutini içerisinde tetiklenen bir flag i kontrol edilsin.

Tam kontrol sırasında,  ( if(flag == next_state) ) kesmeleri kapatmamız gerekiyor. Eğer diğer 4 process de bir şeyler bekliyorsa mcu sürekli bu sorguları yapacak ve yine sürekli int. enable/disable durumu yaşanacak.

Böyle bir senaryo için nasıl bir yöntem izlenmeli ?



uint8_t flag8a=0,flag8b=0;  uint16_t flag16=0;  uint32_t flag32=0
void high_int_func()
{
   if(aa_int_flag)
   {
    flag8b = 1;
   }
}

void low_int_func()
{
   if(timer_int_flag)
   {
    flag8a = 1;
   }

   if(timer2_int_flag)
   {
    flag32++;
   }
}

[code]
void a_process()
{
   
   di();
   if(flaga)
   {
e();
    ......
     }
   e();

}

void b_process()
{
   
   di();
   if(flaga)
   {
e();
    ......
     }
   e();

}

void x_process()
{
   
   di();
   if(flag32 > 5000)
   {
e();
.......
   flag32 =0;
     }
   e();

}

int main()
{
   mcu_init();
   aaa();
   bbb();


while(1)
{

   a_process();
   b_process();
   ....
   x_process();
}
}

X-Fi

#6
@kantirici hocam aslında RTOS olmadan bunlar anlatılamaz. Çünkü RTOS algoritma ve gömülü sistemlerin açıklarını kapatmaya yarayan etkinliğini arttırıp işleri koylaştırmak amacıyla tasarlanmış sistemlerdir.

Sorduğunuz sorunun çözümü ister istemez OS tabanına kayıyor. Anlatılan mekanizmalar yıllardır kullanılan ve geliştirilen yapılardır. Bunları isterseniz kendiniz yazın isterseniz evrensel dilde öğrenip uygulayın birşey değişmez ancak ikinci durum daha doğrudur.

Saygılarımla iyi çalışmalar.
http://www.coskunergan.dev/    (Yürümekle varılmaz, lakin varanlar yürüyenlerdir.)

z

RTOSla doğrudan alakası yok, bunlar en temel şeyler.

Ana program ve interrupt program aynı değişken yada değişkenler üzerinde yazma/okuma yapıyorsa bazı şeylere dikkat etmek gerekir.

Örneğin kullanılan işlemci 8 bit ise ve programın bir bölümünde 16 bit verileri yazıp okuyacaksa 16 bit veriye tek hamlede erişilemeyeceğinden 2 adet 8 bitlik veri işlemi yapılır. 16 bit verinin her 8 li parçasına H ve L çifti dersek;

Ana program HL çiftinden ilkini okurken interrupt gelirse int rutini HL çiftine yeni değer yazarsa ana program kaldığı yerden devam ederken HL çiftinin birini int den önce diğerini ise intten sonra okuduğu için yanlış veri ile çalışacak demektir.

Örneğin HL=0x10FF iken

H 10 olarak okunmuş iken interrupt oluşursa; int rutini örneğin HL çiftini 1 artırırsa HL=0x1100 olmasına rağmen ana program kaldığı yerden devam ederken HL=0x1000 olarak okur.

Olayın en temelindeki sorun bu.

Bu sorunu aşmanın bir kaç yolu var

Ana program HL çiftini okumaya niyetlendiğinde int disable yaparak HL çiftini sorunsuzca okur. Okuma ardından int enable yapar.

Bir diğer yol ise HL çiftini okumadan önce bir flağı set eder. Ardından HL çiftini okur ve flağı sıfırlar.
Int rutini HL çiftini değiştirmeden önce flağın 1 mi yoksa sıfırmı olduğuna bakar. Eğer 1 ise HL üzerinde değişiklik yapmaz 0 ise yapar.

Sonuçta böylesine temel konulara bakılmadan RTOS ortamlarına geçilirse karşımıza mutex, semaphore gibi tabirler çıkmaya başlar.
Bana e^st de diyebilirsiniz.   www.cncdesigner.com

t2

#8
pc programlamada Thread kontrolü esnasında waitforsingle object denen  özellik ile ilgili  tespit ettiğim önemli bir konu var. belki birine lazım olur.

Örneklerde belirtildiği gibi olmuyor, waitforsingleobject  aynen beklendiği gibi çalışmıyor sorun çıkıyordu. 

Bekleme için Threadlerin aynı  CPU çekirdeğinde çalışması gerekiyormuş. bunu sağlamak için  kod var.

Bekleme yapacak thread, 1 nolu çekirdekte çalışacak şekilde ayarlayınca sorun kalmadı.

Farklı  uygulamalar içerisinde de olsa thread çalışırken 1 nolu çekirdek seçiliyor. 

http://stackoverflow.com/questions/5261182/how-do-i-properly-use-the-waitforsingleobject-method-to-wait-for-an-external-pro

http://stackoverflow.com/questions/9078838/setprocessaffinitymask-select-more-than-one-processor


X-Fi

#9
@z hocam söylediğiniz durum bana C deki volatile tanımı kullanarak çözülebilir gibi geldi. Bunu denediniz mi bilmiyorum. Ayrıca handle edilecek veriyi bir task da mutex kontrolünde tutup, xSemaphoreGiveFromISR ile taskı çalıştırıp etişimi sağlamak bence bu sorunun kesin çözümü olur diye düşünüyorum. Sizin söylediğiniz de buna benzer bir çözüm. ister flag ile yapın isterseniz semaphore ile bişey değişmiyecektir. Sadece hangisini öğrendiğiniz önemli. Bişeyleri kitabına uygun yapmak daha iyidir tabi.

Saygılarımla iyi çalışmalar.

Not: flag ile veriyi koruma altın aldınız diyelim, interrupt geldi baktı veri erişime kapalı. Bekleyecek hali yok çıktı gitti o iş yapılamadı. Bu böyle olmaz dediniz interruptları kaybetmemek için kaç tane int. geldi saydırayım ve o işleri erişim açılınca yaptırayım istediniz. Burada kuyruk yazdınız. Sonra bu kuyrugu takip edeyimde işler çok aksamadan yapılsın dediniz oldumu size switch context. Bir flag ile başladığınız iş nerelere dallandı. Baştan en doğrusunu öğrenip uygulamak teknolojiyi baştan keşfetmekten iyidir.
http://www.coskunergan.dev/    (Yürümekle varılmaz, lakin varanlar yürüyenlerdir.)

z

Sorunun "Volatile" çözümünü anlamadım. Nasıl olacak bu iş?

Flagmı kullanılacak, int mı açılıp kapanacak yoksa başka şeyler mi yapılacak artık o uygulamaya göre değişir. Fakat işlemci ile çalışan adam bu temel bilgileri geçiştirip RTOS'a falan atlarsa başına iş alır.


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

X-Fi

#11
@z hocam normalde bir register hem interruptda hem ana döngüde erişilecek ise(örneğin flag gibi) volatile static tanımlıyoruz ki her okuma/yazma direk hafıza adresine yapılsın diye. Bu durum kaynağın bozulma durumunu engelliyor. 8 bit işlemcinin 16 bit veri işlemesi veri transferi demek oluyor hiç denemedim, paket veriyi interruptdan koruyabilir de, korumayadabilir de. Compiler a göre değişebilir diye düşünüyorum. 32bit çalıştığım için volatile kaynağı korumak için yeterli oluyor.

iyi çalışmalar.
http://www.coskunergan.dev/    (Yürümekle varılmaz, lakin varanlar yürüyenlerdir.)

z

Yok volatile burda hiç bir işe yaramaz.

Sonuçta işlemci 8 bit ve birden fazla byte üzerine yazmaktan bahsediyoruz. 32Bit işlemcilerde de aynı. Bu kez 64 bitlik veriyi ya da bir structerı manüple edeceksek gene aynı risk var.

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

yamak

Hocam cpu nu tek seferde işleyebileceği data tiplerin de bile risk var.Mesela @gerbay hocamın verdiği örnekten konuşacak olursak
increment PROC
        LDR      r0,|L1.16|

        LDR      r1,[r0,#0]  ; x
        ADDS     r1,r1,#1
        STR      r1,[r0,#0]  ; x

        LDR      r1,[r0,#4]  ; y
        ADDS     r1,r1,#1
        STR      r1,[r0,#4]  ; y

        BX       lr
        ENDP

Artırma işlemi 3 instruction da yapılıyor.Mesela ilk instruction dan sonra interrupt gelir sonra interrupta değişkenin değeri artırılıp belleğe yazılıp interruptan çıkıldıktan sonra değişkenin eski değeri artırılıp belleğe yazılır ve data bozulumuş olur.

z

#14
Elbette öyle. Ana program ve interrupt programı  aynı değişken üzerinde oku + değiştir + yaz işlemi yapıyorsa ve tüm bu işlemleri tek komut olarak yapamıyorsa muhakkak koruma yapmak lazım.

Bazı işlemciler memory üzerinde tek komutla (int ile bölünemez) değişiklik yapabiliyor. İşlemcinin bit uzunluğunu aşan örnek üzerinden açıklama yapmamın  sebebi buydu.

Neyseki ARM işlemcilerde LDR STR komutları ile sınırlı değiliz ve LDREX, STREX ve CLREX gibi kritik durumlar için bizi uyaran komut setine de sahipiz.
Bana e^st de diyebilirsiniz.   www.cncdesigner.com