Seri Protokoller
TEKNİK REHBER SERİ PROTOKOLLER PWM 2026

PWM — period,
duty cycle, frekans.

Servo, motor, LED dimming — doğru period ve duty cycle her şeyin anahtarı. Linux sysfs PWM arayüzü ve kernel driver ile tam kontrol.

00 PWM temelleri

PWM (Pulse Width Modulation) — sabit frekanslı bir dijital sinyalin yüksek/düşük süre oranını değiştirerek analog benzeri kontrol sağlama tekniği.

  │‾‾‾‾│    │‾‾‾‾│    │‾‾‾‾│    %50 duty cycle
  │    │    │    │    │    │
  ┘    └────┘    └────┘    └────

  │‾‾‾‾‾‾‾│  │‾‾‾‾‾‾‾│         %70 duty cycle
  │       │  │       │
  ┘       └──┘       └──────────

  │‾│  │‾│  │‾│  │‾│           %20 duty cycle
  │ │  │ │  │ │  │ │
  ┘ └──┘ └──┘ └──┘ └───────────
    ←──── period ────►
    
PeriodBir tam dalga döngüsünün süresi. Nanosaniye cinsinden ifade edilir (sysfs API). Period = 1 / Frekans.
Duty cycleSinyalin yüksek kaldığı süre. Nanosaniye cinsinden (sysfs). duty_cycle ≤ period olmalıdır.
FrekansSaniyedeki periyot sayısı (Hz). Frekans = 1 / Period(saniye).
Duty cycle %Duty cycle'ın period'a oranı. % = (duty_cycle / period) × 100.

Kullanım senaryoları

UygulamaFrekansDuty cycle aralığıAçıklama
RC servo motor50 Hz (20ms period)1ms–2ms pulse (5%–10%)Açı 0°–180° arası
DC motor hız1–20 kHz0%–100%Yüksek frekans ses kirliliğini önler
LED dimming100 Hz – 10 kHz0%–100%100 Hz üstünde göz titreşim görmez
Buzzer1 kHz – 4 kHz%50 sabitFrekans tonu belirler
Fan kontrolü25 kHz (PC fan standardı)20%–100%Minimum %20 altında fan durabilir
NOT

Hardware PWM vs Software PWM: Hardware PWM, SoC'nin PWM donanım bloğuyla üretilir; CPU yükü sıfıra yakın ve timing hassastır. Software PWM, kernel veya userspace'deki timer döngüsüyle GPIO pin'i toggle eder; CPU yükü yüksek ve zamanlama daha az deterministiktir. Kritik uygulamalar (servo, motor) için her zaman hardware PWM tercih et.

01 Linux PWM subsystem

Linux PWM subsystem, donanım PWM denetleyicilerini tutarlı bir API üzerinden sunmak için kernel 3.x'te eklendi.

  Userspace   /sys/class/pwm/pwmchip0/pwm0/ (sysfs)
              veya libpwm / pigpio / RPi.GPIO (kütüphane)
                              │
  Kernel     pwm-sysfs.c → PWM subsystem core (pwm.c)
                              │
             Platform driver: pwm-bcm2835.c / pwm-imx.c / pwm-tiehrpwm.c
                              │
             Hardware PWM controller register'ları (SoC)
    

Kernel yapılandırması

kernel config
CONFIG_PWM=y                   # PWM subsystem
CONFIG_PWM_SYSFS=y             # /sys/class/pwm/ arayüzü
CONFIG_PWM_BCM2835=y           # Raspberry Pi BCM2835/2711 PWM
CONFIG_PWM_IMX=y               # NXP i.MX PWM
CONFIG_PWM_TIEHRPWM=y          # TI AM335x eHRPWM
CONFIG_PWM_PCA9685=m           # PCA9685 I²C PWM expander
bash — mevcut pwmchip'leri gör
# PWM chip'lerini listele:
ls /sys/class/pwm/
# pwmchip0  pwmchip2  (PWM chip'ler)

# Chip hakkında bilgi:
cat /sys/class/pwm/pwmchip0/npwm
# 2  (bu chip'te 2 PWM kanalı var)

cat /sys/class/pwm/pwmchip0/device/modalias

