Software Architecture etiketine sahip kayıtlar gösteriliyor. Tüm kayıtları göster
Software Architecture etiketine sahip kayıtlar gösteriliyor. Tüm kayıtları göster

28 Ekim 2025 Salı

C Dilinde Hata Durumları Nasıl Yönetilmelidir? Debouncer Yapısı Nedir?

C Dilinde Hata Durumları Nasıl Yönetilmelidir? Debouncer Yapısı Nedir?

Merhaba! 😊 Bu yazıda iki kritik konuyu pratik bir mühendislik akışıyla ele alacağız: (1) C dilinde hata yönetimi ve (2) mekanik buton/switch girişleri için debouncer (zıplama önleme). Amaç; üretim ortamında güvenilir, izlenebilir ve bakımı kolay bir kod tabanı kurarken giriş sinyallerini de kararlı, tek olaya indirgenmiş şekilde üst katmana aktarmak. Yazının sonunda err_t hata kodları, cleanup/goto, assert + log halkası, Watchdog/safe-state ve SysTick tabanlı debouncer için hazır kopyala–yapıştır C kodları bulacaksınız. 🚀

İçindekiler

Neden Hata Yönetimi ve Debounce?

Gömülü sistemler sahada gürültü, kenar durumlar, zaman aşımları ve kullanıcı etkileşimi gibi belirsizlikler altında çalışır. İyi tasarlanmamış bir hata akışı sistemin kilitlenmesine, veri kaybına veya güvenlik risklerine yol açabilir. Benzer şekilde debounce yapılmadığında tek basış birden fazla olay gibi algılanır; menüler sapıtır, sayaçlar şaşar. İyi haber: Aşağıdaki yapıları bir kere kurduğunuzda yeni projelere şablon gibi taşıyabilirsiniz. ✅

Hata Türleri ve Tepki Matrisi

TürÖrnekÖncelikÖnerilen Tepki
Geçici (Transient) I2C NACK, tek seferlik CRC hatası Orta Tekrar dene, geri basınç (backoff), telemetri kaydı
Kalıcı (Persistent) Sensör kopuk, donanım arızası Yüksek Güvenli mod (safe-state), kullanıcı/servis bildirimi
Programatik (Bug) Null pointer, sınır taşması Çok yüksek Geliştirmede assert, üretimde kontrollü kurtarma/yeniden başlatma

C’de Hata Mimarisinin Omurgası: err_t + Makrolar

Modüller arası anlaşılır ve tutarlı bir dil kurmak için tek bir hata başlığı kullanın. Fonksiyonlar ERR_OK ile döner; başarısızlıklarda anlamlı kodlar kullanılır. Aşağıdaki şablon doğrudan kopyalanabilir.

/* error.h */
#ifndef ERROR_H
#define ERROR_H
#include <stdint.h>

typedef enum {
  ERR_OK = 0,
  ERR_TIMEOUT,
  ERR_PARAM,
  ERR_IO,
  ERR_BUSY,
  ERR_NO_MEM,
  ERR_RANGE,
  ERR_STATE,
  ERR_CRC,
  ERR_UNKNOWN
} err_t;

/* Hızlı dönüş ve tek çıkış noktası için yardımcılar */
#define RET_IF_FAIL(expr)        do { err_t _e = (expr); if (_e != ERR_OK) return _e; } while (0)
#define GOTO_IF_FAIL(expr,label) do { err = (expr);     if (err != ERR_OK) goto label; } while (0)

#endif /* ERROR_H */

Kaynak Yönetimi için cleanup/goto Kalıbı

Çoklu tahsis (buffer, periferal) yapan fonksiyonlarda tek çıkış noktası sızıntıları önler ve okunabilirliği artırır.

/* demo_cleanup.c */
#include "error.h"
#include <stdlib.h>

typedef struct Buf { uint8_t *p; size_t n; } Buf;

static err_t buf_alloc(Buf *b, size_t n){
  if (!b || n==0) return ERR_PARAM;
  b->p = (uint8_t*)malloc(n);
  if (!b->p)     return ERR_NO_MEM;
  b->n = n;
  return ERR_OK;
}
static void buf_free(Buf *b){
  if (b && b->p){ free(b->p); b->p=NULL; b->n=0; }
}

err_t do_job(size_t a, size_t b){
  err_t err = ERR_OK;
  Buf x={0}, y={0};

  GOTO_IF_FAIL(buf_alloc(&x, a), cleanup);
  GOTO_IF_FAIL(buf_alloc(&y, b), cleanup);

  /* ... iş mantığı ... */
  if (a < b){ err = ERR_RANGE; goto cleanup; }

cleanup:
  buf_free(&y);
  buf_free(&x);
  return err;
}

Geliştirmede assert, Sahada Log Halkası

assert geliştirme sırasında hatalı varsayımları erken patlatır. Üretimde ya kapatılır ya da yumuşatılır. Sahada hata analizi için ring buffer ile zaman damgalı olay kaydı çok işe yarar.

/* errlog.h / errlog.c */
#include <stdatomic.h>
typedef struct {
  uint32_t ts_ms;
  uint16_t code;  /* err_t veya modül kodu */
  uint16_t info;  /* ek veri (ör. hangi buton, hangi sensör) */
} error_event_t;

#define ERRLOG_SIZE 64
static error_event_t g_errlog[ERRLOG_SIZE];
static atomic_uint   g_wr = 0;

void errlog_push(uint32_t ts_ms, uint16_t code, uint16_t info){
  unsigned i = atomic_fetch_add(&g_wr, 1u) % ERRLOG_SIZE;
  g_errlog[i].ts_ms = ts_ms;
  g_errlog[i].code  = code;
  g_errlog[i].info  = info;
}

ISR, Watchdog ve Safe-State Stratejisi

  • ISR kısa olmalı: Ağır işi ana döngüye bırak; ISR sadece flag/kuyruk yazar.
  • Watchdog besleme: Tüm görevler “bitti” sinyali vermeden besleme yok; tek noktadan yönet.
  • Safe-state: Kritik hatada röleleri bırak, PWM’i kes, çıkışları güvenli seviyeye çek, kullanıcıyı uyar.

