Forum: Mikrocontroller und Digitale Elektronik Daten zwischen zwei USARTS durchschleusen


von Dennis D. (schwebo)


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
--
1
#define F_CPU 16000000UL
2
3
#include <avr/io.h>
4
#include <avr/interrupt.h>
5
#include <util/delay.h>
6
#include <stdint.h>
7
#include <stdio.h>
8
#include <string.h>
9
#include <stdlib.h>
10
11
#define TRUE 1
12
#define FALSE 0
13
14
15
#define BAUD_RATE 19200
16
#define USART_RATE (F_CPU/16/BAUD_RATE - 1)
17
18
#define GPS_BAUD_RATE 4800
19
#define GPS_USART_RATE (F_CPU/16/GPS_BAUD_RATE - 1)
20
21
#define GPS_RX_BUFFER_LENGTH 100
22
23
static void message_init();
24
static void gps_init();
25
static void write(char* s);
26
static void gps_enable_serial_rx(void);
27
static void gps_disable_serial_rx(void);
28
29
volatile uint8_t rx_buffer[GPS_RX_BUFFER_LENGTH];
30
volatile uint8_t gps_rx_complete;
31
volatile uint8_t gps_rx_within_message;
32
33
int main(void) {
34
    message_init();
35
    gps_init();
36
37
    sei();
38
39
    while (TRUE) {
40
        // wait for complete NMEA record
41
        if (gps_rx_complete) {
42
            write(rx_buffer);
43
44
            // prepare for next read
45
            gps_rx_complete = FALSE;
46
            gps_enable_serial_rx();
47
        }
48
    }
49
50
    return 0;
51
}
52
53
/**
54
 * Initializes the client side USART0
55
 */
56
static void message_init() {
57
58
    // Baud rate
59
    UBRR0 = USART_RATE;
60
61
    // mode 8N1
62
    UCSR0C = (1 << UCSZ01) | (1 << UCSZ00);
63
64
    // enable TX
65
    UCSR0B = (1 << TXEN0);
66
}
67
68
/**
69
 * Writes a string to serial.
70
 */
71
static void write(char* s) {
72
73
    // wait for previous TX to complete
74
    while (TRUE) {
75
        while (!(UCSR0A & (1 << UDRE0)))
76
            ;
77
78
        if (*s == '\0') {
79
            return;
80
        }
81
82
        UDR0 = *s;
83
        s++;
84
    }
85
}
86
87
/**
88
 * Initializes the GPS side USART1
89
 */
90
static void gps_init(void) {
91
92
    // Baud rate
93
    UBRR1 = GPS_USART_RATE;
94
95
    // mode 8N1
96
    UCSR1C = (1 << UCSZ11) | (1 << UCSZ10);
97
98
    // enable TX and RX
99
    UCSR1B = (1 << RXEN1) | (1 << TXEN1) | (1 << RXCIE1);
100
101
    gps_rx_complete = FALSE;
102
}
103
104
105
/**
106
 * GPS RX handler
107
 */
108
ISR(USART1_RX_vect) {
109
    static uint8_t i = 0;
110
111
    uint8_t temp = SREG;
112
113
    // read byte
114
    uint8_t b = UDR1;
115
116
    // check for start delimiter
117
    if (b == '$') {
118
        gps_rx_within_message = TRUE; // start reading the message
119
        i = 0;
120
    }
121
    
122
    if (gps_rx_within_message && i < GPS_RX_BUFFER_LENGTH - 1) {
123
124
        rx_buffer[i++] = b;
125
126
        // check for end delimiter
127
        if (b == '\n') { // '*' funktioniert prima
128
            gps_disable_serial_rx();
129
            gps_rx_within_message = FALSE;
130
            gps_rx_complete = TRUE;
131
            rx_buffer[i] = '\0';
132
        }
133
    }
134
135
    SREG = temp;
136
137
}
138
139
static void gps_enable_serial_rx(void) {
140
    UCSR1B |= (1 << RXCIE1);
141
}
142
143
static void gps_disable_serial_rx(void) {
144
    UCSR1B &= ~(1 << RXCIE1);
145
}

von spess53 (Gast)


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

von Dennis D. (schwebo)


Lesenswert?

Das ist ein Arduino-Board mit 16MHz-Quarz.

von Malte (Gast)


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ß

von spess53 (Gast)


Lesenswert?

Hi

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

Woher kommt das '\0' ?

MfG Spess

von spess53 (Gast)


Lesenswert?

Hi

Habe es gerade gesehen.

MfG Spess

von Dennis D. (schwebo)


Lesenswert?

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

von Karl H. (kbuchegg)


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.

von Karl H. (kbuchegg)


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?

von Karl H. (kbuchegg)


Lesenswert?

Und lass hier
1
ISR(USART1_RX_vect) {
2
    static uint8_t i = 0;
3
4
    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.

von Dennis D. (schwebo)


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.

von Karl H. (kbuchegg)


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.

von Dennis D. (schwebo)


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.

von Rufus Τ. F. (rufus) Benutzerseite


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.

von Karl H. (kbuchegg)


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.

von Dennis D. (schwebo)


Lesenswert?

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

von Dennis D. (schwebo)


Lesenswert?

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

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

einbauen, oder?

von Dennis D. (schwebo)


Lesenswert?

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

Hier der fertige ISR-Code:
1
ISR(USART1_RX_vect) {
2
    static uint8_t i = 0;
3
4
    // read byte
5
    uint8_t b = UDR1;
6
7
    // check for start delimiter
8
    if (b == '$') {
9
        gps_rx_within_message = TRUE; // start reading the message
10
        i = 0;
11
    }
12
    
13
    if (gps_rx_within_message && i < GPS_RX_BUFFER_LENGTH - 1) {
14
15
        rx_buffer[i++] = b;
16
17
        // check for end delimiter
18
        if (b == '\n') {
19
20
            gps_disable_serial_rx();
21
22
            // flush cached bytes
23
            for (uint8_t j = 0; j < 2; j++) {
24
                while ( !(UCSR1A & (1<<RXC1)) )
25
                    ;
26
                b = UDR1;
27
            }
28
29
            gps_rx_within_message = FALSE;
30
            gps_rx_complete = TRUE;
31
            rx_buffer[i] = '\0';
32
 
33
        }
34
    }
35
}

von Karl H. (kbuchegg)


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.
1
            gps_disable_serial_rx();
2
 
3
            // flush cached bytes
4
            while( UCSR1A & (1<<RXC1) )   // solange Zeichen vorhanden sind
5
              b = UDR1;                   // lies sie und mach nichts damit
6
7
            gps_rx_within_message = FALSE;
8
            gps_rx_complete = TRUE;
9
            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.
1
    while (TRUE) {
2
        // wait for complete NMEA record
3
        if (gps_rx_complete) {
4
            write(rx_buffer);
5
6
            // prepare for next read
7
            gps_rx_complete = FALSE;
8
9
            // flush cached bytes
10
            while( UCSR1A & (1<<RXC1) )   // solange Zeichen vorhanden sind
11
              b = UDR1;                   // lies sie und mach nichts damit
12
13
            gps_enable_serial_rx();
14
        }
15
    }

von Dennis (Gast)


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.

von Karl H. (kbuchegg)


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.

von Dennis (Gast)


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?

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.