# Raspberry Pi'da hardware PWM etkinleştirme:
# /boot/config.txt'e ekle:
# dtoverlay=pwm          → GPIO18 (PWM0)
# dtoverlay=pwm-2chan    → GPIO18 (PWM0) + GPIO19 (PWM1)
NOT

Raspberry Pi 4 (BCM2711) üzerinde GPIO18 ve GPIO19 hardware PWM'i destekler. GPIO12/GPIO13 de aynı donanım bloğunu kullanır (BCM2711'de 4 kanal). SoC'ye ve pin mux yapılandırmasına göre farklı GPIO pinleri PWM olarak kullanılabilir; device tree overlay ile hangi pin'in hangi PWM kanalına bağlandığını belirle.

02 Device Tree PWM tanımı

PWM kanallarını tüketici cihazlarla (servo, LED, fan) eşleştirmek device tree'de tanımlanır.

device-tree.dts — PWM controller tanımı
/* SoC içinde PWM donanımı (genellikle mevcut): */
pwm0: pwm@7e20c000 {
    compatible = "brcm,bcm2835-pwm";
    reg = <0x7e20c000 0x28>;
    clocks = <&clocks BCM2835_CLOCK_PWM>;
    #pwm-cells = <2>;             /* (channel, period_ns) */
};
device-tree.dts — tüketici cihaz tanımı
/* Servo motor bağlantısı: */
servo0 {
    compatible = "pwm-servo";
    pwms = <&pwm0 0 20000000 0>;
    /*       ↑chip  ↑kanal ↑period_ns(20ms) ↑flags(0=normal) */
    pwm-names = "servo";
};

/* LED PWM dimming: */
led_pwm {
    compatible = "pwm-leds";
    led0 {
        label = "blue:status";
        pwms = <&pwm0 0 1000000 0>;  /* PWM0, 1ms period = 1kHz */
        max-brightness = <255>;
    };
};

/* #pwm-cells değeri:
   2 → (channel, period_ns)
   3 → (channel, period_ns, flags)  ← flags: PWM_POLARITY_INVERTED=1 */

03 Sysfs PWM arayüzü

/sys/class/pwm/ altındaki dosya sistemi arayüzüyle PWM kanallarını komut satırından tam olarak kontrol edebilirsin.

bash — PWM kanal açma ve yapılandırma
# Adım 1: pwmchip0'da kanal 0'ı export et (aç):
echo 0 | sudo tee /sys/class/pwm/pwmchip0/export
# /sys/class/pwm/pwmchip0/pwm0/ dizini oluşur

# Adım 2: Period ayarla (nanosaniye cinsinden):
# 50 Hz servo için: 1/50 = 0.02s = 20,000,000 ns
echo 20000000 | sudo tee /sys/class/pwm/pwmchip0/pwm0/period

# Adım 3: Duty cycle ayarla (nanosaniye, period'dan küçük olmalı):
# 1.5ms = 1,500,000 ns → servo orta konum
echo 1500000 | sudo tee /sys/class/pwm/pwmchip0/pwm0/duty_cycle

# Adım 4: PWM'i etkinleştir:
echo 1 | sudo tee /sys/class/pwm/pwmchip0/pwm0/enable

# Mevcut değerleri oku:
cat /sys/class/pwm/pwmchip0/pwm0/period
cat /sys/class/pwm/pwmchip0/pwm0/duty_cycle
cat /sys/class/pwm/pwmchip0/pwm0/enable
cat /sys/class/pwm/pwmchip0/pwm0/polarity

# Polarite değiştir (enable=0 iken yapılmalı):
# normal: yüksek → duty süresi, düşük → period - duty
# inversed: düşük → duty süresi
echo 0          | sudo tee /sys/class/pwm/pwmchip0/pwm0/enable
echo inversed   | sudo tee /sys/class/pwm/pwmchip0/pwm0/polarity
echo 1          | sudo tee /sys/class/pwm/pwmchip0/pwm0/enable

# Kanal kapat (unexport):
echo 0 | sudo tee /sys/class/pwm/pwmchip0/unexport
exportKanal numarasını yaz → o kanal için pwmN/ dizini oluşur.
unexportKanal numarasını yaz → pwmN/ dizini silinir ve kanal serbest kalır.
periodPeriyot süresi (nanosaniye). Duty cycle'dan büyük olmalı.
duty_cycleYüksek sinyal süresi (nanosaniye). 0 ≤ duty_cycle ≤ period.
enable0: PWM devre dışı. 1: PWM aktif.
polarity"normal" veya "inversed". enable=0 iken yazılmalı.
UYARI

