Forum: Mikrocontroller und Digitale Elektronik Kann mir wer diese FOR-IF Schleife erklären bitte ?


von Chris M. (sinsor)


Lesenswert?

Es handelt sich hierbei um einen String in dem der empfangene String vom 
UART abgespeichert wird - sprich vom PC.

Aber ich verstehe einfach nicht wofür diese Schleife genau dient.

Viel dank im Voraus

Auszug aus der While Schleife der main.c um die es geht!
1
for(k=0;k<strlen(stringReceived);k++)
2
    {
3
      if(stringReceived[k] == 0x0A)
4
      {
5
        k--;
6
        stringReceived[k] = '\0';
7
        k=0;
8
          
9
      }
10
    }

Auszug der interrupt routine
1
interrupt isr()
2
{
3
  
4
  GIE = 0;
5
  PEIE = 0;
6
7
  if(RCIF==1)
8
  {
9
  stringReceived[k]=RCREG;
10
  k++;
11
  }
12
13
  PEIE = 1;
14
  GIE = 1;
15
16
}

von ... (Gast)


Lesenswert?

Sieht aus wie eine Zeilenendeerkennung. Die Schleife schmeißt LF und das 
Zeichen davor (CR?) aus dem String und beendet ihn.

von Loonix (Gast)


Lesenswert?

... schrieb:
> Die Schleife schmeißt LF und das
> Zeichen davor (CR?) aus dem String und beendet ihn.

Nur das Zeichen vor dem LF wird mit Null überschrieben. In der ISR 
werden empfangene Daten in einen Buffer stringReceived[] geschrieben. 
Wenn eines dieser Zeichen den Wert 0x0A aufweist wird das Zeichen davor 
auf den ASCII-Code NUL (=0x00) gesetzt.
Dafür muss die Schleife aber schneller aufgerufen und abgearbeitet 
werden als Interrupts auftreten, sonst kann es passieren dass nicht 
jedes empfangene Byte dem Vergleich mit 0x0A zugeführt wird.

von Michael U. (amiga)


Lesenswert?

Hallo,

es wird in main ja offensichtlich der komplette empfangene String 
durchsucht, bis 0x0A auftaucht und davor ein \0 als Stringende gesetzt.

Die ISR packt nur jedes empfamgene Zeichen in den Puffer und erhöht den 
Zeiger.

Woran erkannt wird, wann alle Zeichen vollständig empfangen wurden, ist 
hier nicht ersichtlich, genauso, wie der Aufrufzeitpunkt der 
Ersetzroutine nicht einzuordnen ist, ohne die komplette 
Empfangsgeschichte zu sehen.

Gruß aus Berlin
Michael

von (prx) A. K. (prx)


Lesenswert?

... schrieb:

> Sieht aus wie eine Zeilenendeerkennung. Die Schleife schmeißt LF und das
> Zeichen davor (CR?) aus dem String

Soweit ja.

> und beendet ihn.

Das ist hieraus nicht ersichtlich. Da fehlt offenbar wesentlicher Code, 
denn k=0 beendet die Schleife nicht.

von (prx) A. K. (prx)


Lesenswert?

Die ISR ist etwas unsauber, denn sie riskiert einen buffer overrun.

von Detlev T. (detlevt)


Lesenswert?

"Etwas" unsauber ist gut. Was passiert denn, wenn die ISR direkt vor dem 
"k=0" in der Hauptschleife aufgerufen wird? Offenbar soll doch 
stringReceived[] nach einem CRLF als String weiter verarbeitet werden 
und man verlässt sich darauf, dass ein String-Terminator (\0) da ist. 
Das wird dann aber nicht der Fall sein, weil dieser dann mit dem gerade 
neu empfangenen Zeichen überschrieben wurde.

Außerdem kann der Inhalt noch während der Auswertung in der ISR schon 
wieder überschrieben werden. Das ist auch nicht gut.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Detlev T. schrieb:
> "Etwas" unsauber ist gut.

