Forum: Mikrocontroller und Digitale Elektronik ATmeag88 <> RaspberryPi - UART Problem


von Gäst_5 (Gast)


Lesenswert?

Hallo,

ich möchte eine UART Verbindung zwischen einem ATmega88 und einem 
RaspberryPi aufbauen.

Ich hab es bisher geschafft, dass von beiden Seiten aus gesendet und 
empfangen werden kann, und das auch die richtigen Nachrichten ankommen.

Ein Problem besteht leider noch, welches ich bisher nicht lösen konnte:

Wenn ich vom ATmega88 mit fprintf("0123456789") einen String schicke, 
und im Raspberry diesen mit der read() Funktion "empfange" und in einen 
Buffer speichere, welcher 256 Zeichen fassen kann und am Ende diesen 
Buffer auf dem Terminal ausgebe, dann werden lediglich die ersten 8 Byte 
ausgegeben, bei einem zweiten Schleifendurchlauf die restlichen 2 Byte.

Warum werden nur die ersten 8 Byte in den Buffer geschrieben?
Gibt es dafür ein Flag, welchen ich falsch gesetzt haben könnte?

Ich bin nach folgendem Beispiel vorgegangen:
http://www.raspberry-projects.com/pi/programming-in-c/uart-serial-port/using-the-uart

: Verschoben durch Moderator
von Gäst_5 (Gast)


Lesenswert?

Weiß niemand eine Antwort? Oder ist meine Frage unglücklich formuliert?

von g457 (Gast)


Lesenswert?

> Weiß niemand eine Antwort?

man 2 read weiss es latürnich:
1
[..] On success, the number of bytes read is returned (zero indicates end
2
of file) [..].  It is not an error if this number is smaller than the
3
number of bytes requested; [..]

Aber wieso ist das überhaupt relevant? Du darfst Dich eh unter keinen 
Umständen darauf verlassen, dass das Laufzeitverhalten immer gleich(tm) 
ist. Das übertragene Datum musst Du deswegen immer zusammensetzen 
(können).

von Gäst_5 (Gast)


Lesenswert?

g457 schrieb:
> man 2 read weiss es latürnich:
>[..] On success, the number of bytes read is returned (zero indicates end
> of file) [..].  It is not an error if this number is smaller than the
> number of bytes requested; [..]
>
> Aber wieso ist das überhaupt relevant? Du darfst Dich eh unter keinen
> Umständen darauf verlassen, dass das Laufzeitverhalten immer gleich(tm)
> ist. Das übertragene Datum musst Du deswegen immer zusammensetzen
> (können).

Versteh nicht was du mir mit dieser Antwort sagen möchtest!?

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Gäst_5 schrieb:
> Versteh nicht was du mir mit dieser Antwort sagen möchtest!?

Dass du nicht erwarten darfst, dass du 10 hintereinander gesendete
Bytes auch in einem einzigen Aufruf an read() zusammen zurück
bekommst.  Es ist völlig legal, dass (aus was auch immer für Gründen)
zuerst nur die ersten 8 und im nächsten read() noch weitere zwei
Bytes geliefert werden.

Daher implementieren serielle Protokolle immer eine am Anfang
versendete Paketlänge.  Damit weiß der Empfänger, wie viele weitere
Bytes zu erwarten sind.

von Gäst_5 (Gast)


Lesenswert?

Aber ich gebe doch der read() Funktion die Länge, bzw die Bytezahl mit.

von Karl H. (kbuchegg)


Lesenswert?

Gäst_5 schrieb:
> Aber ich gebe doch der read() Funktion die Länge, bzw die Bytezahl mit.

Die wird aber nicht zum Empfänger übertragen.
Die steuert nur, wieviele Zeichen read auf die Schnittstelle ausgibt. 
Davon weiß aber der Empfänger nichts. Der sieht ja nur die Zeichen, die 
tatsächlich übertragen werden.

