Skip to main content
Amateurfunk verbindet die Welt

RS485 - Long distance call

Erstellt: DL6GL, 28.12.2011, letzte Änderung 

Immer dann, wenn Datenübertragungen über größere Entfernungen herzustellen sind, liefert die RS485-Schnittstelle sehr einfache Realisierungsmöglichkeiten. Die Kopplung zweier oder mehrerer Microcontroller ist mit geringem Hard- und Softwareaufwand möglich.

Einige Eigenschaften:

  • Mehrere hundert Meter sind je nach Datenrate zu überbrücken.
  • Es ist im Gegensatz zu einer 1:1-Verbindung mit RS232 ein Bus-System realisierbar, an das mehrere Geräte, 32 mit dem MAX485, anschließbar sind. Die einfachste Variante ist eine Single Master / Multi Slave-Konfiguration.
  • Koppler wie der MAX485 ermöglichen einen einfachen Anschluss an die AVR-UART-Schnittstelle. Die Kopplungssoftware unterscheidet sich praktisch nicht von einer üblichen RS232-Kommunikation.
  • Es können ja nach verwendetem RS485-Koppler Zwei- oder Vierdraht-Verbindungen aufgebaut werden. In der Zweidrahtkonfiguration ist nur Halbduplexbetrieb möglich.


RS485 Zweidraht-Bus schematisch

Abb. 1: Zweidraht-RS485-Bus, z.B. mit dem MAX485 (schematisch).

Die Verteilung der Controller am Bus ist beliebig. Der Master muss nicht wie in Abb. 1 an einem Ende des Busses angeordnet werden. Slave 1 und Master könnten z.B. ihre Plätze tauschen. Die Terminierungen sind auf alle Fälle an beiden Busenden angeordnet. Die optionalen Pullup/Pulldown-Widerstände verbleiben beim Master.

Die Datenübertragung erfolgt differentiell/symmetrisch, d.h. auf einer Leitung (B) wird das nicht invertierte und auf der anderen (A) das dazu invertierte Signal übertragen. Der Empfänger wertet daraus die Differenz aus. Damit wird eine gute Störsicherheit erreicht, da auf beiden Leitungen eingestreute Störsignale (Gleichtaktsignale) sich in der Signaldifferenz nicht (kaum) auswirken. Neben den Leitungen A und B stellt die Abschirmung des Verbindungskabels einen gemeinsamen Massebezug her. Mit einer vierten Leitung können die Controller ggf. mit der +5V-Betriebsspannung versorgt werden. Nähere Beschreibungen in [1] bis [4].

Die Verbindung zu den Controllern erfolgt über drei Leitungen:

  1. R0 an RXD (Controller-UART)
  2. DI an TXD (Controller-UART)
  3. RE und DE an einem beliebigen als Output konfigurierten Controller-Pin, im Bild oben "R/T". Dieser steuert Senden bzw. Empfang. Beim Senden muss dieser Pin logisch H abgeben, beim Empfangen logisch L. Damit schaltet der Kopplerbaustein, in diesem Beispiel ein MAX485, den Sende-/Empfangsbetrieb jeweils um.

Der RS485-Bus stellt insbesondere bei größeren Entfernungen gewisse Anforderungen:

  1. Verdrillte Leitungen (twisted Pair, z.B. CAT-Kabel, Telefonleitungen gehen auch)
  2. Impedanzanpassung (Terminierung) an den beiden äußeren Kabelenden, in der Regel mit jeweils 120 Ohm. Damit werden Signalreflexionen auf den Leitungen vermindert. Bei allen ggf. dazwischen angeordneten Kopplern, im Bild oben Slave 1, entfällt dieser Terminierungswiderstand.
  3. Nicht zwingend nötig sind die in Abb. 1 angedeuteten Pull up/Pull down-Widerstände. Sie stellen definierte Potenziale in den Sendepausen her. Die Werte können je nach Leitungslänge von 390R bis 1k variieren, beide gleich. Im hier besprochenen Testaufbau mit ca. 15m Telefonkabel wurde darauf verzichtet. Im Zweifelsfall nach "RS485 fail safe" googeln oder [4], [5], [6].


Master-Slave-Kopplung mit BASCOM