Debouncer Nedir? Neden Gerekli?

Mekanik buton/switch’ler bas-bırak sırasında 1–20 ms boyunca çok hızlı aç/kapa zıplama (bounce) üretir. Yazılımsal debouncer, bu gürültülü sinyali tek ve kararlı olaya dönüştürür; sayıcılar ve menüler güvenilir çalışır.

Debounce Yöntemleri (Delay, Counter, Majority, FSM)

1) Basit Gecikme (bloklayıcı – öğretici)

  • Değişim algılanınca delay ile bekle, sonra tekrar oku.
  • Eksiler: Ölçeklenmez, ISR/ana döngüyü kilitler.

2) Counter (Sayısal İntegratör) – Üretim için hafif ve sağlam

typedef struct {
  uint8_t stable;   /* onaylı seviye (0/1) */
  uint8_t cnt;      /* ms sayacı */
  uint8_t thr_ms;   /* debounce eşiği (örn 10-20 ms) */
  uint8_t event;    /* 1: durum değişti kenarı */
} debounce_t;

static inline void debounce_step(debounce_t *d, uint8_t raw){
  d->event = 0;
  if (raw == d->stable){ d->cnt = 0; return; }
  if (d->cnt < d->thr_ms){
    if (++d->cnt == d->thr_ms){
      d->stable = raw;
      d->event  = 1; /* kenar olayı */
    }
  }
}

3) Majority (Kaydırmalı Pencere) – Gürültü bağışıklığı yüksek

#define WIN 8u
typedef struct { uint8_t q[WIN]; uint8_t i; uint8_t stable; uint8_t event; } db_win_t;

static inline void db_win_step(db_win_t *d, uint8_t raw){
  d->q[d->i++ % WIN] = raw; d->event = 0;
  uint8_t sum=0; for (uint8_t k=0;k<WIN;k++) sum += d->q[k];
  uint8_t maj = (sum >= (WIN/2 + 1)) ? 1u : 0u;
  if (maj != d->stable){ d->stable = maj; d->event = 1; }
}

4) FSM (Single/Long/Double Press gibi zengin olaylar)

typedef enum { DB_IDLE, DB_BOUNCE, DB_PRESSED } db_state_t;
typedef enum { EV_NONE=0, EV_PRESS, EV_RELEASE, EV_LONG } db_event_t;

typedef struct {
  db_state_t st;
  uint16_t   t_ms;
  uint16_t   db_ms;    /* 10-20 ms */
  uint16_t   long_ms;  /* 600-800 ms */
} db_fsm_t;

static db_event_t db_fsm_step(db_fsm_t *d, uint8_t raw, uint16_t dt){
  d->t_ms += dt;
  switch (d->st){
    case DB_IDLE:
      if (raw){ d->st=DB_BOUNCE; d->t_ms=0; }
      break;
    case DB_BOUNCE:
      if (!raw){ d->st=DB_IDLE; }
      else if (d->t_ms >= d->db_ms){ d->st=DB_PRESSED; d->t_ms=0; return EV_PRESS; }
      break;
    case DB_PRESSED:
      if (!raw){ d->st=DB_IDLE; return EV_RELEASE; }
      else if (d->t_ms >= d->long_ms){ d->t_ms=0; return EV_LONG; }
      break;
  }
  return EV_NONE;
}

STM32 HAL ile 1 ms SysTick Tabanlı Çoklu Buton Debounce

HAL_SYSTICK_Callback() içinde sadece zaman işareti üretip asıl işlemi ana döngüde yapmak daha temiz ve güvenlidir.

/* debounce_stm32.c */
#include "stm32f1xx_hal.h" /* ailenize göre değiştirin */
#include <stdint.h>

#define BTN_COUNT 4

typedef struct { uint8_t stable, cnt, thr_ms, event; } debounce_t;

static volatile uint8_t g_tick1ms = 0;
static debounce_t g_btn[BTN_COUNT];

void HAL_SYSTICK_Callback(void){ g_tick1ms = 1; }

static inline uint8_t btn_raw_read(int i){
  /* Projenize göre uyarlayın; aktif-düşük butonlarda tersleyin */
  switch (i){
    case 0: return (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_SET);
    case 1: return (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_SET);
    case 2: return (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0) == GPIO_PIN_SET);
    case 3: return (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_1) == GPIO_PIN_SET);
    default: return 0;
  }
}

static inline void debounce_step_one(debounce_t *d, uint8_t raw){
  d->event = 0;
  if (raw == d->stable){ d->cnt = 0; }
  else {
    if (d->cnt < d->thr_ms){
      if (++d->cnt == d->thr_ms){
        d->stable = raw;
        d->event  = 1;
      }
    }
  }
}

static void debounce_step_all(void){
  for (int i=0;i<BTN_COUNT;i++){
    debounce_step_one(&g_btn[i], btn_raw_read(i));
  }
}

/* Kenar olaylarını üst katmana bildir */
static void on_button_edge(int idx, uint8_t level){
  /* Basıldı/bırakıldı: uygulamanıza göre doldurun */
  (void)idx; (void)level;
}

int main(void){
  HAL_Init(); /* RCC, GPIO vb. başlatmalarınızı yapın */

  for (int i=0;i<BTN_COUNT;i++){
    g_btn[i].stable = 0;  /* aktif-düşük ise 1/0 normalize edin */
    g_btn[i].cnt    = 0;
    g_btn[i].thr_ms = 15; /* 10–20 ms tipik */
    g_btn[i].event  = 0;
  }

  for (;;){
    if (g_tick1ms){
      g_tick1ms = 0;
      debounce_step_all();
      for (int i=0;i<BTN_COUNT;i++){
        if (g_btn[i].event){
          on_button_edge(i, g_btn[i].stable);
        }
      }
    }
    /* Diğer non-blocking işler */
  }
}

Hata Yönetimi + Debouncer Entegrasyon Örneği