Der Autor des Codes legt an gewissen Stellen einige .. ähem... 
"Unsicherheiten" an den Tag.

1. Er weiß nicht, dass '\n' statt 0x0A lesbarer im Code ist.

2. Er weiß nicht, dass das Zeichen unmittelbar vor dem '\n' ein '\r' ist
   und nullt daher "vorsichtshalber" ein beliebiges Zeichen davor.

3. Als Bedingung im for-Statement steht ein strlen-Funktionsaufruf,
   d.h. er berechnet für jeden Schleifendurchgang die Zeichenkettenlänge
   neu. Das kostet jede Menge Zeit...

4. Steht an der Stelle k == 0 ein '\n', wird an der Stelle
   StringReceived[-1] ein '\0' geschrieben. Damit wird eine beliebige
   Variable im Speicher "gepatched".

5. Nach dem Speichern der '\0' sollte die Schleife mit 'break' verlassen
   werden. Stattdessen wird k auf 0 gesetzt und der ganze String
   "vorsichtshalber" nochmal durchgescannt... wieder mit zig strlen-
   Aufrufen...

Alles in allem: Nicht benutzen, die Schleife ist Quark.

von Peter D. (peda)


Lesenswert?

Abgesehen davon, daß strlen viel zu umständlich ist, es funktioniert 
auch nur dann, wenn vorher der gesamte stringReceived mit memset 
ausgenullt wurde.
Da ja eh die einkommenden Zeichen gezählt werden, ist es auch völlig 
unnnötig.

Man könnte ganz profan im Interupt jedes Byte auf '/n' oder '/a' testen, 
durch '/0' ersetzen und ein Flag setzen.
Das Main guckt dann nur nach, ob das Flag gesetzt ist und erst dann 
schnappt es sich den Puffer.
Diese unsaubere for-Schleife entfällt dann komplett.


Peter

von Alfred (Gast)


Lesenswert?

Ja. Ist scheiße. Ich würde die Tauscherei in der Service-Routine machen.

Und:
for(k=0;k<strlen(stringReceived);k++)
    {
      if(stringReceived[k] == 0x0A)
      {
        k--;
        stringReceived[k] = '\0';
        k=0;

      }
    }

Ist auch Scheiße. Funktioniert zwar, aber die Schleife wird zweimal 
durchlaufen bzw. solange immer wieder von vorn bis keine 0x0As mehr drin 
sind. Zeitvergeudung.

von KLez (Gast)


Lesenswert?

Die ganze Schleife ist, ehrlichgesagt, mieß programmiert. Man könnte 
alle durch "if" gefundenen vorkommen bereits beim ersten Schleifen 
durchlauf ersetzen lassen. Durch k=0 wird die Schleife aber immer wieder 
durchlaufen, solange "if" auf "true" geht.

Wesentlich besser bei gleichem Effekt:
1
for(k=0;k<strlen(stringReceived);k++) {
2
  if(stringReceived[k] == 0x0A) {
3
    stringReceived[k-1] = '\0';
4
  }
5
}

Davon aber abgesehen frage ich mich, warum die Zeichenkette erst 
komplett eingelesen und anschließend geprüft wird?! Whatever... Ohne den 
ganzen Quelltext zu sehen um auch den Kontext zu verstehen, ist das ein 
Stochern im dunkeln.

von KLez (Gast)


Lesenswert?

Noch was: Bleibt zu hoffen, dass bei k=0 nie ein \n steht... Siehe auch 
Punkt 4 von Frank M.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

KLez schrieb:
> Wesentlich besser bei gleichem Effekt:
>
>
1
for(k=0;k<strlen(stringReceived);k++) {
2
>   if(stringReceived[k] == 0x0A) {
3
>     stringReceived[k-1] = '\0';
4
>   }
5
> }

