Forum: Compiler & IDEs UART-Daten binär ausgeben - Verständnisproblem


von perni (Gast)


Lesenswert?

Hallo,

ich bin relativ neu bei der Programmierung von Atmel Controllern und hab 
ein kleines Verständnisproblem mit dem Umgang des UART. (Ja hab im Forum 
gesucht und auch das Tutorial angeschaut)

Ich hab an meinem Controller vier Sensoren angeschlossen, von denen ich 
die Seriennummer, Justierungsdaten, und dann die aktuellen Sensordaten 
auslesen will. Zudem will ich die gemessenen Daten an einen PC schicken. 
Hab soweit auch alles am laufen (messen, auswerten und verschicken der 
Daten).

Jetzt ist es so, dass z.B die Seriennummer 24 Bit lang ist. Wenn ich die 
Daten über UART auf den PC schicke sende ich 7 Zahlen, die dann als 
ASCII-Zeichen gesendet werden (oder nur vom Hyperterminal so angezeigt 
werden??) und ich somit 7 Byte statt 3 Byte (wenn ich 24 Bit binär 
übertrage) benötige. Prinzipiell kein Problem, aber wenn ich sekündlich 
SN(24Bit) und die Messwerte (2* 16Bit) bei vier Sensoren übertragen 
will, dann summiert sich das ganz schön....

Jetzt meine Frage:
Zeigt mir nur der Terminal automatisch Bytes an oder sendet der µC über 
den Uart ASCII-Zeichen? Denn eigentlich wird ja über uart auch nur binär 
gesendet....

Ich habe auch schon versucht die Daten binär umzuwandeln und zu senden, 
was aber irgendwie auch nicht so sinnvoll war, weil ich dann die Nullen 
und Einsen einzeln als ASCII-Zeichen gesendet hab....^^

Sorry, aber ich komm einfach nicht drauf....

Schon mal vielen Dank für eure Antworten

Gruß
Perni

von O. D. (odbs)


Lesenswert?

Wenn dir das Hyperterminal Klartext anzeigt, dann sendest du die Daten 
schon fertig von binär in einen ASCII-String umgewandelt über den USART 
raus.

Wie sieht das in deinem Programm aus? Stell mal die Senderoutine und 
deren Aufruf hier rein, dann können wir dir erklären, wo das passiert. 
An genau der Stelle müsstest du es dann auch ändern, wenn du die Daten 
binär rausschicken möchtest.

Du brauchst dann allerdings ein passendes Anzeigeprogramm auf dem PC, 
Hyperterminal wird die Binärdaten nicht für dich interpretieren und 
umwandeln.

von perni (Gast)


Lesenswert?

Erstmal danke für die schnelle Antwort!

Vorab: Im Moment gebe ich die Daten absichtlich in float und hex aus, 
damit ich es eben besser lesen kann....
Die Daten - Rohdaten und nicht die berechneten die ich im Moment ausgebe 
- sollen aber als Binärzahl am PC ausgegeben und dort erneut berechnet 
werden. (Protokoll hab ich mir schon ausgedacht bzw. erstellt)
1
for (i=0; i<4; i++)
2
 {
3
  printf("%02x %02x %07ld %04.3f %04.3f %04.3f\n", Message_ID, Message_Index, Serial[i], pH[i], Temp[i] );
4
  Message_Index +=1;
5
 }

Wie gesagt, dass ich die Werte als Hex bzw. Gleitkommazahl ausgebe ist 
mir klar, ich kenn aber leider keinen Operator, der mir das als 
Binärzahl ausgibt....