Bir butona basıldığında EEPROM yazımı tetiklensin; hata olursa log’a düşüp uyarı LED’i yansın:

#include "error.h"

/* Donanım/sürücü katmanı: uygulamanızda sağlayın */
extern err_t eeprom_write_page(uint32_t addr, const void *buf, size_t n);
extern void  led_error_on(void);
extern void  errlog_push(uint32_t ts_ms, uint16_t code, uint16_t info);

static void on_button_edge_safe(int idx, uint8_t level){
  if (idx==0 && level==1){ /* buton 0 basıldı */
    uint32_t data = 0x12345678u;
    err_t e = eeprom_write_page(0x0000u, &data, sizeof(data));
    if (e != ERR_OK){
      /* Zaman damgasını sisteminizden (HAL_GetTick vb.) alın */
      errlog_push(/*ts*/0u, (uint16_t)e, /*info*/idx);
      led_error_on();
    }
  }
}

Yaygın Hatalar ve İpuçları

  • ISR’da debounce yapmak: Kesme süresini uzatır, jitter üretir. Ana döngüde yapın.
  • Bloklayıcı HAL_Delay: Demo’da çalışır; gerçek zamanlı işlerde işleri kilitler.
  • Keyfi eşik değerleri: Osiloskopla ölçüp 10–20 ms bandında gerçekçi seçin.
  • Watchdog’u kör beslemek: “Tüm görevler bitti” şartı sağlanmadan besleme yapmayın.
  • Dağınık hata kodları: Tüm modüller tek error.h üzerinden konuşsun.

Kontrol Listesi

  • [ ] Projede tek bir err_t başlığı var
  • [ ] cleanup/goto ile tek çıkış noktası sağlandı
  • [ ] Zaman damgalı log halkası aktif
  • [ ] ISR’lar kısa; ana döngü non-blocking
  • [ ] Debounce eşiği ölçüme dayalı
  • [ ] PRESS/RELEASE/LONG olayları tasarlandı (gerekirse)
  • [ ] Watchdog besleme politikası yazılı

🔖 Terimler Sözlüğü

TerimAçıklama
err_tFonksiyonların döndürdüğü, enum tabanlı hata tipi.
cleanup/gotoKaynakları tek noktada serbest bırakma kalıbı.
assertGeliştirmede hatalı varsayımı anında yakalar.
Ring bufferBaşa saran sabit boyutlu kayıt tamponu.
ISRKesme rutinleri; kısa ve deterministik olmalı.
WatchdogSistem takıldığında reset atarak toparlar.
DebounceGürültülü mekanik girişin kararlı olaya indirgenmesi.
FSMSonlu durum makinesi; olay tabanlı durum geçişi.

📌 Ekstra Kaynaklar

🎨 Görsel Önerisi

Not: Aşağıdaki prompt ve alt metin HTML’in dışındadır (bu yazının içinde sadece bilgilendirme amaçlı listelenmiştir).


Görsel üretim promptu: - “Clean technical illustration: left side shows a noisy mechanical button waveform turning into a stable debounced signal (counter/FSM hint); right side depicts a C error-handling flow (err_t enums → cleanup/goto → safe-state + watchdog). Minimal, vector, white background, high contrast.” Alt metin (alt text) önerisi: - “Debouncer ile gürültülü buton sinyalinin tek olaya indirgenmesi ve C’de err_t + cleanup/goto hata yönetimi akışını anlatan teknik çizim.”

4 Eylül 2025 Perşembe

C Dilinde Exponential Moving Average (EMA) Filter Nedir, Nasıl Uygulanır?

C Dilinde Exponential Moving Average (EMA) Filter Nedir, Nasıl Uygulanır?

Merhaba! 😊 Bugünkü yazımızda hem elektronikçiler hem de yazılımcılar için oldukça faydalı olan Exponential Moving Average (EMA) filtresini inceleyeceğiz. Özellikle gürültülü sensör verilerini yumuşatmak ve daha kararlı ölçümler elde etmek isteyenler için C dilinde bu filtrenin nasıl uygulanacağını adım adım göstereceğim.

Dalgalı sensör verisi ve EMA filtresi ile yumuşatılmış verinin karşılaştırmalı grafiği

Exponential Moving Average (EMA) Filtresi Nedir?

EMA filtresi, geçmiş verilerle birlikte yeni gelen verileri ağırlıklı bir şekilde birleştirerek daha pürüzsüz bir çıkış sinyali elde etmemizi sağlar. Burada “exponential” (üstel) ifadesi, yeni veriye daha fazla, eski verilere ise giderek azalan ağırlık verilmesi anlamına gelir.

Matematiksel Tanım

EMA filtresi şu formülle ifade edilir:

EMA(n) = α * YeniVeri(n) + (1 - α) * EMA(n-1)
  • EMA(n): Şu anki filtrelenmiş değer
  • YeniVeri(n): Şu anki ölçüm/sensör değeri
  • EMA(n-1): Bir önceki filtrelenmiş değer
  • α (alfa): 0 ile 1 arasında bir katsayı, filtre hassasiyetini belirler

Neden EMA Kullanılır?

Birçok uygulamada ham veriler çok fazla gürültü içerir. Örneğin:

  • ADC ile ölçülen sıcaklık sensör verileri
  • Jiroskop veya ivmeölçer (IMU) verileri
  • Motor akım ve voltaj ölçümleri

Bu gibi durumlarda, veriyi doğrudan kullanmak yerine EMA filtresi ile yumuşatmak daha anlamlıdır. Böylece:

  • Veri dalgalanmaları azalır ✅
  • Trendler daha net görülür 📈
  • Kod tarafında basit bir algoritma ile uygulanır ⚡

EMA ile Basit Moving Average (SMA) Arasındaki Fark

Sıklıkla EMA ile Simple Moving Average (SMA) karşılaştırılır. SMA’da belirli sayıda eski veri toplanır ve ortalaması alınır. EMA ise:

  • Eski verileri saklamaz (daha az RAM kullanır)
  • Daha az işlem gücü gerektirir
  • Son veriye daha fazla ağırlık verdiği için daha hızlı tepki verir