RS485 Bus Schaltung

Abb. 2: Schaltungsauszug der RS485-Kopplung.

Beim Kampf mit den Tücken des Objekts (Taktfrequenz, Baudrate, Protokoll und Software an sich) könnte es zweckmäßig sein, zunächst die RS485-Mimik, in Abb. 2 zwischen den beiden ATmegas, einfach wegzulassen. Statt dessen erst einmal mit der direkten Verbindung vom RXD-Pin des einen zum TXD-Pin des anderen Controllers und umgekehrt, also mit überkreuzten kurzen Leitungen nach Art eines Nullmodemkabels beginnen, solange beide Einheiten noch nahe beieinander auf dem Labortisch liegen. Wenn alles läuft, ist die Zeit reif für die RS485-Verbindung.

Zur Tücke des Objekts gehört auch der MAX485. Bei den inzwischen mehreren Versionen des Antennentuners hat sich gezeigt, dass manche MAX485CSA (SMD SO8) einen Kondensator von ca. 1nF an Pin 1 (AVR RXD-Eingang) brauchen, damit die Kommunikation fehlerfrei funktioniert. Manche wiederum nicht, sogar aus dem gleichen Reel. Bei den DIP8-Versionen MAX485CPA war das in meinen Versuchen nicht der Fall.


1   Master

Mit Code-Schnipseln aus dem Antennentuner soll das Prinzip erläutert werden. Als "Master" fungiert das Steuergerät eines Antennentuners. Hier werden die Betriebsdaten für den über die Kopplungsleitung verbundenen Tuner (Remote Unit) eingestellt: bytEnc_L (Induktivitätswert), bytEnc_C (Kapazitätswert) und bitHL_pass (Schaltung als Hoch- oder Tiefpass).

Werden Betriebsdaten verändert, sendet der Master diese an die Remote Unit. Diese prüft die Datenübermittlung mit einer Checksumme. Wenn alles stimmt und die Remote Unit feststellt, dass sie mit der übertragenen Adresse gemeint ist, stellt sie die Betriebsdaten ein und quittiert den Vorgang.

$regfile = "m32def.dat"                           'ATMega32
$crystal = 16000000 'Crystal 16,0 MHz
$baud = 9600 'Baud rate RS485
'PortD RS485-Interface, Encoder and TRX control
'RS485 RXD PD0
'RS485 TXD PD1
'RS485 RE/DE PD2

Zur Erzeugung einer exakten Baudrate, hier 9.600, gibt es passendere Quarze, etwa 14,7456 MHz. 16 MHz wurden gewählt, um im Frequenzzähler mit einfachen Bit-Shifts statt mit Real-Arithmetik die Frequenz berechnen zu können. Der Baudrate-Fehler von ca. 0,2% bei 16 MHz ist tolerierbar. Er sollte kleiner als 2% sein. Ein einfacher Excel-Baudrate Calculator ist im Download zu finden.

'Common Variables, multiply used ----------------------------------------------
Dim bitTmp0 As Bit
Dim bytTmp0 As Byte
Dim bytTmp1 As Byte
Dim bytTmp2 As Byte
Dim bytTmp3 As Byte
Const On = 1
Const Off = 0

'Variables Encoder ------------------------------------------------------------
Dim bitEnc_changed As Bit '1=Encoder turned and/or H/L pass changed
Dim bitHL_pass As Bit 'High pass(1), low pass(0)
Dim bitHL_old As Bit 'High/Low pass before change
Dim bytEnc_L As Byte 'Encoder value L
Dim bytEnc_C As Byte 'Encoder value C
Dim bytEnc_old As Byte 'Encoder value before change
Dim bytEnc_new As Byte 'Encoder value after change

'Variables RS485 -Communication -----------------------------------------------
Const byt_TxdLen = 7 'Length of TX protocol frame (Bytes)
Const byt_RxdLen = 4 'Length of RX protocol frame (Bytes)
Const byt_TxdLenminus1 = byt_TxdLen - 1
Const bytTimeout_maxruns = 70 '~1 sec for time out from Timer0
Dim bitCommErr As Bit '1: communication error
Dim bytTimeout_runs As Byte 'Counter for time out (Timer0)
Dim bytSlaveAdr As Byte 'Slave Address
Dim bytSlaveRes As Byte 'Slave Response
Dim bytBuffCount As Byte 'Byte Buffer counter
Dim bytBuffInp(byt_RxdLen) As Byte 'Input Buffer
Dim bytBuffOut(byt_TxdLen) As Byte 'Output Buffer

