Hallo,
wie gliedere ich die UART Schnittstelle am besten aus der Hauptdatei
aus? Ich empfange einzelne Zeichen und arbeite mit Interrupt. Wäre das
eine Lösung:
Jein. Die Lösung hat Haken.
Lies dich in die Bedeutung von volatile ein. Möglicherweise brauchst
du die für UDR_puffer und UDR_new_byte.
Deine Funktion kann nicht den Wert 0 zurückgeben, weil du den als
Fehlerwert bzw. "nix da"-Wert zurückgibst. Das kannst du ändern, indem
du einen uint16_t Wert zurückgibst und z.B. im unteren Byte das
Datenbyte und im oberen Byte ein Fehlerbyte.
Wenn du mit der avr-libc arbeitest, schau dir das Makros ATOMIC an. Die
kapseln den atomaren Zugriff, den du mit cli()/sei() kapselst ABER das
Makro manipuliert das SREG. Deine Lösung schaltet mit dem sei() immer
die Interrupts global ein, auch wenn sie vorher noch ausgeschaltet
waren. Das ATOMIC Makro macht das nicht. In deinem Beispiel spielt das
wahrscheinlich keine Rolle, in anderen Beispielen sucht man sich an
sowas zur Laufzeit den Wolf!
Hallo,
also die beiden Werte sind schon als voitaille definiert. Ich überlege
nur gerade kommt UDR aus dem internen Puffer als char oder als int? Eher
als char oder? Da wäre der Rückgabewert 0 irgendein Steuerzeichen. Ich
überlege gerade, ob es überhaupt sinnvoll ist (für meine Verwendung) das
Empfangene Byte in die Hauptdatei zurückzuführen. Weil ich möchte wenn
bestimmte Bytes empfangen wurden Befehle ausführen das könnte ich ja
gleich in der RS232 Datei.
ATOMIC werde ich mir mal anschauen.
RS232 schrieb:
> Hallo,> also die beiden Werte sind schon als voitaille definiert. Ich überlege> nur gerade kommt UDR aus dem internen Puffer als char oder als int?
Im UDR stehen einfach nur die acht Bits, die empfangen wurden, wie du
sie interpretierst ist alleine deine Sache...
Die Puffer-Behandlung kannst du direkt in die Empfangs-ISR stecken und
dort ein Flag setzen, dass du 5 Bytes empfangen hast.
Irgendwo anders (z.B. in der main) wertest du die Daten dann aus.
1
unsignedintrs232_byte_count=0;
2
unsignedintrs232_data[5];
Es dürfte sehr sinnfrei sein, int zu benutzen, wenn du nur char
brauchst.
Das kostet unnötig Speicherplatz und Programmcode.
1
staticunsignedcharrs232_byte_count=0;
gehört in die Empfangs-ISR, der Puffer muss natürlich volatile global
vereinbart werden.
mit der Interrupt Routine ist eig ne gute Idee. Da müsste ich halt nur
während der Auswertung die Interrupts deaktivieren und danach wieder
aktivieren.
>kann ich ja auch nicht einfach in den Interrupt schreiben. Weil da wird>ja bei jedem Interrupt der Zähle zurückgesetzt.
Hast du es ausprobiert? Solltest du mal. Funktioniert nämlich.
>mit der Interrupt Routine ist eig ne gute Idee. Da müsste ich halt nur>während der Auswertung die Interrupts deaktivieren und danach wieder>aktivieren.
Beim AVR können sich Interrupts nicht gegenseitig unterbrechen. Die
werden nach der Reihenfolge der Interrupt-Vector-Tabelle abgearbeitet,
nachdem eine ISR beendet wurde.
ok und woran liegt das, dass es trotzdem funktioniert? Am static? Also
da müsste ich ja aber am besten die Auswertung gleich in der Intterupt
Routine machen oder? Weil das meinte ich mit deaktivieren, nicht das er
während der Auswertung den Puffer umschreibt.
>ok und woran liegt das, dass es trotzdem funktioniert? Am static?
Ja.
>Also da müsste ich ja aber am besten die Auswertung gleich in der Intterupt>Routine machen oder?
Das liest sich sehr wirr...
Was willst du überhaupt machen?
Es kann sein, dass deine Auswertung wunderbar in die ISR passt, es kann
aber auch sein, dass das viel zu lange dauert, und andere Interrupts
deswegen nicht pünktlich genug abgearbeitet werden können.
>Weil das meinte ich mit deaktivieren, nicht das er>während der Auswertung den Puffer umschreibt.
Wenn während deiner Protokollbearbeitung mehr als ein Byte empfangen
wird, geht dir eh irgendwas verloren.
Guck dir mal das Thema Ringpuffer an.
>und haben die ATMEL USARTS nicht schon generell einen Ringpuffer?
Nein. Naja, es werden 2 Bytes gepuffert. Das würde ich noch nicht als
Ringpuffer bezeichnen.
>der Header wird 2x included einmal in der main Datei und einmal in der>RS232 Datei.
Muss nicht sein. Es ist eher der Fall, dass zwei Variablen mit dem
gleichen Namen definiert werden. Ob die sich in Header-Dateien befinden
oder im Hauptprogramm, steht da nicht.
Vielleicht sollte sich RS232 mal ein C-Buch besorgen, in dem das alles
beschrieben wird. Das sind nämlich C-Grundlagen.
sie ist auf keinen Fall doppelt definiert. In der Main Datei steht gar
nichts weiter groß. Und wenn ich das: #include "rs232.h" aus der Main
entferne funktioniert es ja.
Überleg dir mal, warum das nicht hilft. Als kleine Hilfestellung: stell
dir vor, der Header wird in beiden c-Dateien jeweils genau 1 mal
eingebunden.
Fazit:
In einer Header-Datei darf keine Variable definiert werden.
Dort solltest du sie deklarieren (bekanntmachen):
Frank K. schrieb:
> Also was ich jetzt so gelesen habe ist das in der Headerdatei verboten:
Verboten nicht.
Aber es macht nicht das, was man auf den ersten Blick vermuten würde :-)
Vor allen dann nicht, wenn man die Regeln nicht kennt. Und die sagen
eindeutig: Wenn etwas eine Initialisierung hat, dann kann es keine
Deklaration sein, sondern nur eine Definition. Und damit ist das extern
wirkungslos.
> Das war wahrscheinlich aber nur ein Tippfehler.
Das war ein Copy-Paste-Fehler ;-)
Mir war der Unterschied zwischen Deklaration und Definition wichtig.
ok danke jetzt ist mir so einiges klar. Ich habe vorher noch nie mehr
als eine C Datei benutzt, deswegen ist das Neuland für mich.
@docean was ist mit den Typen?
Und noch was. Wie löse ich jetzt am besten die Auswertung der
Empfangenen Bytes. Soll ich die Auswertung in der ISR machen, oder
lieber extern? Für den Anfang reicht es, wenn ich einzelne Bytes senden
kann. Also nicht automatisiert, sondern per Hand vom PC.
> Soll ich die Auswertung in der ISR machen,
Nein. Eine ISR ist so lang wie unbedingt nötig, aber auf jeden Fall so
kurz wie möglich. In der ISR schreibst du die Zeichen in einen
Puffer....
> oder lieber extern?
...und wertest sie in der Hauptschleife aus.
Ok. Aber wenn ich sie extern Auswerte muss ich ja aber auch die
Interrupts für den Zeitraum deaktivieren, damit nicht der Puffer
verändert werden kann.
Frank K. schrieb:
> Ok. Aber wenn ich sie extern Auswerte muss ich ja aber auch die> Interrupts für den Zeitraum deaktivieren, damit nicht der Puffer> verändert werden kann.
Nein, lies mal was zum Thema Ringpuffer bzw. Fifo.
Es gibt einen Schreib- und einen Lesepointer, so können keine Zeiger
gegenseitig verbogen werden. Nur die ISR schreibt in den Puffer, und
nur die main-Loop liest.
Sieh dir dazu mal den SIO-Code von Peter Fleury an, der funkt
reibungslos.
So ich habe mich gerade mal ein wenig mit Ringpuffern beschäftigt. Das
Prinzip beruht ja bei Peter Fleury auf der Bitweisen Verknüpfung. Das
ganze ist eigentlich relativ genial. Ich werde jetzt mal versuchen mit
der Teilen der Library mein Vorhaben zu realisieren.
>Das Prinzip beruht ja bei Peter Fleury auf der Bitweisen Verknüpfung.
Echt? Ich dachte, es beruht darauf, dass man einen Puffer hat, für den
es eine Schreib- und einen Lesezeiger gibt.
Die bitweise Verknüpfung hängt eher mit dem beschränkten Platz des
Puffers zusammen. Wenn man den Puffer 256 Bytes groß macht, und als
Indizes welche vom Type unsigned char nimmt, dann kann man sich die
bitweisen Verküpfungen sparen...
zur Überprüfung hätte ich auch nochmal ne Frage. Hier mal code
Ausschnitte:
1
#define UART_NO_DATA 0x0100 /* no receive data available */
2
3
unsignedintrs232_receive(void){
4
5
returnUART_NO_DATA;/* no data available */
6
}
7
8
if(c&UART_FRAME_ERROR)
wie Funktioniert die Bedingung in dem Fall. Wenn 0x0100 zurückgegeben
wird wird das mit 0x0100 binär verknüpft. Es kommt also wieder 0x0100
raus. Aber Sind nicht alle positiven Zahlen true?
>ALso alles, was nciht der Bitmaske entspricht wird durch die Verknüpfung 0.
So würde ich das nicht formulieren.
Was ist denn das was nicht der Bitmaske entspricht? Der andere Operand
der Und-Verknüpfung.
Ist die Bitmaske selbst strukturiert? Ja. In Bits.
Was wird 0? Das Ergebnis der Und-Verknüpfung.
Ist das Ergebnis selbst strukturiert? Ja. Auch in Bits.
Was läßt sich über die Bits, des Ergebnisses sagen? Das jedes Bit des
Ergebnisses 0 ist, bei dem das entsprechende Bit der Bitmaske gleich 0
oder das entsprechende Bit des anderen Operanden gleich 0 ist.
>Aber knnte man da ncit gleich schreiben c==...?
In diesem Fall vielleicht schon. Aber der Code
1
unsignedintrs232_receive(void){
2
3
returnUART_NO_DATA;/* no data available */
4
}
läßt mich bezweifeln ob es sich um ein reales bzw. vollständiges
Beispiel handelt.
Im allgemeinen verwendet man diese Bitmasken mit Und, wenn noch weitere
Information in anderen Bits stecken.
Daher kann man nicht mit == arbeiten. Man will ja die anderen Bits
vielleicht garnicht betrachten.
Komplizierter wird es noch wenn man einige Bits auf 0 und andere auf 1
testen will.
das War der vollständige Code ich habe ihn für meine Zwecke gekürzt.
Also verwendet man die Bitmaske, weil man mehrere Informationen über
return zurückgibt. Da ich da ja aber nicht mache kann ich auch mit ==
und != arbeiten.
Frank K. schrieb:
> return zurückgibt. Da ich da ja aber nicht mache kann ich auch mit ==> und != arbeiten.
Du musst noch lernen, dass man mit defensiven Programmieren auf lange
Sicht immer besser fährt.
Heute ist es vielleicht so, dass das bei dir so ist. Aber in 2 Monaten
brauchst du ev. ein neues Bit, welches dir einen Fehler anzeigt. Machst
du die Abfrage gleich richtig, indem du nur dieses eine Bit, welches du
abfragen willst, ausmaskierst, dann ändert sich beim verwendenden Code
gar nichts. Es wird weiterhin nur dieses eine Bit abgefragt. Gehst du
jetzt aber den verlockenden, vermeintlich einfacheren Weg, dann hast du
in 2 Monaten erst mal eine Menge Arbeit, weil du den kompletten Code
durchforsten musst, auf welche Abfrage sich dein neu hinzugefügtes Bit
auswirkt und wo nicht. Das du bei dieser Änderung natütrlich ein paar
Codestellen übersehen wirst und daher erst mal sporadische, seltsame
Fehler auftauchen werden, braucht nicht extra betont zu werden.
Und das alles nur, weil du jetzt eine Und-Maskierung einsparen willst,
die dir so gut wie keine Rechenzeit kostet.
Dein Code gibt auch ein wunderschönes Beispiel für einen Fall wo Du eben
nicht einfach nur mit == arbeiten kannst. Es bezieht sich nur nicht auf
UART_NO_DATA.
Aber mit
1
return(UART_LastRxError<<8)+data;
wird in den höherwertigen Bits noch der UART-Fehler zurückgegeben.
Um also nur die Fehlerbits zu berücksichtigen brauchst Du 0xFF00 als
Maske. Um nur die Datenbits zu lesen hingegen 0x00FF.
Falls man etwa damit rechnen kann (das hängt von der
UART-Implementierung ab) das sowohl das Datenbyte (zumdinest teilweise)
gültig ist und auch die Fehlerbits so brauchst Du beide Masken und
kannst nicht mit == arbeiten.
Genau diese umständliche Grenz-Abfragerei kann durch eine geeignete Wahl
der Puffergröße als 2er Potenz umgangen werden:
1
#define BUFSIZE 8 /* Zweierpotenz 4,8,16... */
2
#define BUFMASK (BUFSIZE-1)
3
:
4
:
5
rs232_buffer[rs232_byte_count++];
6
rs232_byte_count&=BUFMASK;
Genauso macht das Peter Fleury.
Lustigerweise wird in deinem Code mit dem rs232_buffer[] gar nichts
gemacht :-/
EDIT:
du solltest deinem Lesezeiger einen eindeutigen Namen geben
z.B. rs232_byte_count_rd
im Gegensatz zum Schreibzeiger in der Interrupt-Routine, der demnach
z.B. rs232_byte_count_wr
heißen würde.
naja die Funktion rs232_receive liest ja schon aus dem Ringpuffer. Sie
ist nur zur Kommunikation da. Die Zuweisung des Puffers habe ich aus
versehen vergessen.
Deine Pointerbehandlung ist etwas unüblich.
Erst wird der Pointer aktualisiert, und dann mit dem alten Pointer
geschrieben/ausgelesen. Üblicher ist es, erst die Daten zu manipulieren,
dann den Pointer.
Aber das kann ja jeder machen, wie er will ;-)
1
:
2
tmphead=(UART_RxHead+1)&UART_RX_BUFFER_MASK;
3
UART_RxHead=tmphead;
4
UART_RxBuf[tmphead]=data;
5
:
6
7
tmptail=(UART_RxTail+1)&UART_RX_BUFFER_MASK;
8
UART_RxTail=tmptail;
9
data=UART_RxBuf[tmptail];
Das geht so kürzer und ohne explizite Definition von lokalen Variablen:
also das ist die Library von Fleury nicht von mir. Ziel ist es wie
gesagt, das ich Befehle (5 Bytes) per RS232 sende und diese dann
ausgewertet werden sollen. Also muss ja irgendwo eine Abfrage rein, ob
die 5 Bytes fertig sind.
Frank K. schrieb:
> also das ist die Library von Fleury nicht von mir. Ziel ist es wie> gesagt, das ich Befehle (5 Bytes) per RS232 sende und diese dann> ausgewertet werden sollen. Also muss ja irgendwo eine Abfrage rein, ob> die 5 Bytes fertig sind.
Daran musst du sowieso noch arbeiten.
Die Idee 5 Bytes einfach so zu lesen und davon auszugehen dass diese 5
Bytes tatsächlich irgendetwas miteinander zu tun haben, ist auf lange
Sicht ein sicherer Garant für Desaster.
> naja ich hätte einfach ein timeout eingebaut.
Besser ist ein Protokoll, z.B. so:
1) Ein bestimmtes Startzeichen setzt den Schreibzeiger auf 0,
2) dann werden alle empfangenen Zeichen in den Puffer geschrieben,
bis entweder
3) der voll ist, das wäre ein Fehler :-o
oder
4) das Endezeichen kommt, das dann auch die Auswertung anwirft.
Damit du die Steuerzeichen für Protokollstart und -ende von den anderen
Zeichen unterscheiden kannst, erfolgt die Übertragung der eigentlichen
Daten im ASCII-Klartext. Die Steuerzeichen sind dann z.B. STX (Start of
Text 0x02) und ETX (End of Text 0x03) oder etwas, das du sicher von
deinen Daten unterscheiden kannst (z.B. auch CR/LF).
Ein solches Protokoll ist weit verbreitet:
Beitrag "Re: [AVR] Serielle Protokolle: STX, ETX usw."http://www.meinberg.de/german/info/leap-second.htm
Ein tiefergehendes Protokoll könnt auch das sein:
SOH (start of heading)
LÄNGE
STX (start of text)
DATEN
ETX (end of text)
CHECKSUM
EOT (end of transmission)
BTW:
Bitte ETX (End of Text) nicht mit EOT (End of Transmission) verwechseln
Oder ich könnte die Zeichen ab dem Startzeichen Zählen und prüfen, ob
das 5. Zeichen z.B. das Endzeichen ist. eine andere Möglichkeit sehe ich
eig nicht.
Frank K. schrieb:
> Oder ich könnte die Zeichen ab dem Startzeichen Zählen und prüfen, ob> das 5. Zeichen z.B. das Endzeichen ist. eine andere Möglichkeit sehe ich> eig nicht.
Siehst du.
Jetzt hast du ein Protokoll!
Und dieses Protokoll leitet dein Programm!
Ok da würde ich das ganze jetzt aber zur Übersicht in eine Funktion
auslagern, die das übernimmt und mit so einer art Satemachine arbeiten.
Also je nach Status eine Globale Variable ändern oder eine static in der
Funktion.
> ein anderes Problem wäre noch, wenn ich Zahlen über 256 senden möchte.
Das Problem hast du auch schon mit dem Wert 256 :-o
> Da weis ich noch nicht, wie das gehen soll.
Du sendest (basierend auf meinem Protokoll von oben) z.B. die Zeichen
ASCII STX 1 2 3 4 5 ETX
Binär 0x02 0x31 0x32 0x33 0x34 0x35 0x03
Wenn du das ETX empfangen hast, ersetzt du das ETX (die 0x03) durch eine
binäre 0x00 und lässt atio() ab Pufferadresse+1 darauf los. Heraus kommt
ein bildschöner Integer der den Wert 12345 hat.
achja das atio() direkt über den Rinpuffer laufen lassen? Das ist vlt.
nicht so gut, weil er ja über das letzte Zeichen wieder bei 0 Anfängt.
und wenn ich das ganze nochmal puffere weis ich ja nicht, wie lang das
wird.
> achja das atio() direkt über den Rinpuffer laufen lassen?
Nein, der UART-Ringpuffer dient nur zur zeitlichen Entkopplung der
seriellen Schnittstelle vom Hauptprogramm.
Hier eine exemplarische Auswertungsroutine:
Es wird hier kein Ringpuffer verwendet.
Das Zeichen STX setzt den Schreibpointer auf 0.
Das Zeichen ETX löst die Umwandlung aus.
Die ASCII-Zeichen dazwischen werden in den Puffer geschreiben.
Etwa so (from the scratch):
1
charbuffer[10];
2
charindex=0;
3
charoverrun=0;
4
intwert;
5
:
6
for(;;){
7
:
8
c=uart_getc();// Zeichen aus Uart-Ringpuffer holen
Hallo,
so hatte ih das auch schon probiert. Das Problem hierbei ist ja
allerdings, das man maximal 8 Zahlen senden kann was ja aber ausreicht.
Deshalb habe ich nach einer besseren Lösung gesucht.
Frank K. schrieb:
> Hallo,> so hatte ih das auch schon probiert. Das Problem hierbei ist ja> allerdings, das man maximal 8 Zahlen senden kann
Wenn am anderen Ende ein menschlicher Benutzer sitzt, kann es natürlich
passieren, dass er beim Einschlafen zb. auf die '5' Taste kommt und dir
5555555555555555555555555555555555555555555 schickt.
(Nicht lachen. Ist ein beliebter Test unter Profis. Eingabreoutinen
müssen damit klarkommen, dass ein Kleinkind auf der Tastatur rumhackt.
Das Programm darf so etwas ablehnen, aber es darf nichts Unsinniges
machen oder gar abschmieren)
> Deshalb habe ich nach einer besseren Lösung gesucht.
Und jetzt überleg mal, wofür wohl die Variable overrun in obigen
Beispiel gut ist. Aus dem gleichen Grund ist auch atoi trotz seiner
Einfachheit, keine glückliche Wahl. strtol hat einfach bessere
Möglichkeiten, mir Fehlererkennung zu erlauben.
ok. Also ich würde das ganze jetzt so realisieren. Mit verschachtelten
Zählern.
Start of Transmission
Start of text
1
2
3
End of Text
Start of text
4
5
6
End of Text
.
.
.
End of Transmission
Bei Start of Transmission geht der Zeiger des Auswertungspuffers auf 0,
in dem letztendlich 5 uint16_t Zahlen gespeichert werden. Bei jedem
Start of text geht der Zeiger des Zwischenpuffers auf 0. Nach jedem
Textblock wir der Zeiger des Auswertungspuffer erhöht. Nach 5 Zahlen im
Auswertungsarray wird geprüft, ob das letzte Zeichen ein End of
Transmission ist. Dann wird das Array Ausgewertet.
Abgesehen von unglaublich langen Variablennamen und vielen Unterstrichen
könnte ich damit leben... ;-)
> Den Pufferüberlauf muss ich noch einbauen.
"Die Pufferüberläufe" sollte das heissen.
Sonst läuft dir irgendwann u.U. z.B. dein analysis_counter Amok :-o
Wäre mir persönlich zu unübersichtlich und kryptisch.
Da Prozessorzeit hier keine Rolle spielt, würde ich das wie das
Protokoll aufbauen (jede Protokollebene ist eine Funktion)
Dein Protokoll ist geschachtelt
Ein Record ist so aufgebaut
"Start of Transmission"
Parameter
Parameter
... (wieviele musst du wissen)
"End of Transmission"
Ein Parameter ist so aufgebaut
"Start of Text"
Zahl in ASCII Form
"End of Text"
Und genauso zerpflücke ich das auch in Funktionen
1
uint8_tReceiveRecord(intNumbers[],int*NrNumbers)
2
{
3
charc;
4
inti=0;
5
6
if(uart_getc()!=SOTR)
7
returnFALSE;
8
9
while((c=uart_getc())==SOT)// solange ein SOT daherkommt,
10
// kommt noch ein Parameter
11
ReceiveParameter(&Numbers[i++]);
12
13
if(c!=EOTR)
14
returnFALSE;
15
16
returnTRUE;
17
}
18
19
uint8_tReceiveParameter(int*Number)
20
{
21
charc;
22
23
// der einleitende SOT wurde schon empfangen und hat
24
// dazugeführt, dass diese Funktion aufgerufen wurde
25
//
26
// Zahl einlesen und zusammensetzen.
27
*Number=0;
28
29
while(isdigit(c=uart_getc()))
30
*Number=10**Number+(c-'0');
31
32
return(c==EOT);
33
}
SOTR, EOTR, SOT und EOT sind Makros hinter denen sich die Codes für
Start Of TRansmission, End Of TRansmission, Start of Text, End Of Text
verbergen.
Wie du die Number aus der Funktion rauskriegst (globale Variable,
Parameter für die Funktion): entscheide dich für etwas.
Fehlerbehandlung bei Arrayoverflow muss auch noch rein.
Edit: Das ist ein blockierender Code. Soll heissen ein Aufruf von
ReceiveRecord kommt erst dann zurück, wenn ein kompletter Datensatz
eingelesen wurde. Das kann zu einem Problem werden oder auch nicht, je
nachdem, wie der Rest des Programms aussieht und ob nebenher in der
Hauptschleife noch andere Arbeiten zu erledigen sind.
Frank K. schrieb:
> Also das gefällt mir nicht so. Wie ich das sehe hängt das Porgramm ja an> der Stelle:>
1
>while((c=uart_getc())==SOT)
2
>ReceiveParameter();
3
>
Was soll da hängen?
Solange SOT daherkommen beginnt ein neuer Parameter. Kommt kein SOT mehr
wird abgebrochen (und wenn es dann auch kein SOTR war, ist was faul)
Oder meinst du 'Hängen' im Sinne von: Hauptschleife läuft nicht mehr.
Also im Gegensatz zu: Eventgetriebens Auswerten.
Da hast du recht, hab ich aber auch im Nachsatz (siehe Edit) angemerkt.
Ja also es währe schon gut, wenn in der Hauptschleife weiter gearbeitet
werden könnte. Also ich werde erstmal bei der obigen Lösung bleiben. Bei
den paar Zeilen sieht man ja auch noch durch.