Özellik SMA EMA
Hafıza Kullanımı Yüksek (N veri tutar) Düşük (tek değer tutar)
Tepki Hızı Daha yavaş Daha hızlı
Hesaplama Yükü Toplama + Bölme Çarpma + Toplama

C Dilinde EMA Filtresi Nasıl Uygulanır?

Temel Kod Örneği

#include <stdio.h>

float EMA_Filter(float new_value, float alpha) {
    static float ema = 0; // Başlangıç değeri
    ema = (alpha * new_value) + ((1 - alpha) * ema);
    return ema;
}

int main() {
    float sensor_data[] = {10, 12, 15, 14, 13, 20, 18};
    int data_size = sizeof(sensor_data) / sizeof(sensor_data[0]);
    float alpha = 0.2; // Filtre hassasiyeti

    for(int i = 0; i < data_size; i++) {
        float filtered = EMA_Filter(sensor_data[i], alpha);
        printf("Ham Veri: %.2f  ->  Filtrelenmiş: %.2f\n", sensor_data[i], filtered);
    }
    return 0;
}

Yukarıdaki örnekte:

  • alpha = 0.2 seçildi. Bu durumda yeni veri %20 etkili olurken, geçmiş veri %80 oranında korunur.
  • static float ema, fonksiyon çağrıları arasında eski değeri saklar.
  • Her ölçüm geldiğinde filtre otomatik güncellenir.

α (Alpha) Değeri Nasıl Seçilir?

Alpha değeri filtrenin hızlı mı, yavaş mı tepki vereceğini belirler:

  • α ≈ 0.1 → Daha yavaş, daha yumuşak filtre (gürültüyü azaltır, ama gecikme artar)
  • α ≈ 0.5 → Orta hassasiyet, hem hızlı hem dengeli
  • α ≈ 0.9 → Çok hızlı tepki, ama gürültü daha az bastırılır

Pratikte, sensör tipine ve uygulamaya göre α ayarlanır.

Gerçek Hayat Örneği

Bir sıcaklık sensörü saniyede 100 ölçüm yapıyor ve her ölçümde ±2°C gürültü var. Eğer ham veriyi doğrudan kullanırsak ekranda sürekli dalgalanan değerler görürüz. Ancak EMA filtresi kullanıldığında:

  • Ekranda daha stabil bir sıcaklık değeri görünür 🌡️
  • Küçük dalgalanmalar ortadan kalkar
  • Değişimler daha akıcı yansır

Avantajlar ve Dezavantajlar

Avantajları

  • Kolay implementasyon (tek satır formül yeterli)
  • Düşük hafıza ve işlem gücü ihtiyacı
  • Gerçek zamanlı sistemler için ideal

Dezavantajları

  • Trend değişimlerine gecikmeli tepki verir
  • Alpha değeri doğru seçilmezse ya çok gürültülü ya da çok yavaş tepki verebilir

Sonuç

Exponential Moving Average (EMA) filtresi, sensör verilerini yumuşatmak için C dilinde kolayca uygulanabilecek güçlü bir yöntemdir. Hafif, hızlı ve esnek olması sayesinde hem gömülü sistemlerde hem de masaüstü uygulamalarında yaygın olarak kullanılır. Uygulamanıza göre doğru α değerini seçerek siz de daha kararlı ve güvenilir sonuçlar elde edebilirsiniz 🚀.


🔖 Terimler Sözlüğü

Terim Açıklama
EMA Exponential Moving Average – Üstel hareketli ortalama filtresi
α (Alpha) Filtrenin yeni veriye verdiği ağırlık (0–1 arasında)
SMA Simple Moving Average – Basit hareketli ortalama filtresi
ADC Analog-Dijital Çevirici

📌 Ekstra Kaynaklar

9 Haziran 2025 Pazartesi

C Dilinde Moving Average Filter (Hareketli Ortalama Filtresi) Nedir, Nasıl Uygulanır?

Hareketli Ortalama Filtresi (Moving Average Filter), sinyal işleme ve veri analizi alanlarında en çok tercih edilen basit ve etkili filtrelerden biridir. Gürültülü verilerin pürüzsüzleştirilmesi, ani değişimlerin yumuşatılması ve sensör okumalarının stabilize edilmesi gibi birçok uygulama alanında kullanılır. Özellikle gömülü sistemlerde, mikrodenetleyicilerde ve gerçek zamanlı uygulamalarda, C dili ile kolayca uygulanabilir olması sayesinde oldukça popülerdir.

Hareketli Ortalama Filtresi Nedir?

Kısaca, hareketli ortalama filtresi, belirli bir pencere (örneğin N örnek) içerisindeki verilerin aritmetik ortalamasını alarak yeni bir çıktı üretir. Böylece, kısa süreli gürültüler ve ani değişimler filtrelenmiş olur.

Matematiksel olarak:
Y[n] = (X[n] + X[n-1] + ... + X[n-(N-1)]) / N
Burada:

  • Y[n]: Filtrelenmiş çıktı

  • X[n]: En yeni giriş değeri

  • N: Pencere boyutu (window size)


Avantajları ve Dezavantajları

Avantajlar:

  • Basit ve hızlı algoritma

  • Bellek ve işlemci gereksinimi düşük

  • Gerçek zamanlı uygulamalara uygun

Dezavantajlar:

  • Ani değişimleri geciktirir (faz kayması)

  • Büyük pencerelerde tepki yavaştır

  • Herkese uyan tek boyutlu çözüm değildir; pencere boyutu dikkatle seçilmelidir


C Dilinde Moving Average Filter Nasıl Uygulanır?

Aşağıda, hareketli ortalama filtresinin C dilinde farklı şekillerde uygulanışı anlatılmaktadır.

1. Temel Uygulama: Döngüyle Ortalama Alma

Kullanımı:

Eksisi:

Her yeni örnekte tüm pencereyi toplar, bu da işlemciyi yorar.


2. Kayan Pencere ile Optimizasyon (Dairesel Buffer Kullanımı)

