Forum: Mikrocontroller und Digitale Elektronik UART sendet Zeichen mehrmals ATMega1280


von Terfagter (Gast)


Lesenswert?

Hallo zusammen,

ich möchte einen String an die µC senden und dieser soll dann wieder 
zurück gesendet werden. Das funktioniert soweit auch, wenn ich ein delay 
von 1sec einbaue. Nehme ich das delay weg, sendet die UART den String 
mehrmals (>15) und vorher und zwischendrin mehrmals ein CR.
Das zweite Problem ist, das manchmal einige Zeichen in einem String 
vertaucht werden. Sende ich z.B. 123456789 kommt manchmal auch 132456798 
z.B. an.

Ich benutze einen ATMega1280 an einem 8 Mhz Quartzoszillator.
Die AVR Checkliste bin ich schon durchgegangen.
Danke für eure Hilfe.

main.c:
//****************************************************************
//  Dateiname    :   main.c
//
//  Erstellt    :   19.08.2010
//  Letzte Änderung :   23.08.2010
//
//  Funktion    :
//****************************************************************

#include <avr/io.h>
#include <util/delay.h>
#include <stdlib.h>
#include "uart.h"
#include <avr/interrupt.h>
#include "temp.h"
#include <stdio.h>


int main (void)
{

  initusart0();
  sei();
  char buffer[20];

  while(1)
  {
          //_delay_ms(1000);
    if (char_vorh() == 1)
                {
        uart_gets(buffer, 20);
        putstr0(buffer);
    }
  }
}


uart.h:
//****************************************************************
//  Dateiname    :   uart.h
//
//  Erstellt    :   19.08.2010
//  Letzte Änderung :   21.08.2010
//
//  Funktion    :
//****************************************************************
#include <avr/interrupt.h>

#define TAKT  8000000UL
#define UBRR_VAL0 ((TAKT+BAUD*8)/(BAUD*16)-1)
#define BAUD  9600UL
#define BUFFER_LEN 20

//USART0
//****************************************************************
char Buffer0[BUFFER_LEN];

volatile uint8_t NextWriteBufferPos = 0;
volatile uint8_t NextReadBufferPos = 0;
volatile uint8_t CharsInBuffer = 0;

void initusart0(void)
{
  UBRR0 = UBRR_VAL0;
  UCSR0B |= (1 << TXEN0) | (1 << RXEN0) | (1 << RXCIE0);
  UCSR0C |= (1<<UCSZ01)|(1<<UCSZ00);
}

void putch0( char x )
{
    while (!(UCSR0A & (1<<UDRE0)));
    UDR0 = x;
}

ISR(USART0_RX_vect)
{
  Buffer0[NextWriteBufferPos] = UDR0;

  NextWriteBufferPos = ( NextWriteBufferPos + 1 ) % BUFFER_LEN;
  CharsInBuffer++;
}

int char_vorh(void)
{
  if( CharsInBuffer == 0 )
  return 0;
  return 1;
}


char getch0(void)
{
  char NextChar = Buffer0[NextReadBufferPos];
  NextReadBufferPos = ( NextReadBufferPos + 1 ) % BUFFER_LEN;
  CharsInBuffer--;

  return NextChar;
}


void uart_gets( char* Buffer, uint8_t MaxLen )
{
  uint8_t NextChar;
  uint8_t StringLen = 0;

  NextChar = getch0();
  while( NextChar != '\n' && StringLen < MaxLen - 1 )
  {
    *Buffer++ = NextChar;
    StringLen++;
    NextChar = getch0();
  }
  *Buffer = '\0';
}


void putstr0 (unsigned char *data)
{
   while (*data)
   {
    putch0(*data++);
   }
}

von Karl H. (kbuchegg)


Lesenswert?

Terfagter schrieb:

> ich möchte einen String an die µC senden und dieser soll dann wieder
> zurück gesendet werden. Das funktioniert soweit auch, wenn ich ein delay
> von 1sec einbaue. Nehme ich das delay weg, sendet die UART den String
> mehrmals (>15) und vorher und zwischendrin mehrmals ein CR.
> Das zweite Problem ist, das manchmal einige Zeichen in einem String
> vertaucht werden. Sende ich z.B. 123456789 kommt manchmal auch 132456798
> z.B. an.

