www.mikrocontroller.net

Forum: Mikrocontroller und Digitale Elektronik Daten zwischen zwei USARTS durchschleusen


Autor: Dennis Vonnebrink (schwebo)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo liebe Leut,

ich scheitere beim Versuch, NMEA-Datensätze von einem GPS-Modul über 
einen ATmega auf einen PC zu schicken. Das Modul hängt an USART1, der PC 
an USART0.
Die Sache treibt mich noch in den Wahnsinn, weil ich schon seit Tagen 
dranhänge und nicht weiterkomme.

Den Code hänge ich unten dran.
Ich warte auf das Start-Symbol '$', lese bis zum Endsymbol <LF> und 
schreibe dann das ganze gelesene NMEA-Ding auf den USART0. Dabei kommen 
solche Sachen raus:
$GPGGA,204947.000,5012.5204,N,01325.0072,E,1,03,4.8,96.0,M,44.6,M,,0000* 
63
$G,13,,,,,,,,,,4.9,4.8,1.0*3D
$G,204947.000,A,5012.5204,N,01325.0072,E,0.95,302.95,161209,,*06
$GPGGA,204948.000,5012.5206,N,01325.0068,E,1,00,50.0,96.0,M,44.6,M,,0000 
*5F

Es klappt also fast. Aber irgendeine Schweinerei ist da noch am 
Laufen.
Besonders fies: wenn ich ich anstelle des <LF> schon beim '*' aufhöre, 
klappt alles wunderbar!!!

Über erlösende Antworten würde ich mich sehr freuen.

Viele Grüße
Schwebo
--
#define F_CPU 16000000UL

#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define TRUE 1
#define FALSE 0


#define BAUD_RATE 19200
#define USART_RATE (F_CPU/16/BAUD_RATE - 1)

#define GPS_BAUD_RATE 4800
#define GPS_USART_RATE (F_CPU/16/GPS_BAUD_RATE - 1)

#define GPS_RX_BUFFER_LENGTH 100

static void message_init();
static void gps_init();
static void write(char* s);
static void gps_enable_serial_rx(void);
static void gps_disable_serial_rx(void);

volatile uint8_t rx_buffer[GPS_RX_BUFFER_LENGTH];
volatile uint8_t gps_rx_complete;
volatile uint8_t gps_rx_within_message;

int main(void) {
    message_init();
    gps_init();

    sei();

    while (TRUE) {
        // wait for complete NMEA record
        if (gps_rx_complete) {
            write(rx_buffer);

            // prepare for next read
            gps_rx_complete = FALSE;
            gps_enable_serial_rx();
        }
    }

    return 0;
}

/**
 * Initializes the client side USART0
 */
static void message_init() {

    // Baud rate
    UBRR0 = USART_RATE;

    // mode 8N1
    UCSR0C = (1 << UCSZ01) | (1 << UCSZ00);

    // enable TX
    UCSR0B = (1 << TXEN0);
}

/**
 * Writes a string to serial.
 */
static void write(char* s) {

    // wait for previous TX to complete
    while (TRUE) {
        while (!(UCSR0A & (1 << UDRE0)))
            ;

        if (*s == '\0') {
            return;
        }

        UDR0 = *s;
        s++;
    }
}

/**
 * Initializes the GPS side USART1
 */
static void gps_init(void) {

    // Baud rate
    UBRR1 = GPS_USART_RATE;

    // mode 8N1
    UCSR1C = (1 << UCSZ11) | (1 << UCSZ10);

    // enable TX and RX
    UCSR1B = (1 << RXEN1) | (1 << TXEN1) | (1 << RXCIE1);

    gps_rx_complete = FALSE;
}


/**
 * GPS RX handler
 */
ISR(USART1_RX_vect) {
    static uint8_t i = 0;

    uint8_t temp = SREG;

    // read byte
    uint8_t b = UDR1;

    // check for start delimiter
    if (b == '$') {
        gps_rx_within_message = TRUE; // start reading the message
        i = 0;
    }
    
    if (gps_rx_within_message && i < GPS_RX_BUFFER_LENGTH - 1) {

        rx_buffer[i++] = b;

        // check for end delimiter
        if (b == '\n') { // '*' funktioniert prima
            gps_disable_serial_rx();
            gps_rx_within_message = FALSE;
            gps_rx_complete = TRUE;
            rx_buffer[i] = '\0';
        }
    }

    SREG = temp;

}

static void gps_enable_serial_rx(void) {
    UCSR1B |= (1 << RXCIE1);
}

static void gps_disable_serial_rx(void) {
    UCSR1B &= ~(1 << RXCIE1);
}



Autor: spess53 (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hi

>Es klappt also fast. Aber irgendeine Schweinerei ist da noch am
>Laufen.
>Besonders fies: wenn ich ich anstelle des <LF> schon beim '*' aufhöre,
>klappt alles wunderbar!!!

Taktquelle des AVR?

MfG Spess

Autor: Dennis Vonnebrink (schwebo)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Das ist ein Arduino-Board mit 16MHz-Quarz.

Autor: Malte (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo,


sendet er denn überhaupt ein '\n' oder sendet er vielleicht ein '\r'.
Weil du ja sagst wenn du auf '*' prüfst funktioniert es.

Sendet er überhaupt etwas wenn du auf '\n' wartest ? Also wird die if 
Abfrage ausgeführt ?

Gruß

Autor: spess53 (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hi

>if (*s == '\0')

Woher kommt das '\0' ?

MfG Spess

Autor: spess53 (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hi

Habe es gerade gesehen.

MfG Spess

Autor: Dennis Vonnebrink (schwebo)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
@Malte:
Ein Beispiel von dem, was ich empfange, steht im 1. Beitrag. Das Ende 
scheint er gut zu bekommen und das '$' auch.

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Planst du eigentlich im AVR noch irgendeine Auswertung der NMEA Daten?

Hintergrund:
Aus welchem Grund willst du Datensätze als solche empfangen, wenn du 
sowieso keine Auswertung damit am AVR machst.

Ein Zeichen kommt vom GPS Device und wird als solches ohne Ansehen des 
Inhalts einfach an die andere UART durchgeschleust. -> Der PC empfängt 
alles, was auch der AVR empfangen hat. Der AVR muss sich nicht mit einer 
Auswertung rumärgern.

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Du sendest und empfängst mit jeweils ein und demselben Buffer.
Schon mal dran gedacht was eigentlich passiert, wenn das GPS Gerät zu 
senden anfängt während du noch zum PC überträgst?

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Und lass hier
ISR(USART1_RX_vect) {
    static uint8_t i = 0;

    uint8_t temp = SREG;


das SREG in Ruhe!
Dieses Register geht dich nichts an! Das hat der Compiler unter seiner 
Kontrolle. Pfusche ihm da nicht rein.

Autor: Dennis Vonnebrink (schwebo)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ich möchte später das Signal direkt auf dem AVR auswerten, trotzdem 
möchte ich jetzt erstmal die Daten an den PC weiterleiten.

Wg. dem Buffer: natürlich habe ich das berücksichtigt. Sobald ich ein 
'\n' empfange, schalte ich den RX-Interrupt aus, so dass die ISR nicht 
mehr durchlaufen wird. In der Main-Loop schalte ich ihn wieder ein, 
sobald ich mit dem Weiterleiten an den PC fertig bin.

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Dennis Vonnebrink schrieb:

> Wg. dem Buffer: natürlich habe ich das berücksichtigt.

Entschuldigung. Mein Fehler.
Hab ich überlesen.

Hmm. Auf der anderen Seite.
Wenn du den Empfang einfach nicht mehr auswertest, verlierst du Zeichen. 
Genau das zeigen auch deine Mitschriften.
2 Zeichen kommen noch, die sind von der UART gepuffert worden. Und dann 
fehlen Daten. Höchst wahrscheinlich genau soviele, wie das GPS Gerät mit 
4800 Baud in der Zeit senden konnte, die deine Nachricht mit 19200 Baud 
zum PC brauchte.

Fazit: Du darfst dem GPS Gerät nicht die Leitung zwischenzeitlich 
kappen. Wenn eine Nachricht da ist, den Buffer in einen 2-ten 
umkopieren, aus dem heraus du senden kannst und den Empfang während des 
Sendens in den jetzt wieder leeren Empfangsbuffer weiterlaufen lassen.
Anstelle von Umkopieren kann man auch mit 2 Buffern arbeiten die 
abwechselnd jeweils Empfangs bzw. Sendebuffer sind.

Autor: Dennis Vonnebrink (schwebo)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Genau, aber bevor ich damit anfange, in den Buffer zu schreiben, warte 
ich ja wieder auf das '$'. Es kann natürlich gut sein, dass ich so die 
eine oder andere Message vom GPS-Modul verpasse.

Autor: Rufus Τ. Firefly (rufus) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
> Ich möchte später das Signal direkt auf dem AVR auswerten, trotzdem
> möchte ich jetzt erstmal die Daten an den PC weiterleiten.


Das kannst Du aber einfacher so machen: Jedes empfangene Zeichen wird 
direkt auf der zweiten UART wieder ausgegeben (geht nur bei gleicher 
oder höherer Baudrate als der zum GPS-Empfänger), und zusätzlich für 
die AVR-interne Auswertung in einen Puffer geschrieben.

Noch simpler ist der Verzicht auf die zweite UART und der direkte 
Anschluss des PCs (nur dessen RX-Leitung) parallel zur RX-Leitung des 
AVRs (natürlich mit V24-Pegelwandler).

D.h. der AVR lauscht dem, was der GPS-Empfänger dem PC erzählt.

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Dennis Vonnebrink schrieb:
> Genau, aber bevor ich damit anfange, in den Buffer zu schreiben, warte
> ich ja wieder auf das '$'. Es kann natürlich gut sein, dass ich so die
> eine oder andere Message vom GPS-Modul verpasse.

Die UART selbst buffert 2 Zeichen.
Wenn du den Interrupt abstellst, dann wird das nächste empfangene 
Zeichen in der UART gespeichert. Und das wird nun mal meistens der $ für 
die nächste Message sein. Dieses Zwischenspeichern in der UART findet 
auf jeden Fall statt, egal ob du den Interrupt aktiviert hast oder 
nicht.

Wenn es dir egal ist, ob eine Message verloren geht oder nicht, steht es 
dir natürlich frei, die UART durch Lesen von UDR auszuleeren, ehe du den 
Interrupt wieder freigibst. Dann wartest du auf jeden Fall tatsächlich 
auf den nächsten $, dessen Datensatz du komplett empfangen kannst.

Autor: Dennis Vonnebrink (schwebo)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
@Rufus
Ja OK, evtl. sollte ich mal über Alternativen nachdenken. Trotzdem wurmt 
es mich, dass ich den Fehler im momentanen Code nicht finde.

Autor: Dennis Vonnebrink (schwebo)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
@Karl Heinz:
Das ist es! Super! Das erste G kommt ja auch immer an, das unterstreicht 
Deine Theorie.

Ich kann aber nicht einfach
            b = UDR1;
            b = UDR1;

einbauen, oder?

Autor: Dennis Vonnebrink (schwebo)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Jippi, es läuft!
Vielen Dank für die Tipps - da wäre ich echt nicht drauf gekommen.
--

Hier der fertige ISR-Code:
ISR(USART1_RX_vect) {
    static uint8_t i = 0;

    // read byte
    uint8_t b = UDR1;

    // check for start delimiter
    if (b == '$') {
        gps_rx_within_message = TRUE; // start reading the message
        i = 0;
    }
    
    if (gps_rx_within_message && i < GPS_RX_BUFFER_LENGTH - 1) {

        rx_buffer[i++] = b;

        // check for end delimiter
        if (b == '\n') {

            gps_disable_serial_rx();

            // flush cached bytes
            for (uint8_t j = 0; j < 2; j++) {
                while ( !(UCSR1A & (1<<RXC1)) )
                    ;
                b = UDR1;
            }

            gps_rx_within_message = FALSE;
            gps_rx_complete = TRUE;
            rx_buffer[i] = '\0';
 
        }
    }
}

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Dennis Vonnebrink schrieb:

>             gps_disable_serial_rx();
>
>             // flush cached bytes
>             for (uint8_t j = 0; j < 2; j++) {
>                 while ( !(UCSR1A & (1<<RXC1)) )
>                     ;
>                 b = UDR1;
>             }
>
>             gps_rx_within_message = FALSE;
>             gps_rx_complete = TRUE;
>             rx_buffer[i] = '\0';

Das ist nicht so schlau.
Jetzt wartest du in der ISR darauf, dass das nächste Zeichen eintrifft 
(wenn noch keines gekommen ist). Brauchst du doch gar nicht.
Lies einfach UDR1 zweimal aus. Dir ist doch sowieso egal, ob und wenn ja 
was da in der Zwischenzeit eingetroffen ist.
            gps_disable_serial_rx();
 
            // flush cached bytes
            while( UCSR1A & (1<<RXC1) )   // solange Zeichen vorhanden sind
              b = UDR1;                   // lies sie und mach nichts damit

            gps_rx_within_message = FALSE;
            gps_rx_complete = TRUE;
            rx_buffer[i] = '\0';

ausserdem bist du hier an der falschen Stelle. Du willst die UART 
flushen, nachdem du an den PC geschickt hast und bevor du den Interrupt 
wieder aktivierst.
    while (TRUE) {
        // wait for complete NMEA record
        if (gps_rx_complete) {
            write(rx_buffer);

            // prepare for next read
            gps_rx_complete = FALSE;

            // flush cached bytes
            while( UCSR1A & (1<<RXC1) )   // solange Zeichen vorhanden sind
              b = UDR1;                   // lies sie und mach nichts damit

            gps_enable_serial_rx();
        }
    }

Autor: Dennis (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Deinen ersten Einwand verstehe ich - das funktioniert so nur, wenn 
ständig Daten nachkommen. Allerdings hatte ich das, glaube ich, mal so 
probiert wie Du das schreibst, aber da hatte ich immer noch denselben 
Fehler-Effekt.
Ich bin gerade bei der Arbeit, kann das also erst heute abend 
ausprobieren.

Den zweiten Punkt verstehe ich aber nicht. Ich deaktiviere doch zuerst 
den Empfang mit gps_disable_serial_rx(); und stelle so sicher, dass ich 
noch maximal 2 gepufferte Zeichen flushen muss. Wenn ich in der 
Main-Loop den Empfang wieder einschalte, sollten da nur frische, 
zusammen gehörende Zeichen kommen, oder? Die Variante, die Du 
vorschlägst, würde m.E. genauso gut funktionieren, aber keinen Vorteil 
bieten.

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Dennis schrieb:

> Den zweiten Punkt verstehe ich aber nicht. Ich deaktiviere doch zuerst
> den Empfang mit gps_disable_serial_rx(); und stelle so sicher, dass ich
> noch maximal 2 gepufferte Zeichen flushen muss. Wenn ich in der
> Main-Loop den Empfang wieder einschalte, sollten da nur frische,
> zusammen gehörende Zeichen kommen, oder? Die Variante, die Du
> vorschlägst, würde m.E. genauso gut funktionieren, aber keinen Vorteil
> bieten.

Was ist wenn das GPS-Device nicht sofort mit dem nächsten Datensatz 
anfängt sondern, sagen wir mal erst dann mit dem nächsten loslegt, wenn 
du bereits die Hälfte des ersten Datensatzes rausgebuttert hast?

Gut. Bei dir kann das momentan nicht passieren, weil du ja in der ISR 
auf die nächsten 2 Zeichen wartest. Aber das ist ja eigentlich sowieso 
nicht richtig. Du willst ja nicht auf genau 2 Zeichen warten. Du willst 
Zeichen die möglicherweise eingetroffen sind, verwerfen.
Und wenn du das tust kann es natürlich sein, dass das Gerät noch gar 
nicht mit dem Senden angefangen hat, wenn du den Buffer flusht.
Aber es kann natürlich mit dem Senden angefangen haben während du auf 
den PC rausbutterst.
Damit hast du den Fall, dass du zwar die UART geflusht hast, dir das 
aber nichts bringt, weil das GPS-Gerät noch gar nicht mit dem Senden 
angfangen hat. Trotzdem bist du dann in der Situation, dass bereits 
Zeichen im UART gelandet sind, wenn du den Interrupt dann wieder 
freigibst.

Autor: Dennis (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Da hast Du natürlich recht, das könnte passieren.
Wie wäre es, wenn ich in den Funktionen gps_enable_serial_rx() bzw. 
gps_disable_serial_rx() anstelle des RXCIE1 das RXEN1 setze bzw. lösche? 
Beim Löschen wird doch der Buffer geleert und kann auch nicht mehr 
befüllt werden, bis das RXEN-Bit wieder gesetzt wird, oder?

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.