Im Zusammenhang mit der RS485-Kommunikation ist hier nur Port D des ATmega32 von Bedeutung.

'Configure PortD (RS485-Interface, TRX control & encoder) ---------------------
DDRD = &B00011100 'D.0 RS485 RXD
' 'D.1 RS485 TXD
' 'D.2 RS485 RE/DE output
' 'D.3 TX PTT output
' 'D.4 TX mute output
' 'D.5 Disable remote input
' 'D.6 Encoder B input
' 'D.7 Encoder A input
PORTD = &B11100000 'Pullup D.5 to D.7
bitTransmit Alias Portd.2 '=1:Transmit, =0: Receive
TXmute Alias PortD.4 'TX mute voltage
TXPTT Alias PortD.3 'TX PTT relais
bitRemote Alias PIND.5 'Jumper in: Remote disabled
bitEnc_a Alias PIND.6 'Encoder B @ PIND.6
bitEnc_b Alias PIND.7 'Encoder A @ PIND.7

'Declaration of functions and subroutines -------------------------------------
Declare Sub SendRemote(byVal bytRow As Byte)
Declare Sub GetResponse(ByVal bytRemoteAdr As Byte)
Declare Sub ClearLCDLine(byVal bytRow As Byte , byVal bytChars As Byte)

Der MAX485 ist mit den Anschlüssen RD an Port D.0 (UART RXD) und mit DI an Port D.1 (UART TXD) angeschlossen. Die Sende/Empfangs-Umschaltung erfolgt an Port D.2, mit dem Alias "bitTransmit" benannt, siehe Abb. 2.

Timer0 wird u.a. auch zum Aufsetzen eines Time outs für die Quittierung der Remote Unit benutzt. Prescaler und Presettimer sind speziell für die Berechnung im Frequenzzähler ausgelegt .

'Configure Timer0-Interrupt ---------------------------------------------------
'for Keys, Remote Unit timeout and Counter gate (Timer0 = 8bit)
'Overflow time: Overflow-Counts * Prescale / crystal frequency
'= 255 * 1024 / 16.000.000 = 16,3 msec = ~ 61 Hz
'Possible Prescales: 8, 64, 256, 1024, here 1024
'Gate time for Counter:
'Effective Timer0 Counts * 1024 Timer0-Steps * 5 Timer0-overflows / Crystal freq.
'= 200 * 1024 * 5 / 16.000.000 Hz = 64 msec
'Effective Timer0 Counts: 200 = 256 (8bit Timer0-Counts) - 56 (PresetTimer0)
Config Timer0 = Timer , Prescale = 1024
Const Presettimer0 = 56 'Initialize Timer0 (shorten time, >0)
On Timer0 Timer0_isr 'On Overflow go to Timer0_isr

bytBuffCount = 0 'RS485 Input buffer counter

'Enable interrupts ------------------------------------------------------------
Enable Timer0
SREG.7 = 1 'Enable all interrupts
Timer0 = Presettimer0 'initialize Timer0

Bevor es richtig losgeht, wird geprüft, ob die Remote Unit ansprechbar ist. Die Remote Unit wird über zwei weitere Leitungen des Datenkabels auch mit Spannung versorgt. Mit dem Waitms 500 wird ihr Zeit gegeben anzulaufen. Auf der Controller-Platine ist ein Jumper an PinD.5 vorgesehen, der, wenn er gesteckt ist (bitRemote = 0), die Kommunikation für Testzwecke unterbindet.

'Check remote unit ------------------------------------------------------------
Locate 1 , 1
Lcd "Remote Unit OK?"
Locate 2 , 1
Waitms 500

