STM32F4’te Timer Interrupt ile Alive Led Uygulaması

Alive LED kavramını Türkçe’ye Hayat Işığı diye tercüme etmeyi uygun buldum. Adından da anlaşılabileceği gibi Alive LED kullanmanın amacı işlemci içerisine yüklenen kodun sağlıklı çalışıp, çalışmadığını gözlemleyebilmektir. Eğer bu ışık 1 saniye boyunca yanık bir saniye boyunca da sönük olarak Toggle oluyorsa, işlemci içerisine yüklenilen kodun her hangi bir sıkıntı yaşamadığının göstergesidir. Yani işlemcimiz hayattadır ve sağlıklı bir şekilde çalışıyor demektir.

Alive LED uygulaması için Timer2 Interrupt kullandım. Timer2’nin her saniyede bir Interrupt oluşturması için system_stm32f4xx.c dosyasında 84 MHz‘e ayarlanmış olması gerekir. system_stm32f4xx.c dosyasının nasıl ayarlanması ve oluşturulması gerektiğini önceki yazılarımda açıklamıştım. Timer için yine detaylı bilgiyi de PWM üretimiyle ilgili yazımda vermiştim. Interrupt ile ilgili bilgilere de yine önceki yazılarımdan ulaşılabilir.

Timer için olmazsa olmaz, Period ve Prescaler ayarlarının yapılmasıdır. Bu uygulamada her saniyede bir kez LED Toggle olacağı için 1 Hz‘lik bir Timer üretmemiz gerekiyor. Bunun için yine aynı formülü kullanacağız.

freq formula

Needed Frequency = 1 Hz,

Timer2 Clock Frequency = 84 MHz,

Prescaler = 84 – 1, (Timer PWM ile ilgili yazımda sebebini detaylı şekilde açıkladım.)

Period = 1000000 – 1 .

Yukarıdaki değerleri kullanarak Timer ayarlamasını yapmamız gerekir.

Not: system_stm32f4xx.c dosyasının ayarlarının doğru olarak yapılması çok önemlidir. ST’nin örnek kodlarında bu dosya hatalı olduğunu düşündüğüm için önceden açıkladığım şekilde mutlaka bu dosya tekrar oluşturulması çok önemlidir. Çünkü Timer uygulamalarının gerçek zamanla birbirini tutabilmesi için hesaplamaların Clock ayarlarına göre yapılması mecburidir. Bu kısma tekrar dikkat çekmek istedim.

Keil uVision’ın Project sekmesi aşağıdaki gibi olmalıdır. Timer için stm32f4xx_tim.c, Interrupt için misc.c ve  LED için stm32f4xx_gpio.c dosyalarını StdPeriph_Driver klasörüne dahil edilmesi gerekir.

project for aliveled

main.c dosyasında yazdığım kod aşağıdaki gibidir.

  • LED_D14_Config() fonksiyonu kırmızı LED için gerekli GPIO output (çıkış) olarak ayarlarının yapılması içindir.
  • Timer2_Interrupt_Config() fonksiyonu Timer2’nin Period ve Prescaler ayarlarının ve Interrupt’ını Enable yapmak (aktif hale) getirmek için yazılmıştır.
  • TIM2_IRQn_NVIC_Config() fonksiyonu Timer2’nin Interrupt Handler’ını oluşturmak ve Priority’lerini ayarlamak için yazılmıştır. Priority değerleri 0’dır. Alive LED için kullanacağımız Interrupt’ın önceliği diğer tüm Interrupt’lardan daha öncelikli olması çok önemlidir. Çünkü sistemin kusursuz çalıştığının göstergesidir.

stm32f4xx_it.c dosyasına aşağıdaki gibi gerekli Interrupt Handler kodumuzu da yazdık. Önce Interrupt oluştuğu if blokuyla kontrol ediliyor. Sonrasında yapılmasını istediğimiz komut olan LED’in Toggle yapılması işlemi için gerekli kod satırı bulunuyor. En son olarak da bütün Interrupt Handler (IRQ) ‘ların içinde bulunması gereken xxx_ClearITPendingBit fonksiyonu ile Interrupt Handler vazifesinin bittiğini ve işlemcinin tekrar bu Peripheral için Interrupt oluşmasına hazır olduğunu bildiriyoruz.

stm32f4xx_it.h dosyasına da Timer2 için gerekli olan Interrupt Handler (IRQ) fonksiyonunun Prototype’ını yerleştiriyoruz. Bu zorunlu değildir ama yapılması daha uygundur. Yapmazsanız da program çalışır.

