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.
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:
- R0 an RXD (Controller-UART)
- DI an TXD (Controller-UART)
- 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:
- Verdrillte Leitungen (twisted Pair, z.B. CAT-Kabel, Telefonleitungen gehen auch)
- 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.
- 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
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