Dairesel (circular) buffer ve kayan toplam yaklaşımıyla işlem yükü azaltılabilir.

Kullanımı:

Avantajı:

  • Her örnek için sadece iki toplama/çıkarma işlemi gerekir.

  • Özellikle gömülü sistemlerde çok daha hızlıdır.


3. Uygulama Alanları

  • Sensör verilerinin düzeltilmesi (ör. sıcaklık, ivmeölçer)

  • Finansal zaman serilerinde veri yumuşatma

  • Gürültülü sinyal filtreleme (ör. ADC verisi)

  • Otomotivde, tıbbi cihazlarda, endüstriyel kontrol sistemlerinde


Pencere Boyutu (Window Size) Nasıl Seçilmeli?

  • Küçük pencere (örn. 3–5): Ani değişimlere daha duyarlı, az gecikme.

  • Büyük pencere (örn. 20–50): Daha yumuşak sonuç, daha fazla gecikme.

Kullanım amacınıza ve verinizin doğasına göre pencere boyutunu dikkatli seçmelisiniz.


Kodun Tamamı: Basit Moving Average Filter Kütüphanesi

Sonuç

Hareketli ortalama filtresi, basitliği ve etkililiğiyle öne çıkan bir filtreleme yöntemidir. C dilinde uygulanması da oldukça kolaydır. Kodunuzu ve pencere boyutunuzu ihtiyacınıza göre optimize ederek, birçok gerçek zamanlı uygulamada başarılı sonuçlar elde edebilirsiniz.

Sen de uygulaman için yukarıdaki örnekleri kolayca entegre edebilirsin!

8 Nisan 2025 Salı

STM32CubeIDE’de Canlı Değişken Takibi: Debug Sürecini Güçlendiren İpucu

STM32CubeIDE, STM32 mikrodenetleyici projelerinde hem geliştirme hem de hata ayıklama süreçlerini kolaylaştıran güçlü bir araç. Ancak çoğu kullanıcı, debug sürecini sadece breakpoint koyup değişkenleri tek tek kontrol ederek geçiriyor. Oysa ki “Live Expressions” (canlı ifadeler) özelliği ile, debug sırasında gerçek zamanlı değişken değişimlerini izleyerek çok daha verimli bir şekilde sorunları analiz edebilirsiniz.

Bu yazıda, STM32CubeIDE’de canlı değişken takibi nasıl yapılır, ne gibi avantajlar sağlar ve bunu bir örnek proje üzerinden nasıl kullanabileceğinizi adım adım anlatacağım.


Nedir Bu "Live Expressions"?

Live Expressions penceresi, kodunuz çalışırken belirli değişkenleri (ya da ifadeleri) sürekli gözlemlemenizi sağlar. Kod breakpoint’te durmasa bile, değişkenlerin değerleri anlık olarak güncellenir. Bu, özellikle zamanla değişen sayıcılar, PWM duty cycle’ları veya sensör değerleri gibi dinamik verileri takip etmek için çok faydalıdır.


Neden Kullanmalısınız?

Live Expressions ile şunları kolaylıkla yapabilirsiniz:

  • Kod kesintiye uğramadan değişkenleri takip edebilirsiniz.

  • Belirli bir algoritmanın anlık çıktısını grafiksel ya da sayısal olarak izleyebilirsiniz.

  • Delay’lerle uğraşmadan, breakpoint koymadan performans sorunlarını veya mantıksal hataları yakalayabilirsiniz.

  • Gerçek zamanlı sistemlerde, kod akışını bozmadan değerlerin nasıl değiştiğini görme imkânı sağlar.


Uygulamalı Örnek: ADC ile Sıcaklık Ölçümü

Diyelim ki bir STM32F103RB mikrodenetleyici ile dahili sıcaklık sensöründen ADC kullanarak sıcaklık değeri okuyoruz. Kodumuz şöyle:

c
#include "main.h" ADC_HandleTypeDef hadc1; float temperature = 0; void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) { if (hadc->Instance == ADC1) { uint32_t adc_val = HAL_ADC_GetValue(&hadc1); temperature = ((float)adc_val) * 3.3f / 4096.0f; // Voltaj temperature = ((temperature - 0.76f) / 0.0025f) + 25; // Sıcaklık (yaklaşık hesap) } } int main(void) { HAL_Init(); SystemClock_Config(); MX_ADC1_Init(); HAL_ADC_Start_IT(&hadc1); while (1) { HAL_Delay(500); // Sıcaklık değerini yarım saniyede bir güncelliyoruz } }

Adım Adım: Live Expressions Kullanımı

1. Debug Moduna Geçin

Projeyi build ettikten sonra Debug moduna geçin (F11 veya üstteki bug ikonuna tıklayarak).

2. Live Expressions Panelini Açın

Debug görünümüne geçtikten sonra:

  • Menüden Window > Show View > Expressions yolunu izleyin.

  • Açılan panelin ismi "Expressions" olabilir ama bu panel canlı güncelleme desteği sunar.

3. Değişken Ekleyin

  • Expressions paneline sağ tıklayın ve Add Watch Expression seçeneğine tıklayın.

  • Açılan kutuya temperature yazın.

  • Enter’a bastığınızda, değişken panelde belirecek.

4. Canlı Güncellemeyi Görüntüleyin

  • Kod hala çalışırken (pause durumda değilken!) temperature değerinin saniyede iki kez güncellendiğini göreceksiniz.

  • Değer sürekli güncelleniyorsa, sisteminizde herhangi bir kesinti olmadan anlık sıcaklık verisini izleyebiliyorsunuz demektir.


İleri Seviye: İfade Takibi (Expression Tracking)

Bu panelde sadece değişken değil, ifade de yazabilirsiniz. Örneğin:

temperature > 30

şeklinde yazarsanız, bu ifade true veya false olarak sürekli değerlendirilir. Bu da debug sürecinde koşul kontrollerini anında görmek için ideal bir yöntemdir.