Eine Umwandlung wie
1
for(i = (sizeof *8)-1; ....

brachte mir wie oben erwähnt auch nur Nullen und Einsen die in ASCII 
angezeigt wurden....

Danke!!

Gruß
Michael

von O. D. (odbs)


Lesenswert?

printf ist, wie der Name schon sagt, für die formatierte Ausgabe von 
Daten zuständig. printf selbst baut im Speicher einen String anhand 
deiner Angaben zusammen und benutzt dann eine Low-Level-Funktion, die 
einzelne Zeichen auf dem gewünschten Gerät ausgibt.

Du greifst also eine Ebene zu hoch an. Benutze einfach die Funktion, die 
einzelne Zeichen auf den USART schreibt direkt - anstelle von printf. 
Wenn du nicht weißt, wie die heißt, weil du sie nicht selbst geschrieben 
hast, musst du in der Doku der verwendeten Lib nachsehen.

Irgendetwas in der Art von usart_putc(unsigned char c) gibt einzelne 
Bytes aus. Bei floats oder ints, die breiter als 8 Bit sind, schreibst 
du dir entweder nach dem vorhandenen Muster eine eigene Ausgaberoutine, 
oder baust eine Schleife mit sizeof und benutzt die Variable als Pointer 
auf das erste Byte. Dann kannst du die einzelnen Bytes in beliebiger 
Reihenfolge über die serielle Schnittstelle ausgeben.

von Perni (Gast)


Lesenswert?

Vielen Dank für den Hinweis!
Da fällt mir ein, dass ich sowas schon mal definiert gesehen habe - es 
aber nicht verwendet und somit auch nicht weiter verfolgt habe....

Also, ich hab mir da jetzt mal aus dem vorherigen Bastler-Projekt (das 
ich nicht selbst erstellt habe sondern fast fertig übernommen und nur 
weiter "programmiert" habe) nachgeschaut und da war eben jenes "putchar" 
schon definiert.

Jetzt habe ich das ganze in mein Projekt eingefügt (es funktioniert 
auch) und jetzt sieht meine Ausgabe wie folgt aus:
1
.
2
.
3
.
4
//Definitionen
5
int uart1_getchar(FILE *stream)
6
{
7
  while ( !(UCSR1A & (1<<RXC1)) );
8
  return  UDR1;
9
}
10
int uart1_putchar(char ch, FILE *stream)
11
{
12
      if (ch == '\n')
13
        uart1_putchar('\r', stream);    //add line feed
14
      while ( !(UCSR1A & (1<<UDRE1)) );
15
      UDR1 = ch;
16
      return 0;
17
}
18
19
FILE uart_str = FDEV_SETUP_STREAM(uart1_putchar, uart1_getchar, _FDEV_SETUP_RW);
20
21
stdout = stdin = &uart_str;
22
.
23
.
24
.
25
// Irgendwo im Code
26
for(int w=0; w < 3; w++)
27
   uart1_putchar(Serial_Bytewise[i][w], &uart_str);

Wie man daraus schon erkennen kann, habe ich die Seriennummer jetzt auf 
Bytes aufgeteilt, allerdings wohl nicht sehr elegant:
1
Serial_Bytewise[i][0] = Serial[i];
2
Serial_Bytewise[i][1] = Serial[i]>>8;
3
Serial_Bytewise[i][2] = Serial[i]>>16;
Das gleiche droht mir jetzt bei den anderen Werten (12 & 14 Bit lang) 
auch. Fällt da jemandem eine elegantere Lösung ein??

Nochmal vielen Dank an dich Oliver!!
Deine Beiträge waren mir sehr hilfreich!!

von Karl H. (kbuchegg)


Lesenswert?

Perni schrieb:

> // Irgendwo im Code
> for(int w=0; w < 3; w++)
>    uart1_putchar(Serial_Bytewise[i][w], &uart_str);
> [/c]
>
> Wie man daraus schon erkennen kann, habe ich die Seriennummer jetzt auf
> Bytes aufgeteilt, allerdings wohl nicht sehr elegant:
>
1
> Serial_Bytewise[i][0] = Serial[i];
2
> Serial_Bytewise[i][1] = Serial[i]>>8;
3
> Serial_Bytewise[i][2] = Serial[i]>>16;
4
>
> Das gleiche droht mir jetzt bei den anderen Werten (12 & 14 Bit lang)
> auch. Fällt da jemandem eine elegantere Lösung ein??

Wie auch immer du das machst, schreib dir auf jeden Fall eigene 
Funktionen dafür. Es ist unsinnig die Byte-Zerlegung jedesmal wieder 
aufs neue in den Hauptcode mit aufzunehmen.
1
void uart_put24Bit( uint32_t data, FILE *stream )
2
{
3
  uart1_putchar( data, stream );
4
  uart1_putchar( data >> 8, stream );
5
  uart1_putchar( data >> 16, stream );
6
}
7
8
...
9
10
  // Irgendwo im Code
11
  uart_put24Bit( Serial[i], &uart_str );

12 und 14 Bit sind beides 2 Bytes, geht also über dieselbe Funktion 
uart_put16Bit.


Einer der Tricks beim Programmieren besteht darin, sich einen 
sinnvollen Satz an Funktionen zurechtzulegen, die einem die 
eigentliche Arbeit dann erleichtern.

(Wozu brauchst du eigentlich den ganzen stream Quatsch. Nur damit du 
printf verwenden kannst? Du schleifst da massenhaft Argumente durch für 
einen mehr als zweifelhaften Komfort)

von Perni (Gast)


Lesenswert?

Danke für die Info, die Funktionen hab ich bereits erstellt, nur eben 
auf meine Anforderung abgestimmt (zwecks Protokoll). Hab mir ein Array 
erstellt und schreib dort die Daten an die Stelle, an der ich sie 
brauche und lass mir dann über eine for-Loop die Daten der Reihe nach 
ausgeben...

Wie oben schon erwähnt, hab ich mich mit dem putchar noch nicht wirklich 
befasst und hab es nur so aus einem vorherigen Projekt übernommen.
Im Moment funktioniert es so und ich habe noch keine Ahnung wie ich es 
ohne den "stream Quatsch" realisieren soll...
Die Optimierung werde ich wohl noch in Angriff nehmen müssen....

von Karl H. (kbuchegg)


Lesenswert?

Perni schrieb:

> Im Moment funktioniert es so und ich habe noch keine Ahnung wie ich es
> ohne den "stream Quatsch" realisieren soll...

Einfach das FILE* Argument überall weglassen und die
1
FILE uart_str = FDEV_SETUP_STREAM(uart1_putchar, uart1_getchar, _FDEV_SETUP_RW);
Vereinbarung rausnehmen. Du kannst dann zwar printf nicht mehr benutzen, 
aber das ist kein Beinbruch.

von Perni (Gast)


Lesenswert?

Wie recht du doch hast ;-)