Bu projeyi biraz daha geliştirmek istersek, örneğin şöyle bir şeyler deneyebiliriz. Timer’ın 1 Hz yerine 1 KHz olarak ayarlayarak her 1 ms’de bir Interrupt oluşmasını sağlayabiliriz. Bu şekilde yaparsak LED’i sürekli yandığını görürüz. Çünkü gözümüzün görme frekansı 50 Hz’dir ve fazlasını ayırt edemez. Eğer bu Interrupt Handler içinde LED’in yine 1 Hz’de çalışmasını istersek. 1000’e kadar sayan bir integer counter (tamsayı sayaç) tanımlarız. Bu counter 0’dan saymaya başlar. 1000’e ulaştığını if blok ile kontrol ederiz. Bu if blokunun içinde LED’i Toggle yaparız ve counter’ı 0’larız. Bu sayede 1 KHz ihtiyaç duyduğumuz başka bir işlem için fazladan bir Timer ziyan etmiş olmayız. Hem Alive LED uygulamasını hem de başka bir uygulamayı böyle bir metotla sağlayabiliriz.

Daha iyi anlaşabilmesi için kod ile de açıklamak gerekirse,

Ayrıca açıklamak istediğim bir konu da Interrupt’ların önceliğini program içinde değiştirmek için bir fonksiyon var onun kullanımını göstermek isterim.

Yukarıdaki kod sonrasında öncelik sıralaması  TIM2_IRQn > USART2_IRQn > EXTI0_IRQn şeklidedir.

İnşallah faydalı olmuştur. Herkese iyi çalışmalar dilerim…

