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 ────►
Kullanım senaryoları
| Uygulama | Frekans | Duty cycle aralığı | Açıklama |
|---|---|---|---|
| RC servo motor | 50 Hz (20ms period) | 1ms–2ms pulse (5%–10%) | Açı 0°–180° arası |
| DC motor hız | 1–20 kHz | 0%–100% | Yüksek frekans ses kirliliğini önler |
| LED dimming | 100 Hz – 10 kHz | 0%–100% | 100 Hz üstünde göz titreşim görmez |
| Buzzer | 1 kHz – 4 kHz | %50 sabit | Frekans tonu belirler |
| Fan kontrolü | 25 kHz (PC fan standardı) | 20%–100% | Minimum %20 altında fan durabilir |
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ı
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
# 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)
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.
/* 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) */
};
/* 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.
# 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
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 ──────────►
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ü
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)
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.
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:
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.
#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;
}
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ı
sudo apt install pigpio python3-pigpio
# pigpiod daemon'u başlat:
sudo systemctl start pigpiod
sudo systemctl enable pigpiod
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
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
| Özellik | RPi.GPIO (software PWM) | pigpio (DMA PWM) | sysfs (hardware PWM) |
|---|---|---|---|
| Donanım bağımsız | Evet (herhangi GPIO) | Evet (DMA ile) | Hayır (hardware PWM pini) |
| Timing hassasiyeti | Düşü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 uygun | Kısmen (jitter sorunlu) | Evet | Evet |
| LED dimming | Evet | Evet | Evet |
| Daemon gerektirir | Hayır | Evet (pigpiod) | Hayır |
| Frekans üst sınır | ~1 kHz (pratik) | ~40 kHz | SoC limitine kadar |
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.