Entweder du teilst dem Empfänger im voraus mit, wieviele Bytes er zu 
erwarten hat, oder der Sender einigt sich mit dem Empfänger auf ein 
'Jetzt ist alles fertig'-Zeichen, an dem er erkennen kann, wann zb eine 
Zahl vollständig übertragen wurde (in dem Sinn, dass jetzt alle Zeichen 
eingetrudelt sind, die die Zahl bilden sollen). Zb einen Zeilenvorschub 
'\n' oder einen ';'

von Thorsten (Gast)


Lesenswert?

Die seriellen Schnittstellen haben einen begrenzten Empfangsbuffer/FIFO. 
Deiner ist vermutlich 8 Bytes groß.

von Gäst_5 (Gast)


Lesenswert?

ich bin nun wie folgt vorgegangen:

ATmega88 sendet einmalig: fprintf(f, "Test_Test_Test");

RaspberryPi:
while (1)
    {
       uart_read();
    }

//********** UART Empfangen **********
void uart_read (void)
{

    if (uart0_filestream != -1)
    {

        unsigned char rx_buffer[256];
        int rx_length = read(uart0_filestream, (void*)rx_buffer, 255);

        if (rx_length < 0)
        {
            //printf("Error\n");
        }
        else if (rx_length == 0)
        {
            //printf("No data waiting.\n");
        }
        else
        {
            rx_buffer[rx_length] = '\0';
            printf("UART: %i bytes - read : %s \n", rx_length, 
rx_buffer);

            printf("loop %i\n", x);
            x++;
        }
    }
    return;
}

Auf der Konsole sieht es folgendermaßen aus:

UART: 8 bytes - read : Test_Tes
UART: 6 bytes - read : t_Test

Was sagt mir das?
Ich sende einmalig "Test_Test_Test" welches übertragen wird und im AMA0 
(FIFO) abgelegt ist. Wenn dieser wirklich nur 8 Byte fassen kann, warum 
werden die restlichen 6 Byte anständig übertragen und angezeigt?

Mir ist klar das der RaspPi viel viel schneller läuft, als der ATmega 
und vorallem als die eingestellte Baudrate.
Die Funktion uart_read() wird dann wahrscheinlich 10 mal aufgerufen, 
bevor ein Zeichen geschickt wurde. Warum werden dann aber immer 8 Byte 
"gesammelt" ?

von Karl H. (kbuchegg)


Lesenswert?

Gäst_5 schrieb:

> Was sagt mir das?
> Ich sende einmalig "Test_Test_Test" welches übertragen wird

Ja.
Aber die Übertragung BRAUCHT ZEIT!
Das ist ja nicht so, dass du den fprintf aufrufst und quasi instantan, 
in 0-Zeit, ist der komplette String zur Gegenstelle übertragen worden.

Den Mega beginnt zu senden
  T
  e
  s
  t

irgendwann, zu irgendeinem Zeitpunkt beginnt der Rasperry nachzusehen, 
ob auf der Schnittstelle was da ist.
Ja! Da ist was da! Nämlich "Test" und das kriegst du (bei dir eben 8 
Zeichen). Warum nur diese Zeichen? Weil der Rest vom Mega noch gar nicht 
weggeschickt worden ist! Der Mega ist noch damit beschäftigt, die 
letzten Zeichen auf den Weg zu bringen, währen der Raspberry schon damit 
anfängt, die bereits empfangenen Zeichen aus dem Buffer zu holen.


Gewöhn dich daran, was asynchrone Verarbeitung bedeutet! Die beiden 
Rechner sind unabhängig voneinander! Die warten nicht aufeinander. Es 
gibt auf der UART keinen Mechanismus, der dafür sorgt, dass hier 
synchronisiert wird. Der eine sendet Zeichen, der andere holt sich 
Zeichen. Der der sich die Zeichen von seiner Schnittstelle holt, der 
kriegt dann eben den Teil, der bereits eingetroffen ist. Selbst wenn der 
Sender noch gar nicht alles vollständig weggeschickt hat.