İpuçları & Dikkat Edilmesi Gerekenler

  • Optimization seviyeleri yüksekse (-O2, -O3) bazı değişkenler derleyici tarafından optimize edilebilir ve debug sırasında görünmeyebilir. Geliştirme aşamasında -O0 (optimizasyon kapalı) kullanmanızı öneririm.

  • Eğer değişken global değilse, yani fonksiyon içindeyse ve static değilse, Live Expressions’da gözükmeyebilir. Bu durumda değişkenin kapsamını değiştirin.

  • volatile tanımlaması, anlık değişen donanım temelli değişkenlerde kritik öneme sahiptir. Bu sayede değer her seferinde RAM’den okunur.


Sonuç: Canlı Takiple Daha Akıllı Debug

Live Expressions, STM32CubeIDE’nin az bilinen ama çok etkili bir özelliği. Özellikle gömülü sistemlerde, zamanla değişen verileri izlemek ve mantıksal hataları tespit etmek için oldukça kullanışlıdır.

Her debug seansında breakpoint koymak yerine, bu özelliği kullanarak sisteminizi çalışır haldeyken analiz edebilir, daha kısa sürede daha isabetli çözümler üretebilirsiniz.


Eğer bu özelliği daha önce kullanmadıysanız, bir sonraki debug seansınızda denemenizi kesinlikle tavsiye ederim. STM32CubeIDE’nin sunduğu bu ve benzeri küçük araçlar, geliştirme sürecinizde büyük farklar yaratabilir.

8 Ocak 2025 Çarşamba

Yapay Sinir Ağlarının Temel Bileşeni: Nöronlar

Yapay sinir ağları (YSA), biyolojik sinir sistemlerinin çalışma prensiplerinden esinlenerek geliştirilen ve makine öğrenmesi ile derin öğrenmenin temelini oluşturan matematiksel modellerdir. Bu sistemlerin çekirdeğini ise "nöronlar" oluşturur. Bu yazıda, yapay nöronların yapısı, fonksiyonları ve uygulamaları hakkında detaylı bilgi vereceğiz.

Yapay Nöron Nedir?

Yapay nöronlar, biyolojik nöronları taklit eden matematiksel birimlerdir. Bir yapay nöron genellikle şu bileşenlerden oluşur:

  1. Girdi (Input): Yapay nöron, birçok girdiyi (örneğin, x1, x2, x3 gibi) kabul eder. Bu girdiler, işlenmesi gereken verileri temsil eder.

  2. Ağırlıklar (Weights): Her girdi bir ağırlıkla (örneğin, w1, w2, w3 gibi) çarpılır. Bu ağırlıklar, sistemin girdilere verdiği önemi ifade eder ve eğitim sürecinde optimize edilir.

  3. Toplama (Summation): Girdi ve ağırlıkların çarpımları toplanarak toplam bir değer elde edilir. Bu işlem, biyolojik nöronlarda dendritlerin toplama işlevine benzer.

  4. Aktivasyon Fonksiyonu: Toplam değer, belirli bir çıktı (output) oluşturmak için bir aktivasyon fonksiyonundan geçirilir. Bu fonksiyon, sistemin çıktısının lineer mi yoksa lineer olmayan mı olacağını belirler.

  5. Çıktı (Output): Aktivasyon fonksiyonunun sonucu, nöronun çıktısıdır. Bu çıktı, bir sonraki katmana veya sisteme iletilir.

Aktivasyon Fonksiyonları

Aktivasyon fonksiyonları, yapay nöronların en önemli bileşenlerinden biridir. Bu fonksiyonlar, karmaşık veri modellerini çözmek için çıktıları dönüştürür. En yaygın aktivasyon fonksiyonları şunlardır:

  • Sigmoid Fonksiyonu: Çıktıları 0 ile 1 arasında sınırlar. Genellikle iki sınıflı problemler için kullanılır.

  • ReLU (Rectified Linear Unit): Pozitif girdileri aynen aktarırken, negatif girdileri 0 yapar. Hızlı ve etkin çalışır.

  • Tanh (Hyperbolic Tangent): Çıktıları -1 ile 1 arasında dönüştürür ve sigmoid fonksiyonuna benzer bir yapısı vardır.

  • Softmax Fonksiyonu: Birden fazla sınıfa ait olasılıkları hesaplamak için kullanılır.

Yapay Nöronların Eğitimi

Yapay nöronlar, genellikle bir ileri besleme (feedforward) yapısında organize edilir ve "geri yayılım" (backpropagation) algoritması ile eğitilir. Bu süreç şu adımlardan oluşur:

  1. İleri Besleme (Forward Pass): Girdiler ağ boyunca iletilir ve çıktılar hesaplanır.

  2. Hata Hesaplama: Hesaplanan çıktı, hedef çıktı ile karşılaştırılarak hata bulunur.

  3. Geri Yayılım (Backward Pass): Hata, ağırlıkları optimize etmek için geriye doğru yayılır.

  4. Ağırlıkların Güncellenmesi: Hata miktarına göre ağırlıklar yeniden ayarlanır.

Kullanım Alanları

Yapay nöronlar, birçok farklı alanda kullanılır:

  • Görüntü Tanıma: El yazısı tanıma, obje algılama gibi alanlarda yaygındır.

  • Doğal Dil İşleme (NLP): Makine çevirisi, duygu analizi gibi uygulamalarda temel taşıdır.

  • Finans: Hisse senedi tahmini ve dolandırıcılık tespiti gibi alanlarda kullanılır.

  • Sağlık: Hastalık tanısı, tıbbi görüntüleme analizinde yer alır.

Sonuç

Yapay nöronlar, yapay zeka ve derin öğrenme sistemlerinin şüphesiz en temel bileşenidir. Biyolojik nöronlardan esinlenilerek geliştirilen bu modeller, karmaşık problemleri çözmeyi mümkün kılar. Nöronların işleyişini anlamak, yapay sinir ağlarının daha etkili bir şekilde kullanılmasına katkı sağlar.

19 Mart 2024 Salı

CAN Bus Frame Tipleri