Vielen Dank euch Beiden, jetzt bin ich wieder ein ganzes Stück weiter!!!

von Stefan (Gast)


Lesenswert?

Hast Du Dir schon überlegt, wie Du den Anfang einer Msg feststellst?

Natürlich kannst Du sagen: ok, Byte 1-3 sind die Seriennummer, Byte 4+5 
der Messwert, etc., und danach startet alles wieder von vorne. Wenn Du 
aber irgendwo mal ein Byte verpasst (oder eine Störung ein zusätzliches 
Byte "generiert"), dann hast Du ein Problem, und zwar bis zum nächsten 
Reset der Messwerterfassung.

In der Praxis wird Dir das beim Entwickeln garnicht auffallen (weil der 
fehler selten vorkommt), später im Praxiseinsatz können dann aber ganze 
Messwertreihen unbrauchbar werden.

Bei der Klartext-Variante hast Du da erheblich weniger Probleme, Du 
schliesst einfach jeden Messwert mit CR ab und synchronisierst im PC 
darauf.

Wenn Du wirklich nur 1* je Sek die Daten überträgst, würde ich mir 
keinen Stress machen, ob wenig oder sehr wenig Daten übertragen werden, 
ist relativ egal.

Viele Grüße, Stefan

von Perni (Gast)


Lesenswert?

Ich hab mir da was überlegt, was relativ sicher sein sollte. Aber wenn 
konstruktive Vorschläge kommen bin ich sehr offen ;-)

Ich hab meine MSG wie folgt aufgebaut:

(|Text| entspricht immer 1 Byte)

|MSG-ID|MSG-Index|SN1_1|SN1_2|SN1_3|SN2_1|.....|CRC|

Durch die MSG-ID weiß ich wieviel Daten ankommen sollen (das ist fix).
Die SN z.B. sende ich nur am Anfang einmal, danach sollte der Sensor bis 
zum Abschalten so und so nicht ausgetauscht werden...
MSG-Index lass ich hochlaufen, damit ich weiß ob ich auch alle MSG 
bekommen habe, oder ob eine verloren ging.
Danach kommen meine Daten und am Ende kommt eine CRC (weiß noch nicht ob 
1 oder 2 Byte - damit beschäftige ich mich jetzt erst ;-) )

Danke für die konstruktive Kritik!! Gerne mehr davon...

von Karl H. (kbuchegg)