Der strlen-Aufruf ist hyperfluid. Ausserdem fehlt da immer noch das 
break. Vorausgesetzt, dass ab dem '\r' (das Zeichen vor dem '\n') 
genullt werden soll, würde ich es so machen:
1
for (k = 0; stringReceived[k]; k++)
2
{
3
    if (stringReceived[k] == '\r')
4
    {
5
        stringReceived[k] = '\0';
6
        break;
7
    }
8
}

Einen Ticken effizienter könnte es noch mit einem Pointer gehen... aber 
das kommt auf den Compiler an.

> Davon aber abgesehen frage ich mich, warum die Zeichenkette erst
> komplett eingelesen und anschließend geprüft wird?! Whatever... Ohne den
> ganzen Quelltext zu sehen um auch den Kontext zu verstehen, ist das ein
> Stochern im dunkeln.

Das kann schon manchmal sinnvoll sein, wenn man wiederverwendbaren Code 
benutzen will oder die Codierung auf verschiedenen Software-Schichten 
abläuft. Stell Dir ein simples Schichtenmodell vor:

   1. Applikation (hier: simpler Kommando-Interpreter)
   2. Sicherungsschicht (hier: sorgt für saubere Trennung der Kommandos)
   3. Übertragungsschicht (hier UART)

Sicht PC:
  Die Applikationsschicht versendet Kommandos (beliebige Strings)
  Die Sicherungsschicht trennt die Strings durch CRLF ("\r\n")
  Die Übertragungsschicht pustet den String raus

Sicht µC:
  Die Übertragungsschicht liest den String ein
  Die Sicherungsschicht entfernt den Trenner (CRLF) wieder
  Die Applikationsschicht interpretiert die Kommandos

Deshalb mache ich so etwas auch in verschiedenen Modulen/Funktionen. Das 
ist auch kein Problem, wenn man effizient programiert - und NICHT 
strlen() verwendet ;-)

Die Übertragungsschicht schickt einfach "dumm" irgendwas durch die 
Gegend. Daher kann/darf sie die Trenner nicht entfernen.

von KLez (Gast)


Lesenswert?

@FrankM: Das break habe ich bewusst nicht reingesetzt, weil Du damit 
nach dem ersten Fund die Schleife verlässt. Dadurch werden nicht alle 
eventuellen vorkommen im String ersetzt.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

KLez schrieb:
> @FrankM: Das break habe ich bewusst nicht reingesetzt, weil Du damit
> nach dem ersten Fund die Schleife verlässt. Dadurch werden nicht alle
> eventuellen vorkommen im String ersetzt.

Es ist Quatsch, mehrere '\0' da reinzusetzen. Sobald du den String 
kappst, indem Du das erste '\0' schreibst, kommst Du später an den Rest 
nicht mehr so ohne viele Klimmzüge dran. Nein, da geht es um eine Zeile, 
nichts weiter.

Ausserdem zeigt mit das Setzen von
1
k=0;
 nach dem Kappen des Strings (siehe Source im ersten Posting), dass der 
Autor niemals(!) mehrere NUL-Zeichen schreiben wollte. Deshalb geht er 
brav zurück wieder an den Anfang des Strings. Mit strlen() wird er da 
auch niemals wieder über sein eigens gesetztes '\0' hinauskommen! :-)

von KLez (Gast)


Lesenswert?

Auch wieder wahr.

von Kai S. (zigzeg)


Lesenswert?

KLez schrieb:
> @FrankM: Das break habe ich bewusst nicht reingesetzt, weil Du damit
> nach dem ersten Fund die Schleife verlässt. Dadurch werden nicht alle
> eventuellen vorkommen im String ersetzt.

In Deiner Version glaube ich auch nicht, denn sobald das \r durch 0 
ueberschrieben wird ist der string kuerzer (strlen gibt kleineren Wert 
zurueck), und die Schleife wird verlassen.
Eine Moeglichkeit dies zu umgehen waere es, die Laenge des String 
zunaechst einer Variablen zuzuweisen.

von Chris M. (sinsor)


Lesenswert?

Wow, erstmal danke für die vielen Antworten, leider steige ich da jetzt 
wirklich garnicht mehr durch xD