Sıra önemlidir: period değerini duty_cycle'dan önce yaz. Duty cycle period'dan büyük ayarlanırsa hata döner. PWM etkinken period veya duty_cycle değiştirilmesine izin vermeyebilir (donanıma bağlı) — önce enable=0 yap.

04 Uygulama: servo motor

RC servo motorlar 50 Hz (20ms period) kontrol sinyali bekler. Pulse genişliği açı pozisyonunu belirler.

  0°        90°       180°
  │         │         │
  1ms       1.5ms     2ms
  │‾│       │‾‾│      │‾‾‾│
  │ │       │  │      │   │
  ┘ └───────┘  └──────┘   └─────
  ←────── 20ms period ──────────►
    
bash — servo kontrolü
PWM_PATH=/sys/class/pwm/pwmchip0/pwm0

# Export ve period ayarla (20ms = 50 Hz):
echo 0          | sudo tee /sys/class/pwm/pwmchip0/export
echo 20000000   | sudo tee $PWM_PATH/period
echo 1500000    | sudo tee $PWM_PATH/duty_cycle  # orta konum
echo 1          | sudo tee $PWM_PATH/enable

# 0° → minimum pulse (1ms):
echo 1000000 | sudo tee $PWM_PATH/duty_cycle
sleep 1

# 90° → orta (1.5ms):
echo 1500000 | sudo tee $PWM_PATH/duty_cycle
sleep 1

# 180° → maksimum pulse (2ms):
echo 2000000 | sudo tee $PWM_PATH/duty_cycle
sleep 1

# Kapat:
echo 0 | sudo tee $PWM_PATH/enable
echo 0 | sudo tee /sys/class/pwm/pwmchip0/unexport

Açı → duty cycle dönüşüm formülü

servo_calc.py
def angle_to_duty_ns(angle_deg, min_pulse_us=1000, max_pulse_us=2000):
    """
    Servo açısını duty cycle nanosaniyesine çevir.
    angle_deg: 0–180
    min_pulse_us: 0° için pulse genişliği (mikrosaniye, genellikle 1000)
    max_pulse_us: 180° için pulse genişliği (mikrosaniye, genellikle 2000)
    """
    angle_clamped = max(0.0, min(180.0, angle_deg))
    pulse_us = min_pulse_us + (max_pulse_us - min_pulse_us) * angle_clamped / 180.0
    return int(pulse_us * 1000)  # mikrosaniye → nanosaniye

def set_servo_angle(pwm_path, angle_deg):
    duty_ns = angle_to_duty_ns(angle_deg)
    with open(f"{pwm_path}/duty_cycle", "w") as f:
        f.write(str(duty_ns))

# Test:
for angle in [0, 45, 90, 135, 180]:
    ns = angle_to_duty_ns(angle)
    print(f"{angle:3d}° → {ns:7d} ns ({ns/1_000_000:.3f} ms)")

# 0° →  1000000 ns (1.000 ms)
# 45° →  1250000 ns (1.250 ms)
# 90° →  1500000 ns (1.500 ms)
#135° →  1750000 ns (1.750 ms)
#180° →  2000000 ns (2.000 ms)
NOT

Bazı servo motorların min/max pulse değerleri standart 1ms–2ms aralığından farklıdır. SG90 gibi bütçe servolar 0.5ms–2.4ms aralığında çalışabilir. 180° yerine 270° dönüş yapan servolar da mevcut. Servoyu zorlamadan önce min/max değerlerini kademeli olarak bul.

05 Uygulama: LED dimming

PWM ile LED parlaklığını kontrol etmek, dirençle güç kaybetmeden verimli dimming sağlar.

bash — LED dimming
PWM_PATH=/sys/class/pwm/pwmchip0/pwm0

# 1 kHz LED dimming (period = 1,000,000 ns = 1ms):
echo 0         | sudo tee /sys/class/pwm/pwmchip0/export
echo 1000000   | sudo tee $PWM_PATH/period
echo 0         | sudo tee $PWM_PATH/duty_cycle  # sönük
echo 1         | sudo tee $PWM_PATH/enable

# %25 parlaklık (250,000 ns):
echo 250000  | sudo tee $PWM_PATH/duty_cycle

# %50 parlaklık (500,000 ns):
echo 500000  | sudo tee $PWM_PATH/duty_cycle

# %100 parlaklık (tam açık):
echo 1000000 | sudo tee $PWM_PATH/duty_cycle

# Yumuşak kararma efekti (fade out):
for pct in 100 90 80 70 60 50 40 30 20 10 0; do
    duty_ns=$(( 1000000 * pct / 100 ))
    echo "$duty_ns" | sudo tee $PWM_PATH/duty_cycle
    sleep 0.1
done

PWM LED parlaklık: lineer mi, logaritmik mi?

İnsan gözü parlaklığı logaritmik algılar. Lineer duty cycle artışı parlaklığın ani artıyormuş gibi hissettirmesine yol açar. Daha doğal görünen geçiş için gamma düzeltme veya logaritmik skala kullan:

led_gamma.py
import math
import time

PWM_PATH = "/sys/class/pwm/pwmchip0/pwm0"
PERIOD_NS = 1_000_000   # 1 kHz
GAMMA     = 2.2

def write_pwm(path, value):
    with open(path, "w") as f:
        f.write(str(value))

def setup_pwm():
    with open("/sys/class/pwm/pwmchip0/export", "w") as f:
        f.write("0")
    write_pwm(f"{PWM_PATH}/period",     PERIOD_NS)
    write_pwm(f"{PWM_PATH}/duty_cycle", 0)
    write_pwm(f"{PWM_PATH}/enable",     1)

def set_brightness(percent):
    """0–100% parlaklığı gamma-düzeltilmiş duty cycle'a çevir."""
    linear = percent / 100.0
    gamma_corrected = linear ** GAMMA
    duty_ns = int(gamma_corrected * PERIOD_NS)
    write_pwm(f"{PWM_PATH}/duty_cycle", duty_ns)

setup_pwm()

try:
    # Lineer fazla efekti:
    for step in range(101):
        set_brightness(step)
        time.sleep(0.02)
    for step in range(100, -1, -1):
        set_brightness(step)
        time.sleep(0.02)
finally:
    write_pwm(f"{PWM_PATH}/enable", 0)
    with open("/sys/class/pwm/pwmchip0/unexport", "w") as f:
        f.write("0")

06 C ile PWM: sysfs API

Sysfs tabanlı PWM kontrolü C kodundan dosyaya yazma/okuma ile gerçekleştirilir. Düşük latency gerektirmeyen uygulamalar için yeterlidir.

pwm_sysfs.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>

#define PWM_BASE    "/sys/class/pwm/pwmchip0"
#define PWM_CHANNEL 0

static int pwm_write(const char *path, long long val)
{
    char buf[64];
    snprintf(buf, sizeof(buf), "%lld\n", val);

    int fd = open(path, O_WRONLY);
    if (fd < 0) { perror(path); return -1; }
    int ret = write(fd, buf, strlen(buf));
    close(fd);
    return (ret < 0) ? -1 : 0;
}

static int pwm_write_str(const char *path, const char *val)
{
    int fd = open(path, O_WRONLY);
    if (fd < 0) { perror(path); return -1; }
    int ret = write(fd, val, strlen(val));
    close(fd);
    return (ret < 0) ? -1 : 0;
}

int pwm_export(int channel)
{
    return pwm_write(PWM_BASE "/export", channel);
}

int pwm_unexport(int channel)
{
    return pwm_write(PWM_BASE "/unexport", channel);
}

int pwm_setup(int ch, long long period_ns, long long duty_ns)
{
    char path[128];

    snprintf(path, sizeof(path), PWM_BASE "/pwm%d/period",     ch);
    if (pwm_write(path, period_ns) < 0) return -1;

    snprintf(path, sizeof(path), PWM_BASE "/pwm%d/duty_cycle", ch);
    if (pwm_write(path, duty_ns)   < 0) return -1;

    snprintf(path, sizeof(path), PWM_BASE "/pwm%d/enable",     ch);
    return pwm_write(path, 1);
}