Das Problem ist, dass deine char_vorh() korrekter Weise meldet, dass 
tatsächlich Character vorhanden sind. Das bedeutet aber nicht, dass eine 
komplette Zeile vorliegt.

Deine uart_gets kümmert sich aber nicht weiter darum ob überhaupt 
Zeichen vorhanden sind, sondern liest mittels NextChar Zeichen um 
Zeichen aus, selbst wenn der Empfangsbuffer in der Zwischenzeit 
kurzzeitig mal leer geworden ist. Du musst in uart_gets schon auch noch 
berücksichtigen, ob überhaupt 1 Zeichen angekommen ist und getch0 daher 
etwas sinnvolles zurückliefern wird.

Durch den delay gibst du der UART die Chance erst einmal genügend 
Zeichen im Buffer zu sammeln, so dass sich eine schon eine komplette 
Zeile im Buffer angesammelt hat, die dann von uart_gets nur noch 
umkopiert zu werden braucht.

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Karl heinz Buchegger schrieb:
> Das Problem ist, dass deine char_vorh() korrekter Weise meldet, dass
> tatsächlich Character vorhanden sind
1
int char_vorh(void)
2
{
3
  if( CharsInBuffer == 0 )
4
  return 0;
5
  return 1;
6
}
Wozu braucht man eigentlich eine dermaßen sinnlose Funktion?

Ok, um CharsInBuffer quasi lokal zu halten.
Die Funktion könntest du aber so abkürzen:
1
int char_vorh(void)
2
{
3
  return CharsInBuffer;
4
}
Wobei dann die Abfrage so aussehen müsste:
1
    if ( char_vorh() ) ...


Und ganz ohne diese unnötige Variable CharsInBuffer ginge das so:
1
int char_vorh(void)
2
{
3
  return NextWriteBufferPos != NextReadBufferPos ;
4
}
Wenn die Zeiger ungleich sind ist mindestens ein Zeichen da, und es wird 
eine 1 zurückgegeben...


> #define BUFFER_LEN 20
Wenn du deine Bufferlänge ein wenig besser an die binäre Rechenweise des 
Zielsystems anpassen würdest, könnte die Modulo-Funktion signifikant 
vereinfacht werden...
Modulo 16 ist z.B. ein Ver-Unden mit 0x0F.
Oder Pufferlänge 32 --> 0x1F
Da ist keine aufwendige Division mehr nötig.


EDIT:
Du schreibst:

> uart.h:
1
//****************************************************************
2
//  Dateiname    :   uart.h
3
4
...
5
6
void initusart0(void)
7
{
8
...
9
}
10
11
void putch0( char x )
12
{
13
...
14
}
15
16
...
Hast du allen Ernstes die ganzen Funktionen in einer Header-Datei?

von Terfagter (Gast)


Lesenswert?

@ Lothar Miller:
Ich habe die Bufferlänge auf 32 gestellt und die Funktion auch, wie du 
gesagt hast, umgestellt.

@ Karl heinz Buchegger:
Ich verstehe nicht ganz genau was du mit deinem zweiten Absatz meinst, 
weil ich doch mit chat_vorh() überprüfe, ob Zeichen vorhanden sind und 
danach die uart_gets ausführe?!? Oder sehe ich das falsch?
Ich würde es gerne umändern, aber weiß nocht nicht genau wie.

von Karl H. (kbuchegg)


Lesenswert?

Terfagter schrieb:
> @ Lothar Miller:
> Ich habe die Bufferlänge auf 32 gestellt und die Funktion auch, wie du
> gesagt hast, umgestellt.
>
> @ Karl heinz Buchegger:
> Ich verstehe nicht ganz genau was du mit deinem zweiten Absatz meinst,
> weil ich doch mit chat_vorh() überprüfe, ob Zeichen vorhanden sind und
> danach die uart_gets ausführe?!? Oder sehe ich das falsch?

Angenommen du schickst die Zeile "Hallo World\n"