Um es mal auf den Punkt zu bringen was ich machen möchte:
Ich möchte einfach einen String der über UART empfangen wird (per 
interrupt) in eine Variable speichern. Ich habe schon wirklich tausende 
von Beiträgen durchsucht aber ich finde immer nur was für für 
Atmels//avrs.

Vielen vielen Dank

von Johannes F. (Gast)


Lesenswert?

Chris Meier schrieb:
> Ich möchte einfach einen String der über UART empfangen wird (per
> interrupt) in eine Variable speichern.

Also du willst eine als String vorliegende Zahl in Dezimalform in eine 
Variable überführen?
Dann schau mal nach Funktionen wie atoi und sscanf. Beispiel:
1
int ziel_var;
2
sscanf(stringReceived, "%d", &ziel_var);

Johannes

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Johannes F. schrieb:
>
1
> int ziel_var;
2
> sscanf(stringReceived, "%d", &ziel_var);
3
>

Hm, sscanf für so etwas simples ist aber wie eine Kanone auf Spatzen.
Da bevorzuge ich die einfachere Lösung:
1
ziel_var = atoi (stringReceived);

von Chris M. (sinsor)


Lesenswert?

Also ich möchte bei mir zB den String "getTemp" empfangen, dieser soll 
dann in eine variable geschrieben werden, so das ich ihn später zb so 
abfragen kann:
1
if(strcmp(stringReceived,"getTemp\0")==0)
2
{
3
}

von (prx) A. K. (prx)


Lesenswert?

Alternative zu atoi wäre strtol/strtoul, weil da nicht wie bei atoi 
bloss Mist-rein-Mist-raus gilt.

von Detlev T. (detlevt)


Lesenswert?

Chris Meier schrieb:
> aber ich finde immer nur was für für
> Atmels//avrs.

Die Register mögen anders heißen (z.B. UDR statt RCREG), die 
Funktionalität ist doch aber sehr ähnlich. Da dürfte die Adaption der 
ISR zumindest für den Empfang nicht so schwer sein. Außerhalb ist das 
dann nicht mehr Controller-spezifisch.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

A. K. schrieb:
> Alternative zu atoi wäre strtol/strtoul, weil da nicht wie bei atoi
> bloss Mist-rein-Mist-raus gilt.

Das kann aber durchaus auch praktisch sein, wenn man weiß, wie atoi() 
funktioniert :-)

von Chris M. (sinsor)


Lesenswert?

Ok ich glaube irgendwie reden wir aneinander vorbei, das Problem was ich 
habe fängt wirklich schon von Grund aus an das ich nicht genau weiß wie 
die variable korrekt auslese bzw danach weiter verarbeiten kann !

hier mal mein programm zum besseren Verständnis
1
void uart_init(void);
2
void delay_1s(char s);
3
unsigned char zeichen[15];
4
unsigned char Buffer[15];
5
int k;
6
7
void main(void)
8
{
9
  
10
  ADCON1 = 0xCE;  
11
  ADCON0 = 0x41;      
12
  
13
  RCIE = 1;
14
  PEIE = 1;
15
  GIE = 1;
16
17
  uart_init();
18
  lcd_init();
19
  lcd_goto(0);
20
  lcd_clear();
21
  lcd_puts("Temperature");
22
  lcd_goto(0x40);  
23
  lcd_puts("Measuring");
24
  delay_1s(1);
25
26
  while(1)
27
  {
28
  lcd_clear();
29
30
  lcd_puts("1");
31
  delay_1s(1);
32
  lcd_goto(0);
33
  
34
  lcd_puts("2");
35
  delay_1s(1);
36
  lcd_goto(0);
37
38
    if(strcmp(zeichen,"getTemp\0")==0)
39
    {
40
      sprintf(Buffer,"YEY");
41
      lcd_puts(Buffer);
42
    }
43
  }
44
45
46
47
48
}
49
50
// Interrupt Rountine
51
52
53
interrupt isr()
54
{
55
  
56
  GIE = 0;
57
  PEIE = 0;
58
  if(RCIF==1)
59
  {
60
  RCREG=zeichen[k];
61
  }
62
  
63
  PEIE = 1;
64
  GIE = 1;
65
66
}
67
68
void uart_init(void)
69
{
70
  TRISC = 255;
71
72
  TXSTA = 0x24; //Sender
73
  RCSTA = 0x90; //Empfänger
74
75
  SPBRG = 12;
76
}

von Johannes F. (Gast)


Lesenswert?

Chris Meier schrieb:
> die variable korrekt auslese bzw danach weiter verarbeiten kann !

Welche Variable?

Chris Meier schrieb:
> sprintf(Buffer,"YEY");

Das ergibt keinen Sinn. Was soll "YEY" bedeuten?
Wenn du eine Zahl aus einer Variablen in einen String konvertieren 
willst:
1
sprintf(Buffer, "%d", quell_var);

Siehe 
http://openbook.galileocomputing.de/c_von_a_bis_z/016_c_ein_ausgabe_funktionen_023.htm

Johannes

von Chris M. (sinsor)


Lesenswert?

Die Variable die ich per UART bekommen habe sprich "zeichen" und das 
"YEY" war einfach nur um es erst einmal auszuprobieren :)

von Karl H. (kbuchegg)


Lesenswert?

Chris Meier schrieb:
> Ok ich glaube irgendwie reden wir aneinander vorbei, das Problem was ich
> habe fängt wirklich schon von Grund aus an

Da hast du nicht ganz unrecht.
Das Problem, dass du hast, ist in erster Linie, dass du in der 
abgeschlossenen Welt eines µC C-programmieren lernen willst, ohne dich 
auf einem PC (mit wesentlich besseren Debug-Möglichkeite) mit einem 
vernünftigen Lehrbuch erst mal durch die ersten Kapitel 'gelernt' zu 
haben




> interrupt isr()
> {
>
>   GIE = 0;
>   PEIE = 0;
>   if(RCIF==1)
>   {
>   RCREG=zeichen[k];
>   }
>
>   PEIE = 1;
>   GIE = 1;
>
> }


Das erste Problem beginnt bereits hier.
Die INterrupt Routine sammelt Zeichen. Das ist soweit auch in Ordnung. 
Aber wenn du Zeilenweise übertragen willst (was grundsätzlich immer eine 
Gute Idee ist), dann solltest du bereits hier die Erkennung des 
Zeilendes einbauen und mittels einer globalen Variablen dem Rest des 
Codes mitteilen: "Ich habe eine komplette Zeile zusammengesammelt".

Das wäre erst mal eine vernünftige Vorgehensweise.

(Mal ganz davon abgesehen, dass die Zuweisung in die falsche Richtung 
geht: Du willst dir ja das Zeichen aus RCREG abholen und in zeichen 
sammeln. Und auch k erhöhen wäre eine extrem gute Idee. Anstelle von k 
sollte man die Variable anders benennen. k ist zu allgemein. Diese 
Variable erfüllt einen Zweck und dieser Zweck sollte sich auch im 
Variablennamen niederschlagen)



Denn dann braucht hier nicht dauern

    if(strcmp(zeichen,"getTemp\0")==0)

ein unvollständiger C-String (deine Interrupt Routine erzeugt nämlich 
keinen gültigen C-String, weil die 0-Terminierung fehlt) mit etwas 
verglichen zu werden, was so schon mal in die Hose gehen wird. Ausserdem 
muss man in ein String-Literal nicht selber eine 0-Terminierung 
einbauen, das macht der Compiler schon ganz von alleine.

Und die Variablen, mit denen eine Interrupt Routine mit dem rest der 
Welt kommuniziert, müssen zwingend als 'volatile' markiert werden, damit 
der Compiler sich mit den Optimierungen auf diesen Variablen etwas 
zurückhält.

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.