Yazıya başlamadan önce CAN Bus temelleri ve mesaj yapısının temellerini incelemek için bu linkte yer alan blog yazısını inceleyebilirisiniz.

CAN Bus hattında kullanılan 4 tip frame (çerçeve, yapı) vardır.

  • Data Frame: Data framei, temelde data/veri taşımak için kullanılan ana yapıdır. CAN Bus sistemleri arasında veri transferi için kullanılır. Tek bir data framei üzerinden minimum 1 maksimum 8 byte veri transferi tek seferde gerçekleştirilebilir.
  • Remote Frame: Remote frame, veri talep etme frameidir. Remote framede tanımlanan ID CAN Bus ağına basılır. İlgili mesaj paketini barındıran sistem bu ağa ilgili data frameini basar. Remote frame, bir sistemin, bir veriye özellikle ihtiyaç duyduğu zamanda kullanılır.
  • Error Frame: SOF ile CRC arasında 6 veya 12 bit boyunca aynı seviye (High/Low, Recessive/Dominant) veri gönderilmesiyle error framei tanımlanmış olur ve hattaki sistemler ilgili framei error frame olarak işaretler. Error frame istemli gönderilen bir frame değildir. Bir sistem bir paketi göndermeye başladıktan sonra bir hata tespit edilirse ilgili frame error frameine çevrilir.
  • Overload Frame: CAN Bus peripheralı meşgul olduğu durumlarda hattı meşgul tutmak için overload frame hatta basar. Bu bilgi hattın durdurulması için kullanılır. Günümüzde genellikle kod üzerinden kontrol edilmez. CAN Bus peripheralları bu durumu kendisi tespit eder ve gerektiği kadar hattı meşgulde tutar, sonrasında bırakır.



Kaynaklar
  1. https://medium.com/@mohammednumeir13/can-protocol-types-of-can-frames-51c8444176bb
  2. https://www.researchgate.net/publication/340883976_Cyberattacks_and_Countermeasures_For_In-Vehicle_Networks/figures?lo=1

10 Ocak 2024 Çarşamba

STM32F103 Mikrodenetleyici Çevre Birimlerinde Karşılaştığım Bir Problem #STM32Tips

Konunun hikayesinden başlamak gerekirse, STM32F0 ve STM32F1 serisi işlemcilerden oluşan ve CAN Bus üzerinden birbirleri ile haberleşen bir tasarım üzerine çalışıyordum. Her bir kart için CAN Bus bağlantısının yanında bilgisayar bağlantısını da kurmak için USB bağlantısını sisteme dahil etmiştim. Bu tasarım STM32F0 serisi işlemcide CAN Bus ve USB sorunsuz çalıştı.

STM32F1 serisi işlemciyi kullanırken aynı yazılıp mimarisi üzerinden devam ediyordum ancak bir sorun ile karşılaştım. Bir şekilde CAN Bus hattını kullanamıyordum. Problemi araştırmaya başladığımda USB ve CAN Bus'ın birlikte kullanımı ile ilgili başka problem yaşayanların da olduğunu gördüm. Sonrasında kullandığım işlemci olan STM32F103CB'nin datasheetini (Reference Manual RM0008 Rev 21) inceledim. CAN Bus kısmında aşağıdaki gibi bir not olduğunu gördüm.

"In low, medium-, high- and XL-density devices the USB and CAN share a dedicated 512-

byte SRAM memory for data transmission and reception, and so they cannot be used concurrently (the shared SRAM is accessed through CAN and USB exclusively). The USB and CAN can be used in the same application but not at the same time."

Buradan anladığımız CAN Bus ve USB işlemci üzerinde aynı SRAM alanlarını kullanıyor ve bu yüzden her iki çevre birimi birlikte kullanılamıyor. USB'yi kapatıp CAN Bus'ı çalıştırmak veya tam tersi mümkün ancak her ikisi aynı anda asla çalıştırılamıyor. Bu yüzden örnek vermek gerekirse CAN Bus üzerinden alınan bir veriyi USB üzerinden gerçek zamanlı alıp işlemek mümkün değil.

Ek olarak STM32F0 serisi ile bir çok uygulama yapmıştım ve bu yüzden USPDP, USPDM hatlarını direkt konnektöre bağlamıştım. STM32F103CB işlemci kullanırken USBDP hattına 3.3 V'a 1.5 kR pull-up resistor gerekiyormuş.

STM32 USB kullanılan uygulamalarda donanım tasarımı hakkında aşağıdaki doküman yayınlamış. Donanım tarafında yapılması gereken işler tek bir dokümanda bütün işlemci ailesi için verilmiş. Gelecek tasarımlar için işe yarar gibi görünüyor.


Link: https://www.st.com/resource/en/application_note/an4879-introduction-to-usb-hardware-and-pcb-guidelines-using-stm32-mcus-stmicroelectronics.pdf (10.01.2024)

12 Aralık 2023 Salı

STM32 Serisi İşlemcilerde Timer/Sayaç Modülünü Interrupt/Kesme Modunda Başlatma #STM32Tips

STM32F ve STM32G serisi mikrodenetleyicilerde Device Configurator Tool ve HAL kütüphanesi ile oluşturulan yazılımlarda timer modülleri için gereken ayarlar yapılır. Bu kapsamda timerların prescaler ve counter period gibi seçenekleri ayarlanır. Bunlarla beraber timer interruptları enable edilir.

Bu şekilde konfigürasyon yapıldıktan sonra kod oluşturulur.

Oluşturulan kodda timer başlatılmamış şekilde bir yapı kullanıcıya sunulur. İlgili timerları interrupt modunda başlatmak için aşağıdaki fonksiyon kullanılır. Bu fonksiyonda altı çizili htimX ilgili timerı ifade eder ve X yerine timer numarası yazılır.

HAL_TIM_Base_Start_IT ( &htimX );

Timerlar için yapılan konfigürasyona göre bu fonksiyonun çağrılma yapısı değişiklik gösterir. Auto-Reload edilmiş bir timer için bu fonksiyonu bir defa çalıştırmak yeterlidir.

Belli bir duruma göze özellikle tetiklenecek bir yapı var ise bu fonksiyon her tetiklemede tekrar çağrılır. Şart değil ancak bu modda genellikle auto-reload disable edilmiştir.



31 Mart 2023 Cuma

Pointer Kavramı | C

Pointer, bir değişkenin bellekteki adresini işaret eden bir C programlama dilindeki özel bir veri türüdür. Başka bir değişkenin bellekteki adresini işaret eden bir pointer tanımlamak, bellekteki verilere erişimi ve değiştirilmesini mümkün kılar. Bu, özellikle büyük ve karmaşık veri yapılarıyla çalışırken çok yararlı olabilir.

Örneğin, bir dizideki elemanları değiştirmek istiyorsak, her elemana ayrı ayrı erişmek zorunda kalmayız. Bunun yerine, dizinin ilk elemanının adresini işaret eden bir pointer kullanabiliriz. Bu şekilde, pointer aracılığıyla bellekteki diğer elemanlara erişebilir ve değiştirebiliriz.

Pointer'ın syntax'ı ise şu şekildedir:

Bu kod parçasında, "veri_tipi" pointer tarafından işaret edilen veri tipini belirtirken, "pointer_ismi" ise pointer'ın kendisi için bir isimdir. Örneğin, bir integer tipinde bir pointer tanımlamak istiyorsak şu şekilde yapabiliriz:


Bu kod parçasında, "ptr" bir integer tipinde bir pointer olarak tanımlanır. Bu pointer, bellekteki bir integer değişkenin adresini işaret edebilir. Pointer'lar, işaret ettikleri bellek adresindeki veriye erişmek için dereference (yıldız) operatörü (*) kullanılır. Bu işlem, pointer'ın adresini izleyen bellek bölgesindeki veriyi gösterir. Örneğin, şöyle bir kod yazabiliriz:

Bu kod parçasında, "x" adlı bir integer değişken tanımlarız ve değer olarak "10" veririz. Daha sonra, "ptr" adlı bir integer pointer tanımlarız ve "x" değişkeninin bellekteki adresini işaret ederiz. Son olarak, "printf" fonksiyonunu kullanarak "ptr" pointer'ının işaret ettiği bellek adresindeki veriyi yazdırırız. Bu durumda, "10" çıktısı verir. Pointer'lar, dinamik bellek yönetimi, fonksiyon parametre geçişi ve işaretçi aritmetiği gibi daha ileri seviye programlama konularında da çok önemlidir. Ancak, temel kullanımlarını anladıktan sonra, bu konulara geçmek daha kolay olacaktır.

Basit bir gösterin olarak aşağıdaki görseli ele alalım. Burada tanımlanan pointer belleğin 2047 numaralı adresindedir. Pointer içerisinde 1001 değerini tutar. Bu noktada bir "ptr" ifadesine "*" operatörü ile eriştiğimizde "*ptr" geri dönüş değeri olarak 50 değerine ulaşırız.



11 Ağustos 2022 Perşembe

Circular Buffer (Dairesel Buffer)

Gömülü sistemlerde veri toplama, kaydetme veya depolama için çeşitli veri yapıları kullanmak gerekir. Bu uygulamanın tipine göre farklılık gösterebilir. Özellikle gateway ve benzeri projelerde circular buffer tercih edilebilir. RAM kaynağı sınırlı olduğu için en verimli yöntem ile bu kaynağı kullanmak gerekir. FIFO, LIFO şeklinde davranış gösteren farklı veri yapıları vardır. Bu yazı, circular buffer nedir, nasıl çalışır, hangi durumlarda kullanılmalı gibi konular üzerine olacak.

Circular buffer adından da anlaşılacağı gibi hafızayı dairesel bir formatta kabul ederek/soyutlaştırarak hafıza alanını kullanmaya imkan sunan bir veri yapısıdır. Dairesel bufferın özelliklerinden biri veri yazma ile veri okuma indexlerinin bağımsız kontrolüdür. Tüm bufferın boyutu sabittir.

Görsel üzerindeki end()/item n yazma pointerını ifade eder. begin()/item 1 okuma pointerını ifade eder. Okuma ve yazma pointerları birbirine eşit olduğunda buffer empty flagi set olur. Bu bufferın tamamen boş olduğunu ifade eder. Yazma pointerı okuma pointerının bir eksiğine eşit olduğunda ise buffer full flagı set olur. Bu bufferın tamamen dolu olduğunu ifade eder. Bu yapının kullanıldığı yerin gereksinimlerine göre half full gibi farklı seviyeler için de flag tanımlanabilir.

Circular buffer rutin bir çalışma durumunda full durumuna gelmeyecek şekilde kurgulanmalıdır. Bunun için okuma hızı/sıklığı her zaman yazma okuma hızı/sıklığından daha yüksek olmalıdır. Böylece bufferda saklanan verinin belli bir değeri geçmeyeceği garanti edilir. Yine de beklenmedik durumlar ortaya çıkabilir. Bu durumlarda circular bufferlarda farklı stratejiler uygulanabilir. Birincisi üzerine yazma, ikincisi ise bloklama yaklaşımıdır.

Üzerine yazma yaklaşımında circular buffer full olsa bile yeni gelen veri bir sonraki yazma adresine yazılır. Bu durumda bir önceki data kayıp olmuş olur.

İkinci yöntem olan bloklama yaklaşımında da eğer circular buffer full ise yeni gelen veri olsa bile en sondaki veri okunmadan yeni bir yazmaya geçilmez.

Örnek bir repository olarak https://github.com/Roen-Ro/CircularBuffer incelenebilir.

Referanslar:

  1. https://www.boost.org/doc/libs/1_78_0/doc/html/circular_buffer.html

C Dilinde Hata Durumları Nasıl Yönetilmelidir? Debouncer Yapısı Nedir?

C Dilinde Hata Durumları Nasıl Yönetilmelidir? Debouncer Yapısı Nedir? Merhaba! 😊 Bu yazıda iki kritik konuyu pratik bir mühendislik akış...