STM32 Discovery ile ilk adımlar 4- Blinky

Şimdi tekrar “blinky” projemize dönüyoruz. Bu program STM32 Discovery üzerinde yüklü olarak geliyor. Yaptığı iş temel olarak kart üzerindeki LED’leri belirli aralıklarla yakıp söndürmek.
USER1 butonuna basıldığında LED’lerin yanma sıraları durum değiştiriyor:

1. Durumda sadece LED3 yanıp sönüyor

2. Durumda sadece LED4 yanıp sönüyor

3. durumda hem LED3 hem LED4 yanıp sönüyor.

Bu projede BUTON1, LED3, LED4 kaynakları kullanıldığından bunlara ait giriş çıkış birimlerinin ayarlarının yapılması gerekiyor.

Kart üzerindeki kaynakların bağlantıları şu şekilde:

LED3 -PORTC-9
LED4 -PORTC-8
BUTON1 – PORTA-0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/*----------------------------------------------------------------------------
  MAIN function
 *----------------------------------------------------------------------------*/
int main (void) {
 
  LED_Config();
  BTN_Config();                             
 
  SysTick_Config(SystemCoreClock / 100);       /* Setup SysTick Timer  (10ms) */
 
  while(1) {
 
    if (BTN_Pressed()) {
      ledVal += 1;
      if (ledVal > 3) ledVal = 1;
    }
 
    if (ledBlink == 1) {
      ledBlink  = 0;
	  ledOn^= 1;
	  if (ledOn == 1)
       LED_Out (ledVal);                      /* switch the LEDs on          */
      else
        LED_Out (0);                           /* switch the LEDs off         */    
 
	}
  }
 
}

main() fonksiyonu:

Bildiğiniz gibi bütün C programları main() fonksiyonuyla başlar. Burada da en başta LED_Config() ve BTN_Config() fonksiyonları çağırılıyor. Bu fonksiyonlarda LED’ler ve buton için port giriş ayarları yapılıyor. Daha sonra programımız sonsuz döngü içerisine giriyor (super loop). Burada BTN_Pressed() fonkiyonuyla her bir butona basılma durumunda ledVal durum değişkeni 1’er artırılarak, 1,2,3 değerlerinde dolaşıyor. Bu durumları en başta listeledik.

ledBlink değişkeni her 1 olduğunda (System Handler fonksiyonu içerisinde yapılıyor) LED çıkışlarına dönüşümlü olarak 1 veya 0 değeri veriliyor. ledval değişkeninin değerine göre LED’lerin hangilerinin yanacağına LED_out fonksiyonunda karar veriliyor.

1
2
3
#define LED_BLINK_RATE    10             /* rate = LED_BLINK_RATE * systick   */
#define LED_NUM            2             /* Number of user LEDs               */
const unsigned long led_mask[] = { 1UL<<8, 1UL<<9 }
1
2
3
4
5
6
7
/*----------------------------------------------------------------------------
  Switch on LEDs
 *----------------------------------------------------------------------------*/
__INLINE static void LED_On (uint32_t num) {
 
  GPIOC->BSRR = led_mask[num];                 /* Turn On  LED                */
}
1
2
3
4
5
6
7
/*----------------------------------------------------------------------------
  Switch off LEDs
 *----------------------------------------------------------------------------*/
__INLINE static void LED_Off (uint32_t num) {
 
  GPIOC->BRR  = led_mask[num];                 /* Turn Off LED                */
}

Daha önceki yazımda portlara ait BRR (bit reset register) kaydedicisi ile o porta ait pinlerin durumlarını değiştirebileceğimizi görmüştük. Burada LED_off fonksiyonuna verilen parametre ile ilgili pine ait çıkış değeri sıfırlanıyor. num değerinin aldığı 0 veya 1 değerine göre LED3 veya LED4 söndürülüyor.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/*----------------------------------------------------------------------------
  Function that outputs value to LEDs
 *----------------------------------------------------------------------------*/
void LED_Out(uint32_t value) {
  int i;
 
  for (i = 0; i < LED_NUM; i++) {
    if (value & (1<<i)) {
      LED_On (i);
    } else {
      LED_Off(i);
    }
  }
}

Bu fonksiyonla LED’lerin yakılıp söndürülme durumları kontrol ediliyor. Fonksiyona parametre olarak verilen “value” değerine göre 3 durumdan biri gerçekleşiyor:

value == 1 ise LED3 yakılıyor LED4 söndürülüyor
value == 2 ise LED4 yakılıyor LED3 söndürülüyor
value == 3 ise LED3 ve LED4 yakılıyor

Aslında programda yapılmak istenen işe göre çok karmaşık bir yapı kurulduğunu söylemek mümkün. Aynı işi başka şekilde çok daha az kod yazarak da gerçekleştirebilirsiniz.

1
2
3
4
5
6
7
8
9
/*----------------------------------------------------------------------------
  configure Button pins
 *----------------------------------------------------------------------------*/
__INLINE static void BTN_Config(void) {
 
  RCC->APB2ENR |=  1<<2;                       /* Enable GPIOA clock          */
  GPIOA->CRL   &= 0xFFFFFFF0;                  /* Configure the GPIO for BTNs */
  GPIOA->CRL   |= 0x00000008;                   /* Configure the GPIO for BTNs */
}

Burada BUTON1 pinine ait ayarlar yapılıyor. Yine pinin bulunduğu GPIOA portunun saat sinyali etkinleştirilip porta ait CRL kaydedisine butonu giriş olarak ayarlayacak değer atanıyor.

