Moin, ich möchte verschiedene Boards bauen mit denen ich per UART mit
dem PC kommuniziere.
Dazu haben wir uns ein einfache Protokoll ausgedacht:
Adresse
Flag für "jetzt kommen Daten" oder "Jetzt kommt ein Command"
Startbyte
Entweder Data 1 oder Command
Entweder Data 2 oder Value
...
Endbyte
Funktioniert auch wunderbar, allerdings habe ich das ganze auf dem Atmel
so implementiert, daß der Empfang Interruptgesteuert ist, und dann per
Switch-Abfrage abhängig von einem Empfangszähler entschieden wird was
ich mache (siehe code)
1
//USART-Empfangs Interrupt Handler
2
ISR(USART_RX_vect)
3
{
4
unsignedcharpuffer=UDR;//als erstes das USART Data Reg leeren
5
switch(RX_count)//was gemacht wird hängt davon ab, was im wievielten byte steht
6
{
7
case0:if(puffer==adress)//wenn byte die richtige adresse zeigt
8
{RX_count++;//ist das ganze gültig und der zähler wird um 1 erhöht
9
break;}//break damit die anderen cases nicht ausgeführt werden
10
elsebreak;//wenn nicht die adresse, dann wird verworfen
11
case1:if(puffer==flag_control)//wir nehmen in diesem board nur controls an
12
{RX_count++;//dann gehts weiter
13
break;}
14
else//sonst wird verworfen
15
{RX_count=0;
16
break;}
17
case2:if(puffer==flag_begin_of_data)//dann MUSS BOD kommen
18
{RX_count++;
19
break;}
20
else
21
{RX_count=0;//alles andere wird verworfen
22
break;}
23
case3:if(puffer==flag_command_reset)//wenn jetzt ein reset verlangt wird
24
{RX_command=flag_command_reset;//wird das vorgemerkt
25
RX_count++;//und weiter gehts
26
break;}
27
elseif(puffer==flag_command_send_again)//oder es wird ein erneutes senden gewollt
28
{RX_command=flag_command_send_again;//auch das wird gemerkt
29
RX_count++;
30
break;}
31
else
32
{RX_count=0;//alles andere wollen wir nicht
33
break;}
34
case4:if(puffer==flag_end_of_data)//nur wenn jetzt noch das EOD kommt
35
{if(RX_command==flag_command_send_again)send_data();//wird neu gesendet
RX_command=0;//und das vorgemerkte command gelöscht
42
break;}
43
default:
44
{RX_count=0;//wird ein anderer zählerstand erreicht stimmt was nicht
45
break;}//dann wird er auf null gesetzt. Sollte es nötig sein könnte man
46
}//hier auch noch das board resetten...
47
}//außerdem könnte man ein timeout zwischen zwei RX-Vorgängen starten
48
//der jedesmal wieder gestartet wird und bei ablauf alle daten verwirft
49
//und eine meldung abschickt
Dieses Board kann jetzt aber auch nur die beiden Commands reset und send
again, andere dagegen sollen deutlich mehr Commands verarbeiten und
zusätzlich noch Daten empfangen.
Daher meine Frage, ob das ganze auch irgendwie eleganter geht, mit
weniger Text.
Btw, kann man eine Switch-Abfrage auch in Bereiche einteilen?
Also
Naja, vom reinen Protokoll her ist das ja im Prinzip das, was wir auch
machen, außer daß wir keine CRC bilden, weil diese nach bisherigen
Versuchen nicht notwendig war (hatten noch nicht einen einzigen
Übertragungsfehler).
Allerdings setzt Dein Beispiel vorraus, daß der Slave nicht von sich aus
sendet sondern nur auf Abfrage reagiert, was in unserer Anwendung aber
nicht sinnvoll ist.
Meine Frage bezog sich eher darauf, wie man am geschicktesten im Source
des Controllers die Empfangs-interrupt.routine so kurz wie möglich hält
und darin die Kontrolle der ankommenden Bytes ermöglicht.
Wenn ich jetzt Daten mit 256 Bytes empfange, dann möchte ich ja nicht
eine 256-Stellige Case-Abfrage schreiben die 253mal den selben Inhalt
hat.
Das Board um das es Aktuell geht hat zwei extensions, einmal eine mit 32
8bit Output-Treiber-Schieberegistern um LEDs oder andere Lasten
anzusteuern, das andere Board hat 32 8bit input schieberegister wo
diverse Schalter und ein Paar andere Logiken angeschlossen sind.
Das Board soll nun kontinuierlich die Schalter-Extension auslesen und
bei veränderung die seit dem letzten senden veränderten Bits im Format
[Bitnummer] [new value] sofort und selbständig an den Rechner senden.
Da sich durch die Logiken auch mehrere Bits verändern können, ist dieser
String verschieden lang.
Gleichzeitig soll das Bitweise empfangene im Controller zum
ensprechenden String zusammengebaut werden, bzw in Sequenz sinnvoll
ausgewertet werden und die AUsgangsextension bei veränderung
entpsrechend neu angeseteuert werden um eben die LEDs leuchten zu lassen
oä.
Das Ankommend eist eben auch im Format [adresse] [announce daten]
[startbyte] [bitnummer] [new value]...[endbyte]...
Eine Statusmaschine im Interrupt dauert nicht laenger, nur weil's viel
Text ist. Das wird ein Register gelsen, eine Wert in eine Sprungtabelle
eingegeben und dann noch ein paar Instruktionen. Man muss den jeweile
laengsten Pfad im Auge haben, sowie dessen Haeufigkeit. Der laengste
Pfad limitiert die Interrupt rate.
Ja, das macht sinn.
Das heißt mit der Case Abfrage bin ich auf dem Richtigen Kurs?
Kann ich denn darin nun Bedingungen zusammenfassen?
Also "case (2 bis 17) : ... " ?
Ueblicherweise nicht, dh so wie ich C kenne nicht. Das ist eine Frage
des Compilers. Wenn eine Sprungtabelle verwendet wird, was am
schnellsten ist, dann sollten nicht zuviele Eintrage nichts enthalten.
Was gehen sollte, ist eine Aufzahleung anstelle eines Bereiches. Dh
Switch (irgendwas)
{
case 1: blabla; |
case 3,
case 4,
case 5,
..
case 27 :blablabla; |
case 28 : blubb; |
default : nochwasanderes; |
}
Die andere Moeglichkeit ist den Switch als repetitiven If else zu
implementieren, und dann ist vieles mehr moeglich, auch nicht-ordinale
Vergleiche.
Nimm einfach eine extra Variable für die Statemachine (em besten per
enum :).
Da hast dann die verscheidenen Zustände (zB Adresse, Datenlänge, Daten
Empfangen, Ende, Warten auf Daten) und für jede ein case.
So ganz nebenbei. Falls ein Protokoll nicht Master-Slave ist, kann es
Kollisionen geben. Die muss man detektieren koennen. Dann ist ein CRC
schon Pflicht, da eine Kollision sich als CRC Fehler bemerkbar machen
koennte. Falls nun von gleichwertige Teilnehmern eine Kollision
detektiert wurde muss es weitergehen. Da braucht es Konzepte. zB eine
zufaellige verzoegerte Wiederholung.
Ach ja, logisch, ich kann ja alle ereignise von x bis y einfach ohne
break aufzählen, dann fallen sie einfach bis y durch und dann wird y
ausgeführt klick ;)
Aber warum es zu kollisionen kommen sollte verstehe ich noch nicht so
ganz.
Selbst wenn der Controller etwas vom PC empfängt während er etwas
sendet, der USART ist ja eh Hardware, arbeitet also auch bei einem
Interrupt seine Aufgabe ab.
Der Empfang dauert jeweils nur ein byte, bei 4mhz Controllertakt und
115200 Baud ist das so kurz, daß in der Zwischenzeit wieder gut ein
Stück weitergearbeitet wird bevor evtl wieder ein neues Byte ein
Interrupt auslöst.
Der Timeout vom PC UART sollte doch lang genug sein, und wenn nicht: den
Fall fange ich ab.
Oder mache ich hier einen Denkfehler?
Master-Slave bedeutet der Master, meist der PC, bestimmt die
Kommunikation, Saemtliche Kommunikation wird vom Master initiiert, der
Slave antwortet nur. Dies ist relativ einfach zu implementieren, ist
aber nicht in allen Faellen geeignet. Bei gleichwertigen
Kommunikationspartnern gibt es zwei Moeglichkeiten, Das
Kollosionsdetektion/Retry oder das Token Verfahren. Beim ersteren blaest
jeder mal seine Nachricht raus und versucht eine Kollision zu
detektieren. Bei einer Kollision versuchen es beide an der Kollision
beteiligten Partner eine zufaellige Zeit spaeter nochmals. Beim Token
verfahren bestimmt ein Faehnchen (Token) wer senden darf, und das
Faehnchen wird im Kreis rum gegeben. Das Tokenverfahren hat die bessere
Auslastung der Bandbreite ist aber aufwendiger zu implementieren. zB
muss das Token regeneriert werden falls es verloren geht.
Na, wenn zwei Stationen gleichzeitig senden gibt es eine Kollision.
RS232 ist uebrigens nur punkt-zu-punkt. Das geht hier nicht, Ein
Standardansatz is RS485 und das ist nur halb duplex. Ein RS422 waere
fullduplex und busfaehig. Es wird fuer Master-Slave auch verwendet, bei
multimaster hat man aber die Schwierigkeit welche Richtung nun verwendet
werden soll.
>Ein Standardansatz is RS485 und das ist nur halb duplex.
Richtig.
>Ein RS422 waere fullduplex und busfaehig.
Nö, RS422 ist RS232 symmetrisch/differentiell.
Fullduplex schon, aber auch nur Punkt zu Punkt.
Es gibt noch RS485-4-wire. Das ist ein fullduplex-Bus.
RS422 is Busfaehig. Ein Paar geht in die eine Richtung, das andere Paar
geht in die andere Richtung. Eben fuer Master-Slave. Der nicht sendende
Slave schaltet seinen Transmitter Tristate.
eine Prüfsumme macht auch bei einer einfachen RS232 Sinn weil die
Verbindung damit robuster wird. Wenn Müll übertragen wird weil z.B. die
Bitraten nicht richtig stehen können zufällig falsche Kommandos
ausgelöst werden. Und auch wenn die Stecker abgezogen oder aufgesteckt
werden können Zeichen generiert werden die als Fehler erkannt werden
müssen. Noch sicherer wird es wenn man ein Timeout für eine Message
implementiert um zu verhindern das unvollständige Telegramme die
Schnittstelle blockieren.
Ok, dann füg ich das mal meinem Wissen hinzu ;)
Wie gesagt, bei bisherigen Anwendungen war es nicht nötig weil bei
vielen Tausend versendeten Paketen nicht ein einziger Fehler aufgetreten
ist.
Sollten sich im Betrieb aber welche zeigen, ist unser System flexibel
genug um nachträglich diverse Prüf- und Sicherungsverfahren einzubauen.
Danke erstmal für die vielen Tips und Hinweise.
Grüße
Phil
>RS422 is Busfaehig. Ein Paar geht in die eine Richtung, das andere Paar>geht in die andere Richtung. Eben fuer Master-Slave. Der nicht sendende>Slave schaltet seinen Transmitter Tristate.
Nö...
http://de.wikipedia.org/wiki/RS422
Das was du beschreibst, ist der RS485-4-Wire-Bus.
RS422-Transceiver haben keine Richtungspins.
@ 6635 (Gast)
>RS422 is Busfaehig.
Nope. du verwechselst das Mit Multi-Drop. Ein Sender, mehrere Empänger.
DMX wäre somit klassisches RS422, auch wenn es oft als RS485 bezeichnet
und realisiert wird. Die Unterschiede sind eher gering.
http://www.maxim-ic.com/appnotes.cfm/appnote_number/723/
MfG
Falk
@ Phillip Hommel (Firma hs-bremen) (philharmony)
>Für meine Anwendung aber alles etwas sehr überdinemsioniert ;)
Was ist daran überdimensioniert? Ein kleiner 8 Piner und gut ist.
>Es geht um ca 200 Schalter und 200 Lampen...
Ja eben! Und da die sicherlich räumlich etwas auseinander liegen,
sollte man einen robusten Kommunikationsstandard wählen. RS485.
MFG
Falk
Nope, liegen alle auf einem Panel, werden von 2 Man bedient und in
ersten Versuchen ging es mit RS232 Fehlerfrei.
Direkt auf der Plattine mit dem Atmega ist auch ein FTDI UM232R-Modul
dran, von da an ists eh ne USB-Leitung.
Die eigentlich Leitung wo ich 5V UART betreibe ist ca 5cm Lang.
Meine Frage bezog sich wirklich nur darauf, wie ich die Abfrage des
Protokolls eleganter Lösen kann.
Mit RS232 kann man aber keinen Bus bedienen. Allenfalls kann man auf
einer Leiterplatte die Signale in TTL-Level fuehren, und die nicht
betroffenen Tristaten. 200 Stueck benoetigen aber satte Bustreiber. HC
oder TTL alleine ist nicht genug.
???
Die RS 232 bedient auch nicht den BUS sondern schickt dem ATmega nur ein
Paar Daten, welche der Schieberegister er wie setzen soll.
Wie gesagt:
ES FUNKTIONIERT GENAU SO!!!! Ich weiß das weil ichs aufgebaut habe und
es tadellos sendet und empfängt.
Ich wollte nur wissen ob man das PROTOKOLL anders einbinden kann bzw man
den switch-Befehl optimieren kann.
Alles andere ist doch schon lange gelöst...
@ Phillip Hommel (Firma hs-bremen) (philharmony)
>Ich wollte nur wissen ob man das PROTOKOLL anders einbinden kann bzw man>den switch-Befehl optimieren kann.
Kann man. Erstmal das Startbyte erkennen, dann immer die Daten in einen
Puffer schreiben bis das Endzeichen erkannt wird. Dann erst den
empangenen String/Datenblock dekodieren.
MFG
Falk