If bitRemote = 1 Then 'Remote Disable jumper not set
bytSlaveAdr = 1
bytEnc_L = 0 'Encoder value L
bytEnc_C = 0 'Encoder value C
bitHL_pass = 0 'High pass(1), Low pass(0)
bitTmp0 = 0
For bytTmp0 = 1 To 3 'Try it 3 times
Call SendRemote(2) 'Send dummy message
If bytSlaveRes = 0 Then 'Response = ACK, remote unit is ready
bitTmp0 = 1
Exit For
End If
Waitms 100
Next bytTmp0
Call ClearLCDLine(2 , 20)
If bitTmp0 = 1 Then 'OK
Lcd "... is OK!"
Else
Lcd "... is not OK!"
Stop 'Program stopped
End If
Else
Lcd "... is disabled"
End If
Wait 1
Cls

In der Do...Loop spielt sich die komplette Bedienung des Steuergerätes ab. Hier wird nur die Kommunikation mit der Remote Unit gezeigt, die dann bemüht wird, wenn Einstellungen von Betriebsdaten verändert wurden (bitEnc_changed = 1).

Do                                                'Main program ***************

'Do something else
'Data changed to be sent to remote unit: bitEnc_changed = 1

'Send data to Remote unit --------------------------------------------------
'If L, C, H/L pass are changed, send data to remote unit
If Bitremote = 1 Then 'Remote unit is enabled
If bitEnc_changed = 1 Then 'Data have changed
bytSlaveAdr = 1 'Remote unit 1
Call SendRemote(2) 'Send to remote unit and get response
End If
End If

Loop 'End of main program ********

Das Kommunikationsprotokoll ist bewusst einfach gehalten.

Controller-Protokoll zur Übermittlung der Nutzdaten
Byte 1 2 3 4 5 6 7
  STX ADR LEN B01 B02 B03 CRC

 

 

 

 

Antwort der Remote Unit
Byte 1 2 3 4
  STX ADR
LEN
RES

 

Bedeutung der Bytes
STX Start of Transmission = 2
ADR Adresse der angesprochenen bzw. antwortenden Remote Unit (hier = 1)
LEN Anzahl der nachfolgenden Bytes
B01 Encoder-Wert für L (0 ... 255), für "Christian"-Option 0...127
B02 Encoder-Wert für C (0 ... 255)
B03 Hochpass (1) oder Tiefpass (0)
CRC CRC8-Checksum aus den Bytes 1 bis 6
RES Bestätigung = ACK (6), OK oder = NACK (21), nicht OK

Das Byte ADR ist in dieser Anwendung noch entbehrlich, da es nur eine Remote Unit gibt. Damit ist das Protokoll aber noch offen für Erweiterungen. Mit dem BASCOM-Befehl "PrintBin" werden die Daten binär übertragen.

Sub SendRemote(byVal bytRow As Byte)
   'Send data to the Remote Unit
   'While switching the relais turn TX Mute on
   'RS485 protocol (Master-Controller)
   'Byte     1   2   3   4   5   6   7
   '        STX Adr Len B01 B02 B03 CRC
   '        STX = 2  Start of transmission
   '        Adr      Address Remote processor
   '        Len      Number of following bytes, here 4
   '        B01      Value L (0 ... 255)
   '        B02      Value C (0 ... 255)
   '        B03      Value H/T (1 = High pass, 0 = Low pass)
   '        CRC      Checksum (CRC8, 1 Byte)
   '
   ''PrintBin' is used (no ASCII transformation)

   TXmute = On                                    'TX-Mute voltage on
   TXPTT = On                                     'TX-PTT relais on
   bitTransmit = On                               'set MAX485 to transmit
   'Send data
   bytBuffOut(1) = 2                              'Output buffer, first STX
   bytBuffOut(2) = bytSlaveAdr                    'Slave address
   bytBuffOut(3) = byt_TxdLen - 3                 'Number following bytes
   bytBuffOut(4) = bytEnc_L                       'Encoder value L
   bytBuffOut(5) = bytEnc_C                       'Encoder value C
   bytBuffOut(6) = bitHL_pass                     'High pass(1), Low pass(0)
   bytTmp3 = Crc8(bytBuffOut(1) , byt_TxdLenminus1)
   bytBuffOut(7) = bytTmp3                        'CRC8
   Printbin bytBuffOut(1) ; byt_TxdLen            'Send array bytBuffOut
   Waitms 2
   TXmute = Off                                   'Done, TX-Mute voltage
   TXPTT = Off                                    'and TX-Mute-Relais off
   bitTransmit = Off                              'Set MAX485 to receive

   'Wait for response
   bytTimeout_runs = 0                            'Start Receive time out (Timer0)
   Call GetResponse(bytSlaveAdr)                  'Get response and check it
   'GetResponse answers bytSlaveRes:
   'bytSlaveRes   = 0: ACK, Message acknoledged
   '              = 1: Time out
   '              = 2: NACK, Message not acknoledged
   '              = 3: Garbage is sent
   '              = 4: Address of Remote Unit is not bytRemoteAdr
   '              = 5: STX was not the first byte of the frame
   Select Case bytSlaveRes
       Case 0                                  'OK, got it
            bitCommErr = 0
       Case 1                                  'Time out
            Locate bytRow , 1
            Lcd "Time out " ; bytTimeout_runs
            Waitms 500
            Call ClearLCDLine(bytRow , 20)
            bitCommErr = 1
       Case 2 To 4
            Locate bytRow , 1
            Lcd "Comm Error " ; bytSlaveRes
            Waitms 500
            Call ClearLCDLine(bytRow , 20)
            bitCommErr = 1
   End Select

End Sub

In der Timer0_isr erfolgen das Polling der Taster, die Auswertung des Frequenzzählers und – hier gezeigt – das Hochzählen für das Kommunikations-Timeout.

Timer0_isr:
'Interrupt Service Routine for Timer0 (Keys and Counter)
Timer0 = Presettimer0
'Keys ----------------------------------------------------------------------
'...
'Counter -------------------------------------------------------------------
'...
Incr bytTimeout_runs 'Counter for receive timeout
Return

Nun wird auf die Antwort der Remote Unit so lange gewartet, bis ein vollständiger Antwortsatz vorliegt, längstens aber, bis das Timeout von ca. 1 sec. erreicht ist. Die Remote Unit schickt ein RES=ACK zurück, wenn der Befehl verstanden und ausgeführt wurde, sonst ein RES=NACK.

GetResponse gibt das Prüf-Flag bytSlaveRes zurück (=0: alles OK). Auf ein erneutes Übertragen der Nutzdaten im Fehlerfall wurde verzichtet. Tritt ein Fehler auf, wird dies mit "Time out" oder "Comm Error x", x = 2 bis 5 in der zweiten Displayzeile kurz angezeigt.

Fehler-Nr. Bedeutung
2 Remote Unit hat kein "ACK" als OK-Quittierung gesendet
3 Antwort der Remote Unit enthält falsche Zeichen
4 Remote Unit wurde mit einer falschen Adresse angesprochen
5 Das erste Zeichen in der Nachricht ist kein STX
Sub GetResponse(ByVal bytRemoteAdr As Byte)
'Get Response from RemoteUnit (Address bytRemoteAdr)
'RS485-Protocol (Remote Controller Response)
'Byte 1 2 3 4
' STX Adr Len ACK/NAK
' STX = 2
' Adr = Remote Controller address bytRemoteAdr
' Len Numer following bytes, here 2
' ACK/NACK Acknoledge (6), CRC OK, message confirmed
' Non-ACK (21), CRC not OK, message not confirmed
'Output:
'bytSlaveRes = 0: ACK, Message acknoledged
' = 1: Time out, Input Buffer bytBuffInp not complete
' = 2: NACK, Message not acknoledged
' = 3: Garbage in Response
' = 4: Address Remote Unit is not bytRemoteAdr
' = 5: STX not the first byte in message

Do
If Ischarwaiting() = 1 Then 'Wait for character
Inputbin bytTmp3 'Read byte
Incr bytBuffCount 'Next address in buffer
bytBuffInp(bytBuffCount) = bytTmp3 'Save byte in buffer
End If
Loop Until bytBuffCount = byt_RxdLen Or bytTimeout_runs > bytTimeout_maxruns

If bytBuffCount = byt_RxdLen Then 'Response is complete
If bytBuffInp(1) = 2 Then 'Is STX
If bytBuffInp(2) = bytRemoteAdr Then 'Is from bytRemoteAdr
Select Case bytBuffInp(4) 'Ack or NACK
Case 6 'Ack
bytSlaveRes = 0
Case 21 'NACK
bytSlaveRes = 2
Case Else 'Garbage
bytSlaveRes = 3
End Select
Else
bytSlaveRes = 4 'Remote address not OK
End If
Else
bytSlaveRes = 5 'STX not the first byte of msg
End If
Else
bytSlaveRes = 1 'Time out, response not complete
End If
bytBuffCount = 0 'Reset input buffer for next msg

End Sub

Der Vollständigkeit halber: LCD-Zeile löschen

Sub ClearLCDLine(byVal bytRow As Byte , byVal bytChars As Byte)
'Clear LCD bytChars characters in line bytRow

Locate bytRow , 1
LCD SPC(bytChars)
Locate bytRow , 1

End Sub


2   Remote Unit

Die Remote Unit wertet das Protokoll aus, indem sie prüft, ob das Telegramm mit STX beginnt, sie mit der Adresse ADR gemeint ist und das übertragene CRC mit einem selbst berechneten CRC8 übereinstimmt. Stimmt alles, schaltet sie die Relais entsprechend B01 bis B03 und schickt ein RES=ACK zurück, ansonsten ein RES=NACK.
Der ATmega16 ist völlig überdimensioniert. Er wurde wegen der verfügbaren IO-Ports für die Relais gewählt, um ohne Port-Expander auszukommen.

$regfile = "m16def.dat"                           'ATMega16
$crystal = 16000000 'Crystal 16,0 MHz
$Baud = 9600 'Baud rate
$hwstack = 40
$swstack = 40
$framesize = 40
'PortD RS485 Communication, High/Low pass, N.C.
' RXD PD0
' TXD PD1
' Send/Receive PD2 (1=Send, 0=Receive)

'Variables RS485 Communication ------------------------------------------------
Const byt_TXDLen = 4 'Length of TX protocol frame (Bytes)
Const byt_RXDLen = 7 'Length of RX protocol frame (Bytes)
Const byt_MyAdr = 1 'Bus Address of remote unit
Const byt_RXDLenMinus1 = byt_RXDLen - 1
Dim byt_BuffCount As Byte 'Counter for byte buffer
Dim bytCRC8 As Byte 'CRC8-Checksum
Dim bitCRC_OK As Bit '=1: Checksum is OK
Dim byt_BuffInp(byt_RXDLen) As Byte 'Input buffer
Dim byt_BuffOut(byt_TXDLen) As Byte 'Output buffer
Const ON = 1
Const OFF = 0

'Configure Port A (C-Bank) ----------------------------------------------------
DDRA = &B11111111 'PortA = Output (C-Bank)
PortA = &B00000000 'Output low
Port_CBank Alias PortA

'Configure Port C (L-Bank) ----------------------------------------------------
DDRC = &B11111111 'PortC = Output (L-Bank)
PORTC = &B00000000 'Output low
Port_LBank Alias PortC

'Configure Port D (RS485, enable LCD, Hi/Lo pass relais) ----------------------
DDRD = &B10000110 'PortD.1, D.2, D.7=output,
' D.0=RXD, D.1=TXD
' D.3 jumper in: disable LCD
' D.4-6 not used (input)
PortD = &B01111000 'Output low, Pullup D.3-6
bitTransmit Alias PortD.2 '=1: Transmit, =0: Receive
bitEnableLCD Alias PIND.3 '=1: enable LCD (jumper out)
bitHT_Pass Alias PortD.7 '=1: High pass, =0: Low pass

'Subroutines & Functions ------------------------------------------------------
Declare Sub SendResponse 'Response to Master

Zur Überwachung der UART-Schnittstelle wird eine Interrupt Service Routine verwendet.

'Enable UART interrupt service routine ----------------------------------------
On URXC RXD_isr 'Go to RXD_isr if character is received
Enable URXC 'Enable UART interrupt

'Initialize data --------------------------------------------------------------
byt_BuffCount = 0 'Input buffer counter
bitTransmit = OFF 'RS485 receive
SREG.7 = 1 'Enable all interrupts

In der Do...Loop werden jedes Mal, wenn ein vollständiger Kommunikationssatz empfangen wurde, entsprechend der übertragenen Daten byt_BuffInp(4...6) die Output-Pins gesetzt


  • Port A (C-Bank)

  • Port C (L-Bank)

  • Port D.7 (Hoch-/Tiefpass)

Entsprechend der Pin-Werte (Hi/Lo) schalten die Relais, angesteuert durch Darlington-Treiber ULN2803. Der Master hat in Sub SendRemote mit "PrintBin" die Daten als Binärwerte geschickt (nicht wie in einem Terminalprogramm mit "Print" in ASCII-Codierung). Ein Binärwert von z.B. &B00001101 schaltet an Port A oder Port C die Relais 0, 2 und 3 an, alle anderen aus.

Do                                                'Main program ***************
'Evaluate Input buffer
'RS485 protocol (Master controller)
'Byte 1 2 3 4 5 6 7
' STX Adr Len B01 B02 B03 CRC
' STX = 2 Start of Transmission
' Adr = 1 Remote-Processor 1
' Len Number of consecutive bytes, here 4
' B01 Value L (0 ... 255)
' B02 Value C (0 ... 255)
' B03 Value H/T (1 = high pass, 0 = low pass)
' CRC Checksum (CRC8, 1 Byte)

If byt_BuffCount = byt_RXDLen Then 'input buffer full
bitCRC_OK = 0
If byt_BuffInp(1) = 2 Then 'Is STX
If byt_BuffInp(2) = byt_MyAdr Then 'It's my address
bytCRC8 = CRC8(byt_BuffInp(1) , byt_RXDLenMinus1)
If bytCRC8 = byt_BuffInp(7) Then 'Checksum is OK
bitCRC_OK = 1
'All OK, set Relais
Port_LBank = byt_BuffInp(4) 'Set Relais L
Port_CBank = byt_BuffInp(5) 'Set Relais C
bitHT_Pass = byt_BuffInp(6) 'High-/low pass
End If
End If
Call SendResponse 'Send response to master
End If
byt_BuffCount = 0 'Initialize input buffer
End If
Idle 'Put the processor into the idle mode
'saves ~10 mA current
Loop 'End of main program ****************


'==============================================================================
'URXC interrupt service routine
'Output: byt_BuffInp(1...byt_Count), Input buffer
' byt_BuffCount, current length of input buffer

RXD_isr:
Incr byt_BuffCount 'Next buffer position
byt_BuffInp(byt_BuffCount) = UDR 'Put next byte into buffer
Return

Anschließend erfolgt die Quittierung zurück an den Master, auf die die Master-Routine "GetResponse" wartet, um den Vorgang abzuschließen.

'==============================================================================
Sub SendResponse
'Send response to master
'RS485 protocol (Remote controller response)
'Byte 1 2 3 4
' STX Adr Len ACK/NAK
' STX = 2
' Adr = n Address of Remote controller n, here 1
' Len Number of following bytes, here 1
' ACK/NACK Acknoledge (6), CRC OK, message OK
' Non-ACK (21), CRC not OK, message not OK

byt_BuffOut(1) = 2
byt_BuffOut(2) = byt_MyAdr
byt_BuffOut(3) = byt_TXDLen - 3
If bitCRC_OK = 1 Then 'Acknoledge
byt_BuffOut(4) = 6 'ACK
Else 'Non Acknoledge
byt_BuffOut(4) = 21 'NACK
End If
bitTransmit = ON 'Now send it
Waitms 2
PrintBin byt_BuffOut(1) ; byt_TXDLen 'Send array byt_BuffOut
Waitms 2
bitTransmit = OFF 'ready for receive
End Sub

Das Klappern der Relais war Musik in den Ohren, nachdem die Programme im Master und im Slave liefen. Auch richtig schnellem Drehen am Master-Encoder folgen die Relais mit munterem Scheppern.

Referenzen
[1] http://www.wiki.elektronik-projekt.de/mikrocontroller/rs485_bus
[2] http://www.rn-wissen.de/index.php/RS485
[3] http://halvar.at/elektronik/rs485/
[4] http://www.ti.com/lit/an/slla272b/slla272b.pdf
[5] http://www.ti.com/lit/an/slyt324/slyt324.pdf
[6] http://www.analog.com/static/imported-files/application_notes/AN-960.pdf