> bevor ein Zeichen geschickt wurde. Warum werden dann aber immer 8 Byte
> "gesammelt" ?

Weil irgendein Mechanismus am Pi zb bei 8 Zeichen im Buffer sich 
einklinkt und die Verarbeitung anstösst. Eine enstprechende Analyse, was 
da im Detail ganz genau abläuft, ist aufwändig. Du kannst sie ja führen, 
wenn du willst. Das spielen dann auch noch andere Designentscheidungen 
mit rein. Zb, eine Steuerung im read, welches zb in ein Timeout 
reinläuft oder auch nicht, weil nach einigen Zeichen in einer gewissen 
Zeitspanne kein Zeichen an der Schnittstelle eingetroffen ist und read() 
nicht länger warten will.
Aber du musst endlich weg von der Vorstellung, dass du am Mega einen 
fprintf machst, und der ganze Text wandert in einem Rutsch ohne 
Unterbrechung zum Pi und kommt dort auch als solcher wieder in einem 
Stück an. DEM IST NICHT SO!

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Gäst_5 schrieb:
> Aber ich gebe doch der read() Funktion die Länge, bzw die Bytezahl mit.

Die wird ausschließlich als Maximum betrachtet, wobei man über
tcsetattr() einstellen kann, wie lange ggf. gewartet werden soll
um zu sehen, ob noch Zeichen verfügbar sind.

von Gäst_5 (Gast)


Lesenswert?

hm... wie macht man denn sowas am besten?

Pi sendet zum ATmega, dieser empfängt und soll Antworten, bis das 
geschehen ist, hat der Pi doch schon 10 mal neu gesendet, wenn alles in 
einer Schleife abgearbeitet wird.

Ich müsste also irgendwo warten, bis der ATmega antwortet, und dann auch 
noch wissen wie viel er senden will.
Und zu guter letzt auch noch die Nachricht zusammensetzen, wenn es mehr 
als 8 Byte sind.

Wie kann ich das am besten realisieren?
Könnte man beim Pi nicht eine Art ISR anstoßen, sobald er über den UART 
was empfängt?

von Karl H. (kbuchegg)


Lesenswert?

Gäst_5 schrieb:
> hm... wie macht man denn sowas am besten?
>
> Pi sendet zum ATmega, dieser empfängt und soll Antworten, bis das
> geschehen ist, hat der Pi doch schon 10 mal neu gesendet, wenn alles in
> einer Schleife abgearbeitet wird.
>
> Ich müsste also irgendwo warten, bis der ATmega antwortet, und dann auch
> noch wissen wie viel er senden will.
> Und zu guter letzt auch noch die Nachricht zusammensetzen, wenn es mehr
> als 8 Byte sind.

Ja, sowas nennt man "Protokoll".
Eine Vorschrift, wer wann was sendet.

Denk dir was aus.
Zumindest für letzteres (die Sache mit den 8 Byte) haben wir uns ja 
mitlerweile ohnehin schon den Mund fusselig geredet :-)

von Gäst_5 (Gast)


Lesenswert?

Danke erstmal für die Hilfe :-)

> Denk dir was aus.
Es gibt doch bestimmt eine allg. Herangehensweise, oder?

Würde gern mal eine einfaches, verständliches Tutorial dazu lesen, 
leider habe ich bisher nichts passendes gefunden...

Vor allem wie es beim Pi gemacht wird.

von Gäst_5 (Gast)


Lesenswert?

Kann ich denn den UART abfragen, ob er fertig ist und alle Zeichen 
gesendet hat?

von Karl H. (kbuchegg)


Lesenswert?

Gäst_5 schrieb:
> Kann ich denn den UART abfragen, ob er fertig ist und alle Zeichen
> gesendet hat?

Wozu?
Was bringt dir das?
Die Sende-Funktion kommt aus dem Aufruf zurück, wenn sie alle Zeichen an 
der UART abgeliefert hat. Reicht doch.


zunächst mal brauchst du eine Vereinbarung mit dir selber. Da bis jetzt 
nur von Texten die Rede war, die ausgetauscht werden, bleibe ich mal 
dabei.
Die Vereinbarung lautet: Jeder Text wird an der UART-Schnittstelle mit 
einem # abgeschlossen. Am # kann die Gegenstelle erkennen, dass sie 
jetzt alle Zeichen des zu empfangenden Strings hat. Ein #-Zeichen darf 
im Text nicht vorkommen.

Das ist deine erste Zutat zum Protokoll: Eine Vereinbarung, mit der der 
Empfänger zweifelsfrei feststellen kann, wann eine Nachricht aufhört.

Und der Rest ist jetzt einfach nur eine Vereinbarung wer welchen String 
zum jeweiligen Kommunikationspartner schickt um welche Aktion zu 
veranlassen. Die Gegenstelle muss zb die Ausführung des Befehls 
quittieren.
zb. kannst du vereinbaren, dass der Mega die Befehle versteht
SET p b#
mit der Bedeutung: am Port, der mittels p angegeben wurde, das Bit b zu 
setzen. Will der Pi also, dass der Mega am PORTB das Bit 6 auf 1 setzt, 
dann überträgt er den Befehl
SET B 6#
zu Mega. Der führt die Aktion aus und Antwortet zb mit einem
OK#
Will der Pi wissen, welchen Zustand gerade der Pin 4 vom Port D hat, 
dann sendet er zb an den Mega die Anfrage
? D 4#
der Mega erhält diesen Befehl, liest den Pin aus und antwortet mit
1 OK#
Der Pi kann auch versuchen, vom Mega den Zustand des Pins 5 am Port J zu 
erfragen. Kann er - geht aber nicht. Der Mega hat diesen Port nicht.
Der Pi schickt also die Anfrage
? J 5#
und der Mega antwortet mit
ERROR#


Die Gesamtheit all dieser Vereinbarungen nennt man ein Protokoll. Es 
beginnt damit, dass man zunächst mal definiert, wie man aus einem 
Zeichenstrom eine 'Nachricht' eindeutig identifizieren kann und geht 
dann weiter damit, dass man definiert wer wem was als eine derartige 
Nachricht schickt bzw. was der so angesprochene antwortet.


So ... und jetzt denk dir was aus, was für deine Anwendung vernünftig 
ist. Du darfst ruhig kreativ sein. Schreib das alles zusammen - gib 
deinem Kumpel eine Kopie deiner Mitschrift und setz ihn ins Nebenzimmer. 
Und dann rufst du ihm Buchstaben zu. Einen nach dem anderen. Wenn dein 
Protokoll was taugt, dann kann dein Kumpel mit der Protokollbeschreibung 
auf dem Zettel genau das ausführen, was du (im Rahmen deiner Anwendung) 
von dem Zusammenspiel aus Pi und Mega erwartest. Wenn er bei dir 
mündlich nachfragen muss, wie etwas gemeint ist, dann hast du einen 
weißen Fleck in deiner Protokollbeschreibung entdeckt.


Und ja. Du hast in deinem Leben schon hunderttausendmal Protokolle 
befolgt. Zb war ein Protokoll

  der Lehrer stellt eine Frage
  weißt du die Antwort? Wenn nein, dann machst du nichts
  Wenn ja, dann geht dein Arm mit ausgestrecktem Zeigefinger hoch
  Der Lehrer wählt dich aus
  Du stehst auf
  Du nennst die Antwort
  Der Lehrer bewertet die Antwort
  Du setzt dich

Auch das ist nichts anderes als ein Protokoll. Ein Prozedere, was in 
welcher Reihenfolge zu erfolgen hat.

von Gäst_5 (Gast)


Lesenswert?

ich verstehe noch nicht ganz wie ich vorgehen muss:

Raspberry sendet etwas auf den UART
...Zeit vergeht bis Nachricht vollständig übermittelt wurde...
ATmega bekommt Nachricht und wertet diese aus
ATmega sendet Antwort auf UART
...Zeit vergeht bis Nachricht vollständig übermittelt wurde...
Und erst jetzt sollte doch der Raspberry den UART lesen

Also müsste ich doch beim Raspberry irgendein RX Flag abfragen können, 
was mir signalisiert das der UART etwas empfangen hat...

Oder wie macht man das?

Sorry, hab bisher nur mit µC gearbeitet...

von Karl H. (kbuchegg)


Lesenswert?

Gäst_5 schrieb:

> Also müsste ich doch beim Raspberry irgendein RX Flag abfragen können,
> was mir signalisiert das der UART etwas empfangen hat...

Zb.
Uart Filestreams haben etwas das man ein Timeout nennt.
Du versuchst einfach auf gut Glück mittels read etwas zu lesen. Ist was 
da, dann kriegst du es. Ist nichts da, dann wartet read ein bischen 
(aber nicht zu lange) und meldet dann: nichts da.

von Karl H. (kbuchegg)


Lesenswert?

> Sorry, hab bisher nur mit µC gearbeitet...

Dann such dir ein Tutorial, welches dir beibringt, wie man auf Linux mit 
einer UART umgeht.

von Gäst_5 (Gast)


Lesenswert?

Danke erstmal für die Mühe und die ausführliche Antwort.

Karl Heinz Buchegger schrieb:
> Dann such dir ein Tutorial, welches dir beibringt, wie man auf Linux mit
> einer UART umgeht.

Gäst_5 schrieb:
> Würde gern mal eine einfaches, verständliches Tutorial dazu lesen,
> leider habe ich bisher nichts passendes gefunden...

...

Karl Heinz Buchegger schrieb:
> Jeder Text wird an der UART-Schnittstelle mit
> einem # abgeschlossen. Am # kann die Gegenstelle erkennen, dass sie
> jetzt alle Zeichen des zu empfangenden Strings hat. Ein #-Zeichen darf
> im Text nicht vorkommen.

Das versuche ich grad umzusetzen. Leider hänge ich schon wieder an einer 
ganz dummen stelle Fest.

Ich möchte mit:

int length = strchr(rx_buffer, '#');

Nach deinem vorgeschlagenen Zeichen im String suchen,
leider sagt der Compiler:
Fehler: call of overloaded 'strchr(unsigned char [32], char)' is 
ambiguous

Ich hab nun so viel probiert und gesucht, finde keine Lösung dafür?!

von Sven S. (boldie)


Lesenswert?

Der Prototyp für diese Funktion ist nicht unsigned char  sondern char  
und als character erwartet er einen int. Der character könnte er 
konvertieren, aber zwischen unsigned char und char kann er nicht 
implizit konvertieren, das musst du explizit machen, oder gleich deinen 
Datentyp als char anlegen.

Prototyp:
char * strchr(char * str, int character);

von Name (Gast)


Lesenswert?

...und jetzt nochmal für Anfänger?

> Der Prototyp für diese Funktion ist nicht unsigned char  sondern char

An welcher Stelle hab ich denn festgelegt das es unsigned char ist?


> und als character erwartet er einen int

Warum?

> Prototyp:
> char * strchr(char * str, int character);

Wie würde dies auf meinen Fall angewandt ausschauen?

von Marian (phiarc) Benutzerseite


Lesenswert?

Ich würde vorschlagen, dass du dich zuerst etwas intensiver mit C 
beschäftigst und danach mit POSIX/UNIX I/O.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Name schrieb:
> An welcher Stelle hab ich denn festgelegt das es unsigned char ist?

Offensichtlich bei der Definition von rx_buffer[32], denn genau das
sagt dir bereits der Compiler.

Übrigens lohnt es sich manchmal dazuzuschreiben, dass man nicht C,
sondern C++ benutzt.  Nicht alles ist zwischen beiden völlig gleich.

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.