6 thoughts on “STM32F4’te Timer Interrupt ile Alive Led Uygulaması

  1. Merhaba
    TIM2 ve TIM5′i aynı anda(1s) kesmeye girecek şekilde birlikte kullanmak istiyorum ama kesme önceliğini TIM5′e vermek istiyorum. Debug ile kontrol ediyorum ilk önce TIM2_IRQHandler(), sonra TIM5_IRQHandler() gerçekleştiriyor. Öncelik kısmını ayarladığım kod parçası:

    NVIC_InitTypeDef NVIC_InitStructure;

    NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);

    NVIC_InitStructure.NVIC_IRQChannel = TIM5_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);

    • Merhaba Kaan,
      Bu sorunla ben de karşılaşmıştım. Henüz denemedim ama sıkıntı iki sebepten olabilir.

      Birinci çözüm için NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4); kodunu int main() içinde ilk satıra yaz. Bir Debug yap bakalım olacak mı? Eğer olmazsa
      İkinci çözüm için de NVIC ve GPIO ayarlarının yaptıktan sonra
      NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
      NVIC_SetPriority(TIM5_IRQn,0);
      NVIC_SetPriority(TIM2_IRQn,1);

      kodunu ekle öyle Debug etmeyi dene.
      İkisini de deneyip hangisinin çalıştığını paylaşırsan sevinirim.
      Yazarken aklıma geldi ikisi de senin istediğin sonucu vermezse sebebi Interrupt Enable zamanlarının farklı olması olabilir. Eğer Timer2 Interrupt, Timer5 Interrupt’ından önce Enable edilmişse, haliyle Timer2 Interrupt, Timer5 Interrupt’ından önce Interrupt’a girer. Fakat bu süreler çok kısa süreler olduğu için biz fark edemeyiz. Bence sıkıntı bu. Yine de en garanti çözüm yukarıdaki ikinci çözüm ve ek olarak Timer5 Interrupt’ının Timer2 Interrupt’ından önce Enable etmek.
      İyi çalışmalar…

      Not: NVIC_PriorityGroupConfig(…) ve NVIC_PriorityGroup hakkında daha detaylı bilgiye Standart Peripheral Library’deki misc.c dosyasından ulaşabilirsin. NVIC_PriorityGroup_4 ayarı Subpriority’leri devre dışı bırakır ve 4-bitlik (16 seviye, 0-15) Preemption Priority kullanımına olanak sağlar. Eğer NVIC_PriorityGroupConfig(…) fonksiyonunu kullanmazsak, NVIC_PriorityGroup_2 aktif edilir. STM32F4′te External (Harici) Interrupt ile User Button Uygulaması ve Interrupt’ta Priority (Öncelik) Kavramı yazımda Priority’lerle ilgili bilgiler vermiştim. Bu yazıya da bakabilirsiniz.

  2. İlk haliyle değiştirmeden sadece TIM5 Enable’ı, TIM2 Enable’ın önüne aldım. Debug yaptığımda yine ilk olarak TIM2’ye girdi.
    TIM5 TIM2 Enable sırasını değiştirmeden aşağıdaki komutları ekledim
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
    NVIC_SetPriority(TIM5_IRQn,0);
    NVIC_SetPriority(TIM2_IRQn,1);
    Yine TIM2 kesmesine girdi ilk.

    void TIM_IRQn_NVIC_Config(void)
    {
    NVIC_InitTypeDef NVIC_InitStructure;

    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
    NVIC_SetPriority(TIM5_IRQn,0);
    NVIC_SetPriority(TIM2_IRQn,1);

    NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
    // NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);

    NVIC_InitStructure.NVIC_IRQChannel = TIM5_IRQn;
    // NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    // NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
    }

    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4); komutunun 2 olanından farkı nedir, gruplandırma artınca daha çok kesme kullanmamıza olanak mı sağlıyor?

    Bir sorum daha olucak USART Kesmesi ile ADC Kesmesinde de aynı sorun olur mu ?

    Teşekkürler…

    • Kusura bakma bu mesaj gözümden kaçmış bu sebeple biraz geciktim. Öncelikle şu konuya açıklık getirelim. Bu durum priority (öncelikle) alakalı değil aynı sayıya aynı frekansta sayan iki Tİmer (sayacın) aynı anda o sayıya ulaşması imkansız. İkimiz aynı hızda 100’e kadar saymaya başlasak eğer sen benden önce sayamaya başlarsan benden önce 100’e ulaşırsın. Timer Init ayarları yapılırken, eğer her ikisi de özdeş iki Timer ise Timer5’i Timer2’den önce ENABLE edersen, Timer5 daha önce Interrupt Handler’a girer. Init olarak bahsettiğin kod bu satırları içeriyor:
      /* TIM2 enable interrupt */
      TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
      /* TIM2 enable counter */
      TIM_Cmd(TIM2, ENABLE);

      Timer5 için olanı Timer2’den daha önce çağır. Eğer Timer2 ve Timer5 için ayrı iki fonksiyon kullandıysan,
      Timer5_Interrupt_Config(Period,Prescaler);
      Timer2_Interrupt_Config(Period,Prescaler);

      yukarıdaki sırayla yaz.
      Cevabında yazdığın TIM_IRQn_NVIC_Config fonksiyonunun başına Priority ayarlarını koymanın bir anlamı olmaz. Onları mainde TIM_IRQn_NVIC_Config fonksiyonunun ardından çağır. Hatta NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4); fonksiyonunu int main’in içine ilk satırdan koyman daha iyi olur. Daha NVIC IRQ tanımı yapmadan onların önceliklerini ayarlaman bir işe yaramaz. Eğer bu işlemler doğru yapıldığı taktirde Timer5 Timer2’den daha önceliklidir. Fakat Timer2’nin daha önce Interrupt’a uğramasının sebebi öncelik değil daha önce saymaya başlamasıdır. Bu haliyle Timer2 nin içine örneğin 1 sn’lik Delay(bekleme) koyarsan Timer2 interrupt’ta iken Timer5 intterrupt’a girer ve program Timer2 IRQ’dan çıkar Timer5 IRQ’yu yürütür. Timer5 IRQ tamamlanınca Timer2 IRQ yarım kaldığı yerden devam eder. Çünkü Timer5 Timer2’den daha önceliklidir. Her ikisi de bitince eğer Interrupt oluşmamışsa main’in içindeki while(1) bloğunun içindeki kod tekrar interrupt oluşuncaya kadar yürütülmeye kaldığı yerden devam eder.
      Burada 168 MHz’lik bir işlemciden bahsediyoruz. Eğer Timer2 daha önce ENABLE olduysa 0.1 nanosaniye sonra dahi Timer5 ENABLE olursa olsun Timer2 0.1 nanosaniye daha önce sayma işlemini bitireceği için Timer2 IRQ daha önce oluşur.
      NVIC_PriorityGroup_4’de subpriority yok 16 level pre-emption priority mevcut. NVIC_PriorityGroup_4 ise 4 seviye pre-emption priority ve 4 seviye subpriority mevcut. Detaylı bilgiyi misc.c dosyasında bulabilirsin.
      Son soruna gelirsek, bu işlemcinin sorunu değil yazılan koddaki mantık yanlışlığı. Bahsettiğim ayarları yaparsan hiç bir sorun olmaz inşallah.
      Yardımcı olabildiysem ne mutlu bana. Ben teşekkür ederim. 🙂

Leave a Comment

%d bloggers like this: