00 RS-485 ve RS-422 nedir
EIA/TIA-485 ve EIA/TIA-422 — 1980'lerin başında RS-232'nin mesafe ve hız sınırlarını aşmak için tasarlanan diferansiyel seri haberleşme standartları.
RS-232'nin sınırları
RS-232 single-ended (tek uçlu) çalışır: sinyal GND referansına göre ölçülür. Bu yapı iki önemli kısıtlama getirir: maksimum kablo uzunluğu ~15 metre, maksimum baud rate ise ~115200 bps. Endüstriyel ortamlarda elektriksel gürültü, uzun kablo kapasitansı ve toprak farkı (ground loop) RS-232'yi kullanılamaz hale getirir.
| Standart | Max mesafe | Max hız | Cihaz sayısı | Sinyal türü |
|---|---|---|---|---|
| RS-232 | 15 m | ~115 kbps | 1:1 | Single-ended |
| RS-422 | 1200 m | 10 Mbps | 1 sürücü, 10 alıcı | Diferansiyel (tam duplex) |
| RS-485 | 1200 m | 10 Mbps | 32 birim yük (max 256) | Diferansiyel (yarı duplex) |
Diferansiyel sinyal avantajı
Diferansiyel sinyalde bilgi, iki tel arasındaki voltaj farkıyla taşınır. Dışarıdan gelen gürültü her iki tele de eşit miktarda bindiğinden (common-mode noise), alıcı farkı hesaplayarak gürültüyü yok eder. Bu prensibe Common Mode Rejection denir.
Gürültüsüz: A = +2.5V, B = −2.5V → V_diff = +5V (logic 1)
Gürültülü: A = +2.5V + 1V = +3.5V
B = −2.5V + 1V = −1.5V → V_diff = +5V (logic 1 - gürültü yok)
RS-485 sinyali:
┌───────────────────────────────────────────────────────┐
│ A ─┐ ┌─────────────────────────────────── │
│ │ │ │
│ └──────────┘ │
│ B ──────────┐ ┌────────────────────────── │
│ │ │ │
│ └──────────┘ │
│ SPACE (0) MARK (1) │
└───────────────────────────────────────────────────────┘
Kullanım alanları
RS-485 vs RS-422 farkı
İkisi de aynı diferansiyel elektriksel katmanı kullanır; fark topoloji ve yön kontrolündedir:
- RS-422: Tam duplex (4-tel: TX+, TX−, RX+, RX−), tek sürücü, max 10 alıcı. Point-to-point veya broadcast.
- RS-485: Yarı duplex (2-tel: A/B), multidrop (32 birim yük), tek bus üzerinde çok sürücü. Endüstriyel ağlarda tercih edilir.
RS-485 ve RS-422 yalnızca fiziksel/elektriksel katmanı tanımlar. Üst katmanda hangi protokolün (Modbus, PROFIBUS, DMX vb.) çalışacağı ayrıca belirlenir. RS-485 = kablo standartı; Modbus = protokol.
01 Elektriksel katman
RS-485 diferansiyel sinyal seviyeleri, alıcı eşikleri, terminasyon ve bias — doğru kablolama için temel kavramlar.
Sinyal seviyeleri ve alıcı eşiği
| Durum | A − B voltajı | Anlam |
|---|---|---|
| Logic 1 (mark) | +200 mV ile +6 V | Veri 1 (recessive) |
| Logic 0 (space) | −200 mV ile −6 V | Veri 0 (dominant) |
| Belirsiz bölge | −200 mV ile +200 mV | Alıcı çıkışı tanımsız |
Sürücü çıkış gerilimi tipik olarak ±1.5 V ile ±5 V arasındadır. Alıcı ise yalnızca ±200 mV fark eşiğine ihtiyaç duyar — bu da 10:1 gürültü marjı sağlar. Common-mode aralığı −7 V ile +12 V'dur; toprak farkı bu aralıkta kaldığı sürece iletişim sağlanır.
Terminasyon direnci
RS-485 bus'ı bir iletim hattıdır. Kablo empedansı tipik olarak 100–120 Ω'dur. Bus'ın her iki ucuna kablo empedansıyla eşleşen terminasyon direnci bağlanmazsa sinyal yansımalar (reflections) oluşur ve veri bozulur.
┌──────┐ 120Ω A/B bus 120Ω ┌──────┐
│ Node │──┤├──────────────────────────────────┤├───│ Node │
│ 0 │ │ N │
└──────┘ ← terminasyon terminasyon → └──────┘
sadece bus uçlarında!
Bias (fail-safe) dirençleri
Bus boştayken (idle) hiçbir sürücü aktif değilse A ve B hatları belirsiz kalır. Fail-safe için A hattı Vcc'ye, B hattı GND'ye bağlanan bias dirençleri (genellikle 560 Ω – 1 kΩ) eklenir. Bu, boşta iken bus'ı bilinen bir durumda tutar ve alıcıların yanlış data üretmesini önler.
Mesafe ve hız dengesi
| Baud rate | Max mesafe | Uygulama |
|---|---|---|
| 9600 bps | 1200 m | Modbus RTU, enerji sayaçları |
| 115200 bps | ~400 m | PROFIBUS düşük hız |
| 1 Mbps | ~100 m | PROFIBUS DP orta hız |
| 12 Mbps | ~10 m | PROFIBUS DP maksimum hız |
Terminasyon direncini sadece bus'ın fiziksel uçlarına bağla. Ortadaki node'lara terminasyon eklersen bus'ı aşırı yükler ve sinyal kalitesi düşer. Birden fazla terminasyon direnci yanlış çıkışlara neden olur.
02 Topoloji
RS-485 yarı duplex 2-telli veya tam duplex 4-telli bağlantı; multidrop bus topolojisi ve izolasyonlu transceiver seçenekleri.
2-tel yarı duplex (half-duplex)
En yaygın RS-485 konfigürasyonu. TX ve RX aynı iki tel (A/B) üzerinden sıraya göre iletilir. Her seferinde sadece bir node konuşabilir. Modbus RTU bu modeli kullanır.
Master Slave 1 Slave 2 Slave N
┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐
│ A/B ├───────┤ A/B ├──────┤ A/B ├─────┤ A/B │
└──────┘ └──────┘ └──────┘ └──────┘
[120Ω] [120Ω]
← daisy chain / multidrop bus →
4-tel tam duplex (full-duplex, RS-422 modeli)
TX ve RX ayrı çiftler üzerinden iletilir. Aynı anda hem gönderme hem alma mümkündür. Genellikle point-to-point veya 1 master N slave konfigürasyonunda kullanılır.
Master Slave
┌──────┐ TX+ ───────► ┌──────┐
│ │ TX− ───────► │ │
│ │ RX+ ◄─────── │ │
│ │ RX− ◄─────── │ │
└──────┘ └──────┘
Birim yük (unit load) ve node sayısı
RS-485 standardı her transceiver'ın en fazla 1 birim yük (unit load) çekebileceğini tanımlar ve sürücünün 32 birim yük süreceğini garanti eder. Düşük güç (1/4 veya 1/8 unit load) transceiver'lar kullanıldığında aynı bus'a 128 veya 256 node bağlanabilir.
| Transceiver tipi | Birim yük | Max node sayısı | Örnek çip |
|---|---|---|---|
| Standart | 1 UL | 32 | MAX485, SN75176 |
| 1/2 UL | 0.5 UL | 64 | MAX3082 |
| 1/4 UL | 0.25 UL | 128 | SN65HVD1782 |
| 1/8 UL | 0.125 UL | 256 | ADM485, SP485 |
İzolasyonlu transceiver
Yüksek gerilim ortamlarında veya büyük toprak farkının olduğu sistemlerde izolasyonlu RS-485 transceiver kullanmak gerekir. Bu çipler galvanik izolasyon sağlar ve 1500–5000 Vrms izolasyon gerilimi sunar.
03 Linux UART ile RS-485
Linux kernel, struct serial_rs485 ve TIOCSRS485 ioctl ile RS-485 half-duplex yön kontrolünü doğrudan seri port driver üzerinden yönetir.
struct serial_rs485
/* /usr/include/linux/serial.h veya kernel kaynak kodu: */
struct serial_rs485 {
__u32 flags; /* SER_RS485_ENABLED vb. bayraklar */
__u32 delay_rts_before_send; /* TX öncesi RTS gecikmesi (ms) */
__u32 delay_rts_after_send; /* TX sonrası RTS gecikmesi (ms) */
__u32 padding[5]; /* gelecek kullanım için */
};
/* Kullanılabilir bayraklar: */
/* SER_RS485_ENABLED (1 << 0) RS-485 modu aktif */
/* SER_RS485_RTS_ON_SEND (1 << 1) TX sırasında RTS yüksek */
/* SER_RS485_RTS_AFTER_SEND (1 << 2) TX sonrası RTS yüksek */
/* SER_RS485_RX_DURING_TX (1 << 4) TX sırasında RX etkin */
TIOCSRS485 ve TIOCGRS485 ioctl
#include <linux/serial.h>
#include <sys/ioctl.h>
#include <fcntl.h>
int rs485_enable(const char *port)
{
int fd = open(port, O_RDWR | O_NOCTTY);
if (fd < 0) { perror("open"); return -1; }
struct serial_rs485 rs485 = {0};
/* Mevcut yapılandırmayı oku: */
if (ioctl(fd, TIOCGRS485, &rs485) < 0) {
perror("TIOCGRS485");
}
/* RS-485 half-duplex modunu etkinleştir: */
rs485.flags |= SER_RS485_ENABLED;
rs485.flags |= SER_RS485_RTS_ON_SEND; /* TX sırasında DE=high */
rs485.flags &= ~SER_RS485_RTS_AFTER_SEND; /* TX sonrası DE=low */
rs485.delay_rts_before_send = 1; /* 1 ms gecikme */
rs485.delay_rts_after_send = 1;
if (ioctl(fd, TIOCSRS485, &rs485) < 0) {
perror("TIOCSRS485");
close(fd);
return -1;
}
return fd;
}
Device Tree ile RS-485 yapılandırması
Kernel boot sırasında RS-485 modunu otomatik olarak etkinleştirmek için Device Tree'ye eklemek yeterlidir:
&uart1 {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&uart1_pins>;
linux,rs485-enabled-at-boot-time; /* boot'ta RS-485 etkin */
rs485-rts-active-high; /* DE pini aktif-yüksek */
rs485-rx-during-tx; /* TX sırasında RX aktif değil */
/* RTS/DE pini için GPIO belirtme (bazı sürücülerde): */
rts-gpios = <&gpio0 4 GPIO_ACTIVE_HIGH>;
};
Tüm UART sürücüleri TIOCSRS485 desteklemez. Raspberry Pi PL011 (ttyAMA0), BeagleBone OMAP UART ve STM32 sürücüleri destekler. Destek yoksa yazılım seviyesinde RTS toggle ile yönlendirme yapılmalıdır.
04 Otomatik yön kontrolü
RS-485 half-duplex bus'ta TX ve RX aynı A/B hattını paylaşır. Sürücü (DE/RE) yön pininin doğru zamanda anahtarlanması kritiktir.
Yönlendirme sorunu
UART sadece TX ve RX sinyalleri üretir — RS-485 transceiver'ının Driver Enable (DE) ve Receiver Enable (RE) pinlerini kontrol etmez. TX başlamadan önce DE=HIGH (sürücü aktif), TX bittikten sonra DE=LOW (alıcı aktif) yapılmalıdır. Zamanlama hatalı olursa çerçeve başlangıcı veya sonu kesilir.
UART TX ─┐ ┌──
│ Veri bit'leri │
└────────────────────────────┘
DE (RTS) ──┐ ┌──
│ delay_before ← │
└─┬──────────────────────────┬─┘
│← TX aktif │→ delay_after
Kernel otomatik yön kontrolü
Modern kernel (linux,rs485-enabled-at-boot-time + TIOCSRS485) DE pinini TX zamanlamasına göre otomatik sürer. delay_rts_before_send ve delay_rts_after_send değerleri ince ayar sağlar.
USB-RS485 adaptörler ve otomatik yön kontrolü
CP2102N ve FT232H tabanlı USB-RS485 adaptörler, entegre transceiver sayesinde otomatik yön kontrolü yapar. Yazılım tarafında ekstra işlem gerekmez:
Yazılım RTS toggle (kernel desteği yoksa)
#include <sys/ioctl.h>
#include <termios.h>
/* RTS'yi yüksek yap → DE=HIGH (sürücü aktif) */
static void rts_high(int fd) {
int flags;
ioctl(fd, TIOCMGET, &flags);
flags |= TIOCM_RTS;
ioctl(fd, TIOCMSET, &flags);
}
/* RTS'yi düşür → DE=LOW (alıcı aktif) */
static void rts_low(int fd) {
int flags;
ioctl(fd, TIOCMGET, &flags);
flags &= ~TIOCM_RTS;
ioctl(fd, TIOCMSET, &flags);
}
void rs485_write(int fd, const unsigned char *data, int len) {
rts_high(fd); /* DE=HIGH */
usleep(100); /* küçük gecikme */
write(fd, data, len);
tcdrain(fd); /* TX tamamlanana kadar bekle */
usleep(100);
rts_low(fd); /* DE=LOW → alıcıya geç */
}
Yazılım RTS toggle yöntemi, real-time olmayan Linux kernel'da zamanlama sorunlarına yol açabilir. Endüstriyel uygulamalarda hardware otomatik yön kontrolü veya kernel TIOCSRS485 destekli UART tercih et.
05 C kod örneği
RS-485 üzerinde Modbus RTU protokolü ile sensör okuma — tam C örneği: port açma, termios ayarları, RS-485 modu, frame gönderme ve yanıt okuma.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>
#include <sys/ioctl.h>
#include <linux/serial.h>
#include <stdint.h>
/* Modbus RTU CRC16 hesabı */
uint16_t modbus_crc16(const uint8_t *buf, int len) {
uint16_t crc = 0xFFFF;
for (int i = 0; i < len; i++) {
crc ^= (uint16_t)buf[i];
for (int b = 0; b < 8; b++) {
if (crc & 0x0001) crc = (crc >> 1) ^ 0xA001;
else crc >>= 1;
}
}
return crc;
}
/* Serial port aç ve termios ayarla */
int uart_open(const char *device, speed_t baud) {
int fd = open(device, O_RDWR | O_NOCTTY | O_NDELAY);
if (fd < 0) { perror("open"); return -1; }
struct termios tty;
memset(&tty, 0, sizeof(tty));
tcgetattr(fd, &tty);
cfsetispeed(&tty, baud);
cfsetospeed(&tty, baud);
tty.c_cflag = (tty.c_cflag & ~CSIZE) | CS8; /* 8 data bit */
tty.c_cflag &= ~PARENB; /* parity yok */
tty.c_cflag &= ~CSTOPB; /* 1 stop bit */
tty.c_cflag &= ~CRTSCTS; /* hw flow ctrl yok */
tty.c_cflag |= CREAD | CLOCAL;
tty.c_lflag = 0;
tty.c_iflag = 0;
tty.c_oflag = 0;
tty.c_cc[VMIN] = 0;
tty.c_cc[VTIME] = 20; /* 2 saniyelik zaman aşımı */
tcflush(fd, TCIFLUSH);
tcsetattr(fd, TCSANOW, &tty);
/* RS-485 modunu etkinleştir */
struct serial_rs485 rs485 = {0};
rs485.flags = SER_RS485_ENABLED | SER_RS485_RTS_ON_SEND;
rs485.delay_rts_before_send = 1;
rs485.delay_rts_after_send = 1;
ioctl(fd, TIOCSRS485, &rs485);
fcntl(fd, F_SETFL, 0);
return fd;
}
/* Modbus RTU Read Holding Registers (FC03) */
int modbus_read_regs(int fd, uint8_t slave_addr,
uint16_t start_reg, uint16_t count,
uint16_t *out)
{
uint8_t req[8];
req[0] = slave_addr;
req[1] = 0x03; /* FC03: Read Holding Registers */
req[2] = (start_reg >> 8) & 0xFF;
req[3] = start_reg & 0xFF;
req[4] = (count >> 8) & 0xFF;
req[5] = count & 0xFF;
uint16_t crc = modbus_crc16(req, 6);
req[6] = crc & 0xFF;
req[7] = (crc >> 8) & 0xFF;
write(fd, req, 8);
tcdrain(fd);
uint8_t resp[5 + count * 2];
int n = read(fd, resp, sizeof(resp));
if (n < 5 || resp[0] != slave_addr || resp[1] != 0x03) {
fprintf(stderr, "Modbus yanıt hatası\n");
return -1;
}
for (int i = 0; i < count; i++)
out[i] = ((uint16_t)resp[3 + i*2] << 8) | resp[4 + i*2];
return count;
}
int main(void)
{
int fd = uart_open("/dev/ttyS1", B9600);
if (fd < 0) return 1;
uint16_t regs[10];
if (modbus_read_regs(fd, 1, 0x0000, 10, regs) == 10) {
for (int i = 0; i < 10; i++)
printf("Reg[%d] = 0x%04X (%d)\n", i, regs[i], regs[i]);
}
close(fd);
return 0;
}
gcc -o rs485_modbus rs485_modbus.c
sudo ./rs485_modbus
# Reg[0] = 0x0215 (533)
# Reg[1] = 0x00C8 (200) ← sıcaklık: 20.0°C (×0.1)
06 Python ile pyserial RS-485
pyserial kütüphanesi RS-485 modunu doğrudan destekler. Üst seviye Modbus için pymodbus ile birlikte kullanılır.
pyserial ile RS-485 modu
pip install pyserial pymodbus
import serial
import serial.rs485
# RS-485 half-duplex portunu aç
ser = serial.Serial(
port='/dev/ttyUSB0',
baudrate=9600,
bytesize=serial.EIGHTBITS,
parity=serial.PARITY_NONE,
stopbits=serial.STOPBITS_ONE,
timeout=1.0
)
# RS-485 modunu etkinleştir
ser.rs485_mode = serial.rs485.RS485Settings(
rts_level_for_tx=True, # TX sırasında RTS=HIGH
rts_level_for_rx=False, # RX sırasında RTS=LOW
delay_before_tx=0.001, # 1ms gecikme
delay_before_rx=0.001
)
# Modbus RTU frame gönder (slave 1, FC03, reg 0-9)
frame = bytes([0x01, 0x03, 0x00, 0x00, 0x00, 0x0A, 0xC5, 0xCD])
ser.write(frame)
# Yanıtı oku
response = ser.read(25)
print('Yanıt:', response.hex())
pymodbus ile Modbus RTU master
from pymodbus.client import ModbusSerialClient
# RTU master bağlantısı
client = ModbusSerialClient(
port='/dev/ttyUSB0',
baudrate=9600,
bytesize=8,
parity='N',
stopbits=1,
timeout=1
)
if client.connect():
# Slave 1'den 10 holding register oku (adres 0x0000)
result = client.read_holding_registers(address=0, count=10, slave=1)
if not result.isError():
for i, val in enumerate(result.registers):
print(f'Reg[{i}] = {val} (0x{val:04X})')
else:
print('Hata:', result)
client.close()
else:
print('Bağlanamadı')
Polling ile periyodik okuma
import time
from pymodbus.client import ModbusSerialClient
client = ModbusSerialClient(port='/dev/ttyUSB0', baudrate=9600, timeout=1)
client.connect()
try:
while True:
result = client.read_holding_registers(address=0, count=2, slave=1)
if not result.isError():
temp = result.registers[0] / 10.0 # ×0.1 ölçek faktörü
hum = result.registers[1] / 10.0
print(f'Sıcaklık: {temp:.1f}°C Nem: {hum:.1f}%')
time.sleep(1.0)
except KeyboardInterrupt:
client.close()
07 Termination ve sinyal kalitesi
Yanlış terminasyon, kötü kablo kalitesi ve EMI — RS-485 sorunlarının büyük çoğunluğunun kökeni fiziksel katmandır.
Yanlış terminasyon belirtileri
| Belirti | Muhtemel neden | Çözüm |
|---|---|---|
| Framing error / CRC hatası | Terminasyon eksik → reflection | Her iki uca 120Ω ekle |
| Sinyal titreyişi (ringing) | Terminasyon eksik + yüksek hız | 120Ω + ferrite bead |
| Rastgele bit flip | Common-mode gürültü | İzolasyonlu transceiver |
| İlk byte kayıp | DE timing çok kısa | delay_rts_before_send artır |
| Son byte kayıp | DE erken düşüyor | delay_rts_after_send artır |
| Echo (kendi verisini okuma) | DE/RE pinleri yanlış | SER_RS485_RX_DURING_TX kaldır |
Oscilloscope analizi
RS-485 sinyalini ölçmek için diferansiyel prob kullan (A ve B hatlarını aynı anda ölç). Single-ended ölçüm common-mode gürültüyü sinyale katar ve yanıltıcı sonuç verir. Tipik sorun görüntüleri:
Normal sinyal: Ringing (reflection): Gürültülü sinyal: ┌────────────┐ ┌──┐~┌─────────┐ ┌─/\/─────────┐ │ │ │ │ │ │ │ │ ─┘ └── ─┘ └─ └── ─┘ └─
Kablo kalitesi
08 Pratik: Embedded board ile Modbus RTU ve DMX512
BeagleBone Black veya Raspberry Pi ile RS-485 Modbus RTU master kurulumu, 32-node daisy-chain, endüstriyel sensör okuma ve DMX512 LED kontrolü.
BeagleBone Black — RS-485 bağlantısı
# BeagleBone Black UART1 pinleri: P9_24 (TX), P9_26 (RX), P9_19 (RTS/DE)
# MAX485 transceiver bağlantısı:
# TXD → MAX485 DI (pin 4)
# RXD → MAX485 RO (pin 1)
# RTS → MAX485 DE+RE (pin 2+3, birleştir)
# MAX485 A/B → RS-485 bus
# UART1 aktifleştirme:
sudo config-pin P9_24 uart
sudo config-pin P9_26 uart
# Terminasyon dirençleri (120Ω) bus uçlarına
# Bias dirençleri (560Ω): A → 3.3V, B → GND
# Port kontrolü:
ls /dev/ttyS1
dmesg | grep tty
32-node Modbus RTU ağı
from pymodbus.client import ModbusSerialClient
client = ModbusSerialClient(port='/dev/ttyS1', baudrate=9600, timeout=0.5)
client.connect()
online_nodes = []
for slave_id in range(1, 33): # 1–32 arası tara
result = client.read_holding_registers(0, 1, slave=slave_id)
if not result.isError():
online_nodes.append(slave_id)
print(f'[OK] Slave {slave_id}: reg[0] = {result.registers[0]}')
else:
print(f'[--] Slave {slave_id}: yanıt yok')
print(f'\nToplamda {len(online_nodes)} node bulundu: {online_nodes}')
client.close()
DMX512 LED dimmer kontrolü
import serial, time
# DMX512: 250 kbps, 8N2
dmx = serial.Serial('/dev/ttyUSB0', baudrate=250000,
bytesize=8, parity='N', stopbits=2)
def dmx_send(channels: list):
"""512 kanallık DMX frame gönder."""
# BREAK: 88µs dominant (port'u geçici 100kbps'e al)
dmx.break_condition = True
time.sleep(0.0001) # 100µs break
dmx.break_condition = False
time.sleep(0.000012) # 12µs MAB (Mark After Break)
# Start code + 512 kanal
frame = bytes([0x00]) + bytes(channels[:512]).ljust(512, b'\x00')
dmx.write(frame)
# Kanal 1-3: RGB LED (kırmızı=200, yeşil=100, mavi=50)
while True:
dmx_send([200, 100, 50] + [0] * 509)
time.sleep(0.025) # ~40 fps
DMX512 standart RS-485 donanımıyla çalışır, ancak BREAK sinyali özel bit-bang veya hardware break desteği gerektirir. Gerçek DMX sahne kullanımı için ENTTEC DMX USB Pro veya benzeri adanmış adaptör kullanmak daha güvenilirdir.