1
2
3
4
5
6
7
8
/*----------------------------------------------------------------------------
  Read Button pins
 *----------------------------------------------------------------------------*/
__INLINE static uint32_t BTN_Get(void) {
 
 return (GPIOA->IDR & 0x0001);
 
}

Bu fonksiyon sadece GPIOA portunun 1. pininin durumunu döndürüyor. Böylelikle butonun basılıp basılmadığını anlıyoruz.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/*----------------------------------------------------------------------------
  USER1Pressed
  check if USER1 is pressed (unbounced).
 *----------------------------------------------------------------------------*/
uint32_t BTN_Pressed (void) {
  static uint32_t USER1KeyCount = 0, USER1KeyPressed = 0;
 
  if (USER1KeyPressed) {
    if ((BTN_Get() == 0 )) {                   /* Check if USER1 not pressed  */
      if (USER1KeyCount < UNBOUNCE_CNT) USER1KeyCount++;
      else {
        USER1KeyPressed = 0;
        USER1KeyCount = 0;
      }
    }
  }
  else {
    if (!(BTN_Get() == 0 ))  {                 /* Check if USER1 pressed      */
      if (USER1KeyCount < UNBOUNCE_CNT) USER1KeyCount++;
      else {
        USER1KeyPressed = 1;
        USER1KeyCount = 0;
		return (1);
      }
    }
  }
  return (0);
}

STM32 Discovery üzerinde bulunan mekanik butonlarda “bouncing” denilen bir durum gerçekleşmektedir. Butona basıldığında çok kısa bir süre içerisinde mekanik kontaklar 1 ve 0 değeri arasında gidip gelmektedir. Bu durumda butona tam basıldığını anlamak için kısa bir bekleme süresi konulması gerekiyor. Bu işleme de “debouncing”* deniliyor. BTN_Pressed fonksiyonunda yapılan da işte tam olarak bu. Fonksiyon başında tanımlanan USER1KeyCount ve USER1KeyPressed değişkenleri fonksiyonun iç durumunun fonksiyondan çıkıldığında da saklanmasını sağlıyor. Eğer fonksiyon ilk çağırıldığında henüz butona ilk defa basılmışsa (USER1KeyPressed = 0 ise) butonun durumu tekrar okunuyor ( if ((BTN_Get() == 0 )) ). Bu arada butona basıldığında girişe 0, basılmadığında ise 1 uygulandığını belirteyim. Eğer butona girişinden sıfır değeri okunuyorsa USER1KeyCount değeri bir artırılıyor. Her fonksiyon girişinde aynı işlem tekrarlanarak USER1KeyCount değerinin belirli bir eşik değerine gelmesi sağlanarak (UNBOUNCE_CNT) gecikme oluşturuluyor. Bu süre içerisinde buton mekanik olarak sönümlenmiş oluyor ve kesin olarak butona basılıp basılmadığı anlaşılıyor. “Debouncing” işlemi yapılmadığı takdirde durumlar arası geçişler çok hızlı olacaktır. Fonksiyon sonuç olarak BUTONA BASILDI (1) veya BUTONA BASILMADI (0) değerini döndürüyor.

1
2
3
4
5
6
7
8
9
10
11
12
13
/*----------------------------------------------------------------------------
/*----------------------------------------------------------------------------
  SysTick_Handler
 *----------------------------------------------------------------------------*/
void SysTick_Handler (void) {
  static uint32_t ticks;
 
  if (ticks++ >= LED_BLINK_RATE) {
    ticks    = 0;
    ledBlink = 1;
  }
 
}

SysTick_Handler (System Tick Handler ) fonksiyonu aslında bir “kesme hizmet fonksiyonu”. Yani System Tick kesmesi oluştuğunda bu fonksiyon çağırılıyor. Şimdilik STM32 Kesmelerine çok girmeden basitçe açıklayayım: System Tick kesmesi ile belirli zaman aralıklarında çalışması gereken şeyler için kullanılabilen bir nevi “timer/ zamanlayıcı”. Daha sonra göreceğimiz main() fonksiyonunda System Tick için 10 ms’lik bir periyod belirleniyor. Yani SysTick_Handler fonksiyonu her bir 10 ms’de çağırılıyor. Dolayısıyla bunu zamanla ilgili işlemlerde kullanabiliyoruz.

Burada fonksiyon her çağırıldığında ticks değişkeni 1 artırılıyor. Ticks değişkeni LED_BLINK_RATE değerine ulaştığında (buradaki değeri 10) ticks değeri sıfırlanıp ledBlink değişkenine 1 değeri atanıyor. Bu değer de ana fonksiyon içerisinde led yakıp söndürme işleminde kullanılıyor. LED’lerin yanıp sönme hızını LED_BLINK_RATE sabitiyle kontrol edebiliyoruz.

Basit bir LED yakıp söndüren programın bile bu kadar karmaşık olması gözünüzü korkutmasın. Burada yapılan bütün işlemleri tam olarak anladığınızda C ile gömülü yazılım geliştirmenin temel konularını kavramışsınız demektir. Unutmayın “HERŞEY BİR LED’İ YAKMAKLA BAŞLAR”

*Jack Ganssle’ın “Switch Debouncing” konusuyla ilgili makalesini okumanızı tavsiye ederim: http://www.ganssle.com/debouncing.htm

Bir Cevap Yazın

E-posta hesabınız yayımlanmayacak.