Sobald das H angekommen ist, geht deine Hauptschleife in uart_gets() 
hinein (weil ja ein Zeichen da ist, nämlich das H, welches von der 
Testfunktion auch korrekt gemeldet wird). Was macht uart_gets()?
1
void uart_gets( char* Buffer, uint8_t MaxLen )
2
{
3
  uint8_t NextChar;
4
  uint8_t StringLen = 0;
5
6
  NextChar = getch0();
7
  while( NextChar != '\n' && StringLen < MaxLen - 1 )
8
  {
9
    *Buffer++ = NextChar;
10
    StringLen++;
11
    NextChar = getch0();
12
  }
13
  *Buffer = '\0';
14
}

es holt sich das H und speichert es in den Buffer. Und dann. Nix dann. 
Die Schleife läuft solange, bis endlich der \n ankommt. Nur: Der ist 
noch gar nicht angekommen! Es ist ja noch nicht einmal das 'a' 
angekommen.
Das interessiert uart_gets aber nicht die Bohne! Die versucht weiter 
mittels getch0() ein Zeichen nach dem anderen aus dem Buffer zu lesen, 
selbst wenn gar kein Zeichen mehr im Empfangsbuffer vorhanden ist, weil 
es noch gar nicht empfangen wurde. Nachdem sich getch0 dagegen nicht 
wehren kann, sollte uart_gets() tunlichst beim Buffer nachfragen, ob 
mindestens 1 Zeichen empfangen wurde, ehe es getch0() aufruft.

von Terfagter (Gast)


Lesenswert?

Ich habe die uart_gets mal so angepasst, wie ich es denke:

void uart_gets( char* Buffer, uint8_t MaxLen )
{
  uint8_t NextChar;
  uint8_t StringLen = 0;

  NextChar = getch0();
  while( NextChar != '\n' && StringLen < MaxLen - 1 )
  {
    *Buffer++ = NextChar;
    StringLen++;
    if(char_vorh0() == 1)
      NextChar = getch0();
    else
    return;
  }
  *Buffer = '\0';
}

Jetzt wird nicht mehr zu viel übertragen, sondern es kommt fast immer 
das richtige an. Aber halt nur fast.

Z.B. senke ich ooooooooooooooo und zurück kommt ein o.
Oder es sind manchmal character vertauscht.

von Karl H. (kbuchegg)


Lesenswert?

Terfagter schrieb:
> Ich habe die uart_gets mal so angepasst, wie ich es denke:
>
> void uart_gets( char* Buffer, uint8_t MaxLen )
> {
>   uint8_t NextChar;
>   uint8_t StringLen = 0;
>
>   NextChar = getch0();
>   while( NextChar != '\n' && StringLen < MaxLen - 1 )
>   {
>     *Buffer++ = NextChar;
>     StringLen++;
>     if(char_vorh0() == 1)
>       NextChar = getch0();
>     else
>     return;

wieso return?

uart_gets soll eine Zeile einlesen und das hat sie nicht getan!
Wenn kein Zeichen da ist, dann kann uart_gets nichts machen und muss 
ganz einfach weiter warten!

Ausserdem: Wer sagt denn, dass in der Zwischenzeit nur 1 Zeichen 
angekommen ist. Vielleicht sind beim Aufruf von uart_gets schon 5 
Zeichen der Zeile da, zb weil die Hauptschleife ein wenig länger 
gebraucht hat ehe sie dann uart_gets aufrufen konnte.

von Karl H. (kbuchegg)


Lesenswert?

1
void uart_gets( char* Buffer, uint8_t MaxLen )
2
{
3
  uint8_t NextChar = '\0';
4
  uint8_t StringLen = 0;
5
6
  do {
7
    if( char_vorh0() > 0 )   // es könnten ja auch mehrere Zeichen im Buffer warten
8
    {
9
      NextChar = getch0();      // Hole 1 Zeichen aus dem Buffer
10
                                // und im String ablegen, wenn
11
                                // es sich nicht um das Zeilende handelt UND
12
                                // noch Platz im String ist
13
      if( NextChar != '\n' && StringLen < MaxLen - 1 )
14
        *Buffer++ = NextChar;
15
        StringLen++;
16
      }
17
    }
18
  } while( NextChar != '\n' );
19
20
  *Buffer = '\0';
21
}

von Terfagter (Gast)


Lesenswert?

Ok jetzt hab ich verstanden, wie du das meintest.
Und das funktioniert jetzt auch soweit. Nur die Character sind manchmal 
vertauscht. Und das Beispiel mit dem o, da ist der Fehler auch noch 
vorhanden?!?

von Karl H. (kbuchegg)


Lesenswert?

Terfagter schrieb:
> Ok jetzt hab ich verstanden, wie du das meintest.
> Und das funktioniert jetzt auch soweit.

Dann würde ich mir mal Gedanken darüber machen, wo die Dinge 
durcheinander kommen, wenn ein Empfangsinterrupt an den ungünstigst 
möglichen Stellen durchkommt.

zb. wird da ein ziemliches Chaos herauskommen, wenn die UART 
ausgerechnet dann einen Interrupt auslöst, wenn die getch0 Funktion bei

  CharsInBuffer--;

sich das CharsInBuffer schon geholt hat, drauf und drann ist ihn zu 
decrementieren (jetzt kommt der Interrupt und zählt seinerseits 
CharsInBuffer hoch) und den verringerten Wert nach dem Interrupt wieder 
zurückschreibt. Die Erhöhung durch die ISR ist damit verloren gegangen.

von Terfagter (Gast)


Lesenswert?

Ich habe jetzt einige Zeit überlegt, aber mir fällt nichts ein, wie ich 
dieses Problem gelöst kriege...

In diesem Zusammenhang gibt es aber noch ein Problem.
Ich habe einen GPS-Sender am µC der folgendes sekündlich sendet:
$GPRMC,043836.026,V,8960.0000,N,00000.0000,E,0.00,0.00,060180,,,N*70
$GPGGA,043837.026,8960.0000,N,00000.0000,E,0,0,,137.0,M,13.0,M,,*4C
$GPGSA,A,1,,,,,,,,,,,,,,,*1E
$GPGSV,1,1,00*79

Ich benötige aber nur ein paar Daten von $GPGGA.
Nachdem ich viel gesucht habe bin ich auf folgende Lösung gekommen:

while(1)
{
  uart_gets1(buffer, 128); // Empfang der GPS-Daten. USART1

  char *grad;
  char *ref;

  grad = strstr(buffer, "$GPGGA"); //Ab $GPGGA bis cr ausgeben
  strtok( ref, "," );
  strtok( NULL, "," );
  grad = strtok( NULL, "," ); //Breitengrad auslesen
  putstr0(grad); //Breitengrad über USART0 an PC senden
        putch0(0x0D); //CR ausgeben
}

Das funktioniert auch soweit, bis auf die Tatsache, dass immer drei 
Zeichen zu viel ausgegeben werden:

00000.0000
M
G
G

Ich denke, dass das mit meiner noch nicht einwandfrei funktionierenden 
USART-Funktionen zusammenhängt, da das CR ja auch 3 mal mit ausgegeben 
wird.
Danke für eure Hilfe.

von Karl H. (kbuchegg)


Lesenswert?

Terfagter schrieb:
> Ich habe jetzt einige Zeit überlegt, aber mir fällt nichts ein, wie ich
> dieses Problem gelöst kriege...

Nicht?
1
char getch0(void)
2
{
3
  char NextChar = Buffer0[NextReadBufferPos];
4
  NextReadBufferPos = ( NextReadBufferPos + 1 ) % BUFFER_LEN;
5
6
  cli();
7
  CharsInBuffer--;
8
  sei();
9
10
  return NextChar;
11
}

generell musst du alle Operationen, bei denen Variablen betroffen sind, 
die sowohl in einer INterrupt Funktion als auch in einer normalen 
Funktion benutzt werden, dagegen absichern, dass sich die Variable 
während der Operation mit dieser Variablen nicht verändern kann. 
Interrupt kurzzeitig abdrehen und danach wieder zulassen.

von Karl H. (kbuchegg)


Lesenswert?

Terfagter schrieb:

>
>   grad = strstr(buffer, "$GPGGA"); //Ab $GPGGA bis cr ausgeben

du meinst wohl ref und nicht grad.

Code nicht abtippen sondern per Copy&Paste ins Forum kopieren!


> Ich denke, dass das mit meiner noch nicht einwandfrei funktionierenden
> USART-Funktionen zusammenhängt,

Nö.
Das liegt daran, dass du nicht überprüfst, ob du es mit einem GPGGA 
Datensatz zu tun hast, sondern auch bei den anderen ganz einfach das was 
nach dem 2ten Komma steht rausholst.

von Terfagter (Gast)


Lesenswert?

Mit den Interrupts kurzzeitig abschalten hätte ich auch selber drauf 
kommen können... Danke

Das andere verstehe ich nicht. Wenn ich grad = strstr(buffer, "$GPGGA"); 
ausführe erhalte ich folgenden String:
,043837.026,8960.0000,N,00000.0000,E,0,0,,137.0,M,13.0,M,,*4C
Und dann suche ich in diesem String doch die Kommata ab.
So hab ich es verstanden?!? Wo kommen dann die drei anderen Character 
her?

von Terfagter (Gast)


Lesenswert?

Habs gerade nochmal ausprobiert. Wenn ich nur das in einer 
while-Schleife ausführe:

char *grad;

grad = strstr(buffer, "$GPGGA");
putstr0(grad);
putch0(0x0D);

erhalte ich auch schon das Ergebnis mit jeweils drei mal "G" in dieser 
Formatierung:
$GPGGA,080424.026,8960.0000,N,00000.0000,E,0,0,,137.0,M,13.0,M,,*4D

G
G
G

von Karl H. (kbuchegg)


Lesenswert?

Terfagter schrieb:

> char *grad;
>
> grad = strstr(buffer, "$GPGGA");
> putstr0(grad);
> putch0(0x0D);
>
> erhalte ich auch schon das Ergebnis mit jeweils drei mal "G" in dieser
> Formatierung:
> $GPGGA,080424.026,8960.0000,N,00000.0000,E,0,0,,137.0,M,13.0,M,,*4D
>
> G
> G
> G


Prüf wenigstens das Ergebnis vom strstr ab.

In
$GPGSA,A,1,,,,,,,,,,,,,,,*1E

kommt nun mal kein $GPGGA vor!

Ist das so schwer zu begreifen, dass der Fall "Suchstring kommt im zu 
Durchsuchenden gar nicht vor" abzutesten ist?

Dein GPS schickt dir nicht nur GPGGA Datensätze! Du kannst doch nicht 
die anderen Datensätze mit dem Muster auswerten, die du auf GPGGA 
anwenden willst und erwarten, dass da etwas sinnvolles rauskommt.

von Karl H. (kbuchegg)


Lesenswert?

Terfagter schrieb:
> Das andere verstehe ich nicht. Wenn ich grad = strstr(buffer, "$GPGGA");
> ausführe erhalte ich folgenden String:
> ,043837.026,8960.0000,N,00000.0000,E,0,0,,137.0,M,13.0,M,,*4C


Schon. Aber wie gehts weiter
1
  strtok( ref, "," );
2
  strtok( NULL, "," );

kurze Zwischenfrage:
Auf welchen String zeigt den ref?

> Und dann suche ich in diesem String doch die Kommata ab.

Das wäre die Idee.
Programmier hast du aber etwas anderes.

von Karl H. (kbuchegg)


Lesenswert?

Terfagter schrieb:

> Das andere verstehe ich nicht. Wenn ich grad = strstr(buffer, "$GPGGA");
> ausführe erhalte ich folgenden String:
> ,043837.026,8960.0000,N,00000.0000,E,0,0,,137.0,M,13.0,M,,*4C

Wie kommst du eigentlich drauf, dass du mit strstr diesen Teilstring 
kriegst?

strstr durchsucht einen String, ob ein anderer String in ihm enthalten 
ist.
 Wenn ja, dann bekommt man einen Pointer zurück, der auf den Anfang des 
gesuchten Teilstrings im zu durchsuchenden String zeigt. Ist der 
Suchstring nicht enthalten, dann liefert strstr einen NULL Pointer 
zurück.

Aber deswegen wird der Originalstring ja nicht verändert.

von Terfagter (Gast)


Lesenswert?

Karl heinz Buchegger schrieb:
> Schon. Aber wie gehts weiter
>   strtok( ref, "," );
>   strtok( NULL, "," );
>
> kurze Zwischenfrage:
> Auf welchen String zeigt den ref?
>
>> Und dann suche ich in diesem String doch die Kommata ab.
>
> Das wäre die Idee.
> Programmier hast du aber etwas anderes.

Bei ref hatte ich mich ja oben verschrieben.

1  grad = strstr(buffer, "$GPGGA");
2  strtok( grad, "," );
3  ref = strtok( NULL, "," );

Ich habe das so verstanden, dass grad in der zweiten Zeile auf die 
Adresse zeigt, an der sich "$GPGGA" befindet. Dann sucht strtok nach dem 
ersten Komma und ersetzt es durch einen NULL-Pointer. In der dritten 
Zeile wird noch dem zweiten Komma gesucht, ab dem NULL-Pointer und ein 
neuer NULL-Pointer gesetzt. Zurückgegeben wird dann ein Pointer auf die 
Startadresse zwischen den beiden Kommata. Ist das so richtig?

von Terfagter (Gast)


Lesenswert?

Karl heinz Buchegger schrieb:
> Prüf wenigstens das Ergebnis vom strstr ab.
>
> In
> $GPGSA,A,1,,,,,,,,,,,,,,,*1E
>
> kommt nun mal kein $GPGGA vor!
>
> Ist das so schwer zu begreifen, dass der Fall "Suchstring kommt im zu
> Durchsuchenden gar nicht vor" abzutesten ist?
>
> Dein GPS schickt dir nicht nur GPGGA Datensätze! Du kannst doch nicht
> die anderen Datensätze mit dem Muster auswerten, die du auf GPGGA
> anwenden willst und erwarten, dass da etwas sinnvolles rauskommt.

Dann hatte ich das wohl nicht ganz richtig verstanden.
Ich bin davon ausgegangen, dass
grad = strstr(buffer, "$GPGGA");
den ganzen String untersucht nach "$GPGGA" und dann einen String 
zurückgibt von $GPGGA bis zum nächsten CR oder LF. Aber er gibt ja einen 
Pointer auf die Adresse von $GPGGA zurück, wobei ich dann noch nicht 
verstehe, wie ich die anderen Datensätze ausschließen kann?!?

von Terfagter (Gast)


Lesenswert?

Karl heinz Buchegger schrieb:
> Terfagter schrieb:
>> Ich habe jetzt einige Zeit überlegt, aber mir fällt nichts ein, wie ich
>> dieses Problem gelöst kriege...
>
> Nicht?
> char getch0(void)
> {
>   char NextChar = Buffer0[NextReadBufferPos];
>   NextReadBufferPos = ( NextReadBufferPos + 1 ) % BUFFER_LEN;
>
>   cli();
>   CharsInBuffer--;
>   sei();
>
>   return NextChar;
> }
>
> generell musst du alle Operationen, bei denen Variablen betroffen sind,
> die sowohl in einer INterrupt Funktion als auch in einer normalen
> Funktion benutzt werden, dagegen absichern, dass sich die Variable
> während der Operation mit dieser Variablen nicht verändern kann.
> Interrupt kurzzeitig abdrehen und danach wieder zulassen.

Das habe ich ausprobiert, aber leider bleibt der Fehler:
Sende ich awrognaeogn kommt awrognoeang an.
Oder ich sende oooooooooooo und es kommt o an.

von Helmut L. (helmi1)


Lesenswert?

Terfagter schrieb:
> wobei ich dann noch nicht
> verstehe, wie ich die anderen Datensätze ausschließen kann?!?

grad = strstr(buffer, "$GPGGA");

if(grad != NULL)
{
    Hier weiter suchen ..
}

Nur wenn grad != NULL ist dann hast du eine GPGGA Zeile in der du weiter 
suchen kannst und sonst nicht.

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.