int pwm_set_duty(int ch, long long duty_ns)
{
    char path[128];
    snprintf(path, sizeof(path), PWM_BASE "/pwm%d/duty_cycle", ch);
    return pwm_write(path, duty_ns);
}

int main(void)
{
    const int  ch        = PWM_CHANNEL;
    const long long period = 20000000LL;  /* 20ms = 50 Hz servo */

    if (pwm_export(ch) < 0 && errno != EBUSY) return 1;
    if (pwm_setup(ch, period, 1500000LL) < 0) return 1;  /* 1.5ms, orta */

    printf("Servo: 0° → 90° → 180° → 0°\n");

    long long angles[] = { 1000000, 1500000, 2000000, 1000000 };
    for (int i = 0; i < 4; i++) {
        pwm_set_duty(ch, angles[i]);
        sleep(1);
    }

    char path[128];
    snprintf(path, sizeof(path), PWM_BASE "/pwm%d/enable", ch);
    pwm_write(path, 0);
    pwm_unexport(ch);
    return 0;
}
bash — derleme
gcc -Wall -o pwm_sysfs pwm_sysfs.c
sudo ./pwm_sysfs

07 Python ile PWM: pigpio ve RPi.GPIO karşılaştırması

Raspberry Pi özelindeki iki kütüphane: RPi.GPIO (software PWM) ve pigpio (hardware PWM + DMA). Her ikisini karşılaştıralım.

pigpio — hardware + DMA tabanlı

bash — pigpio kurulum ve daemon
sudo apt install pigpio python3-pigpio

# pigpiod daemon'u başlat:
sudo systemctl start pigpiod
sudo systemctl enable pigpiod
pigpio_servo.py
import pigpio
import time

pi = pigpio.pi()
if not pi.connected:
    raise RuntimeError("pigpiod bağlanamadı")

SERVO_PIN = 18   # GPIO18: hardware PWM pini

def set_servo(pin, angle_deg):
    """0–180° arasında servo konumu ayarla (pigpio)."""
    pulse_us = 1000 + int(angle_deg / 180.0 * 1000)  # 1000–2000 µs
    pi.set_servo_pulsewidth(pin, pulse_us)

try:
    for angle in [0, 45, 90, 135, 180, 90]:
        set_servo(SERVO_PIN, angle)
        print(f"Açı: {angle}°")
        time.sleep(1.0)
finally:
    pi.set_servo_pulsewidth(SERVO_PIN, 0)   # servo servobest bırak
    pi.stop()

RPi.GPIO — software PWM

rpigpio_pwm.py
import RPi.GPIO as GPIO
import time

GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)

LED_PIN   = 18
FREQ_HZ   = 1000   # 1 kHz (software PWM)

GPIO.setup(LED_PIN, GPIO.OUT)
pwm = GPIO.PWM(LED_PIN, FREQ_HZ)
pwm.start(0)   # %0 duty ile başla

try:
    # Fade in:
    for dc in range(0, 101, 5):
        pwm.ChangeDutyCycle(dc)
        time.sleep(0.05)
    # Fade out:
    for dc in range(100, -1, -5):
        pwm.ChangeDutyCycle(dc)
        time.sleep(0.05)
finally:
    pwm.stop()
    GPIO.cleanup()

Karşılaştırma tablosu

ÖzellikRPi.GPIO (software PWM)pigpio (DMA PWM)sysfs (hardware PWM)
Donanım bağımsızEvet (herhangi GPIO)Evet (DMA ile)Hayır (hardware PWM pini)
Timing hassasiyetiDüşük (~ms jitter)Yüksek (~1µs jitter)En yüksek (<100ns)
CPU kullanımıYüksek (threading)Düşük (DMA)Sıfır (donanım)
Servo için uygunKısmen (jitter sorunlu)EvetEvet
LED dimmingEvetEvetEvet
Daemon gerektirirHayırEvet (pigpiod)Hayır
Frekans üst sınır~1 kHz (pratik)~40 kHzSoC limitine kadar
NOT

Servo ve motor kontrolü için pigpio veya sysfs hardware PWM tercih et. RPi.GPIO'nun software PWM'i timing jitter'ı nedeniyle servo'yu titretir ve hassas motor kontrolü için yetersiz kalır. LED dimming için software PWM yeterlidir çünkü gözün algıladığı ortalama parlaklık önemlidir, mikrosaniye jitter değil.