Forum: Mikrocontroller und Digitale Elektronik ATmega328: Array-Daten gehen verloren


von Wolfgang Z. (wolfzi99)


Lesenswert?

Hallo,

über die UART-Schnittstelle lese ich Daten von einem SD-Karten Modul 
zurück,
dazu benutze ich diese Funktion:

void GetSDString0(uint8_t* BUF)       // Bytes empfangen
{
        uint8_t NextChar;
  NextChar = UART0_getc();     // Warte auf das nächste Byte

  while(NextChar != '.')       // empfange Zeichen bis "." kommt
  {
    *BUF++ = NextChar;
    NextChar = UART0_getc();
  }
}

In einer Header-Datei steht:
 uint8_t RetSD[30];        // Bytes vom SD-Card Modul

Funktions-Aufruf:
 GetSDString0(RetSD);

Leider gehen die empfangenen Bytes verloren, sobald die Funktion 
verlassen wird. In RetSD steht dann nur Müll.
Meine Frage: wie kann ich auf die Array-Daten von RetSD zugreifen?

von Karl M. (Gast)


Lesenswert?

Wolfgang Z. schrieb:
> In einer Header-Datei steht:
>  uint8_t RetSD[30];        // Bytes vom SD-Card Modul
>
> Funktions-Aufruf:
>  GetSDString0(RetSD);

Frage dich bitte, ob hier ein Pointer steht und wie das überschreiben 
des Arrays abgefangen wird. Bzw. der Füllgrad mitgeteilt wird.
für mich fehlen da noch vom Type size_t Angaben über die maximale Größe 
und die Anzahl der geschriebenen Zeichen als Rückgabewert einer 
Funktion.

von Patrick (Gast)


Lesenswert?

Das
1
*BUF++ = NextChar;
 kommt mir komisch vor. Damit man es versteht, hätte ich es wie folgt 
gemacht (Wert an Adresse BUF schreiben, Adresse BUF erhöhen)

1
*BUF= NextChar;
2
BUF++;

Ansonsten die Startadresse als

1
GetSDString0(&RetSD[0]);

Übergeben.

Evtl noch die Zeichen zählen, dass du nicht über die Arraygrenzen 
schreibst.

von Wolfgang Z. (wolfzi99)


Lesenswert?

Hallo Patrick,
Danke für die schnelle Antwort.
Habe ich, wie von Dir vorgeschlagen umgesetzt, leider ohne Erfolg.
Bin für jeden Tip dankbar!!!

von Einer K. (Gast)


Lesenswert?

Patrick schrieb:
> Evtl noch die Zeichen zählen, dass du nicht über die Arraygrenzen
> schreibst.
Und noch eine nul ans Ende hängen

von Patrick (Gast)


Lesenswert?

Ich kenne die Funktion UART0_getc() nicht. Ich gehe mal davon aus, dass 
die dafür gedacht ist, nach Empfang eines Bytes aufgerufen zu werden (im 
Empfangsinterrupt zB). Wenn du die Funktion einfach in der 
while-Schleife runterratterst und diese dann am Ende nur stumpf das 
Empfangsregister ausliest, steht da freilich nur Unfug und du rauschst 
auch mit absoluter Sicherheit über die Arraygrenzen hinweg.

von Sebastian S. (amateur)


Lesenswert?

>*BUF++ = NextChar;
und
>*BUF= NextChar;
>BUF++;
sind identisch.
Würde mich nicht wundern, wenn der Compiler aus beidem das Gleiche 
macht.

Bist Du Dir sicher, dass der Datentyp "uint8_t", praktisch für alles, 
der Richtige ist? Theoretisch sollte es zwar gehen, aber...

von Patrick (Gast)


Lesenswert?

UART0_getc() liest einfach nur den von der UART-Bibliothek mitgebrachten 
Ringpuffer aus und setzt den Zeiger ein Element weiter. Im Highbyte 
liefert die Funktion eine Fehlerinformation mit. Du musst also 
auswerten, ob du überhaupt schon was empfangen hast, bevor du 
UART0_getc() aufrufst. Das ufert in deiner Funktion zu einem Polling - 
Massaker aus, oder du musst die umschreiben, dass du sie über mehrere 
Zyklen verteilen kannst.

Alles in allem schätze ich, dass du dich erstmal bisschen mit der 
Bibliothek auseinandersetzen solltest.

von Sebastian S. (amateur)


Lesenswert?

Nachtrag:

Natürlich MUSS sichergestellt sein, dass weniger (gleich) als 30 Zeichen 
vor dem Punkt kommen. Sonst wird es spannend.