Lesenswert?

Perni schrieb:


> (|Text| entspricht immer 1 Byte)
>
> |MSG-ID|MSG-Index|SN1_1|SN1_2|SN1_3|SN2_1|.....|CRC|

Das Problem ist, dass in deinen Seriennummern Bytes grundsätzlich jeder 
Byte Wert vorkommen kann. Auch einer dessen ASCII Code deinem |Text| 
entspricht.

Solange alles in geordneten Bahnen läuft, ist das kein Thema. 
Interessant wird es erst, wenn eine Störung auftritt und dein Empfänger 
das Problem hat, dass er aus dem Datenstrom das Zeichen |Text| 
rausfischen und als Anfang einer Msg erkennen muss. Und zwar eindeutig! 
Denn wenn er das nicht kann, fällt deine Übertragung nie wieder von 
alleine auf die Füsse. Dass der Empfänger durch den CRC Vergleich 
feststellen kann, dass die Msg insgesammt nicht gültig ist, hilft dann 
herzlich wenig.

Wenn du Übertragungsprotokolle auf dem Papier testen willst, dann denk 
dir den fiesesten Datensatz aus, den du dir vorstellen kannst (zb die 
Seriennummer Bytes sind alle identisch zu dem Zeichen in |Text|). Und 
dann stell dir vor, während eine Übertragung läuft zieht die Putzfrau 
das Kabel ab und steckt es erneut an.
Dein Empfänger erhält also nicht das
1
|Text|MSG-ID|MSG_index|SN1_1|SN1_2|SN_2_1|...|CRC|
sonder das
1
   MSG_index|SN1_1|SN1_2|SN_2_1|...|CRC|und weitere Bytes die danach kommen.
Kann der Empfänger zweifelsfrei und absolut eindeutig dieses Problem 
identifizieren und sich selbsttätig hier
1
   MSG_index|SN1_1|SN1_2|SN_2_1|...|CRC|und weitere Bytes die danach kommen.
2
                                        ^
auf die nächste Msg einklinken? Das die vorhergehende Msg (da nicht 
vollständig) verworfen wird bzw. eine Fehlermeldung generiert ist klar. 
Aber danach muss der Empfänger mit der nächsten Msg weitermachen können, 
und zwar richtig und ohne das Bytes falsch zugeordnet werden.
Bei einem guten Protokoll kann er das.

von Perni (Gast)


Lesenswert?

Vielleicht hab ich mich bisschen blöd und unausreichend ausgedrückt.

Nur um sicher zu gehen, dass es richtig verstanden wurde:
|Text| sollte nur sagen, dass zwischen zwei "|" immer 1 Byte ist.
-> MSG-ID = 1 Byte
-> MSG-Index = 1 Byte
-> SN1_1 = 1Byte
usw
.
.
.

D.h. ich fange immer mit einer MSG-ID an, was aber das Problem natürlich 
noch nicht löst. Zusätzlich erwarte ich immer noch eine Antwort des PC's 
auf richtigen Empfang, sprich ein ACK, mit dem MSG-Index oder der 
CRC....soweit bin ich aber noch (lang) nicht....


Man merkt aber sofort, dass hier jede Menge erfahrener Mitglieder 
unterwegs sind ;-)

Danke!!

von Karl H. (kbuchegg)


Lesenswert?

Perni schrieb:

> D.h. ich fange immer mit einer MSG-ID an,

Wenn dein Empfänger diese Bytes bekommt (hexadezimale Schreibweise)

(Aus Sicht des Empfängers beginnt die Übertragung genau jetzt zu laufen. 
Der Sender sendet schon seit 10 Minuten)

  0x56 0x76 0x79 0x02 0x03 0x79 0x48 0x29 0x49

woher weiß er, dass das erste Byte 0x79 einfach nur ein Datenbyte im 
Datensatz der Msg darstellt, in die der Empfänger zufällig 
reingeschlittert ist, und das zweite 0x79 die Msg-Id der nächsten Msg 
darstellt? Oder anders ausgedrückt: Wie stellt der Empfänger fest, dass 
er die ersten 5 Bytes, die er empfangen hat ignorieren soll und beim 6. 
Byte die erste vollständige Msg anfängt?

Wenn du dieses Problem vollständig verstanden hast, hast du auch 
verstanden, warum sich textuelle Übertragungen (also: ASCII) auch heute 
noch großer Beliebtheit erfreuen, wenn man sich den Overhead leisten 
kann. Denn in Textform ist dieses Problem meistens trivial zu lösen, 
weil eben in normalem Text nicht jeder Bytewert vorkommt und man einige 
Werte (meistens Sonderzeichen) dafür reservieren kann, genau diese 
Synchronisierung zu leisten. (Jeder Command Line Interpreter macht das. 
Das Zeichen Return ist das Kennzeichen, dass eine Zeile zu Ende ist und 
eine neue anfängt).

von Perni (Gast)


Lesenswert?

Ich habe das Problem soweit schon verstanden, bei mir ist es nur so, 
dass ich regelmäßig (alle 0,5s bis 1s) die Daten schicke und nebenbei 
noch ein paar andere Werte berechnen und vergleichen will. Wenn ich 
diese dann per printf und somit in ASCII übertrage, dann benötige ich 
110 Byte - Binär brauch ich dagegen nur 24 Byte....

Außerdem werden die Systeme gleichzeitig gestartet und laufen nicht 
länger als 12 h an einem Stück, von daher gehe ich davon aus, dass ich 
das Problem in den Griff bekomme. Sollte ich die Probleme nicht in den 
Griff bekommen, weiß ich zum Einen woran es liegen könnt und zum Anderen 
an wen ich mich (hoffentlich) wieder wenden kann ;-)

Danke nochmal für die Unterstützung!

von Karl H. (kbuchegg)


Lesenswert?

Perni schrieb:
> Ich habe das Problem soweit schon verstanden, bei mir ist es nur so,
> dass ich regelmäßig (alle 0,5s bis 1s) die Daten schicke

Aus Sicht eines µC also massig Zeit.

> und nebenbei
> noch ein paar andere Werte berechnen und vergleichen will.

Das ist gut. Dann ist ihm nicht so langweilig.

> Wenn ich
> diese dann per printf und somit in ASCII übertrage, dann benötige ich
> 110 Byte - Binär brauch ich dagegen nur 24 Byte....

OK. Baudrate hernehmen und ausrechnen, wieviel Zeit für die Übertragung 
drauf geht. bzw. umgekehrt ausrechnen welche Baudrate mindestens 
benötigt wird um all 1 Sekunde 110 Byte zu übertragen. Die Ergebnisse 
werden dein Weltbild ins Wanken bringen :-)

(Baud hat die Einheit Bit/Sekunde. Um 1 Byte, also auch 1 Zeichen zu 
übertragen benötigt man 10 Bit, wegen Start und Stopp-Bit. Bei 9600Baud, 
kannst du also rund 960 Bytes pro Sekunde übertragen. Deine 110 Byte 
sind gerade mal über den Daumen gepeilt ein Neuntel davon und sind daher 
in etwas mehr als einer Zehntel Sekunde übertragen. Und 9600Baud ist 
verdammt langsam)

PS: Sowohl Senden als auch Empfangen kann man mittels Interrupts 
erledigen lassen, d.h. das kostet nur die Zeit die Daten aufzubereiten 
und in einen Buffer zu stecken. Auf den Weg gebracht werden die Daten 
dann mittels Interrupt, d.h. die eigentliche Übertragung belastet dein 
Programm so gut wie gar nicht.
Umgekehrt genauso: Der andere µC macht seinen Empfang ebenfalls per 
INterrupt und erst dann wenn er einen Datensatz komplett hat, 
benachrichtigt er das Hauptprogramm sich den Datensatz abzuholen.


Mir persönlich ist es egal, ob du eine rein binäre Übertragung machst 
oder nicht. Aber bis jetzt hast du kein stichhaltiges Argument gebracht, 
warum du unbedingt rein binär brauchst. Deine Zeitbedenken sind nicht 
stichhaltig.

von Walter (Gast)


Lesenswert?

Perni schrieb:
> int uart1_putchar(char ch, FILE *stream)
> {
>       if (ch == '\n')
>         uart1_putchar('\r', stream);    //add line feed
>       while ( !(UCSR1A & (1<<UDRE1)) );
>       UDR1 = ch;
>       return 0;
> }

das solltest du nicht für eine binäre AUsgabe verwenden, wegen add line 
feed

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.