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?
- Hata Türleri ve Tepki Matrisi
- C’de Hata Mimarisinin Omurgası:
err_t+ Makrolar - Kaynak Yönetimi için cleanup/goto Kalıbı
- Geliştirmede
assert, Sahada Log Halkası - ISR, Watchdog ve Safe-State Stratejisi
- Debouncer Nedir? Neden Gerekli?
- Debounce Yöntemleri (Delay, Counter, Majority, FSM)
- STM32 HAL ile 1 ms SysTick Tabanlı Çoklu Buton Debounce
- Hata Yönetimi + Debouncer Entegrasyon Örneği
- Yaygın Hatalar ve İpuçları
- Kontrol Listesi
- 🔖 Terimler Sözlüğü
- 📌 Ekstra Kaynaklar
- 🎨 Görsel Önerisi
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
delayile 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_tbaş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üğü
| Terim | Açıklama |
|---|---|
err_t | Fonksiyonların döndürdüğü, enum tabanlı hata tipi. |
| cleanup/goto | Kaynakları tek noktada serbest bırakma kalıbı. |
assert | Geliştirmede hatalı varsayımı anında yakalar. |
| Ring buffer | Başa saran sabit boyutlu kayıt tamponu. |
| ISR | Kesme rutinleri; kısa ve deterministik olmalı. |
| Watchdog | Sistem takıldığında reset atarak toparlar. |
| Debounce | Gürültülü mekanik girişin kararlı olaya indirgenmesi. |
| FSM | Sonlu durum makinesi; olay tabanlı durum geçişi. |
📌 Ekstra Kaynaklar
- Jack Ganssle — A Guide to Debouncing
- Barr Group — Debouncing Switches in Embedded Systems
- man7 — errno(3) kılavuzu
- Memfault Interrupt — Defensive Embedded Programming
🎨 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.”