Wie Patrick geschrieben hat: Was passiert, wenn kein Zeichen empfangen 
wurde, oder Du zu schnell abfragst (noch kein Zeichen da)? Ich kenne die 
verwendete Funktion auch nicht.

von Dirk B. (dirkbilland)


Lesenswert?

>In einer Header-Datei steht:
> uint8_t RetSD[30];        // Bytes vom SD-Card Modul

Wenn Du die Variablen-Definition in die Header-Datei schreibst, wird für 
jede Datei, die die Header-Datei einbindet, eine Variable gleichen 
Namens angelegt. Das erklärt den beobachteten Effekt.

Die Variablen-Definition gehört in die c-Datei und die Deklaration mit 
einem vorangestellten "extern" in die Header-Datei. Dann sollte es 
funktionieren.

von foobar (Gast)


Lesenswert?

> In einer Header-Datei steht:
> uint8_t RetSD[30];        // Bytes vom SD-Card Modul

Ich vermute, du hast mehrere RetSD.

In die .h-Datei gehört nur die Deklaration (mit einem "extern" davor), 
in einer .c-Datei dann die Definition so wie oben ohne "extern".

von Wolfgang Z. (wolfzi99)


Lesenswert?

Danke für die zahlreichen Rückmeldungen dazu.

Habe jetzt folgende Änderungen vorgenommen:
1. alle Variablen Definitionen stehen jetzt in der C-Datei
2. in der h-Datei steht: extern void GetSDString0(uint8_t* BUF);
Leider ohne Erfolg!

Hier die Funktion zum Empfang von Zeichen (Bytes):
uint8_t UART0_getc(void)    // Byte empfangen
{
  while (!(UCSR0A & (1<<RXC0)));  // warten bis Byte verfügbar
  return UDR0;      // Byte aus UDR zurückgegeben
}

Zur Kontrolle gebe ich den Array-Inhalt RetSD so über HTerm aus:

  for(i = 1; i < 25; i++)
  {
         UART0_putc(RetSD[i]);
         i++;
  }

Vielleicht liegt der Fehler in der Ausgabe der Bytes?

void UART0_putc(uint8_t xB)    // Byte senden
{
  while (!(UCSR0A & (1<<UDRE0)));  // warten bis Senden möglich
  UDR0 = xB;      // sende Byte
}

von Stefan F. (Gast)


Lesenswert?

Es könnte auch ganz trivial an einem Stack-Überlauf liegen. Ausgelöst 
durch zu viele verschachtelte Funktionsaufrufe bzw. Funktionen mit zu 
vielen lokalen Daten.

von Wolfgang Z. (wolfzi99)


Lesenswert?

Hallo Stefan,

okay, aber wie kann ich das feststellen?

von Stefan F. (Gast)


Lesenswert?

Wolfgang Z. schrieb:
> okay, aber wie kann ich das feststellen?

Es gibt zwei Zeiger, die sich aufeinander zu bewegen. Einer vom Stack 
und einer vom Heap. Wenn sie sich in der Mitte treffen, ist der Speicher 
voll.

Dort erklärt das jemand im Detail: 
https://rn-wissen.de/wiki/index.php/Speicherverbrauch_bestimmen_mit_avr-gcc
1
extern unsigned char __heap_start;
2
uint16_t momentan_frei = SP - (uint16_t) &__heap_start;

Das Ergebnis könntest du innerhalb jeder Prozedur ausgeben.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Wolfgang Z. schrieb:
> uint8_t UART0_getc(void)    // Byte empfangen
> {
>   while (!(UCSR0A & (1<<RXC0)));  // warten bis Byte verfügbar   return
> UDR0;      // Byte aus UDR zurückgegeben
> }

Ist das richtig, dass "return UDR0" im Kommentar steht, oder ist das ein 
Copy&Paste-Fehler?

Das muss so aussehen:
1
uint8_t UART0_getc(void)    // Byte empfangen
2
{
3
  while (!(UCSR0A & (1<<RXC0)));  // warten bis Byte verfügbar
4
  return UDR0;      // Byte aus UDR zurückgegeben
5
}

Hast Du hier auch alle Warnungen, die der Compiler ausgibt, hier 
mitgeteilt?

von Stefan F. (Gast)


Lesenswert?

Frank M. schrieb:
> Ist das richtig, dass "return UDR0" im Kommentar steht, oder ist das ein
> Copy&Paste-Fehler?

Der Kommentar stammt wohl von einem anderen µC Modell das nur einen UART 
hat.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Stefan ⛄ F. schrieb:
> Der Kommentar stammt wohl von einem anderen µC Modell das nur einen UART
> hat.

Häh? die UART0_getc-Funktion ist definiert als uint8_t, da hat sie auch 
gefälligst was zurückzuliefern. Tut sie aber nicht, da das 
return-Statement auskommentiert ist.

Schließlich wird auch der Rückgabewert benutzt:
1
NextChar = UART0_getc();

von Stefan F. (Gast)


Lesenswert?

Frank M. schrieb:
> die UART0_getc-Funktion ist definiert als uint8_t, da hat sie auch
> gefälligst was zurückzuliefern. Tut sie aber nicht

Da steht doch "return UDR0", nicht auskommentiert:

Wolfgang Z. schrieb:
> uint8_t UART0_getc(void)    // Byte empfangen
> {
>   while (!(UCSR0A & (1<<RXC0)));  // warten bis Byte verfügbar
>   return UDR0;      // Byte aus UDR zurückgegeben
> }

In deinem Zitat fehlt jedoch ein Zeilenumbruch. Vielleicht ein 
Darstellungsproblem im Web Browser?

von Wolfgang Z. (wolfzi99)


Lesenswert?

Hallo Frank,
aber das ist nichts auskommentiert:
uint8_t UART0_getc(void)          // Zeichen empfangen
{
  while (!(UCSR0A & (1<<RXC0)));  // warten bis Zeichen verfügbar
  return UDR0;      // Zeichen aus UDR0 zurückgegeben
}
Der Inhalt von UDR0 wird zurückgeliefert!
Der ATmega328 hat zwar nur einen UART, aber trotzdem heißt das Register 
UDRn.

von Wolfgang Z. (wolfzi99)


Lesenswert?

Hallo Stefan,

jetzt zu dem Speicherverbrauch.
Ich habe die Funktion eingesetzt und bekomme als Ergebnis eine 0.
Also kein Speicher-Überlauf?

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Stefan ⛄ F. schrieb:
> In deinem Zitat fehlt jedoch ein Zeilenumbruch.

Als Moderator kann ich Beiträge editieren. Da ist kein Zeilenumbruch 
drin. Aber so viele Leerzeichen, dass es in einer nicht-proportionalen 
Schrift so aussieht, als ob das Return-Statement darunter stände. Steht 
es aber nicht.

Deshalb fragte ich den TO, ob das ein Copy&Paste-Fehler sei.

Besser ist auch, C-Code in
1
[c]C-Code[/c]
einzubetten.

von Frank M. (ukw) (Moderator) Benutzerseite


Angehängte Dateien:

Lesenswert?

Wolfgang Z. schrieb:
> Der Inhalt von UDR0 wird zurückgeliefert!

Das sehe ich anders, siehe Screenshot. Und auch im Editor-Fenster sehe 
ich keinen Zeilenumbruch.
Bitte verwende
1
[c]C-Code[/c]

um C-Code hier zu veröffentlichen.

von Oliver S. (oliverso)


Lesenswert?

Wolfgang Z. schrieb:
> Also kein Speicher-Überlauf?

Zeig mal dein ganzes Programm. Normalerweise läuft der Speicher alleine 
durch Funktionsverschachtelungen nicht so schnell über. Heap wirst du 
auch keinen benutzen, und die 30 Byte für den Puffer sind unkritisch.

Was aber schnell blöd werden kann, ist sind Daten, bei denen der '.' 
erst nach mehr als 30 Zeichen erkannt wird. Deine Empfangsroutine 
schreibt ja fröhlich immer weiter in den Puffer, ohne Größenbegrenzung.

Oliver

von Wolfgang Z. (wolfzi99)


Lesenswert?

Hallo Frank,

vielen Dank für den Hinweis.
Da ich nur selten Fragen im Forum stelle, war mir das nicht bewusst, 
sorry!

von Wolfgang Z. (wolfzi99)


Lesenswert?

Hallo Oliver,

das mit der Zeichen-Menge habe ich vorher geprüft.
Es werden nur Datei-Namen mit .txt zurückgeliefert und
für mich sind nur die Namen ohne .txt relevant.
Die max. Dateinamen-Länge beträgt 18 Zeichen, somit ist ein 
Array-Überlauf nicht möglich.

Wolfgang

von Oliver S. (oliverso)


Lesenswert?

Wolfgang Z. schrieb:
> Die max. Dateinamen-Länge beträgt 18 Zeichen, somit ist ein
> Array-Überlauf nicht möglich.

Famous last words ;)

Oliver

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Wolfgang Z. schrieb:
> Die max. Dateinamen-Länge beträgt 18 Zeichen, somit ist ein
> Array-Überlauf nicht möglich.

Warum benutzt Du dann einen Buffer mit 30 Zeichen? Aus Angst?

von uwe (Gast)


Lesenswert?

Dachte immer SD Karten haben ein SPI interface ;-)

von Einer K. (Gast)


Lesenswert?

Wolfgang Z. schrieb:
> In RetSD steht dann nur Müll.

Warum uint8_t, wenn es sich doch um Text handelt?
Also char.
Oder?

Desweiteren vermisse ich immer noch die nul am Stringende.

So gibt es keine Information im Programm wie lang der String ist.
Neben anderem Ungemach, wundert mich nicht, dass da Mist ausgegeben 
wird, wenn kein Programmteil weiß, wie lang/groß irgendwas ist.

Wolfgang Z. schrieb:
> somit ist ein
> Array-Überlauf nicht möglich.
Is klar....

von Patrick (Gast)


Lesenswert?

Vielleicht versuchst du es mal, den Empfangsinterrupt zu nutzen? Oder 
hast du den schon irgendwo drin und der schießt dir quer? Der würde vom 
reinen Code dann ca so aussehen, zeichenzaehler muss natürlich mit 0 
initialisiert werden:
1
uint8t empfange_SDString(void) {
2
extern volatile uint8t zeichenzaehler; // Zähler für empfangene Zeichen
3
extern volatile uint8_t RetSD[30];
4
uint8t *tempzeiger; // Hilfszeiger, wo das nächste empfangene Zeichen hinsoll
5
uint8t retval;
6
7
tempzeiger = &RetSD [0] + zeichenzaehler; //Zeiger auf nächstes freies Zeichen ermitteln
8
*tempzeiger = UDR0; // Empfangsregister an freie Stelle kopieren
9
if (zeichenzaehler < 29) { // Überlauf abfangen
10
zeichenzaehler++;
11
}
12
if (*tempzeiger == '.') {  // bei Empfang Stopzeichen Stringlänge zurückgeben
13
retval = zeichenzaehler;
14
zeichenzaehler = 0; // Zeichenzähler zurücksetzen
15
}
16
else {
17
retval = 0; // Null zurückgeben, wenn kein Stopzeichen empfangen
18
}
19
return retval;
20
}

Der Code der ISR wäre dann
1
extern volatile uint8t empfangsstring[30];
2
extern volatile uint8_t RetSD[30];
3
uint8t retval;
4
5
retval = empfange_SDString();
6
if (retval ) {
7
for (uint8t i = 0; i < retval; i++) {
8
empfangsstring[i] = RetSD[i];
9
}

Alles bisschen unsauber, aber zum testen sollte es tun.

von Oliver S. (oliverso)


Lesenswert?

Patrick schrieb:
> ... maximal umständlichen Code ...

Was genau ist denn jetzt das Problem im Code des TO, das du durch deinen 
Code löst?

Oliver

von Stefan F. (Gast)


Lesenswert?

Wolfgang Z. schrieb:
> Ich habe die Funktion eingesetzt und bekomme als Ergebnis eine 0.

Das ist aber ein seltsamer Zufall. Bis du sicher, dass du die richtige 
Zahl ausgibst?

0 ist jedenfalls zu wenig. Damit kannst du keine einzige Funktion mehr 
aufrufen, denn Funktionen brauchen Stack.

von Wolfgang Z. (wolfzi99)


Lesenswert?

Ja, war am Anfang so.
Habe ich jetzt auf 20 reduziert.

von Wolfgang Z. (wolfzi99)


Lesenswert?

Hallo Uwe,

das ist keine normale SD-Karten Ansteuerung.
Ich benutze ein Modul der Firma Artekit (Italien) und das hat eine
UART-Schnittstelle.

von Wolfgang Z. (wolfzi99)


Lesenswert?

Hallo,
das Problem ist, dass das SD-Modul einen Mix aus ASCII und Bytes
zurück sendet.
Daher der Typ uint8_t, also kein normaler String, somit kann ich das 
nul-Zeichen auch nicht anfügen.

von Wolfgang Z. (wolfzi99)


Lesenswert?

Hallo an alle,

dank der vielen sehr nützlichen Hinweise, macht das Programm jetzt genau 
das, was es machen sollte.

Vielen, vielen Dank für Eure Unterstützung!

Gruß
Wolfgang

von Oliver S. (oliverso)


Lesenswert?

Und was war jetzt das Problem?

Oliver

von Wolfgang Z. (wolfzi99)


Lesenswert?

Hallo Oliver,

die Lösung war eine Kombi aus verschiedenen Fehlern.
Dieser Hinweis von foobar

foobar schrieb:
> Ich vermute, du hast mehrere RetSD.
>
> In die .h-Datei gehört nur die Deklaration (mit einem "extern" davor),
> in einer .c-Datei dann die Definition so wie oben ohne "extern".

Und der Tip von Dirk B.

Und einen Fehler den ich selbst eingebaut und zuerst nicht gefunden 
habe.

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.