Die RS485-Schnittstelle ist ein echtes Arbeitstier in der industriellen Automatisierung. Sie ermöglicht robuste und zuverlässige Kommunikation über weite Strecken und in störungsreichen Umgebungen. Wenn Sie einen Raspberry Pi in Ihren Projekten verwenden und direkt in C programmieren möchten, um diese Schnittstelle auszulesen, sind Sie hier genau richtig. Dieser Artikel führt Sie Schritt für Schritt durch die Hardware-Einrichtung, die notwendigen Software-Konfigurationen und zeigt Ihnen, wie Sie mit einem C-Programm Daten über RS485 senden und empfangen können.
Warum RS485, Raspberry Pi und C?
Die Vorteile von RS485
RS485 ist ein Standard für die serielle Datenübertragung, der sich durch seine Differenzialsignalisierung auszeichnet. Das bedeutet, Daten werden als Spannungsdifferenz zwischen zwei Leitungen (A und B) übertragen, anstatt gegen eine gemeinsame Masse. Dies macht die Kommunikation extrem unempfindlich gegenüber elektromagnetischen Störungen und ermöglicht Übertragungsdistanzen von bis zu 1200 Metern bei Datenraten von bis zu 10 Mbit/s. Ein weiterer großer Vorteil ist die Möglichkeit, mehrere Geräte an einem gemeinsamen Bus zu betreiben (Multidrop-Fähigkeit), wobei bis zu 32 Transceiver (oder mehr mit Repeatern) kommunizieren können.
Der Raspberry Pi als Kommandozentrale
Der Raspberry Pi ist weit mehr als nur ein Hobby-Computer. Seine kompakte Größe, seine leistungsstarke Hardware, die auf einem Linux-Betriebssystem läuft, und die vielseitigen GPIO-Pins (General Purpose Input/Output) machen ihn zur idealen Plattform für industrielle Steuerungs- und Überwachungsanwendungen. Er bietet eine kostengünstige und flexible Lösung, um Daten von Sensoren, PLCs oder anderen Systemen über Schnittstellen wie RS485 zu erfassen und zu verarbeiten.
Die Kraft von C für direkte Hardware-Kontrolle
Während Python auf dem Raspberry Pi sehr beliebt ist, bietet C Programmierung entscheidende Vorteile, wenn es um die direkte Interaktion mit Hardware und performance-kritische Anwendungen geht. C-Code ist oft schneller, speichereffizienter und ermöglicht eine tiefere Kontrolle über die Systemressourcen. Für die serielle Kommunikation, insbesondere wenn es auf präzises Timing oder die Verarbeitung großer Datenmengen ankommt, ist C oft die bevorzugte Wahl. Es ermöglicht den direkten Zugriff auf die Linux-Systemaufrufe für die serielle Schnittstelle, was maximale Flexibilität und Leistung bietet.
Hardware-Einrichtung: Raspberry Pi und RS485-Modul
Bevor wir uns dem Code widmen, müssen wir die Hardware korrekt einrichten.
Benötigte Komponenten:
- Raspberry Pi (jedes Modell mit GPIO-Pins und UART ist geeignet, z.B. Raspberry Pi 3, 4 oder Zero W)
- RS485 Transceiver-Modul (z.B. basierend auf MAX485, SP3485 oder SN75176). Diese Module wandeln die TTL-Signale des Raspberry Pi in die RS485-Differenzialsignale um.
- Jumper-Kabel
- Optional: Ein zweites RS485-Gerät zum Testen (z.B. ein weiterer Pi mit Modul, ein Modbus-Sensor etc.)
Schaltplan und Verkabelung:
Die serielle Schnittstelle (UART) des Raspberry Pi befindet sich auf den GPIO-Pins. Standardmäßig sind GPIO14 (TXD) und GPIO15 (RXD) dafür vorgesehen. Ein RS485-Transceiver-Modul benötigt üblicherweise folgende Anschlüsse:
- VCC (5V oder 3.3V, je nach Modul – der Pi liefert beides)
- GND (Masse)
- RO (Receiver Output) – wird mit dem RXD (GPIO15) des Raspberry Pi verbunden
- DI (Driver Input) – wird mit dem TXD (GPIO14) des Raspberry Pi verbunden
- DE (Driver Enable) – Aktiviert den RS485-Treiber (Senden)
- RE (Receiver Enable) – Aktiviert den RS485-Empfänger (Empfangen)
- A (RS485+ Leitung)
- B (RS485- Leitung)
Wichtiger Hinweis zu DE und RE: Da RS485 oft halbduplex ist (entweder senden oder empfangen), müssen DE und RE über einen GPIO-Pin des Raspberry Pi gesteuert werden. Viele Module haben DE und RE bereits gebrückt, sodass sie zusammen an einen GPIO-Pin angeschlossen werden können. Wenn dieser GPIO auf HIGH gesetzt wird, ist der Treiber aktiv (Senden), bei LOW der Empfänger (Empfangen). Achten Sie auf die Logik Ihres Moduls; manchmal ist RE aktiv-niedrig. Für unser Beispiel gehen wir davon aus, dass DE und RE gebrückt sind und ein HIGH-Signal zum Senden aktiviert.
Beispiel-Verkabelung (MAX485-Modul):
- Raspberry Pi GPIO14 (TXD) -> MAX485 DI
- Raspberry Pi GPIO15 (RXD) -> MAX485 RO
- Raspberry Pi GPIO [beliebiger freier GPIO, z.B. GPIO17] -> MAX485 DE/RE (gebrückt)
- Raspberry Pi 5V -> MAX485 VCC (oder 3.3V, je nach Modul)
- Raspberry Pi GND -> MAX485 GND
- MAX485 A -> RS485-Bus A
- MAX485 B -> RS485-Bus B
Vergessen Sie nicht die Abschlusswiderstände! An beiden Enden eines RS485-Busses sollte ein 120-Ohm-Widerstand angebracht werden, um Signalreflexionen zu vermeiden und die Signalintegrität zu gewährleisten.
Software-Konfiguration auf dem Raspberry Pi
Bevor wir mit dem C-Code starten, muss die UART-Schnittstelle des Raspberry Pi für unsere Zwecke vorbereitet werden.
1. UART aktivieren und Konsole deaktivieren
Standardmäßig kann die primäre UART-Schnittstelle des Raspberry Pi für die Systemkonsole verwendet werden, was sie für unsere Anwendung blockieren würde. Wir müssen dies ändern:
- Öffnen Sie die Konfigurationsdatei mit
sudo nano /boot/config.txt
- Fügen Sie am Ende der Datei die Zeilen hinzu oder stellen Sie sicher, dass sie wie folgt konfiguriert sind:
enable_uart=1
dtoverlay=disable-bt
(deaktiviert Bluetooth, falls es die UART belegt, besonders bei Pi 3/4/Zero W)
#dtoverlay=miniuart-overlay
(optional, falls Sie die schnellere PL011-UART für Bluetooth belassen und die Mini-UART nutzen möchten – für die meisten Zwecke ist `disable-bt` einfacher) - Speichern und schließen Sie die Datei (Strg+X, Y, Enter).
- Deaktivieren Sie die serielle Konsole:
sudo raspi-config
Gehen Sie zu „3 Interface Options” -> „P6 Serial Port” -> „Would you like a login shell to be accessible over serial?” -> „No” -> „Would you like the serial port hardware to be enabled?” -> „Yes”. - Starten Sie den Raspberry Pi neu:
sudo reboot
Nach dem Neustart sollte die serielle Schnittstelle unter /dev/ttyS0
(oder /dev/ttyAMA0
, je nach Pi-Modell und Konfiguration) verfügbar sein.
C-Programmierung für RS485
Nun geht es ans Eingemachte: Das C-Programm, um die RS485-Schnittstelle anzusteuern. Wir nutzen die Standard-Linux-Systemaufrufe für die serielle Kommunikation.
1. GPIO-Steuerung für DE/RE
Da wir den DE/RE-Pin des RS485-Moduls manuell steuern müssen, verwenden wir den sysfs
-Ansatz, der es erlaubt, GPIO-Pins durch Schreiben in spezielle Dateien zu kontrollieren. Dieser Ansatz ist in C implementierbar und erfordert keine externen Bibliotheken wie `wiringPi` oder `libgpiod` für diesen Teil.
Wir definieren eine Funktion zum Steuern des GPIO-Pins. Für dieses Beispiel nehmen wir GPIO17 als DE/RE-Steuerpin.
„`c
#include
#include
#include
#include
#include
#include
#include
#define SERIAL_PORT „/dev/ttyS0” // Oder „/dev/ttyAMA0”
#define BAUDRATE B9600 // Baudrate für die Kommunikation
#define GPIO_DE_RE 17 // GPIO-Pin für DE/RE
// — GPIO-Steuerfunktionen —
int gpio_export(int gpio) {
char buffer[256];
int fd = open(„/sys/class/gpio/export”, O_WRONLY);
if (fd == -1) {
perror(„Fehler beim Öffnen von /sys/class/gpio/export”);
return -1;
}
snprintf(buffer, sizeof(buffer), „%d”, gpio);
write(fd, buffer, strlen(buffer));
close(fd);
return 0;
}
int gpio_set_direction(int gpio, const char *direction) {
char path[256];
char buffer[256];
snprintf(path, sizeof(path), „/sys/class/gpio/gpio%d/direction”, gpio);
int fd = open(path, O_WRONLY);
if (fd == -1) {
perror(„Fehler beim Öffnen des GPIO-Richtungspfads”);
return -1;
}
write(fd, direction, strlen(direction));
close(fd);
return 0;
}
int gpio_set_value(int gpio, int value) {
char path[256];
char buffer[2];
snprintf(path, sizeof(path), „/sys/class/gpio/gpio%d/value”, gpio);
int fd = open(path, O_WRONLY);
if (fd == -1) {
perror(„Fehler beim Öffnen des GPIO-Wertpfads”);
return -1;
}
snprintf(buffer, sizeof(buffer), „%d”, value);
write(fd, buffer, 1); // Wert ist nur ein Zeichen (‘0’ oder ‘1’)
close(fd);
return 0;
}
int gpio_unexport(int gpio) {
char buffer[256];
int fd = open(„/sys/class/gpio/unexport”, O_WRONLY);
if (fd == -1) {
perror(„Fehler beim Öffnen von /sys/class/gpio/unexport”);
return -1;
}
snprintf(buffer, sizeof(buffer), „%d”, gpio);
write(fd, buffer, strlen(buffer));
close(fd);
return 0;
}
„`
2. Serielle Port-Konfiguration und Kommunikation
Der Kern des Programms besteht aus dem Öffnen, Konfigurieren und Schließen des seriellen Ports, sowie dem Senden und Empfangen von Daten.
„`c
// Hauptprogramm
int main() {
int serial_port;
struct termios tty;
// — GPIO initialisieren für DE/RE —
printf(„Initialisiere GPIO%d für DE/RE-Steuerung…n”, GPIO_DE_RE);
if (gpio_export(GPIO_DE_RE) == -1) return 1;
if (gpio_set_direction(GPIO_DE_RE, „out”) == -1) {
gpio_unexport(GPIO_DE_RE);
return 1;
}
gpio_set_value(GPIO_DE_RE, 0); // Standardmäßig auf Empfangen (LOW) setzen
// — Seriellen Port öffnen —
serial_port = open(SERIAL_PORT, O_RDWR | O_NOCTTY | O_NDELAY);
if (serial_port < 0) {
perror("Fehler beim Öffnen des seriellen Ports");
gpio_unexport(GPIO_DE_RE);
return 1;
}
printf("Serieller Port %s erfolgreich geöffnet.n", SERIAL_PORT);
// --- Serielle Port-Konfiguration ---
if (tcgetattr(serial_port, &tty) != 0) {
perror("Fehler beim Abrufen der seriellen Port-Attribute");
close(serial_port);
gpio_unexport(GPIO_DE_RE);
return 1;
}
// Löschen alter Flags, setzen neuer Flags
tty.c_cflag &= ~PARENB; // Keine Parität
tty.c_cflag &= ~CSTOPB; // 1 Stopbit
tty.c_cflag &= ~CSIZE; // Maskiere Bit-Größen-Bits
tty.c_cflag |= CS8; // 8 Datenbits
tty.c_cflag &= ~CRTSCTS; // Kein Hardware-Flusskontrolle
tty.c_cflag |= CREAD | CLOCAL; // Empfänger aktivieren, lokale Verbindung
tty.c_iflag &= ~IGNBRK; // Kein Break ignorieren
tty.c_iflag &= ~BRKINT; // Keine Break-Unterbrechung
tty.c_iflag &= ~PARMRK; // Keine Paritätsfehler markieren
tty.c_iflag &= ~ISTRIP; // Keine Zeichen von 8 auf 7 Bits strippen
tty.c_iflag &= ~INLCR; // Keine Newline in Carriage Return konvertieren
tty.c_iflag &= ~IGNCR; // Keine Carriage Return ignorieren
tty.c_iflag &= ~ICRNL; // Keine Carriage Return in Newline konvertieren
tty.c_iflag &= ~IXON; // Kein XON/XOFF
tty.c_iflag &= ~IXOFF; // Kein XON/XOFF
tty.c_iflag &= ~IXANY; // Kein XON/XOFF
tty.c_lflag &= ~ECHO; // Kein Echo
tty.c_lflag &= ~ECHONL; // Kein Echo für Newline
tty.c_lflag &= ~ICANON; // Kein kanonischer Modus (Rohdaten)
tty.c_lflag &= ~ISIG; // Keine Signalzeichen (INTR, QUIT, SUSP)
tty.c_oflag &= ~OPOST; // Keine Nachbearbeitung des Outputs
tty.c_oflag &= ~ONLCR; // Keine Newline zu CR/LF Konvertierung
tty.c_cc[VTIME] = 1; // Warte bis zu 100ms (1 * 0.1s) für Zeichen
tty.c_cc[VMIN] = 0; // Minimal 0 Zeichen zum Lesen
// Baudrate setzen
cfsetispeed(&tty, BAUDRATE);
cfsetospeed(&tty, BAUDRATE);
if (tcsetattr(serial_port, TCSANOW, &tty) != 0) {
perror("Fehler beim Setzen der seriellen Port-Attribute");
close(serial_port);
gpio_unexport(GPIO_DE_RE);
return 1;
}
printf("Serieller Port konfiguriert: %s, Baudrate %d, 8N1.n", SERIAL_PORT, BAUDRATE);
// --- Test-Kommunikation ---
char tx_buffer[] = "Hallo RS485 Welt!n";
char rx_buffer[256];
int n_written = 0;
int n_read = 0;
printf("nSende-Modus aktivieren (DE/RE HIGH)...n");
gpio_set_value(GPIO_DE_RE, 1); // DE/RE auf HIGH (Senden)
usleep(1000); // Kurze Verzögerung für Modul zum Umschalten
printf("Sende Daten: "%s"n", tx_buffer);
n_written = write(serial_port, tx_buffer, strlen(tx_buffer));
if (n_written < 0) {
perror("Fehler beim Senden von Daten");
} else {
printf("%d Bytes gesendet.n", n_written);
}
tcdrain(serial_port); // Warten, bis alle Daten gesendet wurden
printf("Empfangs-Modus aktivieren (DE/RE LOW)...n");
gpio_set_value(GPIO_DE_RE, 0); // DE/RE auf LOW (Empfangen)
usleep(1000); // Kurze Verzögerung
printf("Warte auf eingehende Daten...n");
// Einlesen von Daten über eine Schleife
// In einer echten Anwendung würde man hier in einer Endlosschleife lesen oder auf Events warten
n_read = read(serial_port, rx_buffer, sizeof(rx_buffer) - 1);
if (n_read < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
printf("Keine Daten verfügbar.n");
} else {
perror("Fehler beim Lesen von Daten");
}
} else if (n_read > 0) {
rx